fix(sessions): provide plain-text extension theme

This commit is contained in:
Federico Jaramillo Martinez
2026-07-20 14:01:47 +02:00
parent b48b147b5b
commit a884773357
5 changed files with 188 additions and 5 deletions
@@ -48,6 +48,12 @@ describe("PiSessionService lifecycle, listing, and reload", () => {
it("starts sessions through an injected runtime creator", async () => {
const hub = new CapturingSessionEventHub();
const fake = fakeRuntime();
let sessionStartText: string | undefined;
const bindExtensions = fake.session.bindExtensions.bind(fake.session);
fake.session.bindExtensions = (bindings) => {
sessionStartText = bindings.uiContext?.theme.fg("accent", "session started");
return bindExtensions(bindings);
};
let createCalls = 0;
let runtimeAgentDir: string | undefined;
const createAgentRuntime: RuntimeCreator = async (_createRuntime, options) => {
@@ -69,6 +75,7 @@ describe("PiSessionService lifecycle, listing, and reload", () => {
expect(createCalls).toBe(1);
expect(runtimeAgentDir).toBe(TEST_AGENT_DIR);
expect(fake.calls.bindExtensions).toHaveLength(1);
expect(sessionStartText).toBe("session started");
expect(session).toMatchObject({ id: "session-1", cwd: "/workspace", messageCount: 0 });
expect(service.activeCount()).toBe(1);
expect(hub.globalEvents.some((event) => event.type === "status.update" && event.status.sessionId === "session-1")).toBe(true);
@@ -293,6 +300,12 @@ describe("PiSessionService lifecycle, listing, and reload", () => {
const hub = new CapturingSessionEventHub();
const fake = fakeRuntime("session-1");
const replacement = fakeRuntime("session-2");
let replacementSessionStartText: string | undefined;
const bindReplacementExtensions = replacement.session.bindExtensions.bind(replacement.session);
replacement.session.bindExtensions = (bindings) => {
replacementSessionStartText = bindings.uiContext?.theme.fg("success", "replacement started");
return bindReplacementExtensions(bindings);
};
let rebindSession: ((session: PiAgentSession) => Promise<void>) | undefined;
fake.runtime.setRebindSession = (callback) => { rebindSession = callback; };
const service = new PiSessionService(hub, {
@@ -309,6 +322,7 @@ describe("PiSessionService lifecycle, listing, and reload", () => {
expect(fake.calls.bindExtensions).toHaveLength(1);
expect(replacement.calls.bindExtensions).toHaveLength(1);
expect(replacementSessionStartText).toBe("replacement started");
expect(service.activeCount()).toBe(1);
expect(await service.status("session-2")).toMatchObject({ sessionId: "session-2" });
@@ -440,7 +454,7 @@ describe("PiSessionService lifecycle, listing, and reload", () => {
await service.dispose();
});
it("commits Pi /reload only after replacement session_start notifications are bound", async () => {
it("commits Pi /reload only after replacement session_start notifications use the plain-text theme", async () => {
const hub = new CapturingSessionEventHub();
const store = notificationStore();
const fake = fakeRuntime("runtime-reload-notifications");
@@ -459,7 +473,8 @@ describe("PiSessionService lifecycle, listing, and reload", () => {
fake.session.reload = async (options) => {
oldNotify("shutdown notification", "info");
await options?.beforeSessionStart?.();
currentNotify(fake)("replacement startup", "error");
const replacementStartup = fake.session.extensionRunner.getUIContext().theme.fg("error", "replacement startup");
currentNotify(fake)(replacementStartup, "error");
};
await expect(service.runCommand(sessionRef("runtime-reload-notifications"), "/reload")).resolves.toMatchObject({ type: "done" });