bareloop 0.32.0 → 0.34.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 +95 -0
- package/README.md +4 -1
- package/bareloop.context.md +163 -82
- package/package.json +1 -1
- package/src/authorflow.js +5 -3
- package/src/authorjob.js +3 -1
- package/src/authorrun.js +36 -8
- package/src/authorscout.js +10 -6
- package/src/bundlerun.js +266 -0
- package/src/cli.js +24 -352
- package/src/interviewrun.js +6 -4
- package/src/invoke.js +22 -0
- package/src/job.js +2 -2
- package/src/judged.js +27 -5
- package/src/kinds.js +1 -0
- package/src/ledger.js +3 -4
- package/src/monthly.js +112 -35
- package/src/panel/authorroutes.js +12 -2
- package/src/panel/authorsession.js +24 -4
- package/src/panel/server.js +18 -13
- package/src/planrun.js +10 -8
- package/src/providerrows.js +87 -1
- package/src/providers.js +4 -4
- package/src/run.js +7 -2
- package/src/runlist.js +94 -7
- package/src/source.js +8 -3
- package/src/u-watchdog.mjs +296 -0
- package/src/userrun.js +207 -65
- package/types/authorflow.d.ts +8 -2
- package/types/authorjob.d.ts +6 -1
- package/types/authorscout.d.ts +11 -2
- package/types/bundlerun.d.ts +26 -0
- package/types/invoke.d.ts +8 -0
- package/types/judged.d.ts +10 -3
- package/types/monthly.d.ts +57 -10
- package/types/panel/authorroutes.d.ts +15 -0
- package/types/planrun.d.ts +13 -1
- package/types/providerrows.d.ts +66 -0
- package/types/providers.d.ts +1 -2
- package/types/run.d.ts +14 -1
- package/types/runlist.d.ts +79 -3
- package/types/source.d.ts +8 -3
- package/types/userrun.d.ts +49 -0
package/types/authorjob.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ export function refuseLockedKind(kind: string): Refusal;
|
|
|
82
82
|
* verdictType?: string|null,
|
|
83
83
|
* questions?: Record<string|number, string>|null, generate?: Function, provider?: any,
|
|
84
84
|
* seedRef?: string|null, scout?: any, listing?: any, ceilingUsd?: number|null, judgeModel?: string|null,
|
|
85
|
+
* rates?: {in: number, out: number}|null,
|
|
85
86
|
* onPhase?: (phase: string, data?: any) => void,
|
|
86
87
|
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
|
|
87
88
|
* seedFn?: Function, scoutFn?: Function, listingFn?: Function,
|
|
@@ -94,7 +95,7 @@ export function refuseLockedKind(kind: string): Refusal;
|
|
|
94
95
|
* closeDecl: any, seedRef: string|null, authoring: any, judged: any, reds: Red[],
|
|
95
96
|
* stop: string|null, cost: any, interview: any, confirmed: any}>}
|
|
96
97
|
*/
|
|
97
|
-
export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, writeScope, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, ask, confirmGenerate, isRepo, langResult, }: {
|
|
98
|
+
export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, rates, writeScope, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, ask, confirmGenerate, isRepo, langResult, }: {
|
|
98
99
|
answers: Record<string | number, any>;
|
|
99
100
|
repoPath?: string | null;
|
|
100
101
|
lang: string;
|
|
@@ -107,6 +108,10 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
|
|
|
107
108
|
listing?: any;
|
|
108
109
|
ceilingUsd?: number | null;
|
|
109
110
|
judgeModel?: string | null;
|
|
111
|
+
rates?: {
|
|
112
|
+
in: number;
|
|
113
|
+
out: number;
|
|
114
|
+
} | null;
|
|
110
115
|
onPhase?: (phase: string, data?: any) => void;
|
|
111
116
|
onCall?: (call: {
|
|
112
117
|
label: string;
|
package/types/authorscout.d.ts
CHANGED
|
@@ -93,13 +93,14 @@ export function defaultSurveyor({ workdir, granted, auditPath, ctx }: {
|
|
|
93
93
|
* rounds?: number, minBytes?: number, blobMax?: number, maxTokens?: number, ctx?: boolean,
|
|
94
94
|
* attempts?: number, ceilingUsd?: number|null, callTimeoutMs?: number,
|
|
95
95
|
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
|
|
96
|
-
* createLoop?: (o: {system: string, policy: any, onLlmResult: any, provider: any}) => any,
|
|
96
|
+
* createLoop?: (o: {system: string, policy: any, onLlmResult: any, provider: any, rates?: {in: number, out: number}}) => any,
|
|
97
|
+
* rates?: {in: number, out: number}|null,
|
|
97
98
|
* createSurveyor?: (o: {workdir: string, granted: readonly string[], ctx: boolean}) => Promise<any>}} o
|
|
98
99
|
* @returns {Promise<Survey & {raw: string, raws: ReturnType<typeof scrubRaw>[],
|
|
99
100
|
* calls: {label: string, costUsd: number|null, unpricedRounds: number}[],
|
|
100
101
|
* budgetStop: 'cap-halt'|'pricing-red'|null}>}
|
|
101
102
|
*/
|
|
102
|
-
export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, maxTokens, ctx, attempts, ceilingUsd, callTimeoutMs, onCall, createLoop, createSurveyor, }: {
|
|
103
|
+
export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, maxTokens, ctx, attempts, ceilingUsd, callTimeoutMs, onCall, createLoop, rates, createSurveyor, }: {
|
|
103
104
|
workdir: string;
|
|
104
105
|
provider?: any;
|
|
105
106
|
rounds?: number;
|
|
@@ -120,7 +121,15 @@ export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, m
|
|
|
120
121
|
policy: any;
|
|
121
122
|
onLlmResult: any;
|
|
122
123
|
provider: any;
|
|
124
|
+
rates?: {
|
|
125
|
+
in: number;
|
|
126
|
+
out: number;
|
|
127
|
+
};
|
|
123
128
|
}) => any;
|
|
129
|
+
rates?: {
|
|
130
|
+
in: number;
|
|
131
|
+
out: number;
|
|
132
|
+
} | null;
|
|
124
133
|
createSurveyor?: (o: {
|
|
125
134
|
workdir: string;
|
|
126
135
|
granted: readonly string[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bareloop run <bundleDir> --repo <path> [--budget N] [--wall MIN] [--approve <bundleHash>]`
|
|
3
|
+
* `bareloop run <bundleDir> --resume <runid> [--repo <path>] [--budget N] [--wall MIN] [--approve <bundleHash>]`
|
|
4
|
+
* @param {string[]} args
|
|
5
|
+
* @param {{
|
|
6
|
+
* out: (s: string) => void, err: (s: string) => void, cwd: string,
|
|
7
|
+
* env: Record<string,string|undefined>, now: () => number, deps: any, keysHome?: string,
|
|
8
|
+
* parseFlags: (a: string[]) => { positional: string[], flags: Record<string, string|true> },
|
|
9
|
+
* printReds: (reds: import('./bundle.js').Red[], err: (s: string) => void) => void,
|
|
10
|
+
* }} ctx
|
|
11
|
+
* @returns {Promise<number>}
|
|
12
|
+
*/
|
|
13
|
+
export function bundleMain(args: string[], { out, err, cwd, env, now, deps, keysHome, parseFlags, printReds, }: {
|
|
14
|
+
out: (s: string) => void;
|
|
15
|
+
err: (s: string) => void;
|
|
16
|
+
cwd: string;
|
|
17
|
+
env: Record<string, string | undefined>;
|
|
18
|
+
now: () => number;
|
|
19
|
+
deps: any;
|
|
20
|
+
keysHome?: string;
|
|
21
|
+
parseFlags: (a: string[]) => {
|
|
22
|
+
positional: string[];
|
|
23
|
+
flags: Record<string, string | true>;
|
|
24
|
+
};
|
|
25
|
+
printReds: (reds: import("./bundle.js").Red[], err: (s: string) => void) => void;
|
|
26
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command that runs `flow`, spelled the way the caller reached this flow: `bareloop <flow>`
|
|
3
|
+
* when `invokedAs` names the CLI, else the source-tree script.
|
|
4
|
+
* @param {Flow} flow @param {string|undefined} invokedAs
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function commandFor(flow: Flow, invokedAs: string | undefined): string;
|
|
8
|
+
export type Flow = "run-u" | "interview" | "author";
|
package/types/judged.d.ts
CHANGED
|
@@ -229,7 +229,7 @@ export function locatePrompt(card: any): string;
|
|
|
229
229
|
* On every route out, red or clean, `onCost` fires exactly once with the honest
|
|
230
230
|
* read: a paid call that leaves no meter record is F12 wearing a judge's coat.
|
|
231
231
|
* @param {{artifactText: string, card: any, loopFactory: (o: {provider?: any, system: string}) => any,
|
|
232
|
-
* maxTokens?: number, attempt?: number, onCost?: (c: {costUsd: number|null, unpricedRounds: number}) => void,
|
|
232
|
+
* maxTokens?: number, attempt?: number, onCost?: (c: {costUsd: number|null, unpricedRounds: number, rateSource?: string|null}) => void,
|
|
233
233
|
* callBounds?: {timeoutMs?: number, deadlineMs?: number}}} o
|
|
234
234
|
* @returns {Promise<{ok: boolean, facts: any|null, red: {axis: string, detail: string}|null,
|
|
235
235
|
* costUsd: number|null, unpricedRounds: number, truncated: boolean, parseError: boolean,
|
|
@@ -247,6 +247,7 @@ export function runLocate({ artifactText, card, loopFactory, maxTokens, attempt,
|
|
|
247
247
|
onCost?: (c: {
|
|
248
248
|
costUsd: number | null;
|
|
249
249
|
unpricedRounds: number;
|
|
250
|
+
rateSource?: string | null;
|
|
250
251
|
}) => void;
|
|
251
252
|
callBounds?: {
|
|
252
253
|
timeoutMs?: number;
|
|
@@ -382,10 +383,16 @@ export const JUDGE_RULE_IDS: readonly string[];
|
|
|
382
383
|
export const CALIBRATION_SIZE: 10;
|
|
383
384
|
/** the two verdicts a case may expect — `decide()`'s own closed set */
|
|
384
385
|
export const CASE_VERDICTS: readonly string[];
|
|
385
|
-
export function defaultJudgeLoop({ provider, system }: {
|
|
386
|
+
export function defaultJudgeLoop({ provider, system, rates }: {
|
|
386
387
|
provider: any;
|
|
387
388
|
system: string;
|
|
388
|
-
|
|
389
|
+
rates?: {
|
|
390
|
+
in: number;
|
|
391
|
+
out: number;
|
|
392
|
+
} | null;
|
|
393
|
+
}): {
|
|
394
|
+
run: (msgs: any[], tools: any[], opts?: any) => Promise<any>;
|
|
395
|
+
};
|
|
389
396
|
export type RuleRed = {
|
|
390
397
|
fn: string;
|
|
391
398
|
why: string;
|
package/types/monthly.d.ts
CHANGED
|
@@ -29,27 +29,30 @@ export function legTokens(records: any[]): number;
|
|
|
29
29
|
/**
|
|
30
30
|
* Every listed run as one {@link Leg}. The ONE reader the month total, the all-time total
|
|
31
31
|
* and the per-provider figures share.
|
|
32
|
-
* @param {{ home?: string, now?: () => number,
|
|
32
|
+
* @param {{ home?: string, now?: () => number, aboveRunid?: string, thisMonthOnly?: boolean }} [opts] `now` = the clock for the no-pid in-flight test; `aboveRunid` = read only the runs listed EARLIER in the file than that run (a claim yields to the claims above it, never below); `thisMonthOnly` = do not parse the spine of a run listed in another local month (the refusal check drops it anyway) unless its claim is still held
|
|
33
33
|
* @returns {Leg[]}
|
|
34
34
|
*/
|
|
35
35
|
export function readLegs(opts?: {
|
|
36
36
|
home?: string;
|
|
37
37
|
now?: () => number;
|
|
38
|
-
|
|
38
|
+
aboveRunid?: string;
|
|
39
|
+
thisMonthOnly?: boolean;
|
|
39
40
|
}): Leg[];
|
|
40
41
|
/**
|
|
41
|
-
* @param {{ home?: string, now?: () => number,
|
|
42
|
-
* @returns {{ usd: number, atLeast: boolean, runs: number, reservedUsd: number }} `usd` = real spend (the Money tab's figure); `reservedUsd` = the same with in-flight runs at their cap (the refusal check's)
|
|
42
|
+
* @param {{ home?: string, now?: () => number, aboveRunid?: string }} [opts] `aboveRunid`: see {@link readLegs}
|
|
43
|
+
* @returns {{ usd: number, atLeast: boolean, runs: number, reservedUsd: number, heldUsd: number, heldRuns: number }} `usd` = real spend (the Money tab's figure); `reservedUsd` = the same with in-flight runs at their cap (the refusal check's); `heldUsd` = what in-progress runs set aside beyond what they have spent (`reservedUsd - usd`), over `heldRuns` runs
|
|
43
44
|
*/
|
|
44
45
|
export function monthSpend(opts?: {
|
|
45
46
|
home?: string;
|
|
46
47
|
now?: () => number;
|
|
47
|
-
|
|
48
|
+
aboveRunid?: string;
|
|
48
49
|
}): {
|
|
49
50
|
usd: number;
|
|
50
51
|
atLeast: boolean;
|
|
51
52
|
runs: number;
|
|
52
53
|
reservedUsd: number;
|
|
54
|
+
heldUsd: number;
|
|
55
|
+
heldRuns: number;
|
|
53
56
|
};
|
|
54
57
|
/**
|
|
55
58
|
* The Money tab's figures: all-time and this-month spend, and per-provider breakdown
|
|
@@ -97,6 +100,8 @@ export function spendSummary(opts?: {
|
|
|
97
100
|
* @property {number|null} leftUsd what is left this month (null = no limit set)
|
|
98
101
|
* @property {number|null} limitUsd the person's monthly limit (null = none)
|
|
99
102
|
* @property {boolean} atLeast the month's spend is a floor, so `leftUsd` is a ceiling
|
|
103
|
+
* @property {number} [heldUsd] what run(s) in progress have set aside beyond their spend so far (present when a limit is set)
|
|
104
|
+
* @property {number} [heldRuns] how many runs hold it
|
|
100
105
|
*/
|
|
101
106
|
/**
|
|
102
107
|
* The person's monthly limit off a parsed config.json: key absent (or null) = no limit (null);
|
|
@@ -108,25 +113,59 @@ export function spendSummary(opts?: {
|
|
|
108
113
|
*/
|
|
109
114
|
export function monthlyLimitOf(config: Record<string, any>, home?: string): number | null;
|
|
110
115
|
/**
|
|
111
|
-
* Does a run with this $ cap fit in what is left this month (
|
|
116
|
+
* Does a run with this $ cap fit in what is left this month (a run still held counts at its full cap)? No limit set (key absent or null) = ok, no check; a limit that is present but not a
|
|
112
117
|
* number above 0 throws `ConfigError`.
|
|
113
118
|
* Compared in whole cents. Also throws `ConfigError` when config.json is unreadable — a gate
|
|
114
119
|
* whose own instrument is broken refuses; it never silently runs with no limit.
|
|
115
|
-
* @param {{ capUsd: number, home?: string, now?: () => number,
|
|
120
|
+
* @param {{ capUsd: number, home?: string, now?: () => number, aboveRunid?: string }} args `aboveRunid`: count only the claims listed above that run (a claim in progress)
|
|
116
121
|
* @returns {MonthlyRoom}
|
|
117
122
|
*/
|
|
118
|
-
export function checkMonthlyRoom({ capUsd, home, now,
|
|
123
|
+
export function checkMonthlyRoom({ capUsd, home, now, aboveRunid }: {
|
|
119
124
|
capUsd: number;
|
|
120
125
|
home?: string;
|
|
121
126
|
now?: () => number;
|
|
122
|
-
|
|
127
|
+
aboveRunid?: string;
|
|
123
128
|
}): MonthlyRoom;
|
|
124
129
|
/**
|
|
125
|
-
* The ONE refusal text, everywhere (CLI, panel cap note, panel Sign refusal).
|
|
130
|
+
* The ONE refusal text, everywhere (CLI, panel cap note, panel Sign refusal). When run(s) in progress hold
|
|
131
|
+
* part of the limit, the same line says how much.
|
|
126
132
|
* @param {MonthlyRoom} room
|
|
127
133
|
* @returns {string|null} null when the run fits
|
|
128
134
|
*/
|
|
129
135
|
export function monthlyRefusalText(room: MonthlyRoom): string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Close every claim ABOVE `aboveRunid` (file order) whose process is gone: a pid-bearing run row with no
|
|
138
|
+
* `settled` entry whose pid is not a live bareloop runner gets `{ type: 'settled', by: <closer>, reason:
|
|
139
|
+
* 'process gone', spentUsd: <its spine's floor>, spendComplete: false }` appended — so every claim gets a
|
|
140
|
+
* write-back, attributed. Two runs may both close the same dead claim; readers take the FIRST note.
|
|
141
|
+
* @param {{ home?: string, by: string, aboveRunid?: string }} args `by` = the run doing the closing
|
|
142
|
+
* @returns {number} how many notes were appended
|
|
143
|
+
*/
|
|
144
|
+
export function settleDeadClaims({ home, by, aboveRunid }: {
|
|
145
|
+
home?: string;
|
|
146
|
+
by: string;
|
|
147
|
+
aboveRunid?: string;
|
|
148
|
+
}): number;
|
|
149
|
+
/**
|
|
150
|
+
* The CLAIM (hamr 2026-09-30: whoever runs first claims the limit and holds it; no lock file, records
|
|
151
|
+
* only): the run APPENDS its row first (`row` carries its `pid` and `capUsd`), then reads the run list.
|
|
152
|
+
* Only the claims ABOVE its own row count — a live one is held at its cap (a dead one is closed with an
|
|
153
|
+
* attributed note and counts its real spend); rows below yield to it. If `capUsd` fits, it runs; if not
|
|
154
|
+
* it appends `{type:'released', reason:'refused'}` and reports the refusal, nothing spent. No limit set =
|
|
155
|
+
* no claim (`claimed: false`; the caller lists the run as it always did). An unreadable config, or a row
|
|
156
|
+
* that cannot be written, throws `ConfigError` — the gate refuses, never reads "no limit".
|
|
157
|
+
* @param {{ row: import('./runlist.js').RunRow, capUsd: number, home?: string, now?: () => number }} args
|
|
158
|
+
* @returns {{ room: MonthlyRoom, claimed: boolean }}
|
|
159
|
+
*/
|
|
160
|
+
export function claimRun({ row, capUsd, home, now }: {
|
|
161
|
+
row: import("./runlist.js").RunRow;
|
|
162
|
+
capUsd: number;
|
|
163
|
+
home?: string;
|
|
164
|
+
now?: () => number;
|
|
165
|
+
}): {
|
|
166
|
+
room: MonthlyRoom;
|
|
167
|
+
claimed: boolean;
|
|
168
|
+
};
|
|
130
169
|
export type Leg = {
|
|
131
170
|
/**
|
|
132
171
|
* when the run was listed
|
|
@@ -189,4 +228,12 @@ export type MonthlyRoom = {
|
|
|
189
228
|
* the month's spend is a floor, so `leftUsd` is a ceiling
|
|
190
229
|
*/
|
|
191
230
|
atLeast: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* what run(s) in progress have set aside beyond their spend so far (present when a limit is set)
|
|
233
|
+
*/
|
|
234
|
+
heldUsd?: number | undefined;
|
|
235
|
+
/**
|
|
236
|
+
* how many runs hold it
|
|
237
|
+
*/
|
|
238
|
+
heldRuns?: number | undefined;
|
|
192
239
|
};
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
/** @returns {string} a fresh per-process token — never persisted, never logged */
|
|
2
2
|
export function mintToken(): string;
|
|
3
|
+
/**
|
|
4
|
+
* The ONE Host check: the request names this panel's own bind address. A page on another
|
|
5
|
+
* origin reaching the panel through a rebound DNS name sends that name as Host, so it fails here.
|
|
6
|
+
* @param {import('node:http').IncomingMessage} req
|
|
7
|
+
* @param {{port: number}} o
|
|
8
|
+
* @returns {{ok: true}|{ok: false, reason: string}}
|
|
9
|
+
*/
|
|
10
|
+
export function checkHostGuard(req: import("node:http").IncomingMessage, { port }: {
|
|
11
|
+
port: number;
|
|
12
|
+
}): {
|
|
13
|
+
ok: true;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
reason: string;
|
|
17
|
+
};
|
|
3
18
|
/**
|
|
4
19
|
* `true` when a request may reach ANY route in this file — the token header
|
|
5
20
|
* plus an Origin/Host that names this exact bind address. `req.headers`
|
package/types/planrun.d.ts
CHANGED
|
@@ -195,6 +195,10 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
195
195
|
* `false` → the drafter has no tools, so a native session would report NO cost — return a
|
|
196
196
|
* metered claude-json TEXT provider (`--output-format json`, `parse:'claude-json'`) instead,
|
|
197
197
|
* so its spend is never invisible. The Loop path (`anthropic-api`) never touches this.
|
|
198
|
+
* @param {{in: number, out: number}|null} [opts.rates] the customer's own price for the worker's key row (USD per
|
|
199
|
+
* 1K tokens, `ratesFor`) — handed to the one Loop that drives worker, scout and planner rounds, so they read
|
|
200
|
+
* `rateSource:'caller'`; null = bare-agent's built-in guess. Not applied to a native (CLI) session, which reports its own cost.
|
|
201
|
+
* @param {{in: number, out: number}|null} [opts.judgeRates] the same, for the judge's Loop
|
|
198
202
|
* @param {string|null} [opts.judgeModel] softgreen — WHICH model that judge provider drives
|
|
199
203
|
* (PRD item 32.1). Resolved by the CALLER (`resolveJudge`, src/judged.js: the spec's signed
|
|
200
204
|
* `judge` override, else the job's own worker model), never pinned in this library. It
|
|
@@ -416,7 +420,7 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
416
420
|
* 'branch-red' | 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' |
|
|
417
421
|
* 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
418
422
|
*/
|
|
419
|
-
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, closeDir, maxStepRounds, layerRoot, readShim, scout, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun, resumable }: {
|
|
423
|
+
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, closeDir, maxStepRounds, layerRoot, readShim, scout, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, priorSpentUsd, reviewDoor, doorRerun, resumable }: {
|
|
420
424
|
workdir: string;
|
|
421
425
|
provider: any;
|
|
422
426
|
nativeProvider?: ((o: {
|
|
@@ -425,6 +429,14 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
425
429
|
maxTurns: number;
|
|
426
430
|
hasTools: boolean;
|
|
427
431
|
}) => any) | undefined;
|
|
432
|
+
rates?: {
|
|
433
|
+
in: number;
|
|
434
|
+
out: number;
|
|
435
|
+
} | null | undefined;
|
|
436
|
+
judgeRates?: {
|
|
437
|
+
in: number;
|
|
438
|
+
out: number;
|
|
439
|
+
} | null | undefined;
|
|
428
440
|
judgeModel?: string | null | undefined;
|
|
429
441
|
judgeProvider?: any;
|
|
430
442
|
emit: (type: string, data?: object) => object;
|
package/types/providerrows.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export function defaultsFor(envName: string): {
|
|
|
23
23
|
* @property {string} name the model id Chat uses ('' = not offered in Chat)
|
|
24
24
|
* @property {string} provider the `src/providers.js` table entry (the API shape's id)
|
|
25
25
|
* @property {string} baseUrl the saved Base URL ('' = the shape's default host)
|
|
26
|
+
* @property {unknown} [priceInPerM] the customer's own price, USD per 1M input tokens — carried RAW
|
|
27
|
+
* (present only when the config row names it); {@link ratesFor} is the one place that validates it
|
|
28
|
+
* @property {unknown} [priceOutPerM] the same, for output tokens
|
|
26
29
|
*/
|
|
27
30
|
/**
|
|
28
31
|
* The rows: one per name in `filled`, saved entry over defaults.
|
|
@@ -122,6 +125,60 @@ export function keyNameFor(provider: string | null | undefined, baseUrl: string
|
|
|
122
125
|
* @returns {Record<string,string|undefined>}
|
|
123
126
|
*/
|
|
124
127
|
export function applyConfiguredKey(env: Record<string, string | undefined>, provider: string | null | undefined, baseUrl: string | null | undefined, rows: readonly KeyRow[], model?: string | null | undefined): Record<string, string | undefined>;
|
|
128
|
+
/**
|
|
129
|
+
* The one readout line for a resolved customer price, preview and run tail alike:
|
|
130
|
+
* `yours: in $0.006 / out $1.20 per 1M tokens (DEEPSEEK_API_KEY row)`. A 0 / 0 price is accepted (the price
|
|
131
|
+
* is the customer's) but the line says so: every round then books $0, so spend and limits read $0.
|
|
132
|
+
* @param {{ inPerM: number, outPerM: number, envName: string }} price a {@link ratesFor} result
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
export function priceReadout(price: {
|
|
136
|
+
inPerM: number;
|
|
137
|
+
outPerM: number;
|
|
138
|
+
envName: string;
|
|
139
|
+
}): string;
|
|
140
|
+
/**
|
|
141
|
+
* The customer's own price for the row a `(provider, baseUrl[, model])` identity belongs to —
|
|
142
|
+
* the SAME row match {@link keyNameFor} uses (`findRow`), so the price and the key always come
|
|
143
|
+
* from one row. `null` = no price set (the run keeps bare-agent's built-in guess, byte-identical
|
|
144
|
+
* to before). Both fields or neither, each a finite number >= 0, USD per 1M tokens; anything
|
|
145
|
+
* else throws `ConfigError` naming the row and field — a bad price never silently falls back to
|
|
146
|
+
* the guess. `rates` is bare-agent's `Loop({ rates })` shape (USD per 1K); `inPerM`/`outPerM`
|
|
147
|
+
* are what a readout prints.
|
|
148
|
+
* @param {string|null|undefined} provider
|
|
149
|
+
* @param {string|null|undefined} baseUrl
|
|
150
|
+
* @param {readonly KeyRow[]} rows
|
|
151
|
+
* @param {string|null|undefined} [model]
|
|
152
|
+
* @returns {{ rates: { in: number, out: number }, inPerM: number, outPerM: number, envName: string }|null}
|
|
153
|
+
*/
|
|
154
|
+
export function ratesFor(provider: string | null | undefined, baseUrl: string | null | undefined, rows: readonly KeyRow[], model?: string | null | undefined): {
|
|
155
|
+
rates: {
|
|
156
|
+
in: number;
|
|
157
|
+
out: number;
|
|
158
|
+
};
|
|
159
|
+
inPerM: number;
|
|
160
|
+
outPerM: number;
|
|
161
|
+
envName: string;
|
|
162
|
+
} | null;
|
|
163
|
+
/**
|
|
164
|
+
* The price a judge's rounds are booked at. A price belongs to the MODEL's row: when the judge is
|
|
165
|
+
* the worker's own provider it calls the worker's endpoint, so a row on that endpoint NAMED for the
|
|
166
|
+
* judge's model prices it ({@link ratesFor}); a judge model that is absent or names no row there
|
|
167
|
+
* keeps the worker's price (the worker's row, or none = the guess). A different provider resolves
|
|
168
|
+
* on its own default endpoint, as before. A bad price on the resolved row throws like `ratesFor`.
|
|
169
|
+
* The judge's KEY is chosen elsewhere (`applyConfiguredKey`, by the worker's model), so the two
|
|
170
|
+
* can come from different rows — same provider + endpoint, judge model naming another row.
|
|
171
|
+
* @param {{ provider: string, model?: string|null }} judge
|
|
172
|
+
* @param {string|null|undefined} workerProvider
|
|
173
|
+
* @param {string|null|undefined} workerBaseUrl
|
|
174
|
+
* @param {ReturnType<typeof ratesFor>} workerPrice what the worker's own rounds are booked at
|
|
175
|
+
* @param {readonly KeyRow[]} rows
|
|
176
|
+
* @returns {ReturnType<typeof ratesFor>}
|
|
177
|
+
*/
|
|
178
|
+
export function judgeRatesFor(judge: {
|
|
179
|
+
provider: string;
|
|
180
|
+
model?: string | null;
|
|
181
|
+
}, workerProvider: string | null | undefined, workerBaseUrl: string | null | undefined, workerPrice: ReturnType<typeof ratesFor>, rows: readonly KeyRow[]): ReturnType<typeof ratesFor>;
|
|
125
182
|
/** DeepSeek's OpenAI-shaped endpoint (the same string the panel's Model menu used). */
|
|
126
183
|
export const DEEPSEEK_BASE_URL: "https://api.deepseek.com/v1";
|
|
127
184
|
/**
|
|
@@ -161,4 +218,13 @@ export type KeyRow = {
|
|
|
161
218
|
* the saved Base URL ('' = the shape's default host)
|
|
162
219
|
*/
|
|
163
220
|
baseUrl: string;
|
|
221
|
+
/**
|
|
222
|
+
* the customer's own price, USD per 1M input tokens — carried RAW
|
|
223
|
+
* (present only when the config row names it); {@link ratesFor} is the one place that validates it
|
|
224
|
+
*/
|
|
225
|
+
priceInPerM?: unknown;
|
|
226
|
+
/**
|
|
227
|
+
* the same, for output tokens
|
|
228
|
+
*/
|
|
229
|
+
priceOutPerM?: unknown;
|
|
164
230
|
};
|
package/types/providers.d.ts
CHANGED
|
@@ -52,8 +52,7 @@ export function makeProvider(providerName: string, { apiKey, model, baseUrl }?:
|
|
|
52
52
|
}): any;
|
|
53
53
|
/**
|
|
54
54
|
* Build the `{provider, providerFor, judgeProvider}` triple both runners
|
|
55
|
-
* need (`src/
|
|
56
|
-
* wiring) — ONE seam instead of two copies of the same construction and
|
|
55
|
+
* need (`src/authorrun.js` and `src/panel/authorsession.js` call it) — ONE seam instead of two copies of the same construction and
|
|
57
56
|
* tier-memoization logic. `model`/`tierModels` are already-RESOLVED inputs
|
|
58
57
|
* (the caller runs `resolveWorkerModel` itself, exactly as both runners did
|
|
59
58
|
* before this factory existed — that decision stays where each runner's own
|
package/types/run.d.ts
CHANGED
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
* metered claude-json text provider for the toolless drafter); ignored on every Loop-driven provider
|
|
35
35
|
* @param {(type: string, data?: object) => object} opts.emit spine emitter
|
|
36
36
|
* @param {(tier: string) => any} [opts.providerFor] P: per-step model-tier provider factory (forwarded to the plan flow)
|
|
37
|
+
* @param {{in: number, out: number}|null} [opts.rates] the CUSTOMER's own price for the worker's key row
|
|
38
|
+
* (`ratesFor`, src/providerrows.js — USD per 1K tokens), resolved once by the door and handed to every
|
|
39
|
+
* worker/planner Loop as bare-agent's `rates` (rounds then read `rateSource:'caller'`). Absent = the
|
|
40
|
+
* built-in guess, exactly as before. A Loop-driven provider only: a native session reports its own cost.
|
|
41
|
+
* @param {{in: number, out: number}|null} [opts.judgeRates] the same, for the judge's own key row
|
|
37
42
|
* @param {string|null} [opts.judgeModel] SOFTGREEN — WHICH model the judge provider drives
|
|
38
43
|
* (PRD item 32.1), resolved by the CALLER (`resolveJudge`, src/judged.js) and passed
|
|
39
44
|
* through untouched. Travels with `judgeProvider`; absent, a judged stage stops as a
|
|
@@ -204,7 +209,7 @@
|
|
|
204
209
|
* deliberately NOT in the resumable/checkpoint set (`src/reuse.js`) — an
|
|
205
210
|
* unknown in-flight state is not a known-safe resume point.
|
|
206
211
|
*/
|
|
207
|
-
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, draftSpentUsd, draftSpendComplete, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
|
|
212
|
+
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, draftSpentUsd, draftSpendComplete, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, resumable }: {
|
|
208
213
|
approvals: unknown;
|
|
209
214
|
workdir: string;
|
|
210
215
|
provider: any;
|
|
@@ -216,6 +221,14 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
|
|
|
216
221
|
}) => any) | undefined;
|
|
217
222
|
emit: (type: string, data?: object) => object;
|
|
218
223
|
providerFor?: ((tier: string) => any) | undefined;
|
|
224
|
+
rates?: {
|
|
225
|
+
in: number;
|
|
226
|
+
out: number;
|
|
227
|
+
} | null | undefined;
|
|
228
|
+
judgeRates?: {
|
|
229
|
+
in: number;
|
|
230
|
+
out: number;
|
|
231
|
+
} | null | undefined;
|
|
219
232
|
judgeModel?: string | null | undefined;
|
|
220
233
|
judgeProvider?: any;
|
|
221
234
|
capRuns?: number | undefined;
|
package/types/runlist.d.ts
CHANGED
|
@@ -13,7 +13,9 @@ export function runlistHome(override?: string): string;
|
|
|
13
13
|
*/
|
|
14
14
|
export function runlistPath(home?: string): string;
|
|
15
15
|
/**
|
|
16
|
-
* @typedef {{ at: string, runid: string, job: string, spine: string, patient: string|null, via: 'run-u'|'bundle'|'backfill' }} RunRow
|
|
16
|
+
* @typedef {{ at: string, runid: string, job: string, spine: string, patient: string|null, via: 'run-u'|'bundle'|'backfill', pid?: number, capUsd?: number }} RunRow
|
|
17
|
+
* `pid`/`capUsd` (optional; older and backfilled rows carry neither): the runner's process id and the
|
|
18
|
+
* leg's $ cap — what the monthly limit holds while that pid is a live bareloop runner (src/monthly.js).
|
|
17
19
|
*/
|
|
18
20
|
/**
|
|
19
21
|
* Append one row to the run list, mkdir -p'ing the home directory first.
|
|
@@ -42,19 +44,71 @@ export function appendRun(row: RunRow, opts?: {
|
|
|
42
44
|
appended: boolean;
|
|
43
45
|
reason?: "duplicate-runid";
|
|
44
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {{ runid: string, type: 'settled'|'released', by: string, at: string, reason?: string, spentUsd?: number, spendComplete?: boolean }} RunEvent
|
|
49
|
+
* A claim's write-back, appended (never rewritten) beside the run rows. `settled` = the run's claim on the
|
|
50
|
+
* monthly limit is over (`spentUsd`/`spendComplete` are its final figure, `by` is WHO wrote it: the run itself
|
|
51
|
+
* at its `job-end`, or the next run that found its process gone, `reason: 'process gone'`). `released` = the
|
|
52
|
+
* claim was taken and then given back unspent, its run never started. The month's totals keep reading real
|
|
53
|
+
* spend off the spines; an event only releases the hold and records who wrote it.
|
|
54
|
+
*/
|
|
55
|
+
/**
|
|
56
|
+
* Append one {@link RunEvent}. Not locked: one short line, one append. THROWS on IO failure.
|
|
57
|
+
* @param {RunEvent} event
|
|
58
|
+
* @param {{ home?: string }} [opts]
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
61
|
+
export function appendRunEvent(event: RunEvent, opts?: {
|
|
62
|
+
home?: string;
|
|
63
|
+
}): void;
|
|
45
64
|
/**
|
|
46
65
|
* Tolerant reader — reuses {@link parseJsonl} (never a second hand-rolled
|
|
47
66
|
* parser). An absent file reads as an empty list, not an error: a fresh
|
|
48
|
-
* install has never run `appendRun` yet.
|
|
67
|
+
* install has never run `appendRun` yet. FOLDS the file: `rows` are the RUNS
|
|
68
|
+
* only (an event line is never listed as a run, and a run whose claim was
|
|
69
|
+
* `released` never started, so it is not listed either); `events` are the
|
|
70
|
+
* write-backs, in file order.
|
|
49
71
|
* @param {{ home?: string }} [opts]
|
|
50
|
-
* @returns {{ rows: RunRow[], skipped: number }}
|
|
72
|
+
* @returns {{ rows: RunRow[], skipped: number, events: RunEvent[] }}
|
|
51
73
|
*/
|
|
52
74
|
export function readRunList(opts?: {
|
|
53
75
|
home?: string;
|
|
54
76
|
}): {
|
|
55
77
|
rows: RunRow[];
|
|
56
78
|
skipped: number;
|
|
79
|
+
events: RunEvent[];
|
|
57
80
|
};
|
|
81
|
+
/**
|
|
82
|
+
* Is this pid a live bareloop runner? `process.kill(pid, 0)` says whether the
|
|
83
|
+
* pid exists (EPERM = it exists, just not ours); `/proc/<pid>/cmdline` says whether it is a
|
|
84
|
+
* bareloop runner — a recycled pid that now belongs to some other program is NOT. A runner is
|
|
85
|
+
* a node executable (argv[0] is `node`, `nodejs`, `node22`, ...) with a later argv entry named
|
|
86
|
+
* `bareloop`, `bareloop.mjs`, `run-u.mjs` or `u-watchdog.mjs` (the basename, so the npm bin
|
|
87
|
+
* symlink counts), or a process whose argv[0] is itself one of those names. A non-node program
|
|
88
|
+
* that merely has such a name in its arguments (`nvim /x/bareloop`, `git -C /x/bareloop`) is not
|
|
89
|
+
* a runner. Residual, accepted: a recycled pid that is a node program with such an argument
|
|
90
|
+
* still reads as a runner. The wrong direction to err is "not a runner" on a real live run — it
|
|
91
|
+
* would release that run's cap. Where `/proc` is unreadable (not Linux, or not ours) the
|
|
92
|
+
* existence test alone decides: alive. The ONE spelling — the monthly limit's holds, and
|
|
93
|
+
* `--resume`'s old-pid check share it.
|
|
94
|
+
* @param {number} pid
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
97
|
+
export function isLiveRunner(pid: number): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* The ONE "is this run alive" rule, for the panel's glyph ([▶] vs [?]) and the monthly limit's hold. A
|
|
100
|
+
* row that carries its runner's `pid` is alive exactly while that pid is a live bareloop runner
|
|
101
|
+
* ({@link isLiveRunner}) however quiet its spine; only a row with NO pid (an older row) falls back to
|
|
102
|
+
* the spine-mtime rule above. A run that has ENDED (a `job-end` on its spine) is a different fact the
|
|
103
|
+
* callers read off the spine itself — this answers "is a process still running it", nothing more.
|
|
104
|
+
* @param {{ pid?: number, spine: string }} row
|
|
105
|
+
* @param {number} [nowMs] the clock for the mtime fallback
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
export function runIsAlive(row: {
|
|
109
|
+
pid?: number;
|
|
110
|
+
spine: string;
|
|
111
|
+
}, nowMs?: number): boolean;
|
|
58
112
|
/**
|
|
59
113
|
* `bareloop runs backfill <dir>` — recursively scan `dir` (bounded to
|
|
60
114
|
* {@link MAX_BACKFILL_DEPTH} levels, never into `node_modules`/`.git`,
|
|
@@ -103,6 +157,10 @@ export function backfillRuns(dir: string, opts?: {
|
|
|
103
157
|
export function formatRunRow(row: RunRow): string;
|
|
104
158
|
export const DIED_MTIME_MS: number;
|
|
105
159
|
export const MAX_BACKFILL_DEPTH: 6;
|
|
160
|
+
/**
|
|
161
|
+
* `pid`/`capUsd` (optional; older and backfilled rows carry neither): the runner's process id and the
|
|
162
|
+
* leg's $ cap — what the monthly limit holds while that pid is a live bareloop runner (src/monthly.js).
|
|
163
|
+
*/
|
|
106
164
|
export type RunRow = {
|
|
107
165
|
at: string;
|
|
108
166
|
runid: string;
|
|
@@ -110,4 +168,22 @@ export type RunRow = {
|
|
|
110
168
|
spine: string;
|
|
111
169
|
patient: string | null;
|
|
112
170
|
via: "run-u" | "bundle" | "backfill";
|
|
171
|
+
pid?: number;
|
|
172
|
+
capUsd?: number;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* A claim's write-back, appended (never rewritten) beside the run rows. `settled` = the run's claim on the
|
|
176
|
+
* monthly limit is over (`spentUsd`/`spendComplete` are its final figure, `by` is WHO wrote it: the run itself
|
|
177
|
+
* at its `job-end`, or the next run that found its process gone, `reason: 'process gone'`). `released` = the
|
|
178
|
+
* claim was taken and then given back unspent, its run never started. The month's totals keep reading real
|
|
179
|
+
* spend off the spines; an event only releases the hold and records who wrote it.
|
|
180
|
+
*/
|
|
181
|
+
export type RunEvent = {
|
|
182
|
+
runid: string;
|
|
183
|
+
type: "settled" | "released";
|
|
184
|
+
by: string;
|
|
185
|
+
at: string;
|
|
186
|
+
reason?: string;
|
|
187
|
+
spentUsd?: number;
|
|
188
|
+
spendComplete?: boolean;
|
|
113
189
|
};
|
package/types/source.d.ts
CHANGED
|
@@ -72,15 +72,20 @@ export function looksLikeRepoSource(source: string): boolean;
|
|
|
72
72
|
* never hands a repo source's destination to `copyOut` — wiring it into
|
|
73
73
|
* `writeScope` is M3/M4's job, not this one.
|
|
74
74
|
* @param {{source: string, into: string, destination?: string,
|
|
75
|
-
* fetchTimeoutMs?: number
|
|
76
|
-
* callers omit it and get
|
|
75
|
+
* fetchTimeoutMs?: number, afterScan?: () => (void|Promise<void>)}} args
|
|
76
|
+
* `fetchTimeoutMs` is test-only — production callers omit it and get
|
|
77
|
+
* `PROVIDER_TIMEOUT_MS`. `afterScan` is a test seam too: called exactly once,
|
|
78
|
+
* after the secret scan finished and before the freeze loop re-reads any
|
|
79
|
+
* file's bytes, so a test can swap a file inside that window
|
|
80
|
+
* deterministically. Production never sets it; absent, behaviour is unchanged.
|
|
77
81
|
* @returns {Promise<{stop: null, into: string, tree: string, manifestPath: string, manifest: object}|SourceRefusal>}
|
|
78
82
|
*/
|
|
79
|
-
export function prepareSource({ source, into, destination, fetchTimeoutMs }: {
|
|
83
|
+
export function prepareSource({ source, into, destination, fetchTimeoutMs, afterScan }: {
|
|
80
84
|
source: string;
|
|
81
85
|
into: string;
|
|
82
86
|
destination?: string;
|
|
83
87
|
fetchTimeoutMs?: number;
|
|
88
|
+
afterScan?: () => (void | Promise<void>);
|
|
84
89
|
}): Promise<{
|
|
85
90
|
stop: null;
|
|
86
91
|
into: string;
|