bareloop 0.31.0 → 0.33.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 +151 -0
- package/README.md +4 -1
- package/bareloop.context.md +232 -85
- package/bin/bareloop.mjs +0 -1
- package/package.json +1 -1
- package/src/authorflow.js +5 -3
- package/src/authorjob.js +3 -1
- package/src/authorrun.js +49 -11
- package/src/authorscout.js +10 -6
- package/src/bundlerun.js +266 -0
- package/src/cli.js +35 -357
- package/src/config.js +135 -0
- package/src/interviewrun.js +13 -4
- package/src/invoke.js +22 -0
- package/src/judged.js +5 -3
- package/src/keysfile.js +156 -0
- package/src/ledger.js +26 -0
- package/src/monthly.js +422 -0
- package/src/panel/authorroutes.js +61 -16
- package/src/panel/authorsession.js +46 -28
- package/src/panel/index.html +287 -13
- package/src/panel/server.js +58 -51
- package/src/panel/settingsroutes.js +230 -0
- package/src/planrun.js +7 -3
- package/src/providerrows.js +301 -0
- package/src/providers.js +3 -1
- package/src/run.js +7 -2
- package/src/runlist.js +92 -7
- package/src/source.js +8 -3
- package/src/u-watchdog.mjs +296 -0
- package/src/userrun.js +230 -54
- package/types/authorflow.d.ts +8 -2
- package/types/authorjob.d.ts +6 -1
- package/types/authorrun.d.ts +5 -0
- package/types/authorscout.d.ts +11 -2
- package/types/bundlerun.d.ts +26 -0
- package/types/cli.d.ts +2 -1
- package/types/config.d.ts +48 -0
- package/types/interviewrun.d.ts +5 -0
- package/types/invoke.d.ts +8 -0
- package/types/judged.d.ts +5 -1
- package/types/keysfile.d.ts +90 -0
- package/types/ledger.d.ts +14 -0
- package/types/monthly.d.ts +227 -0
- package/types/panel/authorroutes.d.ts +4 -2
- package/types/panel/authorsession.d.ts +7 -15
- package/types/panel/server.d.ts +10 -3
- package/types/panel/settingsroutes.d.ts +14 -0
- package/types/planrun.d.ts +13 -1
- package/types/providerrows.d.ts +229 -0
- package/types/run.d.ts +14 -1
- package/types/runlist.d.ts +66 -3
- package/types/source.d.ts +8 -3
- package/types/userrun.d.ts +54 -0
package/types/authorrun.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {object} Deps
|
|
3
3
|
* @property {Record<string,string|undefined>} [env]
|
|
4
|
+
* @property {string} [keysHome] P4a — test seam: load this home's `.env` keys file even when `env` is injected.
|
|
4
5
|
* @property {NodeJS.ReadStream} [stdin]
|
|
5
6
|
* @property {NodeJS.WritableStream} [stdout]
|
|
6
7
|
* @property {NodeJS.WritableStream} [stderr]
|
|
@@ -23,6 +24,10 @@
|
|
|
23
24
|
export function main(argv: string[], deps?: Deps): Promise<number>;
|
|
24
25
|
export type Deps = {
|
|
25
26
|
env?: Record<string, string | undefined> | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* P4a — test seam: load this home's `.env` keys file even when `env` is injected.
|
|
29
|
+
*/
|
|
30
|
+
keysHome?: string | undefined;
|
|
26
31
|
stdin?: NodeJS.ReadStream | undefined;
|
|
27
32
|
stdout?: NodeJS.WritableStream | undefined;
|
|
28
33
|
stderr?: NodeJS.WritableStream | undefined;
|
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>;
|
package/types/cli.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* code, so `bin/bareloop.mjs` can set `process.exitCode` and let node flush
|
|
4
4
|
* queued stdout on its own (F-something: `process.exit()` can discard it).
|
|
5
5
|
* @param {string[]} argv
|
|
6
|
-
* @param {{ env?: Record<string,string|undefined>, stdout?: any, stderr?: any, cwd?: string, provider?: any, providerFor?: any, judgeProvider?: any, judgeModel?: string|null, now?: () => number, stdin?: any, runlistHome?: string }} deps
|
|
6
|
+
* @param {{ env?: Record<string,string|undefined>, stdout?: any, stderr?: any, cwd?: string, provider?: any, providerFor?: any, judgeProvider?: any, judgeModel?: string|null, now?: () => number, stdin?: any, runlistHome?: string, keysHome?: string }} deps
|
|
7
7
|
* @returns {Promise<number>}
|
|
8
8
|
*/
|
|
9
9
|
export function main(argv: string[], deps?: {
|
|
@@ -18,4 +18,5 @@ export function main(argv: string[], deps?: {
|
|
|
18
18
|
now?: () => number;
|
|
19
19
|
stdin?: any;
|
|
20
20
|
runlistHome?: string;
|
|
21
|
+
keysHome?: string;
|
|
21
22
|
}): Promise<number>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string} [home] test seam; production never passes it
|
|
3
|
+
* @returns {string} `<home>/config.json`
|
|
4
|
+
*/
|
|
5
|
+
export function configPath(home?: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} ConfigRead
|
|
8
|
+
* @property {Record<string, any>} config the parsed document ({} when missing/unreadable)
|
|
9
|
+
* @property {boolean} exists
|
|
10
|
+
* @property {string|null} problem plain words when the file is there but unreadable
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* @param {{ home?: string }} [opts]
|
|
14
|
+
* @returns {ConfigRead}
|
|
15
|
+
*/
|
|
16
|
+
export function readConfig(opts?: {
|
|
17
|
+
home?: string;
|
|
18
|
+
}): ConfigRead;
|
|
19
|
+
/**
|
|
20
|
+
* Apply a patch and write. `null` deletes a field. Top-level fields shallow-merge;
|
|
21
|
+
* `keys` merges per key variable name, then per field. Validated before anything is
|
|
22
|
+
* written; nothing is written on a refusal.
|
|
23
|
+
*
|
|
24
|
+
* Patch fields understood: `monthlyLimitUsd` (finite > 0), `anthropicBalanceNote`
|
|
25
|
+
* (finite >= 0), `keys.<ENV NAME>.{name,shape,baseUrl}` (Name = a model id, shape = a provider table name, baseUrl = blank or http(s)). Any other
|
|
26
|
+
* field is passed through untouched (kept), but the whole document is swept for
|
|
27
|
+
* secret-shaped strings first.
|
|
28
|
+
* @param {Record<string, any>} patch
|
|
29
|
+
* @param {{ home?: string }} [opts]
|
|
30
|
+
* @returns {Record<string, any>} the document as written
|
|
31
|
+
*/
|
|
32
|
+
export function updateConfig(patch: Record<string, any>, opts?: {
|
|
33
|
+
home?: string;
|
|
34
|
+
}): Record<string, any>;
|
|
35
|
+
/** A plain-words config failure (bad value, secret-shaped string, unreadable file). */
|
|
36
|
+
export class ConfigError extends Error {
|
|
37
|
+
}
|
|
38
|
+
export type ConfigRead = {
|
|
39
|
+
/**
|
|
40
|
+
* the parsed document ({} when missing/unreadable)
|
|
41
|
+
*/
|
|
42
|
+
config: Record<string, any>;
|
|
43
|
+
exists: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* plain words when the file is there but unreadable
|
|
46
|
+
*/
|
|
47
|
+
problem: string | null;
|
|
48
|
+
};
|
package/types/interviewrun.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {object} Deps
|
|
3
3
|
* @property {Record<string,string|undefined>} [env]
|
|
4
|
+
* @property {string} [keysHome] P4a — test seam: load this home's `.env` keys file even when `env` is injected.
|
|
4
5
|
* @property {NodeJS.ReadStream} [stdin]
|
|
5
6
|
* @property {NodeJS.WritableStream} [stdout]
|
|
6
7
|
* @property {NodeJS.WritableStream} [stderr]
|
|
@@ -25,6 +26,10 @@
|
|
|
25
26
|
export function main(argv: string[], deps?: Deps): Promise<number>;
|
|
26
27
|
export type Deps = {
|
|
27
28
|
env?: Record<string, string | undefined> | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* P4a — test seam: load this home's `.env` keys file even when `env` is injected.
|
|
31
|
+
*/
|
|
32
|
+
keysHome?: string | undefined;
|
|
28
33
|
stdin?: NodeJS.ReadStream | undefined;
|
|
29
34
|
stdout?: NodeJS.WritableStream | undefined;
|
|
30
35
|
stderr?: NodeJS.WritableStream | undefined;
|
|
@@ -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
|
@@ -382,9 +382,13 @@ export const JUDGE_RULE_IDS: readonly string[];
|
|
|
382
382
|
export const CALIBRATION_SIZE: 10;
|
|
383
383
|
/** the two verdicts a case may expect — `decide()`'s own closed set */
|
|
384
384
|
export const CASE_VERDICTS: readonly string[];
|
|
385
|
-
export function defaultJudgeLoop({ provider, system }: {
|
|
385
|
+
export function defaultJudgeLoop({ provider, system, rates }: {
|
|
386
386
|
provider: any;
|
|
387
387
|
system: string;
|
|
388
|
+
rates?: {
|
|
389
|
+
in: number;
|
|
390
|
+
out: number;
|
|
391
|
+
} | null;
|
|
388
392
|
}): import("bare-agent").Loop;
|
|
389
393
|
export type RuleRed = {
|
|
390
394
|
fn: string;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `~/.config/bareloop` (or the injected override) — same home every other
|
|
3
|
+
* per-person file (runs.jsonl, config.json) lives in.
|
|
4
|
+
* @param {string} [home] test seam; production never passes it
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function keysHome(home?: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} [home]
|
|
10
|
+
* @returns {string} `<home>/.env`
|
|
11
|
+
*/
|
|
12
|
+
export function keysFilePath(home?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Parse the file's text. Lines that are not `NAME=value` are skipped silently
|
|
15
|
+
* (never echoed — a malformed line may hold a secret).
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @returns {Record<string,string>}
|
|
18
|
+
*/
|
|
19
|
+
export function parseKeysText(text: string): Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* The NAMES in the file that carry a non-empty value, in file order — the Providers rows
|
|
22
|
+
* (an empty preset line is not a row). Names only; a missing/unreadable file = none.
|
|
23
|
+
* @param {string} [home]
|
|
24
|
+
* @returns {string[]}
|
|
25
|
+
*/
|
|
26
|
+
export function filledKeyNames(home?: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Create the keys file with the five empty preset lines when — and only when — it does not
|
|
29
|
+
* exist (mode 600). An existing file is never touched.
|
|
30
|
+
* @param {readonly string[]} presetNames the names to seed, in order
|
|
31
|
+
* @param {string} [home]
|
|
32
|
+
* @returns {boolean} true if it was created
|
|
33
|
+
*/
|
|
34
|
+
export function ensureKeysFile(presetNames: readonly string[], home?: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} KeysLoad
|
|
37
|
+
* @property {boolean} exists the file is there
|
|
38
|
+
* @property {string[]} names the NAMES the file defines (sorted; never values)
|
|
39
|
+
* @property {string|null} warning a plain-words warning (file readable by others, or
|
|
40
|
+
* unreadable), else null — a warning, never a refusal
|
|
41
|
+
* @property {Record<string,string|undefined>} env shell env with the file filling the
|
|
42
|
+
* names the shell does not set. THE ONLY place values leave this module.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Load the keys file over an env. Missing file is fine (no names, env unchanged).
|
|
46
|
+
* @param {{ env?: Record<string,string|undefined>, home?: string }} [opts]
|
|
47
|
+
* @returns {KeysLoad}
|
|
48
|
+
*/
|
|
49
|
+
export function loadKeysEnv(opts?: {
|
|
50
|
+
env?: Record<string, string | undefined>;
|
|
51
|
+
home?: string;
|
|
52
|
+
}): KeysLoad;
|
|
53
|
+
/**
|
|
54
|
+
* The door helper the CLI and the panel call: load the file only when the caller is
|
|
55
|
+
* on the REAL environment (no injected env) or names a keys home explicitly — so a
|
|
56
|
+
* test that injects `env` alone never reads the real `~/.config/bareloop/.env`.
|
|
57
|
+
* @param {{ env?: Record<string,string|undefined>, keysHome?: string }} deps
|
|
58
|
+
* @returns {KeysLoad & { env: Record<string,string|undefined>, home: string|undefined }}
|
|
59
|
+
* `home` = the home the file was loaded from (the real `~/.config/bareloop` when none was
|
|
60
|
+
* injected) — or `undefined` when loading was skipped (injected env, no keysHome). The
|
|
61
|
+
* door passes it on so config.json (src/config.js) is read from the SAME place, and never
|
|
62
|
+
* from the real home in a test.
|
|
63
|
+
*/
|
|
64
|
+
export function keysForDoor(deps: {
|
|
65
|
+
env?: Record<string, string | undefined>;
|
|
66
|
+
keysHome?: string;
|
|
67
|
+
}): KeysLoad & {
|
|
68
|
+
env: Record<string, string | undefined>;
|
|
69
|
+
home: string | undefined;
|
|
70
|
+
};
|
|
71
|
+
export type KeysLoad = {
|
|
72
|
+
/**
|
|
73
|
+
* the file is there
|
|
74
|
+
*/
|
|
75
|
+
exists: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* the NAMES the file defines (sorted; never values)
|
|
78
|
+
*/
|
|
79
|
+
names: string[];
|
|
80
|
+
/**
|
|
81
|
+
* a plain-words warning (file readable by others, or
|
|
82
|
+
* unreadable), else null — a warning, never a refusal
|
|
83
|
+
*/
|
|
84
|
+
warning: string | null;
|
|
85
|
+
/**
|
|
86
|
+
* shell env with the file filling the
|
|
87
|
+
* names the shell does not set. THE ONLY place values leave this module.
|
|
88
|
+
*/
|
|
89
|
+
env: Record<string, string | undefined>;
|
|
90
|
+
};
|
package/types/ledger.d.ts
CHANGED
|
@@ -81,6 +81,20 @@ export function spendProvenance(events: any[]): {
|
|
|
81
81
|
unpriced: ProvenanceBucket;
|
|
82
82
|
unknown: ProvenanceBucket;
|
|
83
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* The priced-rounds spend floor + first-record→last-record wall floor,
|
|
86
|
+
* derived straight off the spine's raw records — the same derivation a
|
|
87
|
+
* died run's "at least …" figures use, factored out so build item 6
|
|
88
|
+
* (2026-09-28) can hand a genuinely-still-running spine the identical
|
|
89
|
+
* floor instead of "unknown" on the Run tab, without a second copy of this
|
|
90
|
+
* math.
|
|
91
|
+
* @param {any[]} records raw parsed spine records
|
|
92
|
+
* @returns {{spendFloorUsd: number|null, wallFloorMs: number|null}}
|
|
93
|
+
*/
|
|
94
|
+
export function floorsFromRecords(records: any[]): {
|
|
95
|
+
spendFloorUsd: number | null;
|
|
96
|
+
wallFloorMs: number | null;
|
|
97
|
+
};
|
|
84
98
|
/** severity order, worst-first (the fold's display order; frequency ranks within) */
|
|
85
99
|
export const LEDGER_CLASSES: readonly string[];
|
|
86
100
|
/**
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One run's own spend, off its spine records: this LEG's figure (never the chain fold),
|
|
3
|
+
* plus the drafting spend the run was signed with (counted once — on the first leg only).
|
|
4
|
+
* @param {any[]} records
|
|
5
|
+
* @returns {{ usd: number, complete: boolean }}
|
|
6
|
+
*/
|
|
7
|
+
export function legSpend(records: any[]): {
|
|
8
|
+
usd: number;
|
|
9
|
+
complete: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* One run's wall time off its spine: job-start -> job-end when the run ended (exact), else the
|
|
13
|
+
* first -> last record floor (the SAME floor a died / still-running run's "at least" wall uses,
|
|
14
|
+
* `floorsFromRecords`). `ms` null = unknown — never 0.
|
|
15
|
+
* @param {any[]} records
|
|
16
|
+
* @returns {{ ms: number|null, complete: boolean }}
|
|
17
|
+
*/
|
|
18
|
+
export function legWall(records: any[]): {
|
|
19
|
+
ms: number | null;
|
|
20
|
+
complete: boolean;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Tokens a spine's rounds processed (input + output + cache read + cache write — all four,
|
|
24
|
+
* so a cache-heavy run is not under-reported), off the real `usage` field names.
|
|
25
|
+
* @param {any[]} records
|
|
26
|
+
* @returns {number}
|
|
27
|
+
*/
|
|
28
|
+
export function legTokens(records: any[]): number;
|
|
29
|
+
/**
|
|
30
|
+
* Every listed run as one {@link Leg}. The ONE reader the month total, the all-time total
|
|
31
|
+
* and the per-provider figures share.
|
|
32
|
+
* @param {{ home?: string, now?: () => number, aboveRunid?: string }} [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)
|
|
33
|
+
* @returns {Leg[]}
|
|
34
|
+
*/
|
|
35
|
+
export function readLegs(opts?: {
|
|
36
|
+
home?: string;
|
|
37
|
+
now?: () => number;
|
|
38
|
+
aboveRunid?: string;
|
|
39
|
+
}): Leg[];
|
|
40
|
+
/**
|
|
41
|
+
* @param {{ home?: string, now?: () => number, aboveRunid?: string }} [opts] `aboveRunid`: see {@link readLegs}
|
|
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)
|
|
43
|
+
*/
|
|
44
|
+
export function monthSpend(opts?: {
|
|
45
|
+
home?: string;
|
|
46
|
+
now?: () => number;
|
|
47
|
+
aboveRunid?: string;
|
|
48
|
+
}): {
|
|
49
|
+
usd: number;
|
|
50
|
+
atLeast: boolean;
|
|
51
|
+
runs: number;
|
|
52
|
+
reservedUsd: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The Money tab's figures: all-time and this-month spend, and per-provider breakdown
|
|
56
|
+
* (rows from `rowIdFor`; a run on no known row lands under its own provider name, never
|
|
57
|
+
* pooled into another row). Any leg with unknown spend makes the figure it belongs to
|
|
58
|
+
* an "at least".
|
|
59
|
+
* @param {{ home?: string, now?: () => number, rows?: readonly import('./providerrows.js').KeyRow[] }} [opts] `rows` = the Providers rows: when given, `tokensByRow` sums each row's tokens
|
|
60
|
+
* @returns {{ tokensByRow: Record<string, number>, total: {usd: number, atLeast: boolean, tokens: number}, month: {usd: number, atLeast: boolean, tokens: number},
|
|
61
|
+
* byProvider: Record<string, {label: string, monthUsd: number, monthAtLeast: boolean, totalUsd: number, totalAtLeast: boolean, tokens: number, vouchedRounds: number, otherRounds: number, monthWallMs: number|null, monthWallAtLeast: boolean, totalWallMs: number|null, totalWallAtLeast: boolean}> }}
|
|
62
|
+
*/
|
|
63
|
+
export function spendSummary(opts?: {
|
|
64
|
+
home?: string;
|
|
65
|
+
now?: () => number;
|
|
66
|
+
rows?: readonly import("./providerrows.js").KeyRow[];
|
|
67
|
+
}): {
|
|
68
|
+
tokensByRow: Record<string, number>;
|
|
69
|
+
total: {
|
|
70
|
+
usd: number;
|
|
71
|
+
atLeast: boolean;
|
|
72
|
+
tokens: number;
|
|
73
|
+
};
|
|
74
|
+
month: {
|
|
75
|
+
usd: number;
|
|
76
|
+
atLeast: boolean;
|
|
77
|
+
tokens: number;
|
|
78
|
+
};
|
|
79
|
+
byProvider: Record<string, {
|
|
80
|
+
label: string;
|
|
81
|
+
monthUsd: number;
|
|
82
|
+
monthAtLeast: boolean;
|
|
83
|
+
totalUsd: number;
|
|
84
|
+
totalAtLeast: boolean;
|
|
85
|
+
tokens: number;
|
|
86
|
+
vouchedRounds: number;
|
|
87
|
+
otherRounds: number;
|
|
88
|
+
monthWallMs: number | null;
|
|
89
|
+
monthWallAtLeast: boolean;
|
|
90
|
+
totalWallMs: number | null;
|
|
91
|
+
totalWallAtLeast: boolean;
|
|
92
|
+
}>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* @typedef {object} MonthlyRoom
|
|
96
|
+
* @property {boolean} ok
|
|
97
|
+
* @property {number|null} leftUsd what is left this month (null = no limit set)
|
|
98
|
+
* @property {number|null} limitUsd the person's monthly limit (null = none)
|
|
99
|
+
* @property {boolean} atLeast the month's spend is a floor, so `leftUsd` is a ceiling
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* The person's monthly limit off a parsed config.json: key absent (or null) = no limit (null);
|
|
103
|
+
* a number above 0 = the limit; ANY other value present is a broken setting and throws
|
|
104
|
+
* `ConfigError` — a limit the file names but this code cannot read never reads as "no limit".
|
|
105
|
+
* @param {Record<string, any>} config
|
|
106
|
+
* @param {string} [home]
|
|
107
|
+
* @returns {number|null}
|
|
108
|
+
*/
|
|
109
|
+
export function monthlyLimitOf(config: Record<string, any>, home?: string): number | null;
|
|
110
|
+
/**
|
|
111
|
+
* 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
|
+
* number above 0 throws `ConfigError`.
|
|
113
|
+
* Compared in whole cents. Also throws `ConfigError` when config.json is unreadable — a gate
|
|
114
|
+
* whose own instrument is broken refuses; it never silently runs with no limit.
|
|
115
|
+
* @param {{ capUsd: number, home?: string, now?: () => number, aboveRunid?: string }} args `aboveRunid`: count only the claims listed above that run (a claim in progress)
|
|
116
|
+
* @returns {MonthlyRoom}
|
|
117
|
+
*/
|
|
118
|
+
export function checkMonthlyRoom({ capUsd, home, now, aboveRunid }: {
|
|
119
|
+
capUsd: number;
|
|
120
|
+
home?: string;
|
|
121
|
+
now?: () => number;
|
|
122
|
+
aboveRunid?: string;
|
|
123
|
+
}): MonthlyRoom;
|
|
124
|
+
/**
|
|
125
|
+
* The ONE refusal text, everywhere (CLI, panel cap note, panel Sign refusal).
|
|
126
|
+
* @param {MonthlyRoom} room
|
|
127
|
+
* @returns {string|null} null when the run fits
|
|
128
|
+
*/
|
|
129
|
+
export function monthlyRefusalText(room: MonthlyRoom): string | null;
|
|
130
|
+
/**
|
|
131
|
+
* Close every claim ABOVE `aboveRunid` (file order) whose process is gone: a pid-bearing run row with no
|
|
132
|
+
* `settled` entry whose pid is not a live bareloop runner gets `{ type: 'settled', by: <closer>, reason:
|
|
133
|
+
* 'process gone', spentUsd: <its spine's floor>, spendComplete: false }` appended — so every claim gets a
|
|
134
|
+
* write-back, attributed. Two runs may both close the same dead claim; readers take the FIRST note.
|
|
135
|
+
* @param {{ home?: string, by: string, aboveRunid?: string }} args `by` = the run doing the closing
|
|
136
|
+
* @returns {number} how many notes were appended
|
|
137
|
+
*/
|
|
138
|
+
export function settleDeadClaims({ home, by, aboveRunid }: {
|
|
139
|
+
home?: string;
|
|
140
|
+
by: string;
|
|
141
|
+
aboveRunid?: string;
|
|
142
|
+
}): number;
|
|
143
|
+
/**
|
|
144
|
+
* The CLAIM (hamr 2026-09-30: whoever runs first claims the limit and holds it; no lock file, records
|
|
145
|
+
* only): the run APPENDS its row first (`row` carries its `pid` and `capUsd`), then reads the run list.
|
|
146
|
+
* Only the claims ABOVE its own row count — a live one is held at its cap (a dead one is closed with an
|
|
147
|
+
* attributed note and counts its real spend); rows below yield to it. If `capUsd` fits, it runs; if not
|
|
148
|
+
* it appends `{type:'released', reason:'refused'}` and reports the refusal, nothing spent. No limit set =
|
|
149
|
+
* no claim (`claimed: false`; the caller lists the run as it always did). An unreadable config, or a row
|
|
150
|
+
* that cannot be written, throws `ConfigError` — the gate refuses, never reads "no limit".
|
|
151
|
+
* @param {{ row: import('./runlist.js').RunRow, capUsd: number, home?: string, now?: () => number }} args
|
|
152
|
+
* @returns {{ room: MonthlyRoom, claimed: boolean }}
|
|
153
|
+
*/
|
|
154
|
+
export function claimRun({ row, capUsd, home, now }: {
|
|
155
|
+
row: import("./runlist.js").RunRow;
|
|
156
|
+
capUsd: number;
|
|
157
|
+
home?: string;
|
|
158
|
+
now?: () => number;
|
|
159
|
+
}): {
|
|
160
|
+
room: MonthlyRoom;
|
|
161
|
+
claimed: boolean;
|
|
162
|
+
};
|
|
163
|
+
export { isLiveRunner };
|
|
164
|
+
export type Leg = {
|
|
165
|
+
/**
|
|
166
|
+
* when the run was listed
|
|
167
|
+
*/
|
|
168
|
+
at: Date;
|
|
169
|
+
/**
|
|
170
|
+
* job-start's provider (null = an older spine that carried none)
|
|
171
|
+
*/
|
|
172
|
+
provider: string | null;
|
|
173
|
+
baseUrl: string | null;
|
|
174
|
+
/**
|
|
175
|
+
* this leg's spend (a floor when `!complete`)
|
|
176
|
+
*/
|
|
177
|
+
usd: number;
|
|
178
|
+
/**
|
|
179
|
+
* every figure behind `usd` is known
|
|
180
|
+
*/
|
|
181
|
+
complete: boolean;
|
|
182
|
+
tokens: number;
|
|
183
|
+
/**
|
|
184
|
+
* job-start's model (older spines carry a model but no provider)
|
|
185
|
+
*/
|
|
186
|
+
model: string | null;
|
|
187
|
+
/**
|
|
188
|
+
* this leg's wall time (null = unknown; a floor when `!wallComplete`)
|
|
189
|
+
*/
|
|
190
|
+
wallMs: number | null;
|
|
191
|
+
/**
|
|
192
|
+
* the wall is the exact job-start -> job-end span
|
|
193
|
+
*/
|
|
194
|
+
wallComplete: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* rounds priced by a rate somebody vouched for (`provider`/`caller`)
|
|
197
|
+
*/
|
|
198
|
+
vouchedRounds: number;
|
|
199
|
+
/**
|
|
200
|
+
* every other round: a built-in guess, unpriced, or no provenance on record
|
|
201
|
+
*/
|
|
202
|
+
otherRounds: number;
|
|
203
|
+
/**
|
|
204
|
+
* what the refusal check counts: `usd`, or the leg's cap when the run is in flight
|
|
205
|
+
*/
|
|
206
|
+
reservedUsd: number;
|
|
207
|
+
/**
|
|
208
|
+
* the spine was missing/unreadable or the listed date was bad (spend unknown)
|
|
209
|
+
*/
|
|
210
|
+
unreadable: boolean;
|
|
211
|
+
};
|
|
212
|
+
export type MonthlyRoom = {
|
|
213
|
+
ok: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* what is left this month (null = no limit set)
|
|
216
|
+
*/
|
|
217
|
+
leftUsd: number | null;
|
|
218
|
+
/**
|
|
219
|
+
* the person's monthly limit (null = none)
|
|
220
|
+
*/
|
|
221
|
+
limitUsd: number | null;
|
|
222
|
+
/**
|
|
223
|
+
* the month's spend is a floor, so `leftUsd` is a ceiling
|
|
224
|
+
*/
|
|
225
|
+
atLeast: boolean;
|
|
226
|
+
};
|
|
227
|
+
import { isLiveRunner } from './runlist.js';
|
|
@@ -25,7 +25,7 @@ export function checkHumanGuard(req: import("node:http").IncomingMessage, { toke
|
|
|
25
25
|
* deliberately has none of).
|
|
26
26
|
* @param {{ port: number, token: string, env?: Record<string,string|undefined>,
|
|
27
27
|
* sessionsRoot?: string, spawnFn?: typeof realSpawn, bareloopBin?: string,
|
|
28
|
-
* jobsDir?: string, fetchImpl?: typeof fetch }} opts
|
|
28
|
+
* jobsDir?: string, fetchImpl?: typeof fetch, home?: string }} opts
|
|
29
29
|
*/
|
|
30
30
|
export function createAuthorRoutes(opts: {
|
|
31
31
|
port: number;
|
|
@@ -36,6 +36,7 @@ export function createAuthorRoutes(opts: {
|
|
|
36
36
|
bareloopBin?: string;
|
|
37
37
|
jobsDir?: string;
|
|
38
38
|
fetchImpl?: typeof fetch;
|
|
39
|
+
home?: string;
|
|
39
40
|
}): {
|
|
40
41
|
handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
|
|
41
42
|
sessions: Map<string, any>;
|
|
@@ -48,7 +49,7 @@ export function createAuthorRoutes(opts: {
|
|
|
48
49
|
* it to sign"), and this is where it is checked, not trusted.
|
|
49
50
|
* @param {ReturnType<typeof createSession>} session
|
|
50
51
|
* @param {string} claimedHash
|
|
51
|
-
* @param {{env: any, spawnFn: typeof realSpawn, bareloopBin: string, sessionsRoot?: string}} o
|
|
52
|
+
* @param {{env: any, spawnFn: typeof realSpawn, bareloopBin: string, sessionsRoot?: string, home?: string}} o
|
|
52
53
|
* @returns {{ok: boolean, error?: string, job?: string}}
|
|
53
54
|
*/
|
|
54
55
|
export function signRun(session: ReturnType<typeof createSession>, claimedHash: string, o: {
|
|
@@ -56,6 +57,7 @@ export function signRun(session: ReturnType<typeof createSession>, claimedHash:
|
|
|
56
57
|
spawnFn: typeof realSpawn;
|
|
57
58
|
bareloopBin: string;
|
|
58
59
|
sessionsRoot?: string;
|
|
60
|
+
home?: string;
|
|
59
61
|
}): {
|
|
60
62
|
ok: boolean;
|
|
61
63
|
error?: string;
|
|
@@ -16,9 +16,13 @@ export function jobNameTaken(name: string, opts?: {
|
|
|
16
16
|
* creates a session or spends anything — never inside the async pipeline,
|
|
17
17
|
* so a bad card refuses synchronously, in the same HTTP response.
|
|
18
18
|
* @param {any} card
|
|
19
|
+
* @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
|
|
19
20
|
* @returns {{ok: true}|{ok: false, error: string}}
|
|
20
21
|
*/
|
|
21
|
-
export function validateJobCard(card: any
|
|
22
|
+
export function validateJobCard(card: any, opts?: {
|
|
23
|
+
jobsDir?: string;
|
|
24
|
+
rows?: readonly import("../providerrows.js").KeyRow[];
|
|
25
|
+
}): {
|
|
22
26
|
ok: true;
|
|
23
27
|
} | {
|
|
24
28
|
ok: false;
|
|
@@ -30,7 +34,7 @@ export function validateJobCard(card: any): {
|
|
|
30
34
|
* is the ONE thing a person answers next; `answer()`/`send()`/`revise()`/
|
|
31
35
|
* `signPrepare()` are the only ways in.
|
|
32
36
|
* @param {any} card the validated job card (see {@link validateJobCard})
|
|
33
|
-
* @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, timeoutMs?: number,
|
|
37
|
+
* @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
|
|
34
38
|
* scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
|
|
35
39
|
* prepareSigningFn?: Function}} [deps] the last five are TEST SEAMS ONLY — see the note
|
|
36
40
|
* just above where each is read, below.
|
|
@@ -39,6 +43,7 @@ export function validateJobCard(card: any): {
|
|
|
39
43
|
export function createSession(card: any, deps?: {
|
|
40
44
|
env?: Record<string, string | undefined>;
|
|
41
45
|
sessionsRoot?: string;
|
|
46
|
+
home?: string;
|
|
42
47
|
timeoutMs?: number;
|
|
43
48
|
scout?: any;
|
|
44
49
|
generate?: Function;
|
|
@@ -46,16 +51,3 @@ export function createSession(card: any, deps?: {
|
|
|
46
51
|
authorFn?: Function;
|
|
47
52
|
prepareSigningFn?: Function;
|
|
48
53
|
}): any;
|
|
49
|
-
/**
|
|
50
|
-
* The job card's "Model" select — a fixed, small, source-grounded mapping
|
|
51
|
-
* from a display id to the `{provider, baseUrl?}` pair `src/interviewrun.js`
|
|
52
|
-
* itself documents (that script's own `--provider openai-api --base-url
|
|
53
|
-
* https://api.deepseek.com/v1` usage line for DeepSeek): the panel builds no
|
|
54
|
-
* provider table of its own, this is a UI convenience over the SAME two
|
|
55
|
-
* provider identities the CLI already resolves through `resolveProvider`.
|
|
56
|
-
* @type {Readonly<Record<string, {provider: string, baseUrl?: string}>>}
|
|
57
|
-
*/
|
|
58
|
-
export const MODEL_OPTIONS: Readonly<Record<string, {
|
|
59
|
-
provider: string;
|
|
60
|
-
baseUrl?: string;
|
|
61
|
-
}>>;
|
package/types/panel/server.d.ts
CHANGED
|
@@ -211,13 +211,14 @@ export function getRunJob(runid: string, opts?: {
|
|
|
211
211
|
* only `createPanelServer` below always supplies one.
|
|
212
212
|
* @param {import('node:http').IncomingMessage} req
|
|
213
213
|
* @param {import('node:http').ServerResponse} res
|
|
214
|
-
* @param {{ home?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes> }} opts
|
|
214
|
+
* @param {{ home?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes> }} opts
|
|
215
215
|
*/
|
|
216
216
|
export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
|
|
217
217
|
home?: string;
|
|
218
218
|
port: number;
|
|
219
219
|
token?: string;
|
|
220
220
|
authorRoutes?: ReturnType<typeof createAuthorRoutes>;
|
|
221
|
+
settingsRoutes?: ReturnType<typeof createSettingsRoutes>;
|
|
221
222
|
}): void;
|
|
222
223
|
/**
|
|
223
224
|
* Start the panel server. Binds `127.0.0.1` ONLY. A taken port is a LOUD,
|
|
@@ -259,13 +260,17 @@ export function createPanelServer(opts?: {
|
|
|
259
260
|
* Never picks a different port on collision (see file header): prints a
|
|
260
261
|
* loud, named error to stderr and returns 1.
|
|
261
262
|
* @param {string[]} argv
|
|
262
|
-
* @param {{ out: (s: string) => void, err: (s: string) => void, runlistHome?: string }} ctx
|
|
263
|
+
* @param {{ out: (s: string) => void, err: (s: string) => void, runlistHome?: string, env?: Record<string,string|undefined> }} ctx
|
|
264
|
+
* `env` is the RAW shell env (never the start-time merge with the keys file) — the routes
|
|
265
|
+
* re-merge `~/.config/bareloop/.env` onto it per request so Reload keys is real;
|
|
266
|
+
* undefined = the process env.
|
|
263
267
|
* @returns {Promise<number>}
|
|
264
268
|
*/
|
|
265
269
|
export function panelMain(argv: string[], ctx: {
|
|
266
270
|
out: (s: string) => void;
|
|
267
271
|
err: (s: string) => void;
|
|
268
272
|
runlistHome?: string;
|
|
273
|
+
env?: Record<string, string | undefined>;
|
|
269
274
|
}): Promise<number>;
|
|
270
275
|
/**
|
|
271
276
|
* A runid, as it appears in a URL path segment — never a raw filesystem
|
|
@@ -286,10 +291,12 @@ export const DEFAULT_PORT: 4700;
|
|
|
286
291
|
* 2026-09-25).
|
|
287
292
|
*/
|
|
288
293
|
export const SOFTGREEN_ADMITTED_ISO: "2026-08-18T00:00:00.000Z";
|
|
289
|
-
export
|
|
294
|
+
export { DIED_MTIME_MS };
|
|
290
295
|
/** The default/max page size for {@link getRunRounds} — tighten-only,
|
|
291
296
|
* named so a future change is a deliberate, visible edit (item 5,
|
|
292
297
|
* 2026-09-25). */
|
|
293
298
|
export const ROUNDS_PAGE_DEFAULT: 50;
|
|
294
299
|
export const ROUNDS_PAGE_MAX: 200;
|
|
295
300
|
import { createAuthorRoutes } from './authorroutes.js';
|
|
301
|
+
import { createSettingsRoutes } from './settingsroutes.js';
|
|
302
|
+
import { DIED_MTIME_MS } from '../runlist.js';
|