@yagni-app/code-staging 0.3.4-staging.1155.1 → 0.3.4-staging.1156.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,19 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- export declare function registerCmuxBridge(pi: ExtensionAPI): void;
2
+ import { type SessionState } from "./state.js";
3
+ import { type SummarizeTitleResult } from "../sessionTitle/summarize.js";
4
+ /** Injectable seams so the bridge's state machine is testable without a child. */
5
+ export interface CmuxBridgeDeps {
6
+ summarize?: (prompt: string, deps?: {
7
+ cwd?: string;
8
+ }) => Promise<SummarizeTitleResult>;
9
+ }
10
+ export declare function registerCmuxBridge(pi: ExtensionAPI, deps?: CmuxBridgeDeps): void;
11
+ /**
12
+ * The naming half of `publishCompletion`, extracted so the placeholder→final
13
+ * guard is directly testable without a child process. Once a session's title
14
+ * has been upgraded (or the upgrade was attempted), a later settle does nothing.
15
+ */
16
+ export declare function upgradeTitleOnce(state: SessionState, summarize: (prompt: string, deps?: {
17
+ cwd?: string;
18
+ }) => Promise<SummarizeTitleResult>, setTitle: ((title: string) => void) | undefined, onFail: (reason: string) => void, cwd: string): Promise<void>;
3
19
  //# sourceMappingURL=index.d.ts.map
@@ -3,7 +3,9 @@ import { join } from "node:path";
3
3
  import { CmuxDispatcher } from "./dispatcher.js";
4
4
  import { stateFor, snapshotContext, beginTurn, currentTurnId, finishTurn, settleTurn, lastAssistantMessage, firstString, objectValue, warn, } from "./state.js";
5
5
  import { sendHook, ensureResumeBinding, clearResumeBinding, releaseSessionRuntime, } from "./hooks.js";
6
- import { renameWorkspaceFromPrompt } from "./naming.js";
6
+ import { isDriverCaller } from "../config.js";
7
+ import { sessionTitle } from "../sessionTitle/title.js";
8
+ import { summarizeTitle } from "../sessionTitle/summarize.js";
7
9
  /** Remove a stale cmux-installed extension so hooks don't double-fire. */
8
10
  function removeStaleManagedExtension() {
9
11
  const agentDir = process.env.PI_CODING_AGENT_DIR || process.env.YAGNI_CODING_AGENT_DIR;
@@ -24,11 +26,17 @@ function removeStaleManagedExtension() {
24
26
  }
25
27
  catch { }
26
28
  }
27
- export function registerCmuxBridge(pi) {
29
+ export function registerCmuxBridge(pi, deps = {}) {
28
30
  if (!process.env.CMUX_SURFACE_ID)
29
31
  return;
30
32
  if (process.env.CMUX_PI_HOOKS_DISABLED === "1")
31
33
  return;
34
+ // A child process (guardian consult, advisor, /go stage, subagent) has no
35
+ // direct user — reporting its internals to cmux would overwrite the driver
36
+ // session's card with e.g. the Guardian consult prompt/verdict.
37
+ if (!isDriverCaller())
38
+ return;
39
+ const summarize = deps.summarize ?? summarizeTitle;
32
40
  const dispatcher = new CmuxDispatcher();
33
41
  const sessionStates = new Map();
34
42
  const lifecycleTails = new Map();
@@ -74,6 +82,18 @@ export function registerCmuxBridge(pi) {
74
82
  if (typeof event.prompt === "string" && event.prompt.trim()) {
75
83
  st.lastPrompt = event.prompt.trim();
76
84
  }
85
+ // Set the terminal title to the heuristic session title immediately at the
86
+ // first prompt (mirroring Claude's derive-then-upgrade: the tab is named
87
+ // from the prompt, not the model reply). cmux freezes auto-titling on its
88
+ // own once the user sets a manual name (customTitle), so we never clobber it.
89
+ // The optional LLM upgrade happens later on agent_settled.
90
+ if (!st.nameState && typeof event.prompt === "string" && event.prompt.trim()) {
91
+ const placeholder = sessionTitle(event.prompt.trim());
92
+ if (placeholder) {
93
+ st.nameState = "placeholder";
94
+ context.setTitle?.(placeholder);
95
+ }
96
+ }
77
97
  enqueueLifecycleTask(sessionId, context, () => sendHook(dispatcher, "prompt-submit", context, { prompt: event.prompt, turn_id: turnId }));
78
98
  });
79
99
  pi.on("agent_end", (event, ctx) => {
@@ -104,7 +124,7 @@ export function registerCmuxBridge(pi) {
104
124
  return;
105
125
  const completion = settleTurn(sessionStates, sessionId);
106
126
  if (completion) {
107
- enqueueLifecycleTask(sessionId, context, () => publishCompletion(dispatcher, sessionStates, context, sessionId, completion));
127
+ enqueueLifecycleTask(sessionId, context, () => publishCompletion(dispatcher, sessionStates, context, sessionId, completion, summarize));
108
128
  }
109
129
  });
110
130
  pi.on("session_shutdown", async (event, ctx) => {
@@ -133,7 +153,27 @@ export function registerCmuxBridge(pi) {
133
153
  });
134
154
  });
135
155
  }
136
- async function publishCompletion(dispatcher, sessionStates, context, sessionId, completion) {
156
+ /**
157
+ * The naming half of `publishCompletion`, extracted so the placeholder→final
158
+ * guard is directly testable without a child process. Once a session's title
159
+ * has been upgraded (or the upgrade was attempted), a later settle does nothing.
160
+ */
161
+ export async function upgradeTitleOnce(state, summarize, setTitle, onFail, cwd) {
162
+ if (state.nameState !== "placeholder")
163
+ return;
164
+ state.nameState = "final";
165
+ const prompt = state.lastPrompt;
166
+ if (!prompt)
167
+ return;
168
+ const result = await summarize(prompt, { cwd });
169
+ if (result.ok) {
170
+ setTitle?.(result.title);
171
+ }
172
+ else {
173
+ onFail(result.reason);
174
+ }
175
+ }
176
+ async function publishCompletion(dispatcher, sessionStates, context, sessionId, completion, summarize) {
137
177
  const stopPayload = {
138
178
  last_assistant_message: completion.lastAssistantMessage,
139
179
  turn_id: completion.turnId,
@@ -147,9 +187,8 @@ async function publishCompletion(dispatcher, sessionStates, context, sessionId,
147
187
  stopPayload.cmux_notification_routed = true;
148
188
  await sendHook(dispatcher, "stop", context, stopPayload);
149
189
  const state = stateFor(sessionStates, sessionId);
150
- if (!state.hasNamed) {
151
- renameWorkspaceFromPrompt(dispatcher, sessionStates, context, sessionId);
152
- state.hasNamed = true;
153
- }
190
+ // Upgrade the heuristic placeholder to an LLM-summarized title, once (see
191
+ // upgradeTitleOnce). A failed summary keeps the placeholder in place.
192
+ await upgradeTitleOnce(state, summarize, context.setTitle, (reason) => warn(context, "title summary failed; keeping heuristic title", { session_id: sessionId, reason }), context.cwd);
154
193
  }
155
194
  //# sourceMappingURL=index.js.map
@@ -4,18 +4,22 @@ export interface PendingCompletion {
4
4
  notificationType: string;
5
5
  turnId: string;
6
6
  }
7
+ /** The naming lifecycle of a session's cmux workspace title. */
8
+ export type NameState = "unnamed" | "placeholder" | "final";
7
9
  export interface SessionState {
8
10
  nextTurn: number;
9
11
  activeTurnId?: string;
10
12
  pendingCompletion?: PendingCompletion;
11
13
  stopped: boolean;
12
14
  lastPrompt?: string;
13
- hasNamed?: boolean;
15
+ nameState?: NameState;
14
16
  }
15
17
  export interface PiExtensionContextSnapshot {
16
18
  readonly sessionId: string | null;
17
19
  readonly cwd: string;
18
20
  readonly notifyWarning?: () => void;
21
+ /** Set the terminal tab/window title (pi's OSC 0), mirroring Claude's title channel. */
22
+ readonly setTitle?: (title: string) => void;
19
23
  }
20
24
  export declare function firstString(...values: unknown[]): string | null;
21
25
  export declare function objectValue(value: unknown, keys: string[]): unknown;
@@ -1,3 +1,6 @@
1
+ import { appendFileSync, mkdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { codeStateHome } from "../stateHome.js";
1
4
  export function firstString(...values) {
2
5
  for (const value of values) {
3
6
  if (typeof value === "string" && value.trim().length > 0)
@@ -67,14 +70,18 @@ export function snapshotContext(ctx) {
67
70
  return { sessionId: null, cwd: process.cwd() };
68
71
  }
69
72
  let notifyWarning;
73
+ let setTitle;
70
74
  try {
71
75
  const ui = ctx.ui;
72
76
  if (typeof ui?.notify === "function") {
73
77
  notifyWarning = () => ui.notify?.("cmux integration warning — check the terminal for details", "warning");
74
78
  }
79
+ if (typeof ui?.setTitle === "function") {
80
+ setTitle = (title) => ui.setTitle?.(title);
81
+ }
75
82
  }
76
83
  catch { }
77
- return { sessionId: sessionIdFrom(ctx), cwd: cwdFrom(ctx), notifyWarning };
84
+ return { sessionId: sessionIdFrom(ctx), cwd: cwdFrom(ctx), notifyWarning, setTitle };
78
85
  }
79
86
  export function stateFor(sessionStates, sessionId) {
80
87
  let state = sessionStates.get(sessionId);
@@ -126,11 +133,18 @@ export function settleTurn(sessionStates, sessionId) {
126
133
  }
127
134
  export function warn(ctx, message, details = {}, notifyUser = false) {
128
135
  const payload = { source: "yagni-cmux-bridge", level: "warning", message, ...details };
136
+ // The TUI is in raw mode: writing to stdout/stderr corrupts the terminal (the
137
+ // JSON was landing at the prompt cursor). Route warnings/errors to a rotating
138
+ // local log instead — never to the terminal.
129
139
  try {
130
- console.warn(JSON.stringify(payload));
140
+ if (!process.env.NODE_TEST_CONTEXT) {
141
+ const path = join(codeStateHome(null), "logs", "cmux-bridge.log");
142
+ mkdirSync(dirname(path), { recursive: true });
143
+ appendFileSync(path, JSON.stringify({ ts: new Date().toISOString(), ...payload }) + "\n", "utf8");
144
+ }
131
145
  }
132
146
  catch {
133
- console.warn(`[yagni-cmux-bridge] ${message}`);
147
+ /* logging must never throw into the editor */
134
148
  }
135
149
  if (notifyUser) {
136
150
  try {
@@ -184,6 +184,14 @@ Output ONLY a JSON object with this exact shape:
184
184
  {"outcome":"allow"|"ask"|"deny","riskLevel":"low"|"medium"|"high"|"critical","rationale":"see rationale rules"}
185
185
 
186
186
  Do not output anything else after the JSON. No markdown fences, only the JSON object.`;
187
+ const TITLE_BODY = `You produce a session title from a user's prompt. Output ONLY a concise, sentence-case title of 3-7 words that captures the main topic or goal. Capitalize only the first word and proper nouns. Do not include a ticket code in the title text itself (the caller prepends it). No markdown, no prose, no quotes — just the title on one line.
188
+
189
+ Good:
190
+ Fix the login page
191
+ Add OAuth authentication
192
+
193
+ Bad (too vague): Code changes
194
+ Bad (too long): Investigate and fix the login button not responding on mobile devices`;
187
195
  /** Persona body keyed by the agent name referenced in `stages.ts`. */
188
196
  export const PERSONA_BODIES = {
189
197
  scout: SCOUT_BODY,
@@ -192,6 +200,7 @@ export const PERSONA_BODIES = {
192
200
  reviewer: REVIEWER_BODY,
193
201
  advisor: ADVISOR_BODY,
194
202
  guardian: GUARDIAN_BODY,
203
+ title: TITLE_BODY,
195
204
  orchestrator: [ORCHESTRATOR_BODY, PARTITION_CONTRACT].join("\n\n"),
196
205
  synthesizer: SYNTHESIZER_BODY,
197
206
  };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The I/O half of session titling: summarize a prompt into a title via a
3
+ * locked-down efficient-tier child (same `runStage` seam the Guardian and
4
+ * advisor use). Fully optional — any failure returns undefined so the caller
5
+ * falls back to the heuristic title without ever blocking a turn.
6
+ */
7
+ import { runStage as defaultRunStage } from "../pipeline/runner.js";
8
+ import type { PipelineStage } from "../pipeline/types.js";
9
+ /** The model tier the title child runs on (read-only, like the Guardian). */
10
+ export declare const TITLE_MODEL_TIER = "efficient";
11
+ /** The read-only tools the title child may use (only `read`, for context). */
12
+ export declare const TITLE_TOOLS: string[];
13
+ /** Default wall-clock timeout for the title consult (matches Guardian). */
14
+ export declare const DEFAULT_TITLE_TIMEOUT_MS = 15000;
15
+ /**
16
+ * The synthetic stage a title consult runs as. Borrows the `plan` StageId (same
17
+ * pattern as the advisor/guardian) so it doesn't ripple into /go feed/reducers.
18
+ * The agent name selects the `title` persona from PERSONA_BODIES.
19
+ */
20
+ export declare function titleStage(modelTier?: string): PipelineStage;
21
+ export interface SummarizeTitleDeps {
22
+ runStage?: typeof defaultRunStage;
23
+ timeoutMs?: number;
24
+ cwd?: string;
25
+ }
26
+ /** The outcome of a title consult: a prefixed title, or a failure reason. */
27
+ export type SummarizeTitleResult = {
28
+ ok: true;
29
+ title: string;
30
+ } | {
31
+ ok: false;
32
+ reason: string;
33
+ };
34
+ /**
35
+ * Summarize a prompt into a title. Returns `{ title }` on success, or
36
+ * `{ reason }` on any failure (timeout, abort, non-zero exit, empty or unusable
37
+ * output) — the caller falls back to the heuristic and logs the reason.
38
+ */
39
+ export declare function summarizeTitle(prompt: string, deps?: SummarizeTitleDeps): Promise<SummarizeTitleResult>;
40
+ //# sourceMappingURL=summarize.d.ts.map
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The I/O half of session titling: summarize a prompt into a title via a
3
+ * locked-down efficient-tier child (same `runStage` seam the Guardian and
4
+ * advisor use). Fully optional — any failure returns undefined so the caller
5
+ * falls back to the heuristic title without ever blocking a turn.
6
+ */
7
+ import { runStage as defaultRunStage } from "../pipeline/runner.js";
8
+ import { parseSummarizedTitle } from "./title.js";
9
+ /** The model tier the title child runs on (read-only, like the Guardian). */
10
+ export const TITLE_MODEL_TIER = "efficient";
11
+ /** The read-only tools the title child may use (only `read`, for context). */
12
+ export const TITLE_TOOLS = ["read"];
13
+ /** Default wall-clock timeout for the title consult (matches Guardian). */
14
+ export const DEFAULT_TITLE_TIMEOUT_MS = 15_000;
15
+ /**
16
+ * The synthetic stage a title consult runs as. Borrows the `plan` StageId (same
17
+ * pattern as the advisor/guardian) so it doesn't ripple into /go feed/reducers.
18
+ * The agent name selects the `title` persona from PERSONA_BODIES.
19
+ */
20
+ export function titleStage(modelTier = TITLE_MODEL_TIER) {
21
+ return {
22
+ id: "plan",
23
+ agent: "title",
24
+ model: modelTier,
25
+ tools: TITLE_TOOLS,
26
+ taskTemplate: "{ticket}",
27
+ };
28
+ }
29
+ /**
30
+ * Summarize a prompt into a title. Returns `{ title }` on success, or
31
+ * `{ reason }` on any failure (timeout, abort, non-zero exit, empty or unusable
32
+ * output) — the caller falls back to the heuristic and logs the reason.
33
+ */
34
+ export async function summarizeTitle(prompt, deps = {}) {
35
+ const runStage = deps.runStage ?? defaultRunStage;
36
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_TITLE_TIMEOUT_MS;
37
+ const controller = new AbortController();
38
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
39
+ timer.unref?.();
40
+ try {
41
+ const result = await runStage(titleStage(), { ticket: `Summarize this session into a short title:\n\n${prompt}` }, {
42
+ cwd: deps.cwd ?? process.cwd(),
43
+ signal: controller.signal,
44
+ callerLabel: "title",
45
+ });
46
+ if (result.exitCode !== 0) {
47
+ const stderr = result.stderr?.trim().slice(0, 200);
48
+ return { ok: false, reason: stderr ? `exit=${result.exitCode} ${stderr}` : `exit=${result.exitCode}` };
49
+ }
50
+ const title = parseSummarizedTitle(prompt, result.finalOutput);
51
+ if (title)
52
+ return { ok: true, title };
53
+ return { ok: false, reason: `empty_output output=${JSON.stringify(result.finalOutput.slice(0, 200))}` };
54
+ }
55
+ catch (error) {
56
+ const msg = error instanceof Error ? error.message : String(error);
57
+ return { ok: false, reason: `threw ${msg.slice(0, 200)}` };
58
+ }
59
+ finally {
60
+ clearTimeout(timer);
61
+ }
62
+ }
63
+ //# sourceMappingURL=summarize.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Pure session-title derivation — the reusable half of naming an agent session.
3
+ * No cmux, no workspace, no I/O: just "given a prompt, what's a good title".
4
+ *
5
+ * cmux is the current (and only) consumer, but this module is deliberately
6
+ * cmux-free so a future consumer (terminal title, a resume hint, the Feed) can
7
+ * reuse it without importing cmux vocabulary.
8
+ */
9
+ /** Extract a ticket code from a prompt, or null when none is present. */
10
+ export declare function ticketCode(prompt: string): string | null;
11
+ /** The heuristic fallback title: the first 8 words, capped at 60 chars. */
12
+ export declare function titleFromPrompt(prompt: string): string | undefined;
13
+ /**
14
+ * Compose a session title from a prompt, prepending the ticket code when one is
15
+ * present. The code is stripped from the derived body so it doesn't appear
16
+ * twice ("YAG-532: work on YAG-532 …").
17
+ */
18
+ export declare function sessionTitle(prompt: string, body?: string): string | undefined;
19
+ /**
20
+ * Normalize a model-produced title into the same shape `sessionTitle` emits.
21
+ * Returns undefined when the model's output is empty, whitespace, or otherwise
22
+ * unusable — the caller then falls back to the heuristic.
23
+ */
24
+ export declare function parseSummarizedTitle(prompt: string, raw: string): string | undefined;
25
+ /** The maximum chars `parseSummarizedTitle` may return (the same cap). */
26
+ export declare const SESSION_TITLE_MAX_LEN = 60;
27
+ //# sourceMappingURL=title.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Pure session-title derivation — the reusable half of naming an agent session.
3
+ * No cmux, no workspace, no I/O: just "given a prompt, what's a good title".
4
+ *
5
+ * cmux is the current (and only) consumer, but this module is deliberately
6
+ * cmux-free so a future consumer (terminal title, a resume hint, the Feed) can
7
+ * reuse it without importing cmux vocabulary.
8
+ */
9
+ /** The ticket-code shape ("YAG-532", "PROJ-123"), matching the backend's IDENTIFIER_RE. */
10
+ const TICKET_ID_RE = /\b[A-Z][A-Z0-9]*-\d+\b/i;
11
+ const MAX_TITLE_LEN = 60;
12
+ /** Extract a ticket code from a prompt, or null when none is present. */
13
+ export function ticketCode(prompt) {
14
+ return prompt.match(TICKET_ID_RE)?.[0]?.toUpperCase() ?? null;
15
+ }
16
+ /** The heuristic fallback title: the first 8 words, capped at 60 chars. */
17
+ export function titleFromPrompt(prompt) {
18
+ const words = prompt.trim().split(/\s+/).filter(Boolean);
19
+ if (words.length === 0)
20
+ return undefined;
21
+ return words.slice(0, 8).join(" ").slice(0, MAX_TITLE_LEN) || undefined;
22
+ }
23
+ /**
24
+ * Compose a session title from a prompt, prepending the ticket code when one is
25
+ * present. The code is stripped from the derived body so it doesn't appear
26
+ * twice ("YAG-532: work on YAG-532 …").
27
+ */
28
+ export function sessionTitle(prompt, body) {
29
+ const code = ticketCode(prompt);
30
+ // Derive the body from the prompt with the ticket code removed (unless the
31
+ // caller supplied an already-summarized body, in which case prefer it).
32
+ const effectiveBody = body ?? titleFromPrompt(code ? prompt.replace(TICKET_ID_RE, "") : prompt);
33
+ if (!code)
34
+ return effectiveBody;
35
+ if (!effectiveBody)
36
+ return code;
37
+ return `${code}: ${effectiveBody}`.slice(0, MAX_TITLE_LEN);
38
+ }
39
+ /**
40
+ * Normalize a model-produced title into the same shape `sessionTitle` emits.
41
+ * Returns undefined when the model's output is empty, whitespace, or otherwise
42
+ * unusable — the caller then falls back to the heuristic.
43
+ */
44
+ export function parseSummarizedTitle(prompt, raw) {
45
+ const cleaned = raw.trim().replace(/\s+/g, " ").replace(/^["']|["']$/g, "");
46
+ if (!cleaned)
47
+ return undefined;
48
+ // Strip any ticket-code token the model echoed back into its summary (the
49
+ // prompt carries the code, and models repeat it) so the composed title never
50
+ // doubles the prefix ("YAG-485: YAG-485 …"). Deterministic, not prompt hygiene.
51
+ const code = ticketCode(prompt);
52
+ const body = code ? cleaned.replace(TICKET_ID_RE, "").trim().replace(/\s+/g, " ") : cleaned;
53
+ return sessionTitle(prompt, body);
54
+ }
55
+ /** The maximum chars `parseSummarizedTitle` may return (the same cap). */
56
+ export const SESSION_TITLE_MAX_LEN = MAX_TITLE_LEN;
57
+ //# sourceMappingURL=title.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yagni-app/code-staging",
3
- "version": "0.3.4-staging.1155.1",
3
+ "version": "0.3.4-staging.1156.1",
4
4
  "description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
@@ -39,5 +39,5 @@
39
39
  "smol-toml": "^1.8.0",
40
40
  "typebox": "^1.3.15"
41
41
  },
42
- "yagniSourceSha": "17bd689a1ff1bc6e8d9fc0d420b2d4e580a781dd"
42
+ "yagniSourceSha": "28861a8fed4d500c1391588cc71597d6ff0fa43a"
43
43
  }
@@ -1,5 +0,0 @@
1
- import type { CmuxDispatcher } from "./dispatcher.js";
2
- import { type PiExtensionContextSnapshot, type SessionState } from "./state.js";
3
- export declare function titleFromPrompt(prompt: string): string | undefined;
4
- export declare function renameWorkspaceFromPrompt(dispatcher: CmuxDispatcher, sessionStates: Map<string, SessionState>, context: PiExtensionContextSnapshot, sessionId: string): void;
5
- //# sourceMappingURL=naming.d.ts.map
@@ -1,23 +0,0 @@
1
- import { firstString } from "./state.js";
2
- export function titleFromPrompt(prompt) {
3
- const words = prompt.trim().split(/\s+/).filter(Boolean);
4
- if (words.length === 0)
5
- return undefined;
6
- return words.slice(0, 8).join(" ").slice(0, 60) || undefined;
7
- }
8
- export function renameWorkspaceFromPrompt(dispatcher, sessionStates, context, sessionId) {
9
- const prompt = sessionStates.get(sessionId)?.lastPrompt;
10
- if (!prompt)
11
- return;
12
- const title = titleFromPrompt(prompt);
13
- if (!title)
14
- return;
15
- // Use the workspace-scoped rename form: cmux workspace rename <ws> --title <title>
16
- // (NOT rename-workspace --workspace ... --surface ... which misinterprets --surface
17
- // as part of the title).
18
- const workspaceId = firstString(process.env.CMUX_WORKSPACE_ID);
19
- if (!workspaceId)
20
- return;
21
- void dispatcher.run(["workspace", "rename", workspaceId, "--title", title], context.cwd, undefined, context);
22
- }
23
- //# sourceMappingURL=naming.js.map