bareloop 0.30.0 → 0.32.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 +154 -0
- package/bareloop.context.md +205 -10
- package/bin/bareloop.mjs +0 -1
- package/package.json +1 -1
- package/src/authorflow.js +22 -42
- package/src/authorjob.js +5 -8
- package/src/authorrun.js +42 -16
- package/src/cli.js +12 -6
- package/src/config.js +135 -0
- package/src/interviewrun.js +10 -2
- package/src/keysfile.js +156 -0
- package/src/ledger.js +26 -0
- package/src/monthly.js +352 -0
- package/src/panel/authorroutes.js +328 -0
- package/src/panel/authorsession.js +555 -0
- package/src/panel/index.html +1247 -50
- package/src/panel/server.js +449 -51
- package/src/panel/settingsroutes.js +230 -0
- package/src/planrun.js +61 -5
- package/src/promptregisters.js +3 -0
- package/src/providerrows.js +217 -0
- package/src/providers.js +84 -0
- package/src/replay.js +80 -2
- package/src/replayio.js +14 -3
- package/src/run.js +48 -2
- package/src/runlist.js +13 -0
- package/src/userrun.js +134 -13
- package/types/authorflow.d.ts +4 -21
- package/types/authorrun.d.ts +5 -0
- package/types/cli.d.ts +2 -1
- package/types/config.d.ts +48 -0
- package/types/interviewrun.d.ts +5 -0
- package/types/keysfile.d.ts +90 -0
- package/types/ledger.d.ts +14 -0
- package/types/monthly.d.ts +192 -0
- package/types/panel/authorroutes.d.ts +67 -0
- package/types/panel/authorsession.d.ts +53 -0
- package/types/panel/server.d.ts +42 -5
- package/types/panel/settingsroutes.d.ts +14 -0
- package/types/providerrows.d.ts +164 -0
- 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/runlist.d.ts +1 -0
- package/types/userrun.d.ts +22 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,160 @@ 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.32.0] — 2026-09-29
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Panel Settings (P4a)** — `~/.config/bareloop/.env` keys-file loader (shell env wins; keys
|
|
13
|
+
never reach the page), `config.json` (monthly $ limit, chosen key variables, Anthropic balance
|
|
14
|
+
note), a monthly $ limit that refuses a run start with `Max $X (monthly limit)` at both
|
|
15
|
+
`run-u` and the panel Sign & run, and a Settings view with a Money & limits tab and a Providers
|
|
16
|
+
tab (read, $0 Test, key-name dropdown). Add/edit/remove providers is P4b. There is no monthly
|
|
17
|
+
time limit. **The monthly limit is checked by `run-u` and the panel only — `bareloop run
|
|
18
|
+
<bundle>` does not check it.** An unreadable `config.json` refuses a run start; a month whose
|
|
19
|
+
spend is incomplete reads "at least".
|
|
20
|
+
- **Money & limits tab** — total and this-month spend and tokens as two header tiles ($ and
|
|
21
|
+
tokens, month first), a monthly $ limit that saves on change, and a per-provider breakdown
|
|
22
|
+
table ($, wall minutes, tokens; this month and to date). Wall time is job-start to job-end
|
|
23
|
+
when the run ended, otherwise an "at least" floor; unknown is shown as unknown, never 0. A
|
|
24
|
+
spine with no recorded provider is labelled "not recorded (model X)", never guessed from the
|
|
25
|
+
model name.
|
|
26
|
+
- **`replay-live --live-audit`** (`scripts/replay-live.mjs`) — opt-in: a $0 replay keeps its
|
|
27
|
+
sidecar in a scratch patient dir while it paces, so the panel's live tool-log fallback can be
|
|
28
|
+
seen on screen. Default replay is unchanged.
|
|
29
|
+
- **Panel Providers tab is the keys file (P4b)** — one Settings row per key in
|
|
30
|
+
`~/.config/bareloop/.env` that has a value (the file is created with five empty preset lines,
|
|
31
|
+
`ANTHROPIC_API_KEY` / `DEEPSEEK_API_KEY` / `OPENAI_API_KEY` / `GEMINI_API_KEY` / `LOCAL_API_KEY`,
|
|
32
|
+
when missing; an existing file is never edited). Each row has an editable Name (the model id Chat
|
|
33
|
+
uses), API shape (Anthropic / OpenAI-compatible / Gemini), Base URL, a $0 Test at that shape and
|
|
34
|
+
URL, and a total Tokens used; changes save to `config.json` `keys.<ENV NAME>`. A key value of
|
|
35
|
+
`null` counts as set and means "no real key" (local servers); the provider gets a placeholder,
|
|
36
|
+
and local rounds price as an `estimated` guess, never a real $0. Reload keys re-merges the
|
|
37
|
+
keys file onto the shell environment per request, so edits and removals take effect without a
|
|
38
|
+
restart. Chat's Model menu is the Settings
|
|
39
|
+
rows, and one owner (`src/providerrows.js`) resolves model to key + shape + URL for Chat, the
|
|
40
|
+
author doors and the run doors.
|
|
41
|
+
- **Providers tab names a shell-only key** — a preset key variable exported only in the shell
|
|
42
|
+
(not in the keys file) gets no row and no Chat model; the tab now says so under the table, by
|
|
43
|
+
name only (`GET /api/settings/providers` returns `shellOnly: string[]`, never a value).
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **Panel: no price is displayed** — the Providers table Price column and the Chat job card's
|
|
48
|
+
Token price field are removed. Display only: cost recording, the `estimated` provenance, spend
|
|
49
|
+
sums, caps and the monthly limit are unchanged.
|
|
50
|
+
- The Chat cap row is two equal full-width columns (Cap $ | Time cap) at every width.
|
|
51
|
+
- `config.json` `providers.<row>.key` (P4a key-name dropdown) is replaced by
|
|
52
|
+
`keys.<ENV NAME> = { name, shape, baseUrl }`; the hardcoded `PROVIDER_ROWS` and the panel's fixed
|
|
53
|
+
`MODEL_OPTIONS` are gone.
|
|
54
|
+
- **Monthly limit counts running jobs at their full cap** — a run still in flight (no `job-end`,
|
|
55
|
+
spine written to within the panel's died window) is counted against the limit at its own leg
|
|
56
|
+
cap, not its spend so far, so two runs cannot both start against a limit only one fits. A died
|
|
57
|
+
run still counts its floor. The Money tab's "$ month" stays real spend.
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- **Monthly limit no longer double-counts a resume** — `run-u --resume` on a leg killed within
|
|
62
|
+
`DIED_MTIME_MS` reserved the old leg at its full cap AND asked for the remainder, so a resume
|
|
63
|
+
that fit was refused; the resumed leg now counts its real spend only
|
|
64
|
+
(`checkMonthlyRoom({ resumingSpine })`).
|
|
65
|
+
- **`POST /api/settings/money` no longer clears the limit on a bad body** — an empty or
|
|
66
|
+
unparseable body, or JSON without the `monthlyLimitUsd` key, is a 400 and leaves `config.json`
|
|
67
|
+
alone; only an explicit `null` or blank clears the limit.
|
|
68
|
+
- **A bad `monthlyLimitUsd` no longer reads as "no limit"** — a `monthlyLimitUsd` present in
|
|
69
|
+
`config.json` but not a number above 0 (`"50"`, `-5`, `0`, `true`, `{}`) now refuses the run
|
|
70
|
+
start and shows as a config problem on the Money tab, like an unparseable file. Absent or
|
|
71
|
+
`null` still means no limit.
|
|
72
|
+
- **Panel author routes: `jobsDir` and body size** — the name-collision check's `jobsDir` test
|
|
73
|
+
seam is now server-side only (it was readable off the client's job card, so any
|
|
74
|
+
`/api/author/start` body could repoint it), and POST bodies over 1 MiB get 413 and the request
|
|
75
|
+
is destroyed (the reader had no cap and ran before the click guard).
|
|
76
|
+
- **Providers Test** — a connection failure now reads "nothing answering at <URL>"; the Test
|
|
77
|
+
cell keeps a fixed width and wraps its result. **Tokens used** counts older model-only spines
|
|
78
|
+
toward the row whose Name exactly equals the model.
|
|
79
|
+
|
|
80
|
+
## [0.31.0] — 2026-09-29
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
|
|
84
|
+
- **Panel Chat authoring (P3)** — the Chat tab now drafts a job from the page: a job card
|
|
85
|
+
(source, goal, Model, `Cap $` / Time cap), a chat thread, and Send / Revise / **Sign &
|
|
86
|
+
run** actions, backed by new `POST /api/author/*` routes. Every POST needs the
|
|
87
|
+
per-server-start token plus an Origin/Host check (the human-click guard); the sign route
|
|
88
|
+
additionally requires the exact spec hash from the session's own signing file and is the
|
|
89
|
+
only path that ever spawns `run-u` (argv array, ends in `--approve <hash>`). A single
|
|
90
|
+
progress indicator (amber cycling dots, green `[done]`, red `[stopped]` with the reason)
|
|
91
|
+
replaces per-phase chat bubbles; step lines carry no who-label; a pending ask always wins
|
|
92
|
+
the label ("waiting for your OK" / "install needed" / "waiting for your answer"). The
|
|
93
|
+
page opens on the Chat tab.
|
|
94
|
+
- **Install-needed pause + Check again** — a repo copy with missing dependencies no longer
|
|
95
|
+
refuses outright: the session pauses, names the copy and the exact install command, and a
|
|
96
|
+
**Check again** button re-runs the same check on the same copy (bareloop still never
|
|
97
|
+
installs anything).
|
|
98
|
+
- **Model check under Model** — one `[✓]`/`[✗]` line: key found/missing/bad-shape/rejected
|
|
99
|
+
(401/403 reads "key rejected", not "may be flaky") plus a $0 GET-only reachability probe
|
|
100
|
+
(`GET /api/author/model-check`, `checkProviderReachable` in `src/providers.js`). A missing
|
|
101
|
+
or bad key blocks Start; an unreachable endpoint shows red but never blocks.
|
|
102
|
+
- **One cap covers drafting + run** — the separate Drafting cap field is gone. Drafting runs
|
|
103
|
+
under the same signed Cap `$`, and at sign the run's enforced ceiling is Cap minus what
|
|
104
|
+
drafting already spent. `runJob` takes `draftSpentUsd` and `draftSpendComplete` (carried on
|
|
105
|
+
`job-start` as their own fields, never `priorSpentUsd`, so a first run never reads as a
|
|
106
|
+
resume); `run-u` gains `--draft-spent-usd <n>` and `--draft-spend-incomplete` (validated
|
|
107
|
+
like `--read-shim`/`--scout`, carried in every resume/decide re-invocation tail);
|
|
108
|
+
`bareloop author` prints its drafting spend and the exact `run-u` command. Spend renders
|
|
109
|
+
as "$X ($Y drafting) of $Z", and an incomplete drafting fold reads "at least" on both
|
|
110
|
+
figures.
|
|
111
|
+
- **Plain-question check lines** — the "checks N/M" headline is withdrawn. Run-tab part
|
|
112
|
+
cards and the Audit tab show "all N checks passed" or the first failing check in plain
|
|
113
|
+
words, e.g. "check #2 src/checks.js has 0 type errors? — 4 left, need 0"; an expanded
|
|
114
|
+
attempt row lists every declared check in order, including ones that never ran
|
|
115
|
+
("not run"). One server-side owner maps each declared stage to its question ("did it
|
|
116
|
+
change any file?", "no casts or silencers added?", "does the test suite pass?", "no
|
|
117
|
+
failing tests?", …); numbers use three closed shapes ("N left, need T" / "N of total" /
|
|
118
|
+
"N, limit M").
|
|
119
|
+
- Coloured `[✓]`/`[✗]` result glyphs on Run-tab part cards.
|
|
120
|
+
- The fix worker's prompt now carries a facts-only close-grade history: "checks passed
|
|
121
|
+
N/M -> N/M -> …" (over the DECLARED stage count) and each stage's own number history
|
|
122
|
+
("count-stage 12 -> 0"). Nothing generated, no advice.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- **Panel money renders at 2 decimals everywhere** (`<$0.01` for a real sub-cent amount, a
|
|
127
|
+
floor rounds down). The CLI's 4-decimal `--all` output is unchanged.
|
|
128
|
+
- **The "worse than before" confirm-turn question is retired** from both the panel and the
|
|
129
|
+
CLI — an empty answer always meant "nothing beyond Guardrails"; it now is never asked.
|
|
130
|
+
- Chat tab cap row and labels follow the mockup (`Cap $`, `Token price $`, Time cap);
|
|
131
|
+
Audit tab Time cells show `HH:MM:SS` (full ISO on hover) instead of overflowing at
|
|
132
|
+
390px.
|
|
133
|
+
|
|
134
|
+
### Fixed
|
|
135
|
+
|
|
136
|
+
- **Live runs show a running floor, not "unknown"** — "$X so far (Y drafting) of $CAP ·
|
|
137
|
+
running Zm" on the Summary box and on a live run's own card; part-level tool counts on
|
|
138
|
+
a live run resolve through the same live audit-sidecar reader as the Audit tab.
|
|
139
|
+
- **Job tab was permanently blank** — two elements shared `id="job-card"`; the read-only
|
|
140
|
+
card is now `job-card-readonly`, with a regression test banning duplicate ids.
|
|
141
|
+
- **A failed or rejected chat POST no longer loses the typed answer** — the input clears only
|
|
142
|
+
on a proven success and errors persist in their own element instead of being wiped by
|
|
143
|
+
the 2-second poll.
|
|
144
|
+
- **Fix-loop governor reads a declared close's numbers (F198)** — it fed on text-parsed
|
|
145
|
+
grades that read `null` for every declared count-not-worse gap; it now reads the close's
|
|
146
|
+
structured `{stage, value}`, so its progress/strike counting sees real convergence or
|
|
147
|
+
stalling. No limit or direction change.
|
|
148
|
+
- **Escalated fix-loop exhaustion no longer advertises `--resume` (F197)** — `escalated` is
|
|
149
|
+
not resumable, so the option states that plainly instead of an unreachable "rerun with
|
|
150
|
+
--resume".
|
|
151
|
+
- The plan part's wall time no longer reads ~0.0s for a real multi-second call (replay-side
|
|
152
|
+
fix; display only).
|
|
153
|
+
- "Checks passed N/M" uses the declared stage count, not the count that ran under
|
|
154
|
+
first-red-wins — in the panel and in the fix worker's prompt (F203).
|
|
155
|
+
|
|
156
|
+
### Docs
|
|
157
|
+
|
|
158
|
+
- `bareloop.context.md` documents the `/api/author/*` routes, `draftSpentUsd`,
|
|
159
|
+
`draftSpendComplete`, `--draft-spent-usd`, `--draft-spend-incomplete` and
|
|
160
|
+
`moneyWithDraft`; PANEL-BUILD.md and the PRD carry the dated P3 / one-cap addenda.
|
|
161
|
+
|
|
8
162
|
## [0.30.0] — 2026-09-27
|
|
9
163
|
|
|
10
164
|
### 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,18 +3294,25 @@ 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).
|
|
3234
|
-
|
|
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
|
|
3305
|
+
P1 endpoint is GET/HEAD only (anything else — including every write verb — is `405`, outside the
|
|
3306
|
+
two human-click-guarded write families `/api/author/*` and `/api/settings/*` below); a URL
|
|
3235
3307
|
never joins a path segment into a filesystem read — a runid is looked up in the run list
|
|
3236
3308
|
first (`RUNID_RE`, `src/panel/server.js` — accepts a `~2`-style backfill-disambiguated
|
|
3237
3309
|
suffix too, and every endpoint echoes back the LISTED runid, never a filename-derived one
|
|
3238
3310
|
that can silently drop that suffix), and only the path THAT ROW stores is ever read. A
|
|
3239
3311
|
taken port fails loudly (names the port, exit `1`) — this command never silently tries a
|
|
3240
|
-
different one.
|
|
3241
|
-
|
|
3312
|
+
different one. This read path runs no interview, no author and no run, and reads no key
|
|
3313
|
+
value (the write families below do, server-side, and never return one). One more caller of `src/replayio.js`/`src/runlist.js`, in-process, per the layering
|
|
3242
3314
|
law (`docs/product/PANEL-BUILD.md` §2) — the panel's HTTP handler holds no flow logic of
|
|
3243
|
-
its own. Chat/
|
|
3244
|
-
hiding the gap.
|
|
3315
|
+
its own. Chat/authoring (P3) and Settings (P4a/P4b) are built — see below.
|
|
3245
3316
|
|
|
3246
3317
|
- **Panel P2 (live run view, `docs/product/PANEL-BUILD.md` Addendum 2026-09-27)** → the page
|
|
3247
3318
|
polls, it does not push (no SSE/websocket). One `setInterval` (2s) drives two throttled
|
|
@@ -3260,10 +3331,56 @@ interviews of their own. All three are dispatched by name only: `bareloop run-u
|
|
|
3260
3331
|
`resolveAuditPathForRow` (`src/panel/server.js`), the one shared owner the Run tab's tools
|
|
3261
3332
|
summary and the Audit tab both read through — tried only while the run's own spine has no
|
|
3262
3333
|
`job-end` yet, so a finished run never risks reading a stale leftover file at that path.
|
|
3263
|
-
`scripts/replay-live.mjs <sourceSpine> <outDir> [--speed N]` is a dev-only, unshipped ($0,
|
|
3334
|
+
`scripts/replay-live.mjs <sourceSpine> <outDir> [--speed N] [--live-audit]` is a dev-only, unshipped ($0,
|
|
3264
3335
|
no provider call) instrument that paces a real archived spine's records back out under a
|
|
3265
3336
|
new runid so the panel can be exercised against a "live" run without a paid run — it is not
|
|
3266
3337
|
a substitute for watching a real run start-to-end, which stays a separately authorized step.
|
|
3338
|
+
`--live-audit` (opt-in) puts the sidecar at a scratch patient dir during the replay so the
|
|
3339
|
+
live-fallback path above is exercised, then moves it to the sibling name at the end.
|
|
3340
|
+
|
|
3341
|
+
- **Panel P3 (chat/authoring, `docs/product/PANEL-BUILD.md` Addendum 2026-09-27)** → one of the
|
|
3342
|
+
panel's TWO families of write routes (the other is `/api/settings/*`, see "Settings" below), this
|
|
3343
|
+
one under `/api/author/*` (`src/panel/authorroutes.js`); every other route stays GET/HEAD-only
|
|
3344
|
+
per P1's own rule. A POST body over 1 MiB is refused `413` before it is parsed, and the job
|
|
3345
|
+
card's `jobsDir` (where an existing job name is looked up) is a server-side seam only, never read
|
|
3346
|
+
off the client's card. Every route
|
|
3347
|
+
requires the human-click guard: an `x-bareloop-token` header matching a fresh token minted
|
|
3348
|
+
once per server start (templated into `index.html` like the port) AND an Origin/Host
|
|
3349
|
+
naming this exact `127.0.0.1:<port>` — a request failing either gets `403`, before the
|
|
3350
|
+
route body ever runs. `POST /api/author/start` (one job-card body; `409` while a prior
|
|
3351
|
+
session is still non-terminal — one authoring session at a time) creates a session
|
|
3352
|
+
(`src/panel/authorsession.js`) that runs `prepareSource`/`detectLanguage`/`validateJob`/
|
|
3353
|
+
`authorCloseForJob`/`assembleSpec`/`prepareSigning` in-process, exactly the library calls
|
|
3354
|
+
`bareloop interview`/`bareloop author` already make — never a script's own readline loop,
|
|
3355
|
+
never a reimplementation of any gate. `GET /api/author/model-check?model=<id>` is the $0 readiness probe the job card runs
|
|
3356
|
+
before Start (`checkProviderReachable`, `src/providers.js` — a models-list GET only, never a
|
|
3357
|
+
completion; the page sees the key's NAME and a status word, never the value). `POST
|
|
3358
|
+
/api/author/:id/check-deps` is the install-gap's "Check again" (`phase:'install-needed'`:
|
|
3359
|
+
the session waits on the person's own install, then re-runs `missingDependencies` on the
|
|
3360
|
+
same copy — bareloop never installs). `GET /api/author/:id` polls the session's state
|
|
3361
|
+
(phase, chat messages, cost, `revisesLeft`, `specHash` once prepared). `POST /api/author/
|
|
3362
|
+
:id/send {text}` answers whatever the confirm turn is currently asking (the `language` pick or a plan's
|
|
3363
|
+
own follow-up question — the old `worseThanBefore` ask is retired, 2026-09-28) — refused outright when the pending ask is the
|
|
3364
|
+
plan MENU itself (`{ok:false}`, no route from chat text to a plan decision, ever). `POST
|
|
3365
|
+
/api/author/:id/revise {text}` is the menu's own `fix` pick, with the chat text as the
|
|
3366
|
+
correction (D3: max 2 rounds, `revisesLeft` derived from the confirm turn's own round
|
|
3367
|
+
number, never a second hardcoded cap). `POST /api/author/:id/sign-prepare` is the menu's
|
|
3368
|
+
own `confirm` pick — it runs gates 1–4 and reaches `phase:'prepared'` with a `specHash`; it
|
|
3369
|
+
NEVER signs. `POST /api/author/:id/sign {specHash}` is the ONLY route that spawns a run —
|
|
3370
|
+
it refuses unless `phase==='prepared'` and the posted hash matches the session's own
|
|
3371
|
+
`signing.json` `specHash` exactly, then spawns `setsid systemd-inhibit … node bin/
|
|
3372
|
+
bareloop.mjs run-u --spec <resolved-spec.json> --approve <hash>` (plus `--draft-spent-usd <n>`
|
|
3373
|
+
[`--draft-spend-incomplete`] when the session's drafting spend is > 0 — one cap covers
|
|
3374
|
+
drafting + run) detached (array argv,
|
|
3375
|
+
never a shell string), with its own log file inside the session's own dir, and the
|
|
3376
|
+
server's environment with `~/.config/bareloop/.env` merged in per request (a key is never read
|
|
3377
|
+
into or sent to the page — a missing one refuses the session at $0, naming only the env var). `src/panel/authorsession.js` takes
|
|
3378
|
+
test-only DI seams (`scout`/`generate`/`confirmGenerate`/`authorFn`/`prepareSigningFn`) so
|
|
3379
|
+
a test can drive the real ask()-channel/revise/hash wiring without a live provider call;
|
|
3380
|
+
none of them are reachable from `authorroutes.js`'s real construction path. Sessions live
|
|
3381
|
+
under `~/.config/bareloop/panel-sessions/<id>/` (each one's own `resolved-spec.json` and
|
|
3382
|
+
`signing.json`, the two files `bareloop author` itself already writes). Edit/re-sign, the
|
|
3383
|
+
`~/.config/bareloop/.env` keys-file loader, and Settings have landed (P4a/P4b — see "Settings" below).
|
|
3267
3384
|
|
|
3268
3385
|
- **`bareloop run-u <flags…>`** (PANEL-BUILD.md P0 task 2/4) → the person-path run flow
|
|
3269
3386
|
(the JOBS-table/`--spec` runner, resume, the review door — `docs/logs/FINDINGS.md`'s
|
|
@@ -3327,6 +3444,15 @@ interviews of their own. All three are dispatched by name only: `bareloop run-u
|
|
|
3327
3444
|
returns a code / throws" rule `src/userrun.js` already keeps. Like `interview`, `src/cli.js`
|
|
3328
3445
|
hands it raw `stdin`/`stdout`/`stderr` (its one interactive seam is the confirm turn).
|
|
3329
3446
|
|
|
3447
|
+
**`--budget` and `--draft-spent-usd` (hamr's ruling 2026-09-28, "one cap covers drafting +
|
|
3448
|
+
run"):** `--budget` still means the AUTHORING ceiling, unchanged — the panel sets it equal to
|
|
3449
|
+
the job card's one `Cap $`. On a SIGNING PREPARED stop, `author` prints its own known
|
|
3450
|
+
drafting spend (a floor, never $0 on an unpriced call — F6) and the exact `run-u` command to
|
|
3451
|
+
run it, now including `--draft-spent-usd <known-floor>` when that floor is > 0 — the SAME
|
|
3452
|
+
signed `budgetUsd` is the run's cap too, and `run-u` enforces the remainder
|
|
3453
|
+
(`Cap $ − drafting spent`) as ITS own ceiling. Nothing here is left for a person to
|
|
3454
|
+
hand-compute or re-type, the same rule F185 set for `--spec`.
|
|
3455
|
+
|
|
3330
3456
|
**Tighten-only budget/wall.** `--budget`/`--wall` on `bareloop run` may only lower the
|
|
3331
3457
|
bundle's own signed `budgetUsd`/`maxWallMs` — `checkEnvelope`'s `envelope-widen` red refuses
|
|
3332
3458
|
anything higher. Raising a ceiling is a re-export (a new signed bundle), never a runner flag.
|
|
@@ -3369,6 +3495,75 @@ provider but `anthropic-api`, spine bundling for `formatReplay` on the minting r
|
|
|
3369
3495
|
`runid` is noted), and tarball packing — a bundle is a plain directory; use `npm pack` on it
|
|
3370
3496
|
yourself if you want a tarball.
|
|
3371
3497
|
|
|
3498
|
+
### Settings — the keys file, `config.json`, the monthly $ limit, the Providers tab (PANEL-BUILD.md P4a, `src/keysfile.js`, `src/config.js`, `src/monthly.js`, `src/providerrows.js`, `src/panel/settingsroutes.js`)
|
|
3499
|
+
|
|
3500
|
+
Two files in `~/.config/bareloop/`, outside any repo:
|
|
3501
|
+
|
|
3502
|
+
- **`.env` — key VALUES only.** Plain `NAME=value` lines (optional `export `, `#` comments, one pair
|
|
3503
|
+
of quotes). `loadKeysEnv({ env?, home? })` merges it over the environment; **the shell wins** —
|
|
3504
|
+
the file only fills names the shell leaves unset. Mode other than 600 = a `warning` string
|
|
3505
|
+
(printed on CLI start, shown in Settings), never a refusal. Values go only to provider
|
|
3506
|
+
construction; the page and every log get NAMES and found / not set. `keysForDoor(deps)` is the
|
|
3507
|
+
door helper: an injected `env` with no `keysHome` skips the file entirely (tests never read the
|
|
3508
|
+
real one). The CLI (`run-u`, `interview`, `author`) and the panel both use it.
|
|
3509
|
+
- **`config.json` — every setting, never a key value.** `readConfig({ home })` /
|
|
3510
|
+
`updateConfig(patch, { home })` (atomic tmp + rename, mode 600, unknown fields kept).
|
|
3511
|
+
Fields: `monthlyLimitUsd` (number above 0; absent or `null` = no limit; any other value present
|
|
3512
|
+
is a `ConfigError`, refused like an unreadable file), `anthropicBalanceNote` (a note the
|
|
3513
|
+
person types; no check ever reads it), `keys.<ENV NAME>` = `{ name, shape, baseUrl }` (the Providers rows' settings). A secret-shaped
|
|
3514
|
+
string anywhere in the document is refused on save. An unreadable file is a `problem` string and
|
|
3515
|
+
a refused run start ($0), never "no limit". There is no monthly TIME limit (dropped).
|
|
3516
|
+
|
|
3517
|
+
**Monthly $ limit.** `checkMonthlyRoom({ capUsd, home, now })` → `{ ok, leftUsd, limitUsd,
|
|
3518
|
+
atLeast }`; `monthlyRefusalText(room)` is the ONE spelling: `Max $<left, 2 decimals> (monthly
|
|
3519
|
+
limit)`. It is called at the run-start seam in `src/userrun.js` `execute` (before any token
|
|
3520
|
+
spends; the CLI prints it to stderr, exit 2) and at the panel's Sign & run (`signRun` in
|
|
3521
|
+
`src/panel/authorroutes.js`, refused server-side whatever the page shows). The page only echoes the
|
|
3522
|
+
same text under `#jf-cap-money` via `GET /api/author/monthly-check?cap=`. "This month" is the local
|
|
3523
|
+
calendar month; a died or incomplete-spend run makes the total an "at least" figure. A run still
|
|
3524
|
+
IN FLIGHT (`job-start`, no `job-end`, spine file written within `DIED_MTIME_MS`, `src/runlist.js`) is
|
|
3525
|
+
counted at its full leg cap (`budgetUsd` less `priorSpentUsd` on its `job-start`), not its spend so
|
|
3526
|
+
far (except the run a resume continues, which counts its real spend), so two runs cannot both start against a limit only one fits; a died run counts its floor.
|
|
3527
|
+
Only the refusal reserves the cap (`monthSpend().reservedUsd`); the Money tab's month figure is
|
|
3528
|
+
real spend.
|
|
3529
|
+
|
|
3530
|
+
**Money & limits tab** (`GET/POST /api/settings/money`): two tiles ($ and tokens, this month / to date, a
|
|
3531
|
+
`≥` prefix when a figure is a floor) and a per-provider table ($, minutes, tokens; a run is grouped
|
|
3532
|
+
by the provider on its `job-start`, an older spine by its model). The limit field auto-saves on
|
|
3533
|
+
change (no Save button); blank clears it. `POST` needs an explicit `monthlyLimitUsd` key (a number
|
|
3534
|
+
above 0, or `null`/blank to clear) — an empty or key-less body is a `400`, never "clear the limit".
|
|
3535
|
+
|
|
3536
|
+
**Providers tab (P4b, `src/providerrows.js`).** One row per key in `.env` that HAS A VALUE — nothing is
|
|
3537
|
+
hardcoded; an empty line is no row, a removed line drops its row on Reload keys. A missing `.env` is
|
|
3538
|
+
created (mode 600) on the first Providers read with five empty preset lines (`ANTHROPIC_API_KEY`,
|
|
3539
|
+
`DEEPSEEK_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `LOCAL_API_KEY`); an existing file is never
|
|
3540
|
+
edited. A value of `null` counts as SET and means "no real key" (the provider is handed the
|
|
3541
|
+
placeholder `no-key-needed`, never the word `null`). Each row: the env NAME (read-only) + found,
|
|
3542
|
+
**Name** (the model id Chat uses), **API shape** (Anthropic / OpenAI-compatible / Gemini), **Base
|
|
3543
|
+
URL** (blank = the shape's own host), **Test**, **Tokens used** (total, display only), and Balance
|
|
3544
|
+
(DeepSeek fetched, Anthropic a typed note). Saves on change to `config.json`
|
|
3545
|
+
`keys.<ENV NAME> = { name, shape, baseUrl }`. Defaults for a key with no saved entry:
|
|
3546
|
+
`ANTHROPIC_API_KEY` claude-sonnet-5 / Anthropic; `DEEPSEEK_API_KEY` deepseek-flash /
|
|
3547
|
+
OpenAI-compatible / `https://api.deepseek.com/v1`; `OPENAI_API_KEY` OpenAI-compatible;
|
|
3548
|
+
`GEMINI_API_KEY` Gemini; `LOCAL_API_KEY` OpenAI-compatible / `http://127.0.0.1:11434/v1` (Ollama, LM
|
|
3549
|
+
Studio, llama.cpp: change the URL); any other name OpenAI-compatible; Name blank until typed. **No
|
|
3550
|
+
price is shown anywhere** (display only — cost recording, the estimated provenance, spend sums, caps
|
|
3551
|
+
and the monthly limit are unchanged; a local model has no known rate, so its rounds come out as a
|
|
3552
|
+
non-zero loud guess, never a real $0).
|
|
3553
|
+
|
|
3554
|
+
`providerrows.js` is the ONE owner of "model → key + shape + URL": `keyRows`, `findRow` (rows on the
|
|
3555
|
+
same shape + endpoint; the Name breaks a tie), `modelChoiceFor` / `chatModels` (Chat's Model menu IS
|
|
3556
|
+
the rows; a blank Name is not offered), `keyNameFor` / `applyConfiguredKey` (a job on `(provider,
|
|
3557
|
+
baseUrl[, model])` reads the matching row's variable, else the provider's built-in one). All doors
|
|
3558
|
+
(`run-u`, `interview`, `author`, the panel's drafting) use it. Routes (all behind the per-start token
|
|
3559
|
+
and Origin check; the chat can never reach them): `GET /api/settings/providers`, `POST .../row`
|
|
3560
|
+
(`{envName, name, shape, baseUrl}`), `POST .../balance-note`, `POST .../test` (one
|
|
3561
|
+
`checkProviderReachable` models-list GET at the row's shape + URL, $0; a refused connection reads
|
|
3562
|
+
`nothing answering at <URL>`), `GET .../balance?env=`
|
|
3563
|
+
(DeepSeek-hosted row, server-side), and `GET /api/author/models` (Chat's Model menu). The old
|
|
3564
|
+
`providers.<row>.key` config field and the key dropdown are gone. Like the authoring routes, a POST
|
|
3565
|
+
body over 1 MiB is refused `413`.
|
|
3566
|
+
|
|
3372
3567
|
### The source front door — a plain folder/file/URL, no `--patient` (PRD item 33/M2, `src/source.js`)
|
|
3373
3568
|
|
|
3374
3569
|
A repo job still gives `runJob` a `--patient` checkout it owns. A NON-code job (a resume, a
|
package/bin/bareloop.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bareloop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.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 });
|