agent-nuvira 3.5.0 → 3.5.2
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 +13 -13
- package/dist/agent-sdk/src/scaffold.d.ts +1 -1
- package/dist/agent-sdk/src/scaffold.js +1 -1
- package/dist/cli/chat.d.ts +159 -6
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +583 -35
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/duties.d.ts +11 -0
- package/dist/cli/duties.d.ts.map +1 -1
- package/dist/cli/duties.js +31 -0
- package/dist/cli/duties.js.map +1 -1
- package/dist/cli/edit.d.ts.map +1 -1
- package/dist/cli/edit.js +3 -2
- package/dist/cli/edit.js.map +1 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +10 -3
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +16 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +9 -0
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/models.d.ts.map +1 -1
- package/dist/cli/models.js +55 -0
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/plan.d.ts.map +1 -1
- package/dist/cli/plan.js +18 -6
- package/dist/cli/plan.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +23 -8
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/ui/answer-view.d.ts +38 -0
- package/dist/cli/ui/answer-view.d.ts.map +1 -0
- package/dist/cli/ui/answer-view.js +72 -0
- package/dist/cli/ui/answer-view.js.map +1 -0
- package/dist/cli/ui/cards.d.ts +61 -0
- package/dist/cli/ui/cards.d.ts.map +1 -0
- package/dist/cli/ui/cards.js +141 -0
- package/dist/cli/ui/cards.js.map +1 -0
- package/dist/cli/ui/highlight.d.ts +32 -0
- package/dist/cli/ui/highlight.d.ts.map +1 -0
- package/dist/cli/ui/highlight.js +261 -0
- package/dist/cli/ui/highlight.js.map +1 -0
- package/dist/cli/ui/index.d.ts +21 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/index.js +14 -0
- package/dist/cli/ui/index.js.map +1 -0
- package/dist/cli/ui/markdown.d.ts +90 -0
- package/dist/cli/ui/markdown.d.ts.map +1 -0
- package/dist/cli/ui/markdown.js +292 -0
- package/dist/cli/ui/markdown.js.map +1 -0
- package/dist/cli/ui/presenter.d.ts +147 -0
- package/dist/cli/ui/presenter.d.ts.map +1 -0
- package/dist/cli/ui/presenter.js +313 -0
- package/dist/cli/ui/presenter.js.map +1 -0
- package/dist/cli/ui/render.d.ts +52 -0
- package/dist/cli/ui/render.d.ts.map +1 -0
- package/dist/cli/ui/render.js +384 -0
- package/dist/cli/ui/render.js.map +1 -0
- package/dist/cli/ui/theme.d.ts +36 -0
- package/dist/cli/ui/theme.d.ts.map +1 -0
- package/dist/cli/ui/theme.js +92 -0
- package/dist/cli/ui/theme.js.map +1 -0
- package/dist/inference/model-entitlement.d.ts +10 -0
- package/dist/inference/model-entitlement.d.ts.map +1 -1
- package/dist/inference/model-entitlement.js +1 -1
- package/dist/inference/model-entitlement.js.map +1 -1
- package/dist/learning/auto-router.d.ts +38 -1
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +139 -8
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/duty-lease.d.ts +163 -0
- package/dist/learning/duty-lease.d.ts.map +1 -0
- package/dist/learning/duty-lease.js +301 -0
- package/dist/learning/duty-lease.js.map +1 -0
- package/dist/learning/model-first-router.d.ts +14 -0
- package/dist/learning/model-first-router.d.ts.map +1 -1
- package/dist/learning/model-first-router.js +54 -17
- package/dist/learning/model-first-router.js.map +1 -1
- package/dist/learning/model-warmup.d.ts +92 -0
- package/dist/learning/model-warmup.d.ts.map +1 -1
- package/dist/learning/model-warmup.js +137 -2
- package/dist/learning/model-warmup.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +21 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +70 -4
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/learning/retry-patience.d.ts +139 -0
- package/dist/learning/retry-patience.d.ts.map +1 -0
- package/dist/learning/retry-patience.js +169 -0
- package/dist/learning/retry-patience.js.map +1 -0
- package/dist/tools/process-liveness.d.ts +227 -0
- package/dist/tools/process-liveness.d.ts.map +1 -0
- package/dist/tools/process-liveness.js +486 -0
- package/dist/tools/process-liveness.js.map +1 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +4 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-registry.d.ts +282 -0
- package/dist/tools/run-registry.d.ts.map +1 -0
- package/dist/tools/run-registry.js +544 -0
- package/dist/tools/run-registry.js.map +1 -0
- package/dist/tools/run-terminal.d.ts +100 -2
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +841 -67
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +87 -3
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +375 -6
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +25 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +16 -1
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +7 -1
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +32 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/task-registry.d.ts +98 -0
- package/dist/web-dashboard/task-registry.d.ts.map +1 -0
- package/dist/web-dashboard/task-registry.js +159 -0
- package/dist/web-dashboard/task-registry.js.map +1 -0
- package/dist/web-dashboard/task-runner.d.ts +155 -5
- package/dist/web-dashboard/task-runner.d.ts.map +1 -1
- package/dist/web-dashboard/task-runner.js +505 -14
- package/dist/web-dashboard/task-runner.js.map +1 -1
- package/package.json +4 -1
- package/src/web-dashboard/public/assets/index-DycGBWwy.js +236 -0
- package/src/web-dashboard/public/assets/index-DycGBWwy.js.map +1 -0
- package/src/web-dashboard/public/index.html +1 -1
- package/src/web-dashboard/public/assets/index-yky0dw5e.js +0 -236
- package/src/web-dashboard/public/assets/index-yky0dw5e.js.map +0 -1
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The durable half of the monitored-run registry.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. Monitoring a long command in memory (see `run-terminal.ts`)
|
|
5
|
+
* fixed the killing, but it made the record as fragile as the process holding it:
|
|
6
|
+
* a CLI exit, a dashboard restart, a crash — all normal — and a command that is
|
|
7
|
+
* still running becomes unpollable. Nothing is left to follow it with, its real
|
|
8
|
+
* exit code is never collected, and the turn that started it can only re-run the
|
|
9
|
+
* work (wrong) or guess at the outcome. Two facts have to outlive the process:
|
|
10
|
+
*
|
|
11
|
+
* what is running — id, command, cwd, pid, start time, and how it ended;
|
|
12
|
+
* what it said — its output, from the FIRST byte as well as the last.
|
|
13
|
+
*
|
|
14
|
+
* The second one is why there is a spool file and not just a record. The
|
|
15
|
+
* in-memory buffer is a bounded TAIL (a 40MB build log must not be held), so the
|
|
16
|
+
* beginning of a long run was simply gone — the model could read the end of a
|
|
17
|
+
* failure and not the line that explained it. The spool keeps a bounded HEAD and
|
|
18
|
+
* a bounded TAIL (rewritten whole, so the file never grows past the cap), which
|
|
19
|
+
* is the part a diagnosis actually uses.
|
|
20
|
+
*
|
|
21
|
+
* Everything here is best-effort and must never break a turn: a missing, unreadable
|
|
22
|
+
* or corrupt registry is an empty registry, never a throw.
|
|
23
|
+
*/
|
|
24
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { resolveNuviraDataPath } from '../config/paths.js';
|
|
27
|
+
import { lookupProcess } from './process-liveness.js';
|
|
28
|
+
/** The full-log cap, with an env override read at construction (test-friendly). */
|
|
29
|
+
function envFullBytes() {
|
|
30
|
+
const raw = Number.parseInt(String(process.env.NUVIRA_RUN_LOG_FULL_BYTES ?? '').trim(), 10);
|
|
31
|
+
return Number.isFinite(raw) && raw >= 0 ? raw : SPOOL_FULL_BYTES;
|
|
32
|
+
}
|
|
33
|
+
/** How much of the BEGINNING of a run's output is kept. */
|
|
34
|
+
export const SPOOL_HEAD_BYTES = 32_768;
|
|
35
|
+
/** How much of the END of a run's output is kept. */
|
|
36
|
+
export const SPOOL_TAIL_BYTES = 32_768;
|
|
37
|
+
/**
|
|
38
|
+
* How much of a run's output is kept IN FULL before the window collapses to
|
|
39
|
+
* head+tail.
|
|
40
|
+
*
|
|
41
|
+
* WHY THIS EXISTS. A bounded window throws away the MIDDLE of a long run, and
|
|
42
|
+
* that is exactly where a failure explains itself — so the honest trade is to
|
|
43
|
+
* keep everything while everything is small. Most runs never reach this cap, so
|
|
44
|
+
* the common case stops losing anything at all; past it the file collapses to
|
|
45
|
+
* head+tail (with the dropped count stated), which keeps the guarantee that a
|
|
46
|
+
* chatty command cannot fill the disk. `NUVIRA_RUN_LOG_FULL_BYTES=0` restores
|
|
47
|
+
* "always a bounded window".
|
|
48
|
+
*/
|
|
49
|
+
export const SPOOL_FULL_BYTES = 2_000_000;
|
|
50
|
+
/** Pending appends are flushed once they reach this, so memory stays bounded. */
|
|
51
|
+
const SPOOL_PENDING_BYTES = 262_144;
|
|
52
|
+
/** Bounded history: the registry cannot grow with the session. */
|
|
53
|
+
const MAX_PERSISTED_RUNS = 30;
|
|
54
|
+
/** How often an in-flight spool is written out (a crash loses at most this much). */
|
|
55
|
+
export const SPOOL_FLUSH_MS = 2_000;
|
|
56
|
+
/** `<config dir>/run-terminal` — isolated by `$NUVIRA_CONFIG_DIR`, like the rest. */
|
|
57
|
+
export function runsDir() {
|
|
58
|
+
return resolveNuviraDataPath('run-terminal');
|
|
59
|
+
}
|
|
60
|
+
export function spoolPath(id) {
|
|
61
|
+
return join(runsDir(), 'out', `${id}.log`);
|
|
62
|
+
}
|
|
63
|
+
function registryPath() {
|
|
64
|
+
return join(runsDir(), 'runs.json');
|
|
65
|
+
}
|
|
66
|
+
/** Read the registry. A missing/corrupt file is an empty registry. */
|
|
67
|
+
export function loadRuns() {
|
|
68
|
+
try {
|
|
69
|
+
const path = registryPath();
|
|
70
|
+
if (!existsSync(path))
|
|
71
|
+
return [];
|
|
72
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
73
|
+
if (!parsed || !Array.isArray(parsed.runs))
|
|
74
|
+
return [];
|
|
75
|
+
return parsed.runs.filter((r) => !!r &&
|
|
76
|
+
typeof r.id === 'string' &&
|
|
77
|
+
typeof r.command === 'string' &&
|
|
78
|
+
typeof r.pid === 'number' &&
|
|
79
|
+
typeof r.startedAt === 'number' &&
|
|
80
|
+
(r.status === 'running' || r.status === 'done'));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function writeRuns(runs) {
|
|
87
|
+
try {
|
|
88
|
+
mkdirSync(runsDir(), { recursive: true });
|
|
89
|
+
const path = registryPath();
|
|
90
|
+
// Atomic-ish: a crash mid-write must not leave a half file that reads as an
|
|
91
|
+
// empty registry (which would silently lose every live run).
|
|
92
|
+
const tmp = `${path}.tmp`;
|
|
93
|
+
writeFileSync(tmp, JSON.stringify({ version: 1, runs }, null, 2), 'utf-8');
|
|
94
|
+
renameSync(tmp, path);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Add or update one run. Finished entries are kept (so a restart can still report
|
|
103
|
+
* the real exit code) and the oldest are dropped past the cap.
|
|
104
|
+
*/
|
|
105
|
+
export function saveRun(entry) {
|
|
106
|
+
const runs = loadRuns().filter((r) => r.id !== entry.id);
|
|
107
|
+
runs.push(entry);
|
|
108
|
+
const finished = runs.filter((r) => r.status === 'done');
|
|
109
|
+
while (runs.length > MAX_PERSISTED_RUNS && finished.length > 0) {
|
|
110
|
+
const oldest = finished.shift();
|
|
111
|
+
const at = runs.findIndex((r) => r.id === oldest.id);
|
|
112
|
+
if (at >= 0)
|
|
113
|
+
runs.splice(at, 1);
|
|
114
|
+
}
|
|
115
|
+
// Never drop a LIVE run to satisfy the cap — the record of running work is the
|
|
116
|
+
// whole point of the file.
|
|
117
|
+
while (runs.length > MAX_PERSISTED_RUNS) {
|
|
118
|
+
const oldest = runs.find((r) => r.status === 'done');
|
|
119
|
+
if (!oldest)
|
|
120
|
+
break;
|
|
121
|
+
runs.splice(runs.indexOf(oldest), 1);
|
|
122
|
+
}
|
|
123
|
+
writeRuns(runs);
|
|
124
|
+
}
|
|
125
|
+
/** The spooled output of one run, or null when there is none. */
|
|
126
|
+
export function readSpool(id) {
|
|
127
|
+
try {
|
|
128
|
+
const path = spoolPath(id);
|
|
129
|
+
if (!existsSync(path))
|
|
130
|
+
return null;
|
|
131
|
+
const text = readFileSync(path, 'utf-8');
|
|
132
|
+
return text.length > 0 ? text : null;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function spoolBytes(id) {
|
|
139
|
+
try {
|
|
140
|
+
const path = spoolPath(id);
|
|
141
|
+
if (!existsSync(path))
|
|
142
|
+
return 0;
|
|
143
|
+
return readFileSync(path).length;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function discardSpool(id) {
|
|
150
|
+
try {
|
|
151
|
+
rmSync(spoolPath(id), { force: true });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* best-effort */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Delete spool files no record refers to.
|
|
159
|
+
*
|
|
160
|
+
* The registry is capped, but a file is not a record: without this, every run
|
|
161
|
+
* that was ever monitored would leave its log behind forever, and "bounded state"
|
|
162
|
+
* would be true of the JSON and false of the disk. Run on hydrate, when the live
|
|
163
|
+
* set is exactly known.
|
|
164
|
+
*/
|
|
165
|
+
export function sweepSpools(liveIds) {
|
|
166
|
+
try {
|
|
167
|
+
const keep = new Set(liveIds);
|
|
168
|
+
const dir = join(runsDir(), 'out');
|
|
169
|
+
if (!existsSync(dir))
|
|
170
|
+
return;
|
|
171
|
+
for (const name of readdirSync(dir)) {
|
|
172
|
+
if (!name.endsWith('.log'))
|
|
173
|
+
continue;
|
|
174
|
+
const id = name.slice(0, -4);
|
|
175
|
+
if (keep.has(id))
|
|
176
|
+
continue;
|
|
177
|
+
rmSync(join(dir, name), { force: true });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
/* best-effort: a sweep that fails must not stop a turn */
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* A bounded head+tail window over some text.
|
|
186
|
+
*
|
|
187
|
+
* The head is kept because a run's first lines name what it is doing and how it
|
|
188
|
+
* was invoked; the tail is kept because that is where the failure is. The middle
|
|
189
|
+
* is what gets dropped — bounded, explicit, and never silently a lie about size.
|
|
190
|
+
*/
|
|
191
|
+
export function outputWindow(text, maxChars, headChars = SPOOL_HEAD_BYTES, tailChars = SPOOL_TAIL_BYTES) {
|
|
192
|
+
const clean = text.replace(/\r\n/g, '\n');
|
|
193
|
+
if (clean.length <= maxChars)
|
|
194
|
+
return { text: clean, omittedChars: 0 };
|
|
195
|
+
const head = clean.slice(0, Math.min(headChars, Math.floor(maxChars / 2)));
|
|
196
|
+
const tail = clean.slice(-Math.min(tailChars, Math.floor(maxChars / 2)));
|
|
197
|
+
const omittedChars = Math.max(0, clean.length - head.length - tail.length);
|
|
198
|
+
return {
|
|
199
|
+
text: `${head}\n\n… [${omittedChars} characters omitted — the middle of a long run, kept at the ends] …\n\n${tail}`,
|
|
200
|
+
omittedChars,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Write the spool for one run: HEAD (until it is full) + a rolling TAIL.
|
|
205
|
+
*
|
|
206
|
+
* The file is REWRITTEN whole rather than appended to, which is what keeps it
|
|
207
|
+
* bounded: a chatty command can write gigabytes in an hour and the spool still
|
|
208
|
+
* stays at ~64KB. Flushing is throttled (`SPOOL_FLUSH_MS`) so the write cost is
|
|
209
|
+
* independent of the output rate; `close()` does the final write.
|
|
210
|
+
*
|
|
211
|
+
* NOTHING IS WRITTEN UNTIL {@link startPersisting}. Until a run is handed to
|
|
212
|
+
* monitoring it is not durable (there is no registry entry to read it back), so a
|
|
213
|
+
* file on disk would be litter the user never asked for — and litter they do get:
|
|
214
|
+
* MEASURED, the first output chunk of an ordinary `npm test` created
|
|
215
|
+
* `<config>/run-terminal/out/<id>.log` in the developer's REAL profile on every
|
|
216
|
+
* test that spawned a command and finished inside its budget, for a spool that
|
|
217
|
+
* was then discarded. Buffering in memory until hand-off loses nothing (the head
|
|
218
|
+
* is bounded, and the window written at hand-off is the same one) and keeps the
|
|
219
|
+
* suite out of the profile it runs in.
|
|
220
|
+
*/
|
|
221
|
+
export class SpoolWriter {
|
|
222
|
+
id;
|
|
223
|
+
headBytes;
|
|
224
|
+
tailBytes;
|
|
225
|
+
dir;
|
|
226
|
+
head = '';
|
|
227
|
+
tail = '';
|
|
228
|
+
/** Everything ever written, so the window can say how much it dropped. */
|
|
229
|
+
total = 0;
|
|
230
|
+
dirty = false;
|
|
231
|
+
lastFlush = 0;
|
|
232
|
+
closed = false;
|
|
233
|
+
discarded = false;
|
|
234
|
+
/** False until the run is durable — see {@link startPersisting}. */
|
|
235
|
+
persisting = false;
|
|
236
|
+
/**
|
|
237
|
+
* `append` — the file is growing with the run, byte for byte, because it is
|
|
238
|
+
* still under the full-log cap. `window` — the cap was passed, so the file is
|
|
239
|
+
* the bounded head+tail window from here on.
|
|
240
|
+
*/
|
|
241
|
+
mode = 'append';
|
|
242
|
+
/** Text written since the last flush, in append mode. */
|
|
243
|
+
pending = '';
|
|
244
|
+
fullBytes;
|
|
245
|
+
constructor(id, headBytes = SPOOL_HEAD_BYTES, tailBytes = SPOOL_TAIL_BYTES,
|
|
246
|
+
/**
|
|
247
|
+
* Where the window is written. Defaults to the monitored-run spool dir; the
|
|
248
|
+
* dashboard's tasks keep theirs beside their own records. Parameterised
|
|
249
|
+
* rather than duplicated because the discipline — bounded, rewritten whole,
|
|
250
|
+
* throttled, discarded when nobody will read it — is the part worth sharing.
|
|
251
|
+
*/
|
|
252
|
+
dir = join(runsDir(), 'out'), fullBytes = envFullBytes()) {
|
|
253
|
+
this.id = id;
|
|
254
|
+
this.headBytes = headBytes;
|
|
255
|
+
this.tailBytes = tailBytes;
|
|
256
|
+
this.dir = dir;
|
|
257
|
+
this.fullBytes = fullBytes;
|
|
258
|
+
}
|
|
259
|
+
/** The file this window lives in. */
|
|
260
|
+
path() {
|
|
261
|
+
return join(this.dir, `${this.id}.log`);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Begin writing to disk: the run has outlived its wait budget and is now
|
|
265
|
+
* durable (`persistRun`), so its output has to be readable by another process.
|
|
266
|
+
*/
|
|
267
|
+
startPersisting() {
|
|
268
|
+
this.persisting = true;
|
|
269
|
+
}
|
|
270
|
+
write(text) {
|
|
271
|
+
if (this.closed || this.discarded || !text)
|
|
272
|
+
return;
|
|
273
|
+
this.total += text.length;
|
|
274
|
+
if (this.head.length < this.headBytes) {
|
|
275
|
+
this.head += text.slice(0, this.headBytes - this.head.length);
|
|
276
|
+
}
|
|
277
|
+
// The tail only needs to remember the last `tailBytes`; a 2x window lets the
|
|
278
|
+
// slice below stay cheap without re-slicing on every chunk.
|
|
279
|
+
this.tail += text;
|
|
280
|
+
if (this.tail.length > this.tailBytes * 2)
|
|
281
|
+
this.tail = this.tail.slice(-this.tailBytes);
|
|
282
|
+
this.dirty = true;
|
|
283
|
+
if (this.persisting && this.mode === 'append') {
|
|
284
|
+
if (this.total > this.fullBytes) {
|
|
285
|
+
// The run outgrew the cap: drop what the window cannot hold, once.
|
|
286
|
+
this.collapse();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.pending += text;
|
|
290
|
+
// A chatty command must not be able to buffer megabytes between flushes.
|
|
291
|
+
if (this.pending.length >= SPOOL_PENDING_BYTES)
|
|
292
|
+
this.flush();
|
|
293
|
+
}
|
|
294
|
+
this.flushIfDue();
|
|
295
|
+
}
|
|
296
|
+
flushIfDue() {
|
|
297
|
+
if (!this.persisting)
|
|
298
|
+
return;
|
|
299
|
+
const now = Date.now();
|
|
300
|
+
if (now - this.lastFlush < SPOOL_FLUSH_MS)
|
|
301
|
+
return;
|
|
302
|
+
this.flush();
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Cross the cap: write the bounded window ONCE, and stop growing the file.
|
|
306
|
+
*
|
|
307
|
+
* The bytes already appended are what a run's middle looked like while it still
|
|
308
|
+
* fit; rewriting the file is what keeps a gigabyte of output from becoming a
|
|
309
|
+
* gigabyte of disk.
|
|
310
|
+
*/
|
|
311
|
+
collapse() {
|
|
312
|
+
this.mode = 'window';
|
|
313
|
+
this.pending = '';
|
|
314
|
+
this.flush();
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Write what has accumulated. Safe to call at any time.
|
|
318
|
+
*
|
|
319
|
+
* The file is the HEAD plus the LAST `tailBytes` of the tail — taken from the
|
|
320
|
+
* END, because the tail is where a failure explains itself. When everything
|
|
321
|
+
* fits in the head, only the head is written: otherwise a short run's output
|
|
322
|
+
* would be stored twice and read back doubled.
|
|
323
|
+
*/
|
|
324
|
+
flush() {
|
|
325
|
+
if (!this.persisting || !this.dirty || this.discarded || this.closed)
|
|
326
|
+
return;
|
|
327
|
+
this.dirty = false;
|
|
328
|
+
this.lastFlush = Date.now();
|
|
329
|
+
const tail = this.tail.slice(-this.tailBytes);
|
|
330
|
+
const omitted = Math.max(0, this.total - this.head.length - tail.length);
|
|
331
|
+
const body = omitted > 0
|
|
332
|
+
? `${this.head}\n… [${omitted} characters omitted — kept at the ends] …\n${tail}`
|
|
333
|
+
: this.head || tail;
|
|
334
|
+
try {
|
|
335
|
+
mkdirSync(this.dir, { recursive: true });
|
|
336
|
+
if (this.mode === 'append' && this.pending) {
|
|
337
|
+
// UNDER THE CAP: append what the run actually printed, so the middle of a
|
|
338
|
+
// long-but-not-huge run is still readable afterwards.
|
|
339
|
+
appendFileSync(this.path(), this.pending, 'utf-8');
|
|
340
|
+
this.pending = '';
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
writeFileSync(this.path(), body, 'utf-8');
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
/* the spool is a convenience — never break the run for it */
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Make sure the bytes still held in memory reach the file.
|
|
351
|
+
*
|
|
352
|
+
* `close()` cannot use `flush()` for this: in append mode a pause in output
|
|
353
|
+
* older than the throttle leaves `pending` unwritten, and those are exactly the
|
|
354
|
+
* last lines of a run — the ones that explain why it failed.
|
|
355
|
+
*/
|
|
356
|
+
drainPending() {
|
|
357
|
+
if (!this.persisting || this.discarded || !this.pending)
|
|
358
|
+
return;
|
|
359
|
+
try {
|
|
360
|
+
mkdirSync(this.dir, { recursive: true });
|
|
361
|
+
appendFileSync(this.path(), this.pending, 'utf-8');
|
|
362
|
+
this.pending = '';
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
/* best-effort */
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/** Final write. The file stays (a finished run's output is still worth reading). */
|
|
369
|
+
close() {
|
|
370
|
+
if (this.closed)
|
|
371
|
+
return;
|
|
372
|
+
if (this.mode === 'append')
|
|
373
|
+
this.drainPending();
|
|
374
|
+
else
|
|
375
|
+
this.flush();
|
|
376
|
+
this.closed = true;
|
|
377
|
+
}
|
|
378
|
+
/** Drop the spool: the run finished inside its wait budget, so nobody will poll it. */
|
|
379
|
+
discard() {
|
|
380
|
+
this.discarded = true;
|
|
381
|
+
this.closed = true;
|
|
382
|
+
try {
|
|
383
|
+
rmSync(this.path(), { force: true });
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
/* best-effort */
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Is `pid` still the process we started, or has the pid been recycled?
|
|
392
|
+
*
|
|
393
|
+
* A run recovered from the registry is identified by a NUMBER that outlived the
|
|
394
|
+
* process table it came from. Between the crash and the re-attach, that pid can
|
|
395
|
+
* belong to something else entirely — and this tool's whole safety story is that
|
|
396
|
+
* it only ever signals commands IT started. So before any recovered run is
|
|
397
|
+
* signalled, it is identified all over again.
|
|
398
|
+
*
|
|
399
|
+
* The process line — `ps` on POSIX, the same `Get-CimInstance Win32_Process`
|
|
400
|
+
* query the liveness sampler already runs on Windows — shows the shell we spawned
|
|
401
|
+
* (`/bin/sh -c <command>`) or, when the shell exec'd a simple command, that
|
|
402
|
+
* command's own line; both contain the command text, which is why the check is a
|
|
403
|
+
* containment test rather than equality.
|
|
404
|
+
*/
|
|
405
|
+
export function commandMatchesProcess(pid, command) {
|
|
406
|
+
const row = lookupProcess(pid);
|
|
407
|
+
if (row === null)
|
|
408
|
+
return null; // this platform cannot read the process table
|
|
409
|
+
if (!row.found)
|
|
410
|
+
return false; // the pid is gone — definitely not ours
|
|
411
|
+
return commandVerdict(command, row.command);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* The command-line witness alone, as a verdict: `true` ours, `false` not ours,
|
|
415
|
+
* `null` no reading (which is NOT a match, and must never be treated as one).
|
|
416
|
+
*/
|
|
417
|
+
function commandVerdict(command, liveLine) {
|
|
418
|
+
const wanted = command.trim();
|
|
419
|
+
if (!wanted)
|
|
420
|
+
return null;
|
|
421
|
+
if (!liveLine)
|
|
422
|
+
return null; // a row whose command line could not be read
|
|
423
|
+
try {
|
|
424
|
+
// Quotes and runs of whitespace are NOT differences of identity: the shell
|
|
425
|
+
// strips the quotes from `node -e "…"` before the process exists, so a naive
|
|
426
|
+
// containment test would refuse to stop a run we really do own.
|
|
427
|
+
const normalize = (s) => s.replace(/["']/g, '').replace(/\s+/g, ' ').trim();
|
|
428
|
+
const actual = normalize(liveLine);
|
|
429
|
+
const wantedNorm = normalize(wanted);
|
|
430
|
+
// The process line CONTAINS what we recorded — the shell carries it
|
|
431
|
+
// (`sh -c npm test`), or the command is what the shell exec'd into.
|
|
432
|
+
if (actual.includes(wantedNorm))
|
|
433
|
+
return true;
|
|
434
|
+
// A process listing can TRUNCATE a very long command line, so a substantial
|
|
435
|
+
// PREFIX of what we recorded is accepted — but only with the same binary,
|
|
436
|
+
// and never a short bare name: a recycled pid showing just `node` must NOT
|
|
437
|
+
// satisfy a recorded `node -e …`, which is how a stranger would be adopted.
|
|
438
|
+
const first = wantedNorm.split(' ')[0];
|
|
439
|
+
if (actual.length >= 16 && wantedNorm.startsWith(actual) && actual.split(' ')[0] === first)
|
|
440
|
+
return true;
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return null; // unknown — the caller must not treat this as a match
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Identity, decided by TWO independent witnesses — pure, so every branch can be
|
|
449
|
+
* asserted instead of hoped for.
|
|
450
|
+
*
|
|
451
|
+
* pid + command line what the run was started as
|
|
452
|
+
* pid + OS start token when the OS says that process began
|
|
453
|
+
*
|
|
454
|
+
* The rule that makes this safe rather than merely clever:
|
|
455
|
+
*
|
|
456
|
+
* a witness may VETO, and only the start token may ACCEPT ALONE.
|
|
457
|
+
*
|
|
458
|
+
* - A different command line is not our process, so `false` wins over any
|
|
459
|
+
* agreement elsewhere — a recycled pid running something else is refused.
|
|
460
|
+
* - When the command lines agree, a start token that DISAGREES refuses too: the
|
|
461
|
+
* same command started as a different process (another `npm test` in another
|
|
462
|
+
* directory, a stranger's `node`) is precisely the case that used to be
|
|
463
|
+
* adopted. This is the new refusal, and it can only ever refuse.
|
|
464
|
+
* - When the command line cannot be read at all (some platforms hide it, and a
|
|
465
|
+
* kernel process has none), a MATCHING start token is accepted: that is a
|
|
466
|
+
* value the kernel does not hand out twice, and it is the difference between
|
|
467
|
+
* a recovered run that can be stopped and one that can only be stopped by
|
|
468
|
+
* hand. Anything short of a match stays `null` — unknown, never ours.
|
|
469
|
+
*/
|
|
470
|
+
export function processIdentityVerdict(recorded, live) {
|
|
471
|
+
return processIdentityCheck(recorded, live).verdict;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* The verdict AND the evidence behind it.
|
|
475
|
+
*
|
|
476
|
+
* A refusal the operator cannot act on is barely better than a crash: the whole
|
|
477
|
+
* consequence of `false` here is that a real command keeps running and can only
|
|
478
|
+
* be stopped by hand, so the message has to say WHICH witness failed — a
|
|
479
|
+
* different command line and a mismatched start time are different problems, and
|
|
480
|
+
* "could not tell" is a third.
|
|
481
|
+
*/
|
|
482
|
+
export function processIdentityCheck(recorded, live) {
|
|
483
|
+
if (live === null) {
|
|
484
|
+
return {
|
|
485
|
+
verdict: null,
|
|
486
|
+
why: 'this platform could not read a process table, so the pid cannot be checked',
|
|
487
|
+
kind: 'unreadable-table',
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
// A FACT, as opposed to an inference: the pid is not in the table at all. Only
|
|
491
|
+
// this branch may ever be read as "it is over" — every other refusal says
|
|
492
|
+
// something about IDENTITY, which is not the same claim (see the callers).
|
|
493
|
+
if (!live.found)
|
|
494
|
+
return { verdict: false, why: 'the pid is not running any more', kind: 'gone' };
|
|
495
|
+
const byCommand = commandVerdict(recorded.command, live.command);
|
|
496
|
+
const start = recorded.osStartedAt;
|
|
497
|
+
const byStart = start && live.startedAtText ? start === live.startedAtText : null;
|
|
498
|
+
if (byCommand === false) {
|
|
499
|
+
return {
|
|
500
|
+
verdict: false,
|
|
501
|
+
kind: 'not-ours',
|
|
502
|
+
why: `the pid now runs a different command line (\`${live.command}\`), so it is not the ` +
|
|
503
|
+
`process that was started`,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
if (byCommand === true) {
|
|
507
|
+
if (byStart === false) {
|
|
508
|
+
return {
|
|
509
|
+
verdict: false,
|
|
510
|
+
kind: 'not-ours',
|
|
511
|
+
why: `its command line matches, but the OS start time differs from the one recorded for this run ` +
|
|
512
|
+
`(\`${live.startedAtText}\` now vs \`${start}\` then) — the pid was RECYCLED by a different process ` +
|
|
513
|
+
`that happens to run the same command`,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
verdict: true,
|
|
518
|
+
kind: 'verified',
|
|
519
|
+
why: byStart === true
|
|
520
|
+
? 'its command line AND its OS start time match what was recorded when it was started'
|
|
521
|
+
: 'its command line matches what was recorded (no start-time token was recorded to compare)',
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (byStart === true) {
|
|
525
|
+
return {
|
|
526
|
+
verdict: true,
|
|
527
|
+
kind: 'verified',
|
|
528
|
+
why: 'its OS start time matches the token recorded when it was started, which is a value the OS does not ' +
|
|
529
|
+
'hand to two different processes (its command line could not be read)',
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
verdict: null,
|
|
534
|
+
kind: 'unknown',
|
|
535
|
+
why: byStart === false
|
|
536
|
+
? 'its start time does not match, and its command line could not be read'
|
|
537
|
+
: 'neither its command line nor its start time could be read',
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/** `processIdentityVerdict` against the live table: one read, both witnesses. */
|
|
541
|
+
export function processIdentityMatches(pid, recorded) {
|
|
542
|
+
return processIdentityVerdict(recorded, lookupProcess(pid));
|
|
543
|
+
}
|
|
544
|
+
//# sourceMappingURL=run-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-registry.js","sourceRoot":"","sources":["../../src/tools/run-registry.ts"],"names":[],"mappings":"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"}
|