@crouton-kit/crouter 0.3.82 → 0.3.84
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 +21 -4
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +104 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +17 -44
- package/dist/clients/attach/attach-cmd.d.ts +2 -3
- package/dist/clients/attach/attach-cmd.js +689 -689
- 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 +57 -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 +15 -17
- package/dist/commands/node.js +5 -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 +85 -0
- package/dist/commands/sys/setup-core.d.ts +10 -0
- package/dist/commands/sys/setup-core.js +39 -1
- package/dist/commands/sys/setup.js +10 -1
- package/dist/core/__tests__/boot.test.js +141 -6
- 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 +145 -0
- package/dist/core/__tests__/tmux-surface.test.js +180 -2
- 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 +45 -0
- package/dist/core/canvas/boot.js +36 -3
- package/dist/core/canvas/browse/app.js +3 -3
- package/dist/core/canvas/canvas.d.ts +40 -0
- package/dist/core/canvas/canvas.js +65 -1
- package/dist/core/canvas/pid.d.ts +86 -17
- package/dist/core/canvas/pid.js +152 -37
- 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__/inbox-affordance.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/inbox-affordance.test.js +38 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +2 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +4 -2
- package/dist/core/keybindings/inbox.d.ts +21 -0
- package/dist/core/keybindings/inbox.js +35 -0
- package/dist/core/keybindings/index.d.ts +1 -0
- package/dist/core/keybindings/index.js +1 -0
- package/dist/core/preview-registry.js +23 -29
- package/dist/core/profiles/select.js +4 -4
- 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/session-list-cache.d.ts +22 -2
- package/dist/core/runtime/session-list-cache.js +92 -24
- package/dist/core/runtime/spawn.js +2 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +1 -0
- package/dist/core/runtime/tmux.js +55 -1
- package/dist/core/spawn.d.ts +0 -59
- package/dist/core/spawn.js +5 -163
- package/dist/daemon/crtrd.js +36 -11
- 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.d.ts
CHANGED
|
@@ -14,62 +14,3 @@ export declare function isInTmux(): boolean;
|
|
|
14
14
|
* degrade to the inbox-drain follow-up. */
|
|
15
15
|
export declare function tmuxServerReachable(): boolean;
|
|
16
16
|
export declare function shellQuote(s: string): string;
|
|
17
|
-
/** Count panes in a tmux window (0 outside tmux / on error). With `targetPane`,
|
|
18
|
-
* counts the window THAT pane lives in (the placement decision must reflect the
|
|
19
|
-
* window the new pane will actually open into, not the caller's backstage one);
|
|
20
|
-
* without it, the caller's current window. */
|
|
21
|
-
export declare function countPanesInWindow(targetPane?: string): number;
|
|
22
|
-
/** Back-compat alias: panes in the caller's current window. */
|
|
23
|
-
export declare function countPanesInCurrentWindow(): number;
|
|
24
|
-
/** Does this tmux pane id still exist? `display-message` EXITS 0 with EMPTY
|
|
25
|
-
* output on an unresolvable pane, so test for non-empty stdout, not just `.ok`.
|
|
26
|
-
* False outside tmux / on error. */
|
|
27
|
-
export declare function paneAlive(pane: string): boolean;
|
|
28
|
-
/** Resolve a tmux pane id to its `session:window_index` — the target form
|
|
29
|
-
* `new-window -t` accepts. tmux REJECTS a pane id for new-window ("can't
|
|
30
|
-
* specify pane here"); only split-window -t takes a pane. null outside tmux /
|
|
31
|
-
* on a bad pane id / on error / empty. */
|
|
32
|
-
export declare function paneWindowTarget(pane: string): string | null;
|
|
33
|
-
/** The active pane of the user's attached tmux client — where they are looking
|
|
34
|
-
* right now. `list-clients` first attached client, then its current pane. Used
|
|
35
|
-
* to surface a human prompt in the user's view when nothing in the asking
|
|
36
|
-
* node's graph is focused. null outside tmux / no client / on error. */
|
|
37
|
-
export declare function attachedClientPane(): string | null;
|
|
38
|
-
/**
|
|
39
|
-
* Schedule a kill-pane on the *current* tmux pane after `delaySeconds`, detached
|
|
40
|
-
* so the caller can return normally before the pane dies. No-op outside tmux,
|
|
41
|
-
* when TMUX_PANE is unset, or when delaySeconds <= 0.
|
|
42
|
-
*/
|
|
43
|
-
export declare function scheduleKillCurrentPane(delaySeconds: number): boolean;
|
|
44
|
-
export interface DetachOptions {
|
|
45
|
-
/** Inner command to run in the new pane. */
|
|
46
|
-
command: string;
|
|
47
|
-
cwd: string;
|
|
48
|
-
/** Optional id injected as the CRTR_JOB_ID env var in the pane. */
|
|
49
|
-
jobId?: string;
|
|
50
|
-
/** Where to open the new pane. */
|
|
51
|
-
placement: 'split-h' | 'split-v' | 'new-window';
|
|
52
|
-
/** Seconds before killing the originating pane so the caller can finish. */
|
|
53
|
-
killAfterSeconds: number;
|
|
54
|
-
/** Pin the new pane to this tmux pane: split-window splits it; new-window is
|
|
55
|
-
* inserted immediately after its window (-a -t <pane>). Without this, tmux
|
|
56
|
-
* uses the attached client's currently-focused pane — which drifts if the
|
|
57
|
-
* user switches windows between kickoff and spawn. */
|
|
58
|
-
targetPane?: string;
|
|
59
|
-
/** Pass tmux `-d` to new-window so CREATING the window never switches the
|
|
60
|
-
* attached client to it (split-window already leaves the client's view put).
|
|
61
|
-
* The prompt lands in the target session/window without jumping the user out
|
|
62
|
-
* of what they are looking at. No effect on split-h/split-v. */
|
|
63
|
-
detached?: boolean;
|
|
64
|
-
}
|
|
65
|
-
export interface DetachResult {
|
|
66
|
-
status: 'spawned' | 'spawn-failed' | 'not-in-tmux';
|
|
67
|
-
paneId?: string;
|
|
68
|
-
message: string;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Fire-and-forget: launch `opts.command` in a new pane (or window), then
|
|
72
|
-
* schedule the originating pane to be killed after `killAfterSeconds`. Returns
|
|
73
|
-
* as soon as the new pane is up; does NOT wait for the command to finish.
|
|
74
|
-
*/
|
|
75
|
-
export declare function spawnAndDetach(opts: DetachOptions): DetachResult;
|
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
|
@@ -56,7 +56,7 @@ import { dirname, join } from 'node:path';
|
|
|
56
56
|
import { homedir } from 'node:os';
|
|
57
57
|
import { spawnSync } from 'node:child_process';
|
|
58
58
|
import { crtrHome, nodeDir, jobDir } from '../core/canvas/paths.js';
|
|
59
|
-
import { listNodes, getRow, getNode, dueClockTriggers, consumeTrigger, advanceTrigger, listFocuses, closeFocusRow, pruneToLimit, subscribersOf, } from '../core/canvas/index.js';
|
|
59
|
+
import { listNodes, getRow, getNode, migrateLegacyPidIdentities, dueClockTriggers, consumeTrigger, advanceTrigger, listFocuses, closeFocusRow, pruneToLimit, subscribersOf, } from '../core/canvas/index.js';
|
|
60
60
|
import { fullName } from '../core/canvas/labels.js';
|
|
61
61
|
import { transition } from '../core/runtime/lifecycle.js';
|
|
62
62
|
import { isBusy, busySince } from '../core/runtime/busy.js';
|
|
@@ -65,7 +65,7 @@ import { fanDoctrineWake } from '../core/runtime/close.js';
|
|
|
65
65
|
import { logger } from '../core/log.js';
|
|
66
66
|
export { FAULT_QUIET_MS };
|
|
67
67
|
import { isPidAlive, isRecordedPidAlive, recordedPidLiveness } from '../core/canvas/pid.js';
|
|
68
|
-
import {
|
|
68
|
+
import { reconcileAndMigrateBoot } from '../core/canvas/boot.js';
|
|
69
69
|
import { listLivePanes, tearDownNode } from '../core/runtime/placement.js';
|
|
70
70
|
import { reviveNode } from '../core/runtime/revive.js';
|
|
71
71
|
import { isBrokerLive } from '../core/runtime/model-swap.js';
|
|
@@ -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
|
|
@@ -1613,9 +1614,33 @@ export function runDaemon(opts = {}) {
|
|
|
1613
1614
|
// "leave it, nothing to revive" and never calls reviveNode at all — a
|
|
1614
1615
|
// pid-reuse collision on the first post-boot tick would silently no-op
|
|
1615
1616
|
// forever with no revive attempt to even hit reviveNode's own reconcile call.
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1617
|
+
//
|
|
1618
|
+
// Ordering + gate live in reconcileAndMigrateBoot (canvas/boot.ts) so the
|
|
1619
|
+
// migration-gate regression tests exercise the real composition rather than a
|
|
1620
|
+
// test-side copy of these lines.
|
|
1621
|
+
const { reconcile, migrated } = reconcileAndMigrateBoot(migrateLegacyPidIdentities);
|
|
1622
|
+
if (reconcile.changed) {
|
|
1623
|
+
process.stderr.write(`[crtrd] boot change detected — cleared ${reconcile.cleared.length} stale pi_pid(s) recorded before this boot\n`);
|
|
1624
|
+
}
|
|
1625
|
+
// Then, once, complete the boot-scoped-identity format migration for any
|
|
1626
|
+
// broker that survived an in-place crouter upgrade and still carries a legacy
|
|
1627
|
+
// `lstart#ticks` baseline: re-record it as `<bootId>#<ticks>` if its pid still
|
|
1628
|
+
// names the same live process. `identitiesMatch` only TOLERATES legacy rows;
|
|
1629
|
+
// this is what actually closes the cross-boot ticks-collision exposure for
|
|
1630
|
+
// them, so the legacy compare lane then only engages for genuinely no-boot_id
|
|
1631
|
+
// hosts.
|
|
1632
|
+
//
|
|
1633
|
+
// GATED on PROVEN same-boot provenance: the sweep blesses a legacy row's pid
|
|
1634
|
+
// as a current-boot process (`<currentBootId>#<ticks>`) on a ticks match, so
|
|
1635
|
+
// it is correct ONLY when the recorded pid provably belongs to THIS boot. We
|
|
1636
|
+
// run it solely when reconciliation matched an exact kernel `boot_id` on both
|
|
1637
|
+
// sides (`sameBootProven`) — never on a first-ever/absent/malformed marker or
|
|
1638
|
+
// the wall-clock epoch fallback, where an equal-ticks collision with an
|
|
1639
|
+
// unrelated cross-boot process could otherwise baptize a stranger. (On a
|
|
1640
|
+
// detected boot change the prior-boot pids were just cleared, so there is
|
|
1641
|
+
// nothing left to migrate either.)
|
|
1642
|
+
if (migrated.length > 0) {
|
|
1643
|
+
process.stderr.write(`[crtrd] migrated ${migrated.length} legacy pi_pid identity baseline(s) to boot-scoped format\n`);
|
|
1619
1644
|
}
|
|
1620
1645
|
writePidfile();
|
|
1621
1646
|
log.info(`started (pid ${process.pid}, interval ${interval}ms)`);
|
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';
|