bareloop 0.29.0 → 0.31.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 +132 -0
- package/bareloop.context.md +147 -4
- package/package.json +1 -1
- package/src/authorflow.js +22 -42
- package/src/authorjob.js +5 -8
- package/src/authorrun.js +28 -12
- package/src/panel/authorroutes.js +283 -0
- package/src/panel/authorsession.js +557 -0
- package/src/panel/index.html +1233 -64
- package/src/panel/server.js +487 -40
- package/src/planrun.js +61 -5
- package/src/promptregisters.js +3 -0
- package/src/providers.js +82 -0
- package/src/replay.js +128 -4
- package/src/replayio.js +14 -3
- package/src/run.js +48 -2
- package/src/userrun.js +75 -8
- package/types/authorflow.d.ts +4 -21
- package/types/panel/authorroutes.d.ts +65 -0
- package/types/panel/authorsession.d.ts +61 -0
- package/types/panel/server.d.ts +33 -3
- package/types/providers.d.ts +34 -0
- package/types/replay.d.ts +31 -0
- package/types/replayio.d.ts +12 -1
- package/types/run.d.ts +26 -1
- package/types/userrun.d.ts +17 -0
package/src/userrun.js
CHANGED
|
@@ -30,6 +30,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
30
30
|
import { execFileSync, spawn } from 'node:child_process';
|
|
31
31
|
import { join, resolve, dirname } from 'node:path';
|
|
32
32
|
import { runJob } from './run.js';
|
|
33
|
+
import { moneyWithDraft } from './replay.js';
|
|
33
34
|
import { jobSpecHash, resolveWorkerModel, validateJob } from './job.js';
|
|
34
35
|
import { readShimArm } from './readshim.js';
|
|
35
36
|
import { closeStagesOf } from './plan.js';
|
|
@@ -242,6 +243,15 @@ class ExitSignal extends Error {
|
|
|
242
243
|
* @property {string|null} [model] the raw `--model` tier name (`sonnet`/`haiku`), or null for the default
|
|
243
244
|
* @property {string|null} [readShim] the raw `--read-shim` arm name, or null for the default
|
|
244
245
|
* @property {string|null} [scout] the raw `--scout` value (`on`/`off`), or null for the default
|
|
246
|
+
* @property {string|null} [draftSpentUsd] the raw `--draft-spent-usd` value (hamr's
|
|
247
|
+
* ruling 2026-09-28, "one cap covers drafting + run"), or null when the job never
|
|
248
|
+
* drafted through the panel/`bareloop author`. Parsed and validated in `execute`
|
|
249
|
+
* (finite, >= 0, else refused) — never coerced silently, the same param-guard
|
|
250
|
+
* class `--read-shim`/`--scout` already are.
|
|
251
|
+
* @property {boolean} [draftSpendIncomplete] the raw `--draft-spend-incomplete` presence-
|
|
252
|
+
* flag (hamr's ruling 2026-09-28, 2nd addendum) — whether the drafting fold above was a
|
|
253
|
+
* FLOOR, never an exact figure. A new field beside `draftSpentUsd`, same param-guard
|
|
254
|
+
* class, no value to misread.
|
|
245
255
|
* @property {string|null} [registry] a bridge registry directory
|
|
246
256
|
* @property {string|null} [workflow] the registry's workflow name (defaults to `spec.job`)
|
|
247
257
|
* @property {string|null} [approve] the spec hash being signed; omitted previews only
|
|
@@ -263,6 +273,8 @@ class ExitSignal extends Error {
|
|
|
263
273
|
* @property {string|null} model
|
|
264
274
|
* @property {string|null} readShim
|
|
265
275
|
* @property {string|null} scout
|
|
276
|
+
* @property {string|null} draftSpentUsd
|
|
277
|
+
* @property {boolean} draftSpendIncomplete
|
|
266
278
|
* @property {string|null} registry
|
|
267
279
|
* @property {string|null} workflow
|
|
268
280
|
* @property {string|null} approve
|
|
@@ -394,6 +406,39 @@ async function execute(ctx) {
|
|
|
394
406
|
* so a resume never silently drops it and runs the default under the arm's label. */
|
|
395
407
|
const SCOUT_TAIL = scoutArg !== null && !SCOUT ? ' --scout off' : '';
|
|
396
408
|
|
|
409
|
+
// --draft-spent-usd (hamr's ruling 2026-09-28, "one cap covers drafting +
|
|
410
|
+
// run") — runner territory exactly like --read-shim/--scout above: the
|
|
411
|
+
// spec's signed budgetUsd is unaffected, this only shrinks THIS leg's own
|
|
412
|
+
// enforced ceiling. Same param-guard class and the same reason: an
|
|
413
|
+
// unreadable value here would either silently widen the ceiling (garbage
|
|
414
|
+
// read as 0) or silently narrow it (garbage read as Infinity) — neither is
|
|
415
|
+
// acceptable, so this refuses at argv, before the approval gate, rather
|
|
416
|
+
// than coercing.
|
|
417
|
+
const draftSpentArg = ctx.draftSpentUsd ?? null;
|
|
418
|
+
let DRAFT_SPENT_USD = 0;
|
|
419
|
+
if (draftSpentArg !== null) {
|
|
420
|
+
const v = Number(draftSpentArg);
|
|
421
|
+
if (draftSpentArg.trim() === '' || !Number.isFinite(v) || v < 0) {
|
|
422
|
+
err(`--draft-spent-usd "${draftSpentArg}" is not a finite number >= 0 — refusing rather than coercing it into $0 (which would widen the ceiling) or letting a NaN poison the ceiling arithmetic`);
|
|
423
|
+
throw new ExitSignal(2);
|
|
424
|
+
}
|
|
425
|
+
DRAFT_SPENT_USD = v;
|
|
426
|
+
}
|
|
427
|
+
// --draft-spend-incomplete (hamr's ruling 2026-09-28, 2nd addendum) — a NEW
|
|
428
|
+
// field beside --draft-spent-usd, never a value inside it: whether the
|
|
429
|
+
// drafting fold above was EXACT. Boolean presence-flag, same class as
|
|
430
|
+
// --review-door above (no argument, no value to misread as 0/Infinity).
|
|
431
|
+
// Meaningless alone (no drafting fold to qualify) but never refused for
|
|
432
|
+
// being passed without --draft-spent-usd — a re-invocation tail that always
|
|
433
|
+
// pairs the two never needs a second guard to prove it did.
|
|
434
|
+
const DRAFT_SPEND_INCOMPLETE = ctx.draftSpendIncomplete === true;
|
|
435
|
+
/** every re-invocation this script PRINTS carries the drafting fold, the
|
|
436
|
+
* same SHIM_TAIL/SCOUT_TAIL rule: a resume that dropped it would silently
|
|
437
|
+
* WIDEN the ceiling back up by the drafting spend on its next leg (and
|
|
438
|
+
* dropping the incomplete flag would silently turn a floor into an exact
|
|
439
|
+
* figure on the next leg's own job-start). */
|
|
440
|
+
const DRAFT_TAIL = DRAFT_SPENT_USD > 0 ? ` --draft-spent-usd ${DRAFT_SPENT_USD}${DRAFT_SPEND_INCOMPLETE ? ' --draft-spend-incomplete' : ''}` : '';
|
|
441
|
+
|
|
397
442
|
const WORKDIR = target.workdir;
|
|
398
443
|
const SEED = target.seed;
|
|
399
444
|
const specHash = jobSpecHash(spec);
|
|
@@ -1040,7 +1085,7 @@ async function execute(ctx) {
|
|
|
1040
1085
|
// right key only by accident of the runner's own generic error message,
|
|
1041
1086
|
// never from this hint. `providerEntry.envKey` is the same resolved name
|
|
1042
1087
|
// the real key check at launch (`:1157-1158` below) reads.
|
|
1043
|
-
const invoke = (/** @type {string} */ tail) => ` ${providerEntry.envKey}=... node scripts/run-u.mjs ${SELECTOR}${dead ? ` --resume ${RESUME}` : ''}${SHIM_TAIL}${SCOUT_TAIL}${tail} --approve ${specHash}`;
|
|
1088
|
+
const invoke = (/** @type {string} */ tail) => ` ${providerEntry.envKey}=... node scripts/run-u.mjs ${SELECTOR}${dead ? ` --resume ${RESUME}` : ''}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL}${tail} --approve ${specHash}`;
|
|
1044
1089
|
/** the door the operator has already picked, as flags — hoisted out of the else
|
|
1045
1090
|
* below so the inhibitor line at the bottom can print the WHOLE command rather
|
|
1046
1091
|
* than a shape the operator has to assemble. Empty on an ordinary run and on the
|
|
@@ -1205,8 +1250,8 @@ async function execute(ctx) {
|
|
|
1205
1250
|
out(`\nPAUSED BY YOU — nothing was run and nothing was spent. The checkpoint stands exactly as it was: the work is on the run's own branch, the plan and the money are where the paused leg left them.`);
|
|
1206
1251
|
out(` keeps ${PAUSE_TTL_MS / 86_400_000} days from the pause on the record — after that the checkpoint expires on its own, and nothing has to be decided today to let that happen`);
|
|
1207
1252
|
out(' resume the SAME runid, whenever you want, with the door you pick then:');
|
|
1208
|
-
out(` node scripts/run-u.mjs ${SELECTOR} --resume ${RESUME}${SHIM_TAIL}${SCOUT_TAIL} --decide accept --approve ${specHash}`);
|
|
1209
|
-
out(` node scripts/run-u.mjs ${SELECTOR} --resume ${RESUME}${SHIM_TAIL}${SCOUT_TAIL} --decide rerun --text "<what you want done differently>" --approve ${specHash}`);
|
|
1253
|
+
out(` node scripts/run-u.mjs ${SELECTOR} --resume ${RESUME}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL} --decide accept --approve ${specHash}`);
|
|
1254
|
+
out(` node scripts/run-u.mjs ${SELECTOR} --resume ${RESUME}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL} --decide rerun --text "<what you want done differently>" --approve ${specHash}`);
|
|
1210
1255
|
out(` read the same command with no --decide re-prints the evidence package you just looked at`);
|
|
1211
1256
|
throw new ExitSignal(0);
|
|
1212
1257
|
}
|
|
@@ -1458,6 +1503,14 @@ async function execute(ctx) {
|
|
|
1458
1503
|
// driver's plan is a row nobody can audit from its own log.
|
|
1459
1504
|
out(` ${READ_SHIM_LABEL}`);
|
|
1460
1505
|
out(` ${SCOUT_LABEL}`);
|
|
1506
|
+
// hamr's ruling 2026-09-28 — advertised = enforced: the banner states the
|
|
1507
|
+
// SIGNED cap above unconditionally (it never changes, same spec hash), and
|
|
1508
|
+
// this line states the drafting fold whenever there is one, so the number
|
|
1509
|
+
// this run actually enforces (Cap $ − drafting spent) is never a second,
|
|
1510
|
+
// silent figure a reader has to compute themselves.
|
|
1511
|
+
if (DRAFT_SPENT_USD > 0) {
|
|
1512
|
+
out(` drafting spent $${DRAFT_SPENT_USD.toFixed(4)} — this run's own enforced ceiling is $${(spec.budgetUsd - DRAFT_SPENT_USD).toFixed(4)} of the signed $${spec.budgetUsd}`);
|
|
1513
|
+
}
|
|
1461
1514
|
|
|
1462
1515
|
// F67 — the OUTSIDE watchdog, started before the run and sharing nothing with it.
|
|
1463
1516
|
// Every guard bareloop had lived inside this process, and ms3197n8/ms3jh76q proved
|
|
@@ -1642,6 +1695,8 @@ async function execute(ctx) {
|
|
|
1642
1695
|
closeDir: spineDir,
|
|
1643
1696
|
readShim: READ_SHIM,
|
|
1644
1697
|
scout: SCOUT,
|
|
1698
|
+
draftSpentUsd: DRAFT_SPENT_USD,
|
|
1699
|
+
draftSpendComplete: !DRAFT_SPEND_INCOMPLETE,
|
|
1645
1700
|
// RESUME: the money and the wall the halted run already burned are FOLDED IN (so
|
|
1646
1701
|
// the signed ceiling cannot widen by being re-invoked), and the checkpoint it
|
|
1647
1702
|
// reached is handed over so the plan is reloaded rather than re-drafted and the
|
|
@@ -1731,7 +1786,15 @@ async function execute(ctx) {
|
|
|
1731
1786
|
const writes = audit.filter((e) => e.decision === 'allow' && (e.action?.type === 'write' || e.action?.type === 'edit'));
|
|
1732
1787
|
|
|
1733
1788
|
out(`\noutcome ${outcome}`);
|
|
1734
|
-
|
|
1789
|
+
// hamr's ruling 2026-09-28 — the drafting share (this leg's own job-start
|
|
1790
|
+
// record) rides beside the run's own spend via the ONE shared formatter,
|
|
1791
|
+
// so this line never disagrees with the panel's Run tab/Runs list about
|
|
1792
|
+
// what "$X of $Y" means for a job that drafted through the panel/author.
|
|
1793
|
+
const jobStartRec = events.find((e) => e.type === 'job-start');
|
|
1794
|
+
const legDraftSpentUsd = typeof jobStartRec?.draftSpentUsd === 'number' && jobStartRec.draftSpentUsd > 0 ? jobStartRec.draftSpentUsd : null;
|
|
1795
|
+
const legDraftSpendComplete = legDraftSpentUsd === null ? null : jobStartRec.draftSpendComplete !== false;
|
|
1796
|
+
const spendCore = je?.spentUsd == null ? 'UNKNOWN' : `${je.spendComplete === false ? '≥' : ''}$${je.spentUsd.toFixed(4)}`;
|
|
1797
|
+
out(`spent ${moneyWithDraft(spendCore, legDraftSpentUsd, spec.budgetUsd, legDraftSpendComplete)}`);
|
|
1735
1798
|
// FOLDED, exactly like the money line above it: on a resume the cap governs both legs
|
|
1736
1799
|
// together, and a leg-only wall next to a folded spend is two framings on one cap with
|
|
1737
1800
|
// no label to tell them apart (F83). The leg stays on the line beside it.
|
|
@@ -1768,7 +1831,7 @@ async function execute(ctx) {
|
|
|
1768
1831
|
out(` trend ${mh.trend} — ${mh.reading}`);
|
|
1769
1832
|
out(` lever ${mh.lever}`);
|
|
1770
1833
|
for (const o of mh.options ?? []) out(` · ${o}`);
|
|
1771
|
-
out(` resume node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL} --approve <the NEW hash after you edit budgetUsd>`);
|
|
1834
|
+
out(` resume node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL} --approve <the NEW hash after you edit budgetUsd>`);
|
|
1772
1835
|
out(' (the top-up is yours to sign — nothing in the run may widen its own budget)');
|
|
1773
1836
|
}
|
|
1774
1837
|
// A STALL is a checkpoint too (hamr's go, 2026-08-13). Its own escalation prints one
|
|
@@ -1778,7 +1841,7 @@ async function execute(ctx) {
|
|
|
1778
1841
|
// the hash already approved is the hash that resumes.
|
|
1779
1842
|
if (outcome === 'step-stalled') {
|
|
1780
1843
|
out('\nSTALL HALT — the model stopped producing rounds and reissuing the call did not recover it. The tree, the plan and the steps already finished STAND.');
|
|
1781
|
-
out(` resume node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL} --approve ${specHash}`);
|
|
1844
|
+
out(` resume node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL} --approve ${specHash}`);
|
|
1782
1845
|
out(' (no spec edit, so the hash is unchanged — this re-enters at the stalled step and re-pays for none of the ones before it)');
|
|
1783
1846
|
out(' (if the allowance is what actually ran out underneath the stall, that preview says so and refuses — it is read there, not asserted here)');
|
|
1784
1847
|
}
|
|
@@ -1799,7 +1862,7 @@ async function execute(ctx) {
|
|
|
1799
1862
|
out(` died ${total === null ? 'before a plan was accepted — nothing paid is re-payable'
|
|
1800
1863
|
: done >= total ? `at the close — all ${total} step(s) finished`
|
|
1801
1864
|
: `in step ${done + 1} of ${total}`}`);
|
|
1802
|
-
out(` resume node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL} --approve ${specHash}`);
|
|
1865
|
+
out(` resume node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL} --approve ${specHash}`);
|
|
1803
1866
|
out(' (no spec edit, so the hash is unchanged — this re-enters at the recorded step and re-pays for none of the ones before it)');
|
|
1804
1867
|
out(' (if the allowance is what actually ran out underneath the transport fault, that preview says so and refuses — it is read there, not asserted here)');
|
|
1805
1868
|
}
|
|
@@ -1820,7 +1883,7 @@ async function execute(ctx) {
|
|
|
1820
1883
|
]);
|
|
1821
1884
|
out(' clock STOPPED — the wall does not run while a person is reading (W-2), and this leg\'s elapsed is what folds into the resume');
|
|
1822
1885
|
out('');
|
|
1823
|
-
const answer = (/** @type {string} */ tail) => `node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL}${tail} --approve ${specHash}`;
|
|
1886
|
+
const answer = (/** @type {string} */ tail) => `node scripts/run-u.mjs ${SELECTOR} --resume ${runid}${SHIM_TAIL}${SCOUT_TAIL}${DRAFT_TAIL}${tail} --approve ${specHash}`;
|
|
1824
1887
|
for (const l of doorLines({
|
|
1825
1888
|
rerun: answer(' --decide rerun --text "<what you want done differently>"'),
|
|
1826
1889
|
accept: answer(' --decide accept'),
|
|
@@ -2057,6 +2120,8 @@ function buildCtx(mode, opts) {
|
|
|
2057
2120
|
model: opts.model ?? null,
|
|
2058
2121
|
readShim: opts.readShim ?? null,
|
|
2059
2122
|
scout: opts.scout ?? null,
|
|
2123
|
+
draftSpentUsd: opts.draftSpentUsd ?? null,
|
|
2124
|
+
draftSpendIncomplete: opts.draftSpendIncomplete === true,
|
|
2060
2125
|
registry: opts.registry ?? null,
|
|
2061
2126
|
workflow: opts.workflow ?? null,
|
|
2062
2127
|
approve: opts.approve ?? null,
|
|
@@ -2275,6 +2340,8 @@ export async function main(argv, deps = {}) {
|
|
|
2275
2340
|
model: argFlag('model'),
|
|
2276
2341
|
readShim: argFlag('read-shim'),
|
|
2277
2342
|
scout: argFlag('scout'),
|
|
2343
|
+
draftSpentUsd: argFlag('draft-spent-usd'),
|
|
2344
|
+
draftSpendIncomplete: argv.includes('--draft-spend-incomplete'),
|
|
2278
2345
|
registry: argFlag('registry'),
|
|
2279
2346
|
workflow: argFlag('workflow'),
|
|
2280
2347
|
approve: argFlag('approve'),
|
package/types/authorflow.d.ts
CHANGED
|
@@ -649,7 +649,10 @@ export function confirmPrompt({ answers, questions, labels, facts, listing, writ
|
|
|
649
649
|
* generate: Function, book: ReturnType<typeof makeCostBook>,
|
|
650
650
|
* ask: (step: {kind: string, [k: string]: any}) => Promise<string|null>,
|
|
651
651
|
* onPhase?: (phase: string, data?: any) => void, mode?: 'tool'|'text',
|
|
652
|
-
* worseThanBefore?: string}} o
|
|
652
|
+
* worseThanBefore?: string}} o retired field (hamr's ruling 2026-09-28):
|
|
653
|
+
* `worseThanBefore` is never asked any more and defaults to `''` — a
|
|
654
|
+
* caller may still pass a string through (kept for a future, non-ask
|
|
655
|
+
* source of the same constraint), it just never comes from an ask() step.
|
|
653
656
|
* @returns {Promise<{ok: boolean,
|
|
654
657
|
* stop: null|'cap-halt'|'pricing-red'|'provider-red'|'artifact-red'|'confirm-abandoned'|'confirm-restart',
|
|
655
658
|
* rounds: number,
|
|
@@ -1013,26 +1016,6 @@ export const QUESTION_SETS: Record<string, {
|
|
|
1013
1016
|
* @type {Record<string, string|null>}
|
|
1014
1017
|
*/
|
|
1015
1018
|
export const CLASS_STATEMENTS: Record<string, string | null>;
|
|
1016
|
-
/**
|
|
1017
|
-
* Asked ONLY for a repo source (ruling 2 addendum's D7) — a plain folder or a
|
|
1018
|
-
* URL has no "before" a repo's own history gives it to compare against. This
|
|
1019
|
-
* is the OLD Q5 wording, verbatim ("What would make you say this came back
|
|
1020
|
-
* worse than before?"), which M3 piece 3's reshape folded into Guardrails —
|
|
1021
|
-
* it survives here as its OWN field because the confirm turn (ruling 5's
|
|
1022
|
-
* 2026-09-13 addendum) includes it as a constraint only when the person
|
|
1023
|
-
* actually gave one, and folding it silently into the Guardrails free-text
|
|
1024
|
-
* slot would lose that "present or absent" distinction. An empty answer is
|
|
1025
|
-
* "nothing beyond what Guardrails already said" and is not required — see
|
|
1026
|
-
* the frozen-wording test below: this phrase appears NOWHERE in
|
|
1027
|
-
* {@link questionsFor}'s own sets, so a caller cannot double-ask it by
|
|
1028
|
-
* reading the wrong table.
|
|
1029
|
-
*/
|
|
1030
|
-
export const WORSE_THAN_BEFORE_FIELD: Readonly<{
|
|
1031
|
-
id: "worseThanBefore";
|
|
1032
|
-
kind: "mechanical";
|
|
1033
|
-
label: "Worse than before";
|
|
1034
|
-
prompt: "What would make you say this came back worse than before?";
|
|
1035
|
-
}>;
|
|
1036
1019
|
/**
|
|
1037
1020
|
* Asked ONLY when `src/detectlang.js`'s `detectLanguage` reports its
|
|
1038
1021
|
* `ambiguous` outcome — two supported languages' manifests at the same
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** @returns {string} a fresh per-process token — never persisted, never logged */
|
|
2
|
+
export function mintToken(): string;
|
|
3
|
+
/**
|
|
4
|
+
* `true` when a request may reach ANY route in this file — the token header
|
|
5
|
+
* plus an Origin/Host that names this exact bind address. `req.headers`
|
|
6
|
+
* lower-cases header names (node's own `http` behaviour), so this reads
|
|
7
|
+
* lower-case names throughout.
|
|
8
|
+
* @param {import('node:http').IncomingMessage} req
|
|
9
|
+
* @param {{token: string, port: number}} o
|
|
10
|
+
* @returns {{ok: true}|{ok: false, reason: string}}
|
|
11
|
+
*/
|
|
12
|
+
export function checkHumanGuard(req: import("node:http").IncomingMessage, { token, port }: {
|
|
13
|
+
token: string;
|
|
14
|
+
port: number;
|
|
15
|
+
}): {
|
|
16
|
+
ok: true;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
reason: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Builds this file's own request handler, holding its session store in a
|
|
23
|
+
* closure (one per `createPanelServer` call, exactly like `handleRequest`'s
|
|
24
|
+
* own module holds none — this file owns the state `src/panel/server.js`
|
|
25
|
+
* deliberately has none of).
|
|
26
|
+
* @param {{ port: number, token: string, env?: Record<string,string|undefined>,
|
|
27
|
+
* sessionsRoot?: string, spawnFn?: typeof realSpawn, bareloopBin?: string,
|
|
28
|
+
* jobsDir?: string, fetchImpl?: typeof fetch }} opts
|
|
29
|
+
*/
|
|
30
|
+
export function createAuthorRoutes(opts: {
|
|
31
|
+
port: number;
|
|
32
|
+
token: string;
|
|
33
|
+
env?: Record<string, string | undefined>;
|
|
34
|
+
sessionsRoot?: string;
|
|
35
|
+
spawnFn?: typeof realSpawn;
|
|
36
|
+
bareloopBin?: string;
|
|
37
|
+
jobsDir?: string;
|
|
38
|
+
fetchImpl?: typeof fetch;
|
|
39
|
+
}): {
|
|
40
|
+
handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
|
|
41
|
+
sessions: Map<string, any>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* THE ONLY function in this file (or anywhere in the panel) that spawns a
|
|
45
|
+
* run. Refuses unless the session is `prepared` AND the hash the request
|
|
46
|
+
* carried matches the session's OWN `signing.json` `specHash` exactly — the
|
|
47
|
+
* click carries the hash (per the mockup's own "the person never has to copy
|
|
48
|
+
* it to sign"), and this is where it is checked, not trusted.
|
|
49
|
+
* @param {ReturnType<typeof createSession>} session
|
|
50
|
+
* @param {string} claimedHash
|
|
51
|
+
* @param {{env: any, spawnFn: typeof realSpawn, bareloopBin: string, sessionsRoot?: string}} o
|
|
52
|
+
* @returns {{ok: boolean, error?: string, job?: string}}
|
|
53
|
+
*/
|
|
54
|
+
export function signRun(session: ReturnType<typeof createSession>, claimedHash: string, o: {
|
|
55
|
+
env: any;
|
|
56
|
+
spawnFn: typeof realSpawn;
|
|
57
|
+
bareloopBin: string;
|
|
58
|
+
sessionsRoot?: string;
|
|
59
|
+
}): {
|
|
60
|
+
ok: boolean;
|
|
61
|
+
error?: string;
|
|
62
|
+
job?: string;
|
|
63
|
+
};
|
|
64
|
+
import { spawn as realSpawn } from 'node:child_process';
|
|
65
|
+
import { createSession } from './authorsession.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `true` when `jobs/<name>.json` already exists in the repo's own jobs/ dir
|
|
3
|
+
* (resolved from THIS module's own on-disk location, never `process.cwd()`,
|
|
4
|
+
* the same rule `src/panel/server.js`'s `jobsDir()` follows) — P3 is new-jobs-
|
|
5
|
+
* only (Q4=A), so a name collision refuses at $0 rather than silently
|
|
6
|
+
* overwriting or authoring a second spec under one name.
|
|
7
|
+
* @param {string} name
|
|
8
|
+
* @param {{jobsDir?: string}} [opts] test seam
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function jobNameTaken(name: string, opts?: {
|
|
12
|
+
jobsDir?: string;
|
|
13
|
+
}): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* `$0` validation of the job card fields the server must check BEFORE it
|
|
16
|
+
* creates a session or spends anything — never inside the async pipeline,
|
|
17
|
+
* so a bad card refuses synchronously, in the same HTTP response.
|
|
18
|
+
* @param {any} card
|
|
19
|
+
* @returns {{ok: true}|{ok: false, error: string}}
|
|
20
|
+
*/
|
|
21
|
+
export function validateJobCard(card: any): {
|
|
22
|
+
ok: true;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* ONE session's engine — created already RUNNING (the caller awaits nothing;
|
|
29
|
+
* the pipeline drives itself, reporting through `state`). `state.pendingAsk`
|
|
30
|
+
* is the ONE thing a person answers next; `answer()`/`send()`/`revise()`/
|
|
31
|
+
* `signPrepare()` are the only ways in.
|
|
32
|
+
* @param {any} card the validated job card (see {@link validateJobCard})
|
|
33
|
+
* @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, timeoutMs?: number,
|
|
34
|
+
* scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
|
|
35
|
+
* prepareSigningFn?: Function}} [deps] the last five are TEST SEAMS ONLY — see the note
|
|
36
|
+
* just above where each is read, below.
|
|
37
|
+
* @returns {any} the session object
|
|
38
|
+
*/
|
|
39
|
+
export function createSession(card: any, deps?: {
|
|
40
|
+
env?: Record<string, string | undefined>;
|
|
41
|
+
sessionsRoot?: string;
|
|
42
|
+
timeoutMs?: number;
|
|
43
|
+
scout?: any;
|
|
44
|
+
generate?: Function;
|
|
45
|
+
confirmGenerate?: Function;
|
|
46
|
+
authorFn?: Function;
|
|
47
|
+
prepareSigningFn?: Function;
|
|
48
|
+
}): any;
|
|
49
|
+
/**
|
|
50
|
+
* The job card's "Model" select — a fixed, small, source-grounded mapping
|
|
51
|
+
* from a display id to the `{provider, baseUrl?}` pair `src/interviewrun.js`
|
|
52
|
+
* itself documents (that script's own `--provider openai-api --base-url
|
|
53
|
+
* https://api.deepseek.com/v1` usage line for DeepSeek): the panel builds no
|
|
54
|
+
* provider table of its own, this is a UI convenience over the SAME two
|
|
55
|
+
* provider identities the CLI already resolves through `resolveProvider`.
|
|
56
|
+
* @type {Readonly<Record<string, {provider: string, baseUrl?: string}>>}
|
|
57
|
+
*/
|
|
58
|
+
export const MODEL_OPTIONS: Readonly<Record<string, {
|
|
59
|
+
provider: string;
|
|
60
|
+
baseUrl?: string;
|
|
61
|
+
}>>;
|
package/types/panel/server.d.ts
CHANGED
|
@@ -200,13 +200,24 @@ export function getRunJob(runid: string, opts?: {
|
|
|
200
200
|
* separately from {@link createPanelServer} so tests can drive it without a
|
|
201
201
|
* real listening socket where that is simpler (most path-safety/405 tests
|
|
202
202
|
* still go through a real socket, per the build spec).
|
|
203
|
+
*
|
|
204
|
+
* PANEL-BUILD.md P3: this file's own GET/HEAD-only rule stands for every
|
|
205
|
+
* route it owns; `/api/author/*` is the ONE family of routes that may be
|
|
206
|
+
* POSTed to, and it is handled entirely by {@link createAuthorRoutes}
|
|
207
|
+
* (`src/panel/authorroutes.js`) — a separate module so this file's own
|
|
208
|
+
* "read-only, by construction" header comment stays true of everything else
|
|
209
|
+
* in it. `opts.authorRoutes` is absent on every P1/P2 caller (read-only
|
|
210
|
+
* tests, and the CLI's own `panelMain` when no author routes are wired) —
|
|
211
|
+
* only `createPanelServer` below always supplies one.
|
|
203
212
|
* @param {import('node:http').IncomingMessage} req
|
|
204
213
|
* @param {import('node:http').ServerResponse} res
|
|
205
|
-
* @param {{ home?: string, port: number }} opts
|
|
214
|
+
* @param {{ home?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes> }} opts
|
|
206
215
|
*/
|
|
207
216
|
export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
|
|
208
217
|
home?: string;
|
|
209
218
|
port: number;
|
|
219
|
+
token?: string;
|
|
220
|
+
authorRoutes?: ReturnType<typeof createAuthorRoutes>;
|
|
210
221
|
}): void;
|
|
211
222
|
/**
|
|
212
223
|
* Start the panel server. Binds `127.0.0.1` ONLY. A taken port is a LOUD,
|
|
@@ -214,15 +225,33 @@ export function handleRequest(req: import("node:http").IncomingMessage, res: imp
|
|
|
214
225
|
* header). Resolves once actually listening; rejects on a bind error
|
|
215
226
|
* (including `EADDRINUSE`) with a `.port` field on the error for the
|
|
216
227
|
* caller's message.
|
|
217
|
-
*
|
|
218
|
-
*
|
|
228
|
+
*
|
|
229
|
+
* PANEL-BUILD.md P3: a fresh {@link mintToken} token and one {@link
|
|
230
|
+
* createAuthorRoutes} instance are built here, ONCE per server start, and
|
|
231
|
+
* held for the server's lifetime — `opts.env`/`opts.sessionsRoot`/
|
|
232
|
+
* `opts.spawnFn`/`opts.bareloopBin` are test seams the author routes take
|
|
233
|
+
* (a real caller passes none of them and {@link createAuthorRoutes} defaults
|
|
234
|
+
* each one to the real environment/spawn/binary, this file itself touching
|
|
235
|
+
* none of them, and the real
|
|
236
|
+
* `~/.config/bareloop/panel-sessions`, the real `child_process.spawn`, and
|
|
237
|
+
* this package's own `bin/bareloop.mjs`).
|
|
238
|
+
* @param {{ port?: number, home?: string, env?: Record<string,string|undefined>,
|
|
239
|
+
* sessionsRoot?: string, spawnFn?: (...a: any[]) => any, bareloopBin?: string,
|
|
240
|
+
* fetchImpl?: typeof fetch }} [opts]
|
|
241
|
+
* @returns {Promise<{ server: import('node:http').Server, port: number, token: string, close: () => Promise<void> }>}
|
|
219
242
|
*/
|
|
220
243
|
export function createPanelServer(opts?: {
|
|
221
244
|
port?: number;
|
|
222
245
|
home?: string;
|
|
246
|
+
env?: Record<string, string | undefined>;
|
|
247
|
+
sessionsRoot?: string;
|
|
248
|
+
spawnFn?: (...a: any[]) => any;
|
|
249
|
+
bareloopBin?: string;
|
|
250
|
+
fetchImpl?: typeof fetch;
|
|
223
251
|
}): Promise<{
|
|
224
252
|
server: import("node:http").Server;
|
|
225
253
|
port: number;
|
|
254
|
+
token: string;
|
|
226
255
|
close: () => Promise<void>;
|
|
227
256
|
}>;
|
|
228
257
|
/**
|
|
@@ -263,3 +292,4 @@ export const DIED_MTIME_MS: number;
|
|
|
263
292
|
* 2026-09-25). */
|
|
264
293
|
export const ROUNDS_PAGE_DEFAULT: 50;
|
|
265
294
|
export const ROUNDS_PAGE_MAX: 200;
|
|
295
|
+
import { createAuthorRoutes } from './authorroutes.js';
|
package/types/providers.d.ts
CHANGED
|
@@ -98,6 +98,40 @@ export function buildRunnerProviders({ providerName, apiKey, model, tierModels,
|
|
|
98
98
|
providerFor: (tier: string) => any;
|
|
99
99
|
judgeProvider: any;
|
|
100
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* $0 reachability check: a GET against `providerName`'s own models-list
|
|
103
|
+
* endpoint, server-side, with the key in a header — NEVER a completion
|
|
104
|
+
* request, so it never spends a token (PANEL-BUILD.md P3 build item 7: "the
|
|
105
|
+
* person must get feedback BEFORE drafting if the chosen model can't
|
|
106
|
+
* work"). Reused, unmodified, by the panel's model-readiness route today
|
|
107
|
+
* (`src/panel/authorroutes.js`) and by the future Settings providers
|
|
108
|
+
* table's own "test" column (PANEL-BUILD.md P4) — ONE function, not two
|
|
109
|
+
* hand-typed copies of the same probe.
|
|
110
|
+
*
|
|
111
|
+
* NEVER returns the key, never logs it — the key only ever appears in a
|
|
112
|
+
* request header sent straight to the provider's own host.
|
|
113
|
+
* @param {object} o
|
|
114
|
+
* @param {string} o.providerName
|
|
115
|
+
* @param {string} o.apiKey
|
|
116
|
+
* @param {string} [o.model] when given, the response is also checked for this model id
|
|
117
|
+
* @param {string} [o.baseUrl]
|
|
118
|
+
* @param {typeof fetch} [o.fetchImpl] TEST SEAM — defaults to the real global `fetch`
|
|
119
|
+
* @param {number} [o.timeoutMs] defaults to 4000 — a flaky endpoint must never hang the caller
|
|
120
|
+
* @returns {Promise<{reachable: boolean, modelListed: boolean|null, status: string, note: string|null}>}
|
|
121
|
+
*/
|
|
122
|
+
export function checkProviderReachable({ providerName, apiKey, model, baseUrl, fetchImpl, timeoutMs, }: {
|
|
123
|
+
providerName: string;
|
|
124
|
+
apiKey: string;
|
|
125
|
+
model?: string | undefined;
|
|
126
|
+
baseUrl?: string | undefined;
|
|
127
|
+
fetchImpl?: typeof fetch | undefined;
|
|
128
|
+
timeoutMs?: number | undefined;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
reachable: boolean;
|
|
131
|
+
modelListed: boolean | null;
|
|
132
|
+
status: string;
|
|
133
|
+
note: string | null;
|
|
134
|
+
}>;
|
|
101
135
|
/**
|
|
102
136
|
* The launch-time warning lines for an ADMITTED-PENDING-PROBE provider, or
|
|
103
137
|
* `null` when the provider has paid for its own end-to-end probe (and for an
|
package/types/replay.d.ts
CHANGED
|
@@ -89,6 +89,8 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
|
|
|
89
89
|
stopReason: string | null;
|
|
90
90
|
spentUsd: any;
|
|
91
91
|
spendComplete: boolean;
|
|
92
|
+
draftSpentUsd: any;
|
|
93
|
+
draftSpendComplete: boolean | null;
|
|
92
94
|
floorReasons: string[];
|
|
93
95
|
transportRetryCount: number;
|
|
94
96
|
transportRetriesOutsideWindows: number;
|
|
@@ -239,6 +241,35 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
|
|
|
239
241
|
memoryCache: any;
|
|
240
242
|
skipped: number;
|
|
241
243
|
};
|
|
244
|
+
/**
|
|
245
|
+
* hamr's ruling 2026-09-28 ("one cap covers drafting + run") — the ONE
|
|
246
|
+
* spend display for a run that may carry a drafting share, so every surface
|
|
247
|
+
* that shows a run's money (the CLI's own `spent` line, the panel's Run tab
|
|
248
|
+
* summary/counters, the Runs list row, the Audit header, the Job tab) reads
|
|
249
|
+
* the same string rather than each hand-composing its own. `spendCore` is
|
|
250
|
+
* already-formatted text (`money(...)`, or a `died`/floor line's "at least
|
|
251
|
+
* $X") — this only decides whether to append the `(<draft> drafting)`
|
|
252
|
+
* clause and the `of $<cap>` tail, never re-derives the money itself.
|
|
253
|
+
* Unchanged (`spendCore` alone, or `spendCore of $cap`) when there is no
|
|
254
|
+
* drafting share — a run without one prints exactly what it always did.
|
|
255
|
+
*
|
|
256
|
+
* hamr's ruling 2026-09-28 (2nd addendum) — an INCOMPLETE drafting fold
|
|
257
|
+
* (`draftSpendComplete === false`) is a floor, not a total, and F6's rule
|
|
258
|
+
* ("unpriced is never free, a floor never dresses as exact") applies here
|
|
259
|
+
* exactly as it does to `spendCore` itself: the drafting clause reads `at
|
|
260
|
+
* least $X drafting`, and because a reader summing the two figures would
|
|
261
|
+
* otherwise take the LEADING number as exact too, `spendCore` itself also
|
|
262
|
+
* gains the `at least ` prefix — unless it already carries one (a `died`
|
|
263
|
+
* floor, or an already-prefixed caller), never doubled.
|
|
264
|
+
* @param {string} spendCore already-formatted spend text (e.g. `$3.71` or `at least $2.00`)
|
|
265
|
+
* @param {number|null} draftSpentUsd the drafting share, or null when there isn't one
|
|
266
|
+
* @param {number|null} [budgetUsd] the signed cap, appended as `of $X.XX` when given
|
|
267
|
+
* @param {boolean|null} [draftSpendComplete] whether the drafting fold above was EXACT;
|
|
268
|
+
* `null`/`undefined`/`true` all read as exact (never a new enum value elsewhere) —
|
|
269
|
+
* only an explicit `false` triggers the floor wording.
|
|
270
|
+
* @returns {string}
|
|
271
|
+
*/
|
|
272
|
+
export function moneyWithDraft(spendCore: string, draftSpentUsd: number | null, budgetUsd?: number | null, draftSpendComplete?: boolean | null): string;
|
|
242
273
|
/**
|
|
243
274
|
* Render a {@link replayRun} summary as one printable page: a header block
|
|
244
275
|
* (goal/shape/signed/branch/outcome/spent), a timeline (steps or iterations,
|
package/types/replayio.d.ts
CHANGED
|
@@ -42,7 +42,8 @@ export function resolveSiblings(spinePath: string): {
|
|
|
42
42
|
* Read one run's spine (+ its gate-audit sidecar, when present) off disk and
|
|
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
|
-
* @param {{preParsedSpine?: {records: any[], skipped: number}, skipAudit?: boolean
|
|
45
|
+
* @param {{preParsedSpine?: {records: any[], skipped: number}, skipAudit?: boolean,
|
|
46
|
+
* auditPathOverride?: string|null}} [opts]
|
|
46
47
|
* `preParsedSpine`: avoids a second parse of the same file when the caller
|
|
47
48
|
* ({@link listSpines}) already read it once to run {@link looksLikeSpine}.
|
|
48
49
|
* `skipAudit` (PR #23 review item 5, 2026-08-26, carried over): a
|
|
@@ -50,6 +51,15 @@ export function resolveSiblings(spinePath: string): {
|
|
|
50
51
|
* `summarizeForAllLine` reads none of the fields that sidecar feeds inside
|
|
51
52
|
* `replayRun`, so reading every run's sidecar in a directory listing was
|
|
52
53
|
* pure unused I/O, multiplied by every spine found.
|
|
54
|
+
* `auditPathOverride` (build item 7, 2026-09-28): when a caller has ALREADY
|
|
55
|
+
* resolved the real audit path through the one shared resolver
|
|
56
|
+
* (`src/panel/server.js`'s `resolveAuditPathForRow`, which also covers a
|
|
57
|
+
* still-LIVE run's during-run sidecar location — this function's own
|
|
58
|
+
* `resolveSiblings` call only ever finds the FINISHED-run convention), it
|
|
59
|
+
* passes that path here (`null` included) instead of letting this
|
|
60
|
+
* function re-derive a finished-only path on its own. `undefined` (the
|
|
61
|
+
* default) keeps the original resolveSiblings-only behaviour — every
|
|
62
|
+
* existing caller is unaffected.
|
|
53
63
|
* @returns {ReturnType<typeof replayRun>}
|
|
54
64
|
*/
|
|
55
65
|
export function replayOne(spinePath: string, opts?: {
|
|
@@ -58,6 +68,7 @@ export function replayOne(spinePath: string, opts?: {
|
|
|
58
68
|
skipped: number;
|
|
59
69
|
};
|
|
60
70
|
skipAudit?: boolean;
|
|
71
|
+
auditPathOverride?: string | null;
|
|
61
72
|
}): ReturnType<typeof replayRun>;
|
|
62
73
|
/**
|
|
63
74
|
* List every spine found directly in `dir` (not recursive — same as the
|
package/types/run.d.ts
CHANGED
|
@@ -73,6 +73,29 @@
|
|
|
73
73
|
* load gate refuses one: a refusal returns the distinct `recipe-stale` outcome with zero
|
|
74
74
|
* spend rather than silently drafting cold, because starting a paid run on a decision
|
|
75
75
|
* nobody made is the same class of error as widening a cap to manufacture a green.
|
|
76
|
+
* @param {number} [opts.draftSpentUsd=0] hamr's ruling 2026-09-28 ("one cap covers
|
|
77
|
+
* drafting + run") — money the AUTHORING pipeline (`authorCloseForJob`/
|
|
78
|
+
* `prepareSigning`, run under the SAME signed `budgetUsd` as its ceiling) already
|
|
79
|
+
* spent on THIS job before it was ever signed. The signed `budgetUsd` stays the
|
|
80
|
+
* one number in the spec hash; this shrinks the RUN's own enforced ceiling by
|
|
81
|
+
* what drafting already took, so the two phases never spend more than the one
|
|
82
|
+
* cap together. Deliberately NOT `priorSpentUsd` — that key means "a previous
|
|
83
|
+
* ATTEMPT of this same run died and folded its spend forward" and is read by
|
|
84
|
+
* `readResume`/`replay.js`'s `resumed` flag; a drafting spend is not a resume and
|
|
85
|
+
* must never make an ordinary first run of a job read as one. It is also never
|
|
86
|
+
* folded into `spentUsd` (the run's own ledger, cumulative-so-far and what a
|
|
87
|
+
* resume's `priorSpentUsd` seeds) or into `engagementSpentUsd` — drafting is a
|
|
88
|
+
* DIFFERENT phase's spend, reported beside the run's, never inside it. A garbage
|
|
89
|
+
* value (non-finite, negative) is read as 0, belted exactly like `priorSpentUsd`.
|
|
90
|
+
* @param {boolean} [opts.draftSpendComplete=true] hamr's ruling 2026-09-28 — was the
|
|
91
|
+
* `draftSpentUsd` fold above EXACT? The authoring pipeline's own metered-call list can
|
|
92
|
+
* go unpriced (F6) exactly like a resumed attempt's can, and the unknown does not heal
|
|
93
|
+
* by folding forward. A NEW field beside `draftSpentUsd`, never a new state inside
|
|
94
|
+
* `spendComplete`/`priorSpendComplete` — those answer "is THIS run's own ledger exact,"
|
|
95
|
+
* this answers "was the OTHER phase's ledger exact," and the two must never merge into
|
|
96
|
+
* one flag or a floor drafting spend could read as an exact run. Read only when
|
|
97
|
+
* `draftSpentUsd` itself is emitted; a garbage value is read as `true` (the same belt
|
|
98
|
+
* `priorSpendComplete` gets — an unreadable flag never manufactures a false floor).
|
|
76
99
|
* @param {number} [opts.priorSpentUsd=0] RESUME (module C) — money a PREVIOUS, killed
|
|
77
100
|
* attempt of this same try already spent. It seeds the ledger, so the restarted
|
|
78
101
|
* attempt runs under the REMAINDER of the signed `budgetUsd` and the terminal
|
|
@@ -181,7 +204,7 @@
|
|
|
181
204
|
* deliberately NOT in the resumable/checkpoint set (`src/reuse.js`) — an
|
|
182
205
|
* unknown in-flight state is not a known-safe resume point.
|
|
183
206
|
*/
|
|
184
|
-
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
|
|
207
|
+
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, draftSpentUsd, draftSpendComplete, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
|
|
185
208
|
approvals: unknown;
|
|
186
209
|
workdir: string;
|
|
187
210
|
provider: any;
|
|
@@ -201,6 +224,8 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
|
|
|
201
224
|
closeTimeoutMs?: number | undefined;
|
|
202
225
|
closeDir?: string | null | undefined;
|
|
203
226
|
bridge?: unknown;
|
|
227
|
+
draftSpentUsd?: number | undefined;
|
|
228
|
+
draftSpendComplete?: boolean | undefined;
|
|
204
229
|
priorSpentUsd?: number | undefined;
|
|
205
230
|
priorSpendComplete?: boolean | undefined;
|
|
206
231
|
priorWallMs?: number | undefined;
|
package/types/userrun.d.ts
CHANGED
|
@@ -92,6 +92,21 @@ export type RunOpts = {
|
|
|
92
92
|
* the raw `--scout` value (`on`/`off`), or null for the default
|
|
93
93
|
*/
|
|
94
94
|
scout?: string | null | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* the raw `--draft-spent-usd` value (hamr's
|
|
97
|
+
* ruling 2026-09-28, "one cap covers drafting + run"), or null when the job never
|
|
98
|
+
* drafted through the panel/`bareloop author`. Parsed and validated in `execute`
|
|
99
|
+
* (finite, >= 0, else refused) — never coerced silently, the same param-guard
|
|
100
|
+
* class `--read-shim`/`--scout` already are.
|
|
101
|
+
*/
|
|
102
|
+
draftSpentUsd?: string | null | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* the raw `--draft-spend-incomplete` presence-
|
|
105
|
+
* flag (hamr's ruling 2026-09-28, 2nd addendum) — whether the drafting fold above was a
|
|
106
|
+
* FLOOR, never an exact figure. A new field beside `draftSpentUsd`, same param-guard
|
|
107
|
+
* class, no value to misread.
|
|
108
|
+
*/
|
|
109
|
+
draftSpendIncomplete?: boolean | undefined;
|
|
95
110
|
/**
|
|
96
111
|
* a bridge registry directory
|
|
97
112
|
*/
|
|
@@ -129,6 +144,8 @@ export type RunCtx = {
|
|
|
129
144
|
model: string | null;
|
|
130
145
|
readShim: string | null;
|
|
131
146
|
scout: string | null;
|
|
147
|
+
draftSpentUsd: string | null;
|
|
148
|
+
draftSpendIncomplete: boolean;
|
|
132
149
|
registry: string | null;
|
|
133
150
|
workflow: string | null;
|
|
134
151
|
approve: string | null;
|