@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.
Files changed (84) hide show
  1. package/dist/builtin-pi-packages/pi-mode-switch/README.md +3 -3
  2. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +12 -4
  3. package/dist/clients/attach/__tests__/attach-keybindings.test.js +7 -3
  4. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +104 -0
  5. package/dist/clients/attach/__tests__/crtr-output-render.test.js +16 -44
  6. package/dist/clients/attach/attach-cmd.d.ts +1 -2
  7. package/dist/clients/attach/attach-cmd.js +960 -961
  8. package/dist/clients/attach/attach-def.d.ts +2 -0
  9. package/dist/clients/attach/attach-def.js +93 -0
  10. package/dist/clients/attach/canvas-panels.d.ts +10 -3
  11. package/dist/clients/attach/canvas-panels.js +42 -16
  12. package/dist/clients/attach/chat-view.d.ts +20 -1
  13. package/dist/clients/attach/chat-view.js +85 -2
  14. package/dist/commands/__tests__/human.test.js +7 -22
  15. package/dist/commands/attention.js +31 -30
  16. package/dist/commands/human/prompts.js +51 -38
  17. package/dist/commands/human/queue.d.ts +1 -28
  18. package/dist/commands/human/queue.js +156 -520
  19. package/dist/commands/human/shared.d.ts +28 -68
  20. package/dist/commands/human/shared.js +37 -120
  21. package/dist/commands/human.js +13 -17
  22. package/dist/commands/node.js +4 -2
  23. package/dist/commands/surface.js +1 -1
  24. package/dist/commands/sys/__tests__/setup-core.test.js +1 -1
  25. package/dist/commands/sys/__tests__/setup-front-door.test.js +98 -0
  26. package/dist/commands/sys/setup-core.d.ts +22 -0
  27. package/dist/commands/sys/setup-core.js +62 -0
  28. package/dist/commands/sys/setup.js +8 -1
  29. package/dist/core/__tests__/human-deliver-e2e.test.js +95 -0
  30. package/dist/core/__tests__/human-deliver.test.js +332 -0
  31. package/dist/core/__tests__/human-node-not-supervised.test.js +5 -4
  32. package/dist/core/__tests__/pid-identity-match.test.js +83 -0
  33. package/dist/core/canvas/__tests__/attention.test.d.ts +1 -0
  34. package/dist/core/canvas/__tests__/attention.test.js +126 -0
  35. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +4 -4
  36. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  37. package/dist/core/canvas/attention.d.ts +16 -14
  38. package/dist/core/canvas/attention.js +34 -29
  39. package/dist/core/canvas/boot.d.ts +1 -0
  40. package/dist/core/canvas/boot.js +1 -1
  41. package/dist/core/canvas/browse/app.js +3 -3
  42. package/dist/core/canvas/pid.d.ts +43 -17
  43. package/dist/core/canvas/pid.js +104 -35
  44. package/dist/core/canvas/remote-canvas-source.d.ts +1 -1
  45. package/dist/core/canvas/remote-canvas-source.js +4 -2
  46. package/dist/core/canvas/render.d.ts +5 -5
  47. package/dist/core/canvas/render.js +14 -14
  48. package/dist/core/canvas/source.d.ts +2 -2
  49. package/dist/core/canvas/source.js +3 -3
  50. package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
  51. package/dist/core/keybindings/catalog.d.ts +2 -2
  52. package/dist/core/keybindings/catalog.js +3 -2
  53. package/dist/core/preview-registry.js +22 -29
  54. package/dist/core/profiles/select.js +4 -4
  55. package/dist/core/runtime/front-door.js +19 -6
  56. package/dist/core/runtime/package-health.d.ts +13 -0
  57. package/dist/core/runtime/package-health.js +18 -0
  58. package/dist/core/runtime/pi-vendored.js +15 -1
  59. package/dist/core/runtime/placement.d.ts +1 -10
  60. package/dist/core/runtime/placement.js +43 -26
  61. package/dist/core/runtime/resume.js +72 -4
  62. package/dist/core/runtime/session-list-cache.d.ts +22 -2
  63. package/dist/core/runtime/session-list-cache.js +92 -24
  64. package/dist/core/runtime/spawn.js +9 -0
  65. package/dist/core/spawn.d.ts +0 -59
  66. package/dist/core/spawn.js +5 -163
  67. package/dist/daemon/crtrd.js +7 -6
  68. package/dist/index.d.ts +2 -2
  69. package/dist/index.js +1 -1
  70. package/dist/web-client/assets/index-DZdKS-FB.js +78 -0
  71. package/dist/web-client/index.html +1 -1
  72. package/dist/web-client/sw.js +1 -1
  73. package/package.json +2 -2
  74. package/dist/core/__tests__/full/dead-pane-regression.test.js +0 -154
  75. package/dist/core/__tests__/full/human-new-window-regression.test.js +0 -104
  76. package/dist/core/__tests__/full/review-render-pane-regression.test.js +0 -133
  77. package/dist/core/__tests__/human-stranded-deliver.test.js +0 -145
  78. package/dist/core/__tests__/human-surface-target.test.js +0 -98
  79. package/dist/web-client/assets/index--md2ylfi.js +0 -78
  80. /package/dist/{core/__tests__/full/dead-pane-regression.test.d.ts → clients/attach/__tests__/chat-view-snapshot-ordering.test.d.ts} +0 -0
  81. /package/dist/{core/__tests__/full/human-new-window-regression.test.d.ts → commands/sys/__tests__/setup-front-door.test.d.ts} +0 -0
  82. /package/dist/core/__tests__/{full/review-render-pane-regression.test.d.ts → human-deliver-e2e.test.d.ts} +0 -0
  83. /package/dist/core/__tests__/{human-stranded-deliver.test.d.ts → human-deliver.test.d.ts} +0 -0
  84. /package/dist/core/__tests__/{human-surface-target.test.d.ts → pid-identity-match.test.d.ts} +0 -0
@@ -1,11 +1,9 @@
1
- // Tmux pane detach helpers.
1
+ // Tmux reachability + quoting helpers for the `human` command tree.
2
2
  //
3
- // A small set of tmux primitives used by the `human` command tree to put the
4
- // humanloop TUI in a detached pane: spawnAndDetach (open a pane running a given
5
- // command), countPanesInCurrentWindow (placement decision), plus shellQuote and
6
- // tmuxServerReachable. The canvas runtime has its own one-window-per-node
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
- }
@@ -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 lies to graphSurfaceTarget and blocks its node from
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. The decision/review TUI runs in a detached
1298
- // `crtr human _run` pane (NOT a pi engine), and the row's lifecycle is driven
1299
- // entirely by that worker's `pushFinal` (or `human cancel`). They never record
1300
- // a `pi_pid` or `pi_session_id`, so the universal broker-liveness path would
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 { asksForNodes, asksAcrossCanvas } from './core/canvas/attention.js';
14
- export type { AskEntry } from './core/canvas/attention.js';
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 { asksForNodes, asksAcrossCanvas } from './core/canvas/attention.js';
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';