mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
feat(sessions): make pinning every tab name an opt-in setting
Add pinCliSessionName (synced, default OFF), re-read at each spawn and respawn. Off keeps the #457 behaviour; on pins every non-blank tab name as --name. Update the docs, JSDoc and changeset that stated the old rule.
This commit is contained in:
1 parent
1fefeda0dd
commit
76479fdc96
17 files changed
+174
-53
No files matched your search
@@ -7,10 +7,12 @@
|
||||
* used to change only the tab, so `/resume` kept listing the old name.
|
||||
*
|
||||
* Appending the row is enough for a pane that was spawned WITHOUT `--name`
|
||||
* (every placeholder- or auto-named tab, see `Session.cliPinnedName`): that
|
||||
* process holds no title of its own and never writes one back. A process that
|
||||
* WAS spawned with `--name` re-appends its in-memory title after each turn, so
|
||||
* there the new title holds from the next spawn, which pins the new name.
|
||||
* (every placeholder- or auto-named tab, unless the opt-in `pinCliSessionName`
|
||||
* setting is on, see `Session.cliPinnedName`): that process holds no title of
|
||||
* its own and never writes one back. A process that WAS spawned with `--name`
|
||||
* (every tab with `pinCliSessionName` on) re-appends its in-memory title after
|
||||
* each turn, so there the new title holds only until the next turn, then from
|
||||
* the next spawn, which pins the new name.
|
||||
*
|
||||
* @module claude-session-title
|
||||
*/
|
||||
|
||||
@@ -1131,6 +1131,19 @@ export async function readPlanUsageTelemetryEnabled(): Promise<boolean> {
|
||||
return settings.showPlanUsageLimits !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether every Claude session is launched with `--name` set to its tab name
|
||||
* (the `pinCliSessionName` setting). Read FRESH from settings.json at every
|
||||
* claude create and respawn, like {@link readPlanUsageTelemetryEnabled}, so a
|
||||
* toggle applies from the next spawn with no server restart. An ABSENT key
|
||||
* means OFF: only a name the user chose is pinned, and Claude titles the
|
||||
* placeholder- and auto-named conversations itself (see `Session.cliPinnedName`).
|
||||
*/
|
||||
export async function readPinCliSessionNameEnabled(): Promise<boolean> {
|
||||
const settings = await readJsonConfig<Record<string, unknown>>(SETTINGS_PATH, 'settings.json', {});
|
||||
return settings.pinCliSessionName === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the statusLine command to pass as an EPHEMERAL `claude --settings`
|
||||
* CLI flag for this one process (see buildSpawnCommandFromRegistry in
|
||||
|
||||
@@ -97,7 +97,8 @@ export interface CreateSessionOptions {
|
||||
* Name pinned on a claude spawn as `--name` (version-gated, sanitized, local only).
|
||||
* Deliberately NOT `name`: `--name` owns the prompt-box label, the `/resume` picker
|
||||
* entry and the terminal title, and a pinned title stops Claude generating its own,
|
||||
* so only a user-chosen name belongs here (see `Session.cliPinnedName`).
|
||||
* so by default only a user-chosen name belongs here; the opt-in `pinCliSessionName`
|
||||
* setting pins every tab name (see `Session.cliPinnedName`).
|
||||
*/
|
||||
cliName?: string;
|
||||
niceConfig?: NiceConfig;
|
||||
@@ -141,7 +142,8 @@ export interface RespawnPaneOptions {
|
||||
* Name pinned on a respawned claude as `--name` (version-gated, sanitized, local only).
|
||||
* Deliberately NOT `name`: `--name` owns the prompt-box label, the `/resume` picker
|
||||
* entry and the terminal title, and a pinned title stops Claude generating its own,
|
||||
* so only a user-chosen name belongs here (see `Session.cliPinnedName`).
|
||||
* so by default only a user-chosen name belongs here; the opt-in `pinCliSessionName`
|
||||
* setting pins every tab name (see `Session.cliPinnedName`).
|
||||
*/
|
||||
cliName?: string;
|
||||
niceConfig?: NiceConfig;
|
||||
|
||||
+26
-9
@@ -499,6 +499,8 @@ export class Session extends EventEmitter {
|
||||
|
||||
private _name: string;
|
||||
private _nameSource: SessionNameSource;
|
||||
/** `pinCliSessionName` as of the last spawn (see {@link _refreshCliNamePin}). */
|
||||
private _pinEveryCliName = false;
|
||||
/**
|
||||
* Reconstructs the composer draft from USER keystrokes so the first real
|
||||
* prompt can name the tab. Fed only when a write says `fromUser`, and never
|
||||
@@ -1806,21 +1808,33 @@ export class Session extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* The name to pin on the Claude CLI as `--name`: the tab's own name, whatever its source,
|
||||
* so the CLI's prompt-box label, `/resume` entry and terminal title (and Claude Code's
|
||||
* remote session list) read the same as the tab. Leaving a placeholder or auto-named tab
|
||||
* unpinned let Claude generate its own title, which then differed from the tab it was
|
||||
* running in (`w4-myapp` in Codeman, "Fix the flaky teardown" in Claude).
|
||||
* The name to pin on the Claude CLI as `--name`, or undefined to let Claude title the
|
||||
* conversation itself. `--name` is the prompt-box label, the `/resume` picker entry and the
|
||||
* terminal title all at once, and a pinned title stops Claude generating its own.
|
||||
*
|
||||
* The cost is that every conversation of a case starts with the same `/resume` entry shape
|
||||
* (`w1-myapp`, `w2-myapp`); the tab number keeps those distinct per tab. A rename or an
|
||||
* auto-name reaches the CLI at the next spawn: a process started with `--name` re-appends its
|
||||
* in-memory title after each turn (see claude-session-title.ts).
|
||||
* By default only a name the user chose is pinned. Pinning the `w1-myapp` placeholder gave
|
||||
* every conversation in a case the same `/resume` entry (the tab number restarts once a
|
||||
* case's tabs are closed), and an auto name is a cut of the first prompt, which Claude's
|
||||
* own generated title already beats.
|
||||
*
|
||||
* With the opt-in `pinCliSessionName` setting every non-blank tab name is pinned, so the
|
||||
* CLI's labels read the same as the tabs, at the cost of those generated titles. A rename
|
||||
* in Codeman then holds only until the next turn: a process started with `--name`
|
||||
* re-appends its in-memory title (see claude-session-title.ts). The setting is re-read
|
||||
* at each spawn and respawn by {@link _refreshCliNamePin}.
|
||||
*/
|
||||
get cliPinnedName(): string | undefined {
|
||||
if (!this._pinEveryCliName) return this._nameSource === 'manual' ? this._name : undefined;
|
||||
return this._name.trim() ? this._name : undefined;
|
||||
}
|
||||
|
||||
/** Re-read `pinCliSessionName` so `cliPinnedName` decides per launch, with no server restart. */
|
||||
private async _refreshCliNamePin(): Promise<void> {
|
||||
// Dynamic: hooks-config -> route-helpers imports Session, so a static import would be a cycle.
|
||||
const { readPinCliSessionNameEnabled } = await import('./hooks-config.js');
|
||||
this._pinEveryCliName = await readPinCliSessionNameEnabled();
|
||||
}
|
||||
|
||||
setAutoClear(enabled: boolean, threshold?: number): void {
|
||||
this._autoOps.setAutoClear(enabled, threshold);
|
||||
}
|
||||
@@ -2478,6 +2492,7 @@ export class Session extends EventEmitter {
|
||||
* still inside it and renders no local `--session-id` to collide.
|
||||
*/
|
||||
private async _buildRespawnPaneOptionsWithResumePin(): Promise<import('./mux-interface.js').RespawnPaneOptions> {
|
||||
await this._refreshCliNamePin();
|
||||
const options = this._buildRespawnPaneOptions();
|
||||
if (this._remote || this._docker) return options;
|
||||
const entry = getCli(this.mode);
|
||||
@@ -2742,6 +2757,8 @@ export class Session extends EventEmitter {
|
||||
throw new Error('Session is being closed');
|
||||
}
|
||||
|
||||
await this._refreshCliNamePin();
|
||||
|
||||
// Bounds the workspace-trust scan (see _maybeAcceptTrustDialog). Stamped here
|
||||
// rather than at PTY spawn so a slow mux attach still counts as startup.
|
||||
this._interactiveStartedAt = Date.now();
|
||||
|
||||
+1
-1
@@ -1089,7 +1089,7 @@ export function buildSpawnCommand(options: {
|
||||
advisorModel?: string;
|
||||
/** Resolved by resolveStatusLineCliCommand (hooks-config.ts) — undefined skips the exporter. Claude only. */
|
||||
statusLineCommand?: string;
|
||||
/** Name pinned on claude as `--name` (version-gated, sanitized; local spawns only). Only a user-chosen name: see `Session.cliPinnedName`. */
|
||||
/** Name pinned on claude as `--name` (version-gated, sanitized; local spawns only). By default only a user-chosen name; `pinCliSessionName` pins every tab name: see `Session.cliPinnedName`. */
|
||||
sessionName?: string;
|
||||
/**
|
||||
* Claude CLI version for the `--name` gate. Omitted = probe the local CLI
|
||||
|
||||
@@ -2787,6 +2787,13 @@
|
||||
</div>
|
||||
<label class="switch switch-sm"><input type="checkbox" id="appSettingsWorkspaceHooks"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="set-row" data-search="pin cli session name resume title tab label">
|
||||
<div class="set-row-text">
|
||||
<span class="set-row-label">Pin tab names on the CLI</span>
|
||||
<span class="set-row-desc">Launch every Claude session with its tab name, so the CLI's prompt label and /resume list match your tabs. Claude then stops generating its own titles for those sessions, and a rename in Codeman holds only until the next turn. Applies from the next session start or respawn, no server restart needed.</span>
|
||||
</div>
|
||||
<label class="switch switch-sm"><input type="checkbox" id="appSettingsPinCliSessionName"><span class="slider"></span></label>
|
||||
</div>
|
||||
<div class="set-row" data-search="remote auto reconnect ssh">
|
||||
<div class="set-row-text">
|
||||
<span class="set-row-label">Remote auto-reconnect</span>
|
||||
|
||||
@@ -588,6 +588,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
// Default ON: an absent key is a user who has never seen this setting, and OFF
|
||||
// for them means no tab alerts in any workspace Codeman did not scaffold.
|
||||
document.getElementById('appSettingsWorkspaceHooks').checked = settings.workspaceHooksEnabled !== false;
|
||||
document.getElementById('appSettingsPinCliSessionName').checked = settings.pinCliSessionName === true;
|
||||
document.getElementById('appSettingsClaudeModel').value = settings.claudeModel ?? '';
|
||||
document.getElementById('appSettingsOpusContext1m').checked = settings.opusContext1mEnabled ?? false;
|
||||
document.getElementById('appSettingsRemoteAutoReconnect').checked = settings.remoteAutoReconnect ?? true;
|
||||
@@ -2708,6 +2709,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
agentTeamsEnabled: document.getElementById('appSettingsAgentTeams').checked,
|
||||
agentSkillEnabled: document.getElementById('appSettingsAgentSkill').checked,
|
||||
workspaceHooksEnabled: document.getElementById('appSettingsWorkspaceHooks').checked,
|
||||
pinCliSessionName: document.getElementById('appSettingsPinCliSessionName').checked,
|
||||
claudeVoiceEnabled: document.getElementById('appSettingsClaudeVoice').checked,
|
||||
claudeModel: document.getElementById('appSettingsClaudeModel').value,
|
||||
opusContext1mEnabled: document.getElementById('appSettingsOpusContext1m').checked,
|
||||
|
||||
@@ -1425,6 +1425,14 @@ export const SettingsUpdateSchema = z
|
||||
* never added — for a user who wants Codeman to leave their repos alone.
|
||||
*/
|
||||
workspaceHooksEnabled: z.boolean().optional(),
|
||||
/**
|
||||
* Launch every Claude session with `--name` set to its tab name (default OFF: only a
|
||||
* name the user chose is pinned). Nothing acts on it in `PUT /api/settings`: it is read
|
||||
* fresh from disk by readPinCliSessionNameEnabled() (hooks-config.ts) at every claude
|
||||
* create and respawn, like readPlanUsageTelemetryEnabled(), so a toggle applies from the
|
||||
* next spawn with no restart.
|
||||
*/
|
||||
pinCliSessionName: z.boolean().optional(),
|
||||
/**
|
||||
* Let browser dictation transcribe through this machine's Claude Code login,
|
||||
* the same speech-to-text service the CLI's own `/voice` mode uses
|
||||
|
||||
Reference in new issue
Block a user