@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
@@ -24,7 +24,7 @@ import { writeGoal } from './kickoff.js';
24
24
  import { appendSituationalContext } from './situational-context.js';
25
25
  import { hasRoadmap, seedRoadmap } from './roadmap.js';
26
26
  import { buildIdentityAssertion, buildWakeBearings } from './bearings.js';
27
- import { installMenuBinding, installNavBindings, installViewNavBindings } from './tmux-chrome.js';
27
+ import { installTmuxBindings } from './tmux-chrome.js';
28
28
  import { getNode, fullName, recordPid } from '../canvas/index.js';
29
29
  import { registerViewerFocus, openViewerWindow, waitForBrokerViewSocket, viewerSplitEnv, windowOfPane, currentTmux, inTmux, focusWindow, } from './placement.js';
30
30
  import { transition } from './lifecycle.js';
@@ -72,9 +72,13 @@ export async function bootRoot(opts) {
72
72
  // covering cwd > a synchronous create-or-root-profile prompt (the stable root
73
73
  // profile, headless).
74
74
  const profileId = await selectProfileForCwd(opts.cwd, opts.profile, opts.pickProfile ?? false);
75
+ // Immediate feedback: the broker + viewer boot take ~1s and the profile
76
+ // selector has just released the terminal — a silent cursor reads as a hang.
77
+ if (process.stdout.isTTY)
78
+ process.stdout.write('\u001b[2m › starting…\u001b[0m\r\n');
75
79
  // A born-resident root starts in base mode; it earns the orchestrator persona
76
80
  // the first time it delegates (or on promotion). Resident lifecycle either way.
77
- const { launch } = buildLaunchSpec(kind, 'base', { lifecycle: 'resident', hasManager: false });
81
+ const { launch } = buildLaunchSpec(kind, 'base', { lifecycle: 'resident', hasManager: false, cwd: opts.cwd, profileId });
78
82
  // Born WITHOUT a name. Naming is async + event-driven: the canvas-goal-capture
79
83
  // extension names the node from its FIRST real message (the kickoff prompt or
80
84
  // a human's first line) inside its own pi process, via a headless `pi -p`.
@@ -93,18 +97,9 @@ export async function bootRoot(opts) {
93
97
  // mandate (bare `crtr` has none — writeGoal no-ops on empty).
94
98
  if (opts.prompt !== undefined)
95
99
  writeGoal(meta.node_id, opts.prompt);
96
- // Make the Alt+C action menu + Alt+] / Alt+[ node-nav keys + Alt+V then ]/[
97
- // view-nav chord live on this server (idempotent; guaranteed in tmux above).
100
+ // Reconcile the crouter-owned tmux bindings on this server.
98
101
  try {
99
- installMenuBinding();
100
- }
101
- catch { /* best-effort */ }
102
- try {
103
- installNavBindings();
104
- }
105
- catch { /* best-effort */ }
106
- try {
107
- installViewNavBindings();
102
+ installTmuxBindings();
108
103
  }
109
104
  catch { /* best-effort */ }
110
105
  // This terminal becomes the root's attach VIEWER in the user's CURRENT session,
@@ -140,19 +135,14 @@ export async function bootRoot(opts) {
140
135
  throw new Error(`failed to launch the broker engine for the front-door root ${meta.node_id} — nothing to attach to.`);
141
136
  }
142
137
  recordPid(meta.node_id, placed.pid);
143
- // The terminal has nothing to attach to until view.sock accepts. The root is
144
- // useless without its engine, so a socket timeout IS fatal here (unlike a
145
- // background child's optional viewer, which is skipped on timeout).
146
- if (!(await waitForBrokerViewSocket(meta.node_id, placed.exited))) {
147
- try {
148
- transition(meta.node_id, 'crash');
149
- }
150
- catch {
151
- /* best-effort cleanup */
152
- }
153
- throw new Error(`the front-door root broker ${meta.node_id} never bound its view socket — cannot attach.`);
154
- }
155
- const attachEnv = { ...process.env, ...viewerSplitEnv() };
138
+ // Spawn the attach viewer NOW, in parallel with the broker's boot: the
139
+ // viewer's own `crtr` cold start (~0.4s of module load) then overlaps the
140
+ // broker's ~1s view-socket bind instead of serializing after it — this was
141
+ // the bulk of the post-profile-pick dead air. CRTR_ATTACH_WAIT_MS tells the
142
+ // viewer to poll view.sock until it accepts (preWaitForViewSocket in
143
+ // attach-cmd.ts) instead of failing fast on the not-yet-bound socket; the
144
+ // deadline mirrors placement's BROKER_FOCUS_SOCKET_WAIT_MS.
145
+ const attachEnv = { ...process.env, ...viewerSplitEnv(), CRTR_ATTACH_WAIT_MS: '30000' };
156
146
  // Async, NOT spawnSync: a synchronous spawn would park THIS process's own
157
147
  // event loop for the whole attach session (which legitimately runs for
158
148
  // hours) — starving it of the SIGCHLD delivery it needs to reap its OWN
@@ -165,11 +155,36 @@ export async function bootRoot(opts) {
165
155
  // reap it in real time; `stdio: 'inherit'` still hands the child the TTY
166
156
  // directly, so this is a behavior-neutral swap for the foreground session.
167
157
  const attachChild = spawn('crtr', ['surface', 'attach', 'to', meta.node_id], { stdio: 'inherit', env: attachEnv });
168
- const code = await new Promise((resolveCode) => {
158
+ const attachExit = new Promise((resolveCode) => {
169
159
  attachChild.once('exit', (exitCode) => resolveCode(exitCode ?? 0));
170
160
  attachChild.once('error', () => resolveCode(1));
171
161
  });
172
- process.exit(code);
162
+ // The root is useless without its engine, so a socket timeout IS fatal here
163
+ // (unlike a background child's optional viewer, which is skipped on timeout):
164
+ // await readiness concurrently with the viewer's own pre-wait, mark the node
165
+ // crashed and reap the polling viewer if the broker never binds. Raced
166
+ // against the viewer exiting on its own first (e.g. the user quits during
167
+ // boot), which simply ends the session like any other detach.
168
+ const outcome = await Promise.race([
169
+ waitForBrokerViewSocket(meta.node_id, placed.exited).then((ok) => (ok ? 'ready' : 'never')),
170
+ attachExit.then(() => 'viewer-exit'),
171
+ ]);
172
+ if (outcome === 'never') {
173
+ try {
174
+ attachChild.kill();
175
+ }
176
+ catch {
177
+ /* best-effort */
178
+ }
179
+ try {
180
+ transition(meta.node_id, 'crash');
181
+ }
182
+ catch {
183
+ /* best-effort cleanup */
184
+ }
185
+ throw new Error(`the front-door root broker ${meta.node_id} never bound its view socket — cannot attach.`);
186
+ }
187
+ process.exit(await attachExit);
173
188
  }
174
189
  /** pi's sessions root, VENDORED from pi `config.getSessionsDir()` (= `<agentDir>/
175
190
  * sessions`). pi's package `exports` map is `.`-only, so config.js can't be
@@ -302,10 +317,6 @@ export async function spawnChild(opts) {
302
317
  // reaps when done. A child born as an orchestrator is terminal/orchestrator
303
318
  // (delegates + holds a roadmap, but still reports up), NOT resident.
304
319
  const lifecycle = root ? 'resident' : 'terminal';
305
- // Spine: a managed child reports up to its spawner (has a manager); an
306
- // independent root sits top-of-spine with nobody to push to. Mirrors the
307
- // `parent` set below (root ? null : spawner), so hasManager === parent!==null.
308
- const { launch } = buildLaunchSpec(opts.kind, mode, { lifecycle, hasManager: !root, model: opts.model });
309
320
  // Born WITHOUT a name — the canvas-goal-capture extension names it async from
310
321
  // its first message (the kickoff task) inside its own pi process, so spawn
311
322
  // never blocks on the LLM naming round-trip (the 2-3s freeze it used to cost).
@@ -326,6 +337,14 @@ export async function spawnChild(opts) {
326
337
  managedWorktree = createManagedWorktree(opts.cwd, nodeId);
327
338
  spawnCwd = managedWorktree.path;
328
339
  }
340
+ // Spine: a managed child reports up to its spawner (has a manager); an
341
+ // independent root sits top-of-spine with nobody to push to. Mirrors the
342
+ // `parent` set below (root ? null : spawner), so hasManager === parent!==null.
343
+ // Built HERE — after profile + worktree cwd are resolved — so the kind's
344
+ // model/tools resolve against the CHILD's own profile + project stack, not
345
+ // this process's ambient scope (a front-door shell has no CRTR_PROFILE_ID,
346
+ // which silently hid every profile-scope kind override).
347
+ const { launch } = buildLaunchSpec(opts.kind, mode, { lifecycle, hasManager: !root, model: opts.model, cwd: spawnCwd, profileId });
329
348
  const meta = spawnNode({
330
349
  kind: opts.kind,
331
350
  mode,
@@ -0,0 +1,26 @@
1
+ export declare function resolveStreamWatchdogMs(): number;
2
+ /**
3
+ * A single-timer dead-man's switch: `poke(token)` (re)arms it, `clear()`
4
+ * disarms it, and `onStall(token)` fires exactly once if `poke()` isn't called
5
+ * again within `ms`. The broker pokes it on relayed engine events while a turn
6
+ * is awaiting the provider and clears it at `agent_end` / tool execution — see
7
+ * broker.ts `relayEvent`.
8
+ *
9
+ * The `token` is an opaque generation stamp: each arm captures the token passed
10
+ * to its `poke()`, and `onStall` receives exactly that token. The broker bumps
11
+ * a session generation on every rebind, so a timer armed against a since-
12
+ * superseded session hands its stale generation to `onStall`, which no-ops it
13
+ * instead of aborting the live replacement session.
14
+ */
15
+ export declare class StreamWatchdog {
16
+ private readonly onStall;
17
+ private timer;
18
+ private readonly ms;
19
+ constructor(onStall: (token: number) => void, ms?: number);
20
+ /** Proof of progress — (re)arm the timer for `token`, canceling any prior
21
+ * countdown. The fired `onStall` receives THIS arm's token. */
22
+ poke(token: number): void;
23
+ /** Turn over (or broker tearing down) — no more relay expected until the
24
+ * next `poke()`. Idempotent. */
25
+ clear(): void;
26
+ }
@@ -0,0 +1,75 @@
1
+ // stream-watchdog.ts — broker-side dead-provider-stream detector.
2
+ //
3
+ // A laptop sleep or Wi-Fi loss can leave the provider's HTTP response stream
4
+ // half-open: the socket never errors and never closes, it just stops
5
+ // delivering bytes. pi's turn loop has no read-timeout on that socket, so the
6
+ // engine wedges in phase='turn' indefinitely — nothing drains (steer/followUp
7
+ // queues included) until the daemon's 20-minute wedge detector SIGTERMs the
8
+ // broker, which is the actual data-loss event a wedge this long causes (see
9
+ // queued-messages-never-delivered-diagnosis.md RC1/RC2). This watchdog aborts
10
+ // the stalled stream itself, well before that backstop: if no engine event has
11
+ // been relayed for STREAM_WATCHDOG_MS while a turn is active, the stream is
12
+ // presumed dead — abort it. The turn ends with stopReason 'aborted' (or the
13
+ // stophook's own error classification if the abort races a real error), the
14
+ // existing connection-fault retry machinery (which arms on agent_end) owns
15
+ // re-driving the turn, and the broker process — and its in-memory queues —
16
+ // survive instead of being killed.
17
+ const DEFAULT_STREAM_WATCHDOG_MS = 5 * 60_000; // 5 minutes.
18
+ // Comfortably under the daemon's 20-minute WEDGE_QUIET_MS hard kick (crtrd.ts),
19
+ // so this is the common-case recovery path and the daemon kick becomes the rare
20
+ // backstop. Generous enough that a live, healthy stream never trips it: a
21
+ // single LLM turn's own network round-trips (message_update deltas,
22
+ // tool_execution_start/update/end) land well inside 5 minutes. The watchdog
23
+ // covers ONLY time awaiting the provider: the broker disarms it while a tool is
24
+ // executing (a legitimately silent tool can run longer than the timeout — see
25
+ // broker.ts relayEvent) and rearms it once the last in-flight tool ends, so a
26
+ // slow-but-live turn is never mistaken for a dead one.
27
+ export function resolveStreamWatchdogMs() {
28
+ const raw = process.env['CRTR_STREAM_WATCHDOG_MS'];
29
+ if (raw === undefined)
30
+ return DEFAULT_STREAM_WATCHDOG_MS;
31
+ const n = Number(raw);
32
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_STREAM_WATCHDOG_MS;
33
+ }
34
+ /**
35
+ * A single-timer dead-man's switch: `poke(token)` (re)arms it, `clear()`
36
+ * disarms it, and `onStall(token)` fires exactly once if `poke()` isn't called
37
+ * again within `ms`. The broker pokes it on relayed engine events while a turn
38
+ * is awaiting the provider and clears it at `agent_end` / tool execution — see
39
+ * broker.ts `relayEvent`.
40
+ *
41
+ * The `token` is an opaque generation stamp: each arm captures the token passed
42
+ * to its `poke()`, and `onStall` receives exactly that token. The broker bumps
43
+ * a session generation on every rebind, so a timer armed against a since-
44
+ * superseded session hands its stale generation to `onStall`, which no-ops it
45
+ * instead of aborting the live replacement session.
46
+ */
47
+ export class StreamWatchdog {
48
+ onStall;
49
+ timer;
50
+ ms;
51
+ constructor(onStall, ms) {
52
+ this.onStall = onStall;
53
+ this.ms = ms ?? resolveStreamWatchdogMs();
54
+ }
55
+ /** Proof of progress — (re)arm the timer for `token`, canceling any prior
56
+ * countdown. The fired `onStall` receives THIS arm's token. */
57
+ poke(token) {
58
+ this.clear();
59
+ this.timer = setTimeout(() => {
60
+ this.timer = undefined;
61
+ this.onStall(token);
62
+ }, this.ms);
63
+ // unref() so a live watchdog never keeps the broker process open on its own.
64
+ if (typeof this.timer.unref === 'function')
65
+ this.timer.unref();
66
+ }
67
+ /** Turn over (or broker tearing down) — no more relay expected until the
68
+ * next `poke()`. Idempotent. */
69
+ clear() {
70
+ if (this.timer === undefined)
71
+ return;
72
+ clearTimeout(this.timer);
73
+ this.timer = undefined;
74
+ }
75
+ }
@@ -1 +1 @@
1
- export { installMenuBinding, installNavBindings, installViewNavBindings, sendKeysEnter, displayMessage, paneCurrentPath } from './tmux.js';
1
+ export { installTmuxBindings, sendKeysEnter, displayMessage, paneCurrentPath, type TmuxInstalledPair, type TmuxInstallDiagnostic, type TmuxInstallResult, } from './tmux.js';
@@ -1,4 +1,4 @@
1
1
  // tmux-chrome.ts — chrome seam (§2.1): stateless keybind/input verbs.
2
2
  // The ONLY non-placement module allowed to import the tmux driver, per the
3
- // §5.1 lint. Re-exports the menu/nav/send-keys verbs callers (spawn, chord) need.
4
- export { installMenuBinding, installNavBindings, installViewNavBindings, sendKeysEnter, displayMessage, paneCurrentPath } from './tmux.js';
3
+ // §5.1 lint. Re-exports the server chrome callers need.
4
+ export { installTmuxBindings, sendKeysEnter, displayMessage, paneCurrentPath, } from './tmux.js';
@@ -1,3 +1,4 @@
1
+ import { type BindingId, type BindingResolution } from '../keybindings/index.js';
1
2
  /** POSIX single-quote escaping for one shell word. */
2
3
  export declare function shellQuote(s: string): string;
3
4
  export declare function inTmux(): boolean;
@@ -202,18 +203,20 @@ export declare function switchClient(session: string): boolean;
202
203
  * (`tmux send-keys -t <pane> '<text>' Enter`). Requires the pane's editor be
203
204
  * empty, same limitation as the menu's `/promote` item. Best-effort. */
204
205
  export declare function sendKeysEnter(pane: string, text: string): boolean;
205
- /** Bind Alt+C to the crouter action menu. Best-effort; false if tmux fails. */
206
- export declare function installMenuBinding(): boolean;
207
- /** Bind Alt+] (forward) and Alt+[ (back) to the DFS canvas walk. Best-effort;
208
- * false if either bind fails. NOTE: Alt+[ is only delivered cleanly when the
209
- * terminal/tmux disambiguate it from a raw CSI introducer (`extended-keys on`).
210
- */
211
- export declare function installNavBindings(): boolean;
212
- /** Bind Alt+V → (], [) to the view-monitor cycle. `M-v` enters the private
213
- * `crtr-view` key table (switch-client -T), then ] cycles next / [ cycles prev.
214
- * Best-effort; false if any of the three binds fail. Deliberately distinct from
215
- * installNavBindings' bare Alt+]/Alt+[ (node cycle): the two cycles coexist on
216
- * the same server — brackets alone walk the node graph, Alt+V-then-bracket flips
217
- * view monitors. The bracket keys are bound in the private table, so they NEVER
218
- * shadow the user's root ]/[ and carry no CSI-introducer ambiguity. */
219
- export declare function installViewNavBindings(): boolean;
206
+ export interface TmuxInstalledPair {
207
+ readonly table: string;
208
+ readonly key: string;
209
+ }
210
+ export interface TmuxInstallDiagnostic {
211
+ readonly code: 'occupied' | 'tmux-error';
212
+ readonly bindingId?: BindingId;
213
+ readonly gesture?: string;
214
+ readonly message: string;
215
+ }
216
+ export interface TmuxInstallResult {
217
+ readonly ok: boolean;
218
+ readonly installed: readonly TmuxInstalledPair[];
219
+ readonly diagnostics: readonly TmuxInstallDiagnostic[];
220
+ }
221
+ /** Reconcile the tmux server with one immutable crouter binding snapshot. */
222
+ export declare function installTmuxBindings(bindings?: BindingResolution<BindingId>): TmuxInstallResult;