bareloop 0.34.0 → 0.36.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 +58 -0
- package/bareloop.context.md +276 -20
- package/package.json +1 -1
- package/src/authorrun.js +3 -2
- package/src/bundlerun.js +17 -12
- package/src/draftspend.js +197 -0
- package/src/exits.js +10 -1
- package/src/index.js +3 -3
- package/src/interviewrun.js +7 -7
- package/src/job.js +37 -0
- package/src/judged.js +2 -2
- package/src/kinds.js +2 -1
- package/src/ledger.js +101 -5
- package/src/legs.js +171 -0
- package/src/monthly.js +111 -72
- package/src/movefile.js +22 -0
- package/src/npminstall.js +55 -0
- package/src/panel/authorroutes.js +84 -14
- package/src/panel/authorsession.js +490 -125
- package/src/panel/importroutes.js +359 -0
- package/src/panel/importrun.js +203 -0
- package/src/panel/index.html +1194 -213
- package/src/panel/runroutes.js +219 -0
- package/src/panel/server.js +964 -79
- package/src/panel/settingsroutes.js +52 -2
- package/src/panel/status.js +51 -0
- package/src/planrun.js +60 -26
- package/src/providerrows.js +17 -0
- package/src/ralph.js +4 -1
- package/src/replay.js +139 -19
- package/src/replayio.js +11 -4
- package/src/reuse.js +20 -3
- package/src/run.js +6 -3
- package/src/runlist.js +75 -7
- package/src/source.js +43 -4
- package/src/text.js +20 -0
- package/src/u-patient.js +3 -2
- package/src/u-readout.js +9 -1
- package/src/u-watchdog.mjs +4 -2
- package/src/userrun.js +192 -65
- package/src/worktree.js +120 -0
- package/types/draftspend.d.ts +141 -0
- package/types/job.d.ts +25 -0
- package/types/ledger.d.ts +44 -0
- package/types/legs.d.ts +103 -0
- package/types/monthly.d.ts +23 -18
- package/types/movefile.d.ts +9 -0
- package/types/npminstall.d.ts +30 -0
- package/types/panel/authorroutes.d.ts +23 -1
- package/types/panel/authorsession.d.ts +110 -2
- package/types/panel/importroutes.d.ts +129 -0
- package/types/panel/importrun.d.ts +77 -0
- package/types/panel/runroutes.d.ts +32 -0
- package/types/panel/server.d.ts +266 -2
- package/types/panel/settingsroutes.d.ts +6 -1
- package/types/panel/status.d.ts +51 -0
- package/types/planrun.d.ts +13 -21
- package/types/providerrows.d.ts +12 -0
- package/types/ralph.d.ts +19 -0
- package/types/replay.d.ts +25 -1
- package/types/replayio.d.ts +2 -2
- package/types/reuse.d.ts +5 -0
- package/types/run.d.ts +6 -2
- package/types/runlist.d.ts +52 -2
- package/types/source.d.ts +13 -5
- package/types/text.d.ts +19 -0
- package/types/userrun.d.ts +10 -0
- package/types/worktree.d.ts +48 -0
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/** @typedef {{id: string, label: string, status: 'running'|'done'|'failed', detail: string}} StepLine */
|
|
2
|
+
/** @param {StepLine[]} steps @param {string} id the LATEST line carrying this id (the list is a time-ordered log; an id can repeat) */
|
|
3
|
+
export function latestStep(steps: StepLine[], id: string): StepLine | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* The progress list is a chronological log of segments (hamr 2026-10-05): starting a different step closes the running
|
|
6
|
+
* line; a step that already has a line and starts again AFTER other steps gets a NEW line appended at the end (the
|
|
7
|
+
* earlier line keeps its status and detail). Existing lines are never moved. Re-starting the step that is still the
|
|
8
|
+
* LAST line just resumes that line.
|
|
9
|
+
* @param {StepLine[]} steps @param {string} id @param {string} [detail] (absent = keep what the line has)
|
|
10
|
+
*/
|
|
11
|
+
export function advanceSteps(steps: StepLine[], id: string, detail?: string): void;
|
|
1
12
|
/**
|
|
2
13
|
* `true` when `jobs/<name>.json` already exists in the repo's own jobs/ dir
|
|
3
14
|
* (resolved from THIS module's own on-disk location, never `process.cwd()`,
|
|
@@ -11,6 +22,67 @@
|
|
|
11
22
|
export function jobNameTaken(name: string, opts?: {
|
|
12
23
|
jobsDir?: string;
|
|
13
24
|
}): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Only the form's own fields, nothing else a request body might carry (`startFrom`, stray keys).
|
|
27
|
+
* @param {any} card
|
|
28
|
+
* @returns {Record<string, any>}
|
|
29
|
+
*/
|
|
30
|
+
export function cardFields(card: any): Record<string, any>;
|
|
31
|
+
/**
|
|
32
|
+
* The first LOCKED box on `card` that differs from what the signed workflow prefilled (`origin`), or `null` when
|
|
33
|
+
* every locked box is untouched. Blank and absent read alike; whitespace is not compared.
|
|
34
|
+
* @param {Record<string, any>} card the form as it was submitted
|
|
35
|
+
* @param {Record<string, any>} origin the form as the server prefilled it
|
|
36
|
+
* @returns {string|null} the offending field's label
|
|
37
|
+
*/
|
|
38
|
+
export function lockedFieldChanged(card: Record<string, any>, origin: Record<string, any>): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* The spec a reuse signs: a COPY of the origin's signed spec with ONLY the open fields set from the card —
|
|
41
|
+
* `writeScope` from Destination, `budgetUsd` from the $ cap, `maxWallMs` from the Time cap (absent when blank), and the
|
|
42
|
+
* worker (`provider`/`baseUrl`/`model`) from the Model Name chosen in Settings > Providers, spelled exactly as normal
|
|
43
|
+
* authoring spells it (`baseUrl` only when the row has one; `model` only when the Name is not the provider's default
|
|
44
|
+
* tier). A RUBRIC (soft-green) origin with no explicit `judge` first has its judge pinned to the ORIGIN's resolved judge
|
|
45
|
+
* identity — the judge must not change because the worker did (`resolveJobJudge`, the one spelling); an explicit
|
|
46
|
+
* `judge` is kept; a deterministic job has no judge to move. Source is not in a spec (it lives beside the run). The copy
|
|
47
|
+
* hashes to a NEW `jobSpecHash` and the SAME `workflowKey` — the caller checks the second after building.
|
|
48
|
+
* @param {any} originSpec
|
|
49
|
+
* @param {Record<string, any>} card
|
|
50
|
+
* @param {readonly import('../providerrows.js').KeyRow[]} [rows] the Settings rows; without them (or without `card.model`) the worker is left as signed
|
|
51
|
+
* @returns {any}
|
|
52
|
+
*/
|
|
53
|
+
export function buildReuseSpec(originSpec: any, card: Record<string, any>, rows?: readonly import("../providerrows.js").KeyRow[]): any;
|
|
54
|
+
/**
|
|
55
|
+
* `$0` validation for a REUSE start: the signed job carries the goal, checks and guardrails, so those boxes are not
|
|
56
|
+
* required here — only what the new session itself needs: a Source, a Destination, a Model that is a Settings Name,
|
|
57
|
+
* and the cap. (The spec built from the open boxes is validated by the route with `validateJob`.)
|
|
58
|
+
* @param {any} card
|
|
59
|
+
* @param {{rows?: readonly import('../providerrows.js').KeyRow[]}} [opts]
|
|
60
|
+
* @returns {{ok: true}|{ok: false, error: string}}
|
|
61
|
+
*/
|
|
62
|
+
export function validateReuseCard(card: any, opts?: {
|
|
63
|
+
rows?: readonly import("../providerrows.js").KeyRow[];
|
|
64
|
+
}): {
|
|
65
|
+
ok: true;
|
|
66
|
+
} | {
|
|
67
|
+
ok: false;
|
|
68
|
+
error: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The $0 refusal for a non-repo source, worded by the kind the source door recorded (`prep.manifest.kind`) — a single
|
|
72
|
+
* file is not a "folder", and Source must be the repo folder with the file named in Destination (hamr 2026-10-05).
|
|
73
|
+
* @param {string} kind `prep.manifest.kind`: 'file' | 'folder' | 'url' (a 'repo' never reaches this)
|
|
74
|
+
* @returns {string}
|
|
75
|
+
*/
|
|
76
|
+
export function nonRepoSourceMessage(kind: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* The ONE Destination rule for a REPO source (fresh card and Reuse card alike): there Destination is the write fence,
|
|
79
|
+
* relative to the repo, so an absolute path is refused — at $0, before any copy or model call. A FOLDER source (or a
|
|
80
|
+
* source that does not peek as a repo) keeps today's behaviour: its Destination is an absolute output directory.
|
|
81
|
+
* @param {string} destination
|
|
82
|
+
* @param {boolean} isRepoLike `looksLikeRepoSource(card.source)`
|
|
83
|
+
* @returns {string|null} the plain refusal, or null when the Destination is fine
|
|
84
|
+
*/
|
|
85
|
+
export function repoDestinationProblem(destination: string, isRepoLike: boolean): string | null;
|
|
14
86
|
/**
|
|
15
87
|
* `$0` validation of the job card fields the server must check BEFORE it
|
|
16
88
|
* creates a session or spends anything — never inside the async pipeline,
|
|
@@ -36,8 +108,9 @@ export function validateJobCard(card: any, opts?: {
|
|
|
36
108
|
* @param {any} card the validated job card (see {@link validateJobCard})
|
|
37
109
|
* @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
|
|
38
110
|
* scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
|
|
39
|
-
* prepareSigningFn?: Function
|
|
40
|
-
* just above where each is read, below.
|
|
111
|
+
* prepareSigningFn?: Function, npmCiFn?: Function, reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
|
|
112
|
+
* TEST SEAMS ONLY — see the note just above where each is read, below. `reuse` (Reuse workflow) is NOT a seam: the
|
|
113
|
+
* caller (`/api/author/start`) sets it only when the server's own same-job rule held.
|
|
41
114
|
* @returns {any} the session object
|
|
42
115
|
*/
|
|
43
116
|
export function createSession(card: any, deps?: {
|
|
@@ -50,4 +123,39 @@ export function createSession(card: any, deps?: {
|
|
|
50
123
|
confirmGenerate?: Function;
|
|
51
124
|
authorFn?: Function;
|
|
52
125
|
prepareSigningFn?: Function;
|
|
126
|
+
npmCiFn?: Function;
|
|
127
|
+
reuse?: {
|
|
128
|
+
spec: any;
|
|
129
|
+
workflowKey: string;
|
|
130
|
+
};
|
|
53
131
|
}): any;
|
|
132
|
+
/**
|
|
133
|
+
* THE PROGRESS LIST's one table (hamr 2026-10-04, "one place showing all"): every pipeline step the left Chat panel
|
|
134
|
+
* lists, in the order they first appear, each with a stable id and a plain code-owned label. A step is one line that
|
|
135
|
+
* updates in place (running -> done or failed with its reason); the thread carries only chat turns.
|
|
136
|
+
* @type {Readonly<Record<string, string>>}
|
|
137
|
+
*/
|
|
138
|
+
export const STEP_LABELS: Readonly<Record<string, string>>;
|
|
139
|
+
/**
|
|
140
|
+
* The library's own `onPhase(name, ...)` calls (`src/authorjob.js`/`src/authorflow.js`) -> the step each one advances.
|
|
141
|
+
* A phase with no entry leaves the current step as it was.
|
|
142
|
+
* @type {Readonly<Record<string, keyof typeof STEP_LABELS>>}
|
|
143
|
+
*/
|
|
144
|
+
export const PHASE_STEP: Readonly<Record<string, keyof typeof STEP_LABELS>>;
|
|
145
|
+
/** the form's fields, in card order — what `card.json` stores, verbatim (P5 item 3) */
|
|
146
|
+
export const CARD_FIELDS: readonly string[];
|
|
147
|
+
/**
|
|
148
|
+
* REUSE WORKFLOW (hamr 2026-10-03, replaces P5 item 3's same-job rule; Model opened 2026-10-04): a reuse card has exactly
|
|
149
|
+
* FIVE open boxes — Source, Destination, Model, $ cap, Time cap — and every other box is the signed workflow, shown greyed and never
|
|
150
|
+
* editable. The page greys them; THIS is what refuses (the server never trusts the page). Changing any locked box
|
|
151
|
+
* is a different job: Clear the card, which drafts.
|
|
152
|
+
*/
|
|
153
|
+
export const REUSE_OPEN_FIELDS: readonly string[];
|
|
154
|
+
/** every card field that is NOT open on a reuse card, in card order */
|
|
155
|
+
export const REUSE_LOCKED_FIELDS: readonly string[];
|
|
156
|
+
export type StepLine = {
|
|
157
|
+
id: string;
|
|
158
|
+
label: string;
|
|
159
|
+
status: "running" | "done" | "failed";
|
|
160
|
+
detail: string;
|
|
161
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expand, normalise and `realpath` a requested folder. Never throws.
|
|
3
|
+
* @param {unknown} input the raw `path` (absent/blank = the person's home folder)
|
|
4
|
+
* @param {string} userHome the home folder (`~`)
|
|
5
|
+
* @returns {{ok: true, requested: string, path: string}|{ok: false, error: string}}
|
|
6
|
+
*/
|
|
7
|
+
export function resolveFsPath(input: unknown, userHome: string): {
|
|
8
|
+
ok: true;
|
|
9
|
+
requested: string;
|
|
10
|
+
path: string;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
error: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The exported job bundles below `dir` (a `realpath` already), searched DOWN at most SEARCH_DEPTH levels. Symlinks are
|
|
17
|
+
* never followed, `node_modules`, `.git` and every dot-folder are skipped, a bundle is never descended into, an
|
|
18
|
+
* unreadable folder is skipped silently, and the search is capped (folders visited, results). Only names are read.
|
|
19
|
+
* @param {string} dir
|
|
20
|
+
* @returns {{entries: {name: string, path: string, bundle: true}[], visited: number, stopped: boolean, truncated: boolean}}
|
|
21
|
+
*/
|
|
22
|
+
export function findBundles(dir: string): {
|
|
23
|
+
entries: {
|
|
24
|
+
name: string;
|
|
25
|
+
path: string;
|
|
26
|
+
bundle: true;
|
|
27
|
+
}[];
|
|
28
|
+
visited: number;
|
|
29
|
+
stopped: boolean;
|
|
30
|
+
truncated: boolean;
|
|
31
|
+
};
|
|
32
|
+
/** @param {string} [home] @returns {string} `<home>/imports.jsonl` */
|
|
33
|
+
export function importsPath(home?: string): string;
|
|
34
|
+
/** the stable id of an imported folder (its real path, hashed — never the path itself in a URL) */
|
|
35
|
+
export function importId(dir: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Record one import: ONE row per real folder. Any earlier row for the same folder is dropped and the new one is
|
|
38
|
+
* written, so re-importing updates the entry instead of adding a second. The rewrite goes through a temp file + rename.
|
|
39
|
+
* @param {string|undefined} home
|
|
40
|
+
* @param {{at: string, dir: string, job: string, bundleHash: string}} row
|
|
41
|
+
*/
|
|
42
|
+
export function recordImport(home: string | undefined, row: {
|
|
43
|
+
at: string;
|
|
44
|
+
dir: string;
|
|
45
|
+
job: string;
|
|
46
|
+
bundleHash: string;
|
|
47
|
+
}): void;
|
|
48
|
+
/**
|
|
49
|
+
* The imported list: the LATEST row per folder (a re-import supersedes), newest first. A malformed line is skipped.
|
|
50
|
+
* @param {string} [home]
|
|
51
|
+
* @returns {{at: string, dir: string, job: string, bundleHash: string, id: string}[]}
|
|
52
|
+
*/
|
|
53
|
+
export function readImports(home?: string): {
|
|
54
|
+
at: string;
|
|
55
|
+
dir: string;
|
|
56
|
+
job: string;
|
|
57
|
+
bundleHash: string;
|
|
58
|
+
id: string;
|
|
59
|
+
}[];
|
|
60
|
+
/**
|
|
61
|
+
* Re-read one imported folder NOW and say how it stands against the import: `ok`, `changed` (the bundle's own
|
|
62
|
+
* hash, a tampered file or an unreadable part differ from what was imported — shown red as "changed since
|
|
63
|
+
* import"), or `missing` (the folder is gone). Never throws.
|
|
64
|
+
* @param {{dir: string, bundleHash: string}} row
|
|
65
|
+
* @returns {{status: 'ok'|'changed'|'missing', statusText: string|null, reds: string[], bundle: ReturnType<typeof readBundle>}}
|
|
66
|
+
*/
|
|
67
|
+
export function bundleStatus(row: {
|
|
68
|
+
dir: string;
|
|
69
|
+
bundleHash: string;
|
|
70
|
+
}): {
|
|
71
|
+
status: "ok" | "changed" | "missing";
|
|
72
|
+
statusText: string | null;
|
|
73
|
+
reds: string[];
|
|
74
|
+
bundle: ReturnType<typeof readBundle>;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The `bundle` half of an import view: the exported history (the registry rows the bundle shipped), the approval
|
|
78
|
+
* on THIS machine (`blessing.json`, written by the first green run here), nothing about the folder's other files.
|
|
79
|
+
* @param {ReturnType<typeof readBundle>} bundle
|
|
80
|
+
*/
|
|
81
|
+
export function bundleHistoryAndApproval(bundle: ReturnType<typeof readBundle>): {
|
|
82
|
+
history: {
|
|
83
|
+
greens: number;
|
|
84
|
+
reds: number;
|
|
85
|
+
total: number;
|
|
86
|
+
recent: {
|
|
87
|
+
at: any;
|
|
88
|
+
runid: any;
|
|
89
|
+
outcome: any;
|
|
90
|
+
costUsd: any;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
approved: boolean;
|
|
94
|
+
approvedText: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* The import routes' handler. `describeSpec(spec)` is the server's own spec reader (the SAME fact-readers the Job
|
|
98
|
+
* tab uses, so an imported job and a run read alike): `{checkType, goal, success, guardrails, model, budgetUsd,
|
|
99
|
+
* maxWallMs}`.
|
|
100
|
+
* @param {{ port: number, token: string, home?: string, userHome?: string,
|
|
101
|
+
* describeSpec: (spec: any) => any }} opts
|
|
102
|
+
*/
|
|
103
|
+
export function createImportRoutes(opts: {
|
|
104
|
+
port: number;
|
|
105
|
+
token: string;
|
|
106
|
+
home?: string;
|
|
107
|
+
userHome?: string;
|
|
108
|
+
describeSpec: (spec: any) => any;
|
|
109
|
+
}): {
|
|
110
|
+
handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
|
|
111
|
+
viewOf: (row: {
|
|
112
|
+
id: string;
|
|
113
|
+
at: string;
|
|
114
|
+
dir: string;
|
|
115
|
+
job: string;
|
|
116
|
+
bundleHash: string;
|
|
117
|
+
}) => any;
|
|
118
|
+
};
|
|
119
|
+
/** the longest path the routes will look at (bytes) */
|
|
120
|
+
export const MAX_PATH_LEN: 4096;
|
|
121
|
+
/** how many levels below the current folder the bundle search looks */
|
|
122
|
+
export const SEARCH_DEPTH: 3;
|
|
123
|
+
/** the most folders one search visits; past it `stopped: true` */
|
|
124
|
+
export const MAX_VISITED: 5000;
|
|
125
|
+
/** the most bundles one search returns; past it `truncated: true` */
|
|
126
|
+
export const MAX_RESULTS: 200;
|
|
127
|
+
/** The ONE spelling of the plain-words line for a changed import (Run tab, reuse refusal). The list tag stays the short statusText. */
|
|
128
|
+
export const IMPORT_CHANGED_LINE: "files changed since you imported it \u2014 Reuse is off. Import it again if the change was yours.";
|
|
129
|
+
import { readBundle } from '../bundle.js';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** the id an imported run has in `/api/runs/<id>` @param {string} importId @param {string} runid */
|
|
2
|
+
export function importedRunId(importId: string, runid: string): string;
|
|
3
|
+
/** @param {string} id @returns {{importId: string, runid: string}|null} */
|
|
4
|
+
export function parseImportedRunId(id: string): {
|
|
5
|
+
importId: string;
|
|
6
|
+
runid: string;
|
|
7
|
+
} | null;
|
|
8
|
+
/**
|
|
9
|
+
* The spine path of `<dir>/runs/<runid>`, or null when it is not a plain, bounded file in plain folders: `runs/` and
|
|
10
|
+
* the run folder must be real directories (a link is refused), the spine a regular file under the size bound, and a
|
|
11
|
+
* tool log beside it (either spelling the replay reader looks for) must not be a link or oversize either.
|
|
12
|
+
* @param {string} dir the bundle folder
|
|
13
|
+
* @param {string} runid
|
|
14
|
+
* @returns {string|null}
|
|
15
|
+
*/
|
|
16
|
+
export function safeSpinePath(dir: string, runid: string): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* The run folders of a bundle that hold a readable spine, newest spine first (bounded).
|
|
19
|
+
* @param {string} dir
|
|
20
|
+
* @returns {{runid: string, spine: string, mtimeMs: number}[]}
|
|
21
|
+
*/
|
|
22
|
+
export function bundleRuns(dir: string): {
|
|
23
|
+
runid: string;
|
|
24
|
+
spine: string;
|
|
25
|
+
mtimeMs: number;
|
|
26
|
+
}[];
|
|
27
|
+
/**
|
|
28
|
+
* When the run started: its `job-start` record's `ts`, else the spine file's mtime (an ISO string). Never throws.
|
|
29
|
+
* @param {string} spine
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function spineStartedAt(spine: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The newest run in the bundle whose spine ended green (`green` / `already-green`, the same two the run page's Ended
|
|
35
|
+
* block calls green). A spine that cannot be read is skipped, never fatal.
|
|
36
|
+
* @param {string} dir
|
|
37
|
+
* @returns {{runid: string, spine: string, at: string}|null}
|
|
38
|
+
*/
|
|
39
|
+
export function latestGreenRun(dir: string): {
|
|
40
|
+
runid: string;
|
|
41
|
+
spine: string;
|
|
42
|
+
at: string;
|
|
43
|
+
} | null;
|
|
44
|
+
/**
|
|
45
|
+
* The newest green version across a bundle's bridges (by `greenAt`), with the history row of the same run (it
|
|
46
|
+
* carries whether the spend figure is complete). A malformed bridge or version is skipped.
|
|
47
|
+
* @param {any[]} bridges
|
|
48
|
+
* @returns {{bridge: any, version: any, historyRow: any|null}|null}
|
|
49
|
+
*/
|
|
50
|
+
export function latestBridgeGreen(bridges: any[]): {
|
|
51
|
+
bridge: any;
|
|
52
|
+
version: any;
|
|
53
|
+
historyRow: any | null;
|
|
54
|
+
} | null;
|
|
55
|
+
/**
|
|
56
|
+
* The run detail of the bridge's green version, in the SAME shape `getRunDetail` returns (so the page's one run
|
|
57
|
+
* renderer paints it), plus `fromBridge: true`. Only what the bridge carries is filled; every other field is null
|
|
58
|
+
* ("not recorded" on the page), never 0: no tool counts, no cache, no per-step figures, no close stages' numbers.
|
|
59
|
+
* @param {{bridge: any, version: any, historyRow: any|null}} g
|
|
60
|
+
* @param {{job: string, checkType: string, checkTypeTitle: string|null, model: string|null, budgetUsd: number|null}} facts
|
|
61
|
+
* @returns {any}
|
|
62
|
+
*/
|
|
63
|
+
export function bridgeRunDetail(g: {
|
|
64
|
+
bridge: any;
|
|
65
|
+
version: any;
|
|
66
|
+
historyRow: any | null;
|
|
67
|
+
}, facts: {
|
|
68
|
+
job: string;
|
|
69
|
+
checkType: string;
|
|
70
|
+
checkTypeTitle: string | null;
|
|
71
|
+
model: string | null;
|
|
72
|
+
budgetUsd: number | null;
|
|
73
|
+
}): any;
|
|
74
|
+
/** the most run folders one bundle is searched through (newest first) */
|
|
75
|
+
export const MAX_RUN_DIRS: 40;
|
|
76
|
+
/** the biggest spine / tool-log file that is read (bytes) */
|
|
77
|
+
export const MAX_RUN_FILE_BYTES: number;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run routes' handler. `getResumeContext(runid)` is the server's own reader (the same
|
|
3
|
+
* `resumePlanFor` the Ended block uses, so the page never offers what this refuses):
|
|
4
|
+
* `null` = no such run, else `{row, plan}`.
|
|
5
|
+
* @param {{ port: number, token: string, env?: Record<string,string|undefined>, home?: string,
|
|
6
|
+
* spawnFn?: typeof realSpawn, bareloopBin?: string, settleMs?: number,
|
|
7
|
+
* getResumeContext: (runid: string) => ({row: any, plan: any}|null),
|
|
8
|
+
* getStopContext?: (runid: string) => ({row: any, live: boolean, spineExists: boolean}|null) }} opts
|
|
9
|
+
*/
|
|
10
|
+
export function createRunRoutes(opts: {
|
|
11
|
+
port: number;
|
|
12
|
+
token: string;
|
|
13
|
+
env?: Record<string, string | undefined>;
|
|
14
|
+
home?: string;
|
|
15
|
+
spawnFn?: typeof realSpawn;
|
|
16
|
+
bareloopBin?: string;
|
|
17
|
+
settleMs?: number;
|
|
18
|
+
getResumeContext: (runid: string) => ({
|
|
19
|
+
row: any;
|
|
20
|
+
plan: any;
|
|
21
|
+
} | null);
|
|
22
|
+
getStopContext?: (runid: string) => ({
|
|
23
|
+
row: any;
|
|
24
|
+
live: boolean;
|
|
25
|
+
spineExists: boolean;
|
|
26
|
+
} | null);
|
|
27
|
+
}): {
|
|
28
|
+
handle: (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, pathname: string, body: any) => boolean;
|
|
29
|
+
};
|
|
30
|
+
/** how long a resume waits for an early engine exit before reporting "started" (ms) */
|
|
31
|
+
export const DEFAULT_SETTLE_MS: 6000;
|
|
32
|
+
import { spawn as realSpawn } from 'node:child_process';
|