@crouton-kit/crouter 0.3.68 → 0.3.78

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.
Files changed (176) hide show
  1. package/README.md +1 -1
  2. package/dist/builtin-memory/00-runtime-base.md +3 -10
  3. package/dist/builtin-memory/04-base-worker.md +18 -0
  4. package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +639 -741
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +241 -58
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +365 -94
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
  11. package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
  12. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
  13. package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
  14. package/dist/builtin-views/canvas/tui.mjs +8 -9
  15. package/dist/builtin-views/chat/tui.mjs +12 -12
  16. package/dist/builtin-views/git-pr/tui.mjs +7 -8
  17. package/dist/builtin-views/inbox/tui.mjs +27 -41
  18. package/dist/builtin-views/linkedin/tui.mjs +23 -37
  19. package/dist/builtin-views/prompt-review/tui.mjs +11 -11
  20. package/dist/builtin-views/settings/tui.mjs +11 -11
  21. package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
  22. package/dist/cli.d.ts +1 -1
  23. package/dist/cli.js +1 -0
  24. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -7
  25. package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
  26. package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
  27. package/dist/clients/attach/__tests__/mermaid-render.test.js +20 -0
  28. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
  29. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
  30. package/dist/clients/attach/attach-cmd.d.ts +1 -1
  31. package/dist/clients/attach/attach-cmd.js +733 -730
  32. package/dist/clients/attach/auth-pickers.d.ts +12 -0
  33. package/dist/clients/attach/auth-pickers.js +11 -0
  34. package/dist/clients/attach/canvas-panels.js +2 -3
  35. package/dist/clients/attach/chat-view.d.ts +7 -8
  36. package/dist/clients/attach/chat-view.js +119 -79
  37. package/dist/clients/attach/edit-diff-render.d.ts +6 -0
  38. package/dist/clients/attach/edit-diff-render.js +263 -0
  39. package/dist/clients/attach/input-controller.d.ts +28 -2
  40. package/dist/clients/attach/input-controller.js +38 -5
  41. package/dist/clients/attach/mermaid-render.js +5 -2
  42. package/dist/clients/attach/pickers.d.ts +8 -7
  43. package/dist/clients/attach/pickers.js +13 -17
  44. package/dist/clients/attach/slash-commands.d.ts +9 -0
  45. package/dist/clients/attach/slash-commands.js +128 -7
  46. package/dist/clients/attach/titled-editor.d.ts +12 -1
  47. package/dist/clients/attach/titled-editor.js +103 -8
  48. package/dist/commands/canvas-browse.js +2 -2
  49. package/dist/commands/memory/lint.js +39 -5
  50. package/dist/commands/memory/write.js +1 -0
  51. package/dist/commands/node.js +12 -5
  52. package/dist/commands/profile/new.js +30 -5
  53. package/dist/commands/surface-tmux-spread.js +1 -3
  54. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
  55. package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
  56. package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
  57. package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
  58. package/dist/commands/sys/config.js +18 -21
  59. package/dist/commands/sys/setup-core.d.ts +12 -1
  60. package/dist/commands/sys/setup-core.js +25 -3
  61. package/dist/commands/sys/setup.js +166 -20
  62. package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
  63. package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
  64. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
  65. package/dist/core/__tests__/fault-classifier.test.js +15 -0
  66. package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
  67. package/dist/core/__tests__/full/broker-pane-resolution.test.js +9 -6
  68. package/dist/core/__tests__/full/cascade-close.test.js +5 -2
  69. package/dist/core/__tests__/full/dead-pane-regression.test.js +6 -3
  70. package/dist/core/__tests__/full/detach-focus.test.js +10 -5
  71. package/dist/core/__tests__/full/human-new-window-regression.test.js +6 -3
  72. package/dist/core/__tests__/full/review-render-pane-regression.test.js +2 -2
  73. package/dist/core/__tests__/helpers/harness.d.ts +1 -0
  74. package/dist/core/__tests__/helpers/harness.js +28 -4
  75. package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
  76. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -4
  77. package/dist/core/__tests__/review-model-floor.test.js +1 -1
  78. package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
  79. package/dist/core/__tests__/stream-watchdog.test.js +70 -0
  80. package/dist/core/__tests__/tmux-surface.test.js +72 -0
  81. package/dist/core/canvas/__tests__/remote-event-stream.test.js +11 -9
  82. package/dist/core/canvas/__tests__/render-remote.test.js +7 -4
  83. package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
  84. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +30 -13
  85. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  86. package/dist/core/canvas/browse/app.js +164 -5
  87. package/dist/core/canvas/browse/model.d.ts +10 -6
  88. package/dist/core/canvas/browse/model.js +20 -12
  89. package/dist/core/canvas/browse/pins.d.ts +4 -0
  90. package/dist/core/canvas/browse/pins.js +29 -0
  91. package/dist/core/canvas/browse/render.d.ts +13 -1
  92. package/dist/core/canvas/browse/render.js +72 -15
  93. package/dist/core/canvas/nav-model.js +5 -11
  94. package/dist/core/canvas/paths.d.ts +5 -0
  95. package/dist/core/canvas/paths.js +7 -0
  96. package/dist/core/canvas/pid.d.ts +14 -10
  97. package/dist/core/canvas/pid.js +14 -10
  98. package/dist/core/config.d.ts +53 -2
  99. package/dist/core/config.js +228 -110
  100. package/dist/core/fault-classifier.js +1 -1
  101. package/dist/core/fs-utils.d.ts +1 -0
  102. package/dist/core/fs-utils.js +15 -1
  103. package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
  104. package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
  105. package/dist/core/keybindings/catalog.d.ts +14 -0
  106. package/dist/core/keybindings/catalog.js +256 -0
  107. package/dist/core/keybindings/index.d.ts +5 -0
  108. package/dist/core/keybindings/index.js +4 -0
  109. package/dist/core/keybindings/match.d.ts +29 -0
  110. package/dist/core/keybindings/match.js +99 -0
  111. package/dist/core/keybindings/persistence.d.ts +14 -0
  112. package/dist/core/keybindings/persistence.js +31 -0
  113. package/dist/core/keybindings/resolve.d.ts +14 -0
  114. package/dist/core/keybindings/resolve.js +305 -0
  115. package/dist/core/keybindings/types.d.ts +37 -0
  116. package/dist/core/keybindings/types.js +1 -0
  117. package/dist/core/memory-resolver.d.ts +1 -1
  118. package/dist/core/memory-resolver.js +10 -3
  119. package/dist/core/predicate.d.ts +5 -3
  120. package/dist/core/predicate.js +5 -3
  121. package/dist/core/runtime/broker-protocol.d.ts +33 -5
  122. package/dist/core/runtime/broker.js +165 -14
  123. package/dist/core/runtime/launch.d.ts +39 -6
  124. package/dist/core/runtime/launch.js +79 -21
  125. package/dist/core/runtime/naming.js +3 -3
  126. package/dist/core/runtime/pi-cli.d.ts +6 -0
  127. package/dist/core/runtime/pi-cli.js +16 -2
  128. package/dist/core/runtime/placement.d.ts +2 -2
  129. package/dist/core/runtime/placement.js +4 -1
  130. package/dist/core/runtime/promote.js +4 -0
  131. package/dist/core/runtime/recap.d.ts +3 -3
  132. package/dist/core/runtime/recap.js +47 -44
  133. package/dist/core/runtime/recycle.js +6 -1
  134. package/dist/core/runtime/reset.js +5 -0
  135. package/dist/core/runtime/revive.js +28 -18
  136. package/dist/core/runtime/session-list-cache.d.ts +23 -0
  137. package/dist/core/runtime/session-list-cache.js +270 -0
  138. package/dist/core/runtime/spawn.js +51 -32
  139. package/dist/core/runtime/stream-watchdog.d.ts +26 -0
  140. package/dist/core/runtime/stream-watchdog.js +75 -0
  141. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  142. package/dist/core/runtime/tmux-chrome.js +2 -2
  143. package/dist/core/runtime/tmux.d.ts +18 -15
  144. package/dist/core/runtime/tmux.js +216 -110
  145. package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
  146. package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
  147. package/dist/core/tui/host.d.ts +30 -1
  148. package/dist/core/tui/host.js +148 -34
  149. package/dist/core/view/__tests__/transport-remote.test.js +9 -6
  150. package/dist/core/view/contract.d.ts +17 -3
  151. package/dist/daemon/crtrd-cli.d.ts +1 -1
  152. package/dist/daemon/crtrd-cli.js +1 -0
  153. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
  154. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
  155. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
  156. package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
  157. package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
  158. package/dist/pi-extensions/canvas-recap.d.ts +4 -0
  159. package/dist/pi-extensions/canvas-recap.js +38 -37
  160. package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
  161. package/dist/pi-extensions/canvas-stophook.js +33 -4
  162. package/dist/suppress-experimental-warnings.d.ts +1 -0
  163. package/dist/suppress-experimental-warnings.js +15 -0
  164. package/dist/types.d.ts +3 -13
  165. package/dist/types.js +12 -36
  166. package/dist/web-client/assets/{index-CnF5r8ky.js → index--md2ylfi.js} +18 -17
  167. package/dist/web-client/assets/index-BUdm9s9s.css +2 -0
  168. package/dist/web-client/index.html +3 -3
  169. package/dist/web-client/sw.js +1 -1
  170. package/package.json +11 -8
  171. package/scripts/postinstall.mjs +1 -1
  172. package/dist/pi-extensions/canvas-nav.d.ts +0 -43
  173. package/dist/pi-extensions/canvas-nav.js +0 -640
  174. package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
  175. package/dist/pi-extensions/widget-order-bus.js +0 -34
  176. package/dist/web-client/assets/index-BnmSLNLa.css +0 -2
@@ -26,9 +26,11 @@ export const PROMPT_LINES = 3;
26
26
  export const REPLY_LINES = 3;
27
27
  export const PREVIEW_BODY = PROMPT_LINES + REPLY_LINES;
28
28
  export const PREVIEW_HEIGHT = PREVIEW_BODY + 2;
29
- // header = title + tab bar + status line + column header + separator (+ search input)
30
- export function headerHeight(search) {
31
- return 5 + (search ? 1 : 0);
29
+ // header = title + tab bar + status line + column header + separator (+ input line)
30
+ // `inputLine` is true whenever a text-input mode is open (search / rename / message)
31
+ // each adds one prompt row above the column header.
32
+ export function headerHeight(inputLine) {
33
+ return 5 + (inputLine ? 1 : 0);
32
34
  }
33
35
  // ── Row table layout ──────────────────────────────────────────────────────────
34
36
  // A row is three zones: a fixed-width STATUS rail (glyph + word) on the left, the
@@ -57,6 +59,7 @@ const FG_CYAN = '36';
57
59
  const FG_GRAY = '90'; // bright-black
58
60
  const FG_BRIGHT_YELLOW = '93';
59
61
  const FG_BRIGHT_CYAN = '96'; // query-match highlight (ties to the cyan search accent)
62
+ const FG_MAGENTA = '35'; // pin star (distinct from the yellow ⚑ asks flag)
60
63
  // STATUS_GLYPH + STATUS_COLOR are imported from ../status-glyph.js (the shared
61
64
  // canvas-graph map). STATUS_COLOR there is NUMERIC SGR (e.g. 32) — wrap with
62
65
  // `sgr()` where a string ANSI code is needed (Span fg / template literals accept
@@ -232,7 +235,7 @@ function assembleRow(left, right, width, color, lineBase, fill) {
232
235
  }
233
236
  /** One table row: `<status rail> <indent><collapse> <name> <flags>` ........ `<meta>`.
234
237
  * `showCwd` adds the project column (All-dirs view); `now` drives the age. */
235
- function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
238
+ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now, isPinned) {
236
239
  const node = tree.nodes.get(row.id);
237
240
  if (node === undefined)
238
241
  return '';
@@ -254,12 +257,15 @@ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
254
257
  // label live in the rail). Dim yellow so it reads as secondary detail.
255
258
  if (r.hanging != null)
256
259
  flags.push({ text: ` · ${faultSummary(r.hanging, now)}`, style: { fg: FG_YELLOW, dim: true } });
260
+ // Pin marker travels just before the name so a starred node is scannable in any
261
+ // tab/sort. Magenta keeps it distinct from the yellow ⚑ asks flag.
262
+ const pinMark = isPinned ? [{ text: '★ ', style: { fg: FG_MAGENTA, bold: true } }] : [];
257
263
  const status = statusRail(r);
258
264
  const right = metaCluster(r, now, showCwd);
259
265
  const treeLead = `${indent}${collapse} `;
260
- // Clip the NAME (only) so gutter + status + tree + name + flags + meta all fit;
261
- // the cluster is dropped first (assembleRow) when even a 4-col name won't fit it.
262
- const fixed = spansWidth([gutter]) + spansWidth(status) + [...treeLead].length + spansWidth(flags);
266
+ // Clip the NAME (only) so gutter + status + tree + pin + name + flags + meta all
267
+ // fit; the cluster is dropped first (assembleRow) when even a 4-col name won't fit.
268
+ const fixed = spansWidth([gutter]) + spansWidth(status) + [...treeLead].length + spansWidth(pinMark) + spansWidth(flags);
263
269
  const rightW = spansWidth(right);
264
270
  const nameMax = Math.max(4, width - fixed - rightW - 1);
265
271
  const nameStyle = { dim: !isCursor && terminal, bold: isCursor }; // dim terminal names; bold the cursor row
@@ -267,6 +273,7 @@ function rowLine(row, tree, width, isCursor, query, caps, showCwd, now) {
267
273
  gutter,
268
274
  ...status,
269
275
  { text: treeLead, style: { dim: true } },
276
+ ...pinMark,
270
277
  ...nameSpans(clip(r.name, nameMax), query, nameStyle),
271
278
  ...flags,
272
279
  ];
@@ -403,16 +410,63 @@ function previewPanel(r, width, caps, now, query) {
403
410
  }
404
411
  return out;
405
412
  }
413
+ /** The keybinding help overlay — a full-screen cheatsheet that replaces the body
414
+ * while `?` is held open. Grouped by intent so the whole key surface is legible in
415
+ * one glance (the footer only ever advertises the common few). */
416
+ const HELP_ROWS = [
417
+ ['Move', '↑/↓ j/k', 'up / down'],
418
+ ['', 'g / G', 'top / bottom'],
419
+ ['', '.', 'jump to this node (recenter)'],
420
+ ['Tree', '→/← h/l', 'expand / collapse'],
421
+ ['', 'space', 'toggle fold'],
422
+ ['Tabs', 'Tab / 1-5', 'All · Live · Dormant · Attention · Pinned'],
423
+ ['Find', '/', 'fuzzy search (name · kind · id · prompts)'],
424
+ ['Pin', 'f', 'star / unstar — floats to top, Pinned tab'],
425
+ ['Open', '⏎', 'resume the node'],
426
+ ['Act', 'm', 'message the node (no resume)'],
427
+ ['', 'R', 'rename the node'],
428
+ ['', 'x', 'close-out node + subtree'],
429
+ ['', 'K', 'revive now (a hanging node)'],
430
+ ['View', 's', 'cycle sort (attention/tree/relevance/recency)'],
431
+ ['', 'c', 'toggle cwd scope'],
432
+ ['', 'r', 'toggle residents-only'],
433
+ ['', 'p', 'toggle preview panel'],
434
+ ['Quit', 'q / Esc', ''],
435
+ ];
436
+ function renderHelp(size, caps) {
437
+ const cols = Math.max(20, size.cols);
438
+ const rows = Math.max(8, size.rows);
439
+ const width = cols - 1;
440
+ const lines = [];
441
+ lines.push(`${BOLD}Canvas navigator — keys${RESET}`);
442
+ lines.push('');
443
+ const dim = (s) => `${DIM}${s}${RESET}`;
444
+ for (const [group, keys, desc] of HELP_ROWS) {
445
+ const g = group.padEnd(6);
446
+ const k = keys.padEnd(12);
447
+ const line = ` ${dim(g)}${BOLD}${k}${RESET}${desc === '' ? '' : ` ${dim(desc)}`}`;
448
+ lines.push(clip(line, width));
449
+ }
450
+ lines.push('');
451
+ lines.push(dim(' ? or any key to close'));
452
+ while (lines.length < rows - 1)
453
+ lines.push('');
454
+ const body = lines.map((l) => `${l}${ESC}K`).join('\r\n');
455
+ return `${ESC}H${body}${ESC}J`;
456
+ }
406
457
  /**
407
458
  * Render the whole frame. Returns a single string that, written as-is, repaints
408
459
  * the screen in place. `caps` gates all hue (defaults to a no-color frame so
409
460
  * existing callers / non-TTY paths stay color-free).
410
461
  */
411
462
  export function renderFrame(state, size, caps = { color: false, color256: false }) {
463
+ if (state.help === true)
464
+ return renderHelp(size, caps);
412
465
  const cols = Math.max(20, size.cols);
413
466
  const rows = Math.max(8, size.rows);
414
467
  const width = cols - 1; // leave the last column for \x1b[K
415
468
  const now = Date.now();
469
+ const inputMode = state.search || state.rename === true || state.message === true;
416
470
  const lines = [];
417
471
  // line 1 — title + right-aligned status summary (each glyph in its status hue).
418
472
  const title = `${BOLD}Canvas${RESET} — ${state.totalNodes} nodes`;
@@ -428,10 +482,12 @@ export function renderFrame(state, size, caps = { color: false, color256: false
428
482
  lines.push(tabBar(state.tab, caps.color));
429
483
  // line 3 — status line (scope · sort · filter).
430
484
  lines.push(statusLine(state));
431
- // line 4 — search input, only while actively typing a search.
432
- if (state.search) {
433
- const slash = caps.color ? `${ESC}${FG_CYAN}m/${RESET}` : '/';
434
- lines.push(`${slash} ${state.query}▎`);
485
+ // line 4 — the text-input prompt, only while a search / rename / message is open.
486
+ if (inputMode) {
487
+ const prefix = state.rename === true ? 'rename ▸' : state.message === true ? 'msg ▸' : '/';
488
+ const buf = state.rename === true ? (state.renameBuf ?? '') : state.message === true ? (state.messageBuf ?? '') : state.query;
489
+ const p = caps.color ? `${ESC}${FG_CYAN}m${prefix}${RESET}` : prefix;
490
+ lines.push(`${p} ${buf}▎`);
435
491
  }
436
492
  // column header (table labels) + separator.
437
493
  const showCwd = state.cwdScope === null; // project column only matters across dirs
@@ -451,7 +507,8 @@ export function renderFrame(state, size, caps = { color: false, color256: false
451
507
  }
452
508
  else {
453
509
  for (let i = start; i < end; i++) {
454
- lines.push(rowLine(state.visible[i], state.tree, width, i === state.cursor, state.query, caps, showCwd, now));
510
+ const vr = state.visible[i];
511
+ lines.push(rowLine(vr, state.tree, width, i === state.cursor, state.query, caps, showCwd, now, state.pinned.has(vr.id)));
455
512
  }
456
513
  for (let i = end - start; i < viewport; i++)
457
514
  lines.push('');
@@ -477,11 +534,11 @@ export function renderFrame(state, size, caps = { color: false, color256: false
477
534
  const selRow = state.visible[state.cursor];
478
535
  const cursorRow = selRow !== undefined ? state.tree.nodes.get(selRow.id)?.row : undefined;
479
536
  const hangingHint = cursorRow?.hanging != null ? 'K revive now ' : '';
480
- const footer = state.search
537
+ const footer = inputMode
481
538
  ? '⏎ commit Esc cancel ⌫ delete'
482
539
  : state.readOnly === true
483
- ? '↑↓ move →/← tree Tab tabs / search s sort c cwd r residents p preview q quit — read-only remote canvas'
484
- : `${hangingHint}↑↓ move →/← tree ⏎ resume x close Tab tabs / search s sort c cwd r residents p preview q quit`;
540
+ ? '↑↓ move →/← tree / search f pin Tab tabs ? more q quit — read-only remote canvas'
541
+ : `${hangingHint}↑↓ move ⏎ resume / search f pin m msg x close R rename ? more q quit`;
485
542
  lines.push(`${DIM}${clip(footer, width)}${RESET}`);
486
543
  }
487
544
  // Assemble: home, each line cleared to EOL, then clear below.
@@ -1,16 +1,10 @@
1
- // nav-model.ts — the pure model/render layer of the canvas graph-nav chrome.
1
+ // nav-model.ts — the pure model/render layer for attach's canvas chrome.
2
2
  //
3
- // Extracted from src/pi-extensions/canvas-nav.ts so a SINGLE source feeds both
4
- // the legacy pi extension (canvas-nav) and the headless `crtr surface attach` viewer.
5
3
  // Everything here is pi-API-free: it reads the canvas (sqlite + meta.json + node
6
- // telemetry) and produces ANSI-styled strings + a flattened graph model. The
7
- // extension-host wiring (widgets, key taps, the ask-poll timer, run/focus
8
- // shelling) stays in canvas-nav.ts and drives these functions.
9
- //
10
- // Mutable interaction state that the legacy extension held at module scope —
11
- // the per-node ask counts and the manual fold overrides — is threaded through
12
- // parameters here (see `asks` and `FoldState`) so this layer owns no singletons
13
- // and each consumer keeps its own state.
4
+ // telemetry) and produces ANSI-styled strings plus a flattened graph model.
5
+ // Per-view interaction state, including ask counts and manual fold overrides, is
6
+ // threaded through parameters (see `asks` and `FoldState`) so this layer owns no
7
+ // singletons and each consumer keeps its own state.
14
8
  import { execFileSync } from 'node:child_process';
15
9
  import { existsSync, readFileSync } from 'node:fs';
16
10
  import { join } from 'node:path';
@@ -23,6 +23,11 @@ export declare function canvasDbPath(): string;
23
23
  * local-disk hosts. Parent dir is created on use so both paths just work. */
24
24
  export declare function viewSocketPath(nodeId: string): string;
25
25
  export declare function nodesRoot(): string;
26
+ /** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
27
+ * absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
28
+ * and survives broker restarts so the first `/resume` open after a restart is
29
+ * also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
30
+ export declare function sessionListCachePath(): string;
26
31
  export declare function nodeDir(nodeId: string): string;
27
32
  export declare function contextDir(nodeId: string): string;
28
33
  /** Replace the literal env-var tokens `$CRTR_CONTEXT_DIR` / `$CRTR_NODE_ID` in
@@ -63,6 +63,13 @@ export function viewSocketPath(nodeId) {
63
63
  export function nodesRoot() {
64
64
  return join(crtrHome(), 'nodes');
65
65
  }
66
+ /** Global, cwd-agnostic broker cache of per-session picker metadata, keyed by
67
+ * absolute `.jsonl` path. Shared across every node's broker (paths are absolute)
68
+ * and survives broker restarts so the first `/resume` open after a restart is
69
+ * also fast. Machine-local performance state — safe to delete; rebuilt on demand. */
70
+ export function sessionListCachePath() {
71
+ return join(crtrHome(), 'session-list-cache.json');
72
+ }
66
73
  export function nodeDir(nodeId) {
67
74
  return join(nodesRoot(), nodeId);
68
75
  }
@@ -74,16 +74,20 @@ export declare function identitiesMatch(a: string, b: string): boolean;
74
74
  * `identitiesMatch` says so, else `'dead'` (positively reused). */
75
75
  export type RecordedPidLiveness = 'alive' | 'dead' | 'indeterminate';
76
76
  export declare function recordedPidLiveness(pid: number | null | undefined, expectedIdentity: string | null | undefined): RecordedPidLiveness;
77
- /** Boolean teardown-direction adapter over `recordedPidLiveness`, kept so
78
- * every pre-existing signalling caller stays behavior-identical: `dead` is
79
- * the ONLY thing that reads false. `indeterminate` (can't confirm either
80
- * way) reads true alongside `alive` never signal a maybe-ours pid on mere
81
- * uncertainty, matching this module's universal discipline (a POSITIVE
82
- * mismatch is the only thing that changes behavior, never an absence of
83
- * evidence). Callers that need to distinguish "confirmed alive" from
84
- * "can't confirm" (e.g. a double-launch guard, where treating uncertainty as
85
- * alive risks silently skipping a needed relaunch) must call
86
- * `recordedPidLiveness` directly instead of this adapter. */
77
+ /** Boolean confirmed-dead-direction adapter over `recordedPidLiveness`:
78
+ * `dead` is the ONLY thing that reads false. `indeterminate` (can't confirm
79
+ * either way) reads true alongside `alive` never act on a maybe-ours pid
80
+ * on mere uncertainty, matching this module's universal discipline (a
81
+ * POSITIVE verdict is the only thing that changes behavior, never an
82
+ * absence of evidence). This is the correct read for BOTH signalling
83
+ * callers (never signal on uncertainty) and the double-launch guard in
84
+ * `reviveNode` (never RELAUNCH on uncertainty the identity probe is a
85
+ * fork+exec that fails precisely under the OOM pressure where an extra
86
+ * broker is most destructive, while a truly dead pid always reads `dead`
87
+ * via the fork-free signal-0 path; see the guard's comment for the live
88
+ * incident this closed). Callers that need "confirmed alive" three-valued
89
+ * precision (e.g. the daemon's grace-clock bookkeeping) call
90
+ * `recordedPidLiveness` directly. */
87
91
  export declare function isRecordedPidAlive(pid: number | null | undefined, expectedIdentity: string | null | undefined): boolean;
88
92
  /** SIGTERM a process GROUP by pid (the negative-pid convention) — best-effort,
89
93
  * swallowing ESRCH (already gone). Shared by triggers.ts (cancel-time reap of
@@ -132,16 +132,20 @@ export function recordedPidLiveness(pid, expectedIdentity) {
132
132
  logPidLivenessDecision(pid, alive, expectedIdentity, 'value', matches ? 'identity matches expected → alive' : 'identity differs from expected → REUSED → dead', matches ? 'alive' : 'dead', actual);
133
133
  return matches ? 'alive' : 'dead';
134
134
  }
135
- /** Boolean teardown-direction adapter over `recordedPidLiveness`, kept so
136
- * every pre-existing signalling caller stays behavior-identical: `dead` is
137
- * the ONLY thing that reads false. `indeterminate` (can't confirm either
138
- * way) reads true alongside `alive` never signal a maybe-ours pid on mere
139
- * uncertainty, matching this module's universal discipline (a POSITIVE
140
- * mismatch is the only thing that changes behavior, never an absence of
141
- * evidence). Callers that need to distinguish "confirmed alive" from
142
- * "can't confirm" (e.g. a double-launch guard, where treating uncertainty as
143
- * alive risks silently skipping a needed relaunch) must call
144
- * `recordedPidLiveness` directly instead of this adapter. */
135
+ /** Boolean confirmed-dead-direction adapter over `recordedPidLiveness`:
136
+ * `dead` is the ONLY thing that reads false. `indeterminate` (can't confirm
137
+ * either way) reads true alongside `alive` never act on a maybe-ours pid
138
+ * on mere uncertainty, matching this module's universal discipline (a
139
+ * POSITIVE verdict is the only thing that changes behavior, never an
140
+ * absence of evidence). This is the correct read for BOTH signalling
141
+ * callers (never signal on uncertainty) and the double-launch guard in
142
+ * `reviveNode` (never RELAUNCH on uncertainty the identity probe is a
143
+ * fork+exec that fails precisely under the OOM pressure where an extra
144
+ * broker is most destructive, while a truly dead pid always reads `dead`
145
+ * via the fork-free signal-0 path; see the guard's comment for the live
146
+ * incident this closed). Callers that need "confirmed alive" three-valued
147
+ * precision (e.g. the daemon's grace-clock bookkeeping) call
148
+ * `recordedPidLiveness` directly. */
145
149
  export function isRecordedPidAlive(pid, expectedIdentity) {
146
150
  return recordedPidLiveness(pid, expectedIdentity) !== 'dead';
147
151
  }
@@ -1,11 +1,32 @@
1
- import type { Scope, ScopeConfig, ScopeState, KindConfig, ModelLaddersConfig } from '../types.js';
1
+ import type { Scope, ScopeConfig, ScopeState, CanvasNavConfig, KindConfig, ModelLaddersConfig } from '../types.js';
2
2
  export declare function configPath(scope: Scope): string | null;
3
3
  export declare function statePath(scope: Scope): string | null;
4
- export declare function readConfig(scope: Scope): ScopeConfig;
4
+ export type ConsumerCompatibleScopeConfig = ScopeConfig & {
5
+ readonly canvasNav: CanvasNavConfig;
6
+ };
7
+ export declare function readConfig(scope: Scope): ConsumerCompatibleScopeConfig;
5
8
  export declare function readState(scope: Scope): ScopeState;
9
+ type RawConfig = Record<string, unknown>;
10
+ export declare function updateRawConfigAtomically(scope: Scope, mutate: (latest: RawConfig) => RawConfig): RawConfig;
6
11
  export declare function writeConfig(scope: Scope, config: ScopeConfig): void;
12
+ /** Persist a RAW PARTIAL config to a scope's config.json exactly as given.
13
+ * Unlike `writeConfig`, this makes no promise that every field is present --
14
+ * callers pass the sparse partial that should live on disk, so an untouched
15
+ * key stays absent (and `readConfig`/`mergeConfig` re-derive it live from
16
+ * whichever build is running). This is the single central primitive every
17
+ * mutation path funnels through so no writer ever freezes a fully
18
+ * materialized `modelLadders` (concrete model ids that go stale across
19
+ * builds -- the live-Hearth freeze bug) onto disk. */
20
+ export declare function writeRawConfig(scope: Scope, config: Partial<ScopeConfig>): void;
7
21
  export declare function writeState(scope: Scope, state: ScopeState): void;
8
22
  export declare function ensureScopeInitialized(scope: Scope, root: string): void;
23
+ /** Raw (un-defaulted) partial config for one scope, or null if the scope has
24
+ * no root or no config.json. Used by `readMergedLaunchConfig` to layer
25
+ * scopes onto each other WITHOUT each scope's own default-fill masking a
26
+ * lower-precedence scope's real customization (see that function's comment
27
+ * for why `readConfig(scope)`, which already defaults-fills, is unusable
28
+ * for cross-scope layering). */
29
+ export declare function readRawScopeConfig(scope: Scope): Partial<ScopeConfig> | null;
9
30
  export interface MergedLaunchConfig {
10
31
  kinds: Record<string, KindConfig>;
11
32
  modelLadders: ModelLaddersConfig;
@@ -65,5 +86,35 @@ export declare function subKindsAvailableTo(kind: string): {
65
86
  kind: string;
66
87
  whenToUse: string;
67
88
  }[];
89
+ /** Apply `mutate` to a scope's MERGED config (so callers can read + write any
90
+ * field), then persist only a RAW PARTIAL: every top-level key the mutation
91
+ * left untouched keeps its exact on-disk raw value (absent stays absent, a
92
+ * sparse override stays sparse), and only keys the mutation actually changed
93
+ * are written. `modelLadders` gets sub-field-granular sparse handling via
94
+ * `sparseModelLaddersToPersist`. Result: a fresh bootstrap or any unrelated
95
+ * `sys config set` no longer freezes a full concrete `modelLadders` to disk,
96
+ * and a direct ladder-rung set still persists only the touched rung -- all
97
+ * through one lock-protected atomic update path. */
68
98
  export declare function updateConfig(scope: Scope, mutate: (cfg: ScopeConfig) => void): ScopeConfig;
99
+ /** Persist a model as the DEFAULT for a kind — the `/model`-picker "defaults
100
+ * going forward" write. Writes a SPARSE `kinds.<kind>` patch (just the one
101
+ * model field, merged by `normalizeKindPatch`/`mergeKinds` over whatever the
102
+ * lower scopes define — never a frozen full kind entry) into the PROFILE's
103
+ * config.json when the node runs under a profile, else the user scope's.
104
+ * Mode-aware: an orchestrator's pick lands on `orchestratorModel` (base
105
+ * workers of the same kind keep their own default), a base node's on `model`.
106
+ * Callers pass a model string in any form `normalizeModel` accepts; portable
107
+ * ladder tokens (`ultra`, `anthropic/strong`) are preferred by callers since
108
+ * they never go stale across builds. */
109
+ export declare function persistDefaultKindModel(opts: {
110
+ kind: string;
111
+ model: string;
112
+ orchestrator?: boolean;
113
+ profileId?: string | null;
114
+ }): {
115
+ scope: 'profile' | 'user';
116
+ path: string;
117
+ field: 'model' | 'orchestratorModel';
118
+ };
69
119
  export declare function updateState(scope: Scope, mutate: (s: ScopeState) => void): ScopeState;
120
+ export {};