bareloop 0.27.0 → 0.29.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 +114 -0
- package/bareloop.context.md +245 -33
- package/package.json +1 -1
- package/src/authorreadout.js +323 -0
- package/src/authorrun.js +1345 -0
- package/src/behaviour.js +5 -2
- package/src/cli.js +190 -8
- package/src/index.js +13 -1
- package/src/interviewrun.js +842 -0
- package/src/panel/index.html +1910 -0
- package/src/panel/server.js +1527 -0
- package/src/planrun.js +1 -1
- package/src/replay.js +632 -13
- package/src/replayio.js +159 -0
- package/src/reuse.js +1 -1
- package/src/run.js +1 -1
- package/src/runlist.js +281 -0
- package/src/u-patient.js +73 -0
- package/src/u-readout.js +451 -0
- package/src/userrun.js +2299 -0
- package/types/authorreadout.d.ts +172 -0
- package/types/authorrun.d.ts +37 -0
- package/types/behaviour.d.ts +5 -3
- package/types/cli.d.ts +2 -1
- package/types/index.d.ts +2 -1
- package/types/interviewrun.d.ts +46 -0
- package/types/panel/server.d.ts +265 -0
- package/types/planrun.d.ts +2 -2
- package/types/replay.d.ts +104 -6
- package/types/replayio.d.ts +93 -0
- package/types/reuse.d.ts +2 -2
- package/types/run.d.ts +2 -2
- package/types/runlist.d.ts +112 -0
- package/types/u-patient.d.ts +39 -0
- package/types/u-readout.d.ts +247 -0
- package/types/userrun.d.ts +141 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{goal?: string|null, closeDecl?: any}} spec the RESOLVED spec — the bytes
|
|
3
|
+
* that get hashed and signed, never the operator's draft and never the authored
|
|
4
|
+
* half on its own.
|
|
5
|
+
* @returns {string[]} one console line each, in print order.
|
|
6
|
+
*/
|
|
7
|
+
export function declarationLines({ goal, closeDecl }: {
|
|
8
|
+
goal?: string | null;
|
|
9
|
+
closeDecl?: any;
|
|
10
|
+
}): string[];
|
|
11
|
+
/**
|
|
12
|
+
* F176'S FALLBACK, SAID OUT LOUD — the revise ladder kept the NEWEST SOUND
|
|
13
|
+
* declaration rather than the last one it happened to try, because "kept the
|
|
14
|
+
* last accepted revision" is not the same claim as "kept the best revision"
|
|
15
|
+
* (docs/logs/FINDINGS.md F176). Absent on every run where the fallback never
|
|
16
|
+
* fired (the overwhelming majority), which is why this returns an EMPTY list
|
|
17
|
+
* rather than a "no fallback" line — a green close has nothing to say here and
|
|
18
|
+
* printing it every run would be noise, not information.
|
|
19
|
+
* @param {{fellBack?: {from: string, to: string, brokenStages: string[]}|null}|null} authoring `authored.authoring`
|
|
20
|
+
* @returns {string[]}
|
|
21
|
+
*/
|
|
22
|
+
export function fellBackLines(authoring: {
|
|
23
|
+
fellBack?: {
|
|
24
|
+
from: string;
|
|
25
|
+
to: string;
|
|
26
|
+
brokenStages: string[];
|
|
27
|
+
} | null;
|
|
28
|
+
} | null): string[];
|
|
29
|
+
/**
|
|
30
|
+
* THE TWO SIGNED JUDGED ARTIFACTS, as a person reads them before signing
|
|
31
|
+
* (softgreen modules 4+5). Absent on a close that judges nothing, which is why
|
|
32
|
+
* this returns an EMPTY list rather than a "no card" line: a green close has no
|
|
33
|
+
* rubric and saying so every run is noise, not information.
|
|
34
|
+
*
|
|
35
|
+
* The CARD is printed in full — it is short, it is the whole standard the judge
|
|
36
|
+
* will hold the work to, and it is the signer's own words. The CASES are printed
|
|
37
|
+
* as a ROSTER (id, verdict, itemized reds) rather than in full: ten real source
|
|
38
|
+
* artifacts is a page of code the terminal cannot usefully show, and what a
|
|
39
|
+
* signer checks at this surface is that both polarities are there and that each
|
|
40
|
+
* red names something they recognise. The artifacts themselves are in the spec
|
|
41
|
+
* file, which is named right above this block.
|
|
42
|
+
* @param {{closeDecl?: any}} spec the RESOLVED spec
|
|
43
|
+
* @returns {string[]}
|
|
44
|
+
*/
|
|
45
|
+
export function rubricLines({ closeDecl }: {
|
|
46
|
+
closeDecl?: any;
|
|
47
|
+
}): string[];
|
|
48
|
+
/**
|
|
49
|
+
* THE CONFIRM TURN'S OPEN QUESTIONS (PRD item 33 M3 piece 4, ruling 5's D3
|
|
50
|
+
* addendum) — shown at the SIGNING readout, from `authored.json`'s own
|
|
51
|
+
* `confirmed.openQuestions`, never added to the signed spec format itself
|
|
52
|
+
* (D4: the closeDecl's `notes` are the model's own record; this is the
|
|
53
|
+
* person's "fix" text from a round that hit the 2-round cap, ruling 5,
|
|
54
|
+
* passed to the composer verbatim rather than spending a 3rd call, D3). A
|
|
55
|
+
* close with none is shown as `(none)` rather than an empty line — the
|
|
56
|
+
* absence is itself part of what the signer reads before signing.
|
|
57
|
+
* @param {{openQuestions?: string[]}|null} confirmed `authored.confirmed`
|
|
58
|
+
* @returns {string[]}
|
|
59
|
+
*/
|
|
60
|
+
export function openQuestionLines(confirmed: {
|
|
61
|
+
openQuestions?: string[];
|
|
62
|
+
} | null): string[];
|
|
63
|
+
/**
|
|
64
|
+
* F175's open half, said at the SIGNING readout too (run mu0voeo4, hamr's
|
|
65
|
+
* 2026-09-16 ruling) — every question the plan raised that "Confirm" or
|
|
66
|
+
* "Type the goal yourself" forced the person to answer inline, from
|
|
67
|
+
* `authored.json`'s own `confirmed.answeredQuestions`. Shown only when
|
|
68
|
+
* non-empty (the overwhelming majority of plans raise no question at all,
|
|
69
|
+
* and printing an empty block every run would be noise, not information) —
|
|
70
|
+
* unlike {@link openQuestionLines}, an empty list here says nothing, because
|
|
71
|
+
* absence is the ordinary case, not a fact the signer needs flagged.
|
|
72
|
+
* @param {{answeredQuestions?: string[]}|null} confirmed `authored.confirmed`
|
|
73
|
+
* @returns {string[]}
|
|
74
|
+
*/
|
|
75
|
+
export function answeredQuestionLines(confirmed: {
|
|
76
|
+
answeredQuestions?: string[];
|
|
77
|
+
} | null): string[];
|
|
78
|
+
/**
|
|
79
|
+
* THE CALIBRATION GATE'S OWN READOUT — the one gate that spends money, and the
|
|
80
|
+
* one whose rows a signer has to read case by case.
|
|
81
|
+
*
|
|
82
|
+
* ITEMIZED, never an aggregate percentage: "9/10" tells a person nothing about
|
|
83
|
+
* which line of their own rubric is wrong, and the fix for a miss is a card line
|
|
84
|
+
* or a corrected case (§4.3's ceiling). A CASUALTY prints under its own name and
|
|
85
|
+
* says so, because a dead judge is not a failed set.
|
|
86
|
+
* @param {any} calibration `signing.gates.calibration`
|
|
87
|
+
* @returns {string[]}
|
|
88
|
+
*/
|
|
89
|
+
export function calibrationLines(calibration: any): string[];
|
|
90
|
+
/**
|
|
91
|
+
* THE AUTHORING CEILING, parsed from `--budget`. Housed here for the same reason
|
|
92
|
+
* the readout above is: the runner is a script, and a rule no test can reach is a
|
|
93
|
+
* rule nothing checks.
|
|
94
|
+
*
|
|
95
|
+
* ABSENT IS UNBOUNDED, and that is the only way to get there. A malformed value
|
|
96
|
+
* is an ERROR rather than a fallback: `--budget banana` silently collapsing to
|
|
97
|
+
* "no ceiling" is precisely the failure this flag exists to prevent, wearing the
|
|
98
|
+
* operator's own typo. Zero and negatives are rejected on the same rule — a
|
|
99
|
+
* ceiling that can fund nothing is a typo for "unbounded" far more often than it
|
|
100
|
+
* is a request, and the honest way to ask for nothing is not to ask.
|
|
101
|
+
*
|
|
102
|
+
* There is NO DEFAULT anywhere in this function, deliberately: a defaulted cap is
|
|
103
|
+
* a silent second ceiling (the `maxWallMs` precedent), and the number is the
|
|
104
|
+
* operator's to set or to decline.
|
|
105
|
+
* @param {string|null} raw the argv value, or null when the flag was not given
|
|
106
|
+
* @returns {{ceilingUsd: number|null, error: string|null}}
|
|
107
|
+
*/
|
|
108
|
+
export function parseCeiling(raw: string | null): {
|
|
109
|
+
ceilingUsd: number | null;
|
|
110
|
+
error: string | null;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* The ceiling's header line. An UNBOUNDED run is ANNOUNCED — printed before the
|
|
114
|
+
* provider is built, so it reaches stdout ahead of the first paid byte rather
|
|
115
|
+
* than being inferred from the total afterwards. An unbounded run is legal; it is
|
|
116
|
+
* never allowed to be an accident.
|
|
117
|
+
* @param {number|null} ceilingUsd
|
|
118
|
+
*/
|
|
119
|
+
export function ceilingLine(ceilingUsd: number | null): string;
|
|
120
|
+
/**
|
|
121
|
+
* ONE PROGRESS PHASE, as a console line.
|
|
122
|
+
*
|
|
123
|
+
* The pipeline used to run up to ~15 minutes saying nothing between
|
|
124
|
+
* `author-start` and its result — a real survey ladder, a real declaration
|
|
125
|
+
* ladder, and a real toolchain per close stage, all inside one await. Silence
|
|
126
|
+
* and a hang are the same bytes on a terminal, and the operator's only lever is
|
|
127
|
+
* to kill a run that may be working.
|
|
128
|
+
*
|
|
129
|
+
* It says WHAT is happening, never how far along it is: there is no honest
|
|
130
|
+
* fraction to print (a survey attempt has no progress, and a suite stage
|
|
131
|
+
* finishes when it finishes), and an invented percentage is a number nobody
|
|
132
|
+
* measured. What it does carry is the two facts a person waiting actually uses —
|
|
133
|
+
* which phase, and how long the last thing took.
|
|
134
|
+
*
|
|
135
|
+
* `durationMs` prints as UNKNOWN when it is absent rather than as 0 (F6's rule,
|
|
136
|
+
* in its time form): a stage whose duration was never measured did not take no
|
|
137
|
+
* time.
|
|
138
|
+
* @param {string} phase @param {any} [data]
|
|
139
|
+
* @returns {string}
|
|
140
|
+
*/
|
|
141
|
+
export function phaseLine(phase: string, data?: any): string;
|
|
142
|
+
/**
|
|
143
|
+
* ONE crashed authoring run, ready for the spine.
|
|
144
|
+
*
|
|
145
|
+
* It exists because a crash used to leave NO body. `run-author.mjs` awaited the
|
|
146
|
+
* paid pipeline at the top level, so a throw anywhere inside it went to the
|
|
147
|
+
* operator's terminal as an unhandled rejection and the spine kept exactly one
|
|
148
|
+
* line — `author-start` — which is byte-for-byte what a run still in flight looks
|
|
149
|
+
* like. The terminal scrolls; the spine is the record.
|
|
150
|
+
*
|
|
151
|
+
* The STACK is the evidence (the incident that minted this had no idea WHERE the
|
|
152
|
+
* throw came from), and a stack is the one field here that can be arbitrarily
|
|
153
|
+
* long and can quote anything the process had open. So it goes through `scrubRaw`
|
|
154
|
+
* — the ONE persist boundary for a raw blob — which redacts it over the same
|
|
155
|
+
* `SECRET_PATTERNS` inventory the validator reds on and bounds it with the bound
|
|
156
|
+
* announcing its own size (F28). Nothing here is hand-rolled: a second spelling
|
|
157
|
+
* of either rule is how the two drift.
|
|
158
|
+
*
|
|
159
|
+
* A non-Error throw (a string, a plain object) is honest rather than coerced into
|
|
160
|
+
* an Error shape: it has no `name`, so `name` is `null`, and `String(err)` is what
|
|
161
|
+
* lands in the raw.
|
|
162
|
+
* @param {unknown} err whatever was thrown
|
|
163
|
+
* @returns {{name: string|null, message: string|null, code: string|null,
|
|
164
|
+
* raw: ReturnType<typeof scrubRaw>}}
|
|
165
|
+
*/
|
|
166
|
+
export function crashRecord(err: unknown): {
|
|
167
|
+
name: string | null;
|
|
168
|
+
message: string | null;
|
|
169
|
+
code: string | null;
|
|
170
|
+
raw: ReturnType<typeof scrubRaw>;
|
|
171
|
+
};
|
|
172
|
+
import { scrubRaw } from './text.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} Deps
|
|
3
|
+
* @property {Record<string,string|undefined>} [env]
|
|
4
|
+
* @property {NodeJS.ReadStream} [stdin]
|
|
5
|
+
* @property {NodeJS.WritableStream} [stdout]
|
|
6
|
+
* @property {NodeJS.WritableStream} [stderr]
|
|
7
|
+
* @property {string} [invokedAs] the command name this run was actually
|
|
8
|
+
* reached through, for the usage message ONLY — `scripts/run-author.mjs`
|
|
9
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
10
|
+
* `'bareloop author'` so a person who typed that is told to re-run THAT,
|
|
11
|
+
* never a script path they never invoked and may not have on disk.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Parses this script's own argv shape and runs the authoring pipeline
|
|
15
|
+
* (scout, declaration loop, D9's gates) against the given (or real)
|
|
16
|
+
* stdin/stdout. `scripts/run-author.mjs` is the thin adapter over this, the
|
|
17
|
+
* same shape `bin/bareloop.mjs` already is over `src/cli.js`'s own
|
|
18
|
+
* `main(argv, deps)`.
|
|
19
|
+
* @param {string[]} argv
|
|
20
|
+
* @param {Deps} [deps]
|
|
21
|
+
* @returns {Promise<number>} an exit code — never calls process.exit
|
|
22
|
+
*/
|
|
23
|
+
export function main(argv: string[], deps?: Deps): Promise<number>;
|
|
24
|
+
export type Deps = {
|
|
25
|
+
env?: Record<string, string | undefined> | undefined;
|
|
26
|
+
stdin?: NodeJS.ReadStream | undefined;
|
|
27
|
+
stdout?: NodeJS.WritableStream | undefined;
|
|
28
|
+
stderr?: NodeJS.WritableStream | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* the command name this run was actually
|
|
31
|
+
* reached through, for the usage message ONLY — `scripts/run-author.mjs`
|
|
32
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
33
|
+
* `'bareloop author'` so a person who typed that is told to re-run THAT,
|
|
34
|
+
* never a script path they never invoked and may not have on disk.
|
|
35
|
+
*/
|
|
36
|
+
invokedAs?: string | undefined;
|
|
37
|
+
};
|
package/types/behaviour.d.ts
CHANGED
|
@@ -39,8 +39,10 @@ export function runBehaviour(events: Array<{
|
|
|
39
39
|
* Render a {@link runBehaviour} summary as the agreed printable block
|
|
40
40
|
* (2–4 short lines). A zero-call run formats plainly ("0 tool calls") rather
|
|
41
41
|
* than a NaN-laced percentage — an empty run is a complete, reportable
|
|
42
|
-
* answer, not a division error.
|
|
43
|
-
*
|
|
42
|
+
* answer, not a division error. `summary === null` means the caller never
|
|
43
|
+
* had a gate-audit sidecar to read at all (see `replayRun`'s `auditAvailable`
|
|
44
|
+
* option) — that reads as "unknown", never coerced to a 0-call summary.
|
|
45
|
+
* @param {ReturnType<typeof runBehaviour>|null} summary
|
|
44
46
|
* @returns {string}
|
|
45
47
|
*/
|
|
46
|
-
export function formatBehaviour(summary: ReturnType<typeof runBehaviour>): string;
|
|
48
|
+
export function formatBehaviour(summary: ReturnType<typeof runBehaviour> | null): string;
|
package/types/cli.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* code, so `bin/bareloop.mjs` can set `process.exitCode` and let node flush
|
|
4
4
|
* queued stdout on its own (F-something: `process.exit()` can discard it).
|
|
5
5
|
* @param {string[]} argv
|
|
6
|
-
* @param {{ env?: Record<string,string|undefined>, stdout?: any, stderr?: any, cwd?: string, provider?: any, providerFor?: any, judgeProvider?: any, judgeModel?: string|null, now?: () => number, stdin?: any }} deps
|
|
6
|
+
* @param {{ env?: Record<string,string|undefined>, stdout?: any, stderr?: any, cwd?: string, provider?: any, providerFor?: any, judgeProvider?: any, judgeModel?: string|null, now?: () => number, stdin?: any, runlistHome?: string }} deps
|
|
7
7
|
* @returns {Promise<number>}
|
|
8
8
|
*/
|
|
9
9
|
export function main(argv: string[], deps?: {
|
|
@@ -17,4 +17,5 @@ export function main(argv: string[], deps?: {
|
|
|
17
17
|
judgeModel?: string | null;
|
|
18
18
|
now?: () => number;
|
|
19
19
|
stdin?: any;
|
|
20
|
+
runlistHome?: string;
|
|
20
21
|
}): Promise<number>;
|
package/types/index.d.ts
CHANGED
|
@@ -24,7 +24,8 @@ export { scrubRaw, RAW_PERSIST_MAX } from "./text.js";
|
|
|
24
24
|
export { authorClose, MAX_REVISIONS, questionsFor, requiredAnswersFor } from "./authorflow.js";
|
|
25
25
|
export { validateCloseDecl, runDeclaredStages, declaredStages, HUMAN_PAUSE } from "./declaredclose.js";
|
|
26
26
|
export { runBehaviour, formatBehaviour } from "./behaviour.js";
|
|
27
|
-
export { replayRun, formatReplay } from "./replay.js";
|
|
27
|
+
export { replayRun, formatReplay, summarizeForAllLine, formatAllLines } from "./replay.js";
|
|
28
|
+
export { replayOne, listSpines, readHistoryLog } from "./replayio.js";
|
|
28
29
|
export { runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind, AUTHORED_SPEC_FIELDS } from "./authorjob.js";
|
|
29
30
|
export { PROMPT_REGISTERS, isPromptFile } from "./promptregisters.js";
|
|
30
31
|
export { exportBundle, bundleHash, readBundle, resolveBundleSpec, checkEnvelope, bless, verifyBlessing, appendHistory, checkBundleDeps } from "./bundle.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} Deps
|
|
3
|
+
* @property {Record<string,string|undefined>} [env]
|
|
4
|
+
* @property {NodeJS.ReadStream} [stdin]
|
|
5
|
+
* @property {NodeJS.WritableStream} [stdout]
|
|
6
|
+
* @property {NodeJS.WritableStream} [stderr]
|
|
7
|
+
* @property {typeof realSpawnSync} [spawnSync] the TEST SEAM for the final
|
|
8
|
+
* paid-step offer — a caller that supplies this never actually spawns
|
|
9
|
+
* `scripts/run-author.mjs`.
|
|
10
|
+
* @property {string} [invokedAs] the command name this run was actually
|
|
11
|
+
* reached through, for the usage message ONLY — `scripts/run-interview.mjs`
|
|
12
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
13
|
+
* `'bareloop interview'` so a person who typed that is told to re-run THAT,
|
|
14
|
+
* never a script path they never invoked and may not have on disk.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Parses this script's own argv shape and runs the interview against the
|
|
18
|
+
* given (or real) stdin/stdout. `scripts/run-interview.mjs` is the thin
|
|
19
|
+
* adapter over this, the same shape `bin/bareloop.mjs` already is over
|
|
20
|
+
* `src/cli.js`'s own `main(argv, deps)`.
|
|
21
|
+
* @param {string[]} argv
|
|
22
|
+
* @param {Deps} [deps]
|
|
23
|
+
* @returns {Promise<number>} an exit code — never calls process.exit
|
|
24
|
+
*/
|
|
25
|
+
export function main(argv: string[], deps?: Deps): Promise<number>;
|
|
26
|
+
export type Deps = {
|
|
27
|
+
env?: Record<string, string | undefined> | undefined;
|
|
28
|
+
stdin?: NodeJS.ReadStream | undefined;
|
|
29
|
+
stdout?: NodeJS.WritableStream | undefined;
|
|
30
|
+
stderr?: NodeJS.WritableStream | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* the TEST SEAM for the final
|
|
33
|
+
* paid-step offer — a caller that supplies this never actually spawns
|
|
34
|
+
* `scripts/run-author.mjs`.
|
|
35
|
+
*/
|
|
36
|
+
spawnSync?: typeof realSpawnSync | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* the command name this run was actually
|
|
39
|
+
* reached through, for the usage message ONLY — `scripts/run-interview.mjs`
|
|
40
|
+
* supplies none (defaults to its own real invocation), `src/cli.js` passes
|
|
41
|
+
* `'bareloop interview'` so a person who typed that is told to re-run THAT,
|
|
42
|
+
* never a script path they never invoked and may not have on disk.
|
|
43
|
+
*/
|
|
44
|
+
invokedAs?: string | undefined;
|
|
45
|
+
};
|
|
46
|
+
import { spawnSync as realSpawnSync } from 'node:child_process';
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `[✓]`/`[✗]`/`[▶]` — the ONLY vocabulary a result is ever rendered in
|
|
3
|
+
* (auto-memory `ui-verdict-words.md`: never the words green/red/soft-green
|
|
4
|
+
* anywhere in the page). `null` (no `job-end` reached — a killed-mid-run or
|
|
5
|
+
* still-running spine) reads `▶` — the same "in progress / unresolved" glyph
|
|
6
|
+
* P1's own four-glyph vocabulary reserves for that state; a genuinely still-
|
|
7
|
+
* running attempt and an archived spine that never reached its own end are
|
|
8
|
+
* the same fact from this read-only side: no verdict has been recorded yet.
|
|
9
|
+
* @param {string|null} outcome
|
|
10
|
+
* @returns {'✓'|'✗'|'▶'}
|
|
11
|
+
*/
|
|
12
|
+
export function glyphForOutcome(outcome: string | null): "\u2713" | "\u2717" | "\u25B6";
|
|
13
|
+
/**
|
|
14
|
+
* `deterministic` (green) / `rubric` (soft-green) / `unknown` (a class this
|
|
15
|
+
* reader doesn't recognize) — the settled UI wording (PANEL-BUILD.md §6),
|
|
16
|
+
* never the internal `green`/`soft-green` spelling. Item 3 (2026-09-25): a
|
|
17
|
+
* spine with NO `verdictType` (pre-F117) is genuinely ambiguous UNLESS its
|
|
18
|
+
* own run date (`atIso`, the job-start ts) predates {@link
|
|
19
|
+
* SOFTGREEN_ADMITTED_ISO} — in that one case, `deterministic` was the only
|
|
20
|
+
* check type that could have run, so it is reported as such (not fabricated:
|
|
21
|
+
* derived from the frozen historical fact that soft-green did not exist
|
|
22
|
+
* yet), paired with {@link checkTypeTitle}'s tooltip explaining the
|
|
23
|
+
* derivation rather than presenting it as if it had been directly recorded.
|
|
24
|
+
* `atIso` is optional so every existing single-arg caller/test keeps its
|
|
25
|
+
* prior behavior (no date -> 'unknown', same as before this item).
|
|
26
|
+
* @param {string|null} verdictType
|
|
27
|
+
* @param {string|null} [atIso] the run's own job-start timestamp
|
|
28
|
+
* @returns {'deterministic'|'rubric'|'unknown'}
|
|
29
|
+
*/
|
|
30
|
+
export function checkTypeLabel(verdictType: string | null, atIso?: string | null): "deterministic" | "rubric" | "unknown";
|
|
31
|
+
/**
|
|
32
|
+
* The tooltip/title text for {@link checkTypeLabel}'s pre-softgreen-cutoff
|
|
33
|
+
* branch — `null` for every other case (a real `verdictType` needs no
|
|
34
|
+
* explanation; a genuinely unknown one has none to give).
|
|
35
|
+
* @param {string|null} verdictType
|
|
36
|
+
* @param {string|null} [atIso]
|
|
37
|
+
* @returns {string|null}
|
|
38
|
+
*/
|
|
39
|
+
export function checkTypeTitle(verdictType: string | null, atIso?: string | null): string | null;
|
|
40
|
+
/**
|
|
41
|
+
* `YYYY-MM-DD HH:MM` (local time, 24h, zero-padded) — the ONE
|
|
42
|
+
* timestamp-WITH-TIME format this page ever shows (a plain date elsewhere
|
|
43
|
+
* stays `YYYY-MM-DD`, e.g. `row.at.slice(0,10)`, untouched — that's a
|
|
44
|
+
* different, shorter field, not this helper's concern). Used instead of a
|
|
45
|
+
* locale-dependent `toLocaleString()` spelling (F195: the died "why" line
|
|
46
|
+
* read `9/9/2026, 11:49:40 AM`, inconsistent with every other date on the
|
|
47
|
+
* page) so a future second timestamp-with-time spot routes through the same
|
|
48
|
+
* one function rather than growing its own `toLocale*` call.
|
|
49
|
+
* @param {string|number|Date} ts
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
export function formatTimestamp(ts: string | number | Date): string;
|
|
53
|
+
/**
|
|
54
|
+
* `GET /api/runs` — every listed run, NEWEST FIRST BY `at` (never by file/
|
|
55
|
+
* append order — a backfill scan appends rows in sorted-PATH order, which
|
|
56
|
+
* does not track chronological `at` order; a plain `.reverse()` here once
|
|
57
|
+
* silently mis-sorted any list `appendRun` didn't build strictly
|
|
58
|
+
* chronologically), each enriched with a cheap ($0, `skipAudit:true`)
|
|
59
|
+
* summary. A row with an unparseable/missing `at` sorts last (Unix epoch 0),
|
|
60
|
+
* never crashes the sort or floats to the top. Ties (identical `at`) keep
|
|
61
|
+
* their original file order among themselves (stable sort) rather than an
|
|
62
|
+
* arbitrary one. The Workflows view (item 2, 2026-09-26 merge) groups this
|
|
63
|
+
* SAME payload by job client-side (`groupRunsByJob` in `index.html`) rather
|
|
64
|
+
* than a second server-side endpoint, so the glyph/checkType mapping is
|
|
65
|
+
* still computed in exactly one place.
|
|
66
|
+
* @param {{ home?: string }} [opts]
|
|
67
|
+
* @returns {any[]}
|
|
68
|
+
*/
|
|
69
|
+
export function listRuns(opts?: {
|
|
70
|
+
home?: string;
|
|
71
|
+
}): any[];
|
|
72
|
+
/**
|
|
73
|
+
* `GET /api/runs/:runid` — the full replay for the Run tab: steps (or
|
|
74
|
+
* iterations), counters, summary-box fields. Looks `runid` up in the run
|
|
75
|
+
* list FIRST and reads only the path stored there (path safety — see file
|
|
76
|
+
* header); `null` when the runid is not in the list at all (caller renders
|
|
77
|
+
* 404).
|
|
78
|
+
* @param {string} runid
|
|
79
|
+
* @param {{ home?: string }} [opts]
|
|
80
|
+
* @returns {any|null}
|
|
81
|
+
*/
|
|
82
|
+
export function getRunDetail(runid: string, opts?: {
|
|
83
|
+
home?: string;
|
|
84
|
+
}): any | null;
|
|
85
|
+
/**
|
|
86
|
+
* `GET /api/runs/:runid/audit` — the Audit tab's rows, off the run's own
|
|
87
|
+
* gate-audit sidecar (name-convention resolution, `src/replayio.js`'s
|
|
88
|
+
* `resolveSiblings`), scoped to this run's own ts window (see {@link
|
|
89
|
+
* runAuditWindow} — a sidecar can carry other runs' rows). A row's real
|
|
90
|
+
* fields (`ts`, `action.type`, `action.path`, `decision`) plus `partIndex`/
|
|
91
|
+
* `partLabel`/`attemptN` — see {@link makePartLookup} (build item C,
|
|
92
|
+
* 2026-09-26 rewrite): derived from the row's own ROUND's `seq` matched
|
|
93
|
+
* against `summary.parts`' own attempt windows, never phase-string guessing.
|
|
94
|
+
* `reason` names WHY `partIndex` is `null`: `'no-round'` (the row's `ts` falls
|
|
95
|
+
* before the very first model call) vs `'unassigned'` (a round exists but its
|
|
96
|
+
* seq matched no part window — an honest gap, never fabricated) vs `null`
|
|
97
|
+
* when `partIndex` is real.
|
|
98
|
+
* `round` — see
|
|
99
|
+
* {@link makeRoundLookup}. A `phase:'record'`/`action.type:'llm'` row (item
|
|
100
|
+
* 2: every MODEL CALL, `decision` always `null` on the real record) is
|
|
101
|
+
* marked `kind:'model-call'` with its own `result.{costUsd,tokens,
|
|
102
|
+
* durationMs}` surfaced directly (never re-derived); every other row is
|
|
103
|
+
* `kind:'tool-call'`, unchanged.
|
|
104
|
+
* `reason` distinguishes the two ways this can come back empty (item 1,
|
|
105
|
+
* 2026-09-25): `'no-sidecar'` — no gate-audit sidecar was ever written for
|
|
106
|
+
* this run (an older version, or a run-u/native path that never emitted one)
|
|
107
|
+
* — vs `'sidecar-empty'` — a sidecar file DOES exist but has zero rows FOR
|
|
108
|
+
* THIS RUN'S OWN WINDOW (the run made no tool/model calls the gate audited,
|
|
109
|
+
* or every row on disk belongs to a different run sharing the same
|
|
110
|
+
* filename). The client renders these as two different sentences; never a
|
|
111
|
+
* bare empty table for either.
|
|
112
|
+
* @param {string} runid
|
|
113
|
+
* @param {{ home?: string }} [opts]
|
|
114
|
+
* @returns {{runid: string, rows: any[], raw: string, empty: boolean, reason: 'no-sidecar'|'sidecar-empty'|null}|null}
|
|
115
|
+
*/
|
|
116
|
+
export function getRunAudit(runid: string, opts?: {
|
|
117
|
+
home?: string;
|
|
118
|
+
}): {
|
|
119
|
+
runid: string;
|
|
120
|
+
rows: any[];
|
|
121
|
+
raw: string;
|
|
122
|
+
empty: boolean;
|
|
123
|
+
reason: "no-sidecar" | "sidecar-empty" | null;
|
|
124
|
+
} | null;
|
|
125
|
+
/**
|
|
126
|
+
* `GET /api/runs/:runid/rounds?part=<index>&attempt=<n>&offset=&limit=`
|
|
127
|
+
* — build item C (2026-09-26 rewrite): the Audit tab's expandable attempt
|
|
128
|
+
* detail, built server-side in one endpoint and rendered lazily on expand.
|
|
129
|
+
* REPLACES the earlier step-id/occurrence/`FIX_LOOP_STEP_ID`-sentinel/
|
|
130
|
+
* `timelineKind` branching (three separate ways to resolve a window,
|
|
131
|
+
* deleted here) with ONE rule: `part` indexes directly into
|
|
132
|
+
* `summary.parts` (`src/replay.js`'s own ONE ordered part list — scout,
|
|
133
|
+
* plan, each step occurrence, replan, fix, judge, or a single `run` part on
|
|
134
|
+
* an old spine), and `attempt` indexes into THAT part's own `attempts` array
|
|
135
|
+
* — every part kind already carries at least one (a real per-attempt list
|
|
136
|
+
* for `step`/`fix`, or a single synthetic whole-part attempt for
|
|
137
|
+
* scout/plan/replan/judge/run, `src/replay.js`'s `wholePartAttempt`). No
|
|
138
|
+
* second windowing pass: each attempt's own `startSeq`/`endSeq` is read
|
|
139
|
+
* directly off `replayOne`'s already-computed summary.
|
|
140
|
+
* Tool calls per round reuse {@link getRunAudit}'s own already-scoped,
|
|
141
|
+
* already-round-tagged rows — filtered to this round's number, never
|
|
142
|
+
* re-derived. `toolLogSaved:false` (no gate-audit sidecar at all) still
|
|
143
|
+
* returns every round from the spine's own worker-round records, each with
|
|
144
|
+
* `toolCalls: null` (never a fabricated empty array — the client renders
|
|
145
|
+
* "tool calls: no log saved").
|
|
146
|
+
* Pagination: `offset`/`limit` slice the ATTEMPT's rounds list (never a
|
|
147
|
+
* round's own tool-call list) — `limit` defaults to {@link
|
|
148
|
+
* ROUNDS_PAGE_DEFAULT}, clamped to {@link ROUNDS_PAGE_MAX}; the response
|
|
149
|
+
* always carries `totalRounds` so the client can print "showing A–B of N",
|
|
150
|
+
* never a silently truncated list.
|
|
151
|
+
* `null` when the run isn't listed, the requested part/attempt index doesn't
|
|
152
|
+
* resolve to a real window, or the spine file is missing — the caller
|
|
153
|
+
* renders 404, same posture as {@link getRunAudit}/{@link getRunJob}.
|
|
154
|
+
* @param {string} runid
|
|
155
|
+
* @param {{ part?: number, attempt?: number, offset?: number, limit?: number }} query
|
|
156
|
+
* @param {{ home?: string }} [opts]
|
|
157
|
+
* @returns {any|null}
|
|
158
|
+
*/
|
|
159
|
+
export function getRunRounds(runid: string, query?: {
|
|
160
|
+
part?: number;
|
|
161
|
+
attempt?: number;
|
|
162
|
+
offset?: number;
|
|
163
|
+
limit?: number;
|
|
164
|
+
}, opts?: {
|
|
165
|
+
home?: string;
|
|
166
|
+
}): any | null;
|
|
167
|
+
/**
|
|
168
|
+
* `GET /api/runs/:runid/job` — the Job tab, filled from the first source
|
|
169
|
+
* that actually resolves (item 2, 2026-09-25), in order:
|
|
170
|
+
* (a) the bundle's own `spec.json` (a `bareloop run` bundle keeps one
|
|
171
|
+
* beside its `runs/` dir) — the pre-existing path, unchanged;
|
|
172
|
+
* (b) `jobs/<job>.json` in the repo's own jobs/ dir (resolved from the
|
|
173
|
+
* package root) — its `jobSpecHash` is compared against the run's own
|
|
174
|
+
* job-start `specHash`; a mismatch is shown, never hidden (the job may
|
|
175
|
+
* have been edited since this run signed it);
|
|
176
|
+
* (c) NEW (item 2, 2026-09-25): the run's own `resolved-spec.json`, found
|
|
177
|
+
* beside the spine via {@link sourceNearSpine} — a person-path (`--spec`
|
|
178
|
+
* or a source-seed-backed JOBS-table row) run's signed copy, never
|
|
179
|
+
* searched for, only read when the on-disk shape unambiguously names it;
|
|
180
|
+
* (d) the run's own job-start record (goal/model/budgetUsd/verdictType only
|
|
181
|
+
* — a narrower, unsigned record, `resolved:false`);
|
|
182
|
+
* else every spec-only field reads `'not recorded'`, never fabricated.
|
|
183
|
+
* `resolvedFrom` names which of these actually supplied the fields, shown in
|
|
184
|
+
* the page. `source`/`destination` are resolved SEPARATELY from the rest (any
|
|
185
|
+
* branch above may supply the goal/close/etc while carrying no source.json of
|
|
186
|
+
* its own) — from the source front door's manifest when `sourceNearSpine`
|
|
187
|
+
* finds one, else the run list's own recorded `patient` path for a `source`
|
|
188
|
+
* with no manifest, else `'not recorded'`. `success`/`guardrails`/`tools` come
|
|
189
|
+
* from the resolved SPEC only (a) (b) (c) — a job-start record and `none()`
|
|
190
|
+
* carry no close/writeScope/tools at all, so those stay `'not recorded'`.
|
|
191
|
+
* @param {string} runid
|
|
192
|
+
* @param {{ home?: string }} [opts]
|
|
193
|
+
* @returns {any|null}
|
|
194
|
+
*/
|
|
195
|
+
export function getRunJob(runid: string, opts?: {
|
|
196
|
+
home?: string;
|
|
197
|
+
}): any | null;
|
|
198
|
+
/**
|
|
199
|
+
* Handle one request against the read-only API + the page. Exported
|
|
200
|
+
* separately from {@link createPanelServer} so tests can drive it without a
|
|
201
|
+
* real listening socket where that is simpler (most path-safety/405 tests
|
|
202
|
+
* still go through a real socket, per the build spec).
|
|
203
|
+
* @param {import('node:http').IncomingMessage} req
|
|
204
|
+
* @param {import('node:http').ServerResponse} res
|
|
205
|
+
* @param {{ home?: string, port: number }} opts
|
|
206
|
+
*/
|
|
207
|
+
export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
|
|
208
|
+
home?: string;
|
|
209
|
+
port: number;
|
|
210
|
+
}): void;
|
|
211
|
+
/**
|
|
212
|
+
* Start the panel server. Binds `127.0.0.1` ONLY. A taken port is a LOUD,
|
|
213
|
+
* non-zero-exit failure — this never falls back to another port (see file
|
|
214
|
+
* header). Resolves once actually listening; rejects on a bind error
|
|
215
|
+
* (including `EADDRINUSE`) with a `.port` field on the error for the
|
|
216
|
+
* caller's message.
|
|
217
|
+
* @param {{ port?: number, home?: string }} [opts]
|
|
218
|
+
* @returns {Promise<{ server: import('node:http').Server, port: number, close: () => Promise<void> }>}
|
|
219
|
+
*/
|
|
220
|
+
export function createPanelServer(opts?: {
|
|
221
|
+
port?: number;
|
|
222
|
+
home?: string;
|
|
223
|
+
}): Promise<{
|
|
224
|
+
server: import("node:http").Server;
|
|
225
|
+
port: number;
|
|
226
|
+
close: () => Promise<void>;
|
|
227
|
+
}>;
|
|
228
|
+
/**
|
|
229
|
+
* `bareloop panel [--port N]` — the CLI entry (`src/cli.js` dispatch).
|
|
230
|
+
* Never picks a different port on collision (see file header): prints a
|
|
231
|
+
* loud, named error to stderr and returns 1.
|
|
232
|
+
* @param {string[]} argv
|
|
233
|
+
* @param {{ out: (s: string) => void, err: (s: string) => void, runlistHome?: string }} ctx
|
|
234
|
+
* @returns {Promise<number>}
|
|
235
|
+
*/
|
|
236
|
+
export function panelMain(argv: string[], ctx: {
|
|
237
|
+
out: (s: string) => void;
|
|
238
|
+
err: (s: string) => void;
|
|
239
|
+
runlistHome?: string;
|
|
240
|
+
}): Promise<number>;
|
|
241
|
+
/**
|
|
242
|
+
* A runid, as it appears in a URL path segment — never a raw filesystem
|
|
243
|
+
* path. `~` is included so a backfill-disambiguated runid (F197:
|
|
244
|
+
* `src/runlist.js`'s `backfillRuns`, e.g. `run~2` when two archived spines
|
|
245
|
+
* derive the same filename-based runid) is a legal, reachable id, never a
|
|
246
|
+
* 400.
|
|
247
|
+
*/
|
|
248
|
+
export const RUNID_RE: RegExp;
|
|
249
|
+
/** Default bind port (PANEL-BUILD.md P1: checked free on the build machine, not guaranteed elsewhere — the server still fails loudly on collision, see {@link createPanelServer}). */
|
|
250
|
+
export const DEFAULT_PORT: 4700;
|
|
251
|
+
/**
|
|
252
|
+
* Soft-green was admitted at commit 30df0f9 (2026-08-18) — before that,
|
|
253
|
+
* `green` was the only admissible verdict type, so a run started before this
|
|
254
|
+
* moment that carries no `verdictType` at all (pre-F117 spine) really was
|
|
255
|
+
* deterministic; there was no other check type it could have been. Tighten-
|
|
256
|
+
* only, named so a future change is a deliberate, visible edit (item 3,
|
|
257
|
+
* 2026-09-25).
|
|
258
|
+
*/
|
|
259
|
+
export const SOFTGREEN_ADMITTED_ISO: "2026-08-18T00:00:00.000Z";
|
|
260
|
+
export const DIED_MTIME_MS: number;
|
|
261
|
+
/** The default/max page size for {@link getRunRounds} — tighten-only,
|
|
262
|
+
* named so a future change is a deliberate, visible edit (item 5,
|
|
263
|
+
* 2026-09-25). */
|
|
264
|
+
export const ROUNDS_PAGE_DEFAULT: 50;
|
|
265
|
+
export const ROUNDS_PAGE_MAX: 200;
|
package/types/planrun.d.ts
CHANGED
|
@@ -365,7 +365,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
365
365
|
* it the deterministic name would collide with that very branch and the collision walk
|
|
366
366
|
* would mint a `-2` beside the progress the resume exists to keep. Absent is the cold
|
|
367
367
|
* path. A recorded branch that no longer exists is a STOP, never a fresh start.
|
|
368
|
-
* @param {{decision: string, text?: string}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
368
|
+
* @param {{decision: string, text?: string|null}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
369
369
|
* answer at a hitl pause (2026-08-12 §1, re-cut 2026-08-17: accept | rerun <text> | pause), carried in
|
|
370
370
|
* by the runner on the leg that RESUMES a paused run. Absent on every ordinary run and
|
|
371
371
|
* on the leg that pauses; it is never authored, never defaulted, and never inferred.
|
|
@@ -456,7 +456,7 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
456
456
|
resumeBranch?: string | null | undefined;
|
|
457
457
|
humanRuling?: {
|
|
458
458
|
decision: string;
|
|
459
|
-
text?: string;
|
|
459
|
+
text?: string | null;
|
|
460
460
|
} | null | undefined;
|
|
461
461
|
heldRuling?: {
|
|
462
462
|
decision: string;
|