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
@@ -10,19 +10,36 @@ export function latestStep(steps: StepLine[], id: string): StepLine | undefined;
10
10
  */
11
11
  export function advanceSteps(steps: StepLine[], id: string, detail?: string): void;
12
12
  /**
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}
13
+ * The confirm turn's plan as a chat message (P7): a STRUCTURED `{role:'bot', kind:'plan', plan}` the page draws with the
14
+ * same renderer the Job tab uses — each numbered job line, its `~` rule, its drafted checks folded; the AI's own "not
15
+ * checked"; the always-on guards. `questions` (what the plan still asks) ride inside the plan. One owner of the shape.
16
+ * @param {{goal?: string, checks?: string[], checkItems?: {text: string, fromLine: number[]|null}[], notChecked?: string[], questions?: string[], protections?: string[]}} p the confirm turn's plan
17
+ * @param {{n: number, text: string, rule: string}[]|null} jobLines
18
+ * @returns {{role: 'bot', kind: 'plan', text: string, plan: ReturnType<typeof planFromConfirm> & {goal: string, questions: string[]}}}
19
19
  */
20
- export function planText(p: {
20
+ export function planMessage(p: {
21
21
  goal?: string;
22
22
  checks?: string[];
23
+ checkItems?: {
24
+ text: string;
25
+ fromLine: number[] | null;
26
+ }[];
23
27
  notChecked?: string[];
24
28
  questions?: string[];
25
- }): string;
29
+ protections?: string[];
30
+ }, jobLines: {
31
+ n: number;
32
+ text: string;
33
+ rule: string;
34
+ }[] | null): {
35
+ role: "bot";
36
+ kind: "plan";
37
+ text: string;
38
+ plan: ReturnType<typeof planFromConfirm> & {
39
+ goal: string;
40
+ questions: string[];
41
+ };
42
+ };
26
43
  /**
27
44
  * `true` when `jobs/<name>.json` already exists in the repo's own jobs/ dir
28
45
  * (resolved from THIS module's own on-disk location, never `process.cwd()`,
@@ -36,6 +53,13 @@ export function planText(p: {
36
53
  export function jobNameTaken(name: string, opts?: {
37
54
  jobsDir?: string;
38
55
  }): boolean;
56
+ /**
57
+ * The repo a card names: Inputs line 1 (P7 — the Source box is gone; the first input IS the repo). `''` when the Inputs
58
+ * box does not parse (the card validator refuses that before anything uses this).
59
+ * @param {any} card
60
+ * @returns {string}
61
+ */
62
+ export function cardSource(card: any): string;
39
63
  /**
40
64
  * Only the form's own fields, nothing else a request body might carry (`startFrom`, stray keys).
41
65
  * @param {any} card
@@ -52,12 +76,12 @@ export function cardFields(card: any): Record<string, any>;
52
76
  export function lockedFieldChanged(card: Record<string, any>, origin: Record<string, any>): string | null;
53
77
  /**
54
78
  * 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
79
+ * `writeScope` from Destination, `inputs` from the Inputs box (P7), `budgetUsd` from the $ cap, `maxWallMs` from the Time cap (absent when blank), and the
56
80
  * worker (`provider`/`baseUrl`/`model`) from the Model Name chosen in Settings > Providers, spelled exactly as normal
57
81
  * authoring spells it (`baseUrl` only when the row has one; `model` only when the Name is not the provider's default
58
82
  * tier). A RUBRIC (soft-green) origin with no explicit `judge` first has its judge pinned to the ORIGIN's resolved judge
59
83
  * 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
84
+ * `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
85
  * hashes to a NEW `jobSpecHash` and the SAME `workflowKey` — the caller checks the second after building.
62
86
  * @param {any} originSpec
63
87
  * @param {Record<string, any>} card
@@ -67,7 +91,7 @@ export function lockedFieldChanged(card: Record<string, any>, origin: Record<str
67
91
  export function buildReuseSpec(originSpec: any, card: Record<string, any>, rows?: readonly import("../providerrows.js").KeyRow[]): any;
68
92
  /**
69
93
  * `$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,
94
+ * required here — only what the new session itself needs: Inputs, a Destination, a Model that is a Settings Name,
71
95
  * and the cap. (The spec built from the open boxes is validated by the route with `validateJob`.)
72
96
  * @param {any} card
73
97
  * @param {{rows?: readonly import('../providerrows.js').KeyRow[]}} [opts]
@@ -86,7 +110,7 @@ export function validateReuseCard(card: any, opts?: {
86
110
  * relative to the repo, so an absolute path is refused — at $0, before any copy or model call. A FOLDER source (or a
87
111
  * source that does not peek as a repo) keeps today's behaviour: its Destination is an absolute output directory.
88
112
  * @param {string} destination
89
- * @param {boolean} isRepoLike `looksLikeRepoSource(card.source)`
113
+ * @param {boolean} isRepoLike `looksLikeRepoSource(cardSource(card))`
90
114
  * @returns {string|null} the plain refusal, or null when the Destination is fine
91
115
  */
92
116
  export function repoDestinationProblem(destination: string, isRepoLike: boolean): string | null;
@@ -115,7 +139,7 @@ export function validateJobCard(card: any, opts?: {
115
139
  * @param {any} card the validated job card (see {@link validateJobCard})
116
140
  * @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
117
141
  * scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
118
- * prepareSigningFn?: Function, npmCiFn?: Function, reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
142
+ * prepareSigningFn?: Function, npmCiFn?: Function, draftThinking?: "on"|"off", reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
119
143
  * TEST SEAMS ONLY — see the note just above where each is read, below. `reuse` (Reuse workflow) is NOT a seam: the
120
144
  * caller (`/api/author/start`) sets it only when the server's own same-job rule held.
121
145
  * @returns {any} the session object
@@ -131,6 +155,7 @@ export function createSession(card: any, deps?: {
131
155
  authorFn?: Function;
132
156
  prepareSigningFn?: Function;
133
157
  npmCiFn?: Function;
158
+ draftThinking?: "on" | "off";
134
159
  reuse?: {
135
160
  spec: any;
136
161
  workflowKey: string;
@@ -149,22 +174,26 @@ export const STEP_LABELS: Readonly<Record<string, string>>;
149
174
  * @type {Readonly<Record<string, keyof typeof STEP_LABELS>>}
150
175
  */
151
176
  export const PHASE_STEP: Readonly<Record<string, keyof typeof STEP_LABELS>>;
177
+ /** the line under the plan bubble, in plain words (the bubble itself is drawn by the page's one `renderJobPlan`) */
178
+ export const PLAN_FOOTER: "Sign & run to confirm this plan, or Revise to edit the boxes and draft again.";
152
179
  /** the form's fields, in card order — what `card.json` stores, verbatim (P5 item 3) */
153
180
  export const CARD_FIELDS: readonly string[];
154
181
  /**
155
182
  * 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
183
+ * FIVE open boxes — Inputs, Destination, Model, $ cap, Time cap — and every other box is the signed workflow, shown greyed and never
157
184
  * editable. The page greys them; THIS is what refuses (the server never trusts the page). Changing any locked box
158
185
  * is a different job: Clear the card, which drafts.
159
186
  */
160
187
  export const REUSE_OPEN_FIELDS: readonly string[];
161
188
  /** every card field that is NOT open on a reuse card, in card order */
162
189
  export const REUSE_LOCKED_FIELDS: readonly string[];
163
- export { nonRepoSourceMessage };
164
190
  export type StepLine = {
165
191
  id: string;
166
192
  label: string;
167
193
  status: "running" | "done" | "failed";
168
194
  detail: string;
169
195
  };
196
+ import { planFromConfirm } from './jobplan.js';
197
+ import { thinkingRowText } from '../thinking.js';
170
198
  import { nonRepoSourceMessage } from '../source.js';
199
+ export { thinkingRowText, nonRepoSourceMessage };
@@ -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:
@@ -380,15 +400,17 @@ export function reuseLine(record: {
380
400
  * $ cap and the Time cap — start at what the run had. `ok:false` when the run's signed job is not on disk (nothing
381
401
  * to copy: change the job: Clear the card). `null` when the runid is not listed.
382
402
  * @param {string} runid
383
- * @param {{ home?: string, model?: string }} [opts] `model` = the Model box's current Name (the estimate counts only runs on that worker); absent = the card's own Model
403
+ * @param {{ home?: string, model?: string, provider?: string }} [opts] `model` = the Model box's current Name (the estimate counts only runs on that worker); absent = the card's own Model
384
404
  * @returns {{ok: true, origin: {runid: string, job: string}, outcome: string|null, card: Record<string, any>, from: 'signed job',
385
405
  * locked: readonly string[], open: readonly string[], note: string|null,
386
- * specHash: string, workflowKey: string, spec: any, specPath: string,
406
+ * specHash: string, workflowKey: string, spec: any, specPath: string, specNamesModel: boolean,
407
+ * specWorker: {provider: string, endpoint: string, label: string},
387
408
  * trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
388
409
  */
389
410
  export function getStartFrom(runid: string, opts?: {
390
411
  home?: string;
391
412
  model?: string;
413
+ provider?: string;
392
414
  }): {
393
415
  ok: true;
394
416
  origin: {
@@ -405,6 +427,12 @@ export function getStartFrom(runid: string, opts?: {
405
427
  workflowKey: string;
406
428
  spec: any;
407
429
  specPath: string;
430
+ specNamesModel: boolean;
431
+ specWorker: {
432
+ provider: string;
433
+ endpoint: string;
434
+ label: string;
435
+ };
408
436
  trackRecord: ReturnType<typeof trackRecordFor>;
409
437
  line: string;
410
438
  } | {
@@ -416,12 +444,13 @@ export function getStartFrom(runid: string, opts?: {
416
444
  * that has at least one GREEN local run, newest green first. `runid` = that job's newest green run, so the page hands
417
445
  * it to the existing start-from flow unchanged; `line` is `getStartFrom`'s own estimate line (never a second
418
446
  * spelling). Local runs only — an imported job keeps its Reuse button in the Import view. $0, read-only.
419
- * @param {{ home?: string, model?: string }} [opts] `model` passes through to the estimate line, as start-from's does
447
+ * @param {{ home?: string, model?: string, provider?: string }} [opts] `model` passes through to the estimate line, as start-from's does
420
448
  * @returns {{job: string, runid: string, checkType: 'deterministic'|'rubric', line: string}[]}
421
449
  */
422
450
  export function listReuseJobs(opts?: {
423
451
  home?: string;
424
452
  model?: string;
453
+ provider?: string;
425
454
  }): {
426
455
  job: string;
427
456
  runid: string;
@@ -438,7 +467,7 @@ export function listReuseJobs(opts?: {
438
467
  * signs it on this machine at [Sign & run]. Same card, same locked/open boxes as a run's reuse. `null` when no such
439
468
  * imported job.
440
469
  * @param {string} id
441
- * @param {{ home?: string, model?: string }} [opts] `model` as for {@link getStartFrom}
470
+ * @param {{ home?: string, model?: string, provider?: string }} [opts] `model` as for {@link getStartFrom}
442
471
  * @returns {{ok: true, origin: {runid: null, job: string, importId: string}, card: Record<string, any>, from: 'imported job',
443
472
  * locked: readonly string[], open: readonly string[], note: string|null, specHash: string, workflowKey: string, spec: any,
444
473
  * specPath: null, trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
@@ -446,6 +475,7 @@ export function listReuseJobs(opts?: {
446
475
  export function getStartFromImport(id: string, opts?: {
447
476
  home?: string;
448
477
  model?: string;
478
+ provider?: string;
449
479
  }): {
450
480
  ok: true;
451
481
  origin: {
@@ -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