diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 33ed9b010..4596a3dea 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -8,6 +8,7 @@ ### Fixed +- Fixed extension `pi.setSessionName()` updates to refresh the interactive terminal title immediately ([#3686](https://github.com/badlogic/pi-mono/issues/3686)) - Fixed `/tree` cancellation via `session_before_tree` leaving the session stuck in compaction state ([#3688](https://github.com/badlogic/pi-mono/issues/3688)) - Fixed Escape interrupt handling when extensions hide the built-in working loader row ([#3674](https://github.com/badlogic/pi-mono/issues/3674)) - Fixed coding-agent test expectations for current default models and missing-auth guidance. diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 9f9c4e6ed..7a26deb44 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -119,6 +119,7 @@ export type AgentSessionEvent = followUp: readonly string[]; } | { type: "compaction_start"; reason: "manual" | "threshold" | "overflow" } + | { type: "session_info_changed"; name: string | undefined } | { type: "compaction_end"; reason: "manual" | "threshold" | "overflow"; @@ -2166,7 +2167,7 @@ export class AgentSession { this.sessionManager.appendCustomEntry(customType, data); }, setSessionName: (name) => { - this.sessionManager.appendSessionInfo(name); + this.setSessionName(name); }, getSessionName: () => { return this.sessionManager.getSessionName(); @@ -2648,6 +2649,7 @@ export class AgentSession { */ setSessionName(name: string): void { this.sessionManager.appendSessionInfo(name); + this._emit({ type: "session_info_changed", name: this.sessionManager.getSessionName() }); } // ========================================================================= diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index f5c755f77..2436c7dc1 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -2698,6 +2698,12 @@ export class InteractiveMode { this.ui.requestRender(); break; + case "session_info_changed": + this.updateTerminalTitle(); + this.footer.invalidate(); + this.ui.requestRender(); + break; + case "message_start": if (event.message.role === "custom") { this.addMessageToChat(event.message); @@ -5039,8 +5045,7 @@ export class InteractiveMode { return; } - this.sessionManager.appendSessionInfo(name); - this.updateTerminalTitle(); + this.session.setSessionName(name); this.chatContainer.addChild(new Spacer(1)); this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0)); this.ui.requestRender(); diff --git a/packages/coding-agent/test/suite/regressions/3686-session-name-event.test.ts b/packages/coding-agent/test/suite/regressions/3686-session-name-event.test.ts new file mode 100644 index 000000000..3614c09cc --- /dev/null +++ b/packages/coding-agent/test/suite/regressions/3686-session-name-event.test.ts @@ -0,0 +1,40 @@ +import { afterEach, describe, expect, it } from "vitest"; +import type { ExtensionAPI } from "../../../src/index.js"; +import { createHarness, type Harness } from "../harness.js"; + +describe("regression #3686: session name changes emit an event", () => { + const harnesses: Harness[] = []; + + afterEach(() => { + while (harnesses.length > 0) { + harnesses.pop()?.cleanup(); + } + }); + + it("emits session_info_changed when AgentSession.setSessionName is called", async () => { + const harness = await createHarness(); + harnesses.push(harness); + + harness.session.setSessionName("hello world"); + + expect(harness.sessionManager.getSessionName()).toBe("hello world"); + expect(harness.eventsOfType("session_info_changed").map((event) => event.name)).toEqual(["hello world"]); + }); + + it("emits session_info_changed when an extension calls pi.setSessionName", async () => { + let api: ExtensionAPI | undefined; + const harness = await createHarness({ + extensionFactories: [ + (pi) => { + api = pi; + }, + ], + }); + harnesses.push(harness); + + api?.setSessionName("from extension"); + + expect(harness.sessionManager.getSessionName()).toBe("from extension"); + expect(harness.eventsOfType("session_info_changed").map((event) => event.name)).toEqual(["from extension"]); + }); +});