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
@@ -7,8 +7,9 @@ export function latestStep(steps: StepLine[], id: string): StepLine | undefined;
7
7
  * earlier line keeps its status and detail). Existing lines are never moved. Re-starting the step that is still the
8
8
  * LAST line just resumes that line.
9
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)
10
11
  */
11
- 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;
12
13
  /**
13
14
  * The confirm turn's plan as a chat message (P7): a STRUCTURED `{role:'bot', kind:'plan', plan}` the page draws with the
14
15
  * same renderer the Job tab uses — each numbered job line, its `~` rule, its drafted checks folded; the AI's own "not
@@ -53,6 +54,18 @@ export function planMessage(p: {
53
54
  export function jobNameTaken(name: string, opts?: {
54
55
  jobsDir?: string;
55
56
  }): boolean;
57
+ /**
58
+ * The next free name for a NEW workflow made from `origin` (RULED 2026-10-15, v1.101): `<base>-N`, N the next free number from 2.
59
+ * Base: when the origin ends in `-<digits>` AND the part before it is itself a job (in the run list or jobs/), that part (so `x-2`
60
+ * suggests `x-3`, never `x-2-2`); else the origin's own name. Free = not a run-list job and not {@link jobNameTaken}.
61
+ * @param {string} origin
62
+ * @param {Iterable<string>} runJobs job names in the panel's run list
63
+ * @param {{jobsDir?: string}} [opts] test seam for {@link jobNameTaken}
64
+ * @returns {string}
65
+ */
66
+ export function nextFreeJobName(origin: string, runJobs: Iterable<string>, opts?: {
67
+ jobsDir?: string;
68
+ }): string;
56
69
  /**
57
70
  * The repo a card names: Inputs line 1 (P7 — the Source box is gone; the first input IS the repo). `''` when the Inputs
58
71
  * box does not parse (the card validator refuses that before anything uses this).
@@ -67,13 +80,13 @@ export function cardSource(card: any): string;
67
80
  */
68
81
  export function cardFields(card: any): Record<string, any>;
69
82
  /**
70
- * The first LOCKED box on `card` that differs from what the signed workflow prefilled (`origin`), or `null` when
71
- * every locked box is untouched. Blank and absent read alike; whitespace is not compared.
83
+ * Did any JOB box on `card` change from what the origin run prefilled (`origin`)? Whitespace is not compared (a one-line input drops
84
+ * a text's newlines); an absent thinking tick reads as off.
72
85
  * @param {Record<string, any>} card the form as it was submitted
73
86
  * @param {Record<string, any>} origin the form as the server prefilled it
74
- * @returns {string|null} the offending field's label
87
+ * @returns {boolean}
75
88
  */
76
- export function lockedFieldChanged(card: Record<string, any>, origin: Record<string, any>): string | null;
89
+ export function jobBoxesChanged(card: Record<string, any>, origin: Record<string, any>): boolean;
77
90
  /**
78
91
  * The spec a reuse signs: a COPY of the origin's signed spec with ONLY the open fields set from the card —
79
92
  * `writeScope` from Destination, `inputs` from the Inputs box (P7), `budgetUsd` from the $ cap, `maxWallMs` from the Time cap (absent when blank), and the
@@ -105,6 +118,18 @@ export function validateReuseCard(card: any, opts?: {
105
118
  ok: false;
106
119
  error: string;
107
120
  };
121
+ /**
122
+ * THE ONE Destination check of a session start, at $0, before any session, copy or model call. The session's own pipeline, the HTTP
123
+ * start (`startSession`), the schedule's two clicks and a scheduled fire's `wouldAccept` all call THIS (never a copy of it), so a start
124
+ * that would be refused for its Destination is refused before anything is made.
125
+ * @param {{inputs?: unknown, destination?: unknown, checkType?: unknown}} card the Inputs text (line 1 = the source), the Destination and the check type
126
+ * @returns {string|null} the plain refusal, or null when the Destination is fine
127
+ */
128
+ export function startDestinationProblem(card: {
129
+ inputs?: unknown;
130
+ destination?: unknown;
131
+ checkType?: unknown;
132
+ }): string | null;
108
133
  /**
109
134
  * The ONE Destination rule for a REPO source (fresh card and Reuse card alike): there Destination is the write fence,
110
135
  * relative to the repo, so an absolute path is refused — at $0, before any copy or model call. A FOLDER source (or a
@@ -119,11 +144,12 @@ export function repoDestinationProblem(destination: string, isRepoLike: boolean)
119
144
  * creates a session or spends anything — never inside the async pipeline,
120
145
  * so a bad card refuses synchronously, in the same HTTP response.
121
146
  * @param {any} card
122
- * @param {{jobsDir?: string, rows?: readonly import('../providerrows.js').KeyRow[]}} [opts] `rows` = the Settings rows (the Model menu); jobsDir is a server-side test seam — NEVER read off the client card
147
+ * @param {{jobsDir?: string, allowName?: string, rows?: readonly import('../providerrows.js').KeyRow[]}} [opts] `rows` = the Settings rows (the Model menu); jobsDir is a server-side test seam — NEVER read off the client card; `allowName` = the origin job's own name on an unchanged red Reuse (it is the same job, not a collision)
123
148
  * @returns {{ok: true}|{ok: false, error: string}}
124
149
  */
125
150
  export function validateJobCard(card: any, opts?: {
126
151
  jobsDir?: string;
152
+ allowName?: string;
127
153
  rows?: readonly import("../providerrows.js").KeyRow[];
128
154
  }): {
129
155
  ok: true;
@@ -139,7 +165,7 @@ export function validateJobCard(card: any, opts?: {
139
165
  * @param {any} card the validated job card (see {@link validateJobCard})
140
166
  * @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
141
167
  * scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
142
- * prepareSigningFn?: Function, npmCiFn?: Function, draftThinking?: "on"|"off", reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
168
+ * 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
143
169
  * TEST SEAMS ONLY — see the note just above where each is read, below. `reuse` (Reuse workflow) is NOT a seam: the
144
170
  * caller (`/api/author/start`) sets it only when the server's own same-job rule held.
145
171
  * @returns {any} the session object
@@ -155,6 +181,15 @@ export function createSession(card: any, deps?: {
155
181
  authorFn?: Function;
156
182
  prepareSigningFn?: Function;
157
183
  npmCiFn?: Function;
184
+ lookFn?: Function | null;
185
+ fetchUrl?: (url: string) => Promise<{
186
+ ok: true;
187
+ text: string;
188
+ url: string;
189
+ } | {
190
+ ok: false;
191
+ error: string;
192
+ }>;
158
193
  draftThinking?: "on" | "off";
159
194
  reuse?: {
160
195
  spec: any;
@@ -168,6 +203,12 @@ export function createSession(card: any, deps?: {
168
203
  * @type {Readonly<Record<string, string>>}
169
204
  */
170
205
  export const STEP_LABELS: Readonly<Record<string, string>>;
206
+ /**
207
+ * 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.
208
+ * Code-owned, one table beside STEP_LABELS; a step with no entry keeps its STEP_LABELS word.
209
+ * @type {Readonly<Record<string, string>>}
210
+ */
211
+ export const FILE_JOB_STEP_LABELS: Readonly<Record<string, string>>;
171
212
  /**
172
213
  * The library's own `onPhase(name, ...)` calls (`src/authorjob.js`/`src/authorflow.js`) -> the step each one advances.
173
214
  * A phase with no entry leaves the current step as it was.
@@ -179,14 +220,14 @@ export const PLAN_FOOTER: "Sign & run to confirm this plan, or Revise to edit th
179
220
  /** the form's fields, in card order — what `card.json` stores, verbatim (P5 item 3) */
180
221
  export const CARD_FIELDS: readonly string[];
181
222
  /**
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
183
- * FIVE open boxes — Inputs, Destination, Model, $ cap, Time cap — and every other box is the signed workflow, shown greyed and never
184
- * editable. The page greys them; THIS is what refuses (the server never trusts the page). Changing any locked box
185
- * is a different job: Clear the card, which drafts.
223
+ * REUSE WORKFLOW, ONE DOOR (RULED 2026-10-15, v1.101; replaces v1.88's five open boxes). Every box on the card is open. What decides
224
+ * the path is the CARD compared with the origin run's card, never the drafted spec: the JOB boxes are the job text (numbered lines,
225
+ * `~` rules, `~ PASS`/`~ FAIL`), the check type and the thinking tick. The RUN boxes (Inputs, Destination, Model, $ cap, Time cap)
226
+ * never decide it. A green origin with no job box changed reuses the signed plan verbatim (no drafting); anything else drafts.
186
227
  */
187
- export const REUSE_OPEN_FIELDS: readonly string[];
188
- /** every card field that is NOT open on a reuse card, in card order */
189
- export const REUSE_LOCKED_FIELDS: readonly string[];
228
+ export const JOB_BOXES: readonly string[];
229
+ /** the refusal for a plain-files rubric job whose Destination is not an absolute folder (ends with the words the session start shows) */
230
+ export const FILES_DESTINATION_WORDS: "Destination must be the full path of a folder, like /home/me/out \u2014 the finished files are delivered there. Nothing spent.";
190
231
  export type StepLine = {
191
232
  id: string;
192
233
  label: string;
@@ -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
  };
@@ -1,3 +1,15 @@
1
+ /**
2
+ * RULED 2026-10-16: the person never sees raw engine output. Of the engine's log tail, only a line it
3
+ * owns as a plain sentence passes through (a `Nothing spent.` / `Nothing was spent.` refusal, `DOOR REFUSED (...) — nothing was
4
+ * recorded...`, `CHECKPOINT EXPIRED — ...`); anything else (validator reds, exit statuses, stack traces, argv) becomes ONE fixed
5
+ * sentence naming the log. The one owner for the Resume and Accept routes.
6
+ * @param {string} tail the log tail
7
+ * @param {'resume'|'accept'} kind
8
+ * @param {string} logFile
9
+ * @param {(stage: string) => string|null} [questionOf] stage name to the panel's plain question (`null` = none)
10
+ * @returns {string}
11
+ */
12
+ export function engineRefusalWords(tail: string, kind: "resume" | "accept", logFile: string, questionOf?: (stage: string) => string | null): string;
1
13
  /**
2
14
  * The run routes' handler. `getResumeContext(runid)` is the server's own reader (the same
3
15
  * `resumePlanFor` the Ended block uses, so the page never offers what this refuses):
@@ -5,7 +17,11 @@
5
17
  * @param {{ port: number, token: string, env?: Record<string,string|undefined>, home?: string,
6
18
  * spawnFn?: typeof realSpawn, bareloopBin?: string, settleMs?: number,
7
19
  * getResumeContext: (runid: string) => ({row: any, plan: any}|null),
8
- * getStopContext?: (runid: string) => ({row: any, live: boolean, spineExists: boolean}|null) }} opts
20
+ * getStopContext?: (runid: string) => ({row: any, live: boolean, spineExists: boolean}|null),
21
+ * getNeedsContext?: (runid: string) => ({row: any, needs: 'Accept'|'Resume'|null}|null),
22
+ * getAcceptContext?: (runid: string) => ({row: any, live: boolean, waiting: boolean, specPath: string|null, specHash: string|null}|null),
23
+ * stageQuestionsOfSpec?: (spec: any) => Map<string, string|null>,
24
+ * acceptWaitMs?: number }} opts
9
25
  */
10
26
  export function createRunRoutes(opts: {
11
27
  port: number;
@@ -24,9 +40,24 @@ export function createRunRoutes(opts: {
24
40
  live: boolean;
25
41
  spineExists: boolean;
26
42
  } | null);
43
+ getNeedsContext?: (runid: string) => ({
44
+ row: any;
45
+ needs: "Accept" | "Resume" | null;
46
+ } | null);
47
+ getAcceptContext?: (runid: string) => ({
48
+ row: any;
49
+ live: boolean;
50
+ waiting: boolean;
51
+ specPath: string | null;
52
+ specHash: string | null;
53
+ } | null);
54
+ stageQuestionsOfSpec?: (spec: any) => Map<string, string | null>;
55
+ acceptWaitMs?: number;
27
56
  }): {
28
57
  handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
29
58
  };
30
59
  /** how long a resume waits for an early engine exit before reporting "started" (ms) */
31
60
  export const DEFAULT_SETTLE_MS: 6000;
61
+ /** how long an Accept waits for the engine's answer (it re-runs the mechanical close stages) before saying it is still checking (ms) */
62
+ export const DEFAULT_ACCEPT_WAIT_MS: number;
32
63
  import { spawn as realSpawn } from 'node:child_process';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @param {{ door: ReturnType<typeof import('./schedules.js').createScheduleDoor>, now?: () => number, intervalMs?: number }} opts
3
+ * `intervalMs` 0 = no timer (a test drives `tick`).
4
+ */
5
+ export function createScheduler(opts: {
6
+ door: ReturnType<typeof import("./schedules.js").createScheduleDoor>;
7
+ now?: () => number;
8
+ intervalMs?: number;
9
+ }): {
10
+ tick: () => Promise<void>;
11
+ /** Start the real timer (idempotent). */
12
+ start(): void;
13
+ /** Stop it (idempotent). The server's close calls this. */
14
+ stop(): void;
15
+ /** whether the real timer is running (a test reads this) */
16
+ readonly running: boolean;
17
+ };