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/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,138 @@ All notable changes to bareloop are documented here. Format:
|
|
|
5
5
|
[SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
|
|
6
6
|
feature lands, **patch** = docs, fixes, scaffolding.
|
|
7
7
|
|
|
8
|
+
## [0.31.0] — 2026-09-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Panel Chat authoring (P3)** — the Chat tab now drafts a job from the page: a job card
|
|
13
|
+
(source, goal, Model, `Cap $` / Time cap), a chat thread, and Send / Revise / **Sign &
|
|
14
|
+
run** actions, backed by new `POST /api/author/*` routes. Every POST needs the
|
|
15
|
+
per-server-start token plus an Origin/Host check (the human-click guard); the sign route
|
|
16
|
+
additionally requires the exact spec hash from the session's own signing file and is the
|
|
17
|
+
only path that ever spawns `run-u` (argv array, ends in `--approve <hash>`). A single
|
|
18
|
+
progress indicator (amber cycling dots, green `[done]`, red `[stopped]` with the reason)
|
|
19
|
+
replaces per-phase chat bubbles; step lines carry no who-label; a pending ask always wins
|
|
20
|
+
the label ("waiting for your OK" / "install needed" / "waiting for your answer"). The
|
|
21
|
+
page opens on the Chat tab.
|
|
22
|
+
- **Install-needed pause + Check again** — a repo copy with missing dependencies no longer
|
|
23
|
+
refuses outright: the session pauses, names the copy and the exact install command, and a
|
|
24
|
+
**Check again** button re-runs the same check on the same copy (bareloop still never
|
|
25
|
+
installs anything).
|
|
26
|
+
- **Model check under Model** — one `[✓]`/`[✗]` line: key found/missing/bad-shape/rejected
|
|
27
|
+
(401/403 reads "key rejected", not "may be flaky") plus a $0 GET-only reachability probe
|
|
28
|
+
(`GET /api/author/model-check`, `checkProviderReachable` in `src/providers.js`). A missing
|
|
29
|
+
or bad key blocks Start; an unreachable endpoint shows red but never blocks.
|
|
30
|
+
- **One cap covers drafting + run** — the separate Drafting cap field is gone. Drafting runs
|
|
31
|
+
under the same signed Cap `$`, and at sign the run's enforced ceiling is Cap minus what
|
|
32
|
+
drafting already spent. `runJob` takes `draftSpentUsd` and `draftSpendComplete` (carried on
|
|
33
|
+
`job-start` as their own fields, never `priorSpentUsd`, so a first run never reads as a
|
|
34
|
+
resume); `run-u` gains `--draft-spent-usd <n>` and `--draft-spend-incomplete` (validated
|
|
35
|
+
like `--read-shim`/`--scout`, carried in every resume/decide re-invocation tail);
|
|
36
|
+
`bareloop author` prints its drafting spend and the exact `run-u` command. Spend renders
|
|
37
|
+
as "$X ($Y drafting) of $Z", and an incomplete drafting fold reads "at least" on both
|
|
38
|
+
figures.
|
|
39
|
+
- **Plain-question check lines** — the "checks N/M" headline is withdrawn. Run-tab part
|
|
40
|
+
cards and the Audit tab show "all N checks passed" or the first failing check in plain
|
|
41
|
+
words, e.g. "check #2 src/checks.js has 0 type errors? — 4 left, need 0"; an expanded
|
|
42
|
+
attempt row lists every declared check in order, including ones that never ran
|
|
43
|
+
("not run"). One server-side owner maps each declared stage to its question ("did it
|
|
44
|
+
change any file?", "no casts or silencers added?", "does the test suite pass?", "no
|
|
45
|
+
failing tests?", …); numbers use three closed shapes ("N left, need T" / "N of total" /
|
|
46
|
+
"N, limit M").
|
|
47
|
+
- Coloured `[✓]`/`[✗]` result glyphs on Run-tab part cards.
|
|
48
|
+
- The fix worker's prompt now carries a facts-only close-grade history: "checks passed
|
|
49
|
+
N/M -> N/M -> …" (over the DECLARED stage count) and each stage's own number history
|
|
50
|
+
("count-stage 12 -> 0"). Nothing generated, no advice.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **Panel money renders at 2 decimals everywhere** (`<$0.01` for a real sub-cent amount, a
|
|
55
|
+
floor rounds down). The CLI's 4-decimal `--all` output is unchanged.
|
|
56
|
+
- **The "worse than before" confirm-turn question is retired** from both the panel and the
|
|
57
|
+
CLI — an empty answer always meant "nothing beyond Guardrails"; it now is never asked.
|
|
58
|
+
- Chat tab cap row and labels follow the mockup (`Cap $`, `Token price $`, Time cap);
|
|
59
|
+
Audit tab Time cells show `HH:MM:SS` (full ISO on hover) instead of overflowing at
|
|
60
|
+
390px.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- **Live runs show a running floor, not "unknown"** — "$X so far (Y drafting) of $CAP ·
|
|
65
|
+
running Zm" on the Summary box and on a live run's own card; part-level tool counts on
|
|
66
|
+
a live run resolve through the same live audit-sidecar reader as the Audit tab.
|
|
67
|
+
- **Job tab was permanently blank** — two elements shared `id="job-card"`; the read-only
|
|
68
|
+
card is now `job-card-readonly`, with a regression test banning duplicate ids.
|
|
69
|
+
- **A failed or rejected chat POST no longer loses the typed answer** — the input clears only
|
|
70
|
+
on a proven success and errors persist in their own element instead of being wiped by
|
|
71
|
+
the 2-second poll.
|
|
72
|
+
- **Fix-loop governor reads a declared close's numbers (F198)** — it fed on text-parsed
|
|
73
|
+
grades that read `null` for every declared count-not-worse gap; it now reads the close's
|
|
74
|
+
structured `{stage, value}`, so its progress/strike counting sees real convergence or
|
|
75
|
+
stalling. No limit or direction change.
|
|
76
|
+
- **Escalated fix-loop exhaustion no longer advertises `--resume` (F197)** — `escalated` is
|
|
77
|
+
not resumable, so the option states that plainly instead of an unreachable "rerun with
|
|
78
|
+
--resume".
|
|
79
|
+
- The plan part's wall time no longer reads ~0.0s for a real multi-second call (replay-side
|
|
80
|
+
fix; display only).
|
|
81
|
+
- "Checks passed N/M" uses the declared stage count, not the count that ran under
|
|
82
|
+
first-red-wins — in the panel and in the fix worker's prompt (F203).
|
|
83
|
+
|
|
84
|
+
### Docs
|
|
85
|
+
|
|
86
|
+
- `bareloop.context.md` documents the `/api/author/*` routes, `draftSpentUsd`,
|
|
87
|
+
`draftSpendComplete`, `--draft-spent-usd`, `--draft-spend-incomplete` and
|
|
88
|
+
`moneyWithDraft`; PANEL-BUILD.md and the PRD carry the dated P3 / one-cap addenda.
|
|
89
|
+
|
|
90
|
+
## [0.30.0] — 2026-09-27
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
|
|
94
|
+
- **Panel step map draws the retry loop** — a multi-attempt step (and the post-step fix loop)
|
|
95
|
+
now renders its "try N" curve and label on the map, alongside a pulsing amber dot on the
|
|
96
|
+
live-running box (same geometry as `design/panel-mockup.html`).
|
|
97
|
+
- **Runs list and open run poll live** — a single poll owner refreshes the Runs list every
|
|
98
|
+
tick and the open run's own detail while it is still live (`▶`), stopping once it dies
|
|
99
|
+
(`?`) or gets a real `✓`/`✗` verdict; polling pauses on a hidden tab and catches up on
|
|
100
|
+
return. Re-renders are skipped when the fetched payload is unchanged, and scroll position
|
|
101
|
+
is preserved across poll-driven rebuilds.
|
|
102
|
+
- `scripts/replay-live.mjs` (**dev-only, not shipped** — excluded from the published
|
|
103
|
+
package) paces an archived spine into a live-looking run for panel development and
|
|
104
|
+
screenshot proof, without touching the source spine or `~/.config/bareloop`.
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
|
|
108
|
+
- **Job tab field order**: Check type, Model, `$` cap / Time cap, then the rest — moved per
|
|
109
|
+
hamr's ruling.
|
|
110
|
+
- Job tab no longer shows a separate Job name field; the run's own card title already
|
|
111
|
+
carries it.
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- The retry loop's "try N" curve and label now stay inside their own step box at every
|
|
116
|
+
width, including narrow/mobile widths (previously could render past the box's left edge
|
|
117
|
+
at widths like 350px).
|
|
118
|
+
- An open (still-running) attempt on a live run now reads `▶` everywhere — map, part cards,
|
|
119
|
+
and Audit tab — never the died `?`, which is reserved for a genuinely died attempt.
|
|
120
|
+
- The `[▶]` glyph now pulses for a live run everywhere it appears (runs list rows,
|
|
121
|
+
Workflows, the run header).
|
|
122
|
+
- A running step's own in-progress attempt no longer leaks into a phantom fix-loop part on
|
|
123
|
+
the map.
|
|
124
|
+
- A live fix-loop attempt whose window is still open now reports its outcome as unknown
|
|
125
|
+
rather than a fabricated red, so the map correctly shows it as running (with its pulsing
|
|
126
|
+
dot) instead of prematurely stopped.
|
|
127
|
+
- A live run's Audit tab and tool-call/cache summary now fall back to the during-run
|
|
128
|
+
tree/worktree gate-audit path, so tool calls are visible while a run is still in progress,
|
|
129
|
+
not only after it finishes.
|
|
130
|
+
|
|
131
|
+
### Notes
|
|
132
|
+
|
|
133
|
+
- F195 (open): `/api/runs` re-replays every archived spine on every call (~457ms CPU at 250
|
|
134
|
+
rows, no per-row cache). Mitigated in this release by throttling the Runs list poll to
|
|
135
|
+
10s; the root cause is still open.
|
|
136
|
+
- F196 (parked): a finished bundle run's tool log can be unreadable due to a sidecar naming
|
|
137
|
+
mismatch between `spine.jsonl` and its renamed gate-audit sidecar. Not fixed in this
|
|
138
|
+
release.
|
|
139
|
+
|
|
8
140
|
## [0.29.0] — 2026-09-26
|
|
9
141
|
|
|
10
142
|
### Added
|
package/bareloop.context.md
CHANGED
|
@@ -1160,7 +1160,37 @@ Reserved spine vocabulary (V7, machinery-free until job #1 surfaces one):
|
|
|
1160
1160
|
`coordination-red` — a failure between units (scope contention, step order, store
|
|
1161
1161
|
races), never to be folded into worker/interpreter reds.
|
|
1162
1162
|
|
|
1163
|
-
### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, closeDir?, layerRoot?, readShim?, scout?, bridge?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans?, resumeBranch?, humanRuling?, heldRuling?, reviewDoor?, doorRerun?, resumable? })` → outcome — `src/run.js`
|
|
1163
|
+
### `runJob(spec, { approvals, workdir, provider, nativeProvider?, providerFor?, emit, capRuns?, strikeLimit?, shellCapUsd?, closeTimeoutMs?, closeDir?, layerRoot?, readShim?, scout?, bridge?, draftSpentUsd?, draftSpendComplete?, priorSpentUsd?, priorSpendComplete?, priorWallMs?, resumeSeed?, resumeGrades?, resumeReplans?, resumeBranch?, humanRuling?, heldRuling?, reviewDoor?, doorRerun?, resumable? })` → outcome — `src/run.js`
|
|
1164
|
+
|
|
1165
|
+
**`draftSpentUsd` (hamr's ruling 2026-09-28, "one cap covers drafting + run") — money the
|
|
1166
|
+
AUTHORING pipeline already spent on this job before it was signed.** Shrinks THIS run's own
|
|
1167
|
+
enforced ceiling (the one place the arithmetic lives: `remainingUsd() => Math.min(shellCapUsd,
|
|
1168
|
+
job.budgetUsd - draftFoldUsd - spentUsd)`), never the signed `budgetUsd` itself. Deliberately
|
|
1169
|
+
NOT `priorSpentUsd` — that key means "a previous attempt of this run died and folded its spend
|
|
1170
|
+
forward," read by `src/replay.js`'s `resumed` flag off the key's bare presence; a drafting fold
|
|
1171
|
+
must never make an ordinary first run read as a resume. Belted like `priorSpentUsd` (a
|
|
1172
|
+
non-finite or negative value reads as 0). Rides onto `job-start` as its own field,
|
|
1173
|
+
`draftSpentUsd`, only when > 0 — never inside `spentUsd`/`engagementSpentUsd`, which stay
|
|
1174
|
+
run-only. `bareloop run-u --draft-spent-usd <n>` is the CLI door (validated: finite, ≥ 0, else
|
|
1175
|
+
refused); `bareloop author` prints its own known drafting spend floor and the exact `run-u`
|
|
1176
|
+
command including the flag; the panel's sign route passes the session's own tracked drafting
|
|
1177
|
+
spend the same way. `src/replay.js`'s `moneyWithDraft(spendCore, draftSpentUsd, budgetUsd?, draftSpendComplete?)` is
|
|
1178
|
+
the one shared display formatter: `"$3.71 ($0.81 drafting) of $5.00"`, unchanged
|
|
1179
|
+
(`spendCore`/`spendCore of $cap`) when a run carries no drafting share. When
|
|
1180
|
+
`draftSpendComplete === false` it reads `"at least $3.71 (at least $0.81 drafting) of $5.00"`
|
|
1181
|
+
(the core is not double-prefixed if it is already a floor).
|
|
1182
|
+
|
|
1183
|
+
**`draftSpendComplete` (default `true`; hamr's ruling 2026-09-28, 2nd addendum) — was the
|
|
1184
|
+
`draftSpentUsd` fold EXACT?** The authoring pipeline's metered-call list can go unpriced (F6)
|
|
1185
|
+
and the unknown does not heal by folding forward, so a drafting figure that is only a floor
|
|
1186
|
+
must say so. A NEW field beside `draftSpentUsd`, never a value inside `spendComplete` or
|
|
1187
|
+
`priorSpendComplete` (those answer "is THIS run's own ledger exact"; merging them would let a
|
|
1188
|
+
floor drafting spend read as an exact run). Read only when `draftSpentUsd` is emitted: rides
|
|
1189
|
+
onto `job-start` as `draftSpendComplete` beside `draftSpentUsd` (only when the fold is > 0);
|
|
1190
|
+
only an explicit `false` counts as incomplete — anything else, garbage included, reads `true`,
|
|
1191
|
+
so an unreadable flag never manufactures a false floor. `src/replay.js` surfaces it as
|
|
1192
|
+
`summary.draftSpendComplete` (`null` when no drafting fold; a job-start lacking the field reads
|
|
1193
|
+
`true`) and feeds it to `moneyWithDraft`.
|
|
1164
1194
|
|
|
1165
1195
|
**`closeTimeoutMs` (PRD item 27/M3) is now OPTIONAL for every real caller.** Omit it (both
|
|
1166
1196
|
`src/cli.js`'s bundle runner and `src/userrun.js` — lifted out of `scripts/run-u.mjs` by
|
|
@@ -1276,6 +1306,26 @@ knob, not a product default — the spec names no scout, so the signed hash is u
|
|
|
1276
1306
|
`--read-shim`: an unrecognised value exits 2 at argv, and every re-invocation the runner prints
|
|
1277
1307
|
carries `--scout off` when set, so a resume never silently drops the arm.
|
|
1278
1308
|
|
|
1309
|
+
**`bareloop run-u --draft-spend-incomplete`** is a boolean presence-flag (no value) that sets
|
|
1310
|
+
`runJob`'s `draftSpendComplete: false`, saying the `--draft-spent-usd` figure is a floor. It
|
|
1311
|
+
has no guard against being passed without `--draft-spent-usd` — alone it is inert (nothing is
|
|
1312
|
+
folded, so no `job-start` field is emitted). Every printed re-invocation tail carries it
|
|
1313
|
+
alongside `--draft-spent-usd` (only when drafting spend > 0), so a resumed leg never turns the
|
|
1314
|
+
floor back into an exact figure; `bareloop author` and the panel's sign route pass it when the
|
|
1315
|
+
session's drafting spend was incomplete.
|
|
1316
|
+
|
|
1317
|
+
**`bareloop run-u --draft-spent-usd <n>`** (hamr's ruling 2026-09-28, "one cap covers drafting +
|
|
1318
|
+
run") is the same runner-territory class: a non-finite or negative value exits 2 at argv, before
|
|
1319
|
+
the approval gate — never coerced, since reading garbage as 0 would silently WIDEN the run's own
|
|
1320
|
+
enforced ceiling and reading it as `Infinity` would silently narrow it into "no cap". The spec
|
|
1321
|
+
names no drafting spend, so the signed `budgetUsd` is unaffected; this only shrinks THIS leg's
|
|
1322
|
+
own enforced remainder (`job.budgetUsd - draftSpentUsd - spentUsd`). Every printed
|
|
1323
|
+
resume/decide/door re-invocation carries `--draft-spent-usd` too, mirroring `--read-shim`'s own
|
|
1324
|
+
tail — a resumed leg that dropped it would silently widen its ceiling back up. `bareloop author`
|
|
1325
|
+
prints the exact `run-u` command including this flag, with its own known drafting spend (a floor,
|
|
1326
|
+
never $0 on an unpriced call — F6). The panel's sign route (`src/panel/authorroutes.js`)
|
|
1327
|
+
passes the same flag off the authoring session's own tracked spend.
|
|
1328
|
+
|
|
1279
1329
|
**The review door's two checkpoint terminals (N4 slice 1, doors re-cut 2026-08-18)** are the
|
|
1280
1330
|
class's whole surface at this layer, and each is a CLEAN exit (`spendComplete` stays true —
|
|
1281
1331
|
only the two casualties floor). The pause terminal is a
|
|
@@ -1554,7 +1604,10 @@ greens harmed — all three historical fix-loop greens converted in ≤ 2 verdic
|
|
|
1554
1604
|
case caught, dead flat at 2 errors for 7 verdicts until the wall). It now stops on the same
|
|
1555
1605
|
2-strike no-progress rule, read off a DIFFERENT signal: the close's own graded numbers, **per
|
|
1556
1606
|
stage** (`src/trend.js`). A stage's series is the first number on the first red-marked line of
|
|
1557
|
-
its output
|
|
1607
|
+
its output (for a DECLARED close the structured `{stage, value}` its verdict already carries —
|
|
1608
|
+
`closeGrade`, F198 — never a re-parse of the gap prose, which reads every declared grade as
|
|
1609
|
+
uncomparable; a command close, and a worker-crash's synthetic gap, still read the text),
|
|
1610
|
+
compared only against that stage's own BEST so far (never last-only, the same
|
|
1558
1611
|
oscillator reason the ladder keeps a seen-set); reaching a LATER stage than ever before is
|
|
1559
1612
|
progress too, since a staged close is first-red-wins. Two consecutive comparable readings with
|
|
1560
1613
|
nothing improving ends the loop, under the unchanged `cap-halt` terminal and the unchanged
|
|
@@ -1578,6 +1631,10 @@ vs `the target files are clean but M error(s) exist outside them`) donates both
|
|
|
1578
1631
|
series, and a run crossing that seam can read converging on work that only swapped which wall
|
|
1579
1632
|
it is behind. Neither is sharpened by teaching the reader to tell prose shapes apart (the F49
|
|
1580
1633
|
precedent); the second's root fix is a stage split in the close, which is the spec's to change.
|
|
1634
|
+
The fix worker's prompt also gains one facts-only line ("The close's own numbers so far, oldest
|
|
1635
|
+
first": checks-satisfied counts and each stage's number history, F198) — no advice, omitted when
|
|
1636
|
+
nothing was graded. This exhaustion terminal is `escalated`, a graded red that `--resume`
|
|
1637
|
+
refuses, so its options say plainly it cannot be resumed (revise the spec and rerun fresh, F197).
|
|
1581
1638
|
The spine gains a per-iteration `ladder` record here too; every reading names its `governor`
|
|
1582
1639
|
(`step-ladder` | `close-trend`) so two instruments under one event type can never be averaged
|
|
1583
1640
|
into one number.
|
|
@@ -2553,7 +2610,7 @@ reimplements for the tool-call breakdown. It returns one plain object:
|
|
|
2553
2610
|
|
|
2554
2611
|
```
|
|
2555
2612
|
{ runId, job, goal, budgetUsd, specHash, branch, verdictType, model, code,
|
|
2556
|
-
outcome, stopReason, spentUsd, spendComplete, wallMs, chainClock,
|
|
2613
|
+
outcome, stopReason, spentUsd, spendComplete, draftSpentUsd, wallMs, chainClock,
|
|
2557
2614
|
resumed, resumeSeed, thisFileSpend, spendMismatch,
|
|
2558
2615
|
timelineKind: 'steps'|'iterations', replans, close,
|
|
2559
2616
|
steps: [{ id, occurrence, outcome, rounds, toolCalls, checks: {passed, failed},
|
|
@@ -2583,6 +2640,13 @@ the model string, or `-`) — `model` is kept FULL, never truncated, unlike the
|
|
|
2583
2640
|
column: a wrong or uncertain model reading is exactly the thing a directory-wide scan needs in
|
|
2584
2641
|
full, not cut.
|
|
2585
2642
|
|
|
2643
|
+
**`draftSpentUsd`** (hamr's ruling 2026-09-28, "one cap covers drafting + run") reads
|
|
2644
|
+
`job-start.draftSpentUsd` — `null` on every run that never drafted through the panel/`bareloop
|
|
2645
|
+
author` (the common case, and every spine predating the field), the drafting spend FLOOR
|
|
2646
|
+
otherwise. Never `priorSpentUsd` (that key drives `resumed` below — see `runJob`'s own doc) and
|
|
2647
|
+
never folded into `spentUsd`. `summarizeForAllLine`'s `spend` column and every panel money
|
|
2648
|
+
display route this through `moneyWithDraft` (above).
|
|
2649
|
+
|
|
2586
2650
|
**`code`** (F118, the run→code direction — the commit→run direction is the prompt-commit
|
|
2587
2651
|
check's `Failure:` run-reference rule; the two are companion halves of one loop, built the
|
|
2588
2652
|
same day). `codeVersion()` (`src/codeversion.js`) is a pure, $0, no-shell reader: `version`
|
|
@@ -3230,7 +3294,14 @@ interviews of their own. All three are dispatched by name only: `bareloop run-u
|
|
|
3230
3294
|
paged, for the Audit tab's Grouped view); `GET /api/runs/:runid/job` (the signed spec's
|
|
3231
3295
|
own fields, ONLY when a bundle-layout run's `spec.json` is reachable — a run-u run has
|
|
3232
3296
|
none on disk, and this reads `resolved:false` with every spec-only field honestly
|
|
3233
|
-
`'unknown'`, never guessed from the spine's own narrower `job-start` record).
|
|
3297
|
+
`'unknown'`, never guessed from the spine's own narrower `job-start` record). The run
|
|
3298
|
+
detail also carries `draftSpentUsd`/`draftSpendComplete` (the drafting share of the one cap,
|
|
3299
|
+
`null` when none), `spendFloorUsd`/`wallFloorMs` for a died OR still-running spine (a
|
|
3300
|
+
floor, never `unknown`), and — when a spec resolves — per part attempt `declaredStagesTotal`
|
|
3301
|
+
and `declaredStages[{name, question}]` in DECLARED order, plus per stage `kind`/`direction`/
|
|
3302
|
+
`baselineKind`/`question`: the plain-English question is derived in `src/panel/server.js`
|
|
3303
|
+
(`stageQuestionText`) from the signed stage's kind/params only, `null` when no honest
|
|
3304
|
+
wording exists (the client then shows the stage's own name). Every
|
|
3234
3305
|
endpoint is GET/HEAD only (anything else — including every write verb — is `405`); a URL
|
|
3235
3306
|
never joins a path segment into a filesystem read — a runid is looked up in the run list
|
|
3236
3307
|
first (`RUNID_RE`, `src/panel/server.js` — accepts a `~2`-style backfill-disambiguated
|
|
@@ -3243,6 +3314,69 @@ interviews of their own. All three are dispatched by name only: `bareloop run-u
|
|
|
3243
3314
|
its own. Chat/Settings/authoring (P3/P4) are not built yet; the page says so rather than
|
|
3244
3315
|
hiding the gap.
|
|
3245
3316
|
|
|
3317
|
+
- **Panel P2 (live run view, `docs/product/PANEL-BUILD.md` Addendum 2026-09-27)** → the page
|
|
3318
|
+
polls, it does not push (no SSE/websocket). One `setInterval` (2s) drives two throttled
|
|
3319
|
+
fetches: the OPEN run's own detail (`GET /api/runs/:runid`) refetches every 2s while its
|
|
3320
|
+
glyph is live `▶`, and stops the instant it reaches a real `✓`/`✗` or dies `?`; the left
|
|
3321
|
+
Runs list refetches on a separate 10s throttle inside the same tick (`RUNS_LIST_POLL_MS`,
|
|
3322
|
+
lowered from the original 2s per F195 — `listRuns()` re-replays every archived spine on
|
|
3323
|
+
every call, ~457ms CPU at 250 rows, open/escalated), except the tick right after the open
|
|
3324
|
+
run stops being live, which forces one immediate list refresh so that row's glyph doesn't
|
|
3325
|
+
wait up to 10s. Both fetches skip re-rendering when the payload's JSON is byte-identical to
|
|
3326
|
+
the last one, restore each pane's scroll position across a poll-driven rebuild, and pause
|
|
3327
|
+
while the tab is hidden (`document.hidden`), catching up immediately on
|
|
3328
|
+
`visibilitychange`. The Audit tab does **not** join this loop — it refetches only when
|
|
3329
|
+
opened/switched to. A live run's gate-audit sidecar (before `run-u`/`bareloop run`'s
|
|
3330
|
+
end-of-run rename) is read from its DURING-RUN path (`<row.patient>/gate-audit.jsonl`) via
|
|
3331
|
+
`resolveAuditPathForRow` (`src/panel/server.js`), the one shared owner the Run tab's tools
|
|
3332
|
+
summary and the Audit tab both read through — tried only while the run's own spine has no
|
|
3333
|
+
`job-end` yet, so a finished run never risks reading a stale leftover file at that path.
|
|
3334
|
+
`scripts/replay-live.mjs <sourceSpine> <outDir> [--speed N]` is a dev-only, unshipped ($0,
|
|
3335
|
+
no provider call) instrument that paces a real archived spine's records back out under a
|
|
3336
|
+
new runid so the panel can be exercised against a "live" run without a paid run — it is not
|
|
3337
|
+
a substitute for watching a real run start-to-end, which stays a separately authorized step.
|
|
3338
|
+
|
|
3339
|
+
- **Panel P3 (chat/authoring, `docs/product/PANEL-BUILD.md` Addendum 2026-09-27)** → the ONE
|
|
3340
|
+
family of write routes the panel serves, all under `/api/author/*` (`src/panel/
|
|
3341
|
+
authorroutes.js`); every other route stays GET/HEAD-only per P1's own rule. Every route
|
|
3342
|
+
requires the human-click guard: an `x-bareloop-token` header matching a fresh token minted
|
|
3343
|
+
once per server start (templated into `index.html` like the port) AND an Origin/Host
|
|
3344
|
+
naming this exact `127.0.0.1:<port>` — a request failing either gets `403`, before the
|
|
3345
|
+
route body ever runs. `POST /api/author/start` (one job-card body; `409` while a prior
|
|
3346
|
+
session is still non-terminal — one authoring session at a time) creates a session
|
|
3347
|
+
(`src/panel/authorsession.js`) that runs `prepareSource`/`detectLanguage`/`validateJob`/
|
|
3348
|
+
`authorCloseForJob`/`assembleSpec`/`prepareSigning` in-process, exactly the library calls
|
|
3349
|
+
`bareloop interview`/`bareloop author` already make — never a script's own readline loop,
|
|
3350
|
+
never a reimplementation of any gate. `GET /api/author/model-check?model=<id>` is the $0 readiness probe the job card runs
|
|
3351
|
+
before Start (`checkProviderReachable`, `src/providers.js` — a models-list GET only, never a
|
|
3352
|
+
completion; the page sees the key's NAME and a status word, never the value). `POST
|
|
3353
|
+
/api/author/:id/check-deps` is the install-gap's "Check again" (`phase:'install-needed'`:
|
|
3354
|
+
the session waits on the person's own install, then re-runs `missingDependencies` on the
|
|
3355
|
+
same copy — bareloop never installs). `GET /api/author/:id` polls the session's state
|
|
3356
|
+
(phase, chat messages, cost, `revisesLeft`, `specHash` once prepared). `POST /api/author/
|
|
3357
|
+
:id/send {text}` answers whatever the confirm turn is currently asking (the `language` pick or a plan's
|
|
3358
|
+
own follow-up question — the old `worseThanBefore` ask is retired, 2026-09-28) — refused outright when the pending ask is the
|
|
3359
|
+
plan MENU itself (`{ok:false}`, no route from chat text to a plan decision, ever). `POST
|
|
3360
|
+
/api/author/:id/revise {text}` is the menu's own `fix` pick, with the chat text as the
|
|
3361
|
+
correction (D3: max 2 rounds, `revisesLeft` derived from the confirm turn's own round
|
|
3362
|
+
number, never a second hardcoded cap). `POST /api/author/:id/sign-prepare` is the menu's
|
|
3363
|
+
own `confirm` pick — it runs gates 1–4 and reaches `phase:'prepared'` with a `specHash`; it
|
|
3364
|
+
NEVER signs. `POST /api/author/:id/sign {specHash}` is the ONLY route that spawns a run —
|
|
3365
|
+
it refuses unless `phase==='prepared'` and the posted hash matches the session's own
|
|
3366
|
+
`signing.json` `specHash` exactly, then spawns `setsid systemd-inhibit … node bin/
|
|
3367
|
+
bareloop.mjs run-u --spec <resolved-spec.json> --approve <hash>` (plus `--draft-spent-usd <n>`
|
|
3368
|
+
[`--draft-spend-incomplete`] when the session's drafting spend is > 0 — one cap covers
|
|
3369
|
+
drafting + run) detached (array argv,
|
|
3370
|
+
never a shell string), with its own log file inside the session's own dir, and the
|
|
3371
|
+
server's own environment (a key is never read into or sent to the page — a missing one
|
|
3372
|
+
refuses the session at $0, naming only the env var). `src/panel/authorsession.js` takes
|
|
3373
|
+
test-only DI seams (`scout`/`generate`/`confirmGenerate`/`authorFn`/`prepareSigningFn`) so
|
|
3374
|
+
a test can drive the real ask()-channel/revise/hash wiring without a live provider call;
|
|
3375
|
+
none of them are reachable from `authorroutes.js`'s real construction path. Sessions live
|
|
3376
|
+
under `~/.config/bareloop/panel-sessions/<id>/` (each one's own `resolved-spec.json` and
|
|
3377
|
+
`signing.json`, the two files `bareloop author` itself already writes). Edit/re-sign, the
|
|
3378
|
+
`~/.config/bareloop/.env` keys-file loader, and Settings are P4, not built here.
|
|
3379
|
+
|
|
3246
3380
|
- **`bareloop run-u <flags…>`** (PANEL-BUILD.md P0 task 2/4) → the person-path run flow
|
|
3247
3381
|
(the JOBS-table/`--spec` runner, resume, the review door — `docs/logs/FINDINGS.md`'s
|
|
3248
3382
|
U-mode). `src/cli.js`'s `run-u` dispatch hands `rest` straight to
|
|
@@ -3305,6 +3439,15 @@ interviews of their own. All three are dispatched by name only: `bareloop run-u
|
|
|
3305
3439
|
returns a code / throws" rule `src/userrun.js` already keeps. Like `interview`, `src/cli.js`
|
|
3306
3440
|
hands it raw `stdin`/`stdout`/`stderr` (its one interactive seam is the confirm turn).
|
|
3307
3441
|
|
|
3442
|
+
**`--budget` and `--draft-spent-usd` (hamr's ruling 2026-09-28, "one cap covers drafting +
|
|
3443
|
+
run"):** `--budget` still means the AUTHORING ceiling, unchanged — the panel sets it equal to
|
|
3444
|
+
the job card's one `Cap $`. On a SIGNING PREPARED stop, `author` prints its own known
|
|
3445
|
+
drafting spend (a floor, never $0 on an unpriced call — F6) and the exact `run-u` command to
|
|
3446
|
+
run it, now including `--draft-spent-usd <known-floor>` when that floor is > 0 — the SAME
|
|
3447
|
+
signed `budgetUsd` is the run's cap too, and `run-u` enforces the remainder
|
|
3448
|
+
(`Cap $ − drafting spent`) as ITS own ceiling. Nothing here is left for a person to
|
|
3449
|
+
hand-compute or re-type, the same rule F185 set for `--spec`.
|
|
3450
|
+
|
|
3308
3451
|
**Tighten-only budget/wall.** `--budget`/`--wall` on `bareloop run` may only lower the
|
|
3309
3452
|
bundle's own signed `budgetUsd`/`maxWallMs` — `checkEnvelope`'s `envelope-widen` red refuses
|
|
3310
3453
|
anything higher. Raising a ceiling is a re-export (a new signed bundle), never a runner flag.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/authorflow.js
CHANGED
|
@@ -425,32 +425,18 @@ export function labelsFor(verdictType) { return questionSet(verdictType).labels;
|
|
|
425
425
|
|
|
426
426
|
// ── CONFIRM TURN — person-facing wording (PRD item 33 M3 piece 4) ──────────
|
|
427
427
|
//
|
|
428
|
-
// These
|
|
429
|
-
//
|
|
430
|
-
//
|
|
428
|
+
// These are the $0, no-provider half of the confirm turn (D7): asked before
|
|
429
|
+
// the scout ever runs, so a missing person at end of input stops at $0
|
|
430
|
+
// rather than after a paid call. FROZEN wording, exactly like SOURCE_FIELD
|
|
431
431
|
// and DESTINATION_FIELD_* above — a caller prints these verbatim, never
|
|
432
432
|
// rewords or reorders them.
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
* 2026-09-13 addendum) includes it as a constraint only when the person
|
|
441
|
-
* actually gave one, and folding it silently into the Guardrails free-text
|
|
442
|
-
* slot would lose that "present or absent" distinction. An empty answer is
|
|
443
|
-
* "nothing beyond what Guardrails already said" and is not required — see
|
|
444
|
-
* the frozen-wording test below: this phrase appears NOWHERE in
|
|
445
|
-
* {@link questionsFor}'s own sets, so a caller cannot double-ask it by
|
|
446
|
-
* reading the wrong table.
|
|
447
|
-
*/
|
|
448
|
-
export const WORSE_THAN_BEFORE_FIELD = Object.freeze({
|
|
449
|
-
id: 'worseThanBefore',
|
|
450
|
-
kind: 'mechanical',
|
|
451
|
-
label: 'Worse than before',
|
|
452
|
-
prompt: 'What would make you say this came back worse than before?',
|
|
453
|
-
});
|
|
433
|
+
//
|
|
434
|
+
// RETIRED (hamr's ruling, 2026-09-28): the old Q5 "worse than before" field
|
|
435
|
+
// and ask are gone — it was optional, and an EMPTY answer already meant
|
|
436
|
+
// "nothing beyond Guardrails", so it never earned its own turn. `worseThanBefore`
|
|
437
|
+
// downstream (confirmPrompt, `accepted.worseThanBefore`) is kept and always
|
|
438
|
+
// resolves to `''` — the same value an empty answer always produced — so
|
|
439
|
+
// nothing downstream needed to change shape, only the ask itself is gone.
|
|
454
440
|
|
|
455
441
|
/**
|
|
456
442
|
* Asked ONLY when `src/detectlang.js`'s `detectLanguage` reports its
|
|
@@ -1832,7 +1818,10 @@ async function askConfirmPlan({ convo, generate, mode, book, label }) {
|
|
|
1832
1818
|
* generate: Function, book: ReturnType<typeof makeCostBook>,
|
|
1833
1819
|
* ask: (step: {kind: string, [k: string]: any}) => Promise<string|null>,
|
|
1834
1820
|
* onPhase?: (phase: string, data?: any) => void, mode?: 'tool'|'text',
|
|
1835
|
-
* worseThanBefore?: string}} o
|
|
1821
|
+
* worseThanBefore?: string}} o retired field (hamr's ruling 2026-09-28):
|
|
1822
|
+
* `worseThanBefore` is never asked any more and defaults to `''` — a
|
|
1823
|
+
* caller may still pass a string through (kept for a future, non-ask
|
|
1824
|
+
* source of the same constraint), it just never comes from an ask() step.
|
|
1836
1825
|
* @returns {Promise<{ok: boolean,
|
|
1837
1826
|
* stop: null|'cap-halt'|'pricing-red'|'provider-red'|'artifact-red'|'confirm-abandoned'|'confirm-restart',
|
|
1838
1827
|
* rounds: number,
|
|
@@ -1843,13 +1832,12 @@ async function askConfirmPlan({ convo, generate, mode, book, label }) {
|
|
|
1843
1832
|
export async function runConfirmTurn({
|
|
1844
1833
|
verdictType, answers, questions, labels = {}, facts = null, listing = null, writeScope = null,
|
|
1845
1834
|
isRepo, lang, generate, book, ask, onPhase = () => {}, mode = 'tool',
|
|
1846
|
-
//
|
|
1847
|
-
//
|
|
1848
|
-
//
|
|
1849
|
-
//
|
|
1850
|
-
//
|
|
1851
|
-
|
|
1852
|
-
worseThanBefore: presetWorseThanBefore = undefined,
|
|
1835
|
+
// RETIRED ASK (hamr's ruling 2026-09-28): this turn never asks
|
|
1836
|
+
// "worseThanBefore" any more — an absent value always resolves to `''`,
|
|
1837
|
+
// byte-identical to what an empty answer always produced. A caller may
|
|
1838
|
+
// still pass a resolved string through (kept as a plain pass-through, not
|
|
1839
|
+
// an ask trigger).
|
|
1840
|
+
worseThanBefore: presetWorseThanBefore = '',
|
|
1853
1841
|
}) {
|
|
1854
1842
|
/** @typedef {null|'cap-halt'|'pricing-red'|'provider-red'|'artifact-red'|'confirm-abandoned'|'confirm-restart'} ConfirmStop */
|
|
1855
1843
|
/** @returns {{ok: boolean, stop: ConfirmStop, rounds: number, accepted: null, reds: Red[], cost: any}} */
|
|
@@ -1858,16 +1846,8 @@ export async function runConfirmTurn({
|
|
|
1858
1846
|
const abandon = (rounds) => ({ ...base(), stop: /** @type {ConfirmStop} */ ('confirm-abandoned'), rounds, cost: book.report() });
|
|
1859
1847
|
|
|
1860
1848
|
// ── the $0 half, entirely before any token spends (D7) ────────────────────
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
if (presetWorseThanBefore !== undefined) {
|
|
1864
|
-
worseThanBefore = presetWorseThanBefore;
|
|
1865
|
-
} else if (isRepo) {
|
|
1866
|
-
onPhase('confirm-worse-than-before');
|
|
1867
|
-
const wtb = await ask({ kind: 'worseThanBefore', field: WORSE_THAN_BEFORE_FIELD });
|
|
1868
|
-
if (wtb === null) return abandon(0);
|
|
1869
|
-
worseThanBefore = redactSecrets(String(wtb).trim());
|
|
1870
|
-
}
|
|
1849
|
+
// no ask here any more — see the retirement note above.
|
|
1850
|
+
const worseThanBefore = typeof presetWorseThanBefore === 'string' ? presetWorseThanBefore : '';
|
|
1871
1851
|
|
|
1872
1852
|
/** @type {string} */
|
|
1873
1853
|
let resolvedLang = typeof lang === 'string' ? lang : '';
|
package/src/authorjob.js
CHANGED
|
@@ -111,7 +111,7 @@ import {
|
|
|
111
111
|
} from './authoring.js';
|
|
112
112
|
import {
|
|
113
113
|
QUESTION_SETS, questionsFor, requiredAnswersFor, authorClose, makeCostBook,
|
|
114
|
-
runConfirmTurn,
|
|
114
|
+
runConfirmTurn, LANGUAGE_PICK_FIELD,
|
|
115
115
|
} from './authorflow.js';
|
|
116
116
|
import { runAuthorScout, buildSeedListing, SCOUT_ATTEMPTS } from './authorscout.js';
|
|
117
117
|
import {
|
|
@@ -498,14 +498,11 @@ export async function authorCloseForJob({
|
|
|
498
498
|
// paid survey. `ask` ABSENT is the whole confirm turn's off switch — every
|
|
499
499
|
// caller that predates this piece (or a caller that never wires an
|
|
500
500
|
// interactive seam) reaches the genre check below exactly as it always did.
|
|
501
|
-
|
|
501
|
+
// RETIRED (hamr's ruling 2026-09-28): the "worse than before" ask is gone —
|
|
502
|
+
// it was optional and an empty answer already meant "nothing beyond
|
|
503
|
+
// Guardrails", so `worseThanBefore` always resolves to `''` now, never asked.
|
|
504
|
+
const worseThanBefore = '';
|
|
502
505
|
if (ask) {
|
|
503
|
-
if (isRepo) {
|
|
504
|
-
onPhase('confirm-worse-than-before', {});
|
|
505
|
-
const wtb = await ask({ kind: 'worseThanBefore', field: WORSE_THAN_BEFORE_FIELD });
|
|
506
|
-
if (wtb === null) return { ...base, interview, reds: [], stop: 'confirm-abandoned' };
|
|
507
|
-
worseThanBefore = redactSecrets(String(wtb).trim());
|
|
508
|
-
}
|
|
509
506
|
if (langResult?.kind === 'ambiguous') {
|
|
510
507
|
onPhase('confirm-language-pick', { candidates: langResult.candidates });
|
|
511
508
|
const pick = await ask({ kind: 'language', field: LANGUAGE_PICK_FIELD, candidates: langResult.candidates });
|
package/src/authorrun.js
CHANGED
|
@@ -348,8 +348,9 @@ export async function main(argv, deps = {}) {
|
|
|
348
348
|
// Only meaningful for a REPO source (PRD item 33 M3 piece 4, step S6): a
|
|
349
349
|
// plain folder has no genre to detect and no code-shaped manifest to walk —
|
|
350
350
|
// `LANG`/`langResult` stay at their "nothing to see" values and the confirm
|
|
351
|
-
// turn further down runs with `isRepo: false` (D5), which is what
|
|
352
|
-
//
|
|
351
|
+
// turn further down runs with `isRepo: false` (D5), which is what skips
|
|
352
|
+
// the language pick (the old repo-only "worse than before" ask is retired
|
|
353
|
+
// entirely as of 2026-09-28 — see src/authorflow.js's own retirement note).
|
|
353
354
|
let langResult = /** @type {ReturnType<typeof detectLanguage>|null} */ (null);
|
|
354
355
|
let LANG = 'none-detected';
|
|
355
356
|
if (IS_REPO_SOURCE) {
|
|
@@ -702,9 +703,10 @@ export async function main(argv, deps = {}) {
|
|
|
702
703
|
return String(value);
|
|
703
704
|
};
|
|
704
705
|
/** one free-text answer, possibly several lines; a blank line ends it.
|
|
705
|
-
* `allowBlank` lets the FIRST line be blank —
|
|
706
|
-
*
|
|
707
|
-
*
|
|
706
|
+
* `allowBlank` lets the FIRST line be blank — kept as a general option for
|
|
707
|
+
* a future optional free-text step; every current step below passes
|
|
708
|
+
* `false` (the old "worse than before" ask, retired 2026-09-28, was the
|
|
709
|
+
* only caller that ever passed `true`).
|
|
708
710
|
* @param {boolean} allowBlank @returns {Promise<string|null>} */
|
|
709
711
|
const readFreeText = async (allowBlank) => {
|
|
710
712
|
/** @type {string[]} */
|
|
@@ -729,11 +731,6 @@ export async function main(argv, deps = {}) {
|
|
|
729
731
|
* @param {{kind: string, [k: string]: any}} step @returns {Promise<string|null>} */
|
|
730
732
|
const ask = async (step) => {
|
|
731
733
|
out('');
|
|
732
|
-
if (step.kind === 'worseThanBefore') {
|
|
733
|
-
out(step.field.prompt);
|
|
734
|
-
out(' (press Enter on a blank line for "nothing beyond Guardrails")');
|
|
735
|
-
return readFreeText(true);
|
|
736
|
-
}
|
|
737
734
|
if (step.kind === 'language') {
|
|
738
735
|
out(step.field.prompt);
|
|
739
736
|
for (const c of step.candidates ?? []) out(` · ${c}`);
|
|
@@ -1201,7 +1198,15 @@ export async function main(argv, deps = {}) {
|
|
|
1201
1198
|
// buys a real judge call per case, so a line saying "the gates spend no
|
|
1202
1199
|
// tokens" would have been false the moment a judged close reached here.
|
|
1203
1200
|
out(`authoring ${costLine(authored.cost)}`);
|
|
1204
|
-
|
|
1201
|
+
const totalCost = costSoFar();
|
|
1202
|
+
out(`total cost ${costLine(totalCost)}${judges ? ' (includes the calibration gate\'s judge calls)' : ' (gates 1-3 spend no tokens — they run commands)'}`);
|
|
1203
|
+
// hamr's ruling 2026-09-28 ("one cap covers drafting + run") — this
|
|
1204
|
+
// run's own known FLOOR (never $0 on an unpriced call, F6), handed to
|
|
1205
|
+
// the run-u command below as `--draft-spent-usd` so the SIGNED run's
|
|
1206
|
+
// own enforced ceiling becomes Cap $ minus what THIS pipeline already
|
|
1207
|
+
// spent, computed in the one place (`src/run.js`'s `remainingUsd`)
|
|
1208
|
+
// that arithmetic lives — never re-derived here.
|
|
1209
|
+
const draftSpentUsd = typeof totalCost.costUsd === 'number' ? totalCost.costUsd : totalCost.knownUsd;
|
|
1205
1210
|
|
|
1206
1211
|
if (!signing.ok) {
|
|
1207
1212
|
out('\nSIGNING NOT PREPARED — the close did not clear D9\'s gates. Nothing was signed and nothing was run.');
|
|
@@ -1222,8 +1227,19 @@ export async function main(argv, deps = {}) {
|
|
|
1222
1227
|
// person needs to run their own job, nothing left to hand off.
|
|
1223
1228
|
// `providerEntry.envKey` (never a hardcoded ANTHROPIC_API_KEY) is the
|
|
1224
1229
|
// same F187 rule scripts/run-u.mjs's own hint already follows.
|
|
1230
|
+
// hamr's ruling 2026-09-28 (2nd addendum) — `totalCost.costUsd === null`
|
|
1231
|
+
// means this pipeline's own metered list carried at least one unpriced
|
|
1232
|
+
// call (`tallyCalls`'s `spendComplete: false`), so the drafting fold is
|
|
1233
|
+
// a FLOOR, and the run-u command below must say so via its own
|
|
1234
|
+
// presence-flag rather than let the next leg's job-start read it as
|
|
1235
|
+
// exact.
|
|
1236
|
+
const draftIncomplete = totalCost.costUsd === null;
|
|
1237
|
+
if (draftSpentUsd > 0) {
|
|
1238
|
+
out(`\n drafting spent ${draftIncomplete ? `at least $${draftSpentUsd.toFixed(6)}` : `$${draftSpentUsd.toFixed(6)}`} — the run's own cap is the SAME signed $${spec.budgetUsd}, `
|
|
1239
|
+
+ 'and run-u will enforce the remainder (Cap $ minus this) as ITS ceiling — pass --draft-spent-usd exactly as shown below, never a rounded or re-typed figure.');
|
|
1240
|
+
}
|
|
1225
1241
|
out('\nTo run it (the same signature and gates as any other job — nothing here bypasses them):');
|
|
1226
|
-
out(` ${providerEntry.envKey}=... node scripts/run-u.mjs --spec ${specFile} --approve ${hash}`);
|
|
1242
|
+
out(` ${providerEntry.envKey}=... node scripts/run-u.mjs --spec ${specFile} --approve ${hash}${draftSpentUsd > 0 ? ` --draft-spent-usd ${draftSpentUsd}${draftIncomplete ? ' --draft-spend-incomplete' : ''}` : ''}`);
|
|
1227
1243
|
emit('author-end', { outcome: 'prepared', specHash: hash });
|
|
1228
1244
|
}
|
|
1229
1245
|
}
|