bareloop 0.41.0 → 0.43.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 (75) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +13 -21
  3. package/bareloop.context.md +97 -34
  4. package/package.json +4 -3
  5. package/src/authorflow.js +337 -29
  6. package/src/authoring.js +92 -7
  7. package/src/authorjob.js +84 -16
  8. package/src/authorreadout.js +89 -5
  9. package/src/authorrun.js +14 -5
  10. package/src/authorscout.js +108 -0
  11. package/src/declaredclose.js +25 -14
  12. package/src/facts.js +317 -0
  13. package/src/factwords.js +129 -0
  14. package/src/inputlook.js +301 -0
  15. package/src/interviewrun.js +0 -1
  16. package/src/job.js +40 -12
  17. package/src/jobblock.js +7 -2
  18. package/src/kinds.js +84 -3
  19. package/src/labelscan.js +170 -0
  20. package/src/pagefetch.js +125 -0
  21. package/src/panel/authorroutes.js +121 -34
  22. package/src/panel/authorsession.js +154 -45
  23. package/src/panel/index.html +845 -490
  24. package/src/panel/jobplan.js +34 -11
  25. package/src/panel/runroutes.js +163 -5
  26. package/src/panel/scheduler.js +156 -0
  27. package/src/panel/schedules.js +667 -0
  28. package/src/panel/schedulestart.js +243 -0
  29. package/src/panel/server.js +467 -48
  30. package/src/planrun.js +42 -9
  31. package/src/providerrows.js +3 -3
  32. package/src/reviewdoor.js +37 -0
  33. package/src/rubricstep.js +804 -0
  34. package/src/runlist.js +35 -0
  35. package/src/schedulewords.js +296 -0
  36. package/src/source.js +166 -1
  37. package/src/trigger.js +48 -0
  38. package/src/triggerclock.js +127 -0
  39. package/src/triggerfiles.js +215 -0
  40. package/src/triggerledger.js +79 -0
  41. package/src/u-readout.js +1 -1
  42. package/src/userrun.js +20 -9
  43. package/types/authorflow.d.ts +267 -13
  44. package/types/authoring.d.ts +29 -1
  45. package/types/authorjob.d.ts +7 -5
  46. package/types/authorreadout.d.ts +27 -2
  47. package/types/authorscout.d.ts +70 -0
  48. package/types/declaredclose.d.ts +5 -3
  49. package/types/facts.d.ts +71 -0
  50. package/types/factwords.d.ts +42 -0
  51. package/types/inputlook.d.ts +238 -0
  52. package/types/job.d.ts +8 -1
  53. package/types/jobblock.d.ts +8 -1
  54. package/types/labelscan.d.ts +38 -0
  55. package/types/pagefetch.d.ts +71 -0
  56. package/types/panel/authorroutes.d.ts +34 -1
  57. package/types/panel/authorsession.d.ts +55 -14
  58. package/types/panel/jobplan.d.ts +12 -4
  59. package/types/panel/runroutes.d.ts +32 -1
  60. package/types/panel/scheduler.d.ts +17 -0
  61. package/types/panel/schedules.d.ts +412 -0
  62. package/types/panel/schedulestart.d.ts +106 -0
  63. package/types/panel/server.d.ts +145 -14
  64. package/types/planrun.d.ts +2 -0
  65. package/types/providerrows.d.ts +2 -2
  66. package/types/reviewdoor.d.ts +27 -0
  67. package/types/rubricstep.d.ts +286 -0
  68. package/types/runlist.d.ts +20 -0
  69. package/types/schedulewords.d.ts +135 -0
  70. package/types/source.d.ts +94 -0
  71. package/types/trigger.d.ts +37 -0
  72. package/types/triggerclock.d.ts +52 -0
  73. package/types/triggerfiles.d.ts +93 -0
  74. package/types/triggerledger.d.ts +35 -0
  75. package/types/userrun.d.ts +11 -0
@@ -53,7 +53,8 @@ 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, jobLines?: {n: number}[]|null}} [opts]
56
+ * catalogue?: Record<string, any>, verdictType?: string|null, writeScope?: string[]|null, jobLines?: {n: number}[]|null,
57
+ * inputLabels?: string[]|null}} [opts]
57
58
  * @returns {{ok: boolean, reds: Red[], closeDecl: any, grounded: boolean,
58
59
  * scoped: {scoped: boolean, via: string|null},
59
60
  * ceiling: {class: string, kind: string, stage: string|null, locked: boolean}|null}}
@@ -68,6 +69,7 @@ export function validateCloseDecl(closeDecl: any, opts?: {
68
69
  jobLines?: {
69
70
  n: number;
70
71
  }[] | null;
72
+ inputLabels?: string[] | null;
71
73
  }): {
72
74
  ok: boolean;
73
75
  reds: Red[];
@@ -162,7 +164,7 @@ export function closeGrade(v: any): {
162
164
  /** @typedef {{code: string, path: string, detail: string, [k: string]: any}} Red */
163
165
  /** The genres a `closeDecl` may name. v1 admits exactly one (D13) — a genre
164
166
  * classifier is designed the day there is a second genre to classify. */
165
- export const DECLARED_GENRES: readonly "TYPES"[];
167
+ export const DECLARED_GENRES: readonly string[];
166
168
  /**
167
169
  * The exact field set of a `closeDecl` — anything else is a smuggle channel, the
168
170
  * same rule `STAGE_FIELDS` enforces on a command stage.
@@ -185,7 +187,7 @@ export const CLOSE_DECL_FIELDS: readonly string[];
185
187
  /** what a stored calibration set carries, ENUMERATED: the frozen cases and the judge
186
188
  * tier that graded them. Both are inside the spec, so `jobSpecHash` covers both — which
187
189
  * is the whole mechanism behind "a judge-model bump forces a full recalibration"
188
- * (src/judged.js, `JUDGE_MODEL`): the field moves, the hash moves, the signature dies,
190
+ * (src/judged.js, `resolveJudge`): the field moves, the hash moves, the signature dies,
189
191
  * and the re-sign re-runs the gate. Anything else in there is a field nobody reads. */
190
192
  export const CALIBRATION_FIELDS: readonly string[];
191
193
  /**
@@ -0,0 +1,71 @@
1
+ /** the `fact` part of a step's parameters, or null (a step has at most one) @param {any} params @returns {any|null} */
2
+ export function factPartOf(params: any): any | null;
3
+ /**
4
+ * The SHAPE of a `fact` part (what it may carry), pushed as reds in the same form `rubricStepShapeReds` uses. Whether the
5
+ * bareguard rubric it builds is signable is asked of bareguard itself (`checkRubricParts` builds it once at drafting).
6
+ * @param {any} part @param {string} w the path prefix @param {(code: string, path: string, detail: string) => void} red
7
+ */
8
+ export function factShapeReds(part: any, w: string, red: (code: string, path: string, detail: string) => void): void;
9
+ /**
10
+ * bareguard's `decides` list for a fact part: one decide per fact, in the fact order (so `d<j>` is the fact's own index).
11
+ * @param {any} part @returns {Record<string, any>[]}
12
+ */
13
+ export function factDecides(part: any): Record<string, any>[];
14
+ /**
15
+ * The claims bareguard reads for a fact step, built from the step file's `results`. The claim TEXT is written here, by code,
16
+ * from the values the worker gave (the worker writes no claim text): `name: value; name: value`. A phrase fact contributes its
17
+ * own signed words. A result that is not an object, or has no string quote, still becomes a claim — an empty one — so
18
+ * bareguard reds it by name rather than the result vanishing.
19
+ * @param {any} part @param {unknown} results @returns {{claim: string, quote: string, values?: Record<string, string>}[]}
20
+ */
21
+ export function factClaims(part: any, results: unknown): {
22
+ claim: string;
23
+ quote: string;
24
+ values?: Record<string, string>;
25
+ }[];
26
+ /**
27
+ * One fact as a plain row, written by code from the signed fields:
28
+ * `fact · price — money, at most 450 US dollars, after "Price", quoted from "Flights"`.
29
+ * @param {any} f @param {string} source
30
+ * @returns {string}
31
+ */
32
+ export function factSentence(f: any, source: string): string;
33
+ /**
34
+ * One fact as the WORKER is told it (item 7): what to write for it and where in the quote it sits, in plain words, from the signed
35
+ * fields. A phrase fact has no value to write — the quote has to say it.
36
+ * @param {any} f @returns {string}
37
+ */
38
+ export function factBrief(f: any): string;
39
+ /** the "N results" row of a fact part, or null @param {any} part @returns {string|null} */
40
+ export function countSentence(part: any): string | null;
41
+ /**
42
+ * Every plan-card row a fact part carries: its facts, then its count.
43
+ * @param {any} part @returns {string[]}
44
+ */
45
+ export function factRows(part: any): string[];
46
+ /**
47
+ * One bareguard `cited` item (`#<result>:d<j>:<reason>[:<detail>]`, or `#<result>:<reason>`) as a sentence the worker can act
48
+ * on. The names and numbers are bareguard's measurements and the person's signed facts, never inferred here.
49
+ * @param {string} item @param {any} part @returns {string}
50
+ */
51
+ export function plainFactItem(item: string, part: any): string;
52
+ /**
53
+ * The fields each fact shape takes, in the fact part's own names (`rule` = bareguard's op/value/lo/hi; `unit`/`currency`; `phrase`
54
+ * = the presence decide's phrases), read off bareguard's rubric vocabulary.
55
+ * @returns {Record<string, string[]>} shape -> the fields it takes besides name and shape
56
+ */
57
+ export function factShapeFields(): Record<string, string[]>;
58
+ /**
59
+ * The lines the drafter is shown about which shape takes which fields, generated from {@link factShapeFields} (shapes with the same
60
+ * fields are listed together). One owner: the composer brief and the confirm note both print this.
61
+ * @returns {string}
62
+ */
63
+ export function factShapeGuide(): string;
64
+ /** the shapes a fact may have (bareguard's value kinds, plus `phrase` = a presence decide) */
65
+ export const FACT_SHAPES: readonly string[];
66
+ /** the comparison ops a rule may use (bareguard's) */
67
+ export const FACT_OPS: readonly string[];
68
+ /** bareguard signs at most this many decides on one `cited` check */
69
+ export const MAX_FACTS: 10;
70
+ /** every field a part of class `fact` may carry (the shared ones are checked by rubricStepShapeReds) */
71
+ export const FACT_PART_FIELDS: readonly string[];
@@ -0,0 +1,42 @@
1
+ /**
2
+ * How many times exact words occur in a source, white space blind. Zero for empty words.
3
+ * @param {string} source the raw source text @param {string} words
4
+ * @returns {number}
5
+ */
6
+ export function countExact(source: string, words: string): number;
7
+ /**
8
+ * How many times a phrase or unit occurs as WHOLE words, case and spacing aside ("SFO" is not found inside "sfocus").
9
+ * @param {string} source @param {string} words @returns {number}
10
+ */
11
+ export function countPhrase(source: string, words: string): number;
12
+ /**
13
+ * The reds for ONE fact part against its frozen source text.
14
+ * @param {{part: any, text: string, line: number|string, scan?: import('./labelscan.js').LabelScan}} o
15
+ * @returns {{code: string, detail: string}[]}
16
+ */
17
+ export function checkFactWords({ part, text, line, scan }: {
18
+ part: any;
19
+ text: string;
20
+ line: number | string;
21
+ scan?: import("./labelscan.js").LabelScan;
22
+ }): {
23
+ code: string;
24
+ detail: string;
25
+ }[];
26
+ /**
27
+ * The reds for every fact part of a declaration. A fact part whose input text is not at hand is a red: a check whose words were never
28
+ * proved against the source is never signed.
29
+ * @param {any} declaration @param {Record<string, string>|null} texts label -> frozen text
30
+ * @returns {{code: string, path: string, detail: string}[]}
31
+ */
32
+ export function checkDeclarationWords(declaration: any, texts: Record<string, string> | null): {
33
+ code: string;
34
+ path: string;
35
+ detail: string;
36
+ }[];
37
+ /**
38
+ * The look's own proof hook: how often a phrase the look built is in an input.
39
+ * @param {Record<string, string>} texts @returns {(source: string, phrase: string) => number}
40
+ */
41
+ export function proverOf(texts: Record<string, string>): (source: string, phrase: string) => number;
42
+ export function squash(t: string): string;
@@ -0,0 +1,238 @@
1
+ /**
2
+ * The input as the AI sees it: lines with any run of white space (U+202F included) made one space and the page reader's bracketed tags
3
+ * (`[ref=549]`) removed - an id that is only the page reader's, never part of a label.
4
+ * @param {string} text @returns {string[]}
5
+ */
6
+ export function lookLines(text: string): string[];
7
+ /**
8
+ * The two read-only tools: search the input text, read a slice around a hit. They are closures over in-memory text and touch nothing
9
+ * else - read-only BY CONSTRUCTION, not by a fence. Several inputs: the `input` argument names which (its label).
10
+ * @param {Record<string, string>} texts label -> frozen text
11
+ * @returns {{name: string, description: string, parameters: any, execute: (a: any) => Promise<any>}[]}
12
+ */
13
+ export function lookReadTools(texts: Record<string, string>): {
14
+ name: string;
15
+ description: string;
16
+ parameters: any;
17
+ execute: (a: any) => Promise<any>;
18
+ }[];
19
+ /** the strict answer form (a JSON schema - never free prose with JSON inside) */
20
+ export function lookReportSchema(): {
21
+ type: string;
22
+ additionalProperties: boolean;
23
+ required: string[];
24
+ properties: {
25
+ facts: {
26
+ type: string;
27
+ items: {
28
+ type: string;
29
+ additionalProperties: boolean;
30
+ required: string[];
31
+ properties: {
32
+ line: {
33
+ type: string;
34
+ minimum: number;
35
+ description: string;
36
+ };
37
+ name: {
38
+ type: string;
39
+ minLength: number;
40
+ description: string;
41
+ };
42
+ shape: {
43
+ enum: string[];
44
+ };
45
+ source: {
46
+ type: string;
47
+ minLength: number;
48
+ description: string;
49
+ };
50
+ label: {
51
+ type: string;
52
+ description: string;
53
+ };
54
+ value: {
55
+ type: string;
56
+ description: string;
57
+ };
58
+ example: {
59
+ type: string;
60
+ description: string;
61
+ };
62
+ rule: {
63
+ type: string;
64
+ additionalProperties: boolean;
65
+ required: string[];
66
+ properties: {
67
+ op: {
68
+ enum: string[];
69
+ };
70
+ value: {};
71
+ lo: {};
72
+ hi: {};
73
+ };
74
+ description: string;
75
+ };
76
+ aiProposal: {
77
+ type: string;
78
+ description: string;
79
+ };
80
+ forRule: {
81
+ type: string;
82
+ minimum: number;
83
+ description: string;
84
+ };
85
+ };
86
+ };
87
+ };
88
+ prose: {
89
+ type: string;
90
+ description: string;
91
+ items: {
92
+ type: string;
93
+ additionalProperties: boolean;
94
+ required: string[];
95
+ properties: {
96
+ line: {
97
+ type: string;
98
+ minimum: number;
99
+ };
100
+ text: {
101
+ type: string;
102
+ };
103
+ };
104
+ };
105
+ };
106
+ notFound: {
107
+ type: string;
108
+ description: string;
109
+ items: {
110
+ type: string;
111
+ additionalProperties: boolean;
112
+ required: string[];
113
+ properties: {
114
+ line: {
115
+ type: string;
116
+ minimum: number;
117
+ };
118
+ rule: {
119
+ type: string;
120
+ minimum: number;
121
+ description: string;
122
+ };
123
+ words: {
124
+ type: string;
125
+ description: string;
126
+ };
127
+ why: {
128
+ type: string;
129
+ minLength: number;
130
+ };
131
+ };
132
+ };
133
+ };
134
+ };
135
+ };
136
+ /**
137
+ * What the AI is told: the numbered job, the inputs and their label lists, and what to do.
138
+ * @param {{jobLines: {n: number, text: string, rule?: string, rules?: string[]}[], labels: string[], lists: Record<string, string>}} o
139
+ * @returns {string}
140
+ */
141
+ export function lookPrompt({ jobLines, labels, lists }: {
142
+ jobLines: {
143
+ n: number;
144
+ text: string;
145
+ rule?: string;
146
+ rules?: string[];
147
+ }[];
148
+ labels: string[];
149
+ lists: Record<string, string>;
150
+ }): string;
151
+ /**
152
+ * Turn the AI's raw report into a VERIFIED one, by code, at $0. A fact is carried only when: its shape is a real shape, its source is
153
+ * a real input, and its label is ON THE LIST for that input and shape (an id is resolved to its exact words). Anything else is moved
154
+ * to `notFound` with a plain reason naming the word - never dropped silently. `prove` is the "found in the source" test of the
155
+ * phrase a phrase fact builds (label + value), supplied by the caller.
156
+ * @param {unknown} raw
157
+ * @param {{scans: Record<string, import('./labelscan.js').LabelScan>, jobLines: {n: number}[], prove?: (source: string, phrase: string) => number}} o
158
+ * @returns {LookReport}
159
+ */
160
+ export function verifyLookReport(raw: unknown, { scans, jobLines, prove }: {
161
+ scans: Record<string, import("./labelscan.js").LabelScan>;
162
+ jobLines: {
163
+ n: number;
164
+ }[];
165
+ prove?: (source: string, phrase: string) => number;
166
+ }): LookReport;
167
+ /**
168
+ * The look's report as the DRAFTER reads it: one block, per job line, of what can be checked (with the exact words to copy) and what
169
+ * could not be found. Written by code from the verified report.
170
+ * @param {LookReport} report @returns {string}
171
+ */
172
+ export function renderLookReport(report: LookReport): string;
173
+ /**
174
+ * The label scans and the rendered lists for the inputs the look will read.
175
+ * @param {Record<string, string>} texts
176
+ * @returns {{scans: Record<string, import('./labelscan.js').LabelScan>, lists: Record<string, string>}}
177
+ */
178
+ export function labelListsOf(texts: Record<string, string>): {
179
+ scans: Record<string, import("./labelscan.js").LabelScan>;
180
+ lists: Record<string, string>;
181
+ };
182
+ /**
183
+ * L3: a reason the AI gave, cut only at a sentence end (never mid-sentence): whole sentences while they fit in `max` characters,
184
+ * and nothing at all if the first one alone does not fit. A trailing full stop is dropped (the caller closes the clause).
185
+ * @param {string} text @param {number} max @returns {string}
186
+ */
187
+ export function wholeSentences(text: string, max: number): string;
188
+ /**
189
+ * I1 (hamr, 2026-10-13, "Ruled by hamr" 2): when the look shows the person's intent on one `~` rule was NOT captured - its words or
190
+ * meaning are not in the source - the person is asked about THAT rule at once, named by its line and rule. Only a look entry that names
191
+ * a rule number counts; anything else the look could not find is not asked piecemeal (it is Not checked, or the whole job is reviewed).
192
+ * The wording is the code's own, never the model's. The keep-or-drop and the cap are `normalizeQuestions` (src/authorflow.js).
193
+ * @param {LookNotFound[]} notFound @param {{n: number, text?: string, rule?: string, rules?: string[]}[]} jobLines
194
+ * @returns {{line: number, question: string}[]} every candidate question (the caller applies the machine's caps)
195
+ */
196
+ export function ruleQuestions(notFound: LookNotFound[], jobLines: {
197
+ n: number;
198
+ text?: string;
199
+ rule?: string;
200
+ rules?: string[];
201
+ }[]): {
202
+ line: number;
203
+ question: string;
204
+ }[];
205
+ /** the name of the answer tool - the one place the AI's report enters */
206
+ export const LOOK_REPORT_TOOL: "report_look";
207
+ /** how many hits one search returns at most, and how long a snippet or a read line is */
208
+ export const LOOK_MAX_HITS: 40;
209
+ export { LABEL_SHAPES };
210
+ export type LookFact = {
211
+ line: number;
212
+ name: string;
213
+ shape: string;
214
+ source: string;
215
+ label?: string;
216
+ labelId?: string;
217
+ value?: string;
218
+ phrase?: string;
219
+ example: string;
220
+ rule?: any;
221
+ aiProposal?: boolean;
222
+ forRule?: number;
223
+ };
224
+ export type LookNotFound = {
225
+ line: number;
226
+ rule?: number;
227
+ words?: string;
228
+ why: string;
229
+ };
230
+ export type LookReport = {
231
+ facts: LookFact[];
232
+ prose: {
233
+ line: number;
234
+ text?: string;
235
+ }[];
236
+ notFound: LookNotFound[];
237
+ };
238
+ import { LABEL_SHAPES } from './labelscan.js';
package/types/job.d.ts CHANGED
@@ -99,7 +99,8 @@ export function jobSpecHash(job: object): string;
99
99
  * share a workflow key iff they ran the same goal, checks, guardrails and close — whatever source they ran on, wherever they wrote, whatever caps they had.
100
100
  * It is NOT a signature: {@link jobSpecHash} (which covers the caps and the fence) stays what the person signs, and
101
101
  * a reuse lands on a NEW jobSpecHash with the SAME workflowKey. Same never-throws contract as `jobSpecHash`, and
102
- * the same resolved-`tools` form, so an omitted-`tools` spec and its spelled-out menu share a key.
102
+ * the same resolved-`tools` form, so an omitted-`tools` spec and its spelled-out menu share a key. A soft-green spec keys on its
103
+ * RESOLVED judge identity (an absent `judge` is its worker's), so pinning the default judge never changes the key.
103
104
  * @param {object} job
104
105
  * @returns {string} sha256 hex
105
106
  */
@@ -277,6 +278,12 @@ export const CONDITION_KEYS: readonly string[];
277
278
  * reuse (so what the key ignores is exactly what the person may edit, never two spellings).
278
279
  */
279
280
  export const REUSE_OPEN_SPEC_FIELDS: readonly string[];
281
+ /**
282
+ * The signed spec's schedule fields: {@link workflowKey} ignores them too, so a scheduled spec keeps the workflow
283
+ * key of the green run it was scheduled from (else Reuse would refuse every scheduled spec). They stay inside
284
+ * {@link jobSpecHash}. Kept apart from {@link REUSE_OPEN_SPEC_FIELDS}, which names the Reuse boxes.
285
+ */
286
+ export const SCHEDULE_SPEC_FIELDS: readonly string[];
280
287
  /**
281
288
  * The worker-identity fields {@link workflowKey} ALSO ignores (hamr 2026-10-04: Model is open on a reuse; the Name
282
289
  * chosen in Settings carries its provider and baseUrl with it). They stay inside {@link jobSpecHash}, so what the person
@@ -52,11 +52,18 @@ export function renderInputs(inputs: JobInput[]): string;
52
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
53
  * `FAIL:` example (the calibration compile reads them); a DETERMINISTIC job has no judge, so an example line would be
54
54
  * silently dropped — a red instead.
55
+ * PART 1a (hamr's ruling P1=B, docs/product/ITEM33-BUILD.md "Addendum 2026-10-12"): the examples are required of a rubric
56
+ * job ONLY when it holds a judged part. A rubric job on the person's own local files holds none in this build (its
57
+ * parts are counted or checked against a quote), so the caller says `judged: false` and a block without examples fits.
58
+ * Examples written anyway are never a red there — they simply have no judge to read them yet.
55
59
  * @param {JobLine[]} lines
56
60
  * @param {boolean} rubric
61
+ * @param {{judged?: boolean}} [o] `judged` (default true): does this job hold a judged part, so that it needs examples?
57
62
  * @returns {string|null} the plain red, or null when the block fits the Check type
58
63
  */
59
- export function jobBlockFitsCheck(lines: JobLine[], rubric: boolean): string | null;
64
+ export function jobBlockFitsCheck(lines: JobLine[], rubric: boolean, { judged }?: {
65
+ judged?: boolean;
66
+ }): string | null;
60
67
  /**
61
68
  * The signed `jobLines` field from parsed lines: the person's own words, `{n, text, rule, rules}` (examples stay inside the
62
69
  * rule text, where the person wrote them). `rules` is the same `~` lines as an array, BESIDE the joined `rule` (never
@@ -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,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
+ };
@@ -41,6 +41,9 @@ export function checkHumanGuard(req: import("node:http").IncomingMessage, { toke
41
41
  * @param {{ port: number, token: string, env?: Record<string,string|undefined>,
42
42
  * sessionsRoot?: string, spawnFn?: typeof realSpawn, bareloopBin?: string,
43
43
  * jobsDir?: string, fetchImpl?: typeof fetch, home?: string,
44
+ * schedules?: ReturnType<typeof import('./schedules.js').createScheduleDoor>,
45
+ * sessionDeps?: Record<string, any>,
46
+ * runJobNames?: () => string[],
44
47
  * startFrom?: {get: (runid: string, o?: {model?: string}) => any, getImport: (id: string, o?: {model?: string}) => any, listJobs?: (o?: {model?: string}) => any[]} }} opts
45
48
  */
46
49
  export function createAuthorRoutes(opts: {
@@ -53,6 +56,9 @@ export function createAuthorRoutes(opts: {
53
56
  jobsDir?: string;
54
57
  fetchImpl?: typeof fetch;
55
58
  home?: string;
59
+ schedules?: ReturnType<typeof import("./schedules.js").createScheduleDoor>;
60
+ sessionDeps?: Record<string, any>;
61
+ runJobNames?: () => string[];
56
62
  startFrom?: {
57
63
  get: (runid: string, o?: {
58
64
  model?: string;
@@ -67,6 +73,26 @@ export function createAuthorRoutes(opts: {
67
73
  }): {
68
74
  handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
69
75
  sessions: Map<string, any>;
76
+ headless: Map<string, any>;
77
+ startSession: (card: any, reuse: {
78
+ spec: any;
79
+ workflowKey: string;
80
+ } | null, inputs: import("../jobblock.js").JobInput[], o?: {
81
+ headless?: boolean;
82
+ filesJob?: boolean;
83
+ }) => Promise<{
84
+ ok: true;
85
+ session: ReturnType<typeof createSession>;
86
+ } | {
87
+ ok: false;
88
+ status: number;
89
+ error: string;
90
+ }>;
91
+ envNow: () => Record<string, string | undefined>;
92
+ spawnFn: typeof realSpawn;
93
+ bareloopBin: string;
94
+ /** a headless session the starter has removed from disk leaves its map too @param {string} id */
95
+ forgetHeadless: (id: string) => void;
70
96
  };
71
97
  /**
72
98
  * The panel's OWN 2-decimal money render for a chat line — hamr's ruling
@@ -89,7 +115,7 @@ export function panelMoney2(n: number): string;
89
115
  * it to sign"), and this is where it is checked, not trusted.
90
116
  * @param {ReturnType<typeof createSession>} session
91
117
  * @param {string} claimedHash
92
- * @param {{env: any, spawnFn: typeof realSpawn, bareloopBin: string, sessionsRoot?: string, home?: string}} o
118
+ * @param {{env: any, spawnFn: typeof realSpawn, bareloopBin: string, sessionsRoot?: string, home?: string, reviewDoor?: boolean, onSpawnError?: (e: Error) => void, onExit?: (code: number|null) => void}} o
93
119
  * @returns {{ok: boolean, error?: string, job?: string}}
94
120
  */
95
121
  export function signRun(session: ReturnType<typeof createSession>, claimedHash: string, o: {
@@ -98,10 +124,17 @@ export function signRun(session: ReturnType<typeof createSession>, claimedHash:
98
124
  bareloopBin: string;
99
125
  sessionsRoot?: string;
100
126
  home?: string;
127
+ reviewDoor?: boolean;
128
+ onSpawnError?: (e: Error) => void;
129
+ onExit?: (code: number | null) => void;
101
130
  }): {
102
131
  ok: boolean;
103
132
  error?: string;
104
133
  job?: string;
105
134
  };
135
+ /** plain words for a run whose saved job no longer passes today's `validateJob` (RULED 2026-10-15): never the raw red codes */
136
+ export const OLD_JOB_REUSE_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be reused. Start a new job instead.";
137
+ export const OLD_JOB_RESUME_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be resumed. Use Reuse workflow on a newer run, or start a new job.";
138
+ export const OLD_JOB_ACCEPT_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be accepted. Use Reuse workflow on a newer run, or start a new job.";
106
139
  import { spawn as realSpawn } from 'node:child_process';
107
140
  import { createSession } from './authorsession.js';