@standardagents/code 0.10.1 → 0.11.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.
package/dist/index.js CHANGED
@@ -356,9 +356,13 @@ var ApiClient = class {
356
356
  ).catch(() => [])
357
357
  )
358
358
  ]);
359
- const arr = pages.flatMap((res) => Array.isArray(res) ? res : res.threads || []);
359
+ const arr = pages.flatMap((res, i) => {
360
+ const list = Array.isArray(res) ? res : res.threads || [];
361
+ return list.map((t) => ({ ...t, agent_id: t.agent_id ?? ids[i] }));
362
+ });
360
363
  return arr.map((t) => ({
361
364
  id: t.id,
365
+ agent_id: t.agent_id,
362
366
  tags: Array.isArray(t.tags) ? t.tags : [],
363
367
  created_at: t.created_at,
364
368
  title: t.title,
@@ -447,14 +451,6 @@ var ApiClient = class {
447
451
  })
448
452
  );
449
453
  }
450
- async steerInput(threadId, mutation) {
451
- return parseSharedMessagingSnapshot(
452
- await this.json(this.messagingPath(threadId, "/steer"), {
453
- method: "POST",
454
- body: JSON.stringify(mutation)
455
- })
456
- );
457
- }
458
454
  async updatePendingInput(threadId, pendingId, mutation) {
459
455
  return parseSharedMessagingSnapshot(
460
456
  await this.json(this.messagingPath(threadId, `/pending/${encodeURIComponent(pendingId)}`), {
@@ -495,13 +491,23 @@ var ApiClient = class {
495
491
  })
496
492
  );
497
493
  }
498
- async requestSharedStop(threadId, origin2) {
499
- return parseSharedMessagingSnapshot(
500
- await this.json(this.messagingPath(threadId, "/stop"), {
501
- method: "POST",
502
- body: JSON.stringify(origin2)
503
- })
504
- );
494
+ /**
495
+ * Immediately halt a thread: the instance aborts the in-flight LLM
496
+ * request(s), marks dangling tool calls failed, and holds new turns until
497
+ * the next user message. This is Escape's HARD stop — not the old
498
+ * cooperative "stop at the next safe boundary" intent, which deferred the
499
+ * halt until the current step finished.
500
+ */
501
+ async stopThread(threadId) {
502
+ await this.json(`/api/threads/${threadId}/stop`, { method: "POST" });
503
+ }
504
+ /**
505
+ * Resume a stopped thread's execution loop. Used after a hard stop when a
506
+ * queued (steered) message should play immediately instead of waiting for
507
+ * the user's next direct send.
508
+ */
509
+ async continueThread(threadId) {
510
+ await this.json(`/api/threads/${threadId}/continue`, { method: "POST" });
505
511
  }
506
512
  async getMessages(threadId, limit = 50, order) {
507
513
  const orderParam = order ? `&order=${order}` : "";
@@ -1026,7 +1032,7 @@ function truncateMiddle(text, maxColumns) {
1026
1032
  const headBudget = Math.ceil(available / 2);
1027
1033
  const tailBudget = Math.floor(available / 2);
1028
1034
  const head = [];
1029
- const tail = [];
1035
+ const tail2 = [];
1030
1036
  let headWidth = 0;
1031
1037
  let tailWidth = 0;
1032
1038
  for (let i = 0; i < chars.length; i++) {
@@ -1038,12 +1044,12 @@ function truncateMiddle(text, maxColumns) {
1038
1044
  for (let i = chars.length - 1; i >= head.length; i--) {
1039
1045
  const width = terminalGlyphWidth(chars[i]);
1040
1046
  if (tailWidth + width > tailBudget) break;
1041
- tail.unshift(chars[i]);
1047
+ tail2.unshift(chars[i]);
1042
1048
  tailWidth += width;
1043
1049
  }
1044
1050
  const isSeparator = (ch) => ch === "/" || ch === "\\";
1045
1051
  let headEnd = head.length;
1046
- let tailStart = chars.length - tail.length;
1052
+ let tailStart = chars.length - tail2.length;
1047
1053
  for (let i = headEnd - 1; i >= 0; i--) {
1048
1054
  if (isSeparator(chars[i])) {
1049
1055
  headEnd = i + 1;
@@ -1059,7 +1065,7 @@ function truncateMiddle(text, maxColumns) {
1059
1065
  if (headEnd < tailStart && headEnd > 0 && tailStart < chars.length) {
1060
1066
  return `${chars.slice(0, headEnd).join("")}\u2026${chars.slice(tailStart).join("")}`;
1061
1067
  }
1062
- return `${head.join("")}\u2026${tail.join("")}`;
1068
+ return `${head.join("")}\u2026${tail2.join("")}`;
1063
1069
  }
1064
1070
  function clamp(s, max) {
1065
1071
  return s.length > max ? s.slice(0, Math.max(0, max - 1)) + "\u2026" : s;
@@ -2164,14 +2170,14 @@ ${truncated}`
2164
2170
  }
2165
2171
  if (!pid) return { ok: false, error: "Process failed to start (no pid)." };
2166
2172
  if (earlyExit !== void 0 || !isAlive2(pid)) {
2167
- const tail = await readLogTail(logPath, 15);
2173
+ const tail2 = await readLogTail(logPath, 15);
2168
2174
  const code = earlyExit ?? "unknown";
2169
2175
  return {
2170
2176
  ok: false,
2171
- error: `The process exited immediately (exit code ${code}) \u2014 it did not stay running, so nothing was started or tracked.` + (tail ? `
2177
+ error: `The process exited immediately (exit code ${code}) \u2014 it did not stay running, so nothing was started or tracked.` + (tail2 ? `
2172
2178
 
2173
2179
  Output:
2174
- ${tail}` : " No output was captured.")
2180
+ ${tail2}` : " No output was captured.")
2175
2181
  };
2176
2182
  }
2177
2183
  child.removeListener("exit", onEarlyExit);
@@ -2288,8 +2294,8 @@ ${tail}` : " No output was captured.")
2288
2294
  } catch {
2289
2295
  return { ok: true, result: `(no output captured yet for ${id})` };
2290
2296
  }
2291
- const tail = content.split("\n").slice(-maxLines).join("\n");
2292
- return { ok: true, result: tail || `(no output yet for ${id})` };
2297
+ const tail2 = content.split("\n").slice(-maxLines).join("\n");
2298
+ return { ok: true, result: tail2 || `(no output yet for ${id})` };
2293
2299
  }
2294
2300
  if (action === "stop") {
2295
2301
  if (proc.status !== "running") {
@@ -2673,7 +2679,7 @@ var McpManager = class {
2673
2679
  }
2674
2680
  }
2675
2681
  closeAll() {
2676
- for (const [, c4] of this.clients) c4.close();
2682
+ for (const [, c5] of this.clients) c5.close();
2677
2683
  this.clients.clear();
2678
2684
  }
2679
2685
  get(name) {
@@ -2684,13 +2690,13 @@ var McpManager = class {
2684
2690
  }
2685
2691
  toolCount() {
2686
2692
  let n = 0;
2687
- for (const [, c4] of this.clients) n += c4.tools.length;
2693
+ for (const [, c5] of this.clients) n += c5.tools.length;
2688
2694
  return n;
2689
2695
  }
2690
2696
  /** A JSON-serializable catalog of every connected server for the KV/context. */
2691
2697
  catalog() {
2692
2698
  return {
2693
- servers: Array.from(this.clients.values()).map((c4) => c4.catalogEntry()),
2699
+ servers: Array.from(this.clients.values()).map((c5) => c5.catalogEntry()),
2694
2700
  generatedAt: Date.now()
2695
2701
  };
2696
2702
  }
@@ -2780,9 +2786,9 @@ function flattenContent(content, structured) {
2780
2786
  }
2781
2787
  function flattenResourceContents(contents) {
2782
2788
  const parts = [];
2783
- for (const c4 of contents || []) {
2784
- if (typeof c4.text === "string") parts.push(c4.text);
2785
- else if (typeof c4.blob === "string") parts.push(`[binary resource ${String(c4.uri ?? "")} (${c4.blob.length} b64 chars)]`);
2789
+ for (const c5 of contents || []) {
2790
+ if (typeof c5.text === "string") parts.push(c5.text);
2791
+ else if (typeof c5.blob === "string") parts.push(`[binary resource ${String(c5.uri ?? "")} (${c5.blob.length} b64 chars)]`);
2786
2792
  }
2787
2793
  return parts.join("\n").trim();
2788
2794
  }
@@ -3593,8 +3599,8 @@ function parseStreamingMarkdown(source) {
3593
3599
  let codeEnd = closing?.start ?? source.length;
3594
3600
  if (!closing) {
3595
3601
  const tailStart = Math.max(codeStart, source.lastIndexOf("\n") + 1);
3596
- const tail = source.slice(tailStart);
3597
- const pending = /^ {0,3}(`+|~+)[ \t]*$/.exec(tail);
3602
+ const tail2 = source.slice(tailStart);
3603
+ const pending = /^ {0,3}(`+|~+)[ \t]*$/.exec(tail2);
3598
3604
  if (pending && pending[1][0] === opening.marker && pending[1].length < opening.length) codeEnd = tailStart;
3599
3605
  }
3600
3606
  if (codeEnd > codeStart && source[codeEnd - 1] === "\n") codeEnd--;
@@ -3813,17 +3819,17 @@ function wrapBlock(out, cols2, leadFirst, leadRest, leadWidth, text) {
3813
3819
  function renderTable(rows) {
3814
3820
  const cols2 = Math.max(...rows.map((r) => r.length));
3815
3821
  const widths = [];
3816
- for (let c4 = 0; c4 < cols2; c4++) {
3817
- widths[c4] = Math.max(...rows.map((r) => visibleWidth(inline(r[c4] ?? ""))));
3822
+ for (let c5 = 0; c5 < cols2; c5++) {
3823
+ widths[c5] = Math.max(...rows.map((r) => visibleWidth(inline(r[c5] ?? ""))));
3818
3824
  }
3819
3825
  const sep = `${GRAY} \u2502 ${R}`;
3820
3826
  const out = [];
3821
3827
  rows.forEach((r, ri) => {
3822
3828
  const cells = [];
3823
- for (let c4 = 0; c4 < cols2; c4++) {
3824
- const raw = r[c4] ?? "";
3829
+ for (let c5 = 0; c5 < cols2; c5++) {
3830
+ const raw = r[c5] ?? "";
3825
3831
  const styled = ri === 0 ? `${BOLD}${inline(raw)}${R}` : inline(raw);
3826
- cells.push(padEndVisible(styled, widths[c4]));
3832
+ cells.push(padEndVisible(styled, widths[c5]));
3827
3833
  }
3828
3834
  out.push((" " + cells.join(sep)).replace(/\s+$/, ""));
3829
3835
  if (ri === 0) {
@@ -4260,7 +4266,11 @@ function buildInputBoxRows(opts) {
4260
4266
  if (phase == null || !Number.isFinite(phase)) {
4261
4267
  const dots2 = borderLevelDots(level);
4262
4268
  const dotsStyled2 = borderLevelDotsStyled(level, levelColor);
4263
- const top2 = buildBoxTop(geo.boxW, dots2, borderColor, levelColor, "\x1B[0m", dotsStyled2);
4269
+ const agent2 = (opts.agentLabel ?? "").trim();
4270
+ const withAgent2 = agent2.length > 0 && ` ${agent2} ${dots2} `.length <= geo.boxW - 3;
4271
+ const levelText = withAgent2 ? `${agent2} ${dots2}` : dots2;
4272
+ const levelStyled = withAgent2 ? `\x1B[2m${themeGray}${agent2}\x1B[0m ${dotsStyled2}` : dotsStyled2;
4273
+ const top2 = buildBoxTop(geo.boxW, levelText, borderColor, levelColor, "\x1B[0m", levelStyled);
4264
4274
  const bottom2 = buildBoxBottom(geo.boxW, borderColor);
4265
4275
  return [pad + top2, ...body.map((b) => pad + buildBoxBody(b, geo.contentW, borderColor)), pad + bottom2];
4266
4276
  }
@@ -4271,7 +4281,11 @@ function buildInputBoxRows(opts) {
4271
4281
  const colorAt = (i) => rotatingBorderCellColor(i, P, phase);
4272
4282
  const dots = borderLevelDots(level);
4273
4283
  borderLevelDotsStyled(level, levelColor);
4274
- const labelPlain = ` ${dots} `;
4284
+ const agent = (opts.agentLabel ?? "").trim();
4285
+ const withAgent = agent.length > 0 && ` ${agent} ${dots} `.length <= W - 3;
4286
+ const labelPlain = withAgent ? ` ${agent} ${dots} ` : ` ${dots} `;
4287
+ const agentSpanStart = withAgent ? 1 : -1;
4288
+ const agentSpanEnd = withAgent ? 1 + agent.length : -1;
4275
4289
  const rightFillN = 1;
4276
4290
  const budget = Math.max(1, W - 2 - rightFillN);
4277
4291
  const plain = labelPlain.length > budget ? labelPlain.slice(0, Math.max(1, budget)) : labelPlain;
@@ -4285,15 +4299,20 @@ function buildInputBoxRows(opts) {
4285
4299
  {
4286
4300
  const plainChars = [...plain];
4287
4301
  let di = 0;
4288
- for (const ch of plainChars) {
4289
- const c4 = colorAt(perimeterIndex("top", xi++, W, bodyH));
4302
+ for (let ci = 0; ci < plainChars.length; ci++) {
4303
+ const ch = plainChars[ci];
4304
+ const c5 = colorAt(perimeterIndex("top", xi++, W, bodyH));
4305
+ if (ci >= agentSpanStart && ci < agentSpanEnd) {
4306
+ top += `\x1B[2m${themeGray}${ch}\x1B[0m`;
4307
+ continue;
4308
+ }
4290
4309
  if (ch === "\u25CF" || ch === "\u25CB") {
4291
4310
  const levelN = Math.max(1, Math.min(5, level));
4292
4311
  const filled = di < levelN;
4293
4312
  di++;
4294
- top += (filled ? levelColor || c4 : "\x1B[38;5;240m") + (filled ? "\u25CF" : "\u25CB") + reset;
4313
+ top += (filled ? levelColor || c5 : "\x1B[38;5;240m") + (filled ? "\u25CF" : "\u25CB") + reset;
4295
4314
  } else {
4296
- top += c4 + ch + reset;
4315
+ top += c5 + ch + reset;
4297
4316
  }
4298
4317
  }
4299
4318
  }
@@ -4314,10 +4333,10 @@ function buildInputBoxRows(opts) {
4314
4333
  let bottom = "";
4315
4334
  for (let x = 0; x < W; x++) {
4316
4335
  const idx = perimeterIndex("bottom", W - 1 - x, W, bodyH);
4317
- const c4 = colorAt(idx);
4318
- if (x === 0) bottom += c4 + "\u2570" + reset;
4319
- else if (x === W - 1) bottom += c4 + "\u256F" + reset;
4320
- else bottom += c4 + "\u2500" + reset;
4336
+ const c5 = colorAt(idx);
4337
+ if (x === 0) bottom += c5 + "\u2570" + reset;
4338
+ else if (x === W - 1) bottom += c5 + "\u256F" + reset;
4339
+ else bottom += c5 + "\u2500" + reset;
4321
4340
  }
4322
4341
  return [pad + top, ...bodyRows, pad + bottom];
4323
4342
  }
@@ -4334,6 +4353,36 @@ var C = {
4334
4353
  gray: themeGray,
4335
4354
  teal: "\x1B[38;5;37m"
4336
4355
  };
4356
+ function renderSelectItemRows(it, sel, contentW, busyGlyph) {
4357
+ const hint = it.hint ?? "";
4358
+ const hintW = boxVisibleWidth(hint);
4359
+ const pointerW = 2;
4360
+ const labelMax = Math.max(4, contentW - pointerW - (hintW ? hintW + 2 : 0));
4361
+ let label = it.label.replace(/\s+/g, " ").trim();
4362
+ if (boxVisibleWidth(label) > labelMax) {
4363
+ while (label.length > 0 && boxVisibleWidth(label) > labelMax - 1) label = label.slice(0, -1);
4364
+ label += "\u2026";
4365
+ }
4366
+ const pointer = busyGlyph ? `${busyGlyph} ` : sel ? `${C.magenta}\u276F${C.reset} ` : " ";
4367
+ const styledLabel = it.disabled ? `${C.gray}${label}${C.reset}` : sel ? `${C.bold}${C.cyan}${label}${C.reset}` : `${C.dim}${label}${C.reset}`;
4368
+ let content = `${pointer}${styledLabel}`;
4369
+ if (hintW) {
4370
+ const used = pointerW + boxVisibleWidth(label);
4371
+ const gap = Math.max(1, contentW - used - hintW);
4372
+ content += `${" ".repeat(gap)}${sel ? C.gray : C.dim}${hint}${C.reset}`;
4373
+ }
4374
+ const rows = [content];
4375
+ if (it.detail) {
4376
+ let detail = it.detail.replace(/\s+/g, " ").trim();
4377
+ const detailMax = Math.max(4, contentW - pointerW - 1);
4378
+ if (boxVisibleWidth(detail) > detailMax) {
4379
+ while (detail.length > 0 && boxVisibleWidth(detail) > detailMax - 1) detail = detail.slice(0, -1);
4380
+ detail += "\u2026";
4381
+ }
4382
+ rows.push(`${" ".repeat(pointerW)}${sel ? `${C.dim}${C.cyan}` : `${C.dim}${C.gray}`}${detail}${C.reset}`);
4383
+ }
4384
+ return rows;
4385
+ }
4337
4386
  var SYNC_OUTPUT_BEGIN = "\x1B[?2026h";
4338
4387
  var SYNC_OUTPUT_END = "\x1B[?2026l";
4339
4388
  var CURSOR_HIDE = "\x1B[?25l";
@@ -4486,7 +4535,7 @@ var Tui = class _Tui {
4486
4535
  // event hooks (wired by index.ts)
4487
4536
  onSubmit = () => {
4488
4537
  };
4489
- /** Shift+Return sends a steering input through the portable messaging endpoint. */
4538
+ /** Option/Alt+Return sends a steering input through the portable messaging endpoint. */
4490
4539
  onSteer = () => {
4491
4540
  };
4492
4541
  onInterrupt = () => {
@@ -4584,7 +4633,7 @@ var Tui = class _Tui {
4584
4633
  dispatch(str, key) {
4585
4634
  const seq = key && key.sequence || str || "";
4586
4635
  if (seq === "\n" || seq === "\x1B[13;2u" || seq === "\x1B[27;2;13~") {
4587
- if (!this.takeoverHandler && !this.pasting && !this.paletteOpen()) this.submitInput(true);
4636
+ if (!this.takeoverHandler && !this.pasting && !this.paletteOpen()) this.insertAtCursor("\n");
4588
4637
  return;
4589
4638
  }
4590
4639
  if (key && key.ctrl && key.name === "c") {
@@ -4717,11 +4766,11 @@ var Tui = class _Tui {
4717
4766
  }
4718
4767
  if (key.name === "return" || key.name === "enter") {
4719
4768
  if (key.meta) {
4720
- this.insertAtCursor("\n");
4769
+ this.submitInput(true);
4721
4770
  return;
4722
4771
  }
4723
4772
  if (key.shift) {
4724
- this.submitInput(true);
4773
+ this.insertAtCursor("\n");
4725
4774
  return;
4726
4775
  }
4727
4776
  this.submitInput(false);
@@ -5022,7 +5071,7 @@ var Tui = class _Tui {
5022
5071
  const q = this.inputBuffer.slice(1).trim().toLowerCase();
5023
5072
  if (q === "") return this.commands;
5024
5073
  return this.commands.filter(
5025
- (c4) => c4.name.startsWith(q) || c4.name.includes(q) || c4.label.toLowerCase().includes(q)
5074
+ (c5) => c5.name.startsWith(q) || c5.name.includes(q) || c5.label.toLowerCase().includes(q)
5026
5075
  );
5027
5076
  }
5028
5077
  runCommand(cmd) {
@@ -5276,7 +5325,8 @@ var Tui = class _Tui {
5276
5325
  level: this.level,
5277
5326
  levelColor: this.levelColor(),
5278
5327
  borderColor: inputBoxBorderColor(),
5279
- borderPhase
5328
+ borderPhase,
5329
+ agentLabel: this.agentLabel
5280
5330
  });
5281
5331
  const boxTop = boxRows[0];
5282
5332
  const boxBottom = boxRows[boxRows.length - 1];
@@ -5550,6 +5600,14 @@ var Tui = class _Tui {
5550
5600
  });
5551
5601
  this.print("");
5552
5602
  }
5603
+ // ─── agent label (under the input box) ─────────────────────────────────────
5604
+ /** The loaded agent's display title, shown low-contrast under the input. */
5605
+ agentLabel = "";
5606
+ setAgentLabel(label) {
5607
+ if (label === this.agentLabel) return;
5608
+ this.agentLabel = label;
5609
+ this.renderBottom();
5610
+ }
5553
5611
  // ─── working indicator (turn state) ───────────────────────────────────────
5554
5612
  setWorking(on) {
5555
5613
  if (on === this.working) return;
@@ -5930,8 +5988,13 @@ var Tui = class _Tui {
5930
5988
  * Arrow-key selection menu (slash menu, process menu, resume). Pauses input.
5931
5989
  * Boxed chrome matches the bottom input HUD: side margin + grey rounded
5932
5990
  * border, selected row with a brand-tinted pointer.
5991
+ *
5992
+ * An item may carry a `detail` — a dim second physical line under its label
5993
+ * for context that doesn't fit the one-row label+hint layout (e.g. a
5994
+ * machine's hostname · daemon status). `opts.spaced` inserts a blank row
5995
+ * between items for readability in short, dense menus.
5933
5996
  */
5934
- select(title, items) {
5997
+ select(title, items, opts) {
5935
5998
  return new Promise((resolve) => {
5936
5999
  let idx = Math.max(0, items.findIndex((it) => !it.disabled));
5937
6000
  this.beginTakeover();
@@ -5944,41 +6007,28 @@ var Tui = class _Tui {
5944
6007
  ${pad}${title}
5945
6008
  `);
5946
6009
  else process.stdout.write("\n");
5947
- const renderItemContent = (i) => {
5948
- const it = items[i];
5949
- const sel = i === idx;
5950
- const hint = it.hint ?? "";
5951
- const hintW = hint.length;
5952
- const pointerW = 2;
5953
- const labelMax = Math.max(4, geo.contentW - pointerW - (hintW ? hintW + 2 : 0));
5954
- let label = it.label.replace(/\s+/g, " ").trim();
5955
- if (label.length > labelMax) label = label.slice(0, Math.max(0, labelMax - 1)) + "\u2026";
5956
- const pointer = sel ? `${C.magenta}\u276F${C.reset} ` : " ";
5957
- const styledLabel = it.disabled ? `${C.gray}${label}${C.reset}` : sel ? `${C.bold}${C.cyan}${label}${C.reset}` : `${C.dim}${label}${C.reset}`;
5958
- let content = `${pointer}${styledLabel}`;
5959
- if (hintW) {
5960
- const used = pointerW + label.length;
5961
- const gap = Math.max(1, geo.contentW - used - hintW);
5962
- content += `${" ".repeat(gap)}${sel ? C.gray : C.dim}${hint}${C.reset}`;
5963
- }
5964
- return content;
5965
- };
6010
+ const renderItemRows = (i) => renderSelectItemRows(items[i], i === idx, geo.contentW);
5966
6011
  const writeRow = (line) => {
5967
6012
  const row = this.clampVisible(sanitizeHudRow(line), rowCap);
5968
6013
  process.stdout.write(`\r\x1B[K${row}
5969
6014
  `);
5970
6015
  };
6016
+ const spaced = opts?.spaced ?? false;
6017
+ const contentRows = items.reduce((n, it) => n + 1 + (it.detail ? 1 : 0), 0) + (spaced ? Math.max(0, items.length - 1) : 0);
5971
6018
  const draw = (moveUp) => {
5972
- if (moveUp) process.stdout.write(`\x1B[${items.length + 1}A`);
6019
+ if (moveUp) process.stdout.write(`\x1B[${contentRows + 1}A`);
5973
6020
  else writeRow(pad + buildBoxTopPlain(geo.boxW, border));
5974
6021
  for (let i = 0; i < items.length; i++) {
5975
- writeRow(pad + buildBoxBody(renderItemContent(i), geo.contentW, border));
6022
+ if (spaced && i > 0) writeRow(pad + buildBoxBody("", geo.contentW, border));
6023
+ for (const row of renderItemRows(i)) {
6024
+ writeRow(pad + buildBoxBody(row, geo.contentW, border));
6025
+ }
5976
6026
  }
5977
6027
  writeRow(pad + buildBoxBottom(geo.boxW, border));
5978
6028
  };
5979
6029
  draw(false);
5980
6030
  const titleRows = title ? 2 : 1;
5981
- const boxRows = 1 + items.length + 1;
6031
+ const boxRows = 1 + contentRows + 1;
5982
6032
  const erase = () => {
5983
6033
  process.stdout.write("\r");
5984
6034
  const up = titleRows + boxRows;
@@ -6006,6 +6056,182 @@ ${pad}${title}
6006
6056
  };
6007
6057
  });
6008
6058
  }
6059
+ /**
6060
+ * A select menu that stays OPEN while its contents change — the primitive
6061
+ * behind in-place navigation UIs like the directory browser. Instead of
6062
+ * resolving once like `select`, it returns a handle:
6063
+ *
6064
+ * - `next()` awaits the next interaction: a pick, a registered extra key
6065
+ * (e.g. "." to toggle hidden files), or Escape (cancel).
6066
+ * - `update()` swaps title/items/footer and redraws the SAME screen region
6067
+ * (erase + repaint in one write), so navigating levels never feels like
6068
+ * leaving the menu.
6069
+ * - `setBusy(i)` animates a spinner in row i's pointer column while the
6070
+ * caller loads what's behind it; input (except Escape) is swallowed until
6071
+ * the next update()/setBusy(null).
6072
+ * - `close()` erases the region and releases the takeover.
6073
+ *
6074
+ * The optional `footer` is a dim line under the box — the home for key
6075
+ * hints and transient error text.
6076
+ */
6077
+ openLiveSelect(opts) {
6078
+ let { title, items } = opts;
6079
+ let footer = opts.footer ?? "";
6080
+ const spaced = opts.spaced ?? false;
6081
+ const extraKeys = opts.keys ?? [];
6082
+ let idx = Math.max(0, items.findIndex((it) => !it.disabled));
6083
+ let busyIdx = null;
6084
+ let busyTimer = null;
6085
+ let closed = false;
6086
+ let edit = null;
6087
+ this.beginTakeover();
6088
+ const cols2 = process.stdout.columns || 80;
6089
+ const geo = inputBoxGeometry(cols2);
6090
+ const border = inputBoxBorderColor();
6091
+ const pad = " ".repeat(geo.margin);
6092
+ const rowCap = Math.max(1, cols2 - 1);
6093
+ let regionRows = 0;
6094
+ let lifted = 0;
6095
+ const draw = () => {
6096
+ if (closed) return;
6097
+ const rows = [""];
6098
+ let editCursor = null;
6099
+ if (title) rows.push(pad + title);
6100
+ rows.push(pad + buildBoxTopPlain(geo.boxW, border));
6101
+ for (let i = 0; i < items.length; i++) {
6102
+ const it = items[i];
6103
+ if (spaced && i > 0) rows.push(pad + buildBoxBody("", geo.contentW, border));
6104
+ const glyph = busyIdx === i ? this.spinnerFrame() : void 0;
6105
+ if (edit && edit.index === i) {
6106
+ const pointer = `${glyph ?? `${C.magenta}\u276F${C.reset}`} `;
6107
+ const max = Math.max(4, geo.contentW - 2 - boxVisibleWidth(edit.prefix) - 1);
6108
+ let buf = edit.buf;
6109
+ while (buf.length > 0 && boxVisibleWidth(buf) > max) buf = buf.slice(1);
6110
+ const body = buf ? `${C.bold}${C.cyan}${buf}${C.reset}` : `${C.dim}${edit.placeholder}${C.reset}`;
6111
+ rows.push(pad + buildBoxBody(`${pointer}${C.dim}${edit.prefix}${C.reset}${body}`, geo.contentW, border));
6112
+ if (!glyph) {
6113
+ editCursor = {
6114
+ rowIndex: rows.length - 1,
6115
+ // 1-based ANSI column: margin + "│ " + pointer(2) + prefix + typed text.
6116
+ col: geo.margin + 2 + 2 + boxVisibleWidth(edit.prefix) + boxVisibleWidth(buf) + 1
6117
+ };
6118
+ }
6119
+ continue;
6120
+ }
6121
+ for (const r of renderSelectItemRows(it, i === idx, geo.contentW, glyph)) {
6122
+ rows.push(pad + buildBoxBody(r, geo.contentW, border));
6123
+ }
6124
+ }
6125
+ rows.push(pad + buildBoxBottom(geo.boxW, border));
6126
+ if (footer) rows.push(pad + footer);
6127
+ let out = lifted > 0 ? `\x1B[${lifted}B` : "";
6128
+ lifted = 0;
6129
+ out += regionRows > 0 ? `\r\x1B[${regionRows}A\x1B[J` : "";
6130
+ for (const line of rows) out += `\r\x1B[K${this.clampVisible(sanitizeHudRow(line), rowCap)}
6131
+ `;
6132
+ if (editCursor) {
6133
+ lifted = rows.length - editCursor.rowIndex;
6134
+ out += `\x1B[${lifted}A\x1B[${editCursor.col}G\x1B[?25h`;
6135
+ } else {
6136
+ out += "\x1B[?25l";
6137
+ }
6138
+ process.stdout.write(out);
6139
+ regionRows = rows.length;
6140
+ };
6141
+ draw();
6142
+ const queue = [];
6143
+ let waiter = null;
6144
+ const emit = (e) => {
6145
+ if (waiter) {
6146
+ const w = waiter;
6147
+ waiter = null;
6148
+ w(e);
6149
+ } else queue.push(e);
6150
+ };
6151
+ const stopSpinner = () => {
6152
+ if (busyTimer) clearInterval(busyTimer);
6153
+ busyTimer = null;
6154
+ busyIdx = null;
6155
+ };
6156
+ this.takeoverHandler = (str, key) => {
6157
+ if (edit && busyIdx === null) {
6158
+ if (key?.name === "escape") {
6159
+ edit = null;
6160
+ draw();
6161
+ } else if (key?.name === "return" || key?.name === "enter") {
6162
+ const text = edit.buf.trim();
6163
+ if (!text) {
6164
+ edit = null;
6165
+ draw();
6166
+ } else {
6167
+ emit({ kind: "edit", text, index: edit.index });
6168
+ }
6169
+ } else if (key?.name === "backspace") {
6170
+ edit.buf = edit.buf.slice(0, -1);
6171
+ draw();
6172
+ } else if (str && !key?.ctrl && !key?.meta) {
6173
+ const clean2 = str.replace(/[\x00-\x1f\x7f]/g, "");
6174
+ if (clean2) {
6175
+ edit.buf += clean2;
6176
+ draw();
6177
+ }
6178
+ }
6179
+ return;
6180
+ }
6181
+ if (key?.name === "escape") return emit({ kind: "cancel" });
6182
+ if (busyIdx !== null) return;
6183
+ if (key?.name === "up" || str === "k") {
6184
+ idx = (idx - 1 + items.length) % items.length;
6185
+ draw();
6186
+ } else if (key?.name === "down" || str === "j") {
6187
+ idx = (idx + 1) % items.length;
6188
+ draw();
6189
+ } else if (key?.name === "return" || key?.name === "enter") {
6190
+ if (!items[idx]?.disabled) emit({ kind: "pick", value: items[idx].value, index: idx });
6191
+ } else if (str && extraKeys.includes(str)) {
6192
+ emit({ kind: "key", name: str, index: idx });
6193
+ }
6194
+ };
6195
+ return {
6196
+ next: () => queue.length > 0 ? Promise.resolve(queue.shift()) : new Promise((r) => waiter = r),
6197
+ update: (state) => {
6198
+ stopSpinner();
6199
+ edit = null;
6200
+ if (state.title !== void 0) title = state.title;
6201
+ if (state.items) {
6202
+ items = state.items;
6203
+ if (idx >= items.length || items[idx]?.disabled) {
6204
+ idx = Math.max(0, items.findIndex((it) => !it.disabled));
6205
+ }
6206
+ }
6207
+ if (state.footer !== void 0) footer = state.footer;
6208
+ draw();
6209
+ },
6210
+ setBusy: (index) => {
6211
+ stopSpinner();
6212
+ if (index !== null) {
6213
+ busyIdx = index;
6214
+ busyTimer = setInterval(draw, SPINNER_FRAME_MS);
6215
+ }
6216
+ draw();
6217
+ },
6218
+ beginEdit: (index, editOpts) => {
6219
+ stopSpinner();
6220
+ edit = { index, buf: "", placeholder: editOpts?.placeholder ?? "", prefix: editOpts?.prefix ?? "" };
6221
+ idx = index;
6222
+ draw();
6223
+ },
6224
+ close: () => {
6225
+ if (closed) return;
6226
+ closed = true;
6227
+ stopSpinner();
6228
+ if (lifted > 0) process.stdout.write(`\x1B[${lifted}B`);
6229
+ lifted = 0;
6230
+ if (regionRows > 0) process.stdout.write(`\r\x1B[${regionRows}A\x1B[J`);
6231
+ this.endTakeover();
6232
+ }
6233
+ };
6234
+ }
6009
6235
  /**
6010
6236
  * Free-text prompt (single line). Pauses the main input and reads a line —
6011
6237
  * used where a menu can't, e.g. entering an MCP server command. Enter submits,
@@ -6257,6 +6483,7 @@ var NAME_SUFFIX = ".name";
6257
6483
  var ICON_SUFFIX = ".icon";
6258
6484
  var FSREQ_SUFFIX = ".fsreq";
6259
6485
  var FSRES_SUFFIX = ".fsres";
6486
+ var PROJNAMES_SUFFIX = ".projnames";
6260
6487
  var DAEMON_ONLINE_WINDOW_MS = 90 * 1e3;
6261
6488
  function machineKey(machineId) {
6262
6489
  return `${KEY_PREFIX}${machineId}`;
@@ -6270,6 +6497,9 @@ function nameKey(machineId) {
6270
6497
  function iconKey(machineId) {
6271
6498
  return `${KEY_PREFIX}${machineId}${ICON_SUFFIX}`;
6272
6499
  }
6500
+ function projectNamesKey(machineId) {
6501
+ return `${KEY_PREFIX}${machineId}${PROJNAMES_SUFFIX}`;
6502
+ }
6273
6503
  function fsRequestKey(machineId) {
6274
6504
  return `${KEY_PREFIX}${machineId}${FSREQ_SUFFIX}`;
6275
6505
  }
@@ -6312,25 +6542,6 @@ async function readFsRequest(api, machineId) {
6312
6542
  async function writeFsResponse(api, machineId, res) {
6313
6543
  await api.userKvSet(fsResponseKey(machineId), { ...res, responded_at: Date.now() });
6314
6544
  }
6315
- async function resolveRemoteProjectPath(api, machineId, typedPath, timeoutMs = 12e3) {
6316
- const trimmed = typedPath.trim();
6317
- if (!trimmed.startsWith("~")) return trimmed;
6318
- const nonce = crypto.randomBytes(8).toString("hex");
6319
- try {
6320
- await writeFsRequest(api, machineId, { nonce, op: "list", path: trimmed });
6321
- const deadline = Date.now() + timeoutMs;
6322
- while (Date.now() < deadline) {
6323
- await new Promise((r) => setTimeout(r, 700));
6324
- const res = await readFsResponse(api, machineId).catch(() => null);
6325
- if (res && res.nonce === nonce) {
6326
- const resolved = res.result && typeof res.result.path === "string" ? res.result.path : "";
6327
- return resolved || trimmed;
6328
- }
6329
- }
6330
- } catch {
6331
- }
6332
- return trimmed;
6333
- }
6334
6545
  function machineIcon(record2) {
6335
6546
  if (record2.icon && record2.icon.trim()) return record2.icon.trim();
6336
6547
  if (record2.platform === "darwin") return "\u{1F4BB}";
@@ -6360,7 +6571,10 @@ function parseMachineRecord(value) {
6360
6571
  arch: typeof r.arch === "string" ? r.arch : "",
6361
6572
  version: typeof r.version === "string" ? r.version : void 0,
6362
6573
  daemon: r.daemon && typeof r.daemon === "object" && !Array.isArray(r.daemon) ? r.daemon : null,
6363
- projects: r.projects && typeof r.projects === "object" && !Array.isArray(r.projects) ? r.projects : {},
6574
+ // Clone rather than alias the caller's object: read-side overlays (e.g.
6575
+ // user-assigned project names) mutate the parsed record's entries and
6576
+ // must never write through into the source value.
6577
+ projects: r.projects && typeof r.projects === "object" && !Array.isArray(r.projects) ? structuredClone(r.projects) : {},
6364
6578
  created_at: typeof r.created_at === "number" ? r.created_at : 0,
6365
6579
  updated_at: typeof r.updated_at === "number" ? r.updated_at : 0
6366
6580
  };
@@ -6373,6 +6587,7 @@ async function loadMachines(api) {
6373
6587
  const records = [];
6374
6588
  const names = /* @__PURE__ */ new Map();
6375
6589
  const icons = /* @__PURE__ */ new Map();
6590
+ const projNames = /* @__PURE__ */ new Map();
6376
6591
  for (const e of entries) {
6377
6592
  const rest = e.key.slice(KEY_PREFIX.length);
6378
6593
  if (rest.endsWith(CMD_SUFFIX) || rest.endsWith(FSREQ_SUFFIX) || rest.endsWith(FSRES_SUFFIX)) continue;
@@ -6386,6 +6601,12 @@ async function loadMachines(api) {
6386
6601
  if (typeof e.value === "string" && e.value.trim()) icons.set(id, e.value.trim());
6387
6602
  continue;
6388
6603
  }
6604
+ if (rest.endsWith(PROJNAMES_SUFFIX)) {
6605
+ const id = rest.slice(0, -PROJNAMES_SUFFIX.length);
6606
+ const map = parseProjectNames(e.value);
6607
+ if (map) projNames.set(id, map);
6608
+ continue;
6609
+ }
6389
6610
  const rec = parseMachineRecord(e.value);
6390
6611
  if (rec) records.push(rec);
6391
6612
  }
@@ -6394,9 +6615,58 @@ async function loadMachines(api) {
6394
6615
  if (override) rec.name = override;
6395
6616
  const icon = icons.get(rec.id);
6396
6617
  if (icon) rec.icon = icon;
6618
+ overlayProjectNames(rec, projNames.get(rec.id));
6397
6619
  }
6398
6620
  return records;
6399
6621
  }
6622
+ function parseProjectNames(value) {
6623
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
6624
+ const out = {};
6625
+ for (const [dir2, name] of Object.entries(value)) {
6626
+ if (typeof name === "string" && name.trim()) out[dir2] = name.trim();
6627
+ }
6628
+ return out;
6629
+ }
6630
+ function overlayProjectNames(record2, names) {
6631
+ if (!names) return;
6632
+ for (const [dir2, name] of Object.entries(names)) {
6633
+ const project = record2.projects[dir2];
6634
+ if (project) project.name = name;
6635
+ }
6636
+ }
6637
+ function projectDisplayName(dir2, project) {
6638
+ return project?.name?.trim() || path4.basename(dir2) || dir2;
6639
+ }
6640
+ function projectDisplayLabels(projects) {
6641
+ const names = /* @__PURE__ */ new Map();
6642
+ const counts = /* @__PURE__ */ new Map();
6643
+ for (const [dir2, p] of Object.entries(projects)) {
6644
+ const n = projectDisplayName(dir2, p);
6645
+ names.set(dir2, n);
6646
+ counts.set(n, (counts.get(n) ?? 0) + 1);
6647
+ }
6648
+ const out = /* @__PURE__ */ new Map();
6649
+ for (const [dir2, n] of names) {
6650
+ if ((counts.get(n) ?? 0) > 1) {
6651
+ const parts = dir2.split(/[\\/]/).filter(Boolean);
6652
+ const parent = parts.length > 1 ? parts[parts.length - 2] : "";
6653
+ out.set(dir2, parent ? `${parent}/${n}` : n);
6654
+ } else {
6655
+ out.set(dir2, n);
6656
+ }
6657
+ }
6658
+ return out;
6659
+ }
6660
+ async function getProjectNames(api, machineId) {
6661
+ return parseProjectNames(await api.userKvGet(projectNamesKey(machineId))) ?? {};
6662
+ }
6663
+ async function setProjectName(api, machineId, dir2, name) {
6664
+ const map = await getProjectNames(api, machineId);
6665
+ const trimmed = name.trim();
6666
+ if (trimmed) map[dir2] = trimmed;
6667
+ else delete map[dir2];
6668
+ await api.userKvSet(projectNamesKey(machineId), Object.keys(map).length ? map : null);
6669
+ }
6400
6670
  async function getMachineIcon(api, machineId) {
6401
6671
  const v = await api.userKvGet(iconKey(machineId));
6402
6672
  return typeof v === "string" && v.trim() ? v.trim() : null;
@@ -6408,9 +6678,14 @@ async function setMachineIcon(api, machineId, icon) {
6408
6678
  async function loadMachine(api, machineId) {
6409
6679
  const rec = await loadRawMachine(api, machineId);
6410
6680
  if (!rec) return null;
6411
- const [override, icon] = await Promise.all([getMachineName(api, machineId), getMachineIcon(api, machineId)]);
6681
+ const [override, icon, projNames] = await Promise.all([
6682
+ getMachineName(api, machineId),
6683
+ getMachineIcon(api, machineId),
6684
+ getProjectNames(api, machineId)
6685
+ ]);
6412
6686
  if (override) rec.name = override;
6413
6687
  if (icon) rec.icon = icon;
6688
+ overlayProjectNames(rec, projNames);
6414
6689
  return rec;
6415
6690
  }
6416
6691
  function daemonOnline(record2, now = Date.now()) {
@@ -6508,7 +6783,7 @@ async function clearDaemon(api, identity) {
6508
6783
  function parseCommands(value) {
6509
6784
  if (!Array.isArray(value)) return [];
6510
6785
  return value.filter(
6511
- (c4) => !!c4 && typeof c4 === "object" && typeof c4.id === "string" && typeof c4.kind === "string"
6786
+ (c5) => !!c5 && typeof c5 === "object" && typeof c5.id === "string" && typeof c5.kind === "string"
6512
6787
  );
6513
6788
  }
6514
6789
  async function enqueueMachineCommand(api, machineId, kind, args) {
@@ -6531,7 +6806,7 @@ async function clearMachineCommands(api, machineId, appliedIds) {
6531
6806
  if (appliedIds.length === 0) return;
6532
6807
  const key = commandKey(machineId);
6533
6808
  const remaining = parseCommands(await api.userKvGet(key)).filter(
6534
- (c4) => !appliedIds.includes(c4.id)
6809
+ (c5) => !appliedIds.includes(c5.id)
6535
6810
  );
6536
6811
  await api.userKvSet(key, remaining.length ? remaining : null);
6537
6812
  }
@@ -6555,74 +6830,358 @@ async function applyMachineCommand(api, identity, cmd) {
6555
6830
  return `unknown command ${cmd.kind}`;
6556
6831
  }
6557
6832
  }
6558
-
6559
- // src/relay.ts
6560
- var APPROVAL_REQUEST_KEY = "approval_request";
6561
- var APPROVAL_RESPONSE_KEY = "approval_response";
6562
- function parseApprovalRequest(value) {
6563
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
6564
- const r = value;
6565
- if (typeof r.tool_call_id !== "string" || typeof r.tool !== "string") return null;
6566
- return {
6567
- tool_call_id: r.tool_call_id,
6568
- tool: r.tool,
6569
- summary: typeof r.summary === "string" ? r.summary : r.tool,
6570
- permission: typeof r.permission === "string" ? r.permission : null,
6571
- risk: typeof r.risk === "number" ? r.risk : 3,
6572
- machine: typeof r.machine === "string" ? r.machine : "",
6573
- requested_at: typeof r.requested_at === "number" ? r.requested_at : 0
6574
- };
6575
- }
6576
- function parseApprovalResponse(value) {
6577
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
6578
- const r = value;
6579
- if (typeof r.tool_call_id !== "string" || typeof r.choice !== "string") return null;
6580
- if (!["allow", "deny", "always", "always_risk"].includes(r.choice)) return null;
6581
- return {
6582
- tool_call_id: r.tool_call_id,
6583
- choice: r.choice,
6584
- reason: typeof r.reason === "string" ? r.reason : void 0,
6585
- decided_at: typeof r.decided_at === "number" ? r.decided_at : 0
6586
- };
6833
+ var PROJECT_MARKERS = [
6834
+ ".git",
6835
+ "package.json",
6836
+ "pyproject.toml",
6837
+ "Cargo.toml",
6838
+ "go.mod",
6839
+ "pom.xml",
6840
+ "build.gradle",
6841
+ "Gemfile",
6842
+ "composer.json",
6843
+ "requirements.txt",
6844
+ ".standardagents"
6845
+ ];
6846
+ var MAX_ENTRIES3 = 500;
6847
+ function resolveBrowsePath(input3) {
6848
+ const home = os8.homedir();
6849
+ let p = (input3 ?? "").trim();
6850
+ if (!p) return home;
6851
+ if (p === "~") return home;
6852
+ if (p.startsWith("~/")) p = path4.join(home, p.slice(2));
6853
+ return path4.resolve(p);
6587
6854
  }
6588
- async function writeApprovalResponse(api, threadId, response) {
6589
- await api.kvSet(threadId, APPROVAL_RESPONSE_KEY, response);
6855
+ function markers(dirPath) {
6856
+ let repo = false;
6857
+ let project = false;
6858
+ for (const marker of PROJECT_MARKERS) {
6859
+ let hit = false;
6860
+ try {
6861
+ hit = fs5.existsSync(path4.join(dirPath, marker));
6862
+ } catch {
6863
+ hit = false;
6864
+ }
6865
+ if (!hit) continue;
6866
+ project = true;
6867
+ if (marker === ".git") repo = true;
6868
+ if (repo) break;
6869
+ }
6870
+ return { project, repo };
6590
6871
  }
6591
- async function awaitApprovalViaRelay(api, threadId, request, options = {}) {
6592
- const pollMs = options.pollMs ?? 2e3;
6593
- const timeoutMs = options.timeoutMs ?? 24 * 60 * 60 * 1e3;
6594
- await api.kvSet(threadId, APPROVAL_REQUEST_KEY, request);
6595
- const deadline = Date.now() + timeoutMs;
6872
+ function browseDirectory(input3, opts = {}) {
6873
+ const home = os8.homedir();
6874
+ const abs = resolveBrowsePath(input3);
6875
+ const parent = path4.dirname(abs);
6876
+ const base = {
6877
+ path: abs,
6878
+ parent: parent === abs ? null : parent,
6879
+ home
6880
+ };
6881
+ let dirents;
6596
6882
  try {
6597
- while (Date.now() < deadline) {
6598
- await new Promise((r) => setTimeout(r, pollMs));
6599
- const response = parseApprovalResponse(await api.kvGet(threadId, APPROVAL_RESPONSE_KEY));
6600
- if (response && response.tool_call_id === request.tool_call_id) {
6601
- return response;
6883
+ const stat = fs5.statSync(abs);
6884
+ if (!stat.isDirectory()) {
6885
+ return { ...base, entries: [], truncated: false, error: "Not a directory" };
6886
+ }
6887
+ dirents = fs5.readdirSync(abs, { withFileTypes: true });
6888
+ } catch (err) {
6889
+ const code = err?.code;
6890
+ const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
6891
+ return { ...base, entries: [], truncated: false, error: message };
6892
+ }
6893
+ const showHidden = !!opts.showHidden;
6894
+ const rawDirs = [];
6895
+ const rawFiles = [];
6896
+ for (const d of dirents) {
6897
+ const name = d.name;
6898
+ if (!showHidden && name.startsWith(".")) continue;
6899
+ let isDir = d.isDirectory();
6900
+ if (d.isSymbolicLink()) {
6901
+ try {
6902
+ isDir = fs5.statSync(path4.join(abs, name)).isDirectory();
6903
+ } catch {
6904
+ isDir = false;
6602
6905
  }
6603
6906
  }
6604
- return null;
6605
- } finally {
6606
- await api.kvSet(threadId, APPROVAL_REQUEST_KEY, null).catch(() => {
6607
- });
6608
- await api.kvSet(threadId, APPROVAL_RESPONSE_KEY, null).catch(() => {
6609
- });
6907
+ if (isDir) {
6908
+ const { project, repo } = markers(path4.join(abs, name));
6909
+ rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
6910
+ } else {
6911
+ rawFiles.push({ name, dir: false });
6912
+ }
6610
6913
  }
6914
+ const cmp = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());
6915
+ rawDirs.sort(cmp);
6916
+ rawFiles.sort(cmp);
6917
+ const all = [...rawDirs, ...rawFiles];
6918
+ const truncated = all.length > MAX_ENTRIES3;
6919
+ return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES3) : all, truncated };
6611
6920
  }
6612
-
6613
- // src/hub.ts
6614
- var HubSocket = class {
6615
- constructor(api, clientId, hooks, clientName) {
6616
- this.api = api;
6617
- this.clientId = clientId;
6618
- this.hooks = hooks;
6619
- this.clientName = clientName;
6921
+ function mkdirDirectory(parentInput, name, opts = {}) {
6922
+ const parent = resolveBrowsePath(parentInput);
6923
+ const clean2 = (name ?? "").trim();
6924
+ const listParent = () => browseDirectory(parent, opts);
6925
+ if (!clean2 || clean2 === "." || clean2 === ".." || clean2.includes("/") || clean2.includes("\\") || clean2.includes("\0")) {
6926
+ return { ...listParent(), error: "Invalid folder name." };
6620
6927
  }
6621
- api;
6622
- clientId;
6623
- hooks;
6624
- clientName;
6625
- ws = null;
6928
+ const target = path4.join(parent, clean2);
6929
+ if (path4.dirname(target) !== parent) {
6930
+ return { ...listParent(), error: "Invalid folder name." };
6931
+ }
6932
+ try {
6933
+ fs5.mkdirSync(target, { recursive: false });
6934
+ } catch (err) {
6935
+ const code = err?.code;
6936
+ const message = code === "EEXIST" ? "A folder with that name already exists." : code === "EACCES" || code === "EPERM" ? "Permission denied." : code === "ENOENT" ? "The parent folder no longer exists." : "Could not create the folder.";
6937
+ return { ...listParent(), error: message };
6938
+ }
6939
+ return listParent();
6940
+ }
6941
+
6942
+ // src/dir-picker.ts
6943
+ var c2 = {
6944
+ reset: "\x1B[0m",
6945
+ dim: "\x1B[2m",
6946
+ bold: "\x1B[1m",
6947
+ gray: themeGray,
6948
+ yellow: "\x1B[33m",
6949
+ red: "\x1B[31m",
6950
+ teal: "\x1B[38;5;37m"
6951
+ };
6952
+ function localBrowseBackend(label = "this machine") {
6953
+ return {
6954
+ label,
6955
+ list: async (p, showHidden) => browseDirectory(p, { showHidden }),
6956
+ mkdir: async (parent, name, showHidden) => mkdirDirectory(parent, name, { showHidden })
6957
+ };
6958
+ }
6959
+ function parseBrowseResult(value) {
6960
+ if (!value || typeof value !== "object") return null;
6961
+ const r = value;
6962
+ if (typeof r.path !== "string" || !Array.isArray(r.entries)) return null;
6963
+ return {
6964
+ path: r.path,
6965
+ parent: typeof r.parent === "string" ? r.parent : null,
6966
+ home: typeof r.home === "string" ? r.home : "",
6967
+ entries: r.entries.filter(
6968
+ (e) => !!e && typeof e.name === "string"
6969
+ ),
6970
+ truncated: r.truncated === true,
6971
+ error: typeof r.error === "string" ? r.error : void 0
6972
+ };
6973
+ }
6974
+ function remoteBrowseBackend(api, machineId, label) {
6975
+ const rpc = async (req) => {
6976
+ const nonce = crypto.randomBytes(8).toString("hex");
6977
+ await writeFsRequest(api, machineId, { nonce, ...req });
6978
+ const deadline = Date.now() + 15e3;
6979
+ while (Date.now() < deadline) {
6980
+ await new Promise((r) => setTimeout(r, 700));
6981
+ const res = await readFsResponse(api, machineId).catch(() => null);
6982
+ if (res && res.nonce === nonce) {
6983
+ if (!res.ok) throw new Error(res.error || "Browse failed on the remote machine.");
6984
+ const parsed = parseBrowseResult(res.result);
6985
+ if (!parsed) throw new Error("The remote machine sent an unreadable listing.");
6986
+ return parsed;
6987
+ }
6988
+ }
6989
+ throw new Error(`${label} didn't answer \u2014 is its daemon online?`);
6990
+ };
6991
+ return {
6992
+ label,
6993
+ list: (p, showHidden) => rpc({ op: "list", path: p ?? "~", show_hidden: showHidden }),
6994
+ mkdir: (parent, name, showHidden) => rpc({ op: "mkdir", path: parent, name, show_hidden: showHidden })
6995
+ };
6996
+ }
6997
+ function joinBrowsed(parent, name) {
6998
+ const sep = parent.includes("\\") && !parent.includes("/") ? "\\" : "/";
6999
+ return parent.endsWith(sep) ? parent + name : parent + sep + name;
7000
+ }
7001
+ function tail(p, max = 44) {
7002
+ return p.length > max ? "\u2026" + p.slice(-(max - 1)) : p;
7003
+ }
7004
+ var KEY_HINTS = `enter open \xB7 s use this directory \xB7 . hidden files \xB7 esc back`;
7005
+ async function pickDirectory(tui, backend, opts = {}) {
7006
+ let showHidden = false;
7007
+ let page = 0;
7008
+ const spin = opts.loader?.(`Reading ${backend.label}`);
7009
+ let listing;
7010
+ try {
7011
+ listing = await backend.list(opts.startPath ?? null, showHidden);
7012
+ } catch (e) {
7013
+ spin?.stop();
7014
+ tui.print(`${c2.red}\u2717${c2.reset} ${c2.gray}${e instanceof Error ? e.message : String(e)}${c2.reset}`);
7015
+ return null;
7016
+ }
7017
+ spin?.stop();
7018
+ const title = () => `${c2.bold}${gradientText(`Browse ${backend.label}`)}${c2.reset} ${c2.teal}${tail(listing.path, 48)}${c2.reset}` + (showHidden ? ` ${c2.dim}\xB7 hidden shown${c2.reset}` : "");
7019
+ const footer = (error) => error ? `${c2.yellow}${error}${c2.reset} ${c2.dim}${KEY_HINTS}${c2.reset}` : `${c2.dim}${KEY_HINTS}${c2.reset}`;
7020
+ const buildItems = () => {
7021
+ const pageSize = Math.max(5, Math.min(14, (process.stdout.rows || 24) - 9));
7022
+ const pages = Math.max(1, Math.ceil(listing.entries.length / pageSize));
7023
+ if (page >= pages) page = 0;
7024
+ const slice = listing.entries.slice(page * pageSize, (page + 1) * pageSize);
7025
+ const items = [];
7026
+ if (listing.parent) items.push({ label: "..", hint: "up a level", value: { kind: "up", to: listing.parent } });
7027
+ for (const entry of slice) {
7028
+ if (entry.dir) {
7029
+ items.push({
7030
+ label: `${entry.name}/`,
7031
+ hint: entry.repo ? "git repo" : entry.project ? "project" : "",
7032
+ value: { kind: "open", to: joinBrowsed(listing.path, entry.name) }
7033
+ });
7034
+ } else {
7035
+ items.push({ label: entry.name, value: { kind: "noop" }, disabled: true });
7036
+ }
7037
+ }
7038
+ if (listing.entries.length === 0) {
7039
+ items.push({ label: "(empty directory)", value: { kind: "noop" }, disabled: true });
7040
+ }
7041
+ if (pages > 1) {
7042
+ const remaining = listing.entries.length - slice.length;
7043
+ items.push({
7044
+ label: `\u2192 more (page ${page + 1}/${pages}, ${remaining} more entr${remaining === 1 ? "y" : "ies"})`,
7045
+ value: { kind: "page" }
7046
+ });
7047
+ }
7048
+ if (listing.truncated) {
7049
+ items.push({ label: "\u2026listing capped \u2014 very large directory", value: { kind: "noop" }, disabled: true });
7050
+ }
7051
+ items.push({ label: "\uFF0B New folder here\u2026", value: { kind: "mkdir" } });
7052
+ return items;
7053
+ };
7054
+ const menu = tui.openLiveSelect({
7055
+ title: title(),
7056
+ items: buildItems(),
7057
+ footer: footer(listing.error),
7058
+ keys: ["s", "."]
7059
+ });
7060
+ const refresh = () => menu.update({ title: title(), items: buildItems(), footer: footer(listing.error) });
7061
+ const navigate = async (to, index) => {
7062
+ menu.setBusy(index);
7063
+ try {
7064
+ listing = await backend.list(to, showHidden);
7065
+ page = 0;
7066
+ refresh();
7067
+ } catch (e) {
7068
+ menu.update({ footer: footer(e instanceof Error ? e.message : String(e)) });
7069
+ }
7070
+ };
7071
+ for (; ; ) {
7072
+ const ev = await menu.next();
7073
+ if (ev.kind === "cancel") {
7074
+ menu.close();
7075
+ return null;
7076
+ }
7077
+ if (ev.kind === "key") {
7078
+ if (ev.name === "s") {
7079
+ menu.close();
7080
+ return listing.path;
7081
+ }
7082
+ if (ev.name === ".") {
7083
+ showHidden = !showHidden;
7084
+ await navigate(listing.path, ev.index);
7085
+ }
7086
+ continue;
7087
+ }
7088
+ if (ev.kind === "edit") {
7089
+ menu.setBusy(ev.index);
7090
+ try {
7091
+ const after = await backend.mkdir(listing.path, ev.text, showHidden);
7092
+ if (after.error) {
7093
+ listing = after;
7094
+ page = 0;
7095
+ menu.update({ title: title(), items: buildItems(), footer: footer(after.error) });
7096
+ } else {
7097
+ listing = await backend.list(joinBrowsed(listing.path, ev.text), showHidden);
7098
+ page = 0;
7099
+ refresh();
7100
+ }
7101
+ } catch (e) {
7102
+ menu.update({ footer: footer(e instanceof Error ? e.message : String(e)) });
7103
+ }
7104
+ continue;
7105
+ }
7106
+ const move = ev.value;
7107
+ if (move.kind === "up" || move.kind === "open") {
7108
+ await navigate(move.to, ev.index);
7109
+ } else if (move.kind === "page") {
7110
+ page += 1;
7111
+ refresh();
7112
+ } else if (move.kind === "mkdir") {
7113
+ menu.beginEdit(ev.index, { placeholder: "folder name\u2026", prefix: "\uFF0B " });
7114
+ }
7115
+ }
7116
+ }
7117
+
7118
+ // src/relay.ts
7119
+ var APPROVAL_REQUEST_KEY = "approval_request";
7120
+ var APPROVAL_RESPONSE_KEY = "approval_response";
7121
+ function parseApprovalRequest(value) {
7122
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
7123
+ const r = value;
7124
+ if (typeof r.tool_call_id !== "string" || typeof r.tool !== "string") return null;
7125
+ return {
7126
+ tool_call_id: r.tool_call_id,
7127
+ tool: r.tool,
7128
+ summary: typeof r.summary === "string" ? r.summary : r.tool,
7129
+ permission: typeof r.permission === "string" ? r.permission : null,
7130
+ risk: typeof r.risk === "number" ? r.risk : 3,
7131
+ machine: typeof r.machine === "string" ? r.machine : "",
7132
+ requested_at: typeof r.requested_at === "number" ? r.requested_at : 0
7133
+ };
7134
+ }
7135
+ function parseApprovalResponse(value) {
7136
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
7137
+ const r = value;
7138
+ if (typeof r.tool_call_id !== "string" || typeof r.choice !== "string") return null;
7139
+ if (!["allow", "deny", "always", "always_risk"].includes(r.choice)) return null;
7140
+ return {
7141
+ tool_call_id: r.tool_call_id,
7142
+ choice: r.choice,
7143
+ reason: typeof r.reason === "string" ? r.reason : void 0,
7144
+ decided_at: typeof r.decided_at === "number" ? r.decided_at : 0
7145
+ };
7146
+ }
7147
+ async function writeApprovalResponse(api, threadId, response) {
7148
+ await api.kvSet(threadId, APPROVAL_RESPONSE_KEY, response);
7149
+ }
7150
+ async function awaitApprovalViaRelay(api, threadId, request, options = {}) {
7151
+ const pollMs = options.pollMs ?? 2e3;
7152
+ const timeoutMs = options.timeoutMs ?? 24 * 60 * 60 * 1e3;
7153
+ await api.kvSet(threadId, APPROVAL_REQUEST_KEY, request);
7154
+ const deadline = Date.now() + timeoutMs;
7155
+ try {
7156
+ while (Date.now() < deadline) {
7157
+ await new Promise((r) => setTimeout(r, pollMs));
7158
+ const response = parseApprovalResponse(await api.kvGet(threadId, APPROVAL_RESPONSE_KEY));
7159
+ if (response && response.tool_call_id === request.tool_call_id) {
7160
+ return response;
7161
+ }
7162
+ }
7163
+ return null;
7164
+ } finally {
7165
+ await api.kvSet(threadId, APPROVAL_REQUEST_KEY, null).catch(() => {
7166
+ });
7167
+ await api.kvSet(threadId, APPROVAL_RESPONSE_KEY, null).catch(() => {
7168
+ });
7169
+ }
7170
+ }
7171
+
7172
+ // src/hub.ts
7173
+ var HubSocket = class {
7174
+ constructor(api, clientId, hooks, clientName) {
7175
+ this.api = api;
7176
+ this.clientId = clientId;
7177
+ this.hooks = hooks;
7178
+ this.clientName = clientName;
7179
+ }
7180
+ api;
7181
+ clientId;
7182
+ hooks;
7183
+ clientName;
7184
+ ws = null;
6626
7185
  closed = false;
6627
7186
  heartbeat = null;
6628
7187
  reconnectAttempt = 0;
@@ -6704,114 +7263,6 @@ var HubSocket = class {
6704
7263
  this.ws?.close();
6705
7264
  }
6706
7265
  };
6707
- var PROJECT_MARKERS = [
6708
- ".git",
6709
- "package.json",
6710
- "pyproject.toml",
6711
- "Cargo.toml",
6712
- "go.mod",
6713
- "pom.xml",
6714
- "build.gradle",
6715
- "Gemfile",
6716
- "composer.json",
6717
- "requirements.txt",
6718
- ".standardagents"
6719
- ];
6720
- var MAX_ENTRIES3 = 500;
6721
- function resolveBrowsePath(input3) {
6722
- const home = os8.homedir();
6723
- let p = (input3 ?? "").trim();
6724
- if (!p) return home;
6725
- if (p === "~") return home;
6726
- if (p.startsWith("~/")) p = path4.join(home, p.slice(2));
6727
- return path4.resolve(p);
6728
- }
6729
- function markers(dirPath) {
6730
- let repo = false;
6731
- let project = false;
6732
- for (const marker of PROJECT_MARKERS) {
6733
- let hit = false;
6734
- try {
6735
- hit = fs5.existsSync(path4.join(dirPath, marker));
6736
- } catch {
6737
- hit = false;
6738
- }
6739
- if (!hit) continue;
6740
- project = true;
6741
- if (marker === ".git") repo = true;
6742
- if (repo) break;
6743
- }
6744
- return { project, repo };
6745
- }
6746
- function browseDirectory(input3, opts = {}) {
6747
- const home = os8.homedir();
6748
- const abs = resolveBrowsePath(input3);
6749
- const parent = path4.dirname(abs);
6750
- const base = {
6751
- path: abs,
6752
- parent: parent === abs ? null : parent,
6753
- home
6754
- };
6755
- let dirents;
6756
- try {
6757
- const stat = fs5.statSync(abs);
6758
- if (!stat.isDirectory()) {
6759
- return { ...base, entries: [], truncated: false, error: "Not a directory" };
6760
- }
6761
- dirents = fs5.readdirSync(abs, { withFileTypes: true });
6762
- } catch (err) {
6763
- const code = err?.code;
6764
- const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
6765
- return { ...base, entries: [], truncated: false, error: message };
6766
- }
6767
- const showHidden = !!opts.showHidden;
6768
- const rawDirs = [];
6769
- const rawFiles = [];
6770
- for (const d of dirents) {
6771
- const name = d.name;
6772
- if (!showHidden && name.startsWith(".")) continue;
6773
- let isDir = d.isDirectory();
6774
- if (d.isSymbolicLink()) {
6775
- try {
6776
- isDir = fs5.statSync(path4.join(abs, name)).isDirectory();
6777
- } catch {
6778
- isDir = false;
6779
- }
6780
- }
6781
- if (isDir) {
6782
- const { project, repo } = markers(path4.join(abs, name));
6783
- rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
6784
- } else {
6785
- rawFiles.push({ name, dir: false });
6786
- }
6787
- }
6788
- const cmp = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());
6789
- rawDirs.sort(cmp);
6790
- rawFiles.sort(cmp);
6791
- const all = [...rawDirs, ...rawFiles];
6792
- const truncated = all.length > MAX_ENTRIES3;
6793
- return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES3) : all, truncated };
6794
- }
6795
- function mkdirDirectory(parentInput, name, opts = {}) {
6796
- const parent = resolveBrowsePath(parentInput);
6797
- const clean2 = (name ?? "").trim();
6798
- const listParent = () => browseDirectory(parent, opts);
6799
- if (!clean2 || clean2 === "." || clean2 === ".." || clean2.includes("/") || clean2.includes("\\") || clean2.includes("\0")) {
6800
- return { ...listParent(), error: "Invalid folder name." };
6801
- }
6802
- const target = path4.join(parent, clean2);
6803
- if (path4.dirname(target) !== parent) {
6804
- return { ...listParent(), error: "Invalid folder name." };
6805
- }
6806
- try {
6807
- fs5.mkdirSync(target, { recursive: false });
6808
- } catch (err) {
6809
- const code = err?.code;
6810
- const message = code === "EEXIST" ? "A folder with that name already exists." : code === "EACCES" || code === "EPERM" ? "Permission denied." : code === "ENOENT" ? "The parent folder no longer exists." : "Could not create the folder.";
6811
- return { ...listParent(), error: message };
6812
- }
6813
- return listParent();
6814
- }
6815
7266
  var PKG_NAME = "@standardagents/code";
6816
7267
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
6817
7268
  var CACHE_REL_DIR = ".config/standardagents-cli";
@@ -7576,7 +8027,7 @@ function serviceStatus() {
7576
8027
  }
7577
8028
 
7578
8029
  // src/daemon-cli.ts
7579
- var c2 = {
8030
+ var c3 = {
7580
8031
  reset: "\x1B[0m",
7581
8032
  dim: "\x1B[2m",
7582
8033
  bold: "\x1B[1m",
@@ -7589,9 +8040,9 @@ function usage() {
7589
8040
  stdout.write(
7590
8041
  [
7591
8042
  "",
7592
- `${c2.bold}standardcode daemon${c2.reset} \u2014 headless execution client for this machine`,
8043
+ `${c3.bold}standardcode daemon${c3.reset} \u2014 headless execution client for this machine`,
7593
8044
  "",
7594
- `${c2.bold}Commands${c2.reset}`,
8045
+ `${c3.bold}Commands${c3.reset}`,
7595
8046
  " install [--endpoint url] Sign in (if needed), name this machine, and install",
7596
8047
  " the always-on service (launchd / systemd).",
7597
8048
  " uninstall Stop and remove the service.",
@@ -7631,11 +8082,11 @@ async function ensureSignedIn(endpoint) {
7631
8082
  const api2 = new ApiClient(endpoint, stored.access_token);
7632
8083
  const check2 = await api2.verifyDetailed();
7633
8084
  if (check2.ok) return api2;
7634
- stdout.write(`${c2.yellow}Saved sign-in for ${host} failed:${c2.reset} ${check2.reason}
8085
+ stdout.write(`${c3.yellow}Saved sign-in for ${host} failed:${c3.reset} ${check2.reason}
7635
8086
 
7636
8087
  `);
7637
8088
  }
7638
- stdout.write(`${c2.bold}Sign in to Standard Code${c2.reset} ${c2.dim}(${host})${c2.reset}
8089
+ stdout.write(`${c3.bold}Sign in to Standard Code${c3.reset} ${c3.dim}(${host})${c3.reset}
7639
8090
 
7640
8091
  `);
7641
8092
  const token = await deviceLogin(endpoint);
@@ -7646,7 +8097,7 @@ async function ensureSignedIn(endpoint) {
7646
8097
  { endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
7647
8098
  { updateDefault: false }
7648
8099
  );
7649
- stdout.write(`${c2.green}\u2713${c2.reset} Signed in to ${c2.teal}${host}${c2.reset}
8100
+ stdout.write(`${c3.green}\u2713${c3.reset} Signed in to ${c3.teal}${host}${c3.reset}
7650
8101
 
7651
8102
  `);
7652
8103
  return api;
@@ -7659,26 +8110,26 @@ async function installCommand(endpointFlag) {
7659
8110
  const suggested = machineDisplayName(existing ?? { hostname: os8.hostname(), id: identity.machine_id });
7660
8111
  const rl = readline2.createInterface({ input: stdin, output: stdout });
7661
8112
  const answer = (await rl.question(
7662
- `${c2.bold}Machine name${c2.reset} ${c2.dim}(shown in the session picker)${c2.reset} [${suggested}]: `
8113
+ `${c3.bold}Machine name${c3.reset} ${c3.dim}(shown in the session picker)${c3.reset} [${suggested}]: `
7663
8114
  )).trim();
7664
8115
  rl.close();
7665
8116
  if (answer) await setMachineName(api, identity.machine_id, answer);
7666
8117
  await updateOwnMachineRecord(api, identity);
7667
8118
  const displayName = answer || suggested;
7668
- stdout.write(`${c2.dim}Installing the always-on service\u2026${c2.reset}
8119
+ stdout.write(`${c3.dim}Installing the always-on service\u2026${c3.reset}
7669
8120
  `);
7670
8121
  const extra = endpointFlag ? ["--endpoint", endpoint] : [];
7671
8122
  const result = installService(resolveDaemonCommand(extra), endpointFlag ? endpoint : void 0);
7672
8123
  if (!result.ok) {
7673
- stdout.write(`${c2.red}\u2717${c2.reset} ${result.detail}
8124
+ stdout.write(`${c3.red}\u2717${c3.reset} ${result.detail}
7674
8125
  `);
7675
- if (result.manualHint) stdout.write(`${c2.dim}${result.manualHint}${c2.reset}
8126
+ if (result.manualHint) stdout.write(`${c3.dim}${result.manualHint}${c3.reset}
7676
8127
  `);
7677
8128
  process.exit(1);
7678
8129
  }
7679
- stdout.write(`${c2.green}\u2713${c2.reset} ${result.detail}
8130
+ stdout.write(`${c3.green}\u2713${c3.reset} ${result.detail}
7680
8131
  `);
7681
- stdout.write(`${c2.dim}Waiting for the daemon's first heartbeat\u2026${c2.reset}
8132
+ stdout.write(`${c3.dim}Waiting for the daemon's first heartbeat\u2026${c3.reset}
7682
8133
  `);
7683
8134
  const deadline = Date.now() + 6e4;
7684
8135
  let alive = false;
@@ -7692,17 +8143,17 @@ async function installCommand(endpointFlag) {
7692
8143
  }
7693
8144
  if (alive) {
7694
8145
  stdout.write(
7695
- `${c2.green}\u2713${c2.reset} ${c2.bold}${displayName}${c2.reset} is online.
8146
+ `${c3.green}\u2713${c3.reset} ${c3.bold}${displayName}${c3.reset} is online.
7696
8147
 
7697
8148
  Sessions started elsewhere can now run on this machine.
7698
- ${c2.dim}Projects register automatically when you run standardcode in a directory here,
7699
- or add one now: standardcode daemon add-project <path>${c2.reset}
8149
+ ${c3.dim}Projects register automatically when you run standardcode in a directory here,
8150
+ or add one now: standardcode daemon add-project <path>${c3.reset}
7700
8151
  `
7701
8152
  );
7702
8153
  } else {
7703
8154
  stdout.write(
7704
- `${c2.yellow}\u26A0${c2.reset} The service installed but no heartbeat arrived yet.
7705
- ${c2.dim}Check ~/.standardagents/daemon.log and \`standardcode daemon status\`.${c2.reset}
8155
+ `${c3.yellow}\u26A0${c3.reset} The service installed but no heartbeat arrived yet.
8156
+ ${c3.dim}Check ~/.standardagents/daemon.log and \`standardcode daemon status\`.${c3.reset}
7706
8157
  `
7707
8158
  );
7708
8159
  }
@@ -7710,16 +8161,16 @@ ${c2.dim}Check ~/.standardagents/daemon.log and \`standardcode daemon status\`.$
7710
8161
  async function statusCommand() {
7711
8162
  const status = serviceStatus();
7712
8163
  const identity = loadMachineIdentity();
7713
- stdout.write(`${c2.bold}Service:${c2.reset} ${status.detail}
8164
+ stdout.write(`${c3.bold}Service:${c3.reset} ${status.detail}
7714
8165
  `);
7715
8166
  const endpoint = resolveEndpoint();
7716
8167
  const cred = getCredential(endpoint);
7717
8168
  if (!cred) {
7718
8169
  stdout.write(
7719
- `${c2.bold}Machine:${c2.reset} ${os8.hostname()} ${c2.dim}(${identity.machine_id})${c2.reset}
8170
+ `${c3.bold}Machine:${c3.reset} ${os8.hostname()} ${c3.dim}(${identity.machine_id})${c3.reset}
7720
8171
  `
7721
8172
  );
7722
- stdout.write(`${c2.bold}Account:${c2.reset} ${c2.yellow}not signed in to ${endpoint}${c2.reset}
8173
+ stdout.write(`${c3.bold}Account:${c3.reset} ${c3.yellow}not signed in to ${endpoint}${c3.reset}
7723
8174
  `);
7724
8175
  return;
7725
8176
  }
@@ -7727,33 +8178,33 @@ async function statusCommand() {
7727
8178
  const api = new ApiClient(endpoint, cred.access_token);
7728
8179
  const record2 = await loadMachine(api, identity.machine_id).catch(() => null);
7729
8180
  stdout.write(
7730
- `${c2.bold}Machine:${c2.reset} ${machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id })} ${c2.dim}(${identity.machine_id})${c2.reset}
8181
+ `${c3.bold}Machine:${c3.reset} ${machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id })} ${c3.dim}(${identity.machine_id})${c3.reset}
7731
8182
  `
7732
8183
  );
7733
8184
  if (!record2) {
7734
- stdout.write(`${c2.bold}Registry:${c2.reset} not registered yet
8185
+ stdout.write(`${c3.bold}Registry:${c3.reset} not registered yet
7735
8186
  `);
7736
8187
  return;
7737
8188
  }
7738
8189
  const online = daemonOnline(record2);
7739
8190
  const seen = record2.daemon ? `${Math.round((Date.now() - record2.daemon.last_seen_at) / 1e3)}s ago (v${record2.daemon.version})` : "never";
7740
- stdout.write(`${c2.bold}Registry:${c2.reset} ${online ? `${c2.green}online${c2.reset}` : `${c2.yellow}offline${c2.reset}`} \xB7 last heartbeat ${seen}
8191
+ stdout.write(`${c3.bold}Registry:${c3.reset} ${online ? `${c3.green}online${c3.reset}` : `${c3.yellow}offline${c3.reset}`} \xB7 last heartbeat ${seen}
7741
8192
  `);
7742
8193
  const projects = Object.keys(record2.projects);
7743
- stdout.write(`${c2.bold}Projects:${c2.reset} ${projects.length ? "" : c2.dim + "none registered" + c2.reset}
8194
+ stdout.write(`${c3.bold}Projects:${c3.reset} ${projects.length ? "" : c3.dim + "none registered" + c3.reset}
7744
8195
  `);
7745
- for (const p of projects.sort()) stdout.write(` ${c2.dim}${p}${c2.reset}
8196
+ for (const p of projects.sort()) stdout.write(` ${c3.dim}${p}${c3.reset}
7746
8197
  `);
7747
8198
  }
7748
8199
  async function projectCommand(action, target) {
7749
8200
  if (!target) {
7750
- stdout.write(`${c2.red}\u2717${c2.reset} Expected a project path.
8201
+ stdout.write(`${c3.red}\u2717${c3.reset} Expected a project path.
7751
8202
  `);
7752
8203
  process.exit(1);
7753
8204
  }
7754
8205
  const dir2 = path4.resolve(target);
7755
8206
  if (action === "add" && !fs5.existsSync(dir2)) {
7756
- stdout.write(`${c2.red}\u2717${c2.reset} ${dir2} does not exist on this machine.
8207
+ stdout.write(`${c3.red}\u2717${c3.reset} ${dir2} does not exist on this machine.
7757
8208
  `);
7758
8209
  process.exit(1);
7759
8210
  }
@@ -7764,11 +8215,11 @@ async function projectCommand(action, target) {
7764
8215
  const displayName = machineDisplayName(record2 ?? { hostname: os8.hostname(), id: identity.machine_id });
7765
8216
  if (action === "add") {
7766
8217
  await registerProject(api, identity, dir2);
7767
- stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${displayName}.
8218
+ stdout.write(`${c3.green}\u2713${c3.reset} Registered ${dir2} for remote sessions on ${displayName}.
7768
8219
  `);
7769
8220
  } else {
7770
8221
  await unregisterProject(api, identity, dir2);
7771
- stdout.write(`${c2.green}\u2713${c2.reset} Removed ${dir2} from this machine's projects.
8222
+ stdout.write(`${c3.green}\u2713${c3.reset} Removed ${dir2} from this machine's projects.
7772
8223
  `);
7773
8224
  }
7774
8225
  }
@@ -7784,7 +8235,7 @@ async function runDaemonCommand(argv) {
7784
8235
  return;
7785
8236
  case "uninstall": {
7786
8237
  const result = uninstallService();
7787
- stdout.write(`${result.ok ? c2.green + "\u2713" : c2.red + "\u2717"}${c2.reset} ${result.detail}
8238
+ stdout.write(`${result.ok ? c3.green + "\u2713" : c3.red + "\u2717"}${c3.reset} ${result.detail}
7788
8239
  `);
7789
8240
  const ep = resolveEndpoint(endpoint);
7790
8241
  const cred = getCredential(ep);
@@ -7817,7 +8268,7 @@ async function runDaemonCommand(argv) {
7817
8268
  }
7818
8269
 
7819
8270
  // src/index.ts
7820
- var c3 = {
8271
+ var c4 = {
7821
8272
  reset: "\x1B[0m",
7822
8273
  dim: "\x1B[2m",
7823
8274
  bold: "\x1B[1m",
@@ -7846,10 +8297,10 @@ function printUsage() {
7846
8297
  stdout.write(
7847
8298
  [
7848
8299
  "",
7849
- `${c3.bold}Usage${c3.reset}`,
8300
+ `${c4.bold}Usage${c4.reset}`,
7850
8301
  " standardcode [options] [dir]",
7851
8302
  "",
7852
- `${c3.bold}Options${c3.reset}`,
8303
+ `${c4.bold}Options${c4.reset}`,
7853
8304
  " -e, --endpoint [url] Use a different Standard Agents instance for this run",
7854
8305
  " (default: https://api.standardcode.ai).",
7855
8306
  " If url is omitted, prompt for it.",
@@ -7917,15 +8368,15 @@ function parseArgs2(args) {
7917
8368
  }
7918
8369
  function printCommandBlock(tui, command, output4, ok, where) {
7919
8370
  tui.print("");
7920
- const note = where ? ` ${c3.dim}(ran on ${where})${c3.reset}` : "";
7921
- tui.print(`${c3.magenta}!${c3.reset} ${c3.bold}${command}${c3.reset}${note}`);
8371
+ const note = where ? ` ${c4.dim}(ran on ${where})${c4.reset}` : "";
8372
+ tui.print(`${c4.magenta}!${c4.reset} ${c4.bold}${command}${c4.reset}${note}`);
7922
8373
  const body = (output4 ?? "").replace(/\s+$/, "");
7923
8374
  if (body) {
7924
8375
  for (const line of body.split("\n")) {
7925
- tui.print(` ${ok ? c3.dim : c3.red}${line}${c3.reset}`);
8376
+ tui.print(` ${ok ? c4.dim : c4.red}${line}${c4.reset}`);
7926
8377
  }
7927
8378
  } else {
7928
- tui.print(` ${c3.dim}(no output)${c3.reset}`);
8379
+ tui.print(` ${c4.dim}(no output)${c4.reset}`);
7929
8380
  }
7930
8381
  tui.print("");
7931
8382
  }
@@ -7936,7 +8387,7 @@ function printAssistant(tui, text) {
7936
8387
  let dotted = false;
7937
8388
  for (const line of renderStreamingMarkdown(text, cols2)) {
7938
8389
  if (!dotted && line.trim()) {
7939
- tui.print(`${c3.gray}\u2022${c3.reset} ${line}`);
8390
+ tui.print(`${c4.gray}\u2022${c4.reset} ${line}`);
7940
8391
  dotted = true;
7941
8392
  } else {
7942
8393
  tui.print(` ${line}`);
@@ -7951,7 +8402,7 @@ function startLoader(label) {
7951
8402
  const draw = () => {
7952
8403
  const now = Date.now();
7953
8404
  const f = frames[Math.floor(now / 70) % frames.length];
7954
- stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c3.reset} ${c3.dim}${label}\u2026${c3.reset}`);
8405
+ stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c4.reset} ${c4.dim}${label}\u2026${c4.reset}`);
7955
8406
  };
7956
8407
  draw();
7957
8408
  const timer = setInterval(draw, 70);
@@ -7967,12 +8418,12 @@ function farewell(stoppedProcs = 0) {
7967
8418
  if (stoppedProcs > 0) {
7968
8419
  stdout.write(
7969
8420
  `
7970
- ${c3.cyan}\u2699${c3.reset} Stopped ${stoppedProcs} background process${stoppedProcs === 1 ? "" : "es"}.
8421
+ ${c4.cyan}\u2699${c4.reset} Stopped ${stoppedProcs} background process${stoppedProcs === 1 ? "" : "es"}.
7971
8422
  `
7972
8423
  );
7973
8424
  }
7974
8425
  stdout.write(`
7975
- ${c3.teal}\u25C7${c3.reset} ${c3.dim}Standard Code \u2014 see you soon.${c3.reset}
8426
+ ${c4.teal}\u25C7${c4.reset} ${c4.dim}Standard Code \u2014 see you soon.${c4.reset}
7976
8427
  `);
7977
8428
  }
7978
8429
  function printWelcome(endpoint, projectDir) {
@@ -7986,10 +8437,10 @@ function printWelcome(endpoint, projectDir) {
7986
8437
  const metaWidth = Math.max(1, terminalColumns - pad.length - markWidth - 3 - 1);
7987
8438
  const displayDir = truncateMiddle(dir2, metaWidth);
7988
8439
  const meta = [
7989
- `${c3.bold}${gradientText("Standard Code")}${c3.reset}${version ? ` ${c3.dim}v${version}${c3.reset}` : ""}`,
7990
- `${c3.dim}terminal coding agent${c3.reset}`,
7991
- ...endpoint === PRODUCTION_ENDPOINT ? [] : [`${c3.teal}${host}${c3.reset}`],
7992
- `${c3.dim}${displayDir}${c3.reset}`
8440
+ `${c4.bold}${gradientText("Standard Code")}${c4.reset}${version ? ` ${c4.dim}v${version}${c4.reset}` : ""}`,
8441
+ `${c4.dim}terminal coding agent${c4.reset}`,
8442
+ ...endpoint === PRODUCTION_ENDPOINT ? [] : [`${c4.teal}${host}${c4.reset}`],
8443
+ `${c4.dim}${displayDir}${c4.reset}`
7993
8444
  ];
7994
8445
  const metaTop = Math.floor((LOGO_MARK.length - meta.length) / 2);
7995
8446
  stdout.write("\n");
@@ -8003,11 +8454,11 @@ function printWelcome(endpoint, projectDir) {
8003
8454
  }
8004
8455
  function colorActivity(line) {
8005
8456
  const m = line.match(/^(\s*)([✓✗⛔])\s?([\s\S]*)$/);
8006
- if (!m) return `${c3.dim}${line}${c3.reset}`;
8457
+ if (!m) return `${c4.dim}${line}${c4.reset}`;
8007
8458
  const [, indent, glyph, rest] = m;
8008
8459
  if (glyph === "\u2713") {
8009
- const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${c3.dim}$1${c3.reset}`);
8010
- return `${indent}${c3.green}\u2713${c3.reset} ${body}`;
8460
+ const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${c4.dim}$1${c4.reset}`);
8461
+ return `${indent}${c4.green}\u2713${c4.reset} ${body}`;
8011
8462
  }
8012
8463
  if (glyph === "\u2717") {
8013
8464
  const ERR_MAX_LINES = 7;
@@ -8015,15 +8466,15 @@ function colorActivity(line) {
8015
8466
  const shown = lines.slice(0, ERR_MAX_LINES);
8016
8467
  const hidden = lines.length - shown.length;
8017
8468
  const body = shown.map(
8018
- (l, i) => i === 0 ? `${indent}${c3.red}\u2717 ${l}${c3.reset}` : `${indent}${c3.red}${c3.dim}${l}${c3.reset}`
8469
+ (l, i) => i === 0 ? `${indent}${c4.red}\u2717 ${l}${c4.reset}` : `${indent}${c4.red}${c4.dim}${l}${c4.reset}`
8019
8470
  ).join("\n");
8020
8471
  if (hidden > 0) {
8021
8472
  return `${body}
8022
- ${indent}${c3.dim}\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}${c3.reset}`;
8473
+ ${indent}${c4.dim}\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}${c4.reset}`;
8023
8474
  }
8024
8475
  return body;
8025
8476
  }
8026
- return `${indent}${c3.yellow}\u26D4 ${rest}${c3.reset}`;
8477
+ return `${indent}${c4.yellow}\u26D4 ${rest}${c4.reset}`;
8027
8478
  }
8028
8479
  async function main() {
8029
8480
  if (process.argv[2] === "daemon") {
@@ -8034,7 +8485,7 @@ async function main() {
8034
8485
  try {
8035
8486
  cliArgs = parseArgs2(process.argv.slice(2));
8036
8487
  } catch (error) {
8037
- stdout.write(`${c3.red}error:${c3.reset} ${error instanceof Error ? error.message : String(error)}
8488
+ stdout.write(`${c4.red}error:${c4.reset} ${error instanceof Error ? error.message : String(error)}
8038
8489
  `);
8039
8490
  printUsage();
8040
8491
  process.exit(1);
@@ -8046,7 +8497,7 @@ async function main() {
8046
8497
  const endpointArg = cliArgs.endpoint;
8047
8498
  const endpointOverride = cliArgs.promptEndpoint || typeof endpointArg === "string" && endpointArg.trim() !== "";
8048
8499
  const dirArg = cliArgs.dir;
8049
- const projectDir = path4.resolve(dirArg || process.cwd());
8500
+ let projectDir = path4.resolve(dirArg || process.cwd());
8050
8501
  const machine = os8.hostname();
8051
8502
  const reader = { rl: null };
8052
8503
  let handoffClosing = false;
@@ -8061,7 +8512,7 @@ async function main() {
8061
8512
  }
8062
8513
  preflightArmed = true;
8063
8514
  stdout.write(`
8064
- ${c3.dim}Press Control-C again to exit${c3.reset}
8515
+ ${c4.dim}Press Control-C again to exit${c4.reset}
8065
8516
  `);
8066
8517
  preflightTimer = setTimeout(() => {
8067
8518
  preflightArmed = false;
@@ -8083,10 +8534,10 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8083
8534
  const askEndpoint = async () => {
8084
8535
  for (; ; ) {
8085
8536
  const answer = (await ask(
8086
- `${c3.cyan}Standard Agents instance URL${c3.reset} (e.g. http://localhost:5178): `
8537
+ `${c4.cyan}Standard Agents instance URL${c4.reset} (e.g. http://localhost:5178): `
8087
8538
  )).trim();
8088
8539
  if (answer) return answer;
8089
- stdout.write(`${c3.dim}An endpoint URL is required.${c3.reset}
8540
+ stdout.write(`${c4.dim}An endpoint URL is required.${c4.reset}
8090
8541
  `);
8091
8542
  }
8092
8543
  };
@@ -8101,7 +8552,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8101
8552
  const tlsRelaxed = relaxTlsForLocalEndpoint(endpoint);
8102
8553
  printWelcome(endpoint, projectDir);
8103
8554
  if (tlsRelaxed) {
8104
- stdout.write(`${c3.dim} TLS verification relaxed for local endpoint.${c3.reset}
8555
+ stdout.write(`${c4.dim} TLS verification relaxed for local endpoint.${c4.reset}
8105
8556
 
8106
8557
  `);
8107
8558
  }
@@ -8113,7 +8564,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8113
8564
  loading.stop();
8114
8565
  const applied = consumeAppliedUpdate(version);
8115
8566
  if (applied) {
8116
- stdout.write(` ${c3.green}\u2713${c3.reset} ${c3.dim}Standard Code updated to v${version}.${c3.reset}
8567
+ stdout.write(` ${c4.green}\u2713${c4.reset} ${c4.dim}Standard Code updated to v${version}.${c4.reset}
8117
8568
 
8118
8569
  `);
8119
8570
  }
@@ -8122,21 +8573,21 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8122
8573
  const decision = decideAutoUpdate(updateAvailable, { state: readAutoUpdateState(), pm });
8123
8574
  if (decision === "start" && pm && startBackgroundUpdate(updateAvailable.latest, pm)) {
8124
8575
  stdout.write(
8125
- ` ${c3.teal}\u27F3${c3.reset} ${c3.dim}Standard Code ${c3.reset}${c3.bold}v${updateAvailable.latest}${c3.reset}${c3.dim} is installing in the background \u2014 it applies on your next launch.${c3.reset}
8576
+ ` ${c4.teal}\u27F3${c4.reset} ${c4.dim}Standard Code ${c4.reset}${c4.bold}v${updateAvailable.latest}${c4.reset}${c4.dim} is installing in the background \u2014 it applies on your next launch.${c4.reset}
8126
8577
 
8127
8578
  `
8128
8579
  );
8129
8580
  } else if (decision === "in_flight") {
8130
8581
  stdout.write(
8131
- ` ${c3.teal}\u27F3${c3.reset} ${c3.dim}Standard Code v${updateAvailable.latest} is still installing in the background.${c3.reset}
8582
+ ` ${c4.teal}\u27F3${c4.reset} ${c4.dim}Standard Code v${updateAvailable.latest} is still installing in the background.${c4.reset}
8132
8583
 
8133
8584
  `
8134
8585
  );
8135
8586
  } else {
8136
8587
  const display = updateCommand(pm ?? "npm").display;
8137
8588
  stdout.write(
8138
- ` ${c3.teal}\u25C7${c3.reset} ${c3.dim}Update available:${c3.reset} ${c3.dim}v${updateAvailable.current}${c3.reset} \u2192 ${c3.bold}v${updateAvailable.latest}${c3.reset}
8139
- ${c3.dim}Run ${c3.reset}${c3.bold}${display}${c3.reset}${c3.dim} to update${c3.reset}
8589
+ ` ${c4.teal}\u25C7${c4.reset} ${c4.dim}Update available:${c4.reset} ${c4.dim}v${updateAvailable.current}${c4.reset} \u2192 ${c4.bold}v${updateAvailable.latest}${c4.reset}
8590
+ ${c4.dim}Run ${c4.reset}${c4.bold}${display}${c4.reset}${c4.dim} to update${c4.reset}
8140
8591
 
8141
8592
  `
8142
8593
  );
@@ -8154,39 +8605,39 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8154
8605
  if (!api || !storedCheck?.ok) {
8155
8606
  const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
8156
8607
  if (storedCheck && !storedCheck.ok) {
8157
- stdout.write(`${c3.red}\u2717${c3.reset} ${c3.dim}Saved sign-in for this endpoint failed:${c3.reset} ${storedCheck.reason}
8608
+ stdout.write(`${c4.red}\u2717${c4.reset} ${c4.dim}Saved sign-in for this endpoint failed:${c4.reset} ${storedCheck.reason}
8158
8609
  `);
8159
- if (storedCheck.hint) stdout.write(` ${c3.dim}${storedCheck.hint}${c3.reset}
8610
+ if (storedCheck.hint) stdout.write(` ${c4.dim}${storedCheck.hint}${c4.reset}
8160
8611
  `);
8161
8612
  stdout.write("\n");
8162
8613
  }
8163
8614
  const explainFailure = (result, prefix) => {
8164
- stdout.write(`${c3.red}\u2717${c3.reset} ${prefix}${result.reason}
8615
+ stdout.write(`${c4.red}\u2717${c4.reset} ${prefix}${result.reason}
8165
8616
  `);
8166
- if (result.hint) stdout.write(` ${c3.dim}${result.hint}${c3.reset}
8617
+ if (result.hint) stdout.write(` ${c4.dim}${result.hint}${c4.reset}
8167
8618
  `);
8168
8619
  };
8169
- stdout.write(`${c3.bold}${gradientText("Sign in to Standard Code")}${c3.reset}
8620
+ stdout.write(`${c4.bold}${gradientText("Sign in to Standard Code")}${c4.reset}
8170
8621
  `);
8171
8622
  if (`https://${host}` !== PRODUCTION_ENDPOINT) {
8172
- stdout.write(`${c3.dim}Connecting to${c3.reset} ${c3.teal}${host}${c3.reset}
8623
+ stdout.write(`${c4.dim}Connecting to${c4.reset} ${c4.teal}${host}${c4.reset}
8173
8624
  `);
8174
8625
  }
8175
8626
  stdout.write(
8176
- `${c3.dim}You'll only need to do this once on this machine.${c3.reset}
8627
+ `${c4.dim}You'll only need to do this once on this machine.${c4.reset}
8177
8628
 
8178
8629
  `
8179
8630
  );
8180
8631
  stdout.write(
8181
- `${c3.white}Press ${c3.bold}Enter${c3.reset}${c3.white} to open your browser and sign in.${c3.reset} ${c3.dim}(or paste an API token)${c3.reset}
8632
+ `${c4.white}Press ${c4.bold}Enter${c4.reset}${c4.white} to open your browser and sign in.${c4.reset} ${c4.dim}(or paste an API token)${c4.reset}
8182
8633
 
8183
8634
  `
8184
8635
  );
8185
8636
  for (; ; ) {
8186
- const token = (await ask(`${c3.teal}\u276F${c3.reset} `)).trim();
8637
+ const token = (await ask(`${c4.teal}\u276F${c4.reset} `)).trim();
8187
8638
  if (!token) {
8188
8639
  const got = await deviceLogin(endpoint).catch((e) => {
8189
- stdout.write(`${c3.red}\u2717${c3.reset} ${c3.dim}${e instanceof Error ? e.message : String(e)}${c3.reset}
8640
+ stdout.write(`${c4.red}\u2717${c4.reset} ${c4.dim}${e instanceof Error ? e.message : String(e)}${c4.reset}
8190
8641
  `);
8191
8642
  return null;
8192
8643
  });
@@ -8200,7 +8651,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8200
8651
  { endpoint, access_token: got, token_type: "Bearer", saved_at: Date.now() },
8201
8652
  { updateDefault: !endpointOverride }
8202
8653
  );
8203
- stdout.write(`${c3.green}\u2713${c3.reset} Connected to ${c3.teal}${host}${c3.reset}
8654
+ stdout.write(`${c4.green}\u2713${c4.reset} Connected to ${c4.teal}${host}${c4.reset}
8204
8655
  `);
8205
8656
  break;
8206
8657
  }
@@ -8216,7 +8667,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8216
8667
  { endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
8217
8668
  { updateDefault: !endpointOverride }
8218
8669
  );
8219
- stdout.write(`${c3.green}\u2713${c3.reset} Connected to ${c3.teal}${host}${c3.reset}
8670
+ stdout.write(`${c4.green}\u2713${c4.reset} Connected to ${c4.teal}${host}${c4.reset}
8220
8671
  `);
8221
8672
  break;
8222
8673
  }
@@ -8232,92 +8683,30 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8232
8683
  void registerProject(api, identity, projectDir).catch(() => {
8233
8684
  });
8234
8685
  const tui = new Tui(1);
8235
- let selectedAgent = AGENT_ID;
8686
+ let agentOverride;
8236
8687
  if (cliArgs.agent) {
8237
8688
  const wanted = cliArgs.agent.toLowerCase();
8238
8689
  if (["sama", "opensama", "sama_one", OPENSAMA_AGENT_ID].includes(wanted)) {
8239
- selectedAgent = OPENSAMA_AGENT_ID;
8690
+ agentOverride = OPENSAMA_AGENT_ID;
8240
8691
  } else if (["unlimited", "standard", "unlimited_one", AGENT_ID].includes(wanted)) {
8241
- selectedAgent = AGENT_ID;
8692
+ agentOverride = AGENT_ID;
8242
8693
  } else {
8243
- stdout.write(`${c3.red}error:${c3.reset} Unknown agent "${cliArgs.agent}". Use unlimited or sama.
8694
+ stdout.write(`${c4.red}error:${c4.reset} Unknown agent "${cliArgs.agent}". Use unlimited or sama.
8244
8695
  `);
8245
8696
  process.exit(1);
8246
8697
  }
8247
- } else {
8248
- const picked = await tui.select(
8249
- `${c3.bold}${gradientText("Which agent?")}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8250
- AGENT_CHOICES.map((choice) => ({
8251
- label: choice.title,
8252
- hint: choice.description,
8253
- value: choice.id,
8254
- disabled: choice.disabled
8255
- }))
8256
- );
8257
- if (!picked) process.exit(0);
8258
- selectedAgent = picked;
8259
8698
  }
8260
- if (selectedAgent === OPENSAMA_AGENT_ID) {
8261
- const ok = await ensureSamaAuth(tui, api);
8262
- if (!ok) process.exit(0);
8263
- }
8264
- const home = os8.homedir();
8265
- const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
8266
- const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
8699
+ let selectedAgent = agentOverride ?? AGENT_ID;
8267
8700
  const session = { mode: "local", identity };
8268
- {
8269
- const loadingMachines = startLoader("Checking your machines");
8270
- const machines = await loadMachines(api).catch(() => []);
8271
- loadingMachines.stop();
8272
- session.suggestDaemonInstall = machines.every((m) => !m.daemon);
8273
- const remoteTargets = machines.filter((m) => m.id !== identity.machine_id && daemonOnline(m));
8274
- if (remoteTargets.length > 0) {
8275
- const where = await tui.select(
8276
- `${c3.bold}${gradientText("Where should this session run?")}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8277
- [
8278
- { label: `\u{1F5A5}\uFE0F This machine \u2014 ${shortDir}`, hint: "tools run locally", value: null },
8279
- ...remoteTargets.map((m) => ({
8280
- label: `${machineIcon(m)} ${m.name}`,
8281
- hint: `${m.hostname} \xB7 daemon online${m.daemon ? ` \xB7 v${m.daemon.version}` : ""}`,
8282
- value: m
8283
- }))
8284
- ]
8285
- );
8286
- if (where) {
8287
- const remotePath = await pickRemoteProject(tui, api, where);
8288
- if (remotePath) {
8289
- session.mode = "remote";
8290
- session.runner = where;
8291
- session.remotePath = remotePath;
8292
- }
8293
- }
8294
- }
8295
- }
8296
- let tags;
8297
- let resumeTags;
8298
- if (session.mode === "remote" && session.runner && session.remotePath) {
8299
- tags = [
8300
- `path:${session.remotePath}`,
8301
- `machine:${session.runner.hostname || session.runner.name}`,
8302
- `runner:${session.runner.id}`
8303
- ];
8304
- resumeTags = [`path:${session.remotePath}`, `runner:${session.runner.id}`];
8305
- } else {
8306
- tags = [`path:${projectDir}`, `machine:${machine}`, `runner:${identity.machine_id}`];
8307
- resumeTags = [`path:${projectDir}`, `machine:${machine}`];
8308
- }
8309
- const loadingSessions = startLoader("Loading sessions");
8310
- let existing = [];
8311
- try {
8312
- existing = await api.listThreads(
8313
- selectedAgent === OPENSAMA_AGENT_ID ? [OPENSAMA_AGENT_ID] : AGENT_ID_VARIANTS,
8314
- resumeTags
8315
- );
8316
- } catch {
8317
- existing = [];
8318
- }
8319
- const summaries = existing.length > 0 ? await summarizeThreads(api, existing.slice(0, 8)) : [];
8320
- loadingSessions.stop();
8701
+ const loadingMachines = startLoader("Checking your machines");
8702
+ const machines = await loadMachines(api).catch(() => []);
8703
+ loadingMachines.stop();
8704
+ session.suggestDaemonInstall = machines.every((m) => !m.daemon);
8705
+ const remoteTargets = machines.filter((m) => m.id !== identity.machine_id && daemonOnline(m));
8706
+ const self = machines.find((m) => m.id === identity.machine_id);
8707
+ const launchDir = projectDir;
8708
+ let tags = [];
8709
+ let resumeTags = [];
8321
8710
  const createSessionThread = async () => {
8322
8711
  const id = await api.createThread(selectedAgent, tags);
8323
8712
  if (session.mode === "remote" && session.runner && session.remotePath) {
@@ -8330,33 +8719,161 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
8330
8719
  }
8331
8720
  return id;
8332
8721
  };
8333
- let threadId;
8722
+ let threadId = "";
8334
8723
  let resumed = false;
8335
8724
  let historySeed;
8336
- if (existing.length > 0) {
8337
- const items = summaries.map((s) => ({
8338
- label: s.label,
8339
- hint: s.hint,
8340
- value: s.id
8341
- }));
8342
- items.push({ label: "\uFF0B Start a new session", value: null });
8343
- const whereLabel = session.mode === "remote" && session.runner ? `${session.runner.name}:${shortenPath(session.remotePath ?? "")}` : shortDir;
8344
- const picked = await tui.select(
8345
- `${c3.bold}${gradientText("Resume a session")}${c3.reset} ${c3.gray}${whereLabel}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8346
- items
8347
- );
8348
- if (typeof picked === "string") {
8349
- threadId = picked;
8350
- resumed = true;
8725
+ let existing = [];
8726
+ let hadResumeMenu = false;
8727
+ let where = null;
8728
+ let step = remoteTargets.length > 0 ? "machine" : "thread";
8729
+ flow: for (; ; ) {
8730
+ if (step === "machine") {
8731
+ const picked = await tui.select(
8732
+ `${c4.bold}${gradientText("Where should this session run?")}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8733
+ [
8734
+ {
8735
+ label: `This machine \u2014 ${self ? machineDisplayName(self) : machine}`,
8736
+ detail: "tools run locally",
8737
+ value: null
8738
+ },
8739
+ ...remoteTargets.map((m) => ({
8740
+ label: machineDisplayName(m),
8741
+ detail: `${m.hostname} \xB7 daemon online${m.daemon ? ` \xB7 v${m.daemon.version}` : ""}`,
8742
+ value: m
8743
+ }))
8744
+ ],
8745
+ { spaced: true }
8746
+ );
8747
+ if (picked === void 0) process.exit(0);
8748
+ where = picked;
8749
+ step = "project";
8750
+ } else if (step === "project") {
8751
+ session.mode = "local";
8752
+ session.runner = void 0;
8753
+ session.remotePath = void 0;
8754
+ projectDir = launchDir;
8755
+ if (where) {
8756
+ const remotePath = await pickRemoteProject(tui, api, where);
8757
+ if (!remotePath) {
8758
+ step = "machine";
8759
+ continue;
8760
+ }
8761
+ session.mode = "remote";
8762
+ session.runner = where;
8763
+ session.remotePath = remotePath;
8764
+ } else {
8765
+ const localPath = await pickLocalProject(tui, api, self, launchDir, machine);
8766
+ if (localPath === null) {
8767
+ step = "machine";
8768
+ continue;
8769
+ }
8770
+ if (localPath !== launchDir) {
8771
+ projectDir = localPath;
8772
+ void registerProject(api, identity, projectDir).catch(() => {
8773
+ });
8774
+ }
8775
+ }
8776
+ step = "thread";
8777
+ } else if (step === "thread") {
8778
+ if (session.mode === "remote" && session.runner && session.remotePath) {
8779
+ tags = [
8780
+ `path:${session.remotePath}`,
8781
+ `machine:${session.runner.hostname || session.runner.name}`,
8782
+ `runner:${session.runner.id}`
8783
+ ];
8784
+ resumeTags = [`path:${session.remotePath}`, `runner:${session.runner.id}`];
8785
+ } else {
8786
+ tags = [`path:${projectDir}`, `machine:${machine}`, `runner:${identity.machine_id}`];
8787
+ resumeTags = [`path:${projectDir}`, `machine:${machine}`];
8788
+ }
8789
+ const loadingSessions = startLoader("Loading sessions");
8790
+ try {
8791
+ const listIds = agentOverride ? agentOverride === OPENSAMA_AGENT_ID ? [OPENSAMA_AGENT_ID] : AGENT_ID_VARIANTS : [...AGENT_ID_VARIANTS, OPENSAMA_AGENT_ID];
8792
+ existing = await api.listThreads(listIds, resumeTags);
8793
+ existing.sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
8794
+ } catch {
8795
+ existing = [];
8796
+ }
8797
+ const summaries = existing.length > 0 ? await summarizeThreads(api, existing.slice(0, 8)) : [];
8798
+ loadingSessions.stop();
8799
+ if (existing.length === 0) {
8800
+ hadResumeMenu = false;
8801
+ step = "agent";
8802
+ continue;
8803
+ }
8804
+ hadResumeMenu = true;
8805
+ const home = os8.homedir();
8806
+ const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
8807
+ const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
8808
+ const items = summaries.map((s) => ({
8809
+ label: s.label,
8810
+ hint: s.hint,
8811
+ value: s.id
8812
+ }));
8813
+ items.push({ label: "\uFF0B Start a new session", value: null });
8814
+ const whereLabel = session.mode === "remote" && session.runner ? `${session.runner.name}:${shortenPath(session.remotePath ?? "")}` : shortDir;
8815
+ const picked = await tui.select(
8816
+ `${c4.bold}${gradientText("Resume a session")}${c4.reset} ${c4.gray}${whereLabel}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8817
+ items
8818
+ );
8819
+ if (picked === void 0) {
8820
+ if (remoteTargets.length > 0) {
8821
+ step = "project";
8822
+ continue;
8823
+ }
8824
+ process.exit(0);
8825
+ }
8826
+ if (typeof picked === "string") {
8827
+ threadId = picked;
8828
+ resumed = true;
8829
+ const threadAgent = existing.find((t) => t.id === picked)?.agent_id;
8830
+ selectedAgent = threadAgent === OPENSAMA_AGENT_ID ? OPENSAMA_AGENT_ID : AGENT_ID;
8831
+ if (selectedAgent === OPENSAMA_AGENT_ID) {
8832
+ const ok = await ensureSamaAuth(tui, api);
8833
+ if (!ok) continue;
8834
+ }
8835
+ break flow;
8836
+ }
8837
+ historySeed = existing[0]?.id;
8838
+ step = "agent";
8351
8839
  } else {
8840
+ if (!agentOverride) {
8841
+ const picked = await tui.select(
8842
+ `${c4.bold}${gradientText("Which agent?")}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8843
+ AGENT_CHOICES.map((choice) => ({
8844
+ label: choice.title,
8845
+ hint: choice.description,
8846
+ value: choice.id,
8847
+ disabled: choice.disabled
8848
+ }))
8849
+ );
8850
+ if (!picked) {
8851
+ if (hadResumeMenu) {
8852
+ step = "thread";
8853
+ continue;
8854
+ }
8855
+ if (remoteTargets.length > 0) {
8856
+ step = "project";
8857
+ continue;
8858
+ }
8859
+ process.exit(0);
8860
+ }
8861
+ selectedAgent = picked;
8862
+ }
8863
+ if (selectedAgent === OPENSAMA_AGENT_ID) {
8864
+ const ok = await ensureSamaAuth(tui, api);
8865
+ if (!ok) {
8866
+ if (agentOverride) process.exit(0);
8867
+ continue;
8868
+ }
8869
+ }
8352
8870
  threadId = await createSessionThread();
8353
- historySeed = existing[0]?.id;
8871
+ break flow;
8354
8872
  }
8355
- } else {
8356
- threadId = await createSessionThread();
8357
8873
  }
8358
8874
  for (; ; ) {
8359
- await runInteractive(tui, api, threadId, projectDir, machine, resumed, session, historySeed);
8875
+ const agentTitle = AGENT_CHOICES.find((a) => a.id === selectedAgent)?.title ?? selectedAgent;
8876
+ await runInteractive(tui, api, threadId, projectDir, machine, resumed, session, agentTitle, historySeed);
8360
8877
  historySeed = threadId;
8361
8878
  threadId = await createSessionThread();
8362
8879
  await api.kvSet(threadId, "lease_supersedes", historySeed);
@@ -8372,7 +8889,7 @@ async function ensureSamaAuth(tui, api) {
8372
8889
  checking.stop();
8373
8890
  if (already) return true;
8374
8891
  const picked = await tui.select(
8375
- `${c3.bold}${gradientText("Authenticate with ChatGPT")}${c3.reset} ${c3.dim}Sama One runs on OpenAI using your own ChatGPT Pro account${c3.reset}`,
8892
+ `${c4.bold}${gradientText("Authenticate with ChatGPT")}${c4.reset} ${c4.dim}Sama One runs on OpenAI using your own ChatGPT Pro account${c4.reset}`,
8376
8893
  [
8377
8894
  {
8378
8895
  label: "Continue with ChatGPT",
@@ -8385,7 +8902,7 @@ async function ensureSamaAuth(tui, api) {
8385
8902
  if (picked !== "continue") return false;
8386
8903
  openUrl("https://standardcode.ai/app?connect=sama");
8387
8904
  tui.print(
8388
- `${c3.dim}Finish connecting ChatGPT in the browser \u2014 waiting here for the authorization to land on your account\u2026${c3.reset}`
8905
+ `${c4.dim}Finish connecting ChatGPT in the browser \u2014 waiting here for the authorization to land on your account\u2026${c4.reset}`
8389
8906
  );
8390
8907
  const waiting = startLoader("Waiting for your ChatGPT authorization");
8391
8908
  const deadline = Date.now() + 5 * 60 * 1e3;
@@ -8394,20 +8911,20 @@ async function ensureSamaAuth(tui, api) {
8394
8911
  if (await api.openSamaStatus().catch(() => false)) {
8395
8912
  waiting.stop();
8396
8913
  tui.print(
8397
- `${c3.green}\u2713${c3.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
8914
+ `${c4.green}\u2713${c4.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
8398
8915
  );
8399
8916
  return true;
8400
8917
  }
8401
8918
  }
8402
8919
  waiting.stop();
8403
8920
  tui.print(
8404
- `${c3.yellow}Still not connected.${c3.reset} Finish the flow at ${c3.teal}standardcode.ai/app${c3.reset} and pick Sama One again.`
8921
+ `${c4.yellow}Still not connected.${c4.reset} Finish the flow at ${c4.teal}standardcode.ai/app${c4.reset} and pick Sama One again.`
8405
8922
  );
8406
8923
  return false;
8407
8924
  }
8408
8925
  async function runAgentSwitchMenu(tui, api, threadId) {
8409
8926
  const picked = await tui.select(
8410
- `${c3.bold}${gradientText("Switch agent")}${c3.reset} ${c3.dim}takes effect on the next message${c3.reset}`,
8927
+ `${c4.bold}${gradientText("Switch agent")}${c4.reset} ${c4.dim}takes effect on the next message${c4.reset}`,
8411
8928
  AGENT_CHOICES.map((choice) => ({
8412
8929
  label: choice.title,
8413
8930
  hint: choice.description,
@@ -8423,41 +8940,68 @@ async function runAgentSwitchMenu(tui, api, threadId) {
8423
8940
  const title = AGENT_CHOICES.find((choice) => choice.id === picked)?.title ?? picked;
8424
8941
  try {
8425
8942
  await api.setThreadAgent(threadId, picked);
8426
- tui.print(`${c3.green}\u2713${c3.reset} Session handed to ${c3.bold}${title}${c3.reset} \u2014 applies from your next message.`);
8943
+ tui.setAgentLabel(title);
8944
+ tui.print(`${c4.green}\u2713${c4.reset} Session handed to ${c4.bold}${title}${c4.reset} \u2014 applies from your next message.`);
8427
8945
  } catch (e) {
8428
8946
  tui.print(
8429
- `${c3.red}\u2717 couldn't switch agent:${c3.reset} ${c3.gray}${e instanceof Error ? e.message : String(e)}${c3.reset}`
8947
+ `${c4.red}\u2717 couldn't switch agent:${c4.reset} ${c4.gray}${e instanceof Error ? e.message : String(e)}${c4.reset}`
8430
8948
  );
8431
8949
  }
8432
8950
  }
8951
+ async function pickLocalProject(tui, api, self, cwd, machineName) {
8952
+ const NEW = "__new__";
8953
+ const label = self ? machineDisplayName(self) : machineName;
8954
+ const others = Object.entries(self?.projects ?? {}).filter(([dir2]) => dir2 !== cwd).sort((a, b) => (b[1]?.last_used_at ?? 0) - (a[1]?.last_used_at ?? 0));
8955
+ const labels = projectDisplayLabels({ ...self?.projects ?? {}, [cwd]: self?.projects?.[cwd] });
8956
+ const items = [
8957
+ {
8958
+ label: labels.get(cwd) ?? projectDisplayName(cwd, self?.projects?.[cwd]),
8959
+ hint: "current directory",
8960
+ detail: shortenPath(cwd, 60),
8961
+ value: cwd
8962
+ },
8963
+ ...others.map(([dir2, p]) => ({
8964
+ label: labels.get(dir2) ?? projectDisplayName(dir2, p),
8965
+ hint: p?.last_used_at ? relativeTime(p.last_used_at / 1e3) : "",
8966
+ detail: shortenPath(dir2, 60),
8967
+ value: dir2
8968
+ })),
8969
+ { label: "\uFF0B New project", hint: "browse or create a directory", value: NEW }
8970
+ ];
8971
+ const picked = await tui.select(
8972
+ `${c4.bold}${gradientText(`Project on ${label}`)}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8973
+ items,
8974
+ { spaced: true }
8975
+ );
8976
+ if (!picked) return null;
8977
+ if (picked !== NEW) return picked;
8978
+ return await pickDirectory(tui, localBrowseBackend(label), { startPath: cwd, loader: startLoader });
8979
+ }
8433
8980
  async function pickRemoteProject(tui, api, runner) {
8434
8981
  const ENTER_PATH = "__enter_path__";
8435
8982
  const projects = Object.entries(runner.projects).sort(
8436
8983
  (a, b) => (b[1]?.last_used_at ?? 0) - (a[1]?.last_used_at ?? 0)
8437
8984
  );
8438
- const items = projects.map(([dir2, p]) => ({
8439
- label: shortenPath(dir2, 48),
8440
- hint: p?.last_used_at ? relativeTime(p.last_used_at / 1e3) : "",
8441
- value: dir2
8442
- }));
8443
- items.push({ label: `\uFF0B Another path on ${runner.name}\u2026`, hint: "type a directory", value: ENTER_PATH });
8985
+ const labels = projectDisplayLabels(runner.projects);
8986
+ const items = projects.map(
8987
+ ([dir2, p]) => ({
8988
+ label: labels.get(dir2) ?? projectDisplayName(dir2, p),
8989
+ hint: p?.last_used_at ? relativeTime(p.last_used_at / 1e3) : "",
8990
+ detail: shortenPath(dir2, 60),
8991
+ value: dir2
8992
+ })
8993
+ );
8994
+ items.push({ label: "\uFF0B New project", hint: "browse or create a directory", value: ENTER_PATH });
8444
8995
  const picked = await tui.select(
8445
- `${c3.bold}${gradientText(`Project on ${runner.name}`)}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
8446
- items
8996
+ `${c4.bold}${gradientText(`Project on ${runner.name}`)}${c4.reset} ${c4.dim}\u2191\u2193 \xB7 enter \xB7 esc${c4.reset}`,
8997
+ items,
8998
+ { spaced: true }
8447
8999
  );
8448
9000
  if (!picked) return null;
8449
9001
  if (picked !== ENTER_PATH) return picked;
8450
- const typed = await tui.prompt(
8451
- `Directory on ${runner.name} (absolute, created if missing)`,
8452
- "~/projects/my-app"
8453
- );
8454
- if (!typed) return null;
8455
- const trimmed = typed.trim();
8456
- if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
8457
- tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
8458
- return null;
8459
- }
8460
- return await resolveRemoteProjectPath(api, runner.id, trimmed);
9002
+ return await pickDirectory(tui, remoteBrowseBackend(api, runner.id, runner.name), {
9003
+ loader: startLoader
9004
+ });
8461
9005
  }
8462
9006
  function isSilentMessage(m) {
8463
9007
  return m?.silent === true || m?.metadata?.silent === true;
@@ -8474,14 +9018,15 @@ async function summarizeThreads(api, threads) {
8474
9018
  }
8475
9019
  const label = preview ? preview.length > 64 ? preview.slice(0, 63) + "\u2026" : preview : "(empty session)";
8476
9020
  const when = t.created_at ? relativeTime(t.created_at) : "";
8477
- const hint = [t.id.slice(0, 8), when].filter(Boolean).join(" \xB7 ");
9021
+ const agentTag = t.agent_id === OPENSAMA_AGENT_ID ? AGENT_CHOICES.find((a) => a.id === OPENSAMA_AGENT_ID)?.title ?? "Sama One" : "";
9022
+ const hint = [t.id.slice(0, 8), when, agentTag].filter(Boolean).join(" \xB7 ");
8478
9023
  return { id: t.id, label, hint };
8479
9024
  })
8480
9025
  );
8481
9026
  }
8482
9027
  function subagentLabel(s, titles) {
8483
9028
  const agentName = (s.agent_name || "").trim();
8484
- const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (c4) => c4.toUpperCase()) : "Subagent");
9029
+ const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (c5) => c5.toUpperCase()) : "Subagent");
8485
9030
  const tagged = (s.threadName || "").trim();
8486
9031
  return tagged ? `${title} \xB7 ${tagged}` : title;
8487
9032
  }
@@ -8504,8 +9049,8 @@ async function printHistory(api, threadId, tui) {
8504
9049
  ).sort((a, b) => (a.created_at ?? 0) - (b.created_at ?? 0));
8505
9050
  if (!convo.length) return;
8506
9051
  const shown = convo.slice(-24);
8507
- tui.print(`${c3.dim}\u2500\u2500 resuming session \xB7 ${convo.length} message${convo.length === 1 ? "" : "s"} \u2500\u2500${c3.reset}`);
8508
- if (shown.length < convo.length) tui.print(`${c3.dim} \u2026 earlier messages omitted${c3.reset}`);
9052
+ tui.print(`${c4.dim}\u2500\u2500 resuming session \xB7 ${convo.length} message${convo.length === 1 ? "" : "s"} \u2500\u2500${c4.reset}`);
9053
+ if (shown.length < convo.length) tui.print(`${c4.dim} \u2026 earlier messages omitted${c4.reset}`);
8509
9054
  for (const m of shown) {
8510
9055
  if (m.metadata?.user_command) {
8511
9056
  printCommandBlock(
@@ -8522,9 +9067,12 @@ async function printHistory(api, threadId, tui) {
8522
9067
  else printAssistant(tui, text);
8523
9068
  }
8524
9069
  }
8525
- async function runInteractive(tui, api, threadId, projectDir, machine, resumed, session, historySeedThreadId) {
9070
+ async function runInteractive(tui, api, threadId, projectDir, machine, resumed, session, agentTitle, historySeedThreadId) {
8526
9071
  const remote = session.mode === "remote";
8527
9072
  const runnerName = session.runner?.name ?? "the remote machine";
9073
+ let currentOwner = null;
9074
+ let ownershipKnown = false;
9075
+ const describeExecOwner = (o) => o?.client_kind === "daemon" ? "the daemon on this machine" : o?.client_name || "another client";
8528
9076
  const registry = new ProcessRegistry(api, threadId, machine);
8529
9077
  const refreshBgCount = () => {
8530
9078
  void registry.runningCount().then((n) => tui.setBackgroundCount(n)).catch(() => {
@@ -8550,6 +9098,7 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
8550
9098
  saveApprovals(api, threadId, perm);
8551
9099
  const attaching = startLoader("Attaching to thread");
8552
9100
  let busy = false;
9101
+ let optimisticBusyUntil = 0;
8553
9102
  let sharedMessaging = emptySharedMessagingSnapshot();
8554
9103
  let mirroredDraftRefs = [];
8555
9104
  let sharedMessagingReady = false;
@@ -8609,26 +9158,28 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
8609
9158
  if (detail) for (const d of detail) tui.print(d);
8610
9159
  refreshBgCount();
8611
9160
  },
8612
- onOwnership: (isOwner, owner) => {
9161
+ onOwnership: (isOwner, owner2) => {
9162
+ const wasKnown = ownershipKnown;
9163
+ ownershipKnown = true;
9164
+ currentOwner = owner2;
8613
9165
  if (isOwner) {
8614
9166
  bridge?.setClaim("takeover");
8615
9167
  exec?.writeSessionInfo();
8616
- } else {
8617
- tui.print(
8618
- `${c3.yellow}\u26A0 Another client${owner?.client_name ? ` (${owner.client_name})` : ""} is executing tools for this session \u2014 this terminal is watching.${c3.reset}`
8619
- );
9168
+ if (wasKnown) tui.print(`${c4.dim}Tool execution moved to this terminal.${c4.reset}`);
9169
+ } else if (wasKnown) {
9170
+ tui.print(`${c4.dim}Tool execution moved to ${describeExecOwner(owner2)}.${c4.reset}`);
8620
9171
  }
8621
9172
  },
8622
9173
  onClaimRefused: (reason) => {
8623
9174
  if (reason === "in_flight") {
8624
9175
  tui.print(
8625
- `${c3.yellow}\u26A0 The session's current client is mid-operation \u2014 execution can't move here until it finishes. Watching for now.${c3.reset}`
9176
+ `${c4.dim}The session's current client is mid-operation \u2014 execution stays there until it finishes.${c4.reset}`
8626
9177
  );
8627
9178
  }
8628
9179
  },
8629
9180
  onSuperseded: () => {
8630
9181
  tui.print(
8631
- `${c3.yellow}\u26A0 Another Standard Code process on this machine took over this session \u2014 this terminal is watching.${c3.reset}`
9182
+ `${c4.yellow}\u26A0 Another Standard Code process on this machine took over this session \u2014 this terminal is watching.${c4.reset}`
8632
9183
  );
8633
9184
  },
8634
9185
  onStatus: (id, summary) => {
@@ -8767,7 +9318,7 @@ why: ${req.requestPermission}` : ""}`,
8767
9318
  const sessionEnded = new Promise((r) => endSession = r);
8768
9319
  const quit = async () => {
8769
9320
  tui.end();
8770
- const stopped2 = bridge?.isOwner ?? false ? api.requestSharedStop(threadId, messagingOrigin).catch(() => {
9321
+ const stopped2 = bridge?.isOwner ?? false ? api.stopThread(threadId).catch(() => {
8771
9322
  }) : Promise.resolve();
8772
9323
  const procsStopped2 = host ? host.stopAllLocalProcesses().catch(() => 0) : Promise.resolve(0);
8773
9324
  bridge?.close();
@@ -8786,7 +9337,7 @@ why: ${req.requestPermission}` : ""}`,
8786
9337
  const logout = async () => {
8787
9338
  deleteCredential(api.origin);
8788
9339
  const instanceHost = api.origin.replace(/^https?:\/\//, "");
8789
- tui.print(`${c3.gray}Signed out \u2014 removed the saved token for ${c3.teal}${instanceHost}${c3.reset}${c3.gray}. Run standardcode to sign in again.${c3.reset}`);
9340
+ tui.print(`${c4.gray}Signed out \u2014 removed the saved token for ${c4.teal}${instanceHost}${c4.reset}${c4.gray}. Run standardcode to sign in again.${c4.reset}`);
8790
9341
  await quit();
8791
9342
  };
8792
9343
  const bgMgr = {
@@ -8794,7 +9345,7 @@ why: ${req.requestPermission}` : ""}`,
8794
9345
  stop: async (id) => {
8795
9346
  if (!host) {
8796
9347
  tui.print(
8797
- `${c3.gray}That process runs on ${runnerName} \u2014 ask the agent to stop it (it manages processes there).${c3.reset}`
9348
+ `${c4.gray}That process runs on ${runnerName} \u2014 ask the agent to stop it (it manages processes there).${c4.reset}`
8798
9349
  );
8799
9350
  return;
8800
9351
  }
@@ -8875,7 +9426,7 @@ why: ${req.requestPermission}` : ""}`,
8875
9426
  } catch (error) {
8876
9427
  if (!sharedMessagingReady && !sharedMessagingUnavailableShown) {
8877
9428
  sharedMessagingUnavailableShown = true;
8878
- tui.print(`${c3.dim}shared messaging unavailable: ${error instanceof Error ? error.message : String(error)}${c3.reset}`);
9429
+ tui.print(`${c4.dim}shared messaging unavailable: ${error instanceof Error ? error.message : String(error)}${c4.reset}`);
8879
9430
  }
8880
9431
  }
8881
9432
  };
@@ -8885,7 +9436,7 @@ why: ${req.requestPermission}` : ""}`,
8885
9436
  applySharedMessaging(snapshot, false);
8886
9437
  return true;
8887
9438
  }).catch((error) => {
8888
- tui.print(`${c3.dim}shared messaging failed: ${error instanceof Error ? error.message : String(error)}${c3.reset}`);
9439
+ tui.print(`${c4.dim}shared messaging failed: ${error instanceof Error ? error.message : String(error)}${c4.reset}`);
8889
9440
  return false;
8890
9441
  });
8891
9442
  const appendSharedPending = (text, images, refs = []) => applySharedMutation(api.appendPendingInput(threadId, {
@@ -8893,11 +9444,6 @@ why: ${req.requestPermission}` : ""}`,
8893
9444
  attachments: [...refs, ...toSharedAttachments(images)],
8894
9445
  ...messagingOrigin
8895
9446
  }));
8896
- const steerSharedInput = (text, images, refs = []) => applySharedMutation(api.steerInput(threadId, {
8897
- content: text,
8898
- attachments: [...refs, ...toSharedAttachments(images)],
8899
- ...messagingOrigin
8900
- }));
8901
9447
  const editSharedPending = (item, text, images, refs) => applySharedMutation(api.updatePendingInput(threadId, item.id, {
8902
9448
  content: text,
8903
9449
  attachments: [
@@ -8907,34 +9453,52 @@ why: ${req.requestPermission}` : ""}`,
8907
9453
  ...messagingOrigin
8908
9454
  }));
8909
9455
  const dismissSharedPending = (item) => applySharedMutation(api.dismissPendingInput(threadId, item.id, messagingOrigin));
8910
- const promoteSharedPending = (item) => applySharedMutation(api.steerPendingInput(threadId, item.id, messagingOrigin));
9456
+ const promoteSharedPending = async (item) => {
9457
+ if (busy) {
9458
+ await Promise.all([
9459
+ api.stopThread(threadId).catch(() => {
9460
+ }),
9461
+ ...[...activeSubagents.keys()].map((childId) => api.stopThread(childId).catch(() => {
9462
+ }))
9463
+ ]);
9464
+ const promoted = await applySharedMutation(api.steerPendingInput(threadId, item.id, messagingOrigin));
9465
+ if (promoted) await api.continueThread(threadId).catch(() => {
9466
+ });
9467
+ return promoted;
9468
+ }
9469
+ return applySharedMutation(api.steerPendingInput(threadId, item.id, messagingOrigin));
9470
+ };
8911
9471
  const sendNow = async (text, images = [], refs = []) => {
8912
9472
  lastSent = { text, images, refs };
8913
9473
  tui.printUserMessage(text || `\u{1F4CE} ${refs.length + images.length} attachment(s)`);
8914
9474
  const key = text.trim();
8915
9475
  pendingSent.set(key, (pendingSent.get(key) ?? 0) + 1);
9476
+ busy = true;
9477
+ optimisticBusyUntil = Date.now() + 8e3;
9478
+ tui.setWorking(true);
8916
9479
  try {
8917
9480
  await api.sendMessage(threadId, text, [...refs, ...toAttachments(images)]);
8918
9481
  } catch (e) {
8919
9482
  const n = (pendingSent.get(key) ?? 1) - 1;
8920
9483
  if (n > 0) pendingSent.set(key, n);
8921
9484
  else pendingSent.delete(key);
8922
- tui.print(`${c3.dim}failed to send: ${e instanceof Error ? e.message : String(e)}${c3.reset}`);
9485
+ busy = false;
9486
+ optimisticBusyUntil = 0;
9487
+ tui.setWorking(false);
9488
+ tui.print(`${c4.dim}failed to send: ${e instanceof Error ? e.message : String(e)}${c4.reset}`);
8923
9489
  return false;
8924
9490
  }
8925
- busy = true;
8926
- tui.setWorking(true);
8927
9491
  return true;
8928
9492
  };
8929
9493
  const whereLabel = remote ? runnerName : "this machine";
8930
9494
  let bangRunning = false;
8931
9495
  const runBangCommand = async (command) => {
8932
9496
  if (bangRunning) {
8933
- tui.print(`${c3.dim}a command is already running \u2014 one at a time.${c3.reset}`);
9497
+ tui.print(`${c4.dim}a command is already running \u2014 one at a time.${c4.reset}`);
8934
9498
  return;
8935
9499
  }
8936
9500
  bangRunning = true;
8937
- tui.print(`${c3.magenta}!${c3.reset} ${c3.dim}running on ${whereLabel}\u2026${c3.reset}`);
9501
+ tui.print(`${c4.magenta}!${c4.reset} ${c4.dim}running on ${whereLabel}\u2026${c4.reset}`);
8938
9502
  try {
8939
9503
  const res = await api.runCommand(threadId, command);
8940
9504
  if (res.messageId) shownIds.add(res.messageId);
@@ -8948,7 +9512,7 @@ why: ${req.requestPermission}` : ""}`,
8948
9512
  const openPendingMenu = async () => {
8949
9513
  const items = sharedMessaging.pending.items;
8950
9514
  if (!items.length) {
8951
- tui.print(`${c3.dim}No pending messages.${c3.reset}`);
9515
+ tui.print(`${c4.dim}No pending messages.${c4.reset}`);
8952
9516
  return;
8953
9517
  }
8954
9518
  const picked = await tui.select("Pending messages", items.map((item, index) => ({
@@ -8978,16 +9542,16 @@ why: ${req.requestPermission}` : ""}`,
8978
9542
  try {
8979
9543
  await api.compact(threadId);
8980
9544
  } catch (err) {
8981
- tui.print(`${c3.red}\u2717${c3.reset} couldn't start compaction: ${err.message}`);
9545
+ tui.print(`${c4.red}\u2717${c4.reset} couldn't start compaction: ${err.message}`);
8982
9546
  }
8983
9547
  };
8984
9548
  const runAccountCommand = async () => {
8985
- tui.print(`${c3.gray}Opening your account\u2026${c3.reset}`);
9549
+ tui.print(`${c4.gray}Opening your account\u2026${c4.reset}`);
8986
9550
  const link = await api.accountLink(threadId).catch(() => null);
8987
9551
  const target = link?.url ?? "https://standardcode.ai/account";
8988
9552
  openUrl(target);
8989
9553
  tui.print(
8990
- link?.preauthed ? `${c3.gray}\u2192 account dashboard opened in your browser (signed in)${c3.reset}` : `${c3.gray}\u2192 opened ${target} \u2014 sign in with your account email${c3.reset}`
9554
+ link?.preauthed ? `${c4.gray}\u2192 account dashboard opened in your browser (signed in)${c4.reset}` : `${c4.gray}\u2192 opened ${target} \u2014 sign in with your account email${c4.reset}`
8991
9555
  );
8992
9556
  };
8993
9557
  const ordinal = (n) => {
@@ -8998,24 +9562,24 @@ why: ${req.requestPermission}` : ""}`,
8998
9562
  const renderUpgradePanel = (q) => {
8999
9563
  const dots = [];
9000
9564
  for (let i = 0; i < q.max; i++) {
9001
- if (i < q.current) dots.push(`${c3.teal}\u25CF${c3.reset}`);
9002
- else if (i === q.current) dots.push(`${c3.bold}${gradientText("\uFF0B")}${c3.reset}`);
9003
- else dots.push(`${c3.dim}\xB7${c3.reset}`);
9565
+ if (i < q.current) dots.push(`${c4.teal}\u25CF${c4.reset}`);
9566
+ else if (i === q.current) dots.push(`${c4.bold}${gradientText("\uFF0B")}${c4.reset}`);
9567
+ else dots.push(`${c4.dim}\xB7${c4.reset}`);
9004
9568
  }
9005
9569
  const cost = fmtCost(q);
9006
9570
  const lines = [
9007
9571
  "",
9008
- `${c3.bold}${gradientText("\u2726 Add a parallel session")}${c3.reset}`,
9572
+ `${c4.bold}${gradientText("\u2726 Add a parallel session")}${c4.reset}`,
9009
9573
  "",
9010
- `${dots.join(" ")} ${c3.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c3.reset}`
9574
+ `${dots.join(" ")} ${c4.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c4.reset}`
9011
9575
  ];
9012
9576
  if (q.ends_trial) {
9013
9577
  lines.push(
9014
- `${c3.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c3.reset}`,
9015
- `${c3.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c3.bold}${cost} charged today${c3.reset}${c3.yellow}` : ""}.${c3.reset}`
9578
+ `${c4.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c4.reset}`,
9579
+ `${c4.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c4.bold}${cost} charged today${c4.reset}${c4.yellow}` : ""}.${c4.reset}`
9016
9580
  );
9017
9581
  } else if (cost) {
9018
- lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c3.bold}${cost} charged now${c3.reset}.`);
9582
+ lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c4.bold}${cost} charged now${c4.reset}.`);
9019
9583
  } else {
9020
9584
  lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 prorated on your next invoice.`);
9021
9585
  }
@@ -9028,7 +9592,7 @@ why: ${req.requestPermission}` : ""}`,
9028
9592
  try {
9029
9593
  if (opts.auto) {
9030
9594
  tui.print(
9031
- `${c3.yellow}You're out of simultaneous sessions \u2014 another Standard Code session is using your slot.${c3.reset}`
9595
+ `${c4.yellow}You're out of simultaneous sessions \u2014 another Standard Code session is using your slot.${c4.reset}`
9032
9596
  );
9033
9597
  }
9034
9598
  const quote = await api.sessionsQuote(threadId);
@@ -9036,16 +9600,16 @@ why: ${req.requestPermission}` : ""}`,
9036
9600
  const link = await api.accountLink(threadId).catch(() => null);
9037
9601
  const target = link?.url ?? "https://standardcode.ai/account";
9038
9602
  tui.print(
9039
- `${c3.gray}Close the other session (its slot frees within ~90s) \u2014 or add another simultaneous session to your plan, then resend your message.${c3.reset}`
9603
+ `${c4.gray}Close the other session (its slot frees within ~90s) \u2014 or add another simultaneous session to your plan, then resend your message.${c4.reset}`
9040
9604
  );
9041
9605
  openUrl(target);
9042
- tui.print(`${c3.gray}\u2192 opened ${target} to manage your plan${c3.reset}`);
9606
+ tui.print(`${c4.gray}\u2192 opened ${target} to manage your plan${c4.reset}`);
9043
9607
  return;
9044
9608
  }
9045
9609
  if (quote.current >= quote.max) {
9046
9610
  tui.print(
9047
- `${c3.yellow}You're at the maximum of ${quote.max} parallel session${quote.max === 1 ? "" : "s"}.${c3.reset}
9048
- ${c3.gray}Close another session (its slot frees within ~90s), then resend your message.${c3.reset}`
9611
+ `${c4.yellow}You're at the maximum of ${quote.max} parallel session${quote.max === 1 ? "" : "s"}.${c4.reset}
9612
+ ${c4.gray}Close another session (its slot frees within ~90s), then resend your message.${c4.reset}`
9049
9613
  );
9050
9614
  return;
9051
9615
  }
@@ -9057,25 +9621,25 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9057
9621
  { label: "Not now", value: "no" }
9058
9622
  ]);
9059
9623
  if (choice !== "go") {
9060
- tui.print(`${c3.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c3.reset}`);
9624
+ tui.print(`${c4.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c4.reset}`);
9061
9625
  return;
9062
9626
  }
9063
- tui.print(`${c3.gray}Applying\u2026${c3.reset}`);
9627
+ tui.print(`${c4.gray}Applying\u2026${c4.reset}`);
9064
9628
  let applied;
9065
9629
  try {
9066
9630
  applied = await api.sessionsUpgrade(threadId, quote.sessions);
9067
9631
  } catch (e) {
9068
- tui.print(`${c3.red}\u2717${c3.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
9632
+ tui.print(`${c4.red}\u2717${c4.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
9069
9633
  return;
9070
9634
  }
9071
9635
  if (!applied?.ok) {
9072
- tui.print(`${c3.red}\u2717${c3.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
9636
+ tui.print(`${c4.red}\u2717${c4.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
9073
9637
  return;
9074
9638
  }
9075
9639
  const n = applied.sessions ?? quote.sessions;
9076
- tui.print(`${c3.green}\u2713${c3.reset} ${c3.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c3.reset}`);
9640
+ tui.print(`${c4.green}\u2713${c4.reset} ${c4.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c4.reset}`);
9077
9641
  if (opts.auto && lastSent) {
9078
- tui.print(`${c3.gray}Continuing\u2026${c3.reset}`);
9642
+ tui.print(`${c4.gray}Continuing\u2026${c4.reset}`);
9079
9643
  await sendNow(lastSent.text, lastSent.images, lastSent.refs);
9080
9644
  }
9081
9645
  } finally {
@@ -9217,7 +9781,7 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9217
9781
  const submitComposer = async (text, images, steer) => {
9218
9782
  const draftRefs = mirroredDraftRefs;
9219
9783
  if (!sharedMessagingReady && (busy || steer || editingPendingId !== null)) {
9220
- tui.print(`${c3.dim}Restoring shared message state \u2014 try again in a moment.${c3.reset}`);
9784
+ tui.print(`${c4.dim}Restoring shared message state \u2014 try again in a moment.${c4.reset}`);
9221
9785
  tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
9222
9786
  tui.setInput(text, images);
9223
9787
  return;
@@ -9240,11 +9804,11 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9240
9804
  const item = sharedMessaging.pending.items.find((candidate) => candidate.id === pendingId);
9241
9805
  editingPendingId = null;
9242
9806
  if (!item) {
9243
- tui.print(`${c3.dim}That pending message was already dispatched or dismissed.${c3.reset}`);
9807
+ tui.print(`${c4.dim}That pending message was already dispatched or dismissed.${c4.reset}`);
9244
9808
  return;
9245
9809
  }
9246
9810
  const updated = await editSharedPending(item, text, images, draftRefs);
9247
- const promoted = !steer || !updated ? updated : await applySharedMutation(api.steerPendingInput(threadId, pendingId, messagingOrigin));
9811
+ const promoted = !steer || !updated ? updated : await promoteSharedPending({ ...item, id: pendingId });
9248
9812
  if (!promoted) {
9249
9813
  mirroredDraftRefs = item.attachments.filter(isSharedAttachmentRef);
9250
9814
  tui.setExternalAttachmentNames(mirroredDraftRefs.map((attachment) => attachment.name));
@@ -9253,8 +9817,14 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9253
9817
  return;
9254
9818
  }
9255
9819
  if (steer) {
9256
- tui.print(`${c3.yellow}\u21AA steering at the next safe model boundary:${c3.reset} ${text}`);
9257
- if (!await steerSharedInput(text, images, draftRefs)) {
9820
+ tui.print(`${c4.yellow}\u21AA steering now \u2014 stopping the current step${c4.reset}`);
9821
+ await Promise.all([
9822
+ api.stopThread(threadId).catch(() => {
9823
+ }),
9824
+ ...[...activeSubagents.keys()].map((childId) => api.stopThread(childId).catch(() => {
9825
+ }))
9826
+ ]);
9827
+ if (!await sendNow(text, images, draftRefs)) {
9258
9828
  mirroredDraftRefs = draftRefs;
9259
9829
  tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
9260
9830
  tui.setInput(text, images);
@@ -9263,7 +9833,7 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9263
9833
  }
9264
9834
  if (busy) {
9265
9835
  if (await appendSharedPending(text, images, draftRefs)) {
9266
- tui.print(`${c3.gray}\u23F3 pending:${c3.reset} ${text} ${c3.dim}(/queue to edit, steer, or dismiss)${c3.reset}`);
9836
+ tui.print(`${c4.gray}\u23F3 pending:${c4.reset} ${text} ${c4.dim}(/queue to edit, steer, or dismiss)${c4.reset}`);
9267
9837
  } else {
9268
9838
  mirroredDraftRefs = draftRefs;
9269
9839
  tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
@@ -9286,18 +9856,20 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9286
9856
  tui.onInterrupt = () => {
9287
9857
  const firstPending = sharedMessaging.pending.items[0];
9288
9858
  if (!busy && firstPending) {
9289
- tui.print(`${c3.yellow}\u21AA steering the first pending message\u2026${c3.reset}`);
9859
+ tui.print(`${c4.yellow}\u21AA steering the first pending message\u2026${c4.reset}`);
9290
9860
  void promoteSharedPending(firstPending);
9291
9861
  return;
9292
9862
  }
9293
9863
  if (busy) {
9294
- if (!sharedMessagingReady) {
9295
- tui.print(`${c3.dim}Shared messaging is not connected; the session was not stopped.${c3.reset}`);
9296
- return;
9864
+ const queued = sharedMessaging.pending.items.length;
9865
+ tui.print(`${c4.yellow}\u25A0 stopping now${queued > 0 ? ` \u2014 ${queued} queued message${queued === 1 ? "" : "s"} kept` : ""}${c4.reset}`);
9866
+ const stops = [api.stopThread(threadId).catch(() => {
9867
+ })];
9868
+ for (const childId of activeSubagents.keys()) {
9869
+ stops.push(api.stopThread(childId).catch(() => {
9870
+ }));
9297
9871
  }
9298
- const advancing = sharedMessaging.pending.items.length > 0;
9299
- tui.print(`${c3.yellow}${advancing ? "[stopping; next pending message will run]" : "[stopping at the next safe boundary]"}${c3.reset}`);
9300
- void applySharedMutation(api.requestSharedStop(threadId, messagingOrigin));
9872
+ void Promise.all(stops);
9301
9873
  }
9302
9874
  };
9303
9875
  tui.onBgBadge = () => {
@@ -9320,20 +9892,32 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9320
9892
  void api.getGoal(threadId).then((g) => tui.setGoal(g)).catch(() => {
9321
9893
  });
9322
9894
  attaching.stop();
9895
+ const header = `${c4.bold}${c4.magenta}Standard Code${c4.reset} ${c4.dim}\u2014 ${agentTitle}${c4.reset}`;
9896
+ const remoteDaemonV = session.runner?.daemon?.version;
9897
+ const owner = currentOwner;
9898
+ let localOwnerDesc = ownsExecution ? "this terminal" : describeExecOwner(owner);
9899
+ if (!remote && !ownsExecution && owner?.client_kind === "daemon") {
9900
+ const selfRec = await loadMachine(api, session.identity.machine_id).catch(() => null);
9901
+ if (selfRec?.daemon?.version) localOwnerDesc = `the daemon on this machine (v${selfRec.daemon.version})`;
9902
+ }
9903
+ const execLine = remote ? `${c4.gray}tool execution:${c4.reset} daemon${remoteDaemonV ? ` v${remoteDaemonV}` : ""} on ${runnerName}` : `${c4.gray}tool execution:${c4.reset} ${localOwnerDesc}`;
9904
+ tui.setAgentLabel(agentTitle);
9323
9905
  tui.banner(
9324
9906
  remote ? [
9325
- `${c3.bold}${c3.magenta}Standard Code${c3.reset} ${c3.dim}\u2014 coding agent${c3.reset}`,
9326
- `${c3.gray}project:${c3.reset} ${session.remotePath ?? "?"} ${c3.teal}on ${runnerName}${c3.reset}`,
9327
- `${c3.gray}runs on:${c3.reset} ${runnerName} ${c3.dim}(daemon executes tools; you're watching from ${machine})${c3.reset} ${c3.gray}thread:${c3.reset} ${threadId.slice(0, 8)}`
9907
+ header,
9908
+ `${c4.gray}project:${c4.reset} ${session.remotePath ?? "?"} ${c4.teal}on ${runnerName}${c4.reset}`,
9909
+ `${c4.gray}machine:${c4.reset} ${runnerName} ${c4.gray}thread:${c4.reset} ${threadId.slice(0, 8)}`,
9910
+ execLine
9328
9911
  ] : [
9329
- `${c3.bold}${c3.magenta}Standard Code${c3.reset} ${c3.dim}\u2014 coding agent${c3.reset}`,
9330
- `${c3.gray}project:${c3.reset} ${projectDir}`,
9331
- `${c3.gray}machine:${c3.reset} ${machine} ${c3.gray}thread:${c3.reset} ${threadId.slice(0, 8)}`
9912
+ header,
9913
+ `${c4.gray}project:${c4.reset} ${projectDir}`,
9914
+ `${c4.gray}machine:${c4.reset} ${machine} ${c4.gray}thread:${c4.reset} ${threadId.slice(0, 8)}`,
9915
+ execLine
9332
9916
  ]
9333
9917
  );
9334
9918
  if (!remote && session.suggestDaemonInstall && process.platform !== "win32" && !serviceStatus().installed) {
9335
9919
  tui.print(
9336
- `${c3.dim}Tip: install the always-on daemon (${c3.reset}standardcode daemon install${c3.dim}) to start sessions on this machine from anywhere.${c3.reset}`
9920
+ `${c4.dim}Tip: install the always-on daemon (${c4.reset}standardcode daemon install${c4.dim}) to start sessions on this machine from anywhere.${c4.reset}`
9337
9921
  );
9338
9922
  }
9339
9923
  if (resumed) await printHistory(api, threadId, tui);
@@ -9344,17 +9928,17 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9344
9928
  const runningProcs = (await registry.list()).filter((p) => p.status === "running");
9345
9929
  if (runningProcs.length) {
9346
9930
  tui.print(
9347
- `${c3.cyan}\u2699 ${runningProcs.length} background process${runningProcs.length === 1 ? "" : "es"} running:${c3.reset}`
9931
+ `${c4.cyan}\u2699 ${runningProcs.length} background process${runningProcs.length === 1 ? "" : "es"} running:${c4.reset}`
9348
9932
  );
9349
- for (const p of runningProcs) tui.print(`${c3.gray} ${p.id} ${p.description || p.command}${c3.reset}`);
9933
+ for (const p of runningProcs) tui.print(`${c4.gray} ${p.id} ${p.description || p.command}${c4.reset}`);
9350
9934
  }
9351
9935
  refreshBgCount();
9352
9936
  if (exec && ownsExecution) {
9353
9937
  for (const res of await exec.connectEnabledMcpServers()) {
9354
9938
  if (res.ok) {
9355
- tui.print(`${c3.cyan}\u26A1 MCP "${res.name}" connected${c3.reset} ${c3.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c3.reset}`);
9939
+ tui.print(`${c4.cyan}\u26A1 MCP "${res.name}" connected${c4.reset} ${c4.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c4.reset}`);
9356
9940
  } else {
9357
- tui.print(`${c3.red}\u26A0 MCP "${res.name}" failed:${c3.reset} ${c3.gray}${res.error}${c3.reset}`);
9941
+ tui.print(`${c4.red}\u26A0 MCP "${res.name}" failed:${c4.reset} ${c4.gray}${res.error}${c4.reset}`);
9358
9942
  }
9359
9943
  }
9360
9944
  }
@@ -9370,8 +9954,8 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
9370
9954
  try {
9371
9955
  const { choice, reason } = await tui.approval(
9372
9956
  `${request.summary}${request.permission ? `
9373
- ${c3.bold}why: ${request.permission}${c3.reset}` : ""}
9374
- ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9957
+ ${c4.bold}why: ${request.permission}${c4.reset}` : ""}
9958
+ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
9375
9959
  request.risk
9376
9960
  );
9377
9961
  answeredApprovals.add(request.tool_call_id);
@@ -9422,7 +10006,7 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9422
10006
  continue;
9423
10007
  }
9424
10008
  if (m.role === "assistant" && text) printAssistant(tui, text);
9425
- else if (m.role === "system" && text) tui.print(`${c3.dim}${text}${c3.reset}`);
10009
+ else if (m.role === "system" && text) tui.print(`${c4.dim}${text}${c4.reset}`);
9426
10010
  else if (m.role === "user" && text) {
9427
10011
  const pending = pendingSent.get(text) ?? 0;
9428
10012
  if (pending > 0) {
@@ -9444,7 +10028,8 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9444
10028
  activeSteps.clear();
9445
10029
  refreshStatus();
9446
10030
  }
9447
- busy = polledBusy;
10031
+ if (polledBusy) optimisticBusyUntil = 0;
10032
+ busy = polledBusy || Date.now() < optimisticBusyUntil;
9448
10033
  tui.setWorking(busy);
9449
10034
  if (!busy) {
9450
10035
  if (activeSteps.size) activeSteps.clear();
@@ -9495,7 +10080,7 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9495
10080
  clearInterval(pollTimer);
9496
10081
  clearInterval(heartbeatPoll);
9497
10082
  process.off("SIGCONT", onTerminalResume);
9498
- const stopped = bridge?.isOwner ?? false ? api.requestSharedStop(threadId, messagingOrigin).catch(() => {
10083
+ const stopped = bridge?.isOwner ?? false ? api.stopThread(threadId).catch(() => {
9499
10084
  }) : Promise.resolve();
9500
10085
  const procsStopped = host ? host.stopAllLocalProcesses().catch(() => 0) : Promise.resolve(0);
9501
10086
  bridge?.close();
@@ -9515,16 +10100,16 @@ ${c3.dim}runs on ${request.machine || runnerName}${c3.reset}`,
9515
10100
  tui.setStep(null, 0);
9516
10101
  tui.setBackgroundCount(0);
9517
10102
  if (killed > 0) {
9518
- tui.print(`${c3.cyan}\u2699${c3.reset} Stopped ${killed} background process${killed === 1 ? "" : "es"}.`);
10103
+ tui.print(`${c4.cyan}\u2699${c4.reset} Stopped ${killed} background process${killed === 1 ? "" : "es"}.`);
9519
10104
  }
9520
- tui.print(`${c3.dim}\u2500\u2500 conversation cleared \u2014 starting a fresh session \u2500\u2500${c3.reset}`);
10105
+ tui.print(`${c4.dim}\u2500\u2500 conversation cleared \u2014 starting a fresh session \u2500\u2500${c4.reset}`);
9521
10106
  }
9522
10107
  async function runSkillsMenu(tui, skills) {
9523
10108
  let list;
9524
10109
  try {
9525
10110
  list = await skills.list();
9526
10111
  } catch (e) {
9527
- tui.print(`${c3.red}\u2717 couldn't load skills:${c3.reset} ${c3.gray}${e instanceof Error ? e.message : String(e)}${c3.reset}`);
10112
+ tui.print(`${c4.red}\u2717 couldn't load skills:${c4.reset} ${c4.gray}${e instanceof Error ? e.message : String(e)}${c4.reset}`);
9528
10113
  return;
9529
10114
  }
9530
10115
  const INSTALL = "__install__";
@@ -9535,7 +10120,7 @@ async function runSkillsMenu(tui, skills) {
9535
10120
  }));
9536
10121
  items.push({ label: "\uFF0B Install a skill\u2026", hint: "find & install", value: INSTALL });
9537
10122
  const picked = await tui.select(
9538
- `${c3.bold}Agent skills${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c3.reset}`,
10123
+ `${c4.bold}Agent skills${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c4.reset}`,
9539
10124
  items
9540
10125
  );
9541
10126
  if (!picked) return;
@@ -9548,8 +10133,8 @@ async function runSkillsMenu(tui, skills) {
9548
10133
  return;
9549
10134
  }
9550
10135
  const skill = list.find((s) => s.name === picked);
9551
- tui.print(`${c3.cyan}${skill.name}${c3.reset}${skill.version ? ` ${c3.dim}v${skill.version}${c3.reset}` : ""} ${c3.gray}\u2014 ${skill.description}${c3.reset}`);
9552
- const action = await tui.select(`${c3.bold}${picked}${c3.reset}`, [
10136
+ tui.print(`${c4.cyan}${skill.name}${c4.reset}${skill.version ? ` ${c4.dim}v${skill.version}${c4.reset}` : ""} ${c4.gray}\u2014 ${skill.description}${c4.reset}`);
10137
+ const action = await tui.select(`${c4.bold}${picked}${c4.reset}`, [
9553
10138
  skill.enabled ? { label: "Disable (hide from the agent)", value: "disable" } : { label: "Enable", value: "enable" },
9554
10139
  { label: "View files", value: "files" },
9555
10140
  { label: "Remove this skill", value: "remove" },
@@ -9558,20 +10143,20 @@ async function runSkillsMenu(tui, skills) {
9558
10143
  try {
9559
10144
  if (action === "enable" || action === "disable") {
9560
10145
  await skills.setEnabled(picked, action === "enable");
9561
- tui.print(`${c3.gray}${action}d ${picked}${c3.reset}`);
10146
+ tui.print(`${c4.gray}${action}d ${picked}${c4.reset}`);
9562
10147
  } else if (action === "files") {
9563
- for (const f of skill.files) tui.print(` ${c3.gray}${f}${c3.reset}`);
10148
+ for (const f of skill.files) tui.print(` ${c4.gray}${f}${c4.reset}`);
9564
10149
  } else if (action === "remove") {
9565
10150
  await skills.remove(picked);
9566
- tui.print(`${c3.gray}removed ${picked}${c3.reset}`);
10151
+ tui.print(`${c4.gray}removed ${picked}${c4.reset}`);
9567
10152
  }
9568
10153
  } catch (e) {
9569
- tui.print(`${c3.red}\u2717 ${e instanceof Error ? e.message : String(e)}${c3.reset}`);
10154
+ tui.print(`${c4.red}\u2717 ${e instanceof Error ? e.message : String(e)}${c4.reset}`);
9570
10155
  }
9571
10156
  }
9572
10157
  async function runLevelMenu(tui, perm) {
9573
10158
  const picked = await tui.select(
9574
- `${c3.bold}Auto-accept level${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 shift-tab cycles)${c3.reset}`,
10159
+ `${c4.bold}Auto-accept level${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 shift-tab cycles)${c4.reset}`,
9575
10160
  LEVELS.map((l) => ({
9576
10161
  label: levelLabel(l),
9577
10162
  hint: l === tui.level ? "current" : "",
@@ -9588,16 +10173,16 @@ async function runMachinesMenu(tui, api, self) {
9588
10173
  try {
9589
10174
  machines = await loadMachines(api);
9590
10175
  } catch (e) {
9591
- tui.print(`${c3.red}\u2717 couldn't load machines:${c3.reset} ${c3.gray}${e instanceof Error ? e.message : String(e)}${c3.reset}`);
10176
+ tui.print(`${c4.red}\u2717 couldn't load machines:${c4.reset} ${c4.gray}${e instanceof Error ? e.message : String(e)}${c4.reset}`);
9592
10177
  return;
9593
10178
  }
9594
10179
  if (!machines.length) {
9595
- tui.print(`${c3.gray}No machines registered yet. Run standardcode on a machine (or install its daemon) to register it.${c3.reset}`);
10180
+ tui.print(`${c4.gray}No machines registered yet. Run standardcode on a machine (or install its daemon) to register it.${c4.reset}`);
9596
10181
  return;
9597
10182
  }
9598
10183
  machines.sort((a, b) => (b.updated_at ?? 0) - (a.updated_at ?? 0));
9599
10184
  const picked = await tui.select(
9600
- `${c3.bold}Your machines${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 esc)${c3.reset}`,
10185
+ `${c4.bold}Your machines${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 esc)${c4.reset}`,
9601
10186
  machines.map((m) => {
9602
10187
  const isSelf = m.id === self.machine_id;
9603
10188
  const online = daemonOnline(m);
@@ -9631,26 +10216,26 @@ async function manageMachine(tui, api, self, machine) {
9631
10216
  options.push({ label: "Back", value: "back" });
9632
10217
  if (!isSelf && !machine.daemon) {
9633
10218
  tui.print(
9634
- `${c3.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c3.reset}`
10219
+ `${c4.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c4.reset}`
9635
10220
  );
9636
10221
  } else if (!isSelf && machine.daemon && !online) {
9637
10222
  tui.print(
9638
- `${c3.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c3.reset}`
10223
+ `${c4.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c4.reset}`
9639
10224
  );
9640
10225
  }
9641
- const action = await tui.select(`${c3.bold}${machineIcon(machine)} ${machine.name}${c3.reset}`, options);
10226
+ const action = await tui.select(`${c4.bold}${machineIcon(machine)} ${machine.name}${c4.reset}`, options);
9642
10227
  if (!action || action === "back") return;
9643
10228
  if (action === "icon") {
9644
10229
  const current = machine.icon ?? "";
9645
10230
  const emoji = await tui.prompt(
9646
- `${c3.bold}Icon for ${machine.name}${c3.reset} ${c3.dim}(paste an emoji, blank to reset)${c3.reset}`,
10231
+ `${c4.bold}Icon for ${machine.name}${c4.reset} ${c4.dim}(paste an emoji, blank to reset)${c4.reset}`,
9647
10232
  current
9648
10233
  );
9649
10234
  if (emoji !== null) {
9650
10235
  const trimmed = emoji.trim();
9651
10236
  await setMachineIcon(api, machine.id, trimmed);
9652
10237
  machine.icon = trimmed || void 0;
9653
- tui.print(`${c3.green}\u2713${c3.reset} icon ${trimmed ? `set to ${trimmed}` : "reset"} for ${machine.name}`);
10238
+ tui.print(`${c4.green}\u2713${c4.reset} icon ${trimmed ? `set to ${trimmed}` : "reset"} for ${machine.name}`);
9654
10239
  }
9655
10240
  return manageMachine(tui, api, self, machine);
9656
10241
  }
@@ -9666,7 +10251,7 @@ async function manageMachine(tui, api, self, machine) {
9666
10251
  const name = await tui.prompt(`New name for ${machine.name}`, machine.name);
9667
10252
  if (name === null || !name.trim()) return;
9668
10253
  await setMachineName(api, machine.id, name.trim());
9669
- tui.print(`${c3.green}\u2713${c3.reset} Renamed ${c3.bold}${machine.name}${c3.reset} \u2192 ${c3.bold}${name.trim()}${c3.reset}.`);
10254
+ tui.print(`${c4.green}\u2713${c4.reset} Renamed ${c4.bold}${machine.name}${c4.reset} \u2192 ${c4.bold}${name.trim()}${c4.reset}.`);
9670
10255
  } else if (action === "update") {
9671
10256
  if (isSelf) {
9672
10257
  await runUpdateCommand(tui);
@@ -9677,7 +10262,7 @@ async function manageMachine(tui, api, self, machine) {
9677
10262
  ]);
9678
10263
  if (go !== "yes") return;
9679
10264
  await dispatch("update");
9680
- tui.print(`${c3.green}\u2713${c3.reset} Update ${c3.gray}${applyNote} (its daemon updates and restarts on the new version).${c3.reset}`);
10265
+ tui.print(`${c4.green}\u2713${c4.reset} Update ${c4.gray}${applyNote} (its daemon updates and restarts on the new version).${c4.reset}`);
9681
10266
  }
9682
10267
  } else if (action === "projects") {
9683
10268
  await manageMachineProjects(tui, api, self, machine, dispatch, applyNote);
@@ -9686,78 +10271,103 @@ async function manageMachine(tui, api, self, machine) {
9686
10271
  async function manageMachineProjects(tui, api, self, machine, dispatch, applyNote) {
9687
10272
  const ADD = "__add__";
9688
10273
  const paths = Object.keys(machine.projects).sort();
10274
+ const projLabels = projectDisplayLabels(machine.projects);
9689
10275
  const picked = await tui.select(
9690
- `${c3.bold}Projects on ${machine.name}${c3.reset} ${c3.dim}(enter to remove \xB7 esc)${c3.reset}`,
10276
+ `${c4.bold}Projects on ${machine.name}${c4.reset} ${c4.dim}(\u2191\u2193 \xB7 enter \xB7 esc)${c4.reset}`,
9691
10277
  [
9692
- ...paths.map((p) => ({ label: p, hint: "enter to remove", value: p })),
10278
+ ...paths.map((p) => ({
10279
+ label: projLabels.get(p) ?? projectDisplayName(p, machine.projects[p]),
10280
+ detail: p,
10281
+ value: p
10282
+ })),
9693
10283
  { label: "\uFF0B Add a project directory\u2026", hint: "absolute path", value: ADD }
9694
- ]
10284
+ ],
10285
+ { spaced: true }
9695
10286
  );
9696
10287
  if (!picked) return;
9697
10288
  if (picked === ADD) {
9698
- const path15 = await tui.prompt(
9699
- `Absolute project path on ${machine.name}`,
9700
- machine.id === self.machine_id ? process.cwd() : "/home/you/project"
10289
+ const isSelf = machine.id === self.machine_id;
10290
+ const backend = isSelf ? localBrowseBackend(machine.name) : remoteBrowseBackend(api, machine.id, machine.name);
10291
+ const chosen = await pickDirectory(tui, backend, {
10292
+ startPath: isSelf ? process.cwd() : null,
10293
+ loader: startLoader
10294
+ });
10295
+ if (!chosen) return;
10296
+ await dispatch("add_project", { path: chosen });
10297
+ tui.print(`${c4.green}\u2713${c4.reset} Add ${chosen} ${c4.gray}${applyNote}.${c4.reset}`);
10298
+ return;
10299
+ }
10300
+ const project = machine.projects[picked];
10301
+ const displayName = projectDisplayName(picked, project);
10302
+ const action = await tui.select(`${c4.bold}${displayName}${c4.reset} ${c4.dim}${shortenPath(picked, 48)}${c4.reset}`, [
10303
+ { label: "Rename", hint: "display name only \u2014 the directory is untouched", value: "rename" },
10304
+ { label: "Remove from this machine's projects", hint: "doesn't delete the directory", value: "remove" },
10305
+ { label: "Back", value: "back" }
10306
+ ]);
10307
+ if (!action || action === "back") return;
10308
+ if (action === "rename") {
10309
+ const name = await tui.prompt(
10310
+ `New name for ${displayName} (blank resets to the directory name)`,
10311
+ displayName
9701
10312
  );
9702
- if (!path15 || !path15.trim()) return;
9703
- const trimmed = path15.trim();
9704
- if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
9705
- tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
9706
- return;
9707
- }
9708
- await dispatch("add_project", { path: trimmed });
9709
- tui.print(`${c3.green}\u2713${c3.reset} Add ${trimmed} ${c3.gray}${applyNote}.${c3.reset}`);
10313
+ if (name === null) return;
10314
+ await setProjectName(api, machine.id, picked, name);
10315
+ const now = name.trim() || projectDisplayName(picked, null);
10316
+ if (project) project.name = now;
10317
+ tui.print(`${c4.green}\u2713${c4.reset} Renamed ${c4.bold}${displayName}${c4.reset} \u2192 ${c4.bold}${now}${c4.reset}.`);
9710
10318
  } else {
9711
10319
  await dispatch("remove_project", { path: picked });
9712
- tui.print(`${c3.green}\u2713${c3.reset} Remove ${picked} ${c3.gray}${applyNote}.${c3.reset}`);
10320
+ tui.print(`${c4.green}\u2713${c4.reset} Remove ${picked} ${c4.gray}${applyNote}.${c4.reset}`);
9713
10321
  }
9714
10322
  }
9715
10323
  function showDaemonInfo(tui, session) {
9716
10324
  if (session.mode === "remote" && session.runner) {
9717
10325
  tui.print(
9718
- `${c3.gray}This session runs on${c3.reset} ${c3.bold}${session.runner.name}${c3.reset} ${c3.gray}(${session.runner.hostname}) \u2014 its daemon executes the tools.${c3.reset}`
10326
+ `${c4.gray}This session runs on${c4.reset} ${c4.bold}${session.runner.name}${c4.reset} ${c4.gray}(${session.runner.hostname}) \u2014 its daemon executes the tools.${c4.reset}`
9719
10327
  );
9720
10328
  } else {
9721
- tui.print(`${c3.gray}This session runs on this machine.${c3.reset}`);
10329
+ tui.print(`${c4.gray}This session runs on this machine.${c4.reset}`);
9722
10330
  }
9723
10331
  const status = serviceStatus();
9724
10332
  tui.print(
9725
- `${c3.gray}Daemon on this machine:${c3.reset} ${status.installed ? status.detail : "not installed"}`
10333
+ `${c4.gray}Daemon on this machine:${c4.reset} ${status.installed ? status.detail : "not installed"}`
9726
10334
  );
9727
10335
  if (!status.installed) {
9728
10336
  tui.print(
9729
- `${c3.gray}Install it to start sessions on this machine from anywhere:${c3.reset} ${c3.bold}standardcode daemon install${c3.reset}`
10337
+ `${c4.gray}Install it to start sessions on this machine from anywhere:${c4.reset} ${c4.bold}standardcode daemon install${c4.reset}`
9730
10338
  );
9731
10339
  tui.print(
9732
- `${c3.dim}The daemon keeps running after you close the terminal \u2014 it self-restarts, self-updates, and executes sessions you start from other machines.${c3.reset}`
10340
+ `${c4.dim}The daemon keeps running after you close the terminal \u2014 it self-restarts, self-updates, and executes sessions you start from other machines.${c4.reset}`
9733
10341
  );
9734
10342
  } else {
9735
- tui.print(`${c3.dim}Manage it with standardcode daemon status | uninstall | add-project <path>.${c3.reset}`);
10343
+ tui.print(`${c4.dim}Manage it with standardcode daemon status | uninstall | add-project <path>.${c4.reset}`);
9736
10344
  }
9737
10345
  }
9738
10346
  function showKeybindings(tui) {
9739
- tui.print(`${c3.gray}shortcuts:${c3.reset}`);
9740
- tui.print(`${c3.gray} shift-tab${c3.reset} cycle auto-accept level (1\u20135)`);
9741
- tui.print(`${c3.gray} /${c3.reset} open the command palette (type to filter)`);
9742
- tui.print(`${c3.gray} !cmd${c3.reset} run a shell command on the session's machine (e.g. !ls); !! to send a literal !`);
9743
- tui.print(`${c3.gray} ctrl-v${c3.reset} paste an image from the clipboard ([#Image 1])`);
9744
- tui.print(`${c3.gray} \u2191 / \u2193${c3.reset} cycle past messages (on the input's top line)`);
9745
- tui.print(`${c3.gray} \u2190${c3.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
9746
- tui.print(`${c3.gray} ctrl-c${c3.reset} quit`);
10347
+ tui.print(`${c4.gray}shortcuts:${c4.reset}`);
10348
+ tui.print(`${c4.gray} shift-tab${c4.reset} cycle auto-accept level (1\u20135)`);
10349
+ tui.print(`${c4.gray} shift-\u23CE${c4.reset} insert a newline (multiline input)`);
10350
+ tui.print(`${c4.gray} option-\u23CE${c4.reset} steer now \u2014 stops the current step and plays your message`);
10351
+ tui.print(`${c4.gray} /${c4.reset} open the command palette (type to filter)`);
10352
+ tui.print(`${c4.gray} !cmd${c4.reset} run a shell command on the session's machine (e.g. !ls); !! to send a literal !`);
10353
+ tui.print(`${c4.gray} ctrl-v${c4.reset} paste an image from the clipboard ([#Image 1])`);
10354
+ tui.print(`${c4.gray} \u2191 / \u2193${c4.reset} cycle past messages (on the input's top line)`);
10355
+ tui.print(`${c4.gray} \u2190${c4.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
10356
+ tui.print(`${c4.gray} ctrl-c${c4.reset} quit`);
9747
10357
  }
9748
10358
  async function runUpdateCommand(tui) {
9749
10359
  const version = readVersion();
9750
10360
  const result = await forceCheckForUpdate(version);
9751
10361
  if (!result) {
9752
- tui.print(`${c3.green}\u2713${c3.reset} ${c3.gray}@standardagents/code${c3.reset} is up to date (v${version})`);
10362
+ tui.print(`${c4.green}\u2713${c4.reset} ${c4.gray}@standardagents/code${c4.reset} is up to date (v${version})`);
9753
10363
  return;
9754
10364
  }
9755
10365
  const { latest } = result;
9756
10366
  tui.print(`
9757
- ${c3.yellow}\u27F3${c3.reset} Update available: ${c3.gray}v${version}${c3.reset} \u2192 ${c3.green}v${latest}${c3.reset}`);
10367
+ ${c4.yellow}\u27F3${c4.reset} Update available: ${c4.gray}v${version}${c4.reset} \u2192 ${c4.green}v${latest}${c4.reset}`);
9758
10368
  const pm = detectPackageManager();
9759
10369
  if (!pm) {
9760
- tui.print(` ${c3.gray}This is a source checkout \u2014 pull the repo to update.${c3.reset}`);
10370
+ tui.print(` ${c4.gray}This is a source checkout \u2014 pull the repo to update.${c4.reset}`);
9761
10371
  return;
9762
10372
  }
9763
10373
  const { display } = updateCommand(pm);
@@ -9766,28 +10376,28 @@ async function runUpdateCommand(tui) {
9766
10376
  { label: "No, skip", value: "no" }
9767
10377
  ]);
9768
10378
  if (choice === "yes") {
9769
- tui.print(` ${c3.gray}Running ${display}\u2026${c3.reset}`);
10379
+ tui.print(` ${c4.gray}Running ${display}\u2026${c4.reset}`);
9770
10380
  const { ok, output: pmOutput } = await runUpdate(pm);
9771
10381
  if (ok) {
9772
- tui.print(` ${c3.green}\u2713${c3.reset} Updated to v${latest}. Restart to use the new version.`);
10382
+ tui.print(` ${c4.green}\u2713${c4.reset} Updated to v${latest}. Restart to use the new version.`);
9773
10383
  } else {
9774
- tui.print(` ${c3.red}\u2717${c3.reset} Update failed:`);
10384
+ tui.print(` ${c4.red}\u2717${c4.reset} Update failed:`);
9775
10385
  for (const line of pmOutput.trim().split("\n").slice(-6)) {
9776
- tui.print(` ${c3.dim}${line}${c3.reset}`);
10386
+ tui.print(` ${c4.dim}${line}${c4.reset}`);
9777
10387
  }
9778
10388
  }
9779
10389
  } else {
9780
- tui.print(` ${c3.gray}Skipped. Run /update later.${c3.reset}`);
10390
+ tui.print(` ${c4.gray}Skipped. Run /update later.${c4.reset}`);
9781
10391
  }
9782
10392
  }
9783
10393
  async function runProcessMenu(tui, bg) {
9784
10394
  const procs = await bg.list();
9785
10395
  if (!procs.length) {
9786
- tui.print(`${c3.gray}No background processes for this session.${c3.reset}`);
10396
+ tui.print(`${c4.gray}No background processes for this session.${c4.reset}`);
9787
10397
  return;
9788
10398
  }
9789
10399
  const items = procs.map((p) => {
9790
- const status = p.status === "running" ? `${c3.green}running${c3.reset}` : `${c3.gray}${p.status}${typeof p.exitCode === "number" ? ` (exit ${p.exitCode})` : ""}${c3.reset}`;
10400
+ const status = p.status === "running" ? `${c4.green}running${c4.reset}` : `${c4.gray}${p.status}${typeof p.exitCode === "number" ? ` (exit ${p.exitCode})` : ""}${c4.reset}`;
9791
10401
  return {
9792
10402
  label: `${p.description || p.command}`,
9793
10403
  hint: `${p.id} \xB7 ${status}`,
@@ -9795,22 +10405,22 @@ async function runProcessMenu(tui, bg) {
9795
10405
  };
9796
10406
  });
9797
10407
  const picked = await tui.select(
9798
- `${c3.bold}Background processes${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter to manage \xB7 esc to close)${c3.reset}`,
10408
+ `${c4.bold}Background processes${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter to manage \xB7 esc to close)${c4.reset}`,
9799
10409
  items
9800
10410
  );
9801
10411
  if (!picked) return;
9802
10412
  const proc = procs.find((p) => p.id === picked);
9803
10413
  if (!proc || proc.status !== "running") {
9804
- tui.print(`${c3.gray}${picked} is not running.${c3.reset}`);
10414
+ tui.print(`${c4.gray}${picked} is not running.${c4.reset}`);
9805
10415
  return;
9806
10416
  }
9807
- const action = await tui.select(`${c3.bold}${proc.description || proc.command}${c3.reset}`, [
10417
+ const action = await tui.select(`${c4.bold}${proc.description || proc.command}${c4.reset}`, [
9808
10418
  { label: "Stop this process", value: "stop" },
9809
10419
  { label: "Leave it running", value: "leave" }
9810
10420
  ]);
9811
10421
  if (action === "stop") {
9812
10422
  await bg.stop(picked);
9813
- tui.print(`${c3.gray}stopped ${picked}${c3.reset}`);
10423
+ tui.print(`${c4.gray}stopped ${picked}${c4.reset}`);
9814
10424
  }
9815
10425
  }
9816
10426
  async function runApprovalsMenu(tui, perm, save) {
@@ -9818,7 +10428,7 @@ async function runApprovalsMenu(tui, perm, save) {
9818
10428
  const risks = Array.from(perm.allowRisk).sort((a, b) => a - b);
9819
10429
  if (!tools.length && !risks.length) {
9820
10430
  tui.print(
9821
- `${c3.gray}No pre-approved commands. At a permission prompt, choose "Always allow this tool" or "Allow all level N" to add some.${c3.reset}`
10431
+ `${c4.gray}No pre-approved commands. At a permission prompt, choose "Always allow this tool" or "Allow all level N" to add some.${c4.reset}`
9822
10432
  );
9823
10433
  return;
9824
10434
  }
@@ -9828,22 +10438,22 @@ async function runApprovalsMenu(tui, perm, save) {
9828
10438
  { label: "Clear all approvals", hint: "", value: "clear" }
9829
10439
  ];
9830
10440
  const picked = await tui.select(
9831
- `${c3.bold}Approved commands${c3.reset} ${c3.dim}(enter to revoke \xB7 esc to close)${c3.reset}`,
10441
+ `${c4.bold}Approved commands${c4.reset} ${c4.dim}(enter to revoke \xB7 esc to close)${c4.reset}`,
9832
10442
  items
9833
10443
  );
9834
10444
  if (!picked) return;
9835
10445
  if (picked === "clear") {
9836
10446
  perm.alwaysAllow.clear();
9837
10447
  perm.allowRisk.clear();
9838
- tui.print(`${c3.gray}cleared all approvals${c3.reset}`);
10448
+ tui.print(`${c4.gray}cleared all approvals${c4.reset}`);
9839
10449
  } else if (picked.startsWith("tool:")) {
9840
10450
  const t = picked.slice(5);
9841
10451
  perm.alwaysAllow.delete(t);
9842
- tui.print(`${c3.gray}revoked tool ${t}${c3.reset}`);
10452
+ tui.print(`${c4.gray}revoked tool ${t}${c4.reset}`);
9843
10453
  } else if (picked.startsWith("risk:")) {
9844
10454
  const r = Number(picked.slice(5));
9845
10455
  perm.allowRisk.delete(r);
9846
- tui.print(`${c3.gray}revoked level ${r}${c3.reset}`);
10456
+ tui.print(`${c4.gray}revoked level ${r}${c4.reset}`);
9847
10457
  }
9848
10458
  save();
9849
10459
  }
@@ -9861,7 +10471,7 @@ async function runMcpMenu(tui, mcp) {
9861
10471
  items.push({ label: "\uFF0B Install a new MCP server\u2026", hint: "find & install", value: INSTALL });
9862
10472
  items.push({ label: "Add manually (name: command)\u2026", hint: "advanced", value: ADD_MANUAL });
9863
10473
  const picked = await tui.select(
9864
- `${c3.bold}MCP servers${c3.reset} ${c3.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c3.reset}`,
10474
+ `${c4.bold}MCP servers${c4.reset} ${c4.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c4.reset}`,
9865
10475
  items
9866
10476
  );
9867
10477
  if (!picked) return;
@@ -9875,7 +10485,7 @@ async function runMcpMenu(tui, mcp) {
9875
10485
  }
9876
10486
  const server = configured.find((s) => s.name === picked);
9877
10487
  const isConnected = connected.has(picked);
9878
- const action = await tui.select(`${c3.bold}${picked}${c3.reset}`, [
10488
+ const action = await tui.select(`${c4.bold}${picked}${c4.reset}`, [
9879
10489
  { label: "View tools", value: "tools" },
9880
10490
  isConnected ? { label: "Disconnect", value: "disconnect" } : { label: "Connect", value: "connect" },
9881
10491
  server.enabled ? { label: "Disable (don't auto-connect)", value: "disable" } : { label: "Enable (auto-connect on start)", value: "enable" },
@@ -9885,29 +10495,29 @@ async function runMcpMenu(tui, mcp) {
9885
10495
  if (action === "tools") {
9886
10496
  const entry = mcp.catalog().servers.find((e) => e.name === picked);
9887
10497
  if (!entry || entry.status !== "connected") {
9888
- tui.print(`${c3.gray}${picked} is not connected \u2014 connect it to list tools.${c3.reset}`);
10498
+ tui.print(`${c4.gray}${picked} is not connected \u2014 connect it to list tools.${c4.reset}`);
9889
10499
  return;
9890
10500
  }
9891
- if (!entry.tools.length) tui.print(`${c3.gray}${picked} exposes no tools.${c3.reset}`);
9892
- for (const t of entry.tools) tui.print(` ${c3.cyan}${t.name}${c3.reset}${t.description ? ` ${c3.gray}\u2014 ${t.description}${c3.reset}` : ""}`);
9893
- if (entry.resources.length) tui.print(` ${c3.gray}${entry.resources.length} resource(s)${c3.reset}`);
10501
+ if (!entry.tools.length) tui.print(`${c4.gray}${picked} exposes no tools.${c4.reset}`);
10502
+ for (const t of entry.tools) tui.print(` ${c4.cyan}${t.name}${c4.reset}${t.description ? ` ${c4.gray}\u2014 ${t.description}${c4.reset}` : ""}`);
10503
+ if (entry.resources.length) tui.print(` ${c4.gray}${entry.resources.length} resource(s)${c4.reset}`);
9894
10504
  } else if (action === "connect") {
9895
10505
  const res = await mcp.connect(server);
9896
- tui.print(res.ok ? `${c3.cyan}\u26A1 connected (${res.tools} tools)${c3.reset}` : `${c3.red}\u26A0 ${res.error}${c3.reset}`);
10506
+ tui.print(res.ok ? `${c4.cyan}\u26A1 connected (${res.tools} tools)${c4.reset}` : `${c4.red}\u26A0 ${res.error}${c4.reset}`);
9897
10507
  } else if (action === "disconnect") {
9898
10508
  mcp.disconnect(picked);
9899
- tui.print(`${c3.gray}disconnected ${picked}${c3.reset}`);
10509
+ tui.print(`${c4.gray}disconnected ${picked}${c4.reset}`);
9900
10510
  } else if (action === "enable") {
9901
10511
  mcp.setEnabled(picked, true);
9902
10512
  const res = await mcp.connect(server);
9903
- tui.print(res.ok ? `${c3.cyan}\u26A1 enabled + connected (${res.tools} tools)${c3.reset}` : `${c3.red}\u26A0 enabled but failed: ${res.error}${c3.reset}`);
10513
+ tui.print(res.ok ? `${c4.cyan}\u26A1 enabled + connected (${res.tools} tools)${c4.reset}` : `${c4.red}\u26A0 enabled but failed: ${res.error}${c4.reset}`);
9904
10514
  } else if (action === "disable") {
9905
10515
  mcp.setEnabled(picked, false);
9906
10516
  mcp.disconnect(picked);
9907
- tui.print(`${c3.gray}disabled + disconnected ${picked}${c3.reset}`);
10517
+ tui.print(`${c4.gray}disabled + disconnected ${picked}${c4.reset}`);
9908
10518
  } else if (action === "remove") {
9909
10519
  mcp.remove(picked);
9910
- tui.print(`${c3.gray}removed ${picked}${c3.reset}`);
10520
+ tui.print(`${c4.gray}removed ${picked}${c4.reset}`);
9911
10521
  }
9912
10522
  }
9913
10523
  async function addMcpServer(tui, mcp) {
@@ -9918,13 +10528,13 @@ async function addMcpServer(tui, mcp) {
9918
10528
  if (!spec) return;
9919
10529
  const cfg = parseServerSpec(spec);
9920
10530
  if (!cfg) {
9921
- tui.print(`${c3.yellow}couldn't parse that. Use name: command [args]${c3.reset}`);
10531
+ tui.print(`${c4.yellow}couldn't parse that. Use name: command [args]${c4.reset}`);
9922
10532
  return;
9923
10533
  }
9924
- tui.print(`${c3.gray}connecting MCP "${cfg.name}" (${cfg.command} ${cfg.args.join(" ")})\u2026${c3.reset}`);
10534
+ tui.print(`${c4.gray}connecting MCP "${cfg.name}" (${cfg.command} ${cfg.args.join(" ")})\u2026${c4.reset}`);
9925
10535
  const res = await mcp.add(cfg);
9926
- if (res.ok) tui.print(`${c3.cyan}\u26A1 MCP "${cfg.name}" connected${c3.reset} ${c3.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c3.reset}`);
9927
- else tui.print(`${c3.red}\u26A0 MCP "${cfg.name}" failed:${c3.reset} ${c3.gray}${res.error}${c3.reset} ${c3.dim}(saved; retry from the MCP menu)${c3.reset}`);
10536
+ if (res.ok) tui.print(`${c4.cyan}\u26A1 MCP "${cfg.name}" connected${c4.reset} ${c4.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c4.reset}`);
10537
+ else tui.print(`${c4.red}\u26A0 MCP "${cfg.name}" failed:${c4.reset} ${c4.gray}${res.error}${c4.reset} ${c4.dim}(saved; retry from the MCP menu)${c4.reset}`);
9928
10538
  }
9929
10539
  async function installMcpServerFlow(tui, mcp) {
9930
10540
  const query = await tui.prompt(