bareloop 0.34.0 → 0.36.0
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 +58 -0
- package/bareloop.context.md +276 -20
- package/package.json +1 -1
- package/src/authorrun.js +3 -2
- package/src/bundlerun.js +17 -12
- package/src/draftspend.js +197 -0
- package/src/exits.js +10 -1
- package/src/index.js +3 -3
- package/src/interviewrun.js +7 -7
- package/src/job.js +37 -0
- package/src/judged.js +2 -2
- package/src/kinds.js +2 -1
- package/src/ledger.js +101 -5
- package/src/legs.js +171 -0
- package/src/monthly.js +111 -72
- package/src/movefile.js +22 -0
- package/src/npminstall.js +55 -0
- package/src/panel/authorroutes.js +84 -14
- package/src/panel/authorsession.js +490 -125
- package/src/panel/importroutes.js +359 -0
- package/src/panel/importrun.js +203 -0
- package/src/panel/index.html +1194 -213
- package/src/panel/runroutes.js +219 -0
- package/src/panel/server.js +964 -79
- package/src/panel/settingsroutes.js +52 -2
- package/src/panel/status.js +51 -0
- package/src/planrun.js +60 -26
- package/src/providerrows.js +17 -0
- package/src/ralph.js +4 -1
- package/src/replay.js +139 -19
- package/src/replayio.js +11 -4
- package/src/reuse.js +20 -3
- package/src/run.js +6 -3
- package/src/runlist.js +75 -7
- package/src/source.js +43 -4
- package/src/text.js +20 -0
- package/src/u-patient.js +3 -2
- package/src/u-readout.js +9 -1
- package/src/u-watchdog.mjs +4 -2
- package/src/userrun.js +192 -65
- package/src/worktree.js +120 -0
- package/types/draftspend.d.ts +141 -0
- package/types/job.d.ts +25 -0
- package/types/ledger.d.ts +44 -0
- package/types/legs.d.ts +103 -0
- package/types/monthly.d.ts +23 -18
- package/types/movefile.d.ts +9 -0
- package/types/npminstall.d.ts +30 -0
- package/types/panel/authorroutes.d.ts +23 -1
- package/types/panel/authorsession.d.ts +110 -2
- package/types/panel/importroutes.d.ts +129 -0
- package/types/panel/importrun.d.ts +77 -0
- package/types/panel/runroutes.d.ts +32 -0
- package/types/panel/server.d.ts +266 -2
- package/types/panel/settingsroutes.d.ts +6 -1
- package/types/panel/status.d.ts +51 -0
- package/types/planrun.d.ts +13 -21
- package/types/providerrows.d.ts +12 -0
- package/types/ralph.d.ts +19 -0
- package/types/replay.d.ts +25 -1
- package/types/replayio.d.ts +2 -2
- package/types/reuse.d.ts +5 -0
- package/types/run.d.ts +6 -2
- package/types/runlist.d.ts +52 -2
- package/types/source.d.ts +13 -5
- package/types/text.d.ts +19 -0
- package/types/userrun.d.ts +10 -0
- package/types/worktree.d.ts +48 -0
package/types/panel/server.d.ts
CHANGED
|
@@ -50,6 +50,140 @@ export function checkTypeTitle(verdictType: string | null, atIso?: string | null
|
|
|
50
50
|
* @returns {string}
|
|
51
51
|
*/
|
|
52
52
|
export function formatTimestamp(ts: string | number | Date): string;
|
|
53
|
+
/**
|
|
54
|
+
* P5 item 2 — may this run be resumed, and under which spec file? The ONE owner of
|
|
55
|
+
* "would the engine accept `--resume` for this run" on the panel side: it asks the SAME
|
|
56
|
+
* library readers the engine's own refusals come from (`readResume` with
|
|
57
|
+
* `CHECKPOINT_OUTCOMES`, `checkpointAgeGate`), plus the two facts the panel needs to
|
|
58
|
+
* spawn it (a signed spec file beside the run, whose hash is the one the run was signed
|
|
59
|
+
* under). `ok:false` means the panel declines to offer a button the engine would refuse
|
|
60
|
+
* by design; it carries a plain `why`.
|
|
61
|
+
*
|
|
62
|
+
* The spec file is the run's own `resolved-spec.json` or one of its
|
|
63
|
+
* `resolved-spec-r<k>.json` siblings (a resume under raised caps writes one), picked by
|
|
64
|
+
* HASH against the run's `job-start.specHash` — a run resumed under raised caps must be
|
|
65
|
+
* resumed again under ITS caps, never the original's. No match = no resume offered.
|
|
66
|
+
* @param {{ spine: string, job: string, pid?: number }} row
|
|
67
|
+
* @param {any[]} records the run's raw spine records
|
|
68
|
+
* @returns {{ok: true, specPath: string, spec: any, specHash: string, budgetUsd: number|null,
|
|
69
|
+
* maxWallMin: number|null, spentUsd: number|null, spendComplete: boolean,
|
|
70
|
+
* draftSpentUsd: number|null, draftSpendComplete: boolean|null, wallUsedMs: number|null}|{ok: false, why: string}}
|
|
71
|
+
*/
|
|
72
|
+
export function resumePlanFor(row: {
|
|
73
|
+
spine: string;
|
|
74
|
+
job: string;
|
|
75
|
+
pid?: number;
|
|
76
|
+
}, records: any[]): {
|
|
77
|
+
ok: true;
|
|
78
|
+
specPath: string;
|
|
79
|
+
spec: any;
|
|
80
|
+
specHash: string;
|
|
81
|
+
budgetUsd: number | null;
|
|
82
|
+
maxWallMin: number | null;
|
|
83
|
+
spentUsd: number | null;
|
|
84
|
+
spendComplete: boolean;
|
|
85
|
+
draftSpentUsd: number | null;
|
|
86
|
+
draftSpendComplete: boolean | null;
|
|
87
|
+
wallUsedMs: number | null;
|
|
88
|
+
} | {
|
|
89
|
+
ok: false;
|
|
90
|
+
why: string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* P5-R — the Audit tab's LEG DIVIDERS (hamr 2026-10-02: "clear audit mention resume"). One per resume, CODE-OWNED
|
|
94
|
+
* fixed sentences (ui-verdict-words: never model text) built from how the previous leg ended (`after`) and what the
|
|
95
|
+
* run had spent when it stopped:
|
|
96
|
+
* `── stopped: money cap reached ($8.00) · resumed 2026-10-03 14:10 ──`.
|
|
97
|
+
* `beforePart` is the index of the first part of the leg that picked up (the parts list length when that leg has not
|
|
98
|
+
* started a part yet). Money is the chain's own spend at that leg's end on the ONE basis ({@link chainSpend}).
|
|
99
|
+
* @param {any[]} spineRecords the run's raw records
|
|
100
|
+
* @param {any[]} parts `summary.parts` (each carries `leg` on a resumed run)
|
|
101
|
+
* @returns {{leg: number, beforePart: number, after: string|null, at: string|null, text: string}[]}
|
|
102
|
+
*/
|
|
103
|
+
export function legDividersFor(spineRecords: any[], parts: any[]): {
|
|
104
|
+
leg: number;
|
|
105
|
+
beforePart: number;
|
|
106
|
+
after: string | null;
|
|
107
|
+
at: string | null;
|
|
108
|
+
text: string;
|
|
109
|
+
}[];
|
|
110
|
+
/**
|
|
111
|
+
* P5 item 1 — the ENDED block: why a run ended, what to do next, and which buttons the
|
|
112
|
+
* engine would accept. CODE-OWNED FIXED SENTENCES (ui-verdict-words: never model text);
|
|
113
|
+
* the only slot-fills are numbers and the engine's own recorded detail. ONE owner,
|
|
114
|
+
* feeding both `getRunDetail` (`ended`) and `summarizeRow` (`endedLine`). `null` while
|
|
115
|
+
* a run is live (no Ended block while running).
|
|
116
|
+
*
|
|
117
|
+
* The table (outcome → reason / next / buttons) is PANEL-BUILD.md P5 item 1. The
|
|
118
|
+
* Resume button appears only when `o.resume` says the engine would accept it; "Reuse workflow"
|
|
119
|
+
* (replaces P5 item 3, 2026-10-03) is offered on green rows only, and starts a NEW run.
|
|
120
|
+
* @param {{outcome: string|null, stopReason: string|null, spentUsd: number|null, budgetUsd: number|null, lastEscalation?: any}} summary
|
|
121
|
+
* @param {{died: boolean, lastThing: string|null}} death
|
|
122
|
+
* @param {{resume?: {ok: boolean, why?: string}|null, destinationRefused?: string|null, moneyHalt?: boolean,
|
|
123
|
+
* worktree?: {folder: string, repo: string, branch: string|null, exists: boolean}|null}} [o]
|
|
124
|
+
* `worktree` (P6 item 1): the run's worktree in the person's repo, from {@link worktreeOfRun}; absent for every other run
|
|
125
|
+
* @returns {{reason: string, next: string, line: string, actions: {id: string, label: string}[]}|null}
|
|
126
|
+
*/
|
|
127
|
+
export function endedFor(summary: {
|
|
128
|
+
outcome: string | null;
|
|
129
|
+
stopReason: string | null;
|
|
130
|
+
spentUsd: number | null;
|
|
131
|
+
budgetUsd: number | null;
|
|
132
|
+
lastEscalation?: any;
|
|
133
|
+
}, death: {
|
|
134
|
+
died: boolean;
|
|
135
|
+
lastThing: string | null;
|
|
136
|
+
}, o?: {
|
|
137
|
+
resume?: {
|
|
138
|
+
ok: boolean;
|
|
139
|
+
why?: string;
|
|
140
|
+
} | null;
|
|
141
|
+
destinationRefused?: string | null;
|
|
142
|
+
moneyHalt?: boolean;
|
|
143
|
+
worktree?: {
|
|
144
|
+
folder: string;
|
|
145
|
+
repo: string;
|
|
146
|
+
branch: string | null;
|
|
147
|
+
exists: boolean;
|
|
148
|
+
} | null;
|
|
149
|
+
}): {
|
|
150
|
+
reason: string;
|
|
151
|
+
next: string;
|
|
152
|
+
line: string;
|
|
153
|
+
actions: {
|
|
154
|
+
id: string;
|
|
155
|
+
label: string;
|
|
156
|
+
}[];
|
|
157
|
+
} | null;
|
|
158
|
+
/**
|
|
159
|
+
* The Resume route's reader (P5 item 2): the listed row plus the resume plan, from the
|
|
160
|
+
* ONE {@link resumePlanFor} the Ended block asks. `null` when the runid is not listed
|
|
161
|
+
* or its spine is gone.
|
|
162
|
+
* @param {string} runid
|
|
163
|
+
* @param {{ home?: string }} [opts]
|
|
164
|
+
* @returns {{row: any, plan: any}|null}
|
|
165
|
+
*/
|
|
166
|
+
export function getResumeContext(runid: string, opts?: {
|
|
167
|
+
home?: string;
|
|
168
|
+
}): {
|
|
169
|
+
row: any;
|
|
170
|
+
plan: any;
|
|
171
|
+
} | null;
|
|
172
|
+
/**
|
|
173
|
+
* The Stop route's reader (P5 item 5): the listed row, whether its LATEST leg is live (`runIsAlive`: the row's pid
|
|
174
|
+
* is the latest leg's, folded from the run list's `leg-start`), and whether its spine exists yet. `null` when the
|
|
175
|
+
* runid is not listed.
|
|
176
|
+
* @param {string} runid
|
|
177
|
+
* @param {{ home?: string }} [opts]
|
|
178
|
+
* @returns {{row: any, live: boolean, spineExists: boolean}|null}
|
|
179
|
+
*/
|
|
180
|
+
export function getStopContext(runid: string, opts?: {
|
|
181
|
+
home?: string;
|
|
182
|
+
}): {
|
|
183
|
+
row: any;
|
|
184
|
+
live: boolean;
|
|
185
|
+
spineExists: boolean;
|
|
186
|
+
} | null;
|
|
53
187
|
/**
|
|
54
188
|
* `GET /api/runs` — every listed run, NEWEST FIRST BY `at` (never by file/
|
|
55
189
|
* append order — a backfill scan appends rows in sorted-PATH order, which
|
|
@@ -195,6 +329,127 @@ export function getRunRounds(runid: string, query?: {
|
|
|
195
329
|
export function getRunJob(runid: string, opts?: {
|
|
196
330
|
home?: string;
|
|
197
331
|
}): any | null;
|
|
332
|
+
/**
|
|
333
|
+
* The track record of a WORKFLOW: every LISTED run whose signed spec (the one its latest leg ran under) has this
|
|
334
|
+
* `workflowKey` — the same goal, checks, guardrails, model and close, whatever source, destination or caps. One run =
|
|
335
|
+
* ONE entry (a resumed run is one run, its FINAL outcome — `legsOf`), and a run that is still live is neither green
|
|
336
|
+
* nor not-green yet. `avgSpendUsd` / `avgWallMs` average the FINISHED runs (a recorded terminal) whose figure is exact
|
|
337
|
+
* (chain spend priced; working time from readable stamps, resume gaps excluded) — `null` when no run qualifies, never 0.
|
|
338
|
+
* Read from the runs the panel already lists: no registry, no plan handover (hamr ruling A).
|
|
339
|
+
* `opts.worker` narrows the record to runs on the same WORKER (provider + baseUrl + model — Model is open on a reuse, so
|
|
340
|
+
* the key no longer separates models): `undefined` = every model, a worker = only runs whose signed spec ran that one,
|
|
341
|
+
* `null` = no model chosen, so no run matches.
|
|
342
|
+
* @param {string} key a {@link workflowKey}
|
|
343
|
+
* @param {{ home?: string, worker?: {provider: string, baseUrl?: string, model: string}|null }} [opts]
|
|
344
|
+
* @returns {{runs: number, green: number, notGreen: number, live: number, finished: number, avgSpendUsd: number|null, avgWallMs: number|null}}
|
|
345
|
+
*/
|
|
346
|
+
export function trackRecordFor(key: string, opts?: {
|
|
347
|
+
home?: string;
|
|
348
|
+
worker?: {
|
|
349
|
+
provider: string;
|
|
350
|
+
baseUrl?: string;
|
|
351
|
+
model: string;
|
|
352
|
+
} | null;
|
|
353
|
+
}): {
|
|
354
|
+
runs: number;
|
|
355
|
+
green: number;
|
|
356
|
+
notGreen: number;
|
|
357
|
+
live: number;
|
|
358
|
+
finished: number;
|
|
359
|
+
avgSpendUsd: number | null;
|
|
360
|
+
avgWallMs: number | null;
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* The estimate line above a reuse card (code-owned fixed text): `Same job — G green · N not green · about $X and
|
|
364
|
+
* M min a run`, from the workflow's own listed runs. What is unknown is said, never rendered as $0 or 0 min.
|
|
365
|
+
* @param {{runs?: number, green: number, notGreen: number, finished?: number, avgSpendUsd: number|null, avgWallMs?: number|null}|null} record
|
|
366
|
+
* @returns {string}
|
|
367
|
+
*/
|
|
368
|
+
export function reuseLine(record: {
|
|
369
|
+
runs?: number;
|
|
370
|
+
green: number;
|
|
371
|
+
notGreen: number;
|
|
372
|
+
finished?: number;
|
|
373
|
+
avgSpendUsd: number | null;
|
|
374
|
+
avgWallMs?: number | null;
|
|
375
|
+
} | null): string;
|
|
376
|
+
/**
|
|
377
|
+
* `start-from` (the route keeps its name) for one listed run: the card prefilled from the run's SIGNED job, which
|
|
378
|
+
* boxes are locked and which are open, and the track record. Locked boxes carry the signed values, read by the
|
|
379
|
+
* SAME readers the Job tab uses (`successFromSpec`/`guardrailsFromSpec`); the open four — Source, Destination, the
|
|
380
|
+
* $ cap and the Time cap — start at what the run had. `ok:false` when the run's signed job is not on disk (nothing
|
|
381
|
+
* to copy: change the job: Clear the card). `null` when the runid is not listed.
|
|
382
|
+
* @param {string} runid
|
|
383
|
+
* @param {{ home?: string, model?: string }} [opts] `model` = the Model box's current Name (the estimate counts only runs on that worker); absent = the card's own Model
|
|
384
|
+
* @returns {{ok: true, origin: {runid: string, job: string}, card: Record<string, any>, from: 'signed job',
|
|
385
|
+
* locked: readonly string[], open: readonly string[], note: string|null,
|
|
386
|
+
* specHash: string, workflowKey: string, spec: any, specPath: string,
|
|
387
|
+
* trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
|
|
388
|
+
*/
|
|
389
|
+
export function getStartFrom(runid: string, opts?: {
|
|
390
|
+
home?: string;
|
|
391
|
+
model?: string;
|
|
392
|
+
}): {
|
|
393
|
+
ok: true;
|
|
394
|
+
origin: {
|
|
395
|
+
runid: string;
|
|
396
|
+
job: string;
|
|
397
|
+
};
|
|
398
|
+
card: Record<string, any>;
|
|
399
|
+
from: "signed job";
|
|
400
|
+
locked: readonly string[];
|
|
401
|
+
open: readonly string[];
|
|
402
|
+
note: string | null;
|
|
403
|
+
specHash: string;
|
|
404
|
+
workflowKey: string;
|
|
405
|
+
spec: any;
|
|
406
|
+
specPath: string;
|
|
407
|
+
trackRecord: ReturnType<typeof trackRecordFor>;
|
|
408
|
+
line: string;
|
|
409
|
+
} | {
|
|
410
|
+
ok: false;
|
|
411
|
+
error: string;
|
|
412
|
+
} | null;
|
|
413
|
+
/**
|
|
414
|
+
* Reuse workflow, for an IMPORTED job: the bundle is read AGAIN now and must be clean (`readBundle` — its own hash
|
|
415
|
+
* covers every close script, so a swapped script is `bundle-tampered` here), must resolve its own `bareloop`
|
|
416
|
+
* dependency (`checkBundleDeps`), and every close stage's signed sha256 must match the script's bytes on disk
|
|
417
|
+
* (`checkCloseByteSignature`, over the spec with `$BARELOOP_BUNDLE` resolved to this folder — the same in-memory
|
|
418
|
+
* substitution `bareloop run <bundle>` makes). The reuse spec is that resolved spec; the close scripts stay where
|
|
419
|
+
* they were just verified (the engine re-verifies their bytes at run start and before every close run). The person
|
|
420
|
+
* signs it on this machine at [Sign & run]. Same card, same locked/open boxes as a run's reuse. `null` when no such
|
|
421
|
+
* imported job.
|
|
422
|
+
* @param {string} id
|
|
423
|
+
* @param {{ home?: string, model?: string }} [opts] `model` as for {@link getStartFrom}
|
|
424
|
+
* @returns {{ok: true, origin: {runid: null, job: string, importId: string}, card: Record<string, any>, from: 'imported job',
|
|
425
|
+
* locked: readonly string[], open: readonly string[], note: string|null, specHash: string, workflowKey: string, spec: any,
|
|
426
|
+
* specPath: null, trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
|
|
427
|
+
*/
|
|
428
|
+
export function getStartFromImport(id: string, opts?: {
|
|
429
|
+
home?: string;
|
|
430
|
+
model?: string;
|
|
431
|
+
}): {
|
|
432
|
+
ok: true;
|
|
433
|
+
origin: {
|
|
434
|
+
runid: null;
|
|
435
|
+
job: string;
|
|
436
|
+
importId: string;
|
|
437
|
+
};
|
|
438
|
+
card: Record<string, any>;
|
|
439
|
+
from: "imported job";
|
|
440
|
+
locked: readonly string[];
|
|
441
|
+
open: readonly string[];
|
|
442
|
+
note: string | null;
|
|
443
|
+
specHash: string;
|
|
444
|
+
workflowKey: string;
|
|
445
|
+
spec: any;
|
|
446
|
+
specPath: null;
|
|
447
|
+
trackRecord: ReturnType<typeof trackRecordFor>;
|
|
448
|
+
line: string;
|
|
449
|
+
} | {
|
|
450
|
+
ok: false;
|
|
451
|
+
error: string;
|
|
452
|
+
} | null;
|
|
198
453
|
/**
|
|
199
454
|
* Handle one request against the read-only API + the page. Exported
|
|
200
455
|
* separately from {@link createPanelServer} so tests can drive it without a
|
|
@@ -211,7 +466,7 @@ export function getRunJob(runid: string, opts?: {
|
|
|
211
466
|
* only `createPanelServer` below always supplies one.
|
|
212
467
|
* @param {import('node:http').IncomingMessage} req
|
|
213
468
|
* @param {import('node:http').ServerResponse} res
|
|
214
|
-
* @param {{ home?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes> }} opts
|
|
469
|
+
* @param {{ home?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes> }} opts
|
|
215
470
|
*/
|
|
216
471
|
export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
|
|
217
472
|
home?: string;
|
|
@@ -219,6 +474,8 @@ export function handleRequest(req: import("node:http").IncomingMessage, res: imp
|
|
|
219
474
|
token?: string;
|
|
220
475
|
authorRoutes?: ReturnType<typeof createAuthorRoutes>;
|
|
221
476
|
settingsRoutes?: ReturnType<typeof createSettingsRoutes>;
|
|
477
|
+
runRoutes?: ReturnType<typeof createRunRoutes>;
|
|
478
|
+
importRoutes?: ReturnType<typeof createImportRoutes>;
|
|
222
479
|
}): void;
|
|
223
480
|
/**
|
|
224
481
|
* Start the panel server. Binds `127.0.0.1` ONLY. A taken port is a LOUD,
|
|
@@ -238,7 +495,8 @@ export function handleRequest(req: import("node:http").IncomingMessage, res: imp
|
|
|
238
495
|
* this package's own `bin/bareloop.mjs`).
|
|
239
496
|
* @param {{ port?: number, home?: string, env?: Record<string,string|undefined>,
|
|
240
497
|
* sessionsRoot?: string, spawnFn?: (...a: any[]) => any, bareloopBin?: string,
|
|
241
|
-
* fetchImpl?: typeof fetch
|
|
498
|
+
* fetchImpl?: typeof fetch, settleMs?: number, userHome?: string,
|
|
499
|
+
* openFolderSpawn?: (cmd: string, args: string[], o: object) => any, platform?: string }} [opts]
|
|
242
500
|
* @returns {Promise<{ server: import('node:http').Server, port: number, token: string, close: () => Promise<void> }>}
|
|
243
501
|
*/
|
|
244
502
|
export function createPanelServer(opts?: {
|
|
@@ -249,6 +507,10 @@ export function createPanelServer(opts?: {
|
|
|
249
507
|
spawnFn?: (...a: any[]) => any;
|
|
250
508
|
bareloopBin?: string;
|
|
251
509
|
fetchImpl?: typeof fetch;
|
|
510
|
+
settleMs?: number;
|
|
511
|
+
userHome?: string;
|
|
512
|
+
openFolderSpawn?: (cmd: string, args: string[], o: object) => any;
|
|
513
|
+
platform?: string;
|
|
252
514
|
}): Promise<{
|
|
253
515
|
server: import("node:http").Server;
|
|
254
516
|
port: number;
|
|
@@ -299,4 +561,6 @@ export const ROUNDS_PAGE_DEFAULT: 50;
|
|
|
299
561
|
export const ROUNDS_PAGE_MAX: 200;
|
|
300
562
|
import { createAuthorRoutes } from './authorroutes.js';
|
|
301
563
|
import { createSettingsRoutes } from './settingsroutes.js';
|
|
564
|
+
import { createRunRoutes } from './runroutes.js';
|
|
565
|
+
import { createImportRoutes } from './importroutes.js';
|
|
302
566
|
import { DIED_MTIME_MS } from '../runlist.js';
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* `openFolderSpawn` / `platform` are test seams for the Open keys folder button (a real caller passes
|
|
3
|
+
* neither: `child_process.spawn` and `process.platform`).
|
|
2
4
|
* @param {{ port: number, token: string, home?: string, env?: Record<string,string|undefined>,
|
|
3
|
-
* fetchImpl?: typeof fetch, now?: () => number
|
|
5
|
+
* fetchImpl?: typeof fetch, now?: () => number, openFolderSpawn?: (cmd: string, args: string[], o: object) => any,
|
|
6
|
+
* platform?: string }} opts
|
|
4
7
|
*/
|
|
5
8
|
export function createSettingsRoutes(opts: {
|
|
6
9
|
port: number;
|
|
@@ -9,6 +12,8 @@ export function createSettingsRoutes(opts: {
|
|
|
9
12
|
env?: Record<string, string | undefined>;
|
|
10
13
|
fetchImpl?: typeof fetch;
|
|
11
14
|
now?: () => number;
|
|
15
|
+
openFolderSpawn?: (cmd: string, args: string[], o: object) => any;
|
|
16
|
+
platform?: string;
|
|
12
17
|
}): {
|
|
13
18
|
handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
|
|
14
19
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The status of one run.
|
|
3
|
+
* @param {{outcome: string|null|undefined, died?: boolean, category?: string|null, moneyHalt?: boolean}} o
|
|
4
|
+
* `category` is the last escalation's category; `moneyHalt` is whether the spine carries a `money-halt` record
|
|
5
|
+
* @returns {{key: StatusKey, sign: string, word: string}}
|
|
6
|
+
*/
|
|
7
|
+
export function statusFor(o: {
|
|
8
|
+
outcome: string | null | undefined;
|
|
9
|
+
died?: boolean;
|
|
10
|
+
category?: string | null;
|
|
11
|
+
moneyHalt?: boolean;
|
|
12
|
+
}): {
|
|
13
|
+
key: StatusKey;
|
|
14
|
+
sign: string;
|
|
15
|
+
word: string;
|
|
16
|
+
};
|
|
17
|
+
/** the sentence a passed run carries as its reason (the Ended block's `line`) */
|
|
18
|
+
export const GOAL_MET_LINE: "goal met";
|
|
19
|
+
/** @typedef {'running'|'waiting'|'passed'|'failed'|'capped'|'stopped'|'died'} StatusKey */
|
|
20
|
+
/** the table: key -> sign + word */
|
|
21
|
+
export const STATUS: Readonly<{
|
|
22
|
+
running: Readonly<{
|
|
23
|
+
sign: "▶";
|
|
24
|
+
word: "running";
|
|
25
|
+
}>;
|
|
26
|
+
waiting: Readonly<{
|
|
27
|
+
sign: "·";
|
|
28
|
+
word: "waiting";
|
|
29
|
+
}>;
|
|
30
|
+
passed: Readonly<{
|
|
31
|
+
sign: "✓";
|
|
32
|
+
word: "passed";
|
|
33
|
+
}>;
|
|
34
|
+
failed: Readonly<{
|
|
35
|
+
sign: "✗";
|
|
36
|
+
word: "failed";
|
|
37
|
+
}>;
|
|
38
|
+
capped: Readonly<{
|
|
39
|
+
sign: "✗";
|
|
40
|
+
word: "capped";
|
|
41
|
+
}>;
|
|
42
|
+
stopped: Readonly<{
|
|
43
|
+
sign: "✗";
|
|
44
|
+
word: "stopped";
|
|
45
|
+
}>;
|
|
46
|
+
died: Readonly<{
|
|
47
|
+
sign: "?";
|
|
48
|
+
word: "died";
|
|
49
|
+
}>;
|
|
50
|
+
}>;
|
|
51
|
+
export type StatusKey = "running" | "waiting" | "passed" | "failed" | "capped" | "stopped" | "died";
|
package/types/planrun.d.ts
CHANGED
|
@@ -77,25 +77,6 @@
|
|
|
77
77
|
* @returns {string} the labelled block, or '' when there is nothing to show
|
|
78
78
|
*/
|
|
79
79
|
export function closeGapBlock(gap: string | null | undefined): string;
|
|
80
|
-
/**
|
|
81
|
-
* BA-21 — the WRITE side of the pricing-provenance signal (read side: `rateProvenance` /
|
|
82
|
-
* `spendProvenance` in src/ledger.js). bare-agent >=0.37 rides `rateSource` beside
|
|
83
|
-
* `pricing` on every metering payload; both metering callbacks below forward it onto the
|
|
84
|
-
* spine through THIS one helper, because two sites spelling one rule are two instruments
|
|
85
|
-
* (the ripgrep fix that landed in ci.yml but not publish.yml).
|
|
86
|
-
*
|
|
87
|
-
* Forwarded VERBATIM — the write side reports what upstream said, and src/ledger.js is
|
|
88
|
-
* the one place that decides what counts as a guess. And an ABSENT provenance stays
|
|
89
|
-
* absent: a payload carrying no `rateSource` (bare-agent <0.37, and every round already
|
|
90
|
-
* in the archive) emits no field at all, so a reader sees UNKNOWN rather than a label we
|
|
91
|
-
* invented. Never defaulted to `null` here — `null` is upstream saying "nothing was
|
|
92
|
-
* priced", which is a different fact from "nobody told us".
|
|
93
|
-
* @param {any} arg an `onLlmResult` / `onTurn` payload
|
|
94
|
-
* @returns {{rateSource?: string|null}} the field to spread into the spine record
|
|
95
|
-
*/
|
|
96
|
-
export function rateSourceFields(arg: any): {
|
|
97
|
-
rateSource?: string | null;
|
|
98
|
-
};
|
|
99
80
|
/**
|
|
100
81
|
* What the NEXT attempt is told about the bound that cut the previous one.
|
|
101
82
|
*
|
|
@@ -415,12 +396,18 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
415
396
|
* passes `false` so the three `--resume`-naming readouts below (`MONEY_OPTIONS`,
|
|
416
397
|
* the resume-plan-red option, the fix-loop terminal) say "resume is `run-u`-only
|
|
417
398
|
* in v1" instead of naming a flag that would fail if typed.
|
|
399
|
+
* @param {string|null} [opts.stopFile=null] P5 item 5 — the run's STOP REQUEST file
|
|
400
|
+
* (`stopFilePath(spine)`, src/legs.js). Read at ONE seam, the round boundary of a step worker
|
|
401
|
+
* (the `metered` callback, where the money cap binds): present = emit `stop-requested`, consume
|
|
402
|
+
* the file, end the Loop after that round, and end the leg `stopped` (a checkpoint outcome, filed
|
|
403
|
+
* the way a mid-step cap-halt is; resume re-enters that step). Nothing is in flight at that
|
|
404
|
+
* point, so the spend is exact. `null` = no stop surface (the default).
|
|
418
405
|
* @returns {Promise<string>} 'green' | 'already-green' | 'escalated' | 'plan-red' |
|
|
419
406
|
* 'check-red' | 'close-red' | 'close-unsupported' | 'recipe-stale' | 'pricing-red' |
|
|
420
|
-
* 'branch-red' | 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' |
|
|
407
|
+
* 'branch-red' | 'cap-halt' | 'wall-halt' | 'stopped' | 'provider-red' | 'interpreter-red' |
|
|
421
408
|
* 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
422
409
|
*/
|
|
423
|
-
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, closeDir, maxStepRounds, layerRoot, readShim, scout, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun, resumable }: {
|
|
410
|
+
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, closeDir, maxStepRounds, layerRoot, readShim, scout, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun, resumable, stopFile }: {
|
|
424
411
|
workdir: string;
|
|
425
412
|
provider: any;
|
|
426
413
|
nativeProvider?: ((o: {
|
|
@@ -483,7 +470,9 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
483
470
|
receivedAt?: string | null;
|
|
484
471
|
} | null | undefined;
|
|
485
472
|
resumable?: boolean | undefined;
|
|
473
|
+
stopFile?: string | null | undefined;
|
|
486
474
|
}): Promise<string>;
|
|
475
|
+
export { rateSourceFields };
|
|
487
476
|
/**
|
|
488
477
|
* The runtime belt for F178 (`.git`) and F177's follow-up (`node_modules`) —
|
|
489
478
|
* hamr's option B ruling (2026-09-14): block WRITING to either, never reading.
|
|
@@ -532,7 +521,10 @@ export const FORBIDDEN_WRITE_SEGMENT_PATTERN: RegExp;
|
|
|
532
521
|
* shipped path routinely rides through.
|
|
533
522
|
*/
|
|
534
523
|
export const BOUND_REASON_MAX: 200;
|
|
524
|
+
/** the recorded reason for a step attempt whose round was cut at the output cap (F122 2A b) */
|
|
525
|
+
export const TRUNCATED_REASON: "output cut at the token limit (max_tokens)";
|
|
535
526
|
export type CategorizedError = Error & {
|
|
536
527
|
category?: string;
|
|
537
528
|
lib?: string;
|
|
538
529
|
};
|
|
530
|
+
import { rateSourceFields } from './text.js';
|
package/types/providerrows.d.ts
CHANGED
|
@@ -56,6 +56,18 @@ export function defaultUrlOf(provider: string): string;
|
|
|
56
56
|
* @returns {string}
|
|
57
57
|
*/
|
|
58
58
|
export function endpointOf(provider: string, baseUrl: string | null | undefined): string;
|
|
59
|
+
/**
|
|
60
|
+
* A job's provider in plain words for a refusal line: the host its Base URL points at (else the
|
|
61
|
+
* shape's own default host, else the shape's label), plus the model when the spec names one.
|
|
62
|
+
* Never a key, a path or a status code.
|
|
63
|
+
* @param {{provider?: string, baseUrl?: string|null, model?: string|null}} spec
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
export function providerInWords(spec: {
|
|
67
|
+
provider?: string;
|
|
68
|
+
baseUrl?: string | null;
|
|
69
|
+
model?: string | null;
|
|
70
|
+
}): string;
|
|
59
71
|
/**
|
|
60
72
|
* The URL a row talks to, for display ('' = the vendor's own host, which has no URL to show).
|
|
61
73
|
* @param {KeyRow} row
|
package/types/ralph.d.ts
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} cmd
|
|
3
|
+
* @param {string[]} args
|
|
4
|
+
* @param {{env: NodeJS.ProcessEnv, cwd?: string, timeoutMs?: number}} o
|
|
5
|
+
* @returns {Promise<{error: (Error & {code?: string})|null, status: number|null, signal: string|null, stdout: string, stderr: string}>}
|
|
6
|
+
*/
|
|
7
|
+
export function spawnClose(cmd: string, args: string[], { env, cwd, timeoutMs }: {
|
|
8
|
+
env: NodeJS.ProcessEnv;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
error: (Error & {
|
|
13
|
+
code?: string;
|
|
14
|
+
}) | null;
|
|
15
|
+
status: number | null;
|
|
16
|
+
signal: string | null;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
}>;
|
|
1
20
|
/**
|
|
2
21
|
* Run a close command; its exit code is the verdict (hard green, PRD §4).
|
|
3
22
|
* `expect` → satisfied; any other exit → needs_revision (gap fed back).
|
package/types/replay.d.ts
CHANGED
|
@@ -36,6 +36,18 @@ export function auditWindow(jobStart?: {
|
|
|
36
36
|
startTs: number;
|
|
37
37
|
endTs: number;
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* One window PER LEG of a (possibly resumed) run (P5-R) — the ONE owner the panel and {@link replayRun} share. A
|
|
41
|
+
* run with no `leg-resume` marker is one leg and this is `[auditWindow(jobStart, jobEnd)]`, exactly as before. A
|
|
42
|
+
* resumed run's quiet time between two legs is nobody's, so a row another run wrote into a shared sidecar during it
|
|
43
|
+
* is outside every window; a leg that recorded no terminal (it died) ends where the next leg begins.
|
|
44
|
+
* @param {any[]} spineRecords
|
|
45
|
+
* @returns {{startTs: number, endTs: number}[]}
|
|
46
|
+
*/
|
|
47
|
+
export function auditWindowsOf(spineRecords: any[]): {
|
|
48
|
+
startTs: number;
|
|
49
|
+
endTs: number;
|
|
50
|
+
}[];
|
|
39
51
|
/**
|
|
40
52
|
* Reconstruct one run's whole story from its spine + gate-audit. Pure: no
|
|
41
53
|
* IO, no spine writes, no verdict. Malformed/non-object entries in either
|
|
@@ -109,6 +121,15 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
|
|
|
109
121
|
skipping: any;
|
|
110
122
|
divergence: any;
|
|
111
123
|
} | null;
|
|
124
|
+
legs: {
|
|
125
|
+
leg: number;
|
|
126
|
+
after: string | null;
|
|
127
|
+
outcome: string | null;
|
|
128
|
+
startTs: any;
|
|
129
|
+
endTs: any;
|
|
130
|
+
startSeq: any;
|
|
131
|
+
endSeq: any;
|
|
132
|
+
}[];
|
|
112
133
|
thisFileSpend: {
|
|
113
134
|
value: any;
|
|
114
135
|
source: "engagementSpentUsd" | "summed rounds";
|
|
@@ -178,9 +199,12 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
|
|
|
178
199
|
label: string;
|
|
179
200
|
error: string | null;
|
|
180
201
|
} | null;
|
|
202
|
+
outcome: any;
|
|
203
|
+
stopReason?: string | null | undefined;
|
|
204
|
+
continued?: boolean | undefined;
|
|
205
|
+
tryNumber?: number | undefined;
|
|
181
206
|
id: any;
|
|
182
207
|
occurrence: any;
|
|
183
|
-
outcome: any;
|
|
184
208
|
}[];
|
|
185
209
|
iterations: {
|
|
186
210
|
windowStartSeq: any;
|
package/types/replayio.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export function resolveSiblings(spinePath: string): {
|
|
|
43
43
|
* hand both to {@link replayRun}. Read-only, $0, mints no verdict.
|
|
44
44
|
* @param {string} spinePath absolute or cwd-relative path to a `.jsonl` spine
|
|
45
45
|
* @param {{preParsedSpine?: {records: any[], skipped: number}, skipAudit?: boolean,
|
|
46
|
-
* auditPathOverride?: string|null}} [opts]
|
|
46
|
+
* auditPathOverride?: string|string[]|null}} [opts]
|
|
47
47
|
* `preParsedSpine`: avoids a second parse of the same file when the caller
|
|
48
48
|
* ({@link listSpines}) already read it once to run {@link looksLikeSpine}.
|
|
49
49
|
* `skipAudit` (PR #23 review item 5, 2026-08-26, carried over): a
|
|
@@ -68,7 +68,7 @@ export function replayOne(spinePath: string, opts?: {
|
|
|
68
68
|
skipped: number;
|
|
69
69
|
};
|
|
70
70
|
skipAudit?: boolean;
|
|
71
|
-
auditPathOverride?: string | null;
|
|
71
|
+
auditPathOverride?: string | string[] | null;
|
|
72
72
|
}): ReturnType<typeof replayRun>;
|
|
73
73
|
/**
|
|
74
74
|
* List every spine found directly in `dir` (not recursive — same as the
|
package/types/reuse.d.ts
CHANGED
|
@@ -605,6 +605,11 @@ export const REUSE_GRADED_RED: readonly string[];
|
|
|
605
605
|
* allowances the loop's remaining tries are meant to spend against, and the human
|
|
606
606
|
* ones are the stop no further try can change (see `hardStop`).
|
|
607
607
|
*
|
|
608
|
+
* `stopped` (PANEL-BUILD.md P5 item 5, hamr 2026-10-02): the PERSON ended the leg at a round
|
|
609
|
+
* boundary (a `<spine>.stop` request). Nothing was judged and nothing failed — finished steps stand on
|
|
610
|
+
* disk, the allowance is unspent, so it is a checkpoint exactly like a cap-halt. Arbiter-adjacent (it
|
|
611
|
+
* widens what resume may re-enter); ruled by hamr, not chosen by a builder.
|
|
612
|
+
*
|
|
608
613
|
* `provider-red` folded in 2026-08-25 (PRD v1.80 TODO #4, F115 "Ruled: one transport
|
|
609
614
|
* retry"; hamr's ruling verbatim: *"joins resume anyways and you decide but you are
|
|
610
615
|
* given an honest readout and with 1 retry"*). It is a MACHINE halt, not a human one
|
package/types/run.d.ts
CHANGED
|
@@ -195,10 +195,13 @@
|
|
|
195
195
|
* passes `false` so the escalation tail's option strings say "resume is
|
|
196
196
|
* `run-u`-only in v1" instead of naming a flag that would fail if typed. Forwarded
|
|
197
197
|
* verbatim to the plan flow, which owns the three `--resume`-naming readouts.
|
|
198
|
+
* @param {string|null} [opts.stopFile=null] P5 item 5 — the run's STOP REQUEST file
|
|
199
|
+
* (`stopFilePath(spine)`, src/legs.js), forwarded verbatim to the plan flow, which reads it at the
|
|
200
|
+
* round-boundary seam (where the money cap binds) and ends the leg `stopped`. `null` = no stop surface.
|
|
198
201
|
* @returns {Promise<string>} outcome: 'green' | 'already-green' | 'escalated' |
|
|
199
202
|
* 'unapproved-spec' | 'job-red' | 'smoke-red' | 'plan-red' | 'check-red' |
|
|
200
203
|
* 'close-red' | 'close-unsupported' | 'recipe-stale' | 'branch-red' | 'pricing-red' | 'provider-red' |
|
|
201
|
-
* 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'step-stalled' |
|
|
204
|
+
* 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'stopped' | 'step-stalled' |
|
|
202
205
|
* 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
203
206
|
* NOTE: `job-end` can ALSO land with outcome 'runner-drained' (F140/PRD item
|
|
204
207
|
* 28(c)) — the `beforeExit` backstop's own terminal, emitted from OUTSIDE this
|
|
@@ -209,7 +212,7 @@
|
|
|
209
212
|
* deliberately NOT in the resumable/checkpoint set (`src/reuse.js`) — an
|
|
210
213
|
* unknown in-flight state is not a known-safe resume point.
|
|
211
214
|
*/
|
|
212
|
-
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, draftSpentUsd, draftSpendComplete, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
|
|
215
|
+
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, draftSpentUsd, draftSpendComplete, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable, stopFile }: {
|
|
213
216
|
approvals: unknown;
|
|
214
217
|
workdir: string;
|
|
215
218
|
provider: any;
|
|
@@ -271,6 +274,7 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
|
|
|
271
274
|
receivedAt?: string | null;
|
|
272
275
|
} | null | undefined;
|
|
273
276
|
resumable?: boolean | undefined;
|
|
277
|
+
stopFile?: string | null | undefined;
|
|
274
278
|
}): Promise<string>;
|
|
275
279
|
/** @typedef {{code: string, path: string, detail?: string}} Red */
|
|
276
280
|
/**
|