bareloop 0.40.0 → 0.42.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 (77) hide show
  1. package/CHANGELOG.md +96 -1
  2. package/README.md +13 -21
  3. package/bareloop.context.md +141 -128
  4. package/package.json +5 -4
  5. package/src/authorflow.js +346 -38
  6. package/src/authoring.js +92 -7
  7. package/src/authorjob.js +82 -20
  8. package/src/authorreadout.js +89 -5
  9. package/src/authorrun.js +39 -30
  10. package/src/authorscout.js +112 -4
  11. package/src/bundle.js +7 -4
  12. package/src/bundlerun.js +4 -1
  13. package/src/cli.js +1 -1
  14. package/src/config.js +129 -29
  15. package/src/declaredclose.js +23 -12
  16. package/src/draftspend.js +4 -4
  17. package/src/facts.js +317 -0
  18. package/src/factwords.js +129 -0
  19. package/src/index.js +2 -4
  20. package/src/inputlook.js +301 -0
  21. package/src/interviewrun.js +17 -25
  22. package/src/job.js +15 -8
  23. package/src/jobblock.js +7 -2
  24. package/src/judged.js +11 -43
  25. package/src/keysfile.js +15 -7
  26. package/src/kinds.js +86 -5
  27. package/src/labelscan.js +170 -0
  28. package/src/modellist.js +84 -0
  29. package/src/monthly.js +23 -43
  30. package/src/pagefetch.js +125 -0
  31. package/src/panel/authorroutes.js +34 -16
  32. package/src/panel/authorsession.js +161 -80
  33. package/src/panel/index.html +553 -152
  34. package/src/panel/jobplan.js +34 -11
  35. package/src/panel/runroutes.js +3 -0
  36. package/src/panel/server.js +54 -18
  37. package/src/panel/settingsroutes.js +232 -173
  38. package/src/planrun.js +5 -4
  39. package/src/providerrows.js +226 -141
  40. package/src/providers.js +58 -332
  41. package/src/providershapes.js +18 -0
  42. package/src/replay.js +1 -1
  43. package/src/rubricstep.js +804 -0
  44. package/src/source.js +166 -1
  45. package/src/thinking.js +130 -0
  46. package/src/u-readout.js +1 -1
  47. package/src/userrun.js +64 -75
  48. package/types/authorflow.d.ts +272 -18
  49. package/types/authoring.d.ts +29 -1
  50. package/types/authorjob.d.ts +9 -7
  51. package/types/authorreadout.d.ts +27 -2
  52. package/types/authorscout.d.ts +72 -2
  53. package/types/config.d.ts +23 -6
  54. package/types/declaredclose.d.ts +4 -2
  55. package/types/draftspend.d.ts +8 -8
  56. package/types/facts.d.ts +71 -0
  57. package/types/factwords.d.ts +42 -0
  58. package/types/index.d.ts +1 -1
  59. package/types/inputlook.d.ts +238 -0
  60. package/types/job.d.ts +5 -5
  61. package/types/jobblock.d.ts +8 -1
  62. package/types/judged.d.ts +5 -36
  63. package/types/keysfile.d.ts +15 -4
  64. package/types/labelscan.d.ts +38 -0
  65. package/types/modellist.d.ts +50 -0
  66. package/types/monthly.d.ts +4 -3
  67. package/types/pagefetch.d.ts +71 -0
  68. package/types/panel/authorsession.d.ts +20 -11
  69. package/types/panel/jobplan.d.ts +12 -4
  70. package/types/panel/server.d.ts +18 -6
  71. package/types/providerrows.d.ts +192 -99
  72. package/types/providers.d.ts +34 -159
  73. package/types/providershapes.d.ts +10 -0
  74. package/types/rubricstep.d.ts +286 -0
  75. package/types/source.d.ts +94 -0
  76. package/types/thinking.d.ts +90 -0
  77. package/types/userrun.d.ts +17 -1
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,38 @@
1
+ /** @typedef {{id: string, label: string, count: number, example: string}} LabelEntry */
2
+ /** @typedef {Record<'money'|'number'|'date'|'time'|'text', LabelEntry[]>} LabelScan */
3
+ /** @param {string} t @returns {string} white space made one space, quote marks at the edges dropped, trimmed - how a label is compared */
4
+ export function normLabel(t: string): string;
5
+ /**
6
+ * Scan one source text for the labels that sit before values, per shape.
7
+ * @param {string} text
8
+ * @returns {LabelScan}
9
+ */
10
+ export function scanLabels(text: string): LabelScan;
11
+ /**
12
+ * Is this label on the list for this shape? Compared the way a label is read: white space of any kind is one space, quote marks at the
13
+ * edges are dropped. `idOrText` may be a list id (`t1`) or the exact words.
14
+ * @param {LabelScan} scan @param {string} shape @param {unknown} idOrText
15
+ * @returns {LabelEntry|null}
16
+ */
17
+ export function labelOnList(scan: LabelScan, shape: string, idOrText: unknown): LabelEntry | null;
18
+ /**
19
+ * The list as the look and the drafter are shown it, one block per shape that has any label.
20
+ * @param {string} inputLabel the input the list belongs to @param {LabelScan} scan
21
+ * @returns {string}
22
+ */
23
+ export function renderLabelList(inputLabel: string, scan: LabelScan): string;
24
+ /** the shapes a list is made for */
25
+ export const LABEL_SHAPES: readonly string[];
26
+ /** a label must occur at least this often to be listed (1: a one-page document with one total still has its label; the list is cut to the highest counts) */
27
+ export const MIN_COUNT: 1;
28
+ /** how many labels one shape's list holds at most (the highest counts first) */
29
+ export const MAX_PER_SHAPE: 40;
30
+ /** only this much of a source is scanned (a source over it is scanned up to a line end) */
31
+ export const SCAN_MAX_CHARS: 2000000;
32
+ export type LabelEntry = {
33
+ id: string;
34
+ label: string;
35
+ count: number;
36
+ example: string;
37
+ };
38
+ export type LabelScan = Record<"money" | "number" | "date" | "time" | "text", LabelEntry[]>;
@@ -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;
@@ -0,0 +1,71 @@
1
+ /** @param {unknown} v @returns {boolean} an http(s) address (the one spelling of "this Inputs value is a web address") */
2
+ export function isWebUrl(v: unknown): boolean;
3
+ /**
4
+ * How much of a fetched page is the page's own content: the snapshot's header lines (url, size note, hint) and every part of the tree
5
+ * under a banner / navigation / contentinfo landmark are taken out, the page reader's bracketed tags are removed, and what is left is
6
+ * counted. Generic - no site names, only the ARIA landmark roles. Text that is not a snapshot tree (a plain file) has no landmark, so it
7
+ * counts whole.
8
+ * @param {string} text @returns {number}
9
+ */
10
+ export function pageContentChars(text: string): number;
11
+ /**
12
+ * @typedef {{goto: (url: string, timeout?: number) => Promise<void>, waitForNetworkIdle: (o?: {idle?: number, timeout?: number}) => Promise<void>,
13
+ * snapshot: (o?: {mode?: string}) => Promise<string>, close: () => Promise<void>}} FetchPage the part of barebrowse's page handle the fetch uses
14
+ */
15
+ /**
16
+ * Fetch one page's text. `connect` is injected (a test never reaches the network); the default is barebrowse's own `connect`.
17
+ * @param {string} url
18
+ * @param {{connect?: (opts: Record<string, any>) => Promise<FetchPage>, timeoutMs?: number, idleMs?: number, idleTimeoutMs?: number, closeWaitMs?: number}} [o]
19
+ * @returns {Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>} `error` is plain words, without a spend line
20
+ */
21
+ export function fetchPageText(url: string, { connect, timeoutMs, idleMs, idleTimeoutMs, closeWaitMs }?: {
22
+ connect?: (opts: Record<string, any>) => Promise<FetchPage>;
23
+ timeoutMs?: number;
24
+ idleMs?: number;
25
+ idleTimeoutMs?: number;
26
+ closeWaitMs?: number;
27
+ }): Promise<{
28
+ ok: true;
29
+ text: string;
30
+ url: string;
31
+ } | {
32
+ ok: false;
33
+ error: string;
34
+ }>;
35
+ /** the session options (incognito: no auth ever enters; blockPrivateNetwork: SSRF guard); ONE frozen object so a test can pin them */
36
+ export const CONNECT_OPTIONS: Readonly<{
37
+ incognito: true;
38
+ blockPrivateNetwork: true;
39
+ }>;
40
+ /** the snapshot options step 0 proved */
41
+ export const SNAPSHOT_OPTIONS: Readonly<{
42
+ mode: "act";
43
+ }>;
44
+ /** the page's network must be quiet this long (ms) before we read it: long enough to span the gap between a page's first load and the
45
+ * script requests that fill the results, short enough not to stretch a plain page */
46
+ export const NETWORK_IDLE_MS: 1500;
47
+ /** the most we wait for the network to go quiet (ms); a page that never settles (polling, streaming) is read as it stands after this */
48
+ export const NETWORK_IDLE_TIMEOUT_MS: 20000;
49
+ /** the most we wait for the page to load at all (ms) */
50
+ export const GOTO_TIMEOUT_MS: 30000;
51
+ /** a page that comes back with fewer characters than this (after trimming) is treated as "nothing came back" */
52
+ export const MIN_PAGE_CHARS: 200;
53
+ /** the page text left after the site's own header, menus and footer are taken out must hold at least this many characters, or the page is
54
+ * "only site chrome" (L2: a 719-character fetch of Google's header and sign-in passed the 200-character floor) */
55
+ export const MIN_CONTENT_CHARS: 100;
56
+ /** our own deadline around one whole fetch (a hung browser leaves no trace; this is the only thing that fires on the absence of an answer) */
57
+ export const PAGE_FETCH_TIMEOUT_MS: 90000;
58
+ /**
59
+ * the part of barebrowse's page handle the fetch uses
60
+ */
61
+ export type FetchPage = {
62
+ goto: (url: string, timeout?: number) => Promise<void>;
63
+ waitForNetworkIdle: (o?: {
64
+ idle?: number;
65
+ timeout?: number;
66
+ }) => Promise<void>;
67
+ snapshot: (o?: {
68
+ mode?: string;
69
+ }) => Promise<string>;
70
+ close: () => Promise<void>;
71
+ };
@@ -1,11 +1,3 @@
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;
9
1
  /** @typedef {{id: string, label: string, status: 'running'|'done'|'failed', detail: string}} StepLine */
10
2
  /** @param {StepLine[]} steps @param {string} id the LATEST line carrying this id (the list is a time-ordered log; an id can repeat) */
11
3
  export function latestStep(steps: StepLine[], id: string): StepLine | undefined;
@@ -15,8 +7,9 @@ export function latestStep(steps: StepLine[], id: string): StepLine | undefined;
15
7
  * earlier line keeps its status and detail). Existing lines are never moved. Re-starting the step that is still the
16
8
  * LAST line just resumes that line.
17
9
  * @param {StepLine[]} steps @param {string} id @param {string} [detail] (absent = keep what the line has)
10
+ * @param {Readonly<Record<string, string>>} [labels] the label table (a file job passes its own)
18
11
  */
19
- export function advanceSteps(steps: StepLine[], id: string, detail?: string): void;
12
+ export function advanceSteps(steps: StepLine[], id: string, detail?: string, labels?: Readonly<Record<string, string>>): void;
20
13
  /**
21
14
  * The confirm turn's plan as a chat message (P7): a STRUCTURED `{role:'bot', kind:'plan', plan}` the page draws with the
22
15
  * same renderer the Job tab uses — each numbered job line, its `~` rule, its drafted checks folded; the AI's own "not
@@ -147,7 +140,7 @@ export function validateJobCard(card: any, opts?: {
147
140
  * @param {any} card the validated job card (see {@link validateJobCard})
148
141
  * @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
149
142
  * scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
150
- * prepareSigningFn?: Function, npmCiFn?: Function, draftThinking?: "on"|"off", reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
143
+ * prepareSigningFn?: Function, npmCiFn?: Function, lookFn?: Function|null, fetchUrl?: (url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>, draftThinking?: "on"|"off", reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
151
144
  * TEST SEAMS ONLY — see the note just above where each is read, below. `reuse` (Reuse workflow) is NOT a seam: the
152
145
  * caller (`/api/author/start`) sets it only when the server's own same-job rule held.
153
146
  * @returns {any} the session object
@@ -163,6 +156,15 @@ export function createSession(card: any, deps?: {
163
156
  authorFn?: Function;
164
157
  prepareSigningFn?: Function;
165
158
  npmCiFn?: Function;
159
+ lookFn?: Function | null;
160
+ fetchUrl?: (url: string) => Promise<{
161
+ ok: true;
162
+ text: string;
163
+ url: string;
164
+ } | {
165
+ ok: false;
166
+ error: string;
167
+ }>;
166
168
  draftThinking?: "on" | "off";
167
169
  reuse?: {
168
170
  spec: any;
@@ -176,6 +178,12 @@ export function createSession(card: any, deps?: {
176
178
  * @type {Readonly<Record<string, string>>}
177
179
  */
178
180
  export const STEP_LABELS: Readonly<Record<string, string>>;
181
+ /**
182
+ * The same steps, worded for a job on the person's own local FILES (Part 1a): there is no worktree and no repo to read.
183
+ * Code-owned, one table beside STEP_LABELS; a step with no entry keeps its STEP_LABELS word.
184
+ * @type {Readonly<Record<string, string>>}
185
+ */
186
+ export const FILE_JOB_STEP_LABELS: Readonly<Record<string, string>>;
179
187
  /**
180
188
  * The library's own `onPhase(name, ...)` calls (`src/authorjob.js`/`src/authorflow.js`) -> the step each one advances.
181
189
  * A phase with no entry leaves the current step as it was.
@@ -195,7 +203,6 @@ export const CARD_FIELDS: readonly string[];
195
203
  export const REUSE_OPEN_FIELDS: readonly string[];
196
204
  /** every card field that is NOT open on a reuse card, in card order */
197
205
  export const REUSE_LOCKED_FIELDS: readonly string[];
198
- export { nonRepoSourceMessage };
199
206
  export type StepLine = {
200
207
  id: string;
201
208
  label: string;
@@ -203,4 +210,6 @@ export type StepLine = {
203
210
  detail: string;
204
211
  };
205
212
  import { planFromConfirm } from './jobplan.js';
213
+ import { thinkingRowText } from '../thinking.js';
206
214
  import { nonRepoSourceMessage } from '../source.js';
215
+ export { thinkingRowText, nonRepoSourceMessage };
@@ -1,5 +1,5 @@
1
1
  /** @typedef {{name: string, cls: 'machine check'|'judge'|null}} PlanCheck */
2
- /** @typedef {{n: number, text: string, rule: string, checks: PlanCheck[], refused: string[]}} PlanLine */
2
+ /** @typedef {{n: number, text: string, rule: string, rules: string[], checks: PlanCheck[], refused: string[]}} PlanLine */
3
3
  /** @typedef {{lines: PlanLine[], loose: PlanCheck[], notChecked: string[], alwaysOn: string[], older: boolean, goal?: string}} JobPlan */
4
4
  /**
5
5
  * `machine check` for a kind a command decides, `judge` for one that needs judgement; `null` for a stage whose kind is
@@ -8,9 +8,15 @@
8
8
  * @returns {'machine check'|'judge'|null}
9
9
  */
10
10
  export function checkClassOfKind(kind: unknown): "machine check" | "judge" | null;
11
+ /**
12
+ * The `~` rules of one job line, each on its own (the page draws one `~` row per rule): the line's own `rules` array when it
13
+ * has one (a rule may itself contain "; "), else the joined `rule` split on "; " (an older line that saved only the join).
14
+ * @param {any} l @returns {string[]}
15
+ */
16
+ export function rulesOfLine(l: any): string[];
11
17
  /**
12
18
  * 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
19
+ * a mandatory guard: "Always checked"), `closeDecl.refused` reasons land under their line, and `closeDecl.notes` are the
14
20
  * drafter's own "not checked" words. Without `jobLines` (a spec that predates the job block) the signed goal sentence is
15
21
  * line 1 and every stage name is a check under it — `older: true`.
16
22
  * @param {any} spec
@@ -19,8 +25,8 @@ export function checkClassOfKind(kind: unknown): "machine check" | "judge" | nul
19
25
  export function planFromSpec(spec: any): JobPlan;
20
26
  /**
21
27
  * 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
28
+ * line each names, its own "not checked" words, and the real protections (code-owned) as "Always checked".
29
+ * @param {{n: number, text: string, rule: string, rules?: string[]}[]|null|undefined} jobLines
24
30
  * @param {{checkItems?: {text: string, fromLine: number[]|null}[], checks?: string[], notChecked?: string[], protections?: string[]}} plan
25
31
  * @returns {JobPlan}
26
32
  */
@@ -28,6 +34,7 @@ export function planFromConfirm(jobLines: {
28
34
  n: number;
29
35
  text: string;
30
36
  rule: string;
37
+ rules?: string[];
31
38
  }[] | null | undefined, plan: {
32
39
  checkItems?: {
33
40
  text: string;
@@ -45,6 +52,7 @@ export type PlanLine = {
45
52
  n: number;
46
53
  text: string;
47
54
  rule: string;
55
+ rules: string[];
48
56
  checks: PlanCheck[];
49
57
  refused: string[];
50
58
  };
@@ -400,15 +400,17 @@ export function reuseLine(record: {
400
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
401
401
  * to copy: change the job: Clear the card). `null` when the runid is not listed.
402
402
  * @param {string} runid
403
- * @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
404
404
  * @returns {{ok: true, origin: {runid: string, job: string}, outcome: string|null, card: Record<string, any>, from: 'signed job',
405
405
  * locked: readonly string[], open: readonly string[], note: string|null,
406
- * 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},
407
408
  * trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
408
409
  */
409
410
  export function getStartFrom(runid: string, opts?: {
410
411
  home?: string;
411
412
  model?: string;
413
+ provider?: string;
412
414
  }): {
413
415
  ok: true;
414
416
  origin: {
@@ -425,6 +427,12 @@ export function getStartFrom(runid: string, opts?: {
425
427
  workflowKey: string;
426
428
  spec: any;
427
429
  specPath: string;
430
+ specNamesModel: boolean;
431
+ specWorker: {
432
+ provider: string;
433
+ endpoint: string;
434
+ label: string;
435
+ };
428
436
  trackRecord: ReturnType<typeof trackRecordFor>;
429
437
  line: string;
430
438
  } | {
@@ -436,12 +444,13 @@ export function getStartFrom(runid: string, opts?: {
436
444
  * that has at least one GREEN local run, newest green first. `runid` = that job's newest green run, so the page hands
437
445
  * it to the existing start-from flow unchanged; `line` is `getStartFrom`'s own estimate line (never a second
438
446
  * spelling). Local runs only — an imported job keeps its Reuse button in the Import view. $0, read-only.
439
- * @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
440
448
  * @returns {{job: string, runid: string, checkType: 'deterministic'|'rubric', line: string}[]}
441
449
  */
442
450
  export function listReuseJobs(opts?: {
443
451
  home?: string;
444
452
  model?: string;
453
+ provider?: string;
445
454
  }): {
446
455
  job: string;
447
456
  runid: string;
@@ -458,7 +467,7 @@ export function listReuseJobs(opts?: {
458
467
  * signs it on this machine at [Sign & run]. Same card, same locked/open boxes as a run's reuse. `null` when no such
459
468
  * imported job.
460
469
  * @param {string} id
461
- * @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}
462
471
  * @returns {{ok: true, origin: {runid: null, job: string, importId: string}, card: Record<string, any>, from: 'imported job',
463
472
  * locked: readonly string[], open: readonly string[], note: string|null, specHash: string, workflowKey: string, spec: any,
464
473
  * specPath: null, trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
@@ -466,6 +475,7 @@ export function listReuseJobs(opts?: {
466
475
  export function getStartFromImport(id: string, opts?: {
467
476
  home?: string;
468
477
  model?: string;
478
+ provider?: string;
469
479
  }): {
470
480
  ok: true;
471
481
  origin: {
@@ -504,10 +514,11 @@ export function getStartFromImport(id: string, opts?: {
504
514
  * only `createPanelServer` below always supplies one.
505
515
  * @param {import('node:http').IncomingMessage} req
506
516
  * @param {import('node:http').ServerResponse} res
507
- * @param {{ home?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes> }} opts
517
+ * @param {{ home?: string, packageJsonPath?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes> }} opts
508
518
  */
509
519
  export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
510
520
  home?: string;
521
+ packageJsonPath?: string;
511
522
  port: number;
512
523
  token?: string;
513
524
  authorRoutes?: ReturnType<typeof createAuthorRoutes>;
@@ -534,7 +545,7 @@ export function handleRequest(req: import("node:http").IncomingMessage, res: imp
534
545
  * @param {{ port?: number, home?: string, env?: Record<string,string|undefined>,
535
546
  * sessionsRoot?: string, spawnFn?: (...a: any[]) => any, bareloopBin?: string,
536
547
  * fetchImpl?: typeof fetch, settleMs?: number, userHome?: string,
537
- * openFolderSpawn?: (cmd: string, args: string[], o: object) => any, platform?: string }} [opts]
548
+ * openFolderSpawn?: (cmd: string, args: string[], o: object) => any, platform?: string, packageJsonPath?: string }} [opts]
538
549
  * @returns {Promise<{ server: import('node:http').Server, port: number, token: string, close: () => Promise<void> }>}
539
550
  */
540
551
  export function createPanelServer(opts?: {
@@ -549,6 +560,7 @@ export function createPanelServer(opts?: {
549
560
  userHome?: string;
550
561
  openFolderSpawn?: (cmd: string, args: string[], o: object) => any;
551
562
  platform?: string;
563
+ packageJsonPath?: string;
552
564
  }): Promise<{
553
565
  server: import("node:http").Server;
554
566
  port: number;