bareloop 0.30.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 +82 -0
- package/bareloop.context.md +125 -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 +972 -49
- package/src/panel/server.js +425 -34
- package/src/planrun.js +61 -5
- package/src/promptregisters.js +3 -0
- package/src/providers.js +82 -0
- package/src/replay.js +80 -2
- 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/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;
|