From 11b7ba78464e56576dc3719b7b06eba3eea418b2 Mon Sep 17 00:00:00 2001 From: can1357 Date: Mon, 13 Apr 2026 06:22:37 +0200 Subject: [PATCH] fix(coding-agent): respected session title source when formatting terminal titles - Propagated session title source through terminal-title update calls so auto and user names are handled consistently across controllers. - Suppressed auto-generated names in status-line segments and completion messages by falling back to cwd-based titles. - Updated title formatter tests to verify auto-generated and user-specified session titles produce different terminal labels. --- .../modes/components/status-line/segments.ts | 3 ++- .../modes/controllers/command-controller.ts | 8 +++++-- .../src/modes/controllers/event-controller.ts | 2 +- .../controllers/extension-ui-controller.ts | 24 +++++++++++++++---- .../src/modes/controllers/input-controller.ts | 1 + .../modes/controllers/selector-controller.ts | 3 ++- .../src/modes/interactive-mode.ts | 6 ++++- .../coding-agent/src/utils/title-generator.ts | 17 +++++++++---- .../coding-agent/test/session-color.test.ts | 11 +++++++++ 9 files changed, 61 insertions(+), 14 deletions(-) diff --git a/packages/coding-agent/src/modes/components/status-line/segments.ts b/packages/coding-agent/src/modes/components/status-line/segments.ts index 44f328e5c..0d7317787 100644 --- a/packages/coding-agent/src/modes/components/status-line/segments.ts +++ b/packages/coding-agent/src/modes/components/status-line/segments.ts @@ -359,7 +359,8 @@ const cacheWriteSegment: StatusLineSegment = { const sessionNameSegment: StatusLineSegment = { id: "session_name", render(ctx) { - const name = ctx.session.sessionManager?.getSessionName(); + const sessionManager = ctx.session.sessionManager; + const name = sessionManager?.titleSource === "auto" ? undefined : sessionManager?.getSessionName(); if (!name) return { content: "", visible: false }; const ansi = getSessionAccentAnsi(getSessionAccentHex(name)) ?? theme.getFgAnsi("accent"); diff --git a/packages/coding-agent/src/modes/controllers/command-controller.ts b/packages/coding-agent/src/modes/controllers/command-controller.ts index e4d90eaef..06c3ed191 100644 --- a/packages/coding-agent/src/modes/controllers/command-controller.ts +++ b/packages/coding-agent/src/modes/controllers/command-controller.ts @@ -588,7 +588,11 @@ export class CommandController { } await this.ctx.session.newSession(); this.ctx.resetObserverRegistry(); - setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd()); + setSessionTerminalTitle( + this.ctx.sessionManager.getSessionName(), + this.ctx.sessionManager.getCwd(), + this.ctx.sessionManager.titleSource, + ); this.ctx.statusLine.invalidate(); this.ctx.statusLine.setSessionStartTime(Date.now()); @@ -695,7 +699,7 @@ export class CommandController { return; } const name = this.ctx.sessionManager.getSessionName()!; - setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd()); + setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd(), this.ctx.sessionManager.titleSource); this.ctx.statusLine.invalidate(); this.ctx.updateEditorBorderColor(); this.ctx.showStatus(`Session renamed to "${name}".`); diff --git a/packages/coding-agent/src/modes/controllers/event-controller.ts b/packages/coding-agent/src/modes/controllers/event-controller.ts index f7f34c33d..eddf93eda 100644 --- a/packages/coding-agent/src/modes/controllers/event-controller.ts +++ b/packages/coding-agent/src/modes/controllers/event-controller.ts @@ -638,7 +638,7 @@ export class EventController { if (this.ctx.isBackgrounded === false) return; const notify = settings.get("completion.notify"); if (notify === "off") return; - const title = this.ctx.sessionManager.getSessionName(); + const title = this.ctx.sessionManager.titleSource === "auto" ? undefined : this.ctx.sessionManager.getSessionName(); const message = title ? `${title}: Complete` : "Complete"; TERMINAL.sendNotification(message); } diff --git a/packages/coding-agent/src/modes/controllers/extension-ui-controller.ts b/packages/coding-agent/src/modes/controllers/extension-ui-controller.ts index 9ca656dc1..911377aef 100644 --- a/packages/coding-agent/src/modes/controllers/extension-ui-controller.ts +++ b/packages/coding-agent/src/modes/controllers/extension-ui-controller.ts @@ -123,7 +123,11 @@ export class ExtensionUiController { getSessionName: () => this.ctx.sessionManager.getSessionName(), setSessionName: async name => { await this.ctx.sessionManager.setSessionName(name, "user"); - setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd()); + setSessionTerminalTitle( + this.ctx.sessionManager.getSessionName(), + this.ctx.sessionManager.getCwd(), + this.ctx.sessionManager.titleSource, + ); }, }; const contextActions: ExtensionContextActions = { @@ -169,7 +173,11 @@ export class ExtensionUiController { if (!success) { return { cancelled: true }; } - setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd()); + setSessionTerminalTitle( + this.ctx.sessionManager.getSessionName(), + this.ctx.sessionManager.getCwd(), + this.ctx.sessionManager.titleSource, + ); // Call setup callback if provided if (options?.setup) { @@ -247,7 +255,11 @@ export class ExtensionUiController { if (!result) { return { cancelled: true }; } - setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd()); + setSessionTerminalTitle( + this.ctx.sessionManager.getSessionName(), + this.ctx.sessionManager.getCwd(), + this.ctx.sessionManager.titleSource, + ); this.ctx.chatContainer.clear(); this.ctx.renderInitialMessages(); await this.ctx.reloadTodos(); @@ -390,7 +402,11 @@ export class ExtensionUiController { getSessionName: () => this.ctx.sessionManager.getSessionName(), setSessionName: async name => { await this.ctx.sessionManager.setSessionName(name, "user"); - setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd()); + setSessionTerminalTitle( + this.ctx.sessionManager.getSessionName(), + this.ctx.sessionManager.getCwd(), + this.ctx.sessionManager.titleSource, + ); }, }; const contextActions: ExtensionContextActions = { diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index 3faec3978..595a745da 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -347,6 +347,7 @@ export class InputController { setSessionTerminalTitle( this.ctx.sessionManager.getSessionName()!, this.ctx.sessionManager.getCwd(), + this.ctx.sessionManager.titleSource, ); this.ctx.updateEditorBorderColor(); } diff --git a/packages/coding-agent/src/modes/controllers/selector-controller.ts b/packages/coding-agent/src/modes/controllers/selector-controller.ts index 2b60d1dad..4a518d539 100644 --- a/packages/coding-agent/src/modes/controllers/selector-controller.ts +++ b/packages/coding-agent/src/modes/controllers/selector-controller.ts @@ -747,8 +747,9 @@ export class SelectorController { const sessionManager = this.ctx.sessionManager as { getSessionName?: () => string | undefined; getCwd: () => string; + titleSource?: "auto" | "user" | undefined; }; - setSessionTerminalTitle(sessionManager.getSessionName?.(), sessionManager.getCwd()); + setSessionTerminalTitle(sessionManager.getSessionName?.(), sessionManager.getCwd(), sessionManager.titleSource); } async #detachActiveSessionBeforeDeletion(sessionPath: string): Promise { diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index a97159093..f7a086c33 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -393,7 +393,11 @@ export class InteractiveMode implements InteractiveModeContext { // Start the UI this.ui.start(); pushTerminalTitle(); - setSessionTerminalTitle(this.sessionManager.getSessionName(), this.sessionManager.getCwd()); + setSessionTerminalTitle( + this.sessionManager.getSessionName(), + this.sessionManager.getCwd(), + this.sessionManager.titleSource, + ); this.updateEditorBorderColor(); this.#syncEditorMaxHeight(); this.isInitialized = true; diff --git a/packages/coding-agent/src/utils/title-generator.ts b/packages/coding-agent/src/utils/title-generator.ts index 50783f4f3..afb8809db 100644 --- a/packages/coding-agent/src/utils/title-generator.ts +++ b/packages/coding-agent/src/utils/title-generator.ts @@ -153,8 +153,13 @@ function getFallbackTerminalTitle(cwd: string | undefined): string | undefined { return sanitizeTerminalTitlePart(baseName); } -export function formatSessionTerminalTitle(sessionName: string | undefined, cwd?: string): string { - const label = sanitizeTerminalTitlePart(sessionName) ?? getFallbackTerminalTitle(cwd); +export function formatSessionTerminalTitle( + sessionName: string | undefined, + cwd?: string, + titleSource?: "auto" | "user" | undefined, +): string { + const label = + sanitizeTerminalTitlePart(titleSource === "auto" ? undefined : sessionName) ?? getFallbackTerminalTitle(cwd); return label ? `${DEFAULT_TERMINAL_TITLE}: ${label}` : DEFAULT_TERMINAL_TITLE; } @@ -165,8 +170,12 @@ export function setTerminalTitle(title: string): void { process.stdout.write(`\x1b]0;${sanitizeTerminalTitlePart(title) ?? DEFAULT_TERMINAL_TITLE}\x07`); } -export function setSessionTerminalTitle(sessionName: string | undefined, cwd?: string): void { - setTerminalTitle(formatSessionTerminalTitle(sessionName, cwd)); +export function setSessionTerminalTitle( + sessionName: string | undefined, + cwd?: string, + titleSource?: "auto" | "user" | undefined, +): void { + setTerminalTitle(formatSessionTerminalTitle(sessionName, cwd, titleSource)); } /** diff --git a/packages/coding-agent/test/session-color.test.ts b/packages/coding-agent/test/session-color.test.ts index 823d53081..6c70eac33 100644 --- a/packages/coding-agent/test/session-color.test.ts +++ b/packages/coding-agent/test/session-color.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "bun:test"; import { getSessionAccentHex, getSessionAccentHexForTitle } from "../src/utils/session-color"; +import { formatSessionTerminalTitle } from "../src/utils/title-generator"; describe("getSessionAccentHexForTitle", () => { it("ignores auto-generated titles", () => { @@ -13,3 +14,13 @@ describe("getSessionAccentHexForTitle", () => { expect(getSessionAccentHexForTitle("Named session", undefined)).toBe(expected); }); }); + +describe("formatSessionTerminalTitle", () => { + it("falls back to cwd when the session title was auto-generated", () => { + expect(formatSessionTerminalTitle("Auto title", "/work/pi", "auto")).toBe("π: pi"); + }); + + it("shows explicit session renames in the terminal title", () => { + expect(formatSessionTerminalTitle("Manual title", "/work/pi", "user")).toBe("π: Manual title"); + }); +});