@crouton-kit/crouter 0.3.81 → 0.3.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +3 -3
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +12 -4
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +7 -3
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +104 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +16 -44
- package/dist/clients/attach/attach-cmd.d.ts +1 -2
- package/dist/clients/attach/attach-cmd.js +960 -961
- package/dist/clients/attach/attach-def.d.ts +2 -0
- package/dist/clients/attach/attach-def.js +93 -0
- package/dist/clients/attach/canvas-panels.d.ts +10 -3
- package/dist/clients/attach/canvas-panels.js +42 -16
- package/dist/clients/attach/chat-view.d.ts +20 -1
- package/dist/clients/attach/chat-view.js +85 -2
- package/dist/commands/__tests__/human.test.js +7 -22
- package/dist/commands/attention.js +31 -30
- package/dist/commands/human/prompts.js +51 -38
- package/dist/commands/human/queue.d.ts +1 -28
- package/dist/commands/human/queue.js +156 -520
- package/dist/commands/human/shared.d.ts +28 -68
- package/dist/commands/human/shared.js +37 -120
- package/dist/commands/human.js +13 -17
- package/dist/commands/node.js +4 -2
- package/dist/commands/surface.js +1 -1
- package/dist/commands/sys/__tests__/setup-core.test.js +1 -1
- package/dist/commands/sys/__tests__/setup-front-door.test.js +98 -0
- package/dist/commands/sys/setup-core.d.ts +22 -0
- package/dist/commands/sys/setup-core.js +62 -0
- package/dist/commands/sys/setup.js +8 -1
- package/dist/core/__tests__/human-deliver-e2e.test.js +95 -0
- package/dist/core/__tests__/human-deliver.test.js +332 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +5 -4
- package/dist/core/__tests__/pid-identity-match.test.js +83 -0
- package/dist/core/canvas/__tests__/attention.test.d.ts +1 -0
- package/dist/core/canvas/__tests__/attention.test.js +126 -0
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +4 -4
- package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
- package/dist/core/canvas/attention.d.ts +16 -14
- package/dist/core/canvas/attention.js +34 -29
- package/dist/core/canvas/boot.d.ts +1 -0
- package/dist/core/canvas/boot.js +1 -1
- package/dist/core/canvas/browse/app.js +3 -3
- package/dist/core/canvas/pid.d.ts +43 -17
- package/dist/core/canvas/pid.js +104 -35
- package/dist/core/canvas/remote-canvas-source.d.ts +1 -1
- package/dist/core/canvas/remote-canvas-source.js +4 -2
- package/dist/core/canvas/render.d.ts +5 -5
- package/dist/core/canvas/render.js +14 -14
- package/dist/core/canvas/source.d.ts +2 -2
- package/dist/core/canvas/source.js +3 -3
- package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +3 -2
- package/dist/core/preview-registry.js +22 -29
- package/dist/core/profiles/select.js +4 -4
- package/dist/core/runtime/front-door.js +19 -6
- package/dist/core/runtime/package-health.d.ts +13 -0
- package/dist/core/runtime/package-health.js +18 -0
- package/dist/core/runtime/pi-vendored.js +15 -1
- package/dist/core/runtime/placement.d.ts +1 -10
- package/dist/core/runtime/placement.js +43 -26
- package/dist/core/runtime/resume.js +72 -4
- package/dist/core/runtime/session-list-cache.d.ts +22 -2
- package/dist/core/runtime/session-list-cache.js +92 -24
- package/dist/core/runtime/spawn.js +9 -0
- package/dist/core/spawn.d.ts +0 -59
- package/dist/core/spawn.js +5 -163
- package/dist/daemon/crtrd.js +7 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/web-client/assets/index-DZdKS-FB.js +78 -0
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +2 -2
- package/dist/core/__tests__/full/dead-pane-regression.test.js +0 -154
- package/dist/core/__tests__/full/human-new-window-regression.test.js +0 -104
- package/dist/core/__tests__/full/review-render-pane-regression.test.js +0 -133
- package/dist/core/__tests__/human-stranded-deliver.test.js +0 -145
- package/dist/core/__tests__/human-surface-target.test.js +0 -98
- package/dist/web-client/assets/index--md2ylfi.js +0 -78
- /package/dist/{core/__tests__/full/dead-pane-regression.test.d.ts → clients/attach/__tests__/chat-view-snapshot-ordering.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/human-new-window-regression.test.d.ts → commands/sys/__tests__/setup-front-door.test.d.ts} +0 -0
- /package/dist/core/__tests__/{full/review-render-pane-regression.test.d.ts → human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-stranded-deliver.test.d.ts → human-deliver.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-surface-target.test.d.ts → pid-identity-match.test.d.ts} +0 -0
package/dist/core/spawn.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
// Tmux
|
|
1
|
+
// Tmux reachability + quoting helpers for the `human` command tree.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// machinery in core/runtime/tmux.ts; this module is only the pane-split path the
|
|
8
|
-
// human TUI needs.
|
|
3
|
+
// The human surface gates on whether the canvas tmux server is reachable
|
|
4
|
+
// (tmuxServerReachable) so it can decide whether to surface a prompt or degrade
|
|
5
|
+
// to the inbox-drain follow-up. The canvas runtime has its own
|
|
6
|
+
// one-window-per-node machinery in core/runtime/tmux.ts.
|
|
9
7
|
import { spawnSync } from 'node:child_process';
|
|
10
8
|
/** Does THIS process sit inside a tmux client (its own $TMUX is set)? NOT the
|
|
11
9
|
* gate for the human surface — see tmuxServerReachable. A headless-broker child
|
|
@@ -30,159 +28,3 @@ export function tmuxServerReachable() {
|
|
|
30
28
|
export function shellQuote(s) {
|
|
31
29
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
32
30
|
}
|
|
33
|
-
/** Count panes in a tmux window (0 outside tmux / on error). With `targetPane`,
|
|
34
|
-
* counts the window THAT pane lives in (the placement decision must reflect the
|
|
35
|
-
* window the new pane will actually open into, not the caller's backstage one);
|
|
36
|
-
* without it, the caller's current window. */
|
|
37
|
-
export function countPanesInWindow(targetPane) {
|
|
38
|
-
const args = targetPane !== undefined && targetPane !== ''
|
|
39
|
-
? ['list-panes', '-t', targetPane, '-F', '#{pane_id}']
|
|
40
|
-
: ['list-panes', '-F', '#{pane_id}'];
|
|
41
|
-
const result = spawnSync('tmux', args, { encoding: 'utf8' });
|
|
42
|
-
if (result.status !== 0)
|
|
43
|
-
return 0;
|
|
44
|
-
return result.stdout.split('\n').filter((line) => line.trim() !== '').length;
|
|
45
|
-
}
|
|
46
|
-
/** Back-compat alias: panes in the caller's current window. */
|
|
47
|
-
export function countPanesInCurrentWindow() {
|
|
48
|
-
return countPanesInWindow();
|
|
49
|
-
}
|
|
50
|
-
/** Does this tmux pane id still exist? `display-message` EXITS 0 with EMPTY
|
|
51
|
-
* output on an unresolvable pane, so test for non-empty stdout, not just `.ok`.
|
|
52
|
-
* False outside tmux / on error. */
|
|
53
|
-
export function paneAlive(pane) {
|
|
54
|
-
if (!/^%\d+$/.test(pane))
|
|
55
|
-
return false;
|
|
56
|
-
const r = spawnSync('tmux', ['display-message', '-p', '-t', pane, '#{pane_id}'], {
|
|
57
|
-
encoding: 'utf8',
|
|
58
|
-
});
|
|
59
|
-
return r.status === 0 && r.stdout.trim() !== '';
|
|
60
|
-
}
|
|
61
|
-
/** Resolve a tmux pane id to its `session:window_index` — the target form
|
|
62
|
-
* `new-window -t` accepts. tmux REJECTS a pane id for new-window ("can't
|
|
63
|
-
* specify pane here"); only split-window -t takes a pane. null outside tmux /
|
|
64
|
-
* on a bad pane id / on error / empty. */
|
|
65
|
-
export function paneWindowTarget(pane) {
|
|
66
|
-
if (!/^%\d+$/.test(pane))
|
|
67
|
-
return null;
|
|
68
|
-
const r = spawnSync('tmux', ['display-message', '-p', '-t', pane, '#{session_name}:#{window_index}'], { encoding: 'utf8' });
|
|
69
|
-
if (r.status !== 0)
|
|
70
|
-
return null;
|
|
71
|
-
const t = r.stdout.trim();
|
|
72
|
-
return t !== '' ? t : null;
|
|
73
|
-
}
|
|
74
|
-
/** The active pane of the user's attached tmux client — where they are looking
|
|
75
|
-
* right now. `list-clients` first attached client, then its current pane. Used
|
|
76
|
-
* to surface a human prompt in the user's view when nothing in the asking
|
|
77
|
-
* node's graph is focused. null outside tmux / no client / on error. */
|
|
78
|
-
export function attachedClientPane() {
|
|
79
|
-
const clients = spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], {
|
|
80
|
-
encoding: 'utf8',
|
|
81
|
-
});
|
|
82
|
-
if (clients.status !== 0)
|
|
83
|
-
return null;
|
|
84
|
-
const name = clients.stdout
|
|
85
|
-
.split('\n')
|
|
86
|
-
.map((l) => l.trim())
|
|
87
|
-
.find((l) => l !== '');
|
|
88
|
-
if (name === undefined)
|
|
89
|
-
return null;
|
|
90
|
-
const pane = spawnSync('tmux', ['display-message', '-p', '-c', name, '#{pane_id}'], {
|
|
91
|
-
encoding: 'utf8',
|
|
92
|
-
});
|
|
93
|
-
if (pane.status !== 0)
|
|
94
|
-
return null;
|
|
95
|
-
const id = pane.stdout.trim();
|
|
96
|
-
return id !== '' ? id : null;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Schedule a kill-pane on the *current* tmux pane after `delaySeconds`, detached
|
|
100
|
-
* so the caller can return normally before the pane dies. No-op outside tmux,
|
|
101
|
-
* when TMUX_PANE is unset, or when delaySeconds <= 0.
|
|
102
|
-
*/
|
|
103
|
-
export function scheduleKillCurrentPane(delaySeconds) {
|
|
104
|
-
const currentPane = process.env.TMUX_PANE;
|
|
105
|
-
if (currentPane === undefined || currentPane === '' || delaySeconds <= 0) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
const killCmd = `sleep ${delaySeconds}; tmux kill-pane -t ${currentPane}`;
|
|
109
|
-
spawnSync('sh', ['-c', `nohup sh -c ${shellQuote(killCmd)} </dev/null >/dev/null 2>&1 &`], {
|
|
110
|
-
stdio: 'ignore',
|
|
111
|
-
});
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Fire-and-forget: launch `opts.command` in a new pane (or window), then
|
|
116
|
-
* schedule the originating pane to be killed after `killAfterSeconds`. Returns
|
|
117
|
-
* as soon as the new pane is up; does NOT wait for the command to finish.
|
|
118
|
-
*/
|
|
119
|
-
export function spawnAndDetach(opts) {
|
|
120
|
-
// Gate on the CANVAS tmux server's reachability, NOT the caller's $TMUX. A
|
|
121
|
-
// headless-broker child strips its own $TMUX (broker.ts) but the canvas server
|
|
122
|
-
// is on the default socket, so `split-window -t <pane>` against a resolved
|
|
123
|
-
// target pane (resolveHumanTarget) works fine from it. Only a genuine
|
|
124
|
-
// no-server case degrades to the inbox-drain follow-up.
|
|
125
|
-
if (!tmuxServerReachable()) {
|
|
126
|
-
return {
|
|
127
|
-
status: 'not-in-tmux',
|
|
128
|
-
message: 'handoff requires a reachable tmux server',
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
const splitArgs = [];
|
|
132
|
-
if (opts.placement === 'new-window') {
|
|
133
|
-
splitArgs.push('new-window');
|
|
134
|
-
if (opts.detached === true)
|
|
135
|
-
splitArgs.push('-d'); // don't switch the client to it
|
|
136
|
-
if (opts.targetPane !== undefined && opts.targetPane !== '') {
|
|
137
|
-
// new-window -t REJECTS a pane id (tmux exits 1: "can't specify pane
|
|
138
|
-
// here") — only split-window -t accepts a pane. Resolve the target pane to
|
|
139
|
-
// its session:window first; -a then inserts the new window right after it.
|
|
140
|
-
// If the pane can't be resolved, fall back to no -t (tmux uses current).
|
|
141
|
-
const winTarget = paneWindowTarget(opts.targetPane);
|
|
142
|
-
if (winTarget !== null)
|
|
143
|
-
splitArgs.push('-a', '-t', winTarget);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
splitArgs.push('split-window');
|
|
148
|
-
splitArgs.push(opts.placement === 'split-h' ? '-h' : '-v');
|
|
149
|
-
if (opts.targetPane !== undefined && opts.targetPane !== '') {
|
|
150
|
-
splitArgs.push('-t', opts.targetPane);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
splitArgs.push('-P', '-F', '#{pane_id}');
|
|
154
|
-
splitArgs.push('-c', opts.cwd);
|
|
155
|
-
if (opts.jobId !== undefined) {
|
|
156
|
-
splitArgs.push('-e', `CRTR_JOB_ID=${opts.jobId}`);
|
|
157
|
-
}
|
|
158
|
-
splitArgs.push(opts.command);
|
|
159
|
-
const split = spawnSync('tmux', splitArgs, { encoding: 'utf8' });
|
|
160
|
-
if (split.status !== 0) {
|
|
161
|
-
const stderrText = split.stderr.trim();
|
|
162
|
-
const msg = stderrText === '' ? 'tmux split-window/new-window failed' : stderrText;
|
|
163
|
-
return { status: 'spawn-failed', message: msg };
|
|
164
|
-
}
|
|
165
|
-
const paneId = split.stdout.trim();
|
|
166
|
-
// Force `remain-on-exit off` at PANE scope on the new pane. remain-on-exit is
|
|
167
|
-
// a pane option (tmux 3.x) inherited from the window-scoped value, and the
|
|
168
|
-
// canvas runtime arms `remain-on-exit on` on a node's vehicle/focus WINDOW
|
|
169
|
-
// (F3 freeze, see runtime/tmux.ts setRemainOnExit). A split-window pane opened
|
|
170
|
-
// into that window inherits the `on`, so the humanloop TUI pane would linger
|
|
171
|
-
// as a dead pane ("pane is dead (status 0, …)") when `crtr human _run` exits 0
|
|
172
|
-
// instead of closing. Overriding at pane scope destroys this pane on clean
|
|
173
|
-
// exit WITHOUT touching the window's value (focus freeze still works) or the
|
|
174
|
-
// user's global config. Best-effort: harmless no-op on tmux where the option
|
|
175
|
-
// is window-only.
|
|
176
|
-
if (paneId !== '') {
|
|
177
|
-
spawnSync('tmux', ['set-option', '-p', '-t', paneId, 'remain-on-exit', 'off'], {
|
|
178
|
-
stdio: 'ignore',
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
// Schedule self-kill of the originating pane.
|
|
182
|
-
scheduleKillCurrentPane(opts.killAfterSeconds);
|
|
183
|
-
return {
|
|
184
|
-
status: 'spawned',
|
|
185
|
-
paneId,
|
|
186
|
-
message: `handed off to pane ${paneId}`,
|
|
187
|
-
};
|
|
188
|
-
}
|
package/dist/daemon/crtrd.js
CHANGED
|
@@ -1205,8 +1205,8 @@ function reapDeadResidue(now) {
|
|
|
1205
1205
|
* row when its pane dies outside the sanctioned teardown paths (a user
|
|
1206
1206
|
* kill-pane, a tmux server restart, a crashed swap), so dead-pane rows
|
|
1207
1207
|
* accumulate forever — the audit found 15 of 21 rows pointing at gone panes,
|
|
1208
|
-
* and a stale row both
|
|
1209
|
-
* being adopted into a fresh viewport (UNIQUE node_id). Sweep them here: one
|
|
1208
|
+
* and a stale row both misreports the node's on-screen viewer and blocks its
|
|
1209
|
+
* node from being adopted into a fresh viewport (UNIQUE node_id). Sweep them here: one
|
|
1210
1210
|
* batched `list-panes -a` probe (never a per-row display-message), deleting
|
|
1211
1211
|
* rows whose recorded pane no longer exists. A pane-less row (a bridge/unplaced
|
|
1212
1212
|
* viewport) has nothing to verify and is left alone. On a FAILED probe (tmux
|
|
@@ -1294,10 +1294,11 @@ export async function superviseTick(now = Date.now()) {
|
|
|
1294
1294
|
return;
|
|
1295
1295
|
}
|
|
1296
1296
|
// kind:'human' rows are NOT broker-hosted agent nodes — they are bookkeeping
|
|
1297
|
-
// rows for the `crtr human` bridge.
|
|
1298
|
-
//
|
|
1299
|
-
//
|
|
1300
|
-
// a `pi_pid` or `pi_session_id`, so the universal broker-liveness
|
|
1297
|
+
// rows for the `crtr human` bridge. Their lifecycle is driven entirely by
|
|
1298
|
+
// humanloop's registered `_deliver` handler — which pushFinals the answer when
|
|
1299
|
+
// the human responds from the humanloop inbox — or by `human cancel`. They
|
|
1300
|
+
// never record a `pi_pid` or `pi_session_id`, so the universal broker-liveness
|
|
1301
|
+
// path would
|
|
1301
1302
|
// (after the boot grace) read every one as a never-booted broker and crash it
|
|
1302
1303
|
// with surfaceBootFailure — exactly the regression where a headless
|
|
1303
1304
|
// orchestrator's `human ask` nodes all died at boot. The daemon must never
|
package/dist/index.d.ts
CHANGED
|
@@ -10,8 +10,8 @@ export { appendSituationalContext } from './core/runtime/situational-context.js'
|
|
|
10
10
|
export { getNode, listNodes } from './core/canvas/canvas.js';
|
|
11
11
|
export { nodeDir } from './core/canvas/paths.js';
|
|
12
12
|
export type { NodeMeta, NodeRow, NodeStatus, Lifecycle, Mode, } from './core/canvas/types.js';
|
|
13
|
-
export {
|
|
14
|
-
export type {
|
|
13
|
+
export { ticketCountsForNodes, ticketsAcrossCanvas } from './core/canvas/attention.js';
|
|
14
|
+
export type { TicketEntry } from './core/canvas/attention.js';
|
|
15
15
|
export { readTelemetry, readContextTokens } from './core/canvas/telemetry.js';
|
|
16
16
|
export type { Telemetry } from './core/canvas/telemetry.js';
|
|
17
17
|
export { ViewSocketClient, BrokerUnavailableError } from './clients/attach/view-socket.js';
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { appendSituationalContext } from './core/runtime/situational-context.js'
|
|
|
23
23
|
// ── Canvas reads ─────────────────────────────────────────────────────────
|
|
24
24
|
export { getNode, listNodes } from './core/canvas/canvas.js';
|
|
25
25
|
export { nodeDir } from './core/canvas/paths.js';
|
|
26
|
-
export {
|
|
26
|
+
export { ticketCountsForNodes, ticketsAcrossCanvas } from './core/canvas/attention.js';
|
|
27
27
|
export { readTelemetry, readContextTokens } from './core/canvas/telemetry.js';
|
|
28
28
|
// ── Broker client ────────────────────────────────────────────────────────
|
|
29
29
|
export { ViewSocketClient, BrokerUnavailableError } from './clients/attach/view-socket.js';
|