bareloop 0.42.0 → 0.44.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.
- package/CHANGELOG.md +88 -0
- package/bareloop.context.md +103 -41
- package/package.json +2 -2
- package/src/authorflow.js +26 -11
- package/src/authoring.js +1 -1
- package/src/authorjob.js +33 -15
- package/src/authorreadout.js +7 -6
- package/src/declaredclose.js +6 -3
- package/src/draftspend.js +20 -2
- package/src/facts.js +54 -9
- package/src/factwords.js +58 -6
- package/src/inputlook.js +38 -3
- package/src/interviewrun.js +0 -1
- package/src/job.js +39 -12
- package/src/kinds.js +5 -0
- package/src/pagedropwords.js +64 -0
- package/src/pagemap.js +264 -0
- package/src/pagerules.js +319 -0
- package/src/panel/authorroutes.js +138 -60
- package/src/panel/authorsession.js +88 -32
- package/src/panel/index.html +1251 -522
- package/src/panel/jobplan.js +4 -2
- package/src/panel/runroutes.js +188 -6
- package/src/panel/scheduler.js +156 -0
- package/src/panel/schedules.js +667 -0
- package/src/panel/schedulestart.js +243 -0
- package/src/panel/server.js +1234 -68
- package/src/panel/status.js +33 -0
- package/src/panelmoney.js +29 -0
- package/src/planrun.js +92 -11
- package/src/promptregisters.js +4 -0
- package/src/providerrows.js +3 -3
- package/src/replay.js +118 -7
- package/src/reviewdoor.js +37 -0
- package/src/rubricstep.js +123 -11
- package/src/runlist.js +37 -1
- package/src/schedulewords.js +299 -0
- package/src/source.js +18 -0
- package/src/thinking.js +63 -2
- package/src/trigger.js +48 -0
- package/src/triggerclock.js +127 -0
- package/src/triggerfiles.js +215 -0
- package/src/triggerledger.js +79 -0
- package/src/userrun.js +34 -21
- package/types/authorflow.d.ts +42 -6
- package/types/authorjob.d.ts +10 -2
- package/types/declaredclose.d.ts +3 -1
- package/types/draftspend.d.ts +27 -1
- package/types/facts.d.ts +15 -0
- package/types/factwords.d.ts +26 -3
- package/types/inputlook.d.ts +15 -0
- package/types/job.d.ts +8 -1
- package/types/kinds.d.ts +5 -0
- package/types/pagedropwords.d.ts +26 -0
- package/types/pagemap.d.ts +154 -0
- package/types/pagerules.d.ts +86 -0
- package/types/panel/authorroutes.d.ts +50 -14
- package/types/panel/authorsession.d.ts +37 -12
- package/types/panel/runroutes.d.ts +32 -1
- package/types/panel/scheduler.d.ts +17 -0
- package/types/panel/schedules.d.ts +412 -0
- package/types/panel/schedulestart.d.ts +106 -0
- package/types/panel/server.d.ts +297 -15
- package/types/panel/status.d.ts +51 -0
- package/types/panelmoney.d.ts +5 -0
- package/types/planrun.d.ts +2 -0
- package/types/providerrows.d.ts +2 -2
- package/types/replay.d.ts +21 -1
- package/types/reviewdoor.d.ts +27 -0
- package/types/rubricstep.d.ts +57 -1
- package/types/runlist.d.ts +24 -1
- package/types/schedulewords.d.ts +137 -0
- package/types/source.d.ts +11 -0
- package/types/thinking.d.ts +39 -0
- package/types/trigger.d.ts +37 -0
- package/types/triggerclock.d.ts +52 -0
- package/types/triggerfiles.d.ts +93 -0
- package/types/triggerledger.d.ts +35 -0
- package/types/userrun.d.ts +4 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the AI is told, for ONE page input. Ported from the POC prompt: the job, the `~` rules, "a result is one item", the answer form
|
|
3
|
+
* and the shape meanings.
|
|
4
|
+
* @param {{jobLines: {n: number, text: string, rule?: string, rules?: string[]}[], label: string, text: string}} o
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function mapPrompt({ jobLines, label, text }: {
|
|
8
|
+
jobLines: {
|
|
9
|
+
n: number;
|
|
10
|
+
text: string;
|
|
11
|
+
rule?: string;
|
|
12
|
+
rules?: string[];
|
|
13
|
+
}[];
|
|
14
|
+
label: string;
|
|
15
|
+
text: string;
|
|
16
|
+
}): string;
|
|
17
|
+
/**
|
|
18
|
+
* Read the AI's answer: ONE JSON object of the answer form, and nothing else. No repair (hamr's ruling): text that is not JSON, JSON of
|
|
19
|
+
* another form, or a fact whose shape or lists are not of the form is a malformed answer.
|
|
20
|
+
* @param {unknown} text @returns {{ok: true, mapping: import('./pagerules.js').PageMap}|{ok: false}}
|
|
21
|
+
*/
|
|
22
|
+
export function parseMapAnswer(text: unknown): {
|
|
23
|
+
ok: true;
|
|
24
|
+
mapping: import("./pagerules.js").PageMap;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Run the map: one call per page input.
|
|
30
|
+
* @param {{texts: Record<string, string>, inputs: {n: number, label: string}[], jobLines: {n: number, text: string, rule?: string, rules?: string[]}[],
|
|
31
|
+
* provider?: any, maxTokens?: number, ceilingUsd?: number|null, callTimeoutMs?: number,
|
|
32
|
+
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingStatus?: string, raw?: string}) => void,
|
|
33
|
+
* createLoop?: (o: {system: string, onLlmResult: any, provider: any, rates?: {in: number, out: number}}) => any,
|
|
34
|
+
* rates?: {in: number, out: number}|null, callOptions?: Record<string, any>, sleep?: (ms: number) => Promise<void>, prove?: Function}} o
|
|
35
|
+
* @returns {Promise<{state: 'PRESENT'|'ABSENT', facts: any, reason: string|null, cause: string|null, meta: any, raw: string,
|
|
36
|
+
* raws: ReturnType<typeof scrubRaw>[], calls: {label: string, costUsd: number|null, unpricedRounds: number}[],
|
|
37
|
+
* budgetStop: 'cap-halt'|'pricing-red'|null}>}
|
|
38
|
+
*/
|
|
39
|
+
export function runPageMap({ texts, inputs, jobLines, provider, maxTokens, ceilingUsd, callTimeoutMs, onCall, createLoop, rates, callOptions, sleep, }: {
|
|
40
|
+
texts: Record<string, string>;
|
|
41
|
+
inputs: {
|
|
42
|
+
n: number;
|
|
43
|
+
label: string;
|
|
44
|
+
}[];
|
|
45
|
+
jobLines: {
|
|
46
|
+
n: number;
|
|
47
|
+
text: string;
|
|
48
|
+
rule?: string;
|
|
49
|
+
rules?: string[];
|
|
50
|
+
}[];
|
|
51
|
+
provider?: any;
|
|
52
|
+
maxTokens?: number;
|
|
53
|
+
ceilingUsd?: number | null;
|
|
54
|
+
callTimeoutMs?: number;
|
|
55
|
+
onCall?: (call: {
|
|
56
|
+
label: string;
|
|
57
|
+
costUsd: number | null;
|
|
58
|
+
unpricedRounds: number;
|
|
59
|
+
thinkingStatus?: string;
|
|
60
|
+
raw?: string;
|
|
61
|
+
}) => void;
|
|
62
|
+
createLoop?: (o: {
|
|
63
|
+
system: string;
|
|
64
|
+
onLlmResult: any;
|
|
65
|
+
provider: any;
|
|
66
|
+
rates?: {
|
|
67
|
+
in: number;
|
|
68
|
+
out: number;
|
|
69
|
+
};
|
|
70
|
+
}) => any;
|
|
71
|
+
rates?: {
|
|
72
|
+
in: number;
|
|
73
|
+
out: number;
|
|
74
|
+
} | null;
|
|
75
|
+
callOptions?: Record<string, any>;
|
|
76
|
+
sleep?: (ms: number) => Promise<void>;
|
|
77
|
+
prove?: Function;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
state: "PRESENT" | "ABSENT";
|
|
80
|
+
facts: any;
|
|
81
|
+
reason: string | null;
|
|
82
|
+
cause: string | null;
|
|
83
|
+
meta: any;
|
|
84
|
+
raw: string;
|
|
85
|
+
raws: ReturnType<typeof scrubRaw>[];
|
|
86
|
+
calls: {
|
|
87
|
+
label: string;
|
|
88
|
+
costUsd: number | null;
|
|
89
|
+
unpricedRounds: number;
|
|
90
|
+
}[];
|
|
91
|
+
budgetStop: "cap-halt" | "pricing-red" | null;
|
|
92
|
+
}>;
|
|
93
|
+
/** @param {unknown} q a recorded question @returns {string|null} the reading a reading ask proposed, or null when it was another kind of question */
|
|
94
|
+
export function readingOfQuestion(q: unknown): string | null;
|
|
95
|
+
/**
|
|
96
|
+
* PRD v1.107 Part 5b ("the look reads, code asks"): the early asks CODE forces before any paid drafting, from the look's own result. Every `~` rule is
|
|
97
|
+
* one of three kinds:
|
|
98
|
+
* - PAGE: a kept page word carries it (a kept fact with `forRule` and no `reading`): no ask.
|
|
99
|
+
* - READING: a kept fact carries it with a `reading` (a limit the page cannot hold, e.g. "not a red-eye" -> departure time between 06:00 and 21:00).
|
|
100
|
+
* Asked only when a bound of the reading is NOT in the words the person wrote (the same test as `rubric-fact-unmarked`, `boundInWords`); a
|
|
101
|
+
* bound they wrote themselves is theirs and nothing is asked. `agree` writes the reading as a `~` rule in plain words (the answer text is
|
|
102
|
+
* `reading`), so the drafter is told it and the fact signs plain.
|
|
103
|
+
* - CAN'T CHECK: no kept fact carries it (PRD v1.106 Part 5 Q3 T2). `drop it` leaves it Not checked.
|
|
104
|
+
* One ask per rule text on a line; readings first, then can't-check asks, each in line order. The caller applies the machine's cap and the answer gate.
|
|
105
|
+
* @param {{facts?: {line: number, forRule?: number, name?: string, shape?: string, unit?: string[], currency?: string[], reading?: import('./pagerules.js').Reading}[]}|null|undefined} look
|
|
106
|
+
* @param {{n: number, text?: string, rule?: string, rules?: string[]}[]|null|undefined} jobLines
|
|
107
|
+
* @returns {LookAsk[]}
|
|
108
|
+
*/
|
|
109
|
+
export function askableFromLook(look: {
|
|
110
|
+
facts?: {
|
|
111
|
+
line: number;
|
|
112
|
+
forRule?: number;
|
|
113
|
+
name?: string;
|
|
114
|
+
shape?: string;
|
|
115
|
+
unit?: string[];
|
|
116
|
+
currency?: string[];
|
|
117
|
+
reading?: import("./pagerules.js").Reading;
|
|
118
|
+
}[];
|
|
119
|
+
} | null | undefined, jobLines: {
|
|
120
|
+
n: number;
|
|
121
|
+
text?: string;
|
|
122
|
+
rule?: string;
|
|
123
|
+
rules?: string[];
|
|
124
|
+
}[] | null | undefined): LookAsk[];
|
|
125
|
+
/**
|
|
126
|
+
* PRD v1.107 Part 5b: more asks than the machine's cap means the job needs a rewrite, not a questionnaire. The code-owned stop words list EVERY ask and
|
|
127
|
+
* its reading as the suggestion (no extra paid call). A secret in a rule is masked.
|
|
128
|
+
* @param {LookAsk[]} asks @returns {string}
|
|
129
|
+
*/
|
|
130
|
+
export function tooManyAsksText(asks: LookAsk[]): string;
|
|
131
|
+
/** the label of the one paid call, as the drafting book and the progress line see it */
|
|
132
|
+
export const MAP_CALL_LABEL: "page-map";
|
|
133
|
+
/** the shapes the map may name; a `place` is read by code as a phrase */
|
|
134
|
+
export const MAP_SHAPES: readonly string[];
|
|
135
|
+
export const MAP_SYSTEM: "You map a person's job to the exact words of ONE saved web page. You never grade and never invent words: every string you return must be copied from the page text, character for character.";
|
|
136
|
+
/** the plain-word stop when the answer was not readable twice (no Resume: a draft is not a run) */
|
|
137
|
+
export const MAP_UNREADABLE: "The AI's answer about the page was not readable twice. Nothing was signed.";
|
|
138
|
+
/** the answer that drops a `~` rule the page cannot check: the person types it (any case, a closing full stop allowed) */
|
|
139
|
+
export const DROP_IT: "drop it";
|
|
140
|
+
export function isDropIt(text: unknown): boolean;
|
|
141
|
+
export function isAgree(text: unknown): boolean;
|
|
142
|
+
/**
|
|
143
|
+
* `reading` = the plain words an `agree` writes as a `~` rule (a reading ask only)
|
|
144
|
+
*/
|
|
145
|
+
export type LookAsk = {
|
|
146
|
+
line: number;
|
|
147
|
+
rule: number;
|
|
148
|
+
source: "look";
|
|
149
|
+
question: string;
|
|
150
|
+
ask: "reading" | "cant-check";
|
|
151
|
+
ruleText: string;
|
|
152
|
+
reading?: string;
|
|
153
|
+
};
|
|
154
|
+
import { scrubRaw } from './text.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which job line, and which `~` rule under it, a map fact carries out. The map writes `from` as "line N", "line N rule M", "rule M" or
|
|
3
|
+
* "job line"; anything else is the first job line.
|
|
4
|
+
* @param {unknown} from @param {{n: number, text?: string, rule?: string, rules?: string[]}[]} jobLines
|
|
5
|
+
* @returns {{line: number, rule?: number}}
|
|
6
|
+
*/
|
|
7
|
+
export function tieOf(from: unknown, jobLines: {
|
|
8
|
+
n: number;
|
|
9
|
+
text?: string;
|
|
10
|
+
rule?: string;
|
|
11
|
+
rules?: string[];
|
|
12
|
+
}[]): {
|
|
13
|
+
line: number;
|
|
14
|
+
rule?: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Is this a legal `reading` for a fact of this shape? Only a time or money fact carries one, with the op set and the bound types of a signed
|
|
18
|
+
* fact rule (src/facts.js): no other key, no missing bound. A malformed one makes the whole map answer malformed (no repair).
|
|
19
|
+
* @param {unknown} shape @param {unknown} r @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
export function validReading(shape: unknown, r: unknown): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Apply the rules to the AI's map over one saved page. Returns the kept facts and, for everything a rule removed, one `dropped` entry.
|
|
24
|
+
* The order of the rules is the POC's (apply-v3.mjs): found-on-page, then the rules that read the page. A place is read as a phrase.
|
|
25
|
+
* @param {string} text the frozen page text @param {PageMap} mapping @param {{n: number, text?: string, rule?: string, rules?: string[]}[]} jobLines
|
|
26
|
+
* @returns {{facts: KeptFact[], dropped: Dropped[]}}
|
|
27
|
+
*/
|
|
28
|
+
export function applyPageRules(text: string, mapping: PageMap, jobLines: {
|
|
29
|
+
n: number;
|
|
30
|
+
text?: string;
|
|
31
|
+
rule?: string;
|
|
32
|
+
rules?: string[];
|
|
33
|
+
}[]): {
|
|
34
|
+
facts: KeptFact[];
|
|
35
|
+
dropped: Dropped[];
|
|
36
|
+
};
|
|
37
|
+
export function linesOf(text: string): string[];
|
|
38
|
+
/**
|
|
39
|
+
* what a `~` rule means as a limit (PRD v1.107 Part 5b): the same shape as a signed fact rule
|
|
40
|
+
*/
|
|
41
|
+
export type Reading = {
|
|
42
|
+
op: string;
|
|
43
|
+
value?: string | number;
|
|
44
|
+
lo?: string | number;
|
|
45
|
+
hi?: string | number;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* the AI's map, one fact
|
|
49
|
+
*/
|
|
50
|
+
export type MapFact = {
|
|
51
|
+
name: string;
|
|
52
|
+
from?: string;
|
|
53
|
+
shape: string;
|
|
54
|
+
words?: string[];
|
|
55
|
+
currency?: string[];
|
|
56
|
+
unit?: string[];
|
|
57
|
+
reading?: Reading;
|
|
58
|
+
};
|
|
59
|
+
export type PageMap = {
|
|
60
|
+
facts: MapFact[];
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* `words` = what the page proves: the labels of a money/number/time fact, or the alternative phrases of a phrase fact (a place is a phrase)
|
|
64
|
+
*/
|
|
65
|
+
export type KeptFact = {
|
|
66
|
+
line: number;
|
|
67
|
+
name: string;
|
|
68
|
+
shape: string;
|
|
69
|
+
from: string;
|
|
70
|
+
words: string[];
|
|
71
|
+
unit?: string[];
|
|
72
|
+
currency?: string[];
|
|
73
|
+
forRule?: number;
|
|
74
|
+
reading?: Reading;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* `other` = what the drop is compared to: the first label (R1), the primary form (R1L), the fact it would have repeated (S1)
|
|
78
|
+
*/
|
|
79
|
+
export type Dropped = {
|
|
80
|
+
line: number;
|
|
81
|
+
rule?: number;
|
|
82
|
+
fact: string;
|
|
83
|
+
word?: string;
|
|
84
|
+
reason: string;
|
|
85
|
+
other?: string;
|
|
86
|
+
};
|
|
@@ -33,6 +33,19 @@ export function checkHumanGuard(req: import("node:http").IncomingMessage, { toke
|
|
|
33
33
|
ok: false;
|
|
34
34
|
reason: string;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* The one-at-a-time authoring session store (build spec: "one authoring
|
|
38
|
+
* session at a time — a second Start is refused while one is live"). A
|
|
39
|
+
* session is "live" from creation until its phase settles into a terminal
|
|
40
|
+
* one; a terminal session is kept (so its chat/hash stay readable) but no
|
|
41
|
+
* longer counts against the one-at-a-time rule.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* PRD v1.106 Part 5 Q2: the "fetching the page..." step is done once the run's own refetch has written `refrozenAt` (source.json, written last).
|
|
45
|
+
* Settled when the state is read, so nothing polls and the step never stays running after the fetch.
|
|
46
|
+
* @param {any} state
|
|
47
|
+
*/
|
|
48
|
+
export function settleFetchingStep(state: any): void;
|
|
36
49
|
/**
|
|
37
50
|
* Builds this file's own request handler, holding its session store in a
|
|
38
51
|
* closure (one per `createPanelServer` call, exactly like `handleRequest`'s
|
|
@@ -41,6 +54,9 @@ export function checkHumanGuard(req: import("node:http").IncomingMessage, { toke
|
|
|
41
54
|
* @param {{ port: number, token: string, env?: Record<string,string|undefined>,
|
|
42
55
|
* sessionsRoot?: string, spawnFn?: typeof realSpawn, bareloopBin?: string,
|
|
43
56
|
* jobsDir?: string, fetchImpl?: typeof fetch, home?: string,
|
|
57
|
+
* schedules?: ReturnType<typeof import('./schedules.js').createScheduleDoor>,
|
|
58
|
+
* sessionDeps?: Record<string, any>,
|
|
59
|
+
* runJobNames?: () => string[],
|
|
44
60
|
* startFrom?: {get: (runid: string, o?: {model?: string}) => any, getImport: (id: string, o?: {model?: string}) => any, listJobs?: (o?: {model?: string}) => any[]} }} opts
|
|
45
61
|
*/
|
|
46
62
|
export function createAuthorRoutes(opts: {
|
|
@@ -53,6 +69,9 @@ export function createAuthorRoutes(opts: {
|
|
|
53
69
|
jobsDir?: string;
|
|
54
70
|
fetchImpl?: typeof fetch;
|
|
55
71
|
home?: string;
|
|
72
|
+
schedules?: ReturnType<typeof import("./schedules.js").createScheduleDoor>;
|
|
73
|
+
sessionDeps?: Record<string, any>;
|
|
74
|
+
runJobNames?: () => string[];
|
|
56
75
|
startFrom?: {
|
|
57
76
|
get: (runid: string, o?: {
|
|
58
77
|
model?: string;
|
|
@@ -67,20 +86,26 @@ export function createAuthorRoutes(opts: {
|
|
|
67
86
|
}): {
|
|
68
87
|
handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
|
|
69
88
|
sessions: Map<string, any>;
|
|
89
|
+
headless: Map<string, any>;
|
|
90
|
+
startSession: (card: any, reuse: {
|
|
91
|
+
spec: any;
|
|
92
|
+
workflowKey: string;
|
|
93
|
+
} | null, inputs: import("../jobblock.js").JobInput[], o?: {
|
|
94
|
+
headless?: boolean;
|
|
95
|
+
}) => Promise<{
|
|
96
|
+
ok: true;
|
|
97
|
+
session: ReturnType<typeof createSession>;
|
|
98
|
+
} | {
|
|
99
|
+
ok: false;
|
|
100
|
+
status: number;
|
|
101
|
+
error: string;
|
|
102
|
+
}>;
|
|
103
|
+
envNow: () => Record<string, string | undefined>;
|
|
104
|
+
spawnFn: typeof realSpawn;
|
|
105
|
+
bareloopBin: string;
|
|
106
|
+
/** a headless session the starter has removed from disk leaves its map too @param {string} id */
|
|
107
|
+
forgetHeadless: (id: string) => void;
|
|
70
108
|
};
|
|
71
|
-
/**
|
|
72
|
-
* The panel's OWN 2-decimal money render for a chat line — hamr's ruling
|
|
73
|
-
* 2026-09-28 (2nd addendum, panel money 2-decimals): a positive amount below
|
|
74
|
-
* a cent reads `<$0.01`, never `$0.00` (a real cost must never read as
|
|
75
|
-
* free). This is the server-side (Node, chat-text) sibling of
|
|
76
|
-
* `src/panel/index.html`'s own inline `panelMoney` — the two can't share
|
|
77
|
-
* code (one runs in the browser as inlined script, the other in Node), the
|
|
78
|
-
* same duplication `src/replay.js`'s `money()`/`index.html`'s `money()`
|
|
79
|
-
* already carry, so both apply the identical rule rather than drift.
|
|
80
|
-
* @param {number} n a non-negative dollar amount
|
|
81
|
-
* @returns {string}
|
|
82
|
-
*/
|
|
83
|
-
export function panelMoney2(n: number): string;
|
|
84
109
|
/**
|
|
85
110
|
* THE ONLY function in this file (or anywhere in the panel) that spawns a
|
|
86
111
|
* run. Refuses unless the session is `prepared` AND the hash the request
|
|
@@ -89,7 +114,7 @@ export function panelMoney2(n: number): string;
|
|
|
89
114
|
* it to sign"), and this is where it is checked, not trusted.
|
|
90
115
|
* @param {ReturnType<typeof createSession>} session
|
|
91
116
|
* @param {string} claimedHash
|
|
92
|
-
* @param {{env: any, spawnFn: typeof realSpawn, bareloopBin: string, sessionsRoot?: string, home?: string}} o
|
|
117
|
+
* @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
118
|
* @returns {{ok: boolean, error?: string, job?: string}}
|
|
94
119
|
*/
|
|
95
120
|
export function signRun(session: ReturnType<typeof createSession>, claimedHash: string, o: {
|
|
@@ -98,10 +123,21 @@ export function signRun(session: ReturnType<typeof createSession>, claimedHash:
|
|
|
98
123
|
bareloopBin: string;
|
|
99
124
|
sessionsRoot?: string;
|
|
100
125
|
home?: string;
|
|
126
|
+
reviewDoor?: boolean;
|
|
127
|
+
onSpawnError?: (e: Error) => void;
|
|
128
|
+
onExit?: (code: number | null) => void;
|
|
101
129
|
}): {
|
|
102
130
|
ok: boolean;
|
|
103
131
|
error?: string;
|
|
104
132
|
job?: string;
|
|
105
133
|
};
|
|
134
|
+
/** plain words for a run whose saved job no longer passes today's `validateJob` (RULED 2026-10-15): never the raw red codes */
|
|
135
|
+
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.";
|
|
136
|
+
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.";
|
|
137
|
+
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.";
|
|
138
|
+
export const OLD_JOB_RERUN_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be rerun. Use Reuse workflow on a newer run, or start a new job.";
|
|
139
|
+
export const OLD_JOB_PAUSE_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be paused from here. Use Reuse workflow on a newer run, or start a new job.";
|
|
140
|
+
export { panelMoney2 };
|
|
106
141
|
import { spawn as realSpawn } from 'node:child_process';
|
|
107
142
|
import { createSession } from './authorsession.js';
|
|
143
|
+
import { panelMoney2 } from '../panelmoney.js';
|
|
@@ -54,6 +54,18 @@ export function planMessage(p: {
|
|
|
54
54
|
export function jobNameTaken(name: string, opts?: {
|
|
55
55
|
jobsDir?: string;
|
|
56
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;
|
|
57
69
|
/**
|
|
58
70
|
* The repo a card names: Inputs line 1 (P7 — the Source box is gone; the first input IS the repo). `''` when the Inputs
|
|
59
71
|
* box does not parse (the card validator refuses that before anything uses this).
|
|
@@ -68,13 +80,13 @@ export function cardSource(card: any): string;
|
|
|
68
80
|
*/
|
|
69
81
|
export function cardFields(card: any): Record<string, any>;
|
|
70
82
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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.
|
|
73
85
|
* @param {Record<string, any>} card the form as it was submitted
|
|
74
86
|
* @param {Record<string, any>} origin the form as the server prefilled it
|
|
75
|
-
* @returns {
|
|
87
|
+
* @returns {boolean}
|
|
76
88
|
*/
|
|
77
|
-
export function
|
|
89
|
+
export function jobBoxesChanged(card: Record<string, any>, origin: Record<string, any>): boolean;
|
|
78
90
|
/**
|
|
79
91
|
* The spec a reuse signs: a COPY of the origin's signed spec with ONLY the open fields set from the card —
|
|
80
92
|
* `writeScope` from Destination, `inputs` from the Inputs box (P7), `budgetUsd` from the $ cap, `maxWallMs` from the Time cap (absent when blank), and the
|
|
@@ -106,6 +118,18 @@ export function validateReuseCard(card: any, opts?: {
|
|
|
106
118
|
ok: false;
|
|
107
119
|
error: string;
|
|
108
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;
|
|
109
133
|
/**
|
|
110
134
|
* The ONE Destination rule for a REPO source (fresh card and Reuse card alike): there Destination is the write fence,
|
|
111
135
|
* relative to the repo, so an absolute path is refused — at $0, before any copy or model call. A FOLDER source (or a
|
|
@@ -120,11 +144,12 @@ export function repoDestinationProblem(destination: string, isRepoLike: boolean)
|
|
|
120
144
|
* creates a session or spends anything — never inside the async pipeline,
|
|
121
145
|
* so a bad card refuses synchronously, in the same HTTP response.
|
|
122
146
|
* @param {any} card
|
|
123
|
-
* @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)
|
|
124
148
|
* @returns {{ok: true}|{ok: false, error: string}}
|
|
125
149
|
*/
|
|
126
150
|
export function validateJobCard(card: any, opts?: {
|
|
127
151
|
jobsDir?: string;
|
|
152
|
+
allowName?: string;
|
|
128
153
|
rows?: readonly import("../providerrows.js").KeyRow[];
|
|
129
154
|
}): {
|
|
130
155
|
ok: true;
|
|
@@ -195,14 +220,14 @@ export const PLAN_FOOTER: "Sign & run to confirm this plan, or Revise to edit th
|
|
|
195
220
|
/** the form's fields, in card order — what `card.json` stores, verbatim (P5 item 3) */
|
|
196
221
|
export const CARD_FIELDS: readonly string[];
|
|
197
222
|
/**
|
|
198
|
-
* REUSE WORKFLOW (
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
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.
|
|
202
227
|
*/
|
|
203
|
-
export const
|
|
204
|
-
/**
|
|
205
|
-
export const
|
|
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.";
|
|
206
231
|
export type StepLine = {
|
|
207
232
|
id: string;
|
|
208
233
|
label: string;
|
|
@@ -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'|'rerun'|'pause'} 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" | "rerun" | "pause", 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)
|
|
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
|
+
};
|