fix(coding-agent): emit session name changes to extensions

This commit is contained in:
Alexey Zaytsev
2026-06-30 00:38:06 -05:00
parent 927e98068c
commit 726a9c526c
6 changed files with 49 additions and 1 deletions
+14
View File
@@ -322,6 +322,9 @@ user sends another prompt ◄─────────────────
├─► session_start { reason: "fork", previousSessionFile }
└─► resources_discover { reason: "startup" }
/name or pi.setSessionName()
└─► session_info_changed
/compact or auto-compaction
├─► session_before_compact (can cancel or customize)
└─► session_compact
@@ -395,6 +398,17 @@ pi.on("session_start", async (event, ctx) => {
});
```
#### session_info_changed
Fired when the current session display name is set via `/name`, RPC, or `pi.setSessionName()`.
```typescript
pi.on("session_info_changed", async (event, ctx) => {
// event.name - current normalized name, or undefined if cleared
ctx.ui.notify(`Session renamed: ${event.name ?? "(none)"}`, "info");
});
```
#### session_before_switch
Fired before starting a new session (`/new`) or switching sessions (`/resume`).
@@ -2683,7 +2683,9 @@ export class AgentSession {
*/
setSessionName(name: string): void {
this.sessionManager.appendSessionInfo(name);
this._emit({ type: "session_info_changed", name: this.sessionManager.getSessionName() });
const event = { type: "session_info_changed", name: this.sessionManager.getSessionName() } as const;
this._emit(event);
void this._extensionRunner.emit(event);
}
// =========================================================================
@@ -128,6 +128,7 @@ export type {
SessionBeforeTreeResult,
SessionCompactEvent,
SessionEvent,
SessionInfoChangedEvent,
SessionShutdownEvent,
// Events - Session
SessionStartEvent,
@@ -551,6 +551,13 @@ export interface SessionStartEvent {
previousSessionFile?: string;
}
/** Fired when the current session metadata changes. */
export interface SessionInfoChangedEvent {
type: "session_info_changed";
/** Current normalized session name. Undefined when the name is cleared. */
name: string | undefined;
}
/** Fired before switching to another session (can be cancelled) */
export interface SessionBeforeSwitchEvent {
type: "session_before_switch";
@@ -630,6 +637,7 @@ export interface SessionTreeEvent {
export type SessionEvent =
| SessionStartEvent
| SessionInfoChangedEvent
| SessionBeforeSwitchEvent
| SessionBeforeForkEvent
| SessionBeforeCompactEvent
@@ -1133,6 +1141,7 @@ export interface ExtensionAPI {
on(event: "project_trust", handler: ProjectTrustHandler): void;
on(event: "resources_discover", handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>): void;
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
on(event: "session_info_changed", handler: ExtensionHandler<SessionInfoChangedEvent>): void;
on(
event: "session_before_switch",
handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>,
+1
View File
@@ -122,6 +122,7 @@ export type {
SessionBeforeSwitchEvent,
SessionBeforeTreeEvent,
SessionCompactEvent,
SessionInfoChangedEvent,
SessionShutdownEvent,
SessionStartEvent,
SessionTreeEvent,
@@ -37,4 +37,25 @@ describe("regression #3686: session name changes emit an event", () => {
expect(harness.sessionManager.getSessionName()).toBe("from extension");
expect(harness.eventsOfType("session_info_changed").map((event) => event.name)).toEqual(["from extension"]);
});
it("emits session_info_changed to extensions", async () => {
let api: ExtensionAPI | undefined;
const events: Array<{ name: string | undefined }> = [];
const harness = await createHarness({
extensionFactories: [
(pi) => {
api = pi;
pi.on("session_info_changed", (event) => {
events.push({ name: event.name });
});
},
],
});
harnesses.push(harness);
api?.setSessionName("first");
harness.session.setSessionName("second");
expect(events).toEqual([{ name: "first" }, { name: "second" }]);
});
});