@crouton-kit/crouter 0.3.70 → 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.
- package/README.md +1 -1
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +622 -724
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +231 -68
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +361 -90
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +3 -3
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +20 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +1 -1
- package/dist/clients/attach/attach-cmd.js +738 -735
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +11 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +127 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +9 -2
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/setup-core.d.ts +12 -1
- package/dist/commands/sys/setup-core.js +23 -3
- package/dist/commands/sys/setup.js +42 -12
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +164 -5
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/config.d.ts +53 -2
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +256 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +163 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +78 -20
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +47 -44
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/session-list-cache.d.ts +23 -0
- package/dist/core/runtime/session-list-cache.js +270 -0
- package/dist/core/runtime/spawn.js +51 -32
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +216 -110
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +38 -37
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/types.d.ts +3 -13
- package/dist/types.js +2 -26
- package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +3 -3
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
|
@@ -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 {
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
4
|
-
export {
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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;
|