@aragon-agent/cli 0.6.3 → 0.6.7
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/CHANGELOG.md +142 -0
- package/README.md +203 -13
- package/dist/agent/controller.d.ts +158 -2
- package/dist/agent/controller.js +400 -1
- package/dist/agent/reducer.d.ts +102 -19
- package/dist/agent/reducer.js +162 -71
- package/dist/agent/system-prompt.d.ts +14 -0
- package/dist/agent/system-prompt.js +2 -0
- package/dist/agent/usage.d.ts +34 -0
- package/dist/agent/usage.js +43 -0
- package/dist/cli.js +132 -23
- package/dist/commands/builtins.js +101 -1
- package/dist/compaction/child.js +5 -0
- package/dist/compaction/command.js +14 -1
- package/dist/compaction/compactor.d.ts +94 -24
- package/dist/compaction/compactor.js +247 -98
- package/dist/compaction/context-command.d.ts +33 -0
- package/dist/compaction/context-command.js +166 -0
- package/dist/compaction/meter.d.ts +251 -0
- package/dist/compaction/meter.js +428 -0
- package/dist/compaction/pressure.d.ts +6 -0
- package/dist/compaction/pressure.js +4 -0
- package/dist/compaction/types.d.ts +77 -0
- package/dist/compaction/wiring.d.ts +56 -0
- package/dist/compaction/wiring.js +107 -25
- package/dist/config/cli-commands.js +5 -0
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +63 -2
- package/dist/config/load.d.ts +28 -0
- package/dist/config/load.js +83 -1
- package/dist/config/schema.d.ts +129 -0
- package/dist/config/schema.js +69 -0
- package/dist/config/store.js +47 -1
- package/dist/exec/events.d.ts +138 -1
- package/dist/exec/events.js +42 -0
- package/dist/exec/index.js +43 -6
- package/dist/exec/progress-reporter.d.ts +23 -0
- package/dist/exec/progress-reporter.js +88 -0
- package/dist/exec/runner.d.ts +34 -0
- package/dist/exec/runner.js +137 -25
- package/dist/fast/reviewer.d.ts +19 -1
- package/dist/fast/reviewer.js +22 -0
- package/dist/fast/types.d.ts +15 -0
- package/dist/fast/wiring.d.ts +10 -1
- package/dist/fast/wiring.js +18 -1
- package/dist/input/limits.d.ts +72 -0
- package/dist/input/limits.js +63 -0
- package/dist/input/paste-parse.d.ts +71 -0
- package/dist/input/paste-parse.js +95 -0
- package/dist/input/stdin-filter.d.ts +81 -0
- package/dist/input/stdin-filter.js +379 -0
- package/dist/logging/install.d.ts +2 -0
- package/dist/logging/install.js +53 -1
- package/dist/proc/classify.d.ts +52 -0
- package/dist/proc/classify.js +143 -0
- package/dist/proc/kill-tree.d.ts +42 -0
- package/dist/proc/kill-tree.js +119 -0
- package/dist/proc/limits.d.ts +84 -0
- package/dist/proc/limits.js +84 -0
- package/dist/proc/log-ring.d.ts +68 -0
- package/dist/proc/log-ring.js +102 -0
- package/dist/proc/prompt.d.ts +24 -0
- package/dist/proc/prompt.js +49 -0
- package/dist/proc/readiness.d.ts +102 -0
- package/dist/proc/readiness.js +219 -0
- package/dist/proc/supervisor.d.ts +103 -0
- package/dist/proc/supervisor.js +465 -0
- package/dist/proc/types.d.ts +175 -0
- package/dist/proc/types.js +15 -0
- package/dist/session/persist.js +36 -1
- package/dist/todo/limits.d.ts +4 -17
- package/dist/todo/limits.js +4 -17
- package/dist/todo/panel-rows.d.ts +8 -6
- package/dist/todo/panel-rows.js +19 -6
- package/dist/tools/bash-tool.d.ts +51 -2
- package/dist/tools/bash-tool.js +321 -51
- package/dist/tools/fs-tools.d.ts +17 -0
- package/dist/tools/index.d.ts +55 -2
- package/dist/tools/index.js +39 -1
- package/dist/tools/proc-tools.d.ts +25 -0
- package/dist/tools/proc-tools.js +102 -0
- package/dist/ui/App.d.ts +15 -2
- package/dist/ui/App.js +330 -69
- package/dist/ui/AutocompletePopup.d.ts +3 -1
- package/dist/ui/AutocompletePopup.js +9 -11
- package/dist/ui/BottomStatusRow.d.ts +1 -1
- package/dist/ui/Composer.d.ts +56 -1
- package/dist/ui/Composer.js +50 -6
- package/dist/ui/PromptInput.d.ts +52 -3
- package/dist/ui/PromptInput.js +201 -55
- package/dist/ui/StatusBar.d.ts +40 -15
- package/dist/ui/StatusBar.js +51 -9
- package/dist/ui/TeamPanel.d.ts +3 -1
- package/dist/ui/TeamPanel.js +13 -9
- package/dist/ui/TodoPanel.d.ts +5 -5
- package/dist/ui/TodoPanel.js +56 -24
- package/dist/ui/Transcript.js +29 -1
- package/dist/ui/UpdateLine.d.ts +1 -1
- package/dist/ui/capabilities.js +5 -5
- package/dist/ui/composer-limits.d.ts +31 -0
- package/dist/ui/composer-limits.js +50 -0
- package/dist/ui/composer-rows.d.ts +73 -0
- package/dist/ui/composer-rows.js +201 -0
- package/dist/ui/editor-reducer.d.ts +37 -0
- package/dist/ui/editor-reducer.js +88 -12
- package/dist/ui/entries/ServiceCard.d.ts +66 -0
- package/dist/ui/entries/ServiceCard.js +116 -0
- package/dist/ui/entries/UserEntry.d.ts +17 -3
- package/dist/ui/entries/UserEntry.js +34 -5
- package/dist/ui/exit-snapshot.d.ts +9 -0
- package/dist/ui/layout/ScrollViewport.d.ts +1 -1
- package/dist/ui/layout/autocomplete.d.ts +18 -0
- package/dist/ui/layout/autocomplete.js +26 -0
- package/dist/ui/layout/budget.d.ts +34 -6
- package/dist/ui/layout/budget.js +32 -7
- package/dist/ui/layout/rail.d.ts +4 -53
- package/dist/ui/layout/rail.js +10 -62
- package/dist/ui/layout/team-panel.d.ts +18 -0
- package/dist/ui/layout/team-panel.js +22 -0
- package/dist/ui/layout/todo-layout.d.ts +24 -0
- package/dist/ui/layout/todo-layout.js +27 -0
- package/dist/ui/layout/virtual-window.js +51 -1
- package/dist/ui/overlays/HelpOverlay.js +3 -0
- package/dist/ui/overlays/PlanReviewOverlay.js +10 -2
- package/dist/ui/overlays/QuestionOverlay.js +5 -1
- package/dist/ui/overlays/SettingsScreen.js +14 -1
- package/dist/ui/paste-frames.d.ts +87 -0
- package/dist/ui/paste-frames.js +112 -0
- package/dist/ui/paste-tokens.d.ts +90 -0
- package/dist/ui/paste-tokens.js +147 -0
- package/dist/ui/screen.d.ts +16 -0
- package/dist/ui/screen.js +45 -2
- package/dist/ui/selection/selection-controller.d.ts +1 -1
- package/dist/ui/transcript-text.js +55 -2
- package/dist/ui/use-wheel-routing.d.ts +1 -1
- package/package.json +2 -2
- package/dist/input/stdin-mouse-filter.d.ts +0 -47
- package/dist/input/stdin-mouse-filter.js +0 -153
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readiness detection (background-service-supervision §3.5).
|
|
3
|
+
*
|
|
4
|
+
* Two detectors, raced, first one wins:
|
|
5
|
+
*
|
|
6
|
+
* - the URL detector, which is PURE and therefore testable without a socket;
|
|
7
|
+
* - the port probe, which is one TCP connect and only runs when the command
|
|
8
|
+
* text named a port.
|
|
9
|
+
*
|
|
10
|
+
* ASCII ONLY: `src/proc/**` is inside the glyph scanner's scope.
|
|
11
|
+
*
|
|
12
|
+
* EVERY TIMER AND SOCKET HERE IS `unref()`d (I-3). Readiness detection must
|
|
13
|
+
* never be the reason the CLI does not exit; only the child processes themselves
|
|
14
|
+
* hold the loop open, and they are reaped on exit.
|
|
15
|
+
*/
|
|
16
|
+
import net from 'node:net';
|
|
17
|
+
import { PROC_LIMITS } from './limits.js';
|
|
18
|
+
/**
|
|
19
|
+
* A loopback/wildcard `http(s)` URL, with an optional port and path.
|
|
20
|
+
*
|
|
21
|
+
* ONE ANCHORED SOURCE OF TRUTH, and written out rather than assembled, because
|
|
22
|
+
* the obvious shorthand is not a regex at all: `[::1]` inside a pattern is a
|
|
23
|
+
* CHARACTER CLASS matching `:` or `1`, and unescaped dots match any character.
|
|
24
|
+
* Both mistakes read as correct.
|
|
25
|
+
*/
|
|
26
|
+
export const READY_URL_RE = /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]|::1)(?::(\d{1,5}))?(\/\S*)?/i;
|
|
27
|
+
/**
|
|
28
|
+
* Servers that announce a bare port instead of a URL.
|
|
29
|
+
*
|
|
30
|
+
* A SEPARATE, SMALLER LIST rather than more alternation in the URL pattern: what
|
|
31
|
+
* these produce is a PORT and no URL, which is a different result shape and a
|
|
32
|
+
* different confirmation path (the probe must succeed before anything flips).
|
|
33
|
+
*/
|
|
34
|
+
const PORT_PHRASES = Object.freeze([
|
|
35
|
+
/listening on (?:port )?(\d{2,5})\b/i,
|
|
36
|
+
/server running at .*?:(\d{2,5})\b/i,
|
|
37
|
+
/started server on .*?:(\d{2,5})\b/i,
|
|
38
|
+
/\bLocal:\s+\S*?:(\d{2,5})\b/i,
|
|
39
|
+
]);
|
|
40
|
+
/**
|
|
41
|
+
* Normalise a wildcard/IPv6-loopback host to something clickable.
|
|
42
|
+
*
|
|
43
|
+
* `0.0.0.0` means "every interface", which a browser cannot open; `::1` and
|
|
44
|
+
* `[::1]` are correct but unfamiliar. All three become `127.0.0.1`.
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeHost(url) {
|
|
47
|
+
return url
|
|
48
|
+
.replace(/:\/\/0\.0\.0\.0/i, '://127.0.0.1')
|
|
49
|
+
.replace(/:\/\/\[::1\]/i, '://127.0.0.1')
|
|
50
|
+
.replace(/:\/\/::1/i, '://127.0.0.1');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The first readiness candidate in `rows`, or `undefined`.
|
|
54
|
+
*
|
|
55
|
+
* A MATCH IS A CANDIDATE, NOT A VERDICT (P2-2). A loopback URL printed in a
|
|
56
|
+
* banner or a `--help` blurb appears BEFORE anything binds, and a bare pattern
|
|
57
|
+
* would call that service ready in the first 40 ms. Two guards make the trade
|
|
58
|
+
* explicit, and neither lives here:
|
|
59
|
+
*
|
|
60
|
+
* 1. only rows emitted AFTER spawn are scanned (the ring starts empty, so this
|
|
61
|
+
* is free), and
|
|
62
|
+
* 2. when the match yields a PORT, `ReadinessWatcher` confirms with one TCP
|
|
63
|
+
* connect before the status flips.
|
|
64
|
+
*
|
|
65
|
+
* When the match yields no port the URL is accepted as-is: there is nothing to
|
|
66
|
+
* probe, and a hostname-only loopback URL in a banner is rare enough to trade
|
|
67
|
+
* against the complexity of guessing 80 vs 443.
|
|
68
|
+
*/
|
|
69
|
+
export function detectReadyUrl(rows) {
|
|
70
|
+
for (const row of rows) {
|
|
71
|
+
const m = READY_URL_RE.exec(row);
|
|
72
|
+
if (m) {
|
|
73
|
+
const url = normalizeHost(m[0]);
|
|
74
|
+
const port = m[1] ? Number.parseInt(m[1], 10) : undefined;
|
|
75
|
+
return port === undefined ? { url } : { url, port };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const row of rows) {
|
|
79
|
+
for (const re of PORT_PHRASES) {
|
|
80
|
+
const m = re.exec(row);
|
|
81
|
+
if (!m || !m[1])
|
|
82
|
+
continue;
|
|
83
|
+
const port = Number.parseInt(m[1], 10);
|
|
84
|
+
if (Number.isFinite(port) && port > 0 && port <= 65535)
|
|
85
|
+
return { port };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* One TCP connect. Resolves `true` when something answered.
|
|
92
|
+
*
|
|
93
|
+
* Never rejects, and the socket is destroyed on every path — a probe that leaked
|
|
94
|
+
* a socket would hold the event loop open exactly as long as the service runs.
|
|
95
|
+
*/
|
|
96
|
+
export function probePort(port, host = '127.0.0.1', timeoutMs = PROC_LIMITS.probeSocketTimeoutMs) {
|
|
97
|
+
return new Promise((resolve) => {
|
|
98
|
+
let settled = false;
|
|
99
|
+
const done = (answered) => {
|
|
100
|
+
if (settled)
|
|
101
|
+
return;
|
|
102
|
+
settled = true;
|
|
103
|
+
try {
|
|
104
|
+
socket.destroy();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* ignore */
|
|
108
|
+
}
|
|
109
|
+
resolve(answered);
|
|
110
|
+
};
|
|
111
|
+
const socket = new net.Socket();
|
|
112
|
+
socket.setTimeout(timeoutMs);
|
|
113
|
+
socket.once('connect', () => done(true));
|
|
114
|
+
socket.once('timeout', () => done(false));
|
|
115
|
+
socket.once('error', () => done(false));
|
|
116
|
+
try {
|
|
117
|
+
socket.connect(port, host);
|
|
118
|
+
socket.unref();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
done(false);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Races the URL detector against the port probe and stops at the first hit.
|
|
127
|
+
*
|
|
128
|
+
* ONE SHOT. `stop()` is idempotent and is called from every terminal path, so a
|
|
129
|
+
* service that exits during the window leaves nothing behind.
|
|
130
|
+
*/
|
|
131
|
+
export class ReadinessWatcher {
|
|
132
|
+
opts;
|
|
133
|
+
timer;
|
|
134
|
+
deadline;
|
|
135
|
+
finished = false;
|
|
136
|
+
probing = false;
|
|
137
|
+
probeFn;
|
|
138
|
+
constructor(opts) {
|
|
139
|
+
this.opts = opts;
|
|
140
|
+
this.probeFn = opts.probe ?? ((port) => probePort(port));
|
|
141
|
+
}
|
|
142
|
+
start() {
|
|
143
|
+
if (this.finished)
|
|
144
|
+
return;
|
|
145
|
+
this.deadline = setTimeout(() => {
|
|
146
|
+
if (this.finished)
|
|
147
|
+
return;
|
|
148
|
+
this.stop();
|
|
149
|
+
this.opts.onTimeout();
|
|
150
|
+
}, this.opts.readyTimeoutMs);
|
|
151
|
+
this.deadline.unref?.();
|
|
152
|
+
const hint = this.opts.portHint;
|
|
153
|
+
if (hint === undefined)
|
|
154
|
+
return;
|
|
155
|
+
this.timer = setInterval(() => {
|
|
156
|
+
if (this.finished || this.probing)
|
|
157
|
+
return;
|
|
158
|
+
this.probing = true;
|
|
159
|
+
void this.probeFn(hint)
|
|
160
|
+
.then((answered) => {
|
|
161
|
+
this.probing = false;
|
|
162
|
+
if (!answered || this.finished)
|
|
163
|
+
return;
|
|
164
|
+
this.settle({ port: hint, url: `http://127.0.0.1:${hint}`, detectedBy: 'probe' });
|
|
165
|
+
})
|
|
166
|
+
.catch(() => {
|
|
167
|
+
this.probing = false;
|
|
168
|
+
});
|
|
169
|
+
}, PROC_LIMITS.probeIntervalMs);
|
|
170
|
+
this.timer.unref?.();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Offer the rows emitted since the last call.
|
|
174
|
+
*
|
|
175
|
+
* A MATCH WITH A PORT IS CONFIRMED BY ONE CONNECT before the status flips; a
|
|
176
|
+
* refused connect leaves the service `starting` and the row is simply not
|
|
177
|
+
* re-considered on the next call, because the caller passes only NEW rows.
|
|
178
|
+
*/
|
|
179
|
+
offerRows(rows) {
|
|
180
|
+
if (this.finished || rows.length === 0)
|
|
181
|
+
return;
|
|
182
|
+
const hit = detectReadyUrl(rows);
|
|
183
|
+
if (!hit)
|
|
184
|
+
return;
|
|
185
|
+
if (hit.port === undefined) {
|
|
186
|
+
this.settle({ ...(hit.url ? { url: hit.url } : {}), detectedBy: 'url' });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const port = hit.port;
|
|
190
|
+
void this.probeFn(port)
|
|
191
|
+
.then((answered) => {
|
|
192
|
+
if (!answered || this.finished)
|
|
193
|
+
return;
|
|
194
|
+
this.settle({
|
|
195
|
+
port,
|
|
196
|
+
url: hit.url ?? `http://127.0.0.1:${port}`,
|
|
197
|
+
detectedBy: 'url',
|
|
198
|
+
});
|
|
199
|
+
})
|
|
200
|
+
.catch(() => {
|
|
201
|
+
/* a refused connect is not an error; it just is not ready yet */
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
stop() {
|
|
205
|
+
this.finished = true;
|
|
206
|
+
if (this.timer)
|
|
207
|
+
clearInterval(this.timer);
|
|
208
|
+
if (this.deadline)
|
|
209
|
+
clearTimeout(this.deadline);
|
|
210
|
+
this.timer = undefined;
|
|
211
|
+
this.deadline = undefined;
|
|
212
|
+
}
|
|
213
|
+
settle(result) {
|
|
214
|
+
if (this.finished)
|
|
215
|
+
return;
|
|
216
|
+
this.stop();
|
|
217
|
+
this.opts.onReady(result);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProcSupervisor — the owner of every child process this session spawned
|
|
3
|
+
* (background-service-supervision §3.3 / §3.5 / §3.6).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY: `src/proc/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* ONE REGISTRY, TWO LIFETIMES (I-4). Foreground `bash` children are tracked so
|
|
8
|
+
* `forceStop()` can reach them; background services are tracked so `Ctrl+C`,
|
|
9
|
+
* `bash_kill` and process exit can reach them. Splitting those into two
|
|
10
|
+
* registries is how a force-stop ends up killing the user's dev server, or how a
|
|
11
|
+
* quit leaves it running. They are two collections in ONE object, and the two
|
|
12
|
+
* kill entry points are named for which one they own.
|
|
13
|
+
*
|
|
14
|
+
* CONSTRUCTED UNCONDITIONALLY (P1-6 / D-9). `cfg.bash.background` gates the
|
|
15
|
+
* SERVICE half — the `background` tool param, `bash_output`/`bash_kill`, the
|
|
16
|
+
* prompt block, the status chip — and never the foreground half, because G2
|
|
17
|
+
* ("bash always settles") and G3 ("Esc, Esc always works") are promised
|
|
18
|
+
* unconditionally. With the flag off this object holds foreground pids, nothing
|
|
19
|
+
* else, and allocates no timers.
|
|
20
|
+
*/
|
|
21
|
+
import { type ProcEventListener, type ProcSupervisorPort, type ServiceLogPage, type ServiceSnapshot, type ServiceStartRequest, type ServiceStartResult } from './types.js';
|
|
22
|
+
export interface ProcSupervisorOptions {
|
|
23
|
+
/** `cfg.bash.readyTimeoutMs`, read once per service at spawn. */
|
|
24
|
+
readyTimeoutMs: () => number;
|
|
25
|
+
/** Injected by the tests; production uses a real TCP connect. */
|
|
26
|
+
probe?: (port: number) => Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
export declare class ProcSupervisor implements ProcSupervisorPort {
|
|
29
|
+
private readonly opts;
|
|
30
|
+
private readonly services;
|
|
31
|
+
/**
|
|
32
|
+
* Live FOREGROUND `bash` children, by pid.
|
|
33
|
+
*
|
|
34
|
+
* A `Set` of pids rather than of `ChildProcess` objects: the only thing
|
|
35
|
+
* `killForeground` needs is the number, and holding the object would keep a
|
|
36
|
+
* reference to a settled child's stdio for the life of the session.
|
|
37
|
+
*/
|
|
38
|
+
private readonly foreground;
|
|
39
|
+
private readonly listeners;
|
|
40
|
+
private nextId;
|
|
41
|
+
private disposed;
|
|
42
|
+
constructor(opts: ProcSupervisorOptions);
|
|
43
|
+
trackForeground(pid: number | undefined): () => void;
|
|
44
|
+
/**
|
|
45
|
+
* Hard-kill every tracked foreground child. Returns how many were signalled.
|
|
46
|
+
*
|
|
47
|
+
* THIS IS WHAT ACTUALLY UNBLOCKS A WEDGED `await tool.execute` on rung two of
|
|
48
|
+
* the Esc ladder. It NEVER touches services: `Esc` interrupts the AGENT,
|
|
49
|
+
* `Ctrl+C` stops the SERVICES, and that asymmetry is the user's own two
|
|
50
|
+
* sentences (I-4). A later reader will want to "fix" this by making it kill
|
|
51
|
+
* everything; that is the bug, not the fix.
|
|
52
|
+
*/
|
|
53
|
+
killForeground(_reason: 'force'): number;
|
|
54
|
+
start(req: ServiceStartRequest): Promise<ServiceStartResult>;
|
|
55
|
+
get(id: string): ServiceSnapshot | undefined;
|
|
56
|
+
list(): ServiceSnapshot[];
|
|
57
|
+
read(id: string, since?: number): ServiceLogPage | undefined;
|
|
58
|
+
liveCount(): number;
|
|
59
|
+
/**
|
|
60
|
+
* Stop one service, or every service.
|
|
61
|
+
*
|
|
62
|
+
* `SIGTERM` FIRST, `SIGKILL` AFTER `stopGraceMs` — the graceful ladder, which
|
|
63
|
+
* belongs here rather than in `reapSync` because this path has a live event
|
|
64
|
+
* loop to run it on. It then CHECKS `process.kill(pid, 0)` and records
|
|
65
|
+
* `killIncomplete` when the pid is still there (P2-6): an honest warning beats
|
|
66
|
+
* a silent leak, and `killTree` cannot know on its own whether it worked.
|
|
67
|
+
*/
|
|
68
|
+
stop(id: string): Promise<ServiceSnapshot[]>;
|
|
69
|
+
private stopOne;
|
|
70
|
+
/**
|
|
71
|
+
* SYNCHRONOUS, TOTAL, IDEMPOTENT. The only entry point a signal hook,
|
|
72
|
+
* `process.on('exit')` or `handleFatal` may call.
|
|
73
|
+
*
|
|
74
|
+
* No escalation ladder, no promises, no logging: a reaper that can block or
|
|
75
|
+
* throw is worse than one that misses a child. It reaps FOREGROUND children
|
|
76
|
+
* too, because a `bash` call in flight at quit time is a process nobody else
|
|
77
|
+
* will ever kill.
|
|
78
|
+
*/
|
|
79
|
+
reapSync(): void;
|
|
80
|
+
dispose(): void;
|
|
81
|
+
subscribe(listener: ProcEventListener): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* EVERY LISTENER IS WRAPPED, and that is not defensive noise (I-5). This runs
|
|
84
|
+
* inside `child.stdout.on('data')`, so a throw here would kill a ten-minute
|
|
85
|
+
* build because of a render bug. `debug` rather than `warn` because a listener
|
|
86
|
+
* that throws once throws on every chunk — the same rule, the same reason, as
|
|
87
|
+
* `AgentController.emitToolOutput`.
|
|
88
|
+
*/
|
|
89
|
+
private emit;
|
|
90
|
+
/** Coalesce `output` at the SOURCE — `/bg` and `bash_output` read it too. */
|
|
91
|
+
private scheduleOutput;
|
|
92
|
+
/** Release every timer, wake every waiter, and announce the terminal state. */
|
|
93
|
+
private finish;
|
|
94
|
+
/**
|
|
95
|
+
* Evict TERMINAL records only, oldest first, to make room.
|
|
96
|
+
*
|
|
97
|
+
* IDS ARE NEVER REUSED (I-6): `nextId` only ever increases, so a stale
|
|
98
|
+
* `bash_kill({ service: 's1' })` can only be a no-op and never a kill of
|
|
99
|
+
* something else.
|
|
100
|
+
*/
|
|
101
|
+
private evictTerminal;
|
|
102
|
+
private snapshot;
|
|
103
|
+
}
|