@north-light/crouter 0.3.176 → 0.3.177

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 (140) hide show
  1. package/dist/api/client.d.ts +4 -4
  2. package/dist/api/client.js +2 -2
  3. package/dist/api/dto/lifecycle.d.ts +1 -1
  4. package/dist/api/dto/reports.d.ts +1 -1
  5. package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
  6. package/dist/clients/attach/config.js +5 -5
  7. package/dist/clients/attach/input/clipboard-image.js +2 -2
  8. package/dist/clients/attach/input/ref-autocomplete.js +1 -1
  9. package/dist/clients/attach/input/titled-editor.d.ts +12 -13
  10. package/dist/clients/attach/input/titled-editor.js +14 -15
  11. package/dist/clients/attach/overlays/dialogs.d.ts +1 -1
  12. package/dist/clients/attach/overlays/dialogs.js +2 -2
  13. package/dist/clients/attach/overlays/mcp.js +5 -12
  14. package/dist/clients/attach/render/chat-view.d.ts +1 -1
  15. package/dist/clients/attach/render/chat-view.js +2 -2
  16. package/dist/clients/attach/render/group-recap.js +7 -0
  17. package/dist/clients/attach/session/context.js +3 -3
  18. package/dist/clients/attach/session/editor-inventory.js +6 -6
  19. package/dist/clients/attach/session/input-wiring.js +1 -1
  20. package/dist/clients/attach/session/teardown.js +7 -9
  21. package/dist/clients/attach/slash/dispatch.d.ts +1 -2
  22. package/dist/clients/attach/slash/dispatch.js +4 -8
  23. package/dist/clients/attach/viewer.js +394 -394
  24. package/dist/clients/inbox/controller.js +3 -2
  25. package/dist/clients/inbox/deck-adapter.js +3 -2
  26. package/dist/clients/surfaces/host.d.ts +1 -1
  27. package/dist/commands/api-client.d.ts +12 -13
  28. package/dist/commands/api-client.js +15 -16
  29. package/dist/commands/chord.js +1 -2
  30. package/dist/commands/human/queue.js +6 -5
  31. package/dist/commands/memory/find.js +3 -3
  32. package/dist/commands/node/lifecycle.js +1 -1
  33. package/dist/commands/node-lifecycle-revive.d.ts +1 -1
  34. package/dist/commands/node-lifecycle-revive.js +3 -3
  35. package/dist/commands/push.js +1 -1
  36. package/dist/commands/surface/node/placement.d.ts +1 -1
  37. package/dist/commands/surface/node/placement.js +1 -1
  38. package/dist/commands/surface-edit.js +1 -4
  39. package/dist/commands/sys/panels/panel.d.ts +3 -3
  40. package/dist/commands/sys/panels/panel.js +3 -3
  41. package/dist/commands/sys/setup-core.js +1 -7
  42. package/dist/commands/sys/sync-deps.js +1 -3
  43. package/dist/commands/sys/sync-project-guidance.js +2 -11
  44. package/dist/commands/sys/sysprompt.js +1 -3
  45. package/dist/core/__tests__/fixtures/fake-engine.js +1 -1
  46. package/dist/core/__tests__/helpers/harness.d.ts +1 -1
  47. package/dist/core/__tests__/helpers/harness.js +1 -1
  48. package/dist/core/canvas/boot.js +1 -1
  49. package/dist/core/canvas/browse/app.js +2 -2
  50. package/dist/core/canvas/canvas.d.ts +3 -3
  51. package/dist/core/canvas/canvas.js +3 -3
  52. package/dist/core/canvas/daemon-owner.js +1 -2
  53. package/dist/core/canvas/db.js +7 -8
  54. package/dist/core/canvas/index.js +2 -2
  55. package/dist/core/canvas/pid.d.ts +12 -14
  56. package/dist/core/canvas/pid.js +16 -20
  57. package/dist/core/canvas/remote-transport.js +2 -2
  58. package/dist/core/canvas/status-glyph.d.ts +1 -1
  59. package/dist/core/canvas/status-glyph.js +1 -1
  60. package/dist/core/canvas/types.d.ts +1 -1
  61. package/dist/core/command-manifests/manifest.js +4 -6
  62. package/dist/core/command-manifests/schema.js +10 -12
  63. package/dist/core/command-plugins/transport/exec-invoke.js +5 -7
  64. package/dist/core/command-plugins/transport/http-invoke.js +4 -6
  65. package/dist/core/config.d.ts +1 -1
  66. package/dist/core/config.js +2 -2
  67. package/dist/core/events/read.js +1 -3
  68. package/dist/core/fault-classifier.js +1 -3
  69. package/dist/core/fs-utils.d.ts +7 -0
  70. package/dist/core/fs-utils.js +29 -2
  71. package/dist/core/host-exports/export.d.ts +4 -5
  72. package/dist/core/host-exports/export.js +4 -5
  73. package/dist/core/human/claim.js +3 -2
  74. package/dist/core/human/convention.d.ts +0 -1
  75. package/dist/core/human/convention.js +1 -9
  76. package/dist/core/human/scan.js +5 -4
  77. package/dist/core/profiles/manifest.d.ts +1 -1
  78. package/dist/core/profiles/manifest.js +1 -1
  79. package/dist/core/review/types.d.ts +1 -1
  80. package/dist/core/review/types.js +1 -1
  81. package/dist/core/runtime/broker/frame-dispatch.js +4 -4
  82. package/dist/core/runtime/broker-cli.js +1 -1
  83. package/dist/core/runtime/broker-protocol.d.ts +9 -10
  84. package/dist/core/runtime/broker-protocol.js +9 -10
  85. package/dist/core/runtime/broker-sdk.d.ts +1 -1
  86. package/dist/core/runtime/broker.js +6 -6
  87. package/dist/core/runtime/host.js +14 -14
  88. package/dist/core/runtime/launch.d.ts +1 -1
  89. package/dist/core/runtime/launch.js +1 -1
  90. package/dist/core/runtime/lifecycle.js +16 -21
  91. package/dist/core/runtime/node-read.js +2 -3
  92. package/dist/core/runtime/package-health.js +1 -8
  93. package/dist/core/runtime/persona.js +2 -2
  94. package/dist/core/runtime/pi-vendored.d.ts +4 -2
  95. package/dist/core/runtime/pi-vendored.js +5 -16
  96. package/dist/core/runtime/promote.js +1 -1
  97. package/dist/core/runtime/reopen.js +2 -2
  98. package/dist/core/runtime/revive-all.d.ts +3 -3
  99. package/dist/core/runtime/revive-all.js +6 -6
  100. package/dist/core/runtime/revive.js +1 -1
  101. package/dist/core/runtime/spawn.d.ts +1 -2
  102. package/dist/core/runtime/spawn.js +2 -3
  103. package/dist/core/runtime/structured-output.js +4 -6
  104. package/dist/core/runtime/tmux-driver.d.ts +2 -2
  105. package/dist/core/runtime/tmux-driver.js +3 -5
  106. package/dist/core/runtime/tool-group-summary.js +5 -4
  107. package/dist/core/runtime/warm-pool.js +1 -1
  108. package/dist/core/scope.js +2 -9
  109. package/dist/core/self-update.js +3 -3
  110. package/dist/core/spawn.d.ts +0 -1
  111. package/dist/core/spawn.js +1 -4
  112. package/dist/core/substrate/on-read.js +1 -9
  113. package/dist/core/wake.js +2 -2
  114. package/dist/core/worktree.d.ts +3 -3
  115. package/dist/core/worktree.js +5 -7
  116. package/dist/daemon/api/bridge.js +1 -1
  117. package/dist/daemon/api/handlers/broker-ops.js +1 -2
  118. package/dist/daemon/api/handlers/messages.js +2 -2
  119. package/dist/daemon/api/handlers/nodes.js +1 -1
  120. package/dist/daemon/api/handlers/reports.js +1 -1
  121. package/dist/daemon/api/handlers/validate.d.ts +2 -1
  122. package/dist/daemon/api/handlers/validate.js +2 -3
  123. package/dist/daemon/cron-run.js +7 -7
  124. package/dist/daemon/human/finish.js +3 -2
  125. package/dist/daemon/manage.d.ts +6 -6
  126. package/dist/daemon/manage.js +15 -16
  127. package/dist/pi-extensions/canvas-context-intro.js +1 -14
  128. package/dist/pi-extensions/canvas-doc-substrate.js +1 -1
  129. package/dist/pi-extensions/canvas-review-boundary.js +1 -14
  130. package/dist/pi-extensions/truncate.d.ts +5 -0
  131. package/dist/pi-extensions/truncate.js +14 -0
  132. package/dist/shared/predicates.d.ts +2 -0
  133. package/dist/shared/predicates.js +4 -0
  134. package/dist/shared/shell-quote.d.ts +3 -0
  135. package/dist/shared/shell-quote.js +5 -0
  136. package/dist/shared/tool-groups.d.ts +4 -2
  137. package/dist/shared/tool-groups.js +3 -3
  138. package/dist/types.d.ts +4 -5
  139. package/package.json +1 -1
  140. package/runtime.lock.json +2 -2
@@ -33,7 +33,7 @@ export interface CrtrClientOptions {
33
33
  * autostart is on; after it resolves, the client polls `/healthz` and retries
34
34
  * the original request once. Absent → a cold socket throws `daemon_unavailable`. */
35
35
  onColdSocket?: () => Promise<void>;
36
- /** Injected cold-start diagnostic (issue #516). Called ONLY when the bounded
36
+ /** Injected cold-start diagnostic. Called ONLY when the bounded
37
37
  * `/healthz` poll times out after `onColdSocket`, so the caller can attach
38
38
  * operator-useful context (e.g. a bounded tail of crtrd's stderr log) to the
39
39
  * `daemon_unavailable` error instead of it staying a bare message. Must
@@ -43,7 +43,7 @@ export interface CrtrClientOptions {
43
43
  /** Bounded window (ms) to poll `/healthz` after `onColdSocket` resolves
44
44
  * before giving up with `daemon_unavailable`. Defaults to
45
45
  * `HEALTHZ_POLL_WINDOW_MS`. This must NOT silently drift from whatever
46
- * window actually governs "did the daemon start" (issue #508 follow-up):
46
+ * window actually governs "did the daemon start":
47
47
  * `onColdSocket` is fire-and-forget, so this poll is the ONLY deadline that
48
48
  * determines whether the CLI reports success. A caller whose `onColdSocket`
49
49
  * hook triggers a differently-windowed startup verifier (e.g. the CLI's
@@ -253,8 +253,8 @@ export declare class CrtrClient {
253
253
  private handleColdSocket;
254
254
  }
255
255
  /** Compose the cold-start `/healthz`-timeout `daemon_unavailable` message,
256
- * appending the injected diagnostic (issue #516) when one is present instead
257
- * of discarding the real startup failure behind a bare message. Exported (not
256
+ * appending the injected diagnostic when one is present instead of discarding
257
+ * the real startup failure behind a bare message. Exported (not
258
258
  * from the package's public `index.ts` surface, which is dependency-light by
259
259
  * design) purely so the regression test can assert the composition without
260
260
  * waiting out the real poll window. */
@@ -655,8 +655,8 @@ function sleep(ms) {
655
655
  return new Promise((resolve) => setTimeout(resolve, ms));
656
656
  }
657
657
  /** Compose the cold-start `/healthz`-timeout `daemon_unavailable` message,
658
- * appending the injected diagnostic (issue #516) when one is present instead
659
- * of discarding the real startup failure behind a bare message. Exported (not
658
+ * appending the injected diagnostic when one is present instead of discarding
659
+ * the real startup failure behind a bare message. Exported (not
660
660
  * from the package's public `index.ts` surface, which is dependency-light by
661
661
  * design) purely so the regression test can assert the composition without
662
662
  * waiting out the real poll window. */
@@ -3,7 +3,7 @@ import type { NodeIdDTO, NodeStatusDTO } from './common.js';
3
3
  export interface ReviveRequest {
4
4
  /** Resume the saved conversation (true) vs a fresh launch (false). */
5
5
  resume?: boolean;
6
- /** Clear the finalization latch before reviving (#339 `--reopen` gate). The
6
+ /** Clear the finalization latch before reviving (the `--reopen` gate). The
7
7
  * latch clear is a canvas write, so the gate is enforced server-side: with
8
8
  * `reopen:false` (default) a finalized node is REJECTED (`node_finalized`);
9
9
  * with `reopen:true` a node that is NOT finalized is rejected
@@ -20,7 +20,7 @@ export interface PushReportResultDTO {
20
20
  /** Present (and true) only for a `final` push whose reporting node owned a
21
21
  * managed worktree with zero commits ahead of its base and no uncommitted
22
22
  * changes: the server auto-dropped it instead of blocking on `node worktree
23
- * close` (#327/#333). A worktree with real unlanded work instead blocks with
23
+ * close`. A worktree with real unlanded work instead blocks with
24
24
  * an `open_managed_worktree` error. */
25
25
  worktree_auto_dropped?: boolean;
26
26
  /** Present only alongside `worktree_auto_dropped`: the checkout path dropped. */
@@ -30,7 +30,7 @@ async function summarizedGroup(messages, key) {
30
30
  stats: {},
31
31
  state: { isStreaming: false },
32
32
  toolGroupSummaries: {
33
- [key]: { bullets: ['broker recap'], nodesSpawned: 0, filesEdited: 0 },
33
+ [key]: { bullets: ['broker recap'], nodesSpawned: 0, filesDeleted: 0 },
34
34
  },
35
35
  });
36
36
  const group = internals.groups.find((candidate) => candidate.key === key);
@@ -70,7 +70,7 @@ test('a folded recap carries paths and counts, never diff or write payload bodie
70
70
  bashMode: tone('bashMode'), bashModeAlt: tone('bashModeAlt'),
71
71
  };
72
72
  const recap = new GroupRecapComponent();
73
- recap.setRecap({ bullets: ['finished'], nodesSpawned: 0, filesEdited: 2 }, 2, { memories: [{ action: 'read', name: 'alpha', path: '/tmp/memories/alpha.md' }], files: recorder.activity().files }, true, palette);
73
+ recap.setRecap({ bullets: ['finished'], nodesSpawned: 0, filesDeleted: 2 }, 2, { memories: [{ action: 'read', name: 'alpha', path: '/tmp/memories/alpha.md' }], files: recorder.activity().files }, true, palette);
74
74
  const text = recap.render(120).join('\n');
75
75
  assert.match(text, /overwritten\.ts/, 'the recap still names what changed');
76
76
  assert.doesNotMatch(text, /sensitive-old-diff|sensitive-new-diff|sensitive-write-payload/, 'payload bodies never leak into a recap');
@@ -29,10 +29,10 @@ export function defaultAgentDir() {
29
29
  }
30
30
  /**
31
31
  * pi's `app.*` editor keybindings — VENDORED from pi `core/keybindings.ts`
32
- * `KEYBINDINGS` (the const is not exported; review-C1 pattern). This is the
33
- * subset CustomEditor's main editor consults; the selector-internal bindings
34
- * (`app.tree.*`, `app.models.*`, session delete/rename) are omitted because the
35
- * attach viewer does not open those selectors in Phase 4. Re-sync on each SDK
32
+ * `KEYBINDINGS` (the const is not exported). This is the subset CustomEditor's
33
+ * main editor consults; the selector-internal bindings (`app.tree.*`,
34
+ * `app.models.*`, session delete/rename) are omitted because the attach viewer
35
+ * does not open those selectors. Re-sync on each SDK
36
36
  * bump against `core/keybindings.js`.
37
37
  */
38
38
  const APP_KEYBINDINGS = {
@@ -148,7 +148,7 @@ export async function mirrorKittyProtocolToEditor(active) {
148
148
  * Resolve the `fd` binary path the @-mention file picker needs. pi-tui's
149
149
  * `CombinedAutocompleteProvider` only returns file suggestions when handed an
150
150
  * `fdPath` (its third ctor arg) — without it, typing `@` triggers the picker but
151
- * `getFuzzyFileSuggestions` returns `[]`, so the picker never appears (issue #6).
151
+ * `getFuzzyFileSuggestions` returns `[]`, so the picker never appears.
152
152
  * pi's own interactive mode resolves this via `ensureTool('fd')` from its
153
153
  * internal `utils/tools-manager` (checks pi's managed bin dir + system PATH,
154
154
  * downloading the binary if missing). That module is NOT on pi-coding-agent's
@@ -1,4 +1,4 @@
1
- // input/clipboard-image.ts — read the clipboard image for `crtr surface attach` (T6, review M1).
1
+ // input/clipboard-image.ts — read the clipboard image for `crtr surface attach`.
2
2
  //
3
3
  // pi's `readClipboardImage` is NOT re-exported (it lives behind a native
4
4
  // clipboard binding + Photon worker the package gates off `.`-only exports), so
@@ -21,7 +21,7 @@
21
21
  // JPEG and picks the smaller, so it doubles as format normalization;
22
22
  // `convertToPng` is the fallback when the Photon resizer is unavailable. The
23
23
  // fallbacks (convertToPng / raw bytes) are NOT size-bounded by `resizeImage`, so
24
- // each is gated on the SAME MAX_BYTES base64 ceiling (review M1): over it → the
24
+ // each is gated on the SAME MAX_BYTES base64 ceiling: over it → the
25
25
  // image is DROPPED with a user-visible note rather than shipped as an over-cap
26
26
  // frame that would overflow BROKER_READ_CAPS.maxLineBytes (24 MiB) and destroy
27
27
  // the viewer socket. The result is an `ImageContent` (pi-ai) ready to drop
@@ -94,7 +94,7 @@ export class RefAwareAutocompleteProvider {
94
94
  }));
95
95
  // Filtering above used the full slash-form `prefix` (context.prefix always
96
96
  // starts with '/' for a matched token), but the RETURNED prefix strips that
97
- // leading '/' (review Major 3). pi-tui's `handleInput` (tui.select.confirm
97
+ // leading '/'. pi-tui's `handleInput` (tui.select.confirm
98
98
  // branch) falls through to `submitValue()` whenever `autocompletePrefix`
99
99
  // (set verbatim from this return value) starts with '/' — that's its own
100
100
  // leading-command-completion submit shortcut, and a non-leading ref
@@ -91,7 +91,7 @@ export declare class TitledEditor extends CustomEditor {
91
91
  * The base `Editor` resolves newLine against `getKeybindings()` on ITS pi-tui
92
92
  * instance, which in a non-deduped install is a DIFFERENT module than the one
93
93
  * carrying crtr's `alt+enter` override — so the override can silently never
94
- * reach it and Alt+Enter falls through to submit (issue #11). crtr's `km` (the
94
+ * reach it and Alt+Enter falls through to submit. crtr's `km` (the
95
95
  * same manager CustomEditor matches `app.*` against) always carries the
96
96
  * override, so matching the chord here makes the newline behavior independent
97
97
  * of cross-instance keybinding mirroring. Skipped while the autocomplete popup
@@ -159,8 +159,8 @@ export declare class TitledEditor extends CustomEditor {
159
159
  paneFocused: boolean;
160
160
  /** Canonical names the broker currently resolves — the exact-membership set
161
161
  * a candidate ref token must be in to render as a resolved highlight.
162
- * Empty by default (no decoration) until the caller (T3, out of scope for
163
- * this wave) supplies the broker-supplied inventory. */
162
+ * Empty by default (no decoration) until the caller supplies the
163
+ * broker-supplied inventory. */
164
164
  private resolvedRefNames;
165
165
  /** Command names the VIEWER dispatches ENTIRELY locally (never reach the
166
166
  * engine as prose) — checked against the TRIMMED leading-name rule
@@ -169,15 +169,14 @@ export declare class TitledEditor extends CustomEditor {
169
169
  * `locallyConsumedCommandNames()` from `slash/dispatch.ts` — independent
170
170
  * of `remote` and of the broker's `get_commands` reply (see that
171
171
  * function's doc comment). Deliberately NEVER the merged display/
172
- * completion list (review Major 6 — `slashCommandList` is a different,
173
- * remote-filtered, broker-augmented list, not a dispatch predicate). */
172
+ * completion list (`slashCommandList` is a different, remote-filtered,
173
+ * broker-augmented list, not a dispatch predicate). */
174
174
  private localCommandNames;
175
175
  /** Leading tokens the ENGINE (broker) independently consumes as a leading
176
176
  * command/skill/template dispatch — the broker's own
177
- * `engineLeadingCommandNames` (design: inline memory references, review
178
- * Major 6), checked against the UNTRIMMED leading-name rule
179
- * (`matchesEngineLeadingCommand`, mirroring the broker's own
180
- * `isLeadingEngineCommand` — review Minor 10). Populated from each
177
+ * `engineLeadingCommandNames`, checked against the UNTRIMMED leading-name
178
+ * rule (`matchesEngineLeadingCommand`, mirroring the broker's own
179
+ * `isLeadingEngineCommand`). Populated from each
181
180
  * `list_memory_refs` reply, alongside `resolvedRefNames`. */
182
181
  private engineCommandNames;
183
182
  setResolvedRefNames(names: Iterable<string>): void;
@@ -192,12 +191,12 @@ export declare class TitledEditor extends CustomEditor {
192
191
  * is simply left undecorated. Suppressed entirely (command-wide) whenever
193
192
  * the composer's own leading token dispatches — as a LOCAL viewer command
194
193
  * (`localCommandNames`, TRIMMED rule) or as an ENGINE-consumed leading
195
- * command/skill/template (`engineCommandNames`, UNTRIMMED rule, review
196
- * Minor 10) — the ref grammar and slash-dispatch's name charset differ, so
194
+ * command/skill/template (`engineCommandNames`, UNTRIMMED rule) — the ref
195
+ * grammar and slash-dispatch's name charset differ, so
197
196
  * highlighting a ref inside a command that's about to dispatch would be
198
197
  * misleading chrome for text the command, not memory resolution, is about
199
- * to consume. The two rules are kept deliberately SEPARATE (review Major
200
- * 6) rather than collapsed into one set/predicate: they classify against
198
+ * to consume. The two rules are kept deliberately SEPARATE rather than
199
+ * collapsed into one set/predicate: they classify against
201
200
  * different text (trimmed vs. raw) and different authorities (this
202
201
  * viewer's own dispatch vs. the broker's). */
203
202
  private decorateResolvedRefs;
@@ -401,7 +401,7 @@ function extractLeadingCommandName(text) {
401
401
  return m ? m[1] : null;
402
402
  }
403
403
  /** Mirrors the broker's `isLeadingEngineCommand` (`core/runtime/broker.ts`)
404
- * leading-token rule EXACTLY for classification purposes (review Minor 10):
404
+ * leading-token rule EXACTLY for classification purposes:
405
405
  * the RAW, un-trimmed `text` must itself start with `/` — no `.trim()` — for
406
406
  * ANY token to be a candidate, matching the broker's own untrimmed
407
407
  * `text.startsWith('/')` checks byte-for-byte (space-based name split,
@@ -447,7 +447,7 @@ export class TitledEditor extends CustomEditor {
447
447
  constructor(tui, theme, keybindings, options) {
448
448
  super(tui, theme, keybindings, options);
449
449
  this.km = keybindings;
450
- // Preserve raw (untrimmed) submit text end-to-end (review Major 4). The base
450
+ // Preserve raw (untrimmed) submit text end-to-end. The base
451
451
  // `Editor.submitValue()` — private in the `.d.ts` (blocking a normal TS
452
452
  // `override`), hence the structural cast, same pattern as the other private-
453
453
  // internals casts throughout this file (`scrollOffset`, `historyIndex`,
@@ -497,7 +497,7 @@ export class TitledEditor extends CustomEditor {
497
497
  * The base `Editor` resolves newLine against `getKeybindings()` on ITS pi-tui
498
498
  * instance, which in a non-deduped install is a DIFFERENT module than the one
499
499
  * carrying crtr's `alt+enter` override — so the override can silently never
500
- * reach it and Alt+Enter falls through to submit (issue #11). crtr's `km` (the
500
+ * reach it and Alt+Enter falls through to submit. crtr's `km` (the
501
501
  * same manager CustomEditor matches `app.*` against) always carries the
502
502
  * override, so matching the chord here makes the newline behavior independent
503
503
  * of cross-instance keybinding mirroring. Skipped while the autocomplete popup
@@ -655,8 +655,8 @@ export class TitledEditor extends CustomEditor {
655
655
  paneFocused = true;
656
656
  /** Canonical names the broker currently resolves — the exact-membership set
657
657
  * a candidate ref token must be in to render as a resolved highlight.
658
- * Empty by default (no decoration) until the caller (T3, out of scope for
659
- * this wave) supplies the broker-supplied inventory. */
658
+ * Empty by default (no decoration) until the caller supplies the
659
+ * broker-supplied inventory. */
660
660
  resolvedRefNames = new Set();
661
661
  /** Command names the VIEWER dispatches ENTIRELY locally (never reach the
662
662
  * engine as prose) — checked against the TRIMMED leading-name rule
@@ -665,15 +665,14 @@ export class TitledEditor extends CustomEditor {
665
665
  * `locallyConsumedCommandNames()` from `slash/dispatch.ts` — independent
666
666
  * of `remote` and of the broker's `get_commands` reply (see that
667
667
  * function's doc comment). Deliberately NEVER the merged display/
668
- * completion list (review Major 6 — `slashCommandList` is a different,
669
- * remote-filtered, broker-augmented list, not a dispatch predicate). */
668
+ * completion list (`slashCommandList` is a different, remote-filtered,
669
+ * broker-augmented list, not a dispatch predicate). */
670
670
  localCommandNames = new Set();
671
671
  /** Leading tokens the ENGINE (broker) independently consumes as a leading
672
672
  * command/skill/template dispatch — the broker's own
673
- * `engineLeadingCommandNames` (design: inline memory references, review
674
- * Major 6), checked against the UNTRIMMED leading-name rule
675
- * (`matchesEngineLeadingCommand`, mirroring the broker's own
676
- * `isLeadingEngineCommand` — review Minor 10). Populated from each
673
+ * `engineLeadingCommandNames`, checked against the UNTRIMMED leading-name
674
+ * rule (`matchesEngineLeadingCommand`, mirroring the broker's own
675
+ * `isLeadingEngineCommand`). Populated from each
677
676
  * `list_memory_refs` reply, alongside `resolvedRefNames`. */
678
677
  engineCommandNames = new Set();
679
678
  setResolvedRefNames(names) {
@@ -694,12 +693,12 @@ export class TitledEditor extends CustomEditor {
694
693
  * is simply left undecorated. Suppressed entirely (command-wide) whenever
695
694
  * the composer's own leading token dispatches — as a LOCAL viewer command
696
695
  * (`localCommandNames`, TRIMMED rule) or as an ENGINE-consumed leading
697
- * command/skill/template (`engineCommandNames`, UNTRIMMED rule, review
698
- * Minor 10) — the ref grammar and slash-dispatch's name charset differ, so
696
+ * command/skill/template (`engineCommandNames`, UNTRIMMED rule) — the ref
697
+ * grammar and slash-dispatch's name charset differ, so
699
698
  * highlighting a ref inside a command that's about to dispatch would be
700
699
  * misleading chrome for text the command, not memory resolution, is about
701
- * to consume. The two rules are kept deliberately SEPARATE (review Major
702
- * 6) rather than collapsed into one set/predicate: they classify against
700
+ * to consume. The two rules are kept deliberately SEPARATE rather than
701
+ * collapsed into one set/predicate: they classify against
703
702
  * different text (trimmed vs. raw) and different authorities (this
704
703
  * viewer's own dispatch vs. the broker's). */
705
704
  decorateResolvedRefs(lines, width) {
@@ -19,7 +19,7 @@ export declare function isBlockingDialog(req: RpcExtensionUIRequest): boolean;
19
19
  * `keybindings.matches(key, "app.editor.external")` for the Ctrl+G external-editor
20
20
  * shortcut. Defaults to a fresh `KeybindingsManager(TUI_KEYBINDINGS)` — that
21
21
  * knows the base TUI bindings but not pi's app-level `app.editor.external`, so
22
- * Ctrl+G is inert until the input controller (T6) passes the app keybindings
22
+ * Ctrl+G is inert until the input controller passes the app keybindings
23
23
  * manager. `ExtensionEditorComponent` is typed against pi's CORE
24
24
  * KeybindingsManager (a subclass of pi-tui's), so we widen via the constructor's
25
25
  * own parameter type rather than importing the core class (type-only export).
@@ -1,4 +1,4 @@
1
- // overlays/dialogs.ts — the blocking-dialog renderer for `crtr surface attach` (T5).
1
+ // overlays/dialogs.ts — the blocking-dialog renderer for `crtr surface attach`.
2
2
  //
3
3
  // The broker forwards an extension's user-blocking UI request as an
4
4
  // `extension_ui_request` frame (RpcExtensionUIRequest). The viewer renders it
@@ -45,7 +45,7 @@ const OVERLAY_OPTIONS = { anchor: 'center', width: '70%', maxHeight: '80%' };
45
45
  * `keybindings.matches(key, "app.editor.external")` for the Ctrl+G external-editor
46
46
  * shortcut. Defaults to a fresh `KeybindingsManager(TUI_KEYBINDINGS)` — that
47
47
  * knows the base TUI bindings but not pi's app-level `app.editor.external`, so
48
- * Ctrl+G is inert until the input controller (T6) passes the app keybindings
48
+ * Ctrl+G is inert until the input controller passes the app keybindings
49
49
  * manager. `ExtensionEditorComponent` is typed against pi's CORE
50
50
  * KeybindingsManager (a subclass of pi-tui's), so we widen via the constructor's
51
51
  * own parameter type rather than importing the core class (type-only export).
@@ -40,12 +40,13 @@
40
40
  // installed adapter (pi-mcp-adapter index.ts: "auth-start" / "auth-complete" /
41
41
  // "ui-messages"; `connect: <name>`).
42
42
  import { createHash } from 'node:crypto';
43
- import { existsSync, readFileSync } from 'node:fs';
43
+ import { existsSync } from 'node:fs';
44
44
  import { homedir } from 'node:os';
45
45
  import { join, resolve } from 'node:path';
46
46
  import { piAgentDir } from '../../../core/runtime/package-health.js';
47
47
  import { SelectList, visibleWidth } from '@earendil-works/pi-tui';
48
48
  import { getSelectListTheme } from '@earendil-works/pi-coding-agent';
49
+ import { readJsonOrNull } from '../../../core/fs-utils.js';
49
50
  /** Adapter metadata-cache.ts CACHE_MAX_AGE_MS — entries older than this are misses. */
50
51
  const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
51
52
  /** The adapter's import-source table (pi-mcp-adapter config.ts IMPORT_PATHS).
@@ -72,14 +73,6 @@ function importPathCandidates(kind, home) {
72
73
  return [];
73
74
  }
74
75
  }
75
- function readJson(path) {
76
- try {
77
- return JSON.parse(readFileSync(path, 'utf-8'));
78
- }
79
- catch {
80
- return null; // missing or corrupt → empty state, never a crash
81
- }
82
- }
83
76
  /** Mirror of the adapter's validateConfig (config.ts): a source file's own
84
77
  * servers may live under `mcpServers` OR the `mcp-servers` alias — but a
85
78
  * non-object value invalidates the WHOLE file, dropping its `imports` too. */
@@ -130,7 +123,7 @@ function mergeServerMaps(base, next) {
130
123
  /** Read one config file: its own servers layered OVER any `imports` servers
131
124
  * (first import wins per name, own config wins overall — adapter expandImports). */
132
125
  function readConfigSource(path, cwd, home) {
133
- const raw = readJson(path);
126
+ const raw = readJsonOrNull(path);
134
127
  if (raw === null)
135
128
  return {};
136
129
  const { servers: own, imports } = validateConfigSource(raw);
@@ -144,7 +137,7 @@ function readConfigSource(path, cwd, home) {
144
137
  .find((candidate) => existsSync(candidate));
145
138
  if (full === undefined)
146
139
  continue;
147
- const importedRaw = readJson(full);
140
+ const importedRaw = readJsonOrNull(full);
148
141
  if (importedRaw === null)
149
142
  continue;
150
143
  for (const [name, spec] of Object.entries(extractImportedServers(importedRaw, kind))) {
@@ -228,7 +221,7 @@ export function readMcpServers(home = homedir(), cwd = process.cwd()) {
228
221
  for (const path of sourcePaths) {
229
222
  servers = mergeServerMaps(servers, readConfigSource(path, cwd, home));
230
223
  }
231
- const cache = readJson(join(agentDir, 'mcp-cache.json'));
224
+ const cache = readJsonOrNull(join(agentDir, 'mcp-cache.json'));
232
225
  return Object.entries(servers).map(([name, spec]) => {
233
226
  const transport = spec.type ?? (spec.url !== undefined ? 'http' : 'stdio');
234
227
  const target = spec.url ?? [spec.command, ...(spec.args ?? [])].filter(Boolean).join(' ');
@@ -35,7 +35,7 @@ export interface ChatViewOptions {
35
35
  toolOutputExpanded?: boolean;
36
36
  /** Sink for footer/header-only events ChatView does not itself render
37
37
  * (queue_update, session_info_changed, thinking_level_changed). The attach
38
- * command (T7) wires a footer here; absent → these events are no-ops. */
38
+ * command wires a footer here; absent → these events are no-ops. */
39
39
  onFooterEvent?: (event: AgentSessionEvent) => void;
40
40
  /** Theme palette for the activity spinner / dim status / error lines. Absent →
41
41
  * the module defaults (faint + ANSI red) are used. */
@@ -1,4 +1,4 @@
1
- // render/chat-view.ts — the render half of `crtr surface attach` (Phase 4, T5).
1
+ // render/chat-view.ts — the render half of `crtr surface attach`.
2
2
  //
3
3
  // A direct port of pi's `InteractiveMode.handleEvent()`
4
4
  // (interactive-mode.js:2169-2462) + its rebuild path
@@ -11,7 +11,7 @@
11
11
  // Its activity state (working spinner, compaction/retry indicators) is signaled
12
12
  // to the viewer-owned fixed slot when one is supplied. It does NOT own the
13
13
  // editor, the footer, or the terminal title — those are chrome laid out by the
14
- // attach command (T7). Events that only affect that chrome
14
+ // attach command. Events that only affect that chrome
15
15
  // (queue_update / session_info_changed / thinking_level_changed) are forwarded
16
16
  // to the optional `onFooterEvent` sink instead of rendered here.
17
17
  //
@@ -6,6 +6,8 @@ import { pathToFileURL } from 'node:url';
6
6
  import { ICON } from '../../../core/inspector/model.js';
7
7
  import { TOOL_ICON } from '../../../core/preview-registry.js';
8
8
  const MEMORY_ICON = String.fromCodePoint(0xf02d);
9
+ // trash — deletion has no tool of its own, so it borrows no TOOL_ICON entry.
10
+ const DELETE_ICON = String.fromCodePoint(0xf1f8);
9
11
  function count(value, singular, plural) {
10
12
  return `${value} ${value === 1 ? singular : plural}`;
11
13
  }
@@ -74,6 +76,11 @@ export class GroupRecapComponent extends Container {
74
76
  if (changedFiles >= 1) {
75
77
  stats.push(`${palette.warning(TOOL_ICON.edit)} ${palette.muted(count(changedFiles, 'file changed', 'files changed'))}`);
76
78
  }
79
+ // Deletions never enter activity.files (only edit/write calls do), so the
80
+ // model's count covers a disjoint set and cannot double-count a change.
81
+ if (summary.filesDeleted >= 1) {
82
+ stats.push(`${palette.diffRemoved(DELETE_ICON)} ${palette.muted(count(summary.filesDeleted, 'file deleted', 'files deleted'))}`);
83
+ }
77
84
  const memoriesRead = countMemories(activity.memories, 'read');
78
85
  if (memoriesRead >= 1) {
79
86
  stats.push(`${palette.info(MEMORY_ICON)} ${palette.muted(count(memoriesRead, 'memory read', 'memory reads'))}`);
@@ -1,8 +1,8 @@
1
1
  // session/context.ts — the named home for an attach session's STABLE facts.
2
2
  //
3
- // `runAttach` used to hold every one of these as a closure local, which is why
4
- // pulling any block out of it meant inventing a bespoke parameter list for that
5
- // block alone. `AttachSession` gives the state one declaration; each
3
+ // Held as closure locals inside `runAttach`, pulling any block out of it would
4
+ // mean inventing a bespoke parameter list for that block alone.
5
+ // `AttachSession` gives the state one declaration; each
6
6
  // collaborator then names the slice it needs as a `Pick<AttachSession, …>`
7
7
  // view, so the module's dependencies are a *projection* of one truth rather
8
8
  // than a hand-maintained bag that can drift from it.
@@ -62,8 +62,8 @@ export function createEditorInventory(ctx, capabilities, hooks) {
62
62
  let brokerCommands;
63
63
  let memoryRefs = [];
64
64
  // The broker's `engineLeadingCommandNames` from the latest `list_memory_refs`
65
- // reply (design: inline memory references, review Major 6) — leading tokens
66
- // the ENGINE independently consumes, kept separate from the viewer-local
65
+ // reply — leading tokens the ENGINE independently consumes, kept separate
66
+ // from the viewer-local
67
67
  // `locallyConsumedCommandNames()` set and fed to the editor's untrimmed
68
68
  // engine-suppression rule via `setEngineCommandNames`.
69
69
  let engineLeadingCommandNames = [];
@@ -82,9 +82,9 @@ export function createEditorInventory(ctx, capabilities, hooks) {
82
82
  // Atomically rebuild the editor's provider/highlight state from whatever
83
83
  // command + ref inventory is CURRENTLY known — the one seam `setCommands` and
84
84
  // `setMemoryRefs` both call, so a command-only or ref-only response can never
85
- // rebuild the provider having silently dropped the other inventory (T3
86
- // ownership note: this module combines command metadata, ref metadata,
87
- // provider rebuild, and editor highlight sets in one place). Command-only
85
+ // rebuild the provider having silently dropped the other inventory. This
86
+ // module owns command metadata, ref metadata, provider rebuild, and editor
87
+ // highlight sets in one place. Command-only
88
88
  // autocomplete is unchanged: `RefAwareAutocompleteProvider` fully delegates
89
89
  // leading-slash and every non-ref context to the wrapped command provider, so
90
90
  // refs never enter command completion or the loaded-command preview (which
@@ -93,7 +93,7 @@ export function createEditorInventory(ctx, capabilities, hooks) {
93
93
  const commandProvider = buildAttachAutocompleteProvider(meta.cwd, hooks.fdPath, brokerCommands, capabilities);
94
94
  editor.setAutocompleteProvider(new RefAwareAutocompleteProvider(memoryRefs, commandProvider));
95
95
  editor.setResolvedRefNames(memoryRefs.map((ref) => ref.name));
96
- // Two independent suppression inventories (review Major 6): the viewer-local
96
+ // Two independent suppression inventories: the viewer-local
97
97
  // set (everything `dispatchSlashCommand` handles entirely locally, trimmed
98
98
  // rule) and the broker's own `engineLeadingCommandNames` (untrimmed rule) —
99
99
  // never the merged display/completion list `slashCommandList` returns.
@@ -36,7 +36,7 @@ export function createInputController(ctx, km, capabilities, hooks) {
36
36
  //
37
37
  // A read-only viewer wires NEITHER. A REMOTE attach (`--canvas`) disables both
38
38
  // because they target the LOCAL `auth.json` via `defaultAgentDir()`, the wrong
39
- // host for a remote node (Phase 3 review Major 1, 1d); an OBSERVER because
39
+ // host for a remote node; an OBSERVER because
40
40
  // switching credentials and telling the broker to reload them mutates the
41
41
  // engine's auth, which a read-only viewer must not do. Unwired here means the
42
42
  // slash context carries no picker at all, so `/login` and `/logout` report
@@ -57,18 +57,16 @@ export function createTeardown(s, hooks) {
57
57
  socket.close();
58
58
  restore();
59
59
  // A clean detach leaves the broker running (the engine runs on) — the
60
- // documented "detach leaves the broker running" contract (B2). Per-action
61
- // reaping of an empty husk used to run here (reapIfEmpty →
62
- // teardown/deleteNode, which reaps EVEN a live broker since the user
63
- // explicitly detached), but it required a direct canvas.db mutation the
64
- // API-only client cannot reach and no per-node reap route exposes, so it is
65
- // dropped (B-10). Disposition of an abandoned empty broker: `crtr canvas
60
+ // documented "detach leaves the broker running" contract. Teardown reaps
61
+ // nothing per-action: reaping an empty husk here would need a direct
62
+ // canvas.db mutation the API-only client cannot reach, and no per-node reap
63
+ // route exposes one. Disposition of an abandoned empty broker: `crtr canvas
66
64
  // prune --empty` (reapEmptyNodes) sweeps it, but only ONCE its pid is dead
67
65
  // — it deliberately spares a LIVE broker as the daemon's domain. So an empty
68
66
  // broker lingers until it idle-releases (or is closed) to a dormant husk,
69
- // then prune reaps it. Net vs OLD: a narrow, transient live-empty window
70
- // replaces immediate reap — the only faithful option under the API boundary;
71
- // a per-node reap route would restore immediate cleanup (future work).
67
+ // then prune reaps it, leaving a narrow, transient live-empty window — the
68
+ // only faithful option under the API boundary. A per-node reap route would
69
+ // buy immediate cleanup (future work).
72
70
  if (reason === 'broker-gone') {
73
71
  const msg = hooks.lastSocketError() ??
74
72
  `broker gone — node ${nodeId} is no longer running its engine. Re-focus to reattach.`;
@@ -87,8 +87,7 @@ export declare const CANVAS_SLASH_COMMANDS: ReadonlyArray<{
87
87
  * `true` from `dispatchSlashCommand` — swallowed with a notice, never
88
88
  * forwarded as prose — so it counts as consumed here too, independent of
89
89
  * `remote`). Exactly BUILTIN_NAMES ∪ CANVAS_NAMES ∪ SCOPED_OUT — every name
90
- * `dispatchSlashCommand` returns `true` for (design: inline memory
91
- * references, review Major 6/terminal). Used ONLY for terminal
90
+ * `dispatchSlashCommand` returns `true` for. Used ONLY for terminal
92
91
  * ref-decoration suppression (`input/titled-editor.ts`); never merged into
93
92
  * command completion, which is `slashCommandList` — a different,
94
93
  * remote-filtered, broker-augmented list. */
@@ -19,12 +19,13 @@
19
19
  // against pi 0.79.1 — `ScopedModelsSelectorComponent` is the one selector pi does
20
20
  // not re-export, so /scoped-models renders read-only via a SelectList.)
21
21
  //
22
- // SCOPED OUT (review m2 — no engine method): `/login`, `/logout`, `/share`,
22
+ // SCOPED OUT (no engine method): `/login`, `/logout`, `/share`,
23
23
  // `/trust` — a brief "not supported in attach" notice, never a frame.
24
24
  import { execFile } from 'node:child_process';
25
25
  import { BUILTIN_SLASH_COMMANDS } from '../../../core/runtime/pi-vendored.js';
26
26
  import { surfaceTmuxStyleArgs } from '../../../core/runtime/surface-bg.js';
27
27
  import { viewerOperationForSlash } from '../input/capabilities.js';
28
+ import { shellQuote } from '../../../shared/shell-quote.js';
28
29
  /** Canvas slash-commands — implemented NATIVE in the viewer, the ONLY live host:
29
30
  * the broker binds mode:'rpc', so broker pi-extensions never run their
30
31
  * interactive (mode==='tui') paths. `/promote` triggers a turn after shelling
@@ -58,7 +59,7 @@ const TMUX_NAMED_COLORS = new Set([
58
59
  'brightred', 'brightgreen', 'brightyellow', 'brightblue', 'brightmagenta', 'brightcyan',
59
60
  'orange', 'grey', 'gray', 'default',
60
61
  ]);
61
- /** Builtins with no Phase-4 engine method — scoped out (review m2). */
62
+ /** Builtins with no engine method — scoped out. */
62
63
  const SCOPED_OUT = new Set(['trust']);
63
64
  /** Valid `/settings thinking` levels — the `SetThinkingLevelFrame['level']`
64
65
  * union (= AgentSession['thinkingLevel'], pi-agent-core ThinkingLevel),
@@ -92,8 +93,7 @@ function readOnlyRefusal(name, ctx) {
92
93
  * `true` from `dispatchSlashCommand` — swallowed with a notice, never
93
94
  * forwarded as prose — so it counts as consumed here too, independent of
94
95
  * `remote`). Exactly BUILTIN_NAMES ∪ CANVAS_NAMES ∪ SCOPED_OUT — every name
95
- * `dispatchSlashCommand` returns `true` for (design: inline memory
96
- * references, review Major 6/terminal). Used ONLY for terminal
96
+ * `dispatchSlashCommand` returns `true` for. Used ONLY for terminal
97
97
  * ref-decoration suppression (`input/titled-editor.ts`); never merged into
98
98
  * command completion, which is `slashCommandList` — a different,
99
99
  * remote-filtered, broker-augmented list. */
@@ -605,10 +605,6 @@ function popup(cmd) {
605
605
  /* best-effort */
606
606
  }
607
607
  }
608
- /** Single-quote a string for safe interpolation into a `sh -c` command line. */
609
- function shellQuote(s) {
610
- return `'${s.replace(/'/g, `'\\''`)}'`;
611
- }
612
608
  // ---------------------------------------------------------------------------
613
609
  function usage(ctx, form) {
614
610
  ctx.notify(`Usage: ${form}`);