feat(coding-agent): add /rename command with session accent coloring

- Add /rename <title> slash command to set an explicit session name,
  updating the session header, terminal tab title, and status line
- Add session_name status segment: displays the session name on the
  right side of the status bar with a stable djb2-hash-derived accent
  color unique to each name; shown in all presets when a name is set
- Add setSessionName source tracking ('user' vs 'auto'): auto-generated
  titles are silently ignored once the user has explicitly set a name,
  preventing the async title generation from overwriting a /rename
- Sanitize session names at storage time: strip C0/C1 control characters
  (including ANSI ESC) via static #sanitizeName before storing, blocking
  escape sequence injection into the TUI, terminal title, and session file
- Extend sanitizeStatusText to cover the full C0/C1 range as
  defense-in-depth (superset of the previous \r\n\t-only strip)
- Use stored (sanitized) name for showStatus message and terminal title
  in handleRenameCommand, not the raw user input
- Guard terminal title update in auto-title path via titleSource === 'auto'
  so a user rename is never overwritten by a late-resolving LLM call
- Reset #titleSource in #newSessionSync so each new session starts fresh
- Thread source parameter through AgentSession.setSessionName wrapper;
  extension API and RPC set_session_name treated as 'user' intent

Closes #658
This commit is contained in:
Christoph Gross
2026-04-11 13:49:01 +02:00
parent f96422d911
commit 09810120ba
17 changed files with 194 additions and 22 deletions
@@ -686,6 +686,23 @@ export class CommandController {
}
}
async handleRenameCommand(title: string): Promise<void> {
try {
const stored = await this.ctx.sessionManager.setSessionName(title, "user");
if (!stored) {
this.ctx.showError("Session name cannot be empty.");
return;
}
const name = this.ctx.sessionManager.getSessionName()!;
setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd());
this.ctx.statusLine.invalidate();
this.ctx.updateEditorBorderColor();
this.ctx.showStatus(`Session renamed to "${name}".`);
} catch (err) {
this.ctx.showError(`Rename failed: ${err instanceof Error ? err.message : String(err)}`);
}
}
async handleBashCommand(command: string, excludeFromContext = false): Promise<void> {
const isDeferred = this.ctx.session.isStreaming;
this.ctx.bashComponent = new BashExecutionComponent(command, this.ctx.ui, excludeFromContext);