bareloop 0.39.0 → 0.41.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.
Files changed (76) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/bareloop.context.md +137 -112
  3. package/package.json +2 -2
  4. package/src/authorflow.js +333 -58
  5. package/src/authoring.js +65 -2
  6. package/src/authorjob.js +57 -9
  7. package/src/authorreadout.js +50 -2
  8. package/src/authorrun.js +30 -27
  9. package/src/authorscout.js +9 -6
  10. package/src/bridges.js +1 -1
  11. package/src/bundle.js +7 -4
  12. package/src/bundlerun.js +4 -1
  13. package/src/calibrate.js +1 -1
  14. package/src/cardauthor.js +4 -1
  15. package/src/cli.js +1 -1
  16. package/src/config.js +129 -29
  17. package/src/declaredclose.js +4 -3
  18. package/src/draftspend.js +6 -1
  19. package/src/index.js +2 -4
  20. package/src/interviewrun.js +73 -52
  21. package/src/job.js +50 -11
  22. package/src/jobblock.js +140 -0
  23. package/src/judged.js +12 -44
  24. package/src/keysfile.js +15 -7
  25. package/src/kinds.js +2 -2
  26. package/src/ledger.js +1 -1
  27. package/src/modellist.js +84 -0
  28. package/src/monthly.js +23 -43
  29. package/src/panel/authorroutes.js +34 -19
  30. package/src/panel/authorsession.js +187 -72
  31. package/src/panel/index.html +699 -215
  32. package/src/panel/jobplan.js +107 -0
  33. package/src/panel/runroutes.js +3 -0
  34. package/src/panel/server.js +102 -28
  35. package/src/panel/settingsroutes.js +232 -173
  36. package/src/planrun.js +6 -6
  37. package/src/promptregisters.js +1 -1
  38. package/src/providerrows.js +226 -141
  39. package/src/providers.js +58 -250
  40. package/src/providershapes.js +18 -0
  41. package/src/replay.js +1 -1
  42. package/src/reuse.js +1 -1
  43. package/src/root.js +1 -1
  44. package/src/run.js +3 -3
  45. package/src/selection.js +1 -1
  46. package/src/source.js +64 -1
  47. package/src/text.js +13 -0
  48. package/src/thinking.js +130 -0
  49. package/src/transport.js +1 -1
  50. package/src/userrun.js +53 -76
  51. package/types/authorflow.d.ts +258 -39
  52. package/types/authoring.d.ts +7 -1
  53. package/types/authorjob.d.ts +35 -3
  54. package/types/authorreadout.d.ts +17 -1
  55. package/types/authorscout.d.ts +5 -3
  56. package/types/config.d.ts +23 -6
  57. package/types/declaredclose.d.ts +4 -1
  58. package/types/draftspend.d.ts +8 -2
  59. package/types/index.d.ts +1 -1
  60. package/types/job.d.ts +6 -6
  61. package/types/jobblock.d.ts +102 -0
  62. package/types/judged.d.ts +5 -36
  63. package/types/keysfile.d.ts +15 -4
  64. package/types/modellist.d.ts +50 -0
  65. package/types/monthly.d.ts +4 -3
  66. package/types/panel/authorsession.d.ts +44 -15
  67. package/types/panel/jobplan.d.ts +58 -0
  68. package/types/panel/server.d.ts +34 -4
  69. package/types/planrun.d.ts +1 -1
  70. package/types/providerrows.d.ts +192 -99
  71. package/types/providers.d.ts +35 -116
  72. package/types/providershapes.d.ts +10 -0
  73. package/types/run.d.ts +1 -1
  74. package/types/source.d.ts +26 -0
  75. package/types/thinking.d.ts +90 -0
  76. package/types/userrun.d.ts +6 -1
@@ -79,26 +79,38 @@ export function refuseLockedKind(kind: string): Refusal;
79
79
  * one-genre-at-a-time law is untouched.
80
80
  *
81
81
  * @param {{answers: Record<string|number, any>, repoPath?: string|null, lang: string,
82
+ * jobLines?: {n: number, text: string}[]|null, inputs?: {n: number, label: string, value: string}[]|null,
82
83
  * verdictType?: string|null,
83
84
  * questions?: Record<string|number, string>|null, generate?: Function, provider?: any,
84
85
  * seedRef?: string|null, scout?: any, listing?: any, ceilingUsd?: number|null, judgeModel?: string|null,
85
- * rates?: {in: number, out: number}|null,
86
+ * rates?: {in: number, out: number}|null, draftThinking?: 'on'|'off',
86
87
  * onPhase?: (phase: string, data?: any) => void,
87
- * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
88
+ * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string}) => void,
88
89
  * seedFn?: Function, scoutFn?: Function, listingFn?: Function,
89
90
  * authorFn?: Function, authorOpts?: object, writeScope?: string[]|null,
90
91
  * signerFix?: Function|any, proposeFn?: Function, compileOpts?: object,
91
92
  * ask?: ((step: {kind: string, [k: string]: any}) => Promise<string|null>)|null,
93
+ * answerQuestions?: ((o: {questions: {line: number, question: string}[], dropped: {reason: string, raw: any}[], confirmed: any}) =>
94
+ * Promise<{jobLines: {n: number, text: string, rule?: string}[], answers: Record<string|number, any>}|null>)|null,
92
95
  * confirmGenerate?: Function|null, isRepo?: boolean,
93
96
  * langResult?: {kind: string, candidates?: string[], [k: string]: any}|null}} o
94
97
  * @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
95
98
  * closeDecl: any, seedRef: string|null, authoring: any, judged: any, reds: Red[],
96
99
  * stop: string|null, cost: any, interview: any, confirmed: any}>}
97
100
  */
98
- export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, rates, writeScope, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, ask, confirmGenerate, isRepo, langResult, }: {
101
+ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, jobLines, inputs, generate, provider, seedRef, scout, listing, ceilingUsd, rates, draftThinking, writeScope, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, ask, confirmGenerate, isRepo, langResult, answerQuestions, }: {
99
102
  answers: Record<string | number, any>;
100
103
  repoPath?: string | null;
101
104
  lang: string;
105
+ jobLines?: {
106
+ n: number;
107
+ text: string;
108
+ }[] | null;
109
+ inputs?: {
110
+ n: number;
111
+ label: string;
112
+ value: string;
113
+ }[] | null;
102
114
  verdictType?: string | null;
103
115
  questions?: Record<string | number, string> | null;
104
116
  generate?: Function;
@@ -112,11 +124,13 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
112
124
  in: number;
113
125
  out: number;
114
126
  } | null;
127
+ draftThinking?: "on" | "off";
115
128
  onPhase?: (phase: string, data?: any) => void;
116
129
  onCall?: (call: {
117
130
  label: string;
118
131
  costUsd: number | null;
119
132
  unpricedRounds: number;
133
+ thinkingStatus?: string;
120
134
  }) => void;
121
135
  seedFn?: Function;
122
136
  scoutFn?: Function;
@@ -131,6 +145,24 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
131
145
  kind: string;
132
146
  [k: string]: any;
133
147
  }) => Promise<string | null>) | null;
148
+ answerQuestions?: ((o: {
149
+ questions: {
150
+ line: number;
151
+ question: string;
152
+ }[];
153
+ dropped: {
154
+ reason: string;
155
+ raw: any;
156
+ }[];
157
+ confirmed: any;
158
+ }) => Promise<{
159
+ jobLines: {
160
+ n: number;
161
+ text: string;
162
+ rule?: string;
163
+ }[];
164
+ answers: Record<string | number, any>;
165
+ } | null>) | null;
134
166
  confirmGenerate?: Function | null;
135
167
  isRepo?: boolean;
136
168
  langResult?: {
@@ -12,7 +12,7 @@ export function declarationLines({ goal, closeDecl }: {
12
12
  * F176'S FALLBACK, SAID OUT LOUD — the revise ladder kept the NEWEST SOUND
13
13
  * declaration rather than the last one it happened to try, because "kept the
14
14
  * last accepted revision" is not the same claim as "kept the best revision"
15
- * (docs/logs/FINDINGS.md F176). Absent on every run where the fallback never
15
+ * (docs/wiki/FINDINGS.md F176). Absent on every run where the fallback never
16
16
  * fired (the overwhelming majority), which is why this returns an EMPTY list
17
17
  * rather than a "no fallback" line — a green close has nothing to say here and
18
18
  * printing it every run would be noise, not information.
@@ -216,6 +216,22 @@ export function proposalStopText({ stop, reds, cases, spend, source }: {
216
216
  } | null;
217
217
  source?: "proposal" | "signer";
218
218
  }): string;
219
+ /**
220
+ * @param {{stop: string, reds?: PlainRed[]|null, refusal?: {detail?: string}|null,
221
+ * spend?: {knownUsd?: number|null, spendComplete?: boolean|null}|null}} o
222
+ * @returns {string}
223
+ */
224
+ export function draftStopText({ stop, reds, refusal, spend }: {
225
+ stop: string;
226
+ reds?: PlainRed[] | null;
227
+ refusal?: {
228
+ detail?: string;
229
+ } | null;
230
+ spend?: {
231
+ knownUsd?: number | null;
232
+ spendComplete?: boolean | null;
233
+ } | null;
234
+ }): string;
219
235
  /**
220
236
  * Plain sentences for the reds of a refused signing, one or more per red. Card and practice-case reds reuse the
221
237
  * proposal table; an unknown code is a generic sentence that quotes the code.
@@ -92,15 +92,15 @@ export function defaultSurveyor({ workdir, granted, auditPath, ctx }: {
92
92
  * @param {{workdir: string, provider?: any,
93
93
  * rounds?: number, minBytes?: number, blobMax?: number, maxTokens?: number, ctx?: boolean,
94
94
  * attempts?: number, ceilingUsd?: number|null, callTimeoutMs?: number,
95
- * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
95
+ * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string}) => void,
96
96
  * createLoop?: (o: {system: string, policy: any, onLlmResult: any, provider: any, rates?: {in: number, out: number}}) => any,
97
- * rates?: {in: number, out: number}|null,
97
+ * rates?: {in: number, out: number}|null, callOptions?: Record<string, any>,
98
98
  * createSurveyor?: (o: {workdir: string, granted: readonly string[], ctx: boolean}) => Promise<any>}} o
99
99
  * @returns {Promise<Survey & {raw: string, raws: ReturnType<typeof scrubRaw>[],
100
100
  * calls: {label: string, costUsd: number|null, unpricedRounds: number}[],
101
101
  * budgetStop: 'cap-halt'|'pricing-red'|null}>}
102
102
  */
103
- export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, maxTokens, ctx, attempts, ceilingUsd, callTimeoutMs, onCall, createLoop, rates, createSurveyor, }: {
103
+ export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, maxTokens, ctx, attempts, ceilingUsd, callTimeoutMs, onCall, createLoop, rates, callOptions, createSurveyor, }: {
104
104
  workdir: string;
105
105
  provider?: any;
106
106
  rounds?: number;
@@ -115,6 +115,7 @@ export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, m
115
115
  label: string;
116
116
  costUsd: number | null;
117
117
  unpricedRounds: number;
118
+ thinkingStatus?: string;
118
119
  }) => void;
119
120
  createLoop?: (o: {
120
121
  system: string;
@@ -130,6 +131,7 @@ export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, m
130
131
  in: number;
131
132
  out: number;
132
133
  } | null;
134
+ callOptions?: Record<string, any>;
133
135
  createSurveyor?: (o: {
134
136
  workdir: string;
135
137
  granted: readonly string[];
package/types/config.d.ts CHANGED
@@ -16,15 +16,31 @@ export function configPath(home?: string): string;
16
16
  export function readConfig(opts?: {
17
17
  home?: string;
18
18
  }): ConfigRead;
19
+ /**
20
+ * One key-row field's value problem in plain words, or null when fine.
21
+ * @param {string} field `model` | `shape` | `baseUrl` | `legacyMaxTokens`
22
+ * @param {unknown} v
23
+ * @returns {string|null}
24
+ */
25
+ export function providerFieldProblem(field: string, v: unknown): string | null;
26
+ /**
27
+ * THE one rule for a row's model lines, used by config validation and the Settings routes. A price
28
+ * is a PAIR (`inPerM` and `outPerM`, each a finite number >= 0 — 0 / 0 typed is a chosen price) or
29
+ * neither (null / absent). A ticked line (`use`) needs both. Plain words, or null.
30
+ * @param {unknown} list
31
+ * @returns {string|null}
32
+ */
33
+ export function modelsProblem(list: unknown): string | null;
19
34
  /**
20
35
  * Apply a patch and write. `null` deletes a field. Top-level fields shallow-merge;
21
- * `keys` merges per key variable name, then per field. Validated before anything is
22
- * written; nothing is written on a refusal.
36
+ * `keys` merges per key variable name, then per field; `models.<ENV>` is REPLACED WHOLE (`null`
37
+ * clears it). Validated before anything is written; nothing is written on a refusal.
23
38
  *
24
- * Patch fields understood: `monthlyLimitUsd` (finite > 0), `anthropicBalanceNote`
25
- * (finite >= 0), `keys.<ENV NAME>.{name,shape,baseUrl}` (Name = a model id, shape = a provider table name, baseUrl = blank or http(s)). Any other
26
- * field is passed through untouched (kept), but the whole document is swept for
27
- * secret-shaped strings first.
39
+ * Patch fields understood: `monthlyLimitUsd` (finite > 0), `keys.<ENV>.{shape,baseUrl,legacyMaxTokens}`,
40
+ * `models.<ENV>` (a list judged by {@link modelsProblem}). The first write of a row's `models` list
41
+ * removes that row's older `name` / `priceInPerM` / `priceOutPerM` in the SAME write (never two
42
+ * sources); a retired top-level `anthropicBalanceNote` is dropped on any write. Any other field is
43
+ * kept, but the whole document is swept for secret-shaped strings first.
28
44
  * @param {Record<string, any>} patch
29
45
  * @param {{ home?: string }} [opts]
30
46
  * @returns {Record<string, any>} the document as written
@@ -35,6 +51,7 @@ export function updateConfig(patch: Record<string, any>, opts?: {
35
51
  /** A plain-words config failure (bad value, secret-shaped string, unreadable file). */
36
52
  export class ConfigError extends Error {
37
53
  }
54
+ export function cleanBaseUrl(v: string): string;
38
55
  export type ConfigRead = {
39
56
  /**
40
57
  * the parsed document ({} when missing/unreadable)
@@ -53,7 +53,7 @@ export function declaredStages(closeDecl: any): any[] | null;
53
53
  *
54
54
  * @param {any} closeDecl
55
55
  * @param {{at?: string, listing?: string[]|null, deferListing?: boolean,
56
- * catalogue?: Record<string, any>, verdictType?: string|null, writeScope?: string[]|null}} [opts]
56
+ * catalogue?: Record<string, any>, verdictType?: string|null, writeScope?: string[]|null, jobLines?: {n: number}[]|null}} [opts]
57
57
  * @returns {{ok: boolean, reds: Red[], closeDecl: any, grounded: boolean,
58
58
  * scoped: {scoped: boolean, via: string|null},
59
59
  * ceiling: {class: string, kind: string, stage: string|null, locked: boolean}|null}}
@@ -65,6 +65,9 @@ export function validateCloseDecl(closeDecl: any, opts?: {
65
65
  catalogue?: Record<string, any>;
66
66
  verdictType?: string | null;
67
67
  writeScope?: string[] | null;
68
+ jobLines?: {
69
+ n: number;
70
+ }[] | null;
68
71
  }): {
69
72
  ok: boolean;
70
73
  reds: Red[];
@@ -2,7 +2,10 @@
2
2
  * One line of the drafting log. `no` = the step's position in the session's progress list (a step id can repeat).
3
3
  * - `step-start` {no, id, label, at}
4
4
  * - `step-end` {no, status: 'done'|'failed', label, at} (a re-opened step writes `step-reopen` {no, at} first)
5
- * - `call` {no, step, label, model, costUsd (null stays null), unpricedRounds, at}: one METERED model call
5
+ * - `step-label` {no, label, at} (the step reads `label` from now on, status and times untouched: the thinking row's "the model thought anyway" / "can't change it" / "not confirmed")
6
+ * - `call` {no, step, label, model, thinking, thinkingSupport, thinkingStatus?, costUsd (null stays null), unpricedRounds, at}: one METERED model call
7
+ * (`thinking` = the tick's mode: off | on; `thinkingSupport` = bare-agent's lookup for the model: true | false | 'unverified';
8
+ * `thinkingStatus` = bare-agent's own verdict for the call's run: applied | unsupported | ignored | unknown, present only when thinkingMode was sent)
6
9
  * (no token counts: the call path does not carry them without reworking the library, so none are recorded)
7
10
  * @typedef {{kind: string, no: number, at: string, [k: string]: any}} DraftEvent
8
11
  */
@@ -105,7 +108,10 @@ export const DRAFT_LOG_FILE: "draft-log.jsonl";
105
108
  * One line of the drafting log. `no` = the step's position in the session's progress list (a step id can repeat).
106
109
  * - `step-start` {no, id, label, at}
107
110
  * - `step-end` {no, status: 'done'|'failed', label, at} (a re-opened step writes `step-reopen` {no, at} first)
108
- * - `call` {no, step, label, model, costUsd (null stays null), unpricedRounds, at}: one METERED model call
111
+ * - `step-label` {no, label, at} (the step reads `label` from now on, status and times untouched: the thinking row's "the model thought anyway" / "can't change it" / "not confirmed")
112
+ * - `call` {no, step, label, model, thinking, thinkingSupport, thinkingStatus?, costUsd (null stays null), unpricedRounds, at}: one METERED model call
113
+ * (`thinking` = the tick's mode: off | on; `thinkingSupport` = bare-agent's lookup for the model: true | false | 'unverified';
114
+ * `thinkingStatus` = bare-agent's own verdict for the call's run: applied | unsupported | ignored | unknown, present only when thinkingMode was sent)
109
115
  * (no token counts: the call path does not carry them without reworking the library, so none are recorded)
110
116
  */
111
117
  export type DraftEvent = {
package/types/index.d.ts CHANGED
@@ -15,7 +15,7 @@ export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBr
15
15
  export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES, VOUCHED_RATE_SOURCES, rateProvenance, spendProvenance } from "./ledger.js";
16
16
  export { readShimArm, READ_SHIM_CAP, READ_SHIM_STRATEGY, READ_SHIM_DIFF_STRATEGY } from "./readshim.js";
17
17
  export { runDeclaredClose, runStage, seedRead, seedAtHead, LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER, HUMAN_DECISIONS, SEED_EXEMPT_KINDS, normalizeHumanRuling, resolveHumanRuling, JUDGE_ATTEMPTS, MAX_JUDGED_PATHS, DOOR_OPEN_OUTCOMES, REVIEW_DOOR_CLASSES, doorOpens, mechanicalStages } from "./kinds.js";
18
- export { JUDGE_MODEL, resolveJudge, JUDGE_RULES, JUDGE_RULE_IDS, runLocate, decide, CALIBRATION_SIZE, validateCalibrationSet, expectedOf } from "./judged.js";
18
+ export { resolveJudge, JUDGE_RULES, JUDGE_RULE_IDS, runLocate, decide, CALIBRATION_SIZE, validateCalibrationSet, expectedOf } from "./judged.js";
19
19
  export { proposeJudgedArtifacts, signJudgedArtifacts, foldJudgedArtifacts } from "./cardauthor.js";
20
20
  export { runCalibration, INJECTION_LOCATE_BATTERY, CASUALTY_AXES } from "./calibrate.js";
21
21
  export { validateDeclaration, KIND_CATALOGUE, LOCKED_KINDS, genreEnv, genreInstruments, LOCKED_CLASSES, CLASS_BATTERIES, classGuards, closeCeiling, NEVER_OFFERED_KINDS } from "./authoring.js";
package/types/job.d.ts CHANGED
@@ -122,18 +122,18 @@ export function checkApproval(job: object, approvals: unknown): boolean;
122
122
  * mismatched flag is a mistake worth stopping on, never a silent swap); the
123
123
  * same id from both is not a conflict. Extracted from scripts/run-u.mjs so
124
124
  * the decision is testable without running the script or touching a provider.
125
- * @param {{ specModel?: string, flagModel?: string, defaultModel: string }} args
125
+ * @param {{ specModel?: string, flagModel?: string, suppliedModel?: string }} args
126
126
  * `specModel` — `job.model` if the signed spec carries one, else undefined.
127
127
  * `flagModel` — the model id an explicit `--model <tier>` resolved to, else
128
128
  * undefined (a flag that was never passed, not merely a default tier name).
129
- * `defaultModel` — the runner's own default when neither spec nor flag says.
129
+ * `suppliedModel` — a model the CALLER supplies when neither spec nor flag says (bareloop keeps none of its own).
130
130
  * @returns {{ model: string, source: 'spec'|'flag'|'default' }}
131
- * @throws {Error} when the spec names a model and the flag names a different one
131
+ * @throws {Error} when the spec names a model and the flag names a different one, or when no model is named at all
132
132
  */
133
- export function resolveWorkerModel({ specModel, flagModel, defaultModel }: {
133
+ export function resolveWorkerModel({ specModel, flagModel, suppliedModel }: {
134
134
  specModel?: string;
135
135
  flagModel?: string;
136
- defaultModel: string;
136
+ suppliedModel?: string;
137
137
  }): {
138
138
  model: string;
139
139
  source: "spec" | "flag" | "default";
@@ -272,7 +272,7 @@ export const PROVIDERS: readonly string[];
272
272
  export const CONDITION_KEYS: readonly string[];
273
273
  /**
274
274
  * The fields a "Reuse workflow" run may change: where the work lands (the write fence, `writeScope`, and a
275
- * `destination`/`source` where a spec ever carries them) and the two ceilings. Everything else in a signed spec is
275
+ * `destination`/`source` where a spec ever carries them), the `inputs` the person pointed at (P7: Inputs is open on a reuse) and the two ceilings. Everything else in a signed spec is
276
276
  * the WORKFLOW — goal, checks, guardrails, close (the worker model is the third kind, {@link WORKFLOW_KEY_IDENTITY_FIELDS}). ONE list, shared by {@link workflowKey} and by the panel's
277
277
  * reuse (so what the key ignores is exactly what the person may edit, never two spellings).
278
278
  */
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Parse "The job" box. A plain line is a numbered job line (1, 2, 3...). A line starting `~` has no number and belongs
3
+ * to the numbered line above; consecutive `~` lines become ONE rule joined with `'; '`. A `~` line starting `PASS:` or
4
+ * `FAIL:` is also a judge example (kept with its line number); any other `~` line is a guardrail. Empty lines are
5
+ * ignored. A `~` before line 1 is a red.
6
+ * @param {unknown} text the box's raw text
7
+ * @returns {{ok: true, lines: JobLine[]}|{ok: false, error: string}}
8
+ */
9
+ export function parseJobBlock(text: unknown): {
10
+ ok: true;
11
+ lines: JobLine[];
12
+ } | {
13
+ ok: false;
14
+ error: string;
15
+ };
16
+ /**
17
+ * Parse the "Inputs" box: one `label: value` per line, numbered 1, 2, 3 in the order written. Empty lines are ignored.
18
+ * This only checks the SHAPE — what a value must point at (line 1 an absolute repo, lines 2+ tracked files inside it)
19
+ * is proven by the panel at $0.
20
+ * @param {unknown} text
21
+ * @returns {{ok: true, inputs: JobInput[]}|{ok: false, error: string}}
22
+ */
23
+ export function parseInputs(text: unknown): {
24
+ ok: true;
25
+ inputs: JobInput[];
26
+ } | {
27
+ ok: false;
28
+ error: string;
29
+ };
30
+ /**
31
+ * The person's job lines as the drafter reads them (answer 1 of the interview): each numbered line, then its rule
32
+ * (the `~` lines joined) indented under it. One spelling, so the number the person typed beside a line is the number a
33
+ * stage's `fromLine` names.
34
+ * @param {JobLine[]} lines
35
+ * @returns {string}
36
+ */
37
+ export function renderJobLines(lines: JobLine[]): string;
38
+ /**
39
+ * The PASS:/FAIL: examples the person wrote, each kept with its line number (answer 2 of a rubric interview — what the
40
+ * calibration compile reads). Empty string when there are none.
41
+ * @param {JobLine[]} lines
42
+ * @returns {string}
43
+ */
44
+ export function renderExamples(lines: JobLine[]): string;
45
+ /**
46
+ * The inputs as the drafter is told about them: "the person pointed at these inputs: N label path".
47
+ * @param {JobInput[]} inputs
48
+ * @returns {string}
49
+ */
50
+ export function renderInputs(inputs: JobInput[]): string;
51
+ /**
52
+ * The two cross-checks between a parsed job block and the Check type: a RUBRIC job needs at least one `PASS:` and one
53
+ * `FAIL:` example (the calibration compile reads them); a DETERMINISTIC job has no judge, so an example line would be
54
+ * silently dropped — a red instead.
55
+ * @param {JobLine[]} lines
56
+ * @param {boolean} rubric
57
+ * @returns {string|null} the plain red, or null when the block fits the Check type
58
+ */
59
+ export function jobBlockFitsCheck(lines: JobLine[], rubric: boolean): string | null;
60
+ /**
61
+ * The signed `jobLines` field from parsed lines: the person's own words, `{n, text, rule, rules}` (examples stay inside the
62
+ * rule text, where the person wrote them). `rules` is the same `~` lines as an array, BESIDE the joined `rule` (never
63
+ * replacing it); it is omitted on a line with no `~` line, and an older spec lacks it entirely.
64
+ * @param {JobLine[]} lines
65
+ * @returns {{n: number, text: string, rule: string, rules?: string[]}[]}
66
+ */
67
+ export function signedJobLines(lines: JobLine[]): {
68
+ n: number;
69
+ text: string;
70
+ rule: string;
71
+ rules?: string[];
72
+ }[];
73
+ /** @typedef {{verdict: 'PASS'|'FAIL', text: string}} JobExample a `~ PASS:` / `~ FAIL:` line (rubric only), kept with its job line */
74
+ /** @typedef {{n: number, text: string, rule: string, rules: string[], examples: JobExample[]}} JobLine one numbered job line; `rule` = its `~` lines joined with '; ' ('' when none), `rules` = those same `~` lines one by one (so an example's own '; ' survives) */
75
+ /** @typedef {{n: number, label: string, value: string}} JobInput one numbered `label: value` input line */
76
+ /** the joiner for consecutive `~` lines (fwd's joinGuardrails) */
77
+ export const RULE_JOINER: "; ";
78
+ /**
79
+ * a `~ PASS:` / `~ FAIL:` line (rubric only), kept with its job line
80
+ */
81
+ export type JobExample = {
82
+ verdict: "PASS" | "FAIL";
83
+ text: string;
84
+ };
85
+ /**
86
+ * one numbered job line; `rule` = its `~` lines joined with '; ' ('' when none), `rules` = those same `~` lines one by one (so an example's own '; ' survives)
87
+ */
88
+ export type JobLine = {
89
+ n: number;
90
+ text: string;
91
+ rule: string;
92
+ rules: string[];
93
+ examples: JobExample[];
94
+ };
95
+ /**
96
+ * one numbered `label: value` input line
97
+ */
98
+ export type JobInput = {
99
+ n: number;
100
+ label: string;
101
+ value: string;
102
+ };
package/types/judged.d.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  * item 31.4 a green job runs on its own key alone, and this is what lets a
18
18
  * JUDGED one do the same.
19
19
  *
20
- * There is deliberately NO third fallback to `JUDGE_MODEL`. A caller that cannot
20
+ * There is deliberately NO third fallback to a library-pinned model. A caller that cannot
21
21
  * say which model will grade has not wired a judge, and the grading path stops
22
22
  * as a wiring gap rather than grading under an identity nobody chose — the same
23
23
  * answer an absent `judgeLoop` already gets. A silent default here would be the
@@ -79,7 +79,7 @@ export function resolveJudge({ specJudge, workerProvider, workerModel }?: {
79
79
  *
80
80
  * @param {{provider?: string, model?: string, judge?: any}} source draft or spec
81
81
  * @param {string} fallbackProviderName used only when `source.provider` is absent
82
- * @param {(o: {specModel?: string, flagModel?: string, defaultModel: string}) => {model: string, source: string}} resolveWorkerModelFn `resolveWorkerModel` from `./job.js`
82
+ * @param {(o: {specModel?: string, flagModel?: string, suppliedModel?: string}) => {model: string, source: string}} resolveWorkerModelFn `resolveWorkerModel` from `./job.js`
83
83
  * @returns {{provider: string, model: string}}
84
84
  */
85
85
  export function resolveJobJudge(source: {
@@ -89,7 +89,7 @@ export function resolveJobJudge(source: {
89
89
  }, fallbackProviderName: string, resolveWorkerModelFn: (o: {
90
90
  specModel?: string;
91
91
  flagModel?: string;
92
- defaultModel: string;
92
+ suppliedModel?: string;
93
93
  }) => {
94
94
  model: string;
95
95
  source: string;
@@ -309,38 +309,6 @@ export function decide(facts: any, card: any, { artifactText }?: {
309
309
  firstRed: string | null;
310
310
  reason: string | null;
311
311
  };
312
- /**
313
- * THE JUDGE'S HISTORICAL PIN. Until PRD item 32 (2026-09-10) this constant WAS
314
- * the judge: every grading path imported it, so bareloop could run a worker on
315
- * any provider and still judge only on Claude.
316
- *
317
- * It is no longer read by any grading path. The judge identity is now RESOLVED
318
- * per job (`resolveJudge` below) and travels as data — `ctx.judgeModel` into
319
- * `runJudgedFloor`, an explicit argument into the calibration gate, and
320
- * `closeDecl.calibration.judgeModel` into the signed spec, exactly as before.
321
- * Keeping the export is deliberate: it is part of the public API
322
- * (`src/index.js`) and an adopter importing it should not break on a minor.
323
- * What it means now is "the judge bareloop pinned before item 32", which is
324
- * still the right DEFAULT for an anthropic-api job and still the only tier with
325
- * established injection resistance upstream (BA-20's `INJECTION_BATTERY` is
326
- * haiku-4.5 only).
327
- *
328
- * THE ENFORCEMENT IS UNCHANGED, and still lives in three wired places because a
329
- * rule with no detector is prose (F45) — only the value being compared moved
330
- * from this constant to the resolved identity:
331
- * 1. the identity is STORED with the set it graded — `foldJudgedArtifacts`
332
- * writes it as `closeDecl.calibration.judgeModel`, inside the spec, so
333
- * `jobSpecHash` covers it by construction. Change the judge and every
334
- * signed judged spec hashes differently: the signature dies, and re-signing
335
- * is what re-runs the calibration gate.
336
- * 2. `validateCloseDecl` REQUIRES it on any stored set — a set nobody can
337
- * attribute to a judge is a floor nobody can tell apart from a moved one.
338
- * 3. `runJudgedFloor` refuses to grade when the stored identity is not the one
339
- * about to grade, naming both. It does not degrade in either direction:
340
- * grading on the new judge judges against a floor nobody calibrated, and
341
- * the old judge is not on offer.
342
- */
343
- export const JUDGE_MODEL: "claude-haiku-4-5";
344
312
  /**
345
313
  * The locate call's token ceiling. The POC's two real artifacts came back well
346
314
  * inside 2000; this doubles that for headroom on a longer artifact, and the
@@ -402,13 +370,14 @@ export const JUDGE_RULE_IDS: readonly string[];
402
370
  export const CALIBRATION_SIZE: 10;
403
371
  /** the two verdicts a case may expect — `decide()`'s own closed set */
404
372
  export const CASE_VERDICTS: readonly string[];
405
- export function defaultJudgeLoop({ provider, system, rates }: {
373
+ export function defaultJudgeLoop({ provider, system, rates, thinking }: {
406
374
  provider: any;
407
375
  system: string;
408
376
  rates?: {
409
377
  in: number;
410
378
  out: number;
411
379
  } | null;
380
+ thinking?: boolean;
412
381
  }): {
413
382
  run: (msgs: any[], tools: any[], opts?: any) => Promise<any>;
414
383
  };
@@ -25,19 +25,20 @@ export function parseKeysText(text: string): Record<string, string>;
25
25
  */
26
26
  export function filledKeyNames(home?: string): string[];
27
27
  /**
28
- * Create the keys file with the five empty preset lines when — and only when — it does not
29
- * exist (mode 600). An existing file is never touched.
30
- * @param {readonly string[]} presetNames the names to seed, in order
28
+ * Create the keys file with {@link KEYS_FILE_BODY} when — and only when — it does not
29
+ * exist (mode 600, dir 700). An existing file is never touched.
31
30
  * @param {string} [home]
32
31
  * @returns {boolean} true if it was created
33
32
  */
34
- export function ensureKeysFile(presetNames: readonly string[], home?: string): boolean;
33
+ export function ensureKeysFile(home?: string): boolean;
35
34
  /**
36
35
  * @typedef {object} KeysLoad
37
36
  * @property {boolean} exists the file is there
38
37
  * @property {string[]} names the NAMES the file defines (sorted; never values)
39
38
  * @property {string|null} warning a plain-words warning (file readable by others, or
40
39
  * unreadable), else null — a warning, never a refusal
40
+ * @property {boolean} [unreadable] true only when the file is there but could not be read (the Settings row then
41
+ * reads "refused — see above"); absent otherwise
41
42
  * @property {Record<string,string|undefined>} env shell env with the file filling the
42
43
  * names the shell does not set. THE ONLY place values leave this module.
43
44
  */
@@ -68,6 +69,11 @@ export function keysForDoor(deps: {
68
69
  env: Record<string, string | undefined>;
69
70
  home: string | undefined;
70
71
  };
72
+ /**
73
+ * The two comment lines a fresh keys file is created with — no provider name, no preset key.
74
+ * Each key the person types becomes a row in Settings > Providers.
75
+ */
76
+ export const KEYS_FILE_BODY: string;
71
77
  export type KeysLoad = {
72
78
  /**
73
79
  * the file is there
@@ -82,6 +88,11 @@ export type KeysLoad = {
82
88
  * unreadable), else null — a warning, never a refusal
83
89
  */
84
90
  warning: string | null;
91
+ /**
92
+ * true only when the file is there but could not be read (the Settings row then
93
+ * reads "refused — see above"); absent otherwise
94
+ */
95
+ unreadable?: boolean | undefined;
85
96
  /**
86
97
  * shell env with the file filling the
87
98
  * names the shell does not set. THE ONLY place values leave this module.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The key goes only in a header, in the form each shape expects.
3
+ * @param {string} shape an API shape id (src/providershapes.js)
4
+ * @param {string} key
5
+ * @returns {Record<string, string>}
6
+ */
7
+ export function authHeaders(shape: string, key: string): Record<string, string>;
8
+ /**
9
+ * The model ids a provider's list answer holds, per API shape (`data[].id` for two shapes;
10
+ * `models[].name` minus its `models/` prefix for the third). Sorted, de-duplicated.
11
+ * @param {string} shape
12
+ * @param {any} body
13
+ * @returns {string[]}
14
+ */
15
+ export function idsOf(shape: string, body: any): string[];
16
+ /**
17
+ * @typedef {{ ok: true, models: string[], ms: number }
18
+ * | { ok: false, why: string, status: string, detail?: string }} ListResult
19
+ */
20
+ /**
21
+ * One free GET of `<baseUrl>/models` with the key in the header only and a deadline.
22
+ * Failure is data, never a throw: `why` is a plain sentence, `status` a short machine word
23
+ * (`no-base-url`, `redirect`, `HTTP <n>`, `timeout`, `unreachable`) for callers that need it.
24
+ * @param {object} o
25
+ * @param {string} o.shape an API shape id
26
+ * @param {string} o.baseUrl the row's Base URL ('' = no call)
27
+ * @param {string} o.key the key value (header only)
28
+ * @param {typeof fetch} [o.fetchImpl] test seam; defaults to the global `fetch`
29
+ * @param {number} [o.timeoutMs]
30
+ * @returns {Promise<ListResult>}
31
+ */
32
+ export function listModels({ shape, baseUrl, key, fetchImpl, timeoutMs }: {
33
+ shape: string;
34
+ baseUrl: string;
35
+ key: string;
36
+ fetchImpl?: typeof fetch | undefined;
37
+ timeoutMs?: number | undefined;
38
+ }): Promise<ListResult>;
39
+ /** The default deadline for the one GET. */
40
+ export const LIST_TIMEOUT_MS: 4000;
41
+ export type ListResult = {
42
+ ok: true;
43
+ models: string[];
44
+ ms: number;
45
+ } | {
46
+ ok: false;
47
+ why: string;
48
+ status: string;
49
+ detail?: string;
50
+ };
@@ -48,11 +48,11 @@ export function monthSpend(opts?: {
48
48
  };
49
49
  /**
50
50
  * The Money tab's figures: all-time and this-month spend, and per-provider breakdown
51
- * (rows from `rowIdFor`; a run on no known row lands under its own provider name, never
52
- * pooled into another row). Any leg with unknown spend makes the figure it belongs to
51
+ * (grouped by the matched Providers row's key variable name; a run on no row lands under its
52
+ * own provider name, never pooled into another row). Any leg with unknown spend makes the figure it belongs to
53
53
  * an "at least".
54
54
  * @param {{ home?: string, sessionsRoot?: string, now?: () => number, rows?: readonly import('./providerrows.js').KeyRow[] }} [opts] `rows` = the Providers rows: when given, `tokensByRow` sums each row's tokens
55
- * @returns {{ tokensByRow: Record<string, number>, total: {usd: number, atLeast: boolean, tokens: number}, month: {usd: number, atLeast: boolean, tokens: number},
55
+ * @returns {{ tokensByRow: Record<string, number>, tokensByModel: Record<string, Record<string, number>>, total: {usd: number, atLeast: boolean, tokens: number}, month: {usd: number, atLeast: boolean, tokens: number},
56
56
  * byProvider: Record<string, {label: string, monthUsd: number, monthAtLeast: boolean, totalUsd: number, totalAtLeast: boolean, tokens: number, vouchedRounds: number, otherRounds: number, monthWallMs: number|null, monthWallAtLeast: boolean, totalWallMs: number|null, totalWallAtLeast: boolean}> }}
57
57
  */
58
58
  export function spendSummary(opts?: {
@@ -62,6 +62,7 @@ export function spendSummary(opts?: {
62
62
  rows?: readonly import("./providerrows.js").KeyRow[];
63
63
  }): {
64
64
  tokensByRow: Record<string, number>;
65
+ tokensByModel: Record<string, Record<string, number>>;
65
66
  total: {
66
67
  usd: number;
67
68
  atLeast: boolean;