bareloop 0.39.0 → 0.40.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 (51) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bareloop.context.md +63 -11
  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 +5 -2
  9. package/src/authorscout.js +9 -6
  10. package/src/bridges.js +1 -1
  11. package/src/calibrate.js +1 -1
  12. package/src/cardauthor.js +4 -1
  13. package/src/declaredclose.js +4 -3
  14. package/src/draftspend.js +6 -1
  15. package/src/interviewrun.js +56 -27
  16. package/src/job.js +38 -5
  17. package/src/jobblock.js +140 -0
  18. package/src/judged.js +1 -1
  19. package/src/ledger.js +1 -1
  20. package/src/panel/authorroutes.js +15 -5
  21. package/src/panel/authorsession.js +158 -47
  22. package/src/panel/index.html +179 -70
  23. package/src/panel/jobplan.js +107 -0
  24. package/src/panel/server.js +67 -13
  25. package/src/planrun.js +4 -4
  26. package/src/promptregisters.js +1 -1
  27. package/src/providers.js +101 -19
  28. package/src/reuse.js +1 -1
  29. package/src/root.js +1 -1
  30. package/src/run.js +3 -3
  31. package/src/selection.js +1 -1
  32. package/src/source.js +64 -1
  33. package/src/text.js +13 -0
  34. package/src/transport.js +1 -1
  35. package/src/userrun.js +3 -3
  36. package/types/authorflow.d.ts +258 -39
  37. package/types/authoring.d.ts +7 -1
  38. package/types/authorjob.d.ts +35 -3
  39. package/types/authorreadout.d.ts +17 -1
  40. package/types/authorscout.d.ts +5 -3
  41. package/types/declaredclose.d.ts +4 -1
  42. package/types/draftspend.d.ts +8 -2
  43. package/types/job.d.ts +1 -1
  44. package/types/jobblock.d.ts +102 -0
  45. package/types/panel/authorsession.d.ts +50 -14
  46. package/types/panel/jobplan.d.ts +58 -0
  47. package/types/panel/server.d.ts +20 -0
  48. package/types/planrun.d.ts +1 -1
  49. package/types/providers.d.ts +47 -3
  50. package/types/run.d.ts +1 -1
  51. package/types/source.d.ts +26 -0
@@ -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, thinkingApplied?: boolean|null}) => 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
+ thinkingApplied?: boolean | null;
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, thinkingApplied?: boolean|null}) => 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
+ thinkingApplied?: boolean | null;
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[];
@@ -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")
6
+ * - `call` {no, step, label, model, thinking, thinkingApplied?, costUsd (null stays null), unpricedRounds, at}: one METERED model call
7
+ * (`thinking` = what was asked and sent: off | on | not-switchable | unverified; `thinkingApplied` = bare-agent's own verdict for the
8
+ * call's run (true | false | null), 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")
112
+ * - `call` {no, step, label, model, thinking, thinkingApplied?, costUsd (null stays null), unpricedRounds, at}: one METERED model call
113
+ * (`thinking` = what was asked and sent: off | on | not-switchable | unverified; `thinkingApplied` = bare-agent's own verdict for the
114
+ * call's run (true | false | null), 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/job.d.ts CHANGED
@@ -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
+ };
@@ -1,3 +1,11 @@
1
+ /**
2
+ * The first drafting row's words (hamr 2026-10-11) — code-owned, one owner, ALWAYS starting "thinking on" or "thinking off".
3
+ * `sent` is what the drafting calls carry (`draftThinkingFor`); `thoughtAnyway` = a call reported `thinkingApplied === false`
4
+ * after 'off' was sent (bare-agent saw reasoning in the reply).
5
+ * @param {'on'|'off'|'not-switchable'|'unverified'} sent @param {boolean} [thoughtAnyway]
6
+ * @returns {string}
7
+ */
8
+ export function thinkingRowText(sent: "on" | "off" | "not-switchable" | "unverified", thoughtAnyway?: boolean): string;
1
9
  /** @typedef {{id: string, label: string, status: 'running'|'done'|'failed', detail: string}} StepLine */
2
10
  /** @param {StepLine[]} steps @param {string} id the LATEST line carrying this id (the list is a time-ordered log; an id can repeat) */
3
11
  export function latestStep(steps: StepLine[], id: string): StepLine | undefined;
@@ -10,19 +18,36 @@ export function latestStep(steps: StepLine[], id: string): StepLine | undefined;
10
18
  */
11
19
  export function advanceSteps(steps: StepLine[], id: string, detail?: string): void;
12
20
  /**
13
- * The confirm turn's plan as the chat shows it: ONE owner of the text (hamr, 2026-10-09: the one-blob "Plan: ...
14
- * Checks: ... Not checked: ..." was unreadable). Each section is a heading on its own line, its content on the next,
15
- * and a blank line between sections. `#NOT CHECKED:` and `#QUESTIONS:` appear only when there is something to say.
16
- * The CLI composes its own plan screen (src/authorrun.js) and shares no text with this one.
17
- * @param {{goal?: string, checks?: string[], notChecked?: string[], questions?: string[]}} p
18
- * @returns {string}
21
+ * The confirm turn's plan as a chat message (P7): a STRUCTURED `{role:'bot', kind:'plan', plan}` the page draws with the
22
+ * same renderer the Job tab uses — each numbered job line, its `~` rule, its drafted checks folded; the AI's own "not
23
+ * checked"; the always-on guards. `questions` (what the plan still asks) ride inside the plan. One owner of the shape.
24
+ * @param {{goal?: string, checks?: string[], checkItems?: {text: string, fromLine: number[]|null}[], notChecked?: string[], questions?: string[], protections?: string[]}} p the confirm turn's plan
25
+ * @param {{n: number, text: string, rule: string}[]|null} jobLines
26
+ * @returns {{role: 'bot', kind: 'plan', text: string, plan: ReturnType<typeof planFromConfirm> & {goal: string, questions: string[]}}}
19
27
  */
20
- export function planText(p: {
28
+ export function planMessage(p: {
21
29
  goal?: string;
22
30
  checks?: string[];
31
+ checkItems?: {
32
+ text: string;
33
+ fromLine: number[] | null;
34
+ }[];
23
35
  notChecked?: string[];
24
36
  questions?: string[];
25
- }): string;
37
+ protections?: string[];
38
+ }, jobLines: {
39
+ n: number;
40
+ text: string;
41
+ rule: string;
42
+ }[] | null): {
43
+ role: "bot";
44
+ kind: "plan";
45
+ text: string;
46
+ plan: ReturnType<typeof planFromConfirm> & {
47
+ goal: string;
48
+ questions: string[];
49
+ };
50
+ };
26
51
  /**
27
52
  * `true` when `jobs/<name>.json` already exists in the repo's own jobs/ dir
28
53
  * (resolved from THIS module's own on-disk location, never `process.cwd()`,
@@ -36,6 +61,13 @@ export function planText(p: {
36
61
  export function jobNameTaken(name: string, opts?: {
37
62
  jobsDir?: string;
38
63
  }): boolean;
64
+ /**
65
+ * The repo a card names: Inputs line 1 (P7 — the Source box is gone; the first input IS the repo). `''` when the Inputs
66
+ * box does not parse (the card validator refuses that before anything uses this).
67
+ * @param {any} card
68
+ * @returns {string}
69
+ */
70
+ export function cardSource(card: any): string;
39
71
  /**
40
72
  * Only the form's own fields, nothing else a request body might carry (`startFrom`, stray keys).
41
73
  * @param {any} card
@@ -52,12 +84,12 @@ export function cardFields(card: any): Record<string, any>;
52
84
  export function lockedFieldChanged(card: Record<string, any>, origin: Record<string, any>): string | null;
53
85
  /**
54
86
  * The spec a reuse signs: a COPY of the origin's signed spec with ONLY the open fields set from the card —
55
- * `writeScope` from Destination, `budgetUsd` from the $ cap, `maxWallMs` from the Time cap (absent when blank), and the
87
+ * `writeScope` from Destination, `inputs` from the Inputs box (P7), `budgetUsd` from the $ cap, `maxWallMs` from the Time cap (absent when blank), and the
56
88
  * worker (`provider`/`baseUrl`/`model`) from the Model Name chosen in Settings > Providers, spelled exactly as normal
57
89
  * authoring spells it (`baseUrl` only when the row has one; `model` only when the Name is not the provider's default
58
90
  * tier). A RUBRIC (soft-green) origin with no explicit `judge` first has its judge pinned to the ORIGIN's resolved judge
59
91
  * identity — the judge must not change because the worker did (`resolveJobJudge`, the one spelling); an explicit
60
- * `judge` is kept; a deterministic job has no judge to move. Source is not in a spec (it lives beside the run). The copy
92
+ * `judge` is kept; a deterministic job has no judge to move. The repo (Inputs line 1) is in a spec only as `inputs`. The copy
61
93
  * hashes to a NEW `jobSpecHash` and the SAME `workflowKey` — the caller checks the second after building.
62
94
  * @param {any} originSpec
63
95
  * @param {Record<string, any>} card
@@ -67,7 +99,7 @@ export function lockedFieldChanged(card: Record<string, any>, origin: Record<str
67
99
  export function buildReuseSpec(originSpec: any, card: Record<string, any>, rows?: readonly import("../providerrows.js").KeyRow[]): any;
68
100
  /**
69
101
  * `$0` validation for a REUSE start: the signed job carries the goal, checks and guardrails, so those boxes are not
70
- * required here — only what the new session itself needs: a Source, a Destination, a Model that is a Settings Name,
102
+ * required here — only what the new session itself needs: Inputs, a Destination, a Model that is a Settings Name,
71
103
  * and the cap. (The spec built from the open boxes is validated by the route with `validateJob`.)
72
104
  * @param {any} card
73
105
  * @param {{rows?: readonly import('../providerrows.js').KeyRow[]}} [opts]
@@ -86,7 +118,7 @@ export function validateReuseCard(card: any, opts?: {
86
118
  * relative to the repo, so an absolute path is refused — at $0, before any copy or model call. A FOLDER source (or a
87
119
  * source that does not peek as a repo) keeps today's behaviour: its Destination is an absolute output directory.
88
120
  * @param {string} destination
89
- * @param {boolean} isRepoLike `looksLikeRepoSource(card.source)`
121
+ * @param {boolean} isRepoLike `looksLikeRepoSource(cardSource(card))`
90
122
  * @returns {string|null} the plain refusal, or null when the Destination is fine
91
123
  */
92
124
  export function repoDestinationProblem(destination: string, isRepoLike: boolean): string | null;
@@ -115,7 +147,7 @@ export function validateJobCard(card: any, opts?: {
115
147
  * @param {any} card the validated job card (see {@link validateJobCard})
116
148
  * @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
117
149
  * scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
118
- * prepareSigningFn?: Function, npmCiFn?: Function, reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
150
+ * prepareSigningFn?: Function, npmCiFn?: Function, draftThinking?: "on"|"off", reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
119
151
  * TEST SEAMS ONLY — see the note just above where each is read, below. `reuse` (Reuse workflow) is NOT a seam: the
120
152
  * caller (`/api/author/start`) sets it only when the server's own same-job rule held.
121
153
  * @returns {any} the session object
@@ -131,6 +163,7 @@ export function createSession(card: any, deps?: {
131
163
  authorFn?: Function;
132
164
  prepareSigningFn?: Function;
133
165
  npmCiFn?: Function;
166
+ draftThinking?: "on" | "off";
134
167
  reuse?: {
135
168
  spec: any;
136
169
  workflowKey: string;
@@ -149,11 +182,13 @@ export const STEP_LABELS: Readonly<Record<string, string>>;
149
182
  * @type {Readonly<Record<string, keyof typeof STEP_LABELS>>}
150
183
  */
151
184
  export const PHASE_STEP: Readonly<Record<string, keyof typeof STEP_LABELS>>;
185
+ /** the line under the plan bubble, in plain words (the bubble itself is drawn by the page's one `renderJobPlan`) */
186
+ export const PLAN_FOOTER: "Sign & run to confirm this plan, or Revise to edit the boxes and draft again.";
152
187
  /** the form's fields, in card order — what `card.json` stores, verbatim (P5 item 3) */
153
188
  export const CARD_FIELDS: readonly string[];
154
189
  /**
155
190
  * REUSE WORKFLOW (hamr 2026-10-03, replaces P5 item 3's same-job rule; Model opened 2026-10-04): a reuse card has exactly
156
- * FIVE open boxes — Source, Destination, Model, $ cap, Time cap — and every other box is the signed workflow, shown greyed and never
191
+ * FIVE open boxes — Inputs, Destination, Model, $ cap, Time cap — and every other box is the signed workflow, shown greyed and never
157
192
  * editable. The page greys them; THIS is what refuses (the server never trusts the page). Changing any locked box
158
193
  * is a different job: Clear the card, which drafts.
159
194
  */
@@ -167,4 +202,5 @@ export type StepLine = {
167
202
  status: "running" | "done" | "failed";
168
203
  detail: string;
169
204
  };
205
+ import { planFromConfirm } from './jobplan.js';
170
206
  import { nonRepoSourceMessage } from '../source.js';
@@ -0,0 +1,58 @@
1
+ /** @typedef {{name: string, cls: 'machine check'|'judge'|null}} PlanCheck */
2
+ /** @typedef {{n: number, text: string, rule: string, checks: PlanCheck[], refused: string[]}} PlanLine */
3
+ /** @typedef {{lines: PlanLine[], loose: PlanCheck[], notChecked: string[], alwaysOn: string[], older: boolean, goal?: string}} JobPlan */
4
+ /**
5
+ * `machine check` for a kind a command decides, `judge` for one that needs judgement; `null` for a stage whose kind is
6
+ * unknown (a hand-written command close has no kind: it is a command, so a machine check).
7
+ * @param {unknown} kind
8
+ * @returns {'machine check'|'judge'|null}
9
+ */
10
+ export function checkClassOfKind(kind: unknown): "machine check" | "judge" | null;
11
+ /**
12
+ * The plan of a SIGNED spec. With `jobLines`, each stage lands under EACH line its `fromLine` array names (a stage with none is
13
+ * a mandatory guard: "Always on"), `closeDecl.refused` reasons land under their line, and `closeDecl.notes` are the
14
+ * drafter's own "not checked" words. Without `jobLines` (a spec that predates the job block) the signed goal sentence is
15
+ * line 1 and every stage name is a check under it — `older: true`.
16
+ * @param {any} spec
17
+ * @returns {JobPlan}
18
+ */
19
+ export function planFromSpec(spec: any): JobPlan;
20
+ /**
21
+ * The plan of the confirm turn, before anything is drafted: the person's own job lines, the model's checks under the
22
+ * line each names, its own "not checked" words, and the real protections (code-owned) as "Always on".
23
+ * @param {{n: number, text: string, rule: string}[]|null|undefined} jobLines
24
+ * @param {{checkItems?: {text: string, fromLine: number[]|null}[], checks?: string[], notChecked?: string[], protections?: string[]}} plan
25
+ * @returns {JobPlan}
26
+ */
27
+ export function planFromConfirm(jobLines: {
28
+ n: number;
29
+ text: string;
30
+ rule: string;
31
+ }[] | null | undefined, plan: {
32
+ checkItems?: {
33
+ text: string;
34
+ fromLine: number[] | null;
35
+ }[];
36
+ checks?: string[];
37
+ notChecked?: string[];
38
+ protections?: string[];
39
+ }): JobPlan;
40
+ export type PlanCheck = {
41
+ name: string;
42
+ cls: "machine check" | "judge" | null;
43
+ };
44
+ export type PlanLine = {
45
+ n: number;
46
+ text: string;
47
+ rule: string;
48
+ checks: PlanCheck[];
49
+ refused: string[];
50
+ };
51
+ export type JobPlan = {
52
+ lines: PlanLine[];
53
+ loose: PlanCheck[];
54
+ notChecked: string[];
55
+ alwaysOn: string[];
56
+ older: boolean;
57
+ goal?: string;
58
+ };
@@ -298,6 +298,26 @@ export function getRunRounds(runid: string, query?: {
298
298
  }, opts?: {
299
299
  home?: string;
300
300
  }): any | null;
301
+ /**
302
+ * The Signed row of the Job tab: `YYYY-MM-DD HH:MM · <first 6 of specHash>`, the time being the run's FIRST `job-start`
303
+ * in local time (hamr, P7). `null` when either part is not on record — never a made-up stamp.
304
+ * @param {string|null|undefined} ts an ISO stamp @param {string|null|undefined} hash
305
+ * @returns {string|null}
306
+ */
307
+ export function signedLine(ts: string | null | undefined, hash: string | null | undefined): string | null;
308
+ /**
309
+ * The Job box's text for a signed spec: the person's own numbered lines (a `~` line per entry of `rules`, or — for a spec
310
+ * signed before `rules` existed — per '; '-split piece of `rule`), or — for
311
+ * an older job that saved no job lines — the signed goal sentence as the one line.
312
+ * @param {any} spec @returns {string}
313
+ */
314
+ export function jobTextFromSpec(spec: any): string;
315
+ /**
316
+ * The Inputs box's text for a signed spec: its numbered inputs, or — for an older job — `repo: <source>` from the
317
+ * caller's best knowledge of the source (or empty).
318
+ * @param {any} spec @param {string} source @returns {string}
319
+ */
320
+ export function inputsTextFromSpec(spec: any, source: string): string;
301
321
  /**
302
322
  * `GET /api/runs/:runid/job` — the Job tab, filled from the first source
303
323
  * that actually resolves (item 2, 2026-09-25), in order:
@@ -221,7 +221,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
221
221
  * item 27/M3 Part B), forwarded to every close invocation as
222
222
  * `BARELOOP_CLOSE_DIR`
223
223
  * @param {number} [opts.maxStepRounds] the shell's per-step rounds ceiling (validatePlan's bound)
224
- * @param {boolean} [opts.scout=true] operator-only switch (docs/product/SCOUT-CONTRAST.md):
224
+ * @param {boolean} [opts.scout=true] operator-only switch (docs/logs/SCOUT-CONTRAST.md):
225
225
  * `false` on a FRESH run (no `resumeSeed`) skips the survey entirely — `scoutBlob`
226
226
  * stays `''`, no `scout-start`/`scout-result`/`scout-empty` is emitted, and a single
227
227
  * `scout-skipped {reason:'operator-off'}` records it (a RECORD, never silence — the
@@ -1,10 +1,47 @@
1
1
  /**
2
- * The per-call options the rubric JUDGE's model calls carry for this provider
3
- * instance, from the allow-list above. Empty for any provider/model not listed.
2
+ * The per-call options the rubric JUDGE's model calls carry for this provider instance: `{ thinkingMode: 'off' }` when
3
+ * bare-agent measures the model switchable, else `{}` (F192: judge runs thinking off; a judge on a model that cannot turn it
4
+ * off must not throw).
4
5
  * @param {any} provider a constructed bare-agent provider
5
6
  * @returns {Record<string, any>}
6
7
  */
7
8
  export function judgeCallOptions(provider: any): Record<string, any>;
9
+ /**
10
+ * Throw unless `mode` is one of {@link DRAFT_THINKING_MODES} — an operator-param guard, so a typo never silently means a mode.
11
+ * @param {unknown} mode
12
+ * @returns {'on'|'off'}
13
+ */
14
+ export function assertDraftThinking(mode: unknown): "on" | "off";
15
+ /**
16
+ * What the drafting calls on this provider ACTUALLY carry for thinking, for the draft log and the panel row: `'on'` (mode on,
17
+ * nothing sent), `'off'` (mode off and the switch is sent), `'not-switchable'` (mode off, model measured not switchable —
18
+ * nothing sent), `'unverified'` (mode off, model not measured by bare-agent — nothing sent). Never re-derives a model list.
19
+ * @param {any} provider a constructed bare-agent provider
20
+ * @param {unknown} [mode] 'on' | 'off' (default 'off')
21
+ * @returns {'on'|'off'|'not-switchable'|'unverified'}
22
+ */
23
+ export function draftThinkingSent(provider: any, mode?: unknown): "on" | "off" | "not-switchable" | "unverified";
24
+ /**
25
+ * {@link draftThinkingSent} for a model the caller has only NAMED (provider shape, model id, baseUrl) — no key, no network. For
26
+ * the panel's first drafting row, which is shown before the key is read. Constructing the provider only makes the class and
27
+ * the model id available to `thinkingSupport`; it sends nothing.
28
+ * @param {{providerName: string, model: string, baseUrl?: string|null}} o
29
+ * @param {unknown} [mode] 'on' | 'off' (default 'off')
30
+ * @returns {'on'|'off'|'not-switchable'|'unverified'}
31
+ */
32
+ export function draftThinkingFor({ providerName, model, baseUrl }: {
33
+ providerName: string;
34
+ model: string;
35
+ baseUrl?: string | null;
36
+ }, mode?: unknown): "on" | "off" | "not-switchable" | "unverified";
37
+ /**
38
+ * The ONE owner of the per-call options every DRAFTING model call carries (scout, confirm, author, revise, structure retries).
39
+ * `{ thinkingMode: 'off' }` exactly when {@link draftThinkingSent} says `'off'`, else `{}`.
40
+ * @param {any} provider a constructed bare-agent provider
41
+ * @param {unknown} [mode] 'on' | 'off' (default 'off')
42
+ * @returns {Record<string, any>}
43
+ */
44
+ export function draftCallOptions(provider: any, mode?: unknown): Record<string, any>;
8
45
  /**
9
46
  * Look up a provider's table entry. Throws a NAMED error on an unrecognized
10
47
  * provider — never a silent default (the `resolveRates`-style-substring-
@@ -18,7 +55,7 @@ export function resolveProvider(providerName: string): ProviderTableEntry;
18
55
  /**
19
56
  * Report whether a resolved API key VALUE (not its presence — every call
20
57
  * site already presence-checks separately) carries a shape an HTTP header
21
- * value cannot: F181 (`docs/logs/FINDINGS.md`) — a two-line secret-store
58
+ * value cannot: F181 (`docs/wiki/FINDINGS.md`) — a two-line secret-store
22
59
  * entry put the key plus a trailing metadata line into `OPENAI_API_KEY`
23
60
  * (`"<key>\nmeta"`), the presence-only check at every door passed, and Node
24
61
  * crashed `TypeError [ERR_INVALID_CHAR]` inside its own header-encode at the
@@ -217,6 +254,13 @@ export const PROBE_STATUS: Readonly<Record<string, {
217
254
  probed: boolean;
218
255
  evidence: string;
219
256
  }>>;
257
+ /**
258
+ * The drafter's THINKING knob — an enumerated OPERATOR param, never model output and never read from env inside the library.
259
+ * `'on'` sends nothing (the model's own thinking, byte-identical to a no-knob call); `'off'` sends `thinkingMode: 'off'` where
260
+ * bare-agent measures the model switchable. The DEFAULT is `'off'` (hamr 2026-10-11, decided by the frozen ON/OFF contrast,
261
+ * docs/logs/2026-10-10-drafter-thinking-contrast-prereg.md); `'on'` stays selectable for a later Settings checkbox.
262
+ */
263
+ export const DRAFT_THINKING_MODES: readonly string[];
220
264
  export type ProviderTableEntry = {
221
265
  /**
222
266
  * the bare-agent provider class
package/types/run.d.ts CHANGED
@@ -158,7 +158,7 @@
158
158
  * frozen A0 baseline arm has to be exactly today, so a guard firing under a disabled
159
159
  * shim would make the baseline a treatment arm. The default-flip belongs to a
160
160
  * paid contrast nobody has approved yet (the `layerRoot` precedent, F41).
161
- * @param {boolean} [opts.scout=true] operator-only switch (docs/product/SCOUT-CONTRAST.md),
161
+ * @param {boolean} [opts.scout=true] operator-only switch (docs/logs/SCOUT-CONTRAST.md),
162
162
  * forwarded verbatim to the plan flow: `false` on a fresh run (no `resumeSeed`) skips the
163
163
  * read-only survey and records `scout-skipped {reason:'operator-off'}` instead — the
164
164
  * planner drafts from `(no scout notes)`. Runner territory exactly like `readShim`: the