@yagni-app/code 0.1.0 → 0.2.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/README.md +65 -11
- package/dist/claudeCompat.d.ts +109 -0
- package/dist/claudeCompat.js +260 -0
- package/dist/claudePlugins.d.ts +109 -0
- package/dist/claudePlugins.js +336 -0
- package/dist/cli.d.ts +1 -5
- package/dist/cli.js +79 -20
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/crashReport.d.ts +135 -0
- package/dist/crashReport.js +291 -0
- package/dist/credentials.js +2 -1
- package/dist/distribution.d.ts +15 -0
- package/dist/distribution.js +45 -0
- package/dist/doctor.d.ts +27 -0
- package/dist/doctor.js +81 -3
- package/dist/extension/advisor.d.ts +84 -0
- package/dist/extension/advisor.js +101 -0
- package/dist/extension/askAdvisorTool.d.ts +83 -0
- package/dist/extension/askAdvisorTool.js +194 -0
- package/dist/extension/askYagniTool.d.ts +39 -0
- package/dist/extension/askYagniTool.js +63 -0
- package/dist/extension/bless.d.ts +45 -0
- package/dist/extension/bless.js +87 -0
- package/dist/extension/boostCommand.d.ts +144 -0
- package/dist/extension/boostCommand.js +263 -0
- package/dist/extension/branding.d.ts +79 -0
- package/dist/extension/branding.js +156 -0
- package/dist/extension/chipEditor.d.ts +117 -0
- package/dist/extension/chipEditor.js +373 -0
- package/dist/extension/claudeRules.d.ts +54 -0
- package/dist/extension/claudeRules.js +180 -0
- package/dist/extension/config.d.ts +173 -0
- package/dist/extension/config.js +194 -0
- package/dist/extension/costHud.d.ts +186 -0
- package/dist/extension/costHud.js +293 -0
- package/dist/extension/crashReport.d.ts +89 -0
- package/dist/extension/crashReport.js +241 -0
- package/dist/extension/decisionCapture.d.ts +52 -0
- package/dist/extension/decisionCapture.js +66 -0
- package/dist/extension/decisions.d.ts +83 -0
- package/dist/extension/decisions.js +200 -0
- package/dist/extension/diagnostics.d.ts +41 -0
- package/dist/extension/diagnostics.js +110 -0
- package/dist/extension/index.d.ts +155 -0
- package/dist/extension/index.js +597 -0
- package/dist/extension/initDone.d.ts +28 -0
- package/dist/extension/initDone.js +66 -0
- package/dist/extension/initPass.d.ts +170 -0
- package/dist/extension/initPass.js +394 -0
- package/dist/extension/mcpTools.d.ts +57 -0
- package/dist/extension/mcpTools.js +132 -0
- package/dist/extension/nextWorkTool.d.ts +51 -0
- package/dist/extension/nextWorkTool.js +80 -0
- package/dist/extension/permission.d.ts +91 -0
- package/dist/extension/permission.js +236 -0
- package/dist/extension/pipeline/activity.d.ts +37 -0
- package/dist/extension/pipeline/activity.js +151 -0
- package/dist/extension/pipeline/activityFeed.d.ts +59 -0
- package/dist/extension/pipeline/activityFeed.js +175 -0
- package/dist/extension/pipeline/budget.d.ts +48 -0
- package/dist/extension/pipeline/budget.js +68 -0
- package/dist/extension/pipeline/checkpoint.d.ts +31 -0
- package/dist/extension/pipeline/checkpoint.js +176 -0
- package/dist/extension/pipeline/eval.d.ts +205 -0
- package/dist/extension/pipeline/eval.js +226 -0
- package/dist/extension/pipeline/events.d.ts +56 -0
- package/dist/extension/pipeline/events.js +147 -0
- package/dist/extension/pipeline/findings.d.ts +42 -0
- package/dist/extension/pipeline/findings.js +144 -0
- package/dist/extension/pipeline/finish.d.ts +128 -0
- package/dist/extension/pipeline/finish.js +307 -0
- package/dist/extension/pipeline/goCommand.d.ts +146 -0
- package/dist/extension/pipeline/goCommand.js +1085 -0
- package/dist/extension/pipeline/goCompareCommand.d.ts +58 -0
- package/dist/extension/pipeline/goCompareCommand.js +203 -0
- package/dist/extension/pipeline/goFlags.d.ts +40 -0
- package/dist/extension/pipeline/goFlags.js +46 -0
- package/dist/extension/pipeline/goStatusCommands.d.ts +59 -0
- package/dist/extension/pipeline/goStatusCommands.js +188 -0
- package/dist/extension/pipeline/invocation.d.ts +45 -0
- package/dist/extension/pipeline/invocation.js +64 -0
- package/dist/extension/pipeline/orchestrator.d.ts +131 -0
- package/dist/extension/pipeline/orchestrator.js +645 -0
- package/dist/extension/pipeline/personas.d.ts +44 -0
- package/dist/extension/pipeline/personas.js +248 -0
- package/dist/extension/pipeline/resilience.d.ts +85 -0
- package/dist/extension/pipeline/resilience.js +166 -0
- package/dist/extension/pipeline/resume.d.ts +18 -0
- package/dist/extension/pipeline/resume.js +106 -0
- package/dist/extension/pipeline/runCostTable.d.ts +37 -0
- package/dist/extension/pipeline/runCostTable.js +165 -0
- package/dist/extension/pipeline/runRegistry.d.ts +112 -0
- package/dist/extension/pipeline/runRegistry.js +202 -0
- package/dist/extension/pipeline/runSession.d.ts +152 -0
- package/dist/extension/pipeline/runSession.js +167 -0
- package/dist/extension/pipeline/runState.d.ts +177 -0
- package/dist/extension/pipeline/runState.js +275 -0
- package/dist/extension/pipeline/runner.d.ts +98 -0
- package/dist/extension/pipeline/runner.js +310 -0
- package/dist/extension/pipeline/scrubSecrets.d.ts +15 -0
- package/dist/extension/pipeline/scrubSecrets.js +32 -0
- package/dist/extension/pipeline/stages.d.ts +53 -0
- package/dist/extension/pipeline/stages.js +115 -0
- package/dist/extension/pipeline/ticketResolution.d.ts +34 -0
- package/dist/extension/pipeline/ticketResolution.js +75 -0
- package/dist/extension/pipeline/types.d.ts +439 -0
- package/dist/extension/pipeline/types.js +48 -0
- package/dist/extension/pipeline/verify.d.ts +277 -0
- package/dist/extension/pipeline/verify.js +763 -0
- package/dist/extension/pipeline/workspace.d.ts +32 -0
- package/dist/extension/pipeline/workspace.js +73 -0
- package/dist/extension/pipeline/worktree.d.ts +107 -0
- package/dist/extension/pipeline/worktree.js +202 -0
- package/dist/extension/provider.d.ts +17 -0
- package/dist/extension/provider.js +33 -0
- package/dist/extension/recall.d.ts +93 -0
- package/dist/extension/recall.js +190 -0
- package/dist/extension/recordContextTool.d.ts +38 -0
- package/dist/extension/recordContextTool.js +85 -0
- package/dist/extension/recordDecisionTool.d.ts +52 -0
- package/dist/extension/recordDecisionTool.js +102 -0
- package/dist/extension/repoDocs.d.ts +81 -0
- package/dist/extension/repoDocs.js +260 -0
- package/dist/extension/rerouteNotice.d.ts +42 -0
- package/dist/extension/rerouteNotice.js +67 -0
- package/dist/extension/resilientFetch.d.ts +60 -0
- package/dist/extension/resilientFetch.js +133 -0
- package/dist/extension/reviewTool.d.ts +34 -0
- package/dist/extension/reviewTool.js +81 -0
- package/dist/extension/sessionRuns.d.ts +45 -0
- package/dist/extension/sessionRuns.js +77 -0
- package/dist/extension/spool.d.ts +92 -0
- package/dist/extension/spool.js +266 -0
- package/dist/extension/stateHome.d.ts +2 -0
- package/dist/extension/stateHome.js +6 -0
- package/dist/extension/subagents.d.ts +145 -0
- package/dist/extension/subagents.js +326 -0
- package/dist/extension/surface.d.ts +10 -0
- package/dist/extension/surface.js +12 -0
- package/dist/extension/todos.d.ts +110 -0
- package/dist/extension/todos.js +217 -0
- package/dist/extension/tokenProvider.d.ts +93 -0
- package/dist/extension/tokenProvider.js +234 -0
- package/dist/launch.d.ts +25 -3
- package/dist/launch.js +27 -9
- package/dist/login.d.ts +7 -0
- package/dist/login.js +3 -1
- package/dist/paths.d.ts +13 -4
- package/dist/paths.js +17 -5
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.js +5 -2
- package/dist/upgrade.d.ts +97 -0
- package/dist/upgrade.js +284 -0
- package/package.json +11 -15
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PURE resume-decision core — the analogue of `findings.ts#shouldStop`.
|
|
3
|
+
*
|
|
4
|
+
* Given the append-only checkpoint journal for a run key and a LIVE workspace
|
|
5
|
+
* snapshot, decide whether /go can safely resume the bounded review→fix loop,
|
|
6
|
+
* must warn about a partially-built / diverged tree, or should just run fresh.
|
|
7
|
+
*
|
|
8
|
+
* Safety boundary (the whole point): we only auto-resume the EXPENSIVE half (the
|
|
9
|
+
* review→fix loop) and only when the build half fully completed AND the live git
|
|
10
|
+
* tree still matches the boundary we checkpointed. A crash mid-build, or a tree
|
|
11
|
+
* that drifted since the checkpoint, would risk double-applying edits if we
|
|
12
|
+
* replayed — so those resolve to `warn_partial` (warn + run fresh), never a
|
|
13
|
+
* silent replay. Anything unknowable fails OPEN to `fresh` (mirrors
|
|
14
|
+
* `workspaceChanged`). The fold never throws.
|
|
15
|
+
*/
|
|
16
|
+
import { EMPTY_RUN_USAGE } from "./budget.js";
|
|
17
|
+
/** Two snapshots match only when BOTH are present, tracked, and identical (HEAD + status). */
|
|
18
|
+
function snapshotsMatch(a, b) {
|
|
19
|
+
return Boolean(a) && Boolean(b) && a.tracked && b.tracked && a.head === b.head && a.status === b.status;
|
|
20
|
+
}
|
|
21
|
+
/** The last record of a given kind, or undefined. */
|
|
22
|
+
function lastOfKind(records, kind) {
|
|
23
|
+
for (let i = records.length - 1; i >= 0; i -= 1) {
|
|
24
|
+
if (records[i]?.kind === kind)
|
|
25
|
+
return records[i];
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
export function planResume(records, liveSnapshot) {
|
|
30
|
+
try {
|
|
31
|
+
if (!Array.isArray(records) || records.length === 0)
|
|
32
|
+
return { mode: "fresh" };
|
|
33
|
+
// A terminal run_finish means the run completed (even `round_cap`) — a
|
|
34
|
+
// completed run is NOT an interrupted one, so it is never resumable. A
|
|
35
|
+
// finish_done is equally terminal: the FINISH stage only runs after a clean
|
|
36
|
+
// stop, so a crash in the narrow window before run_finish lands must not
|
|
37
|
+
// read as "resumable" over a tree the FINISH commit has since moved.
|
|
38
|
+
if (records.some((r) => r?.kind === "run_finish" || r?.kind === "finish_done"))
|
|
39
|
+
return { mode: "fresh" };
|
|
40
|
+
const runStart = records.find((r) => r?.kind === "run_start");
|
|
41
|
+
if (!runStart)
|
|
42
|
+
return { mode: "fresh" }; // no anchor → nothing trustworthy to resume
|
|
43
|
+
const ticket = runStart.ticket;
|
|
44
|
+
const buildDone = lastOfKind(records, "build_done");
|
|
45
|
+
if (!buildDone) {
|
|
46
|
+
// Interrupted during the build half: the tree may hold partial edits, so
|
|
47
|
+
// replaying review against it would be wrong. Warn; the caller runs fresh.
|
|
48
|
+
return { mode: "warn_partial", reason: "build_interrupted", ticket };
|
|
49
|
+
}
|
|
50
|
+
const roundDones = records.filter((r) => r?.kind === "round_done");
|
|
51
|
+
const latest = roundDones.length > 0 ? maxByRound(roundDones) : buildDone;
|
|
52
|
+
// Unknowable tree (not a git work tree) → fail open to fresh rather than
|
|
53
|
+
// claim a resume we cannot verify.
|
|
54
|
+
if (!liveSnapshot?.tracked)
|
|
55
|
+
return { mode: "fresh" };
|
|
56
|
+
// The live tree must still be exactly where we checkpointed it; otherwise an
|
|
57
|
+
// orphaned child or a manual edit drifted it and replay is unsafe.
|
|
58
|
+
if (!snapshotsMatch(latest.snapshot, liveSnapshot)) {
|
|
59
|
+
return { mode: "warn_partial", reason: "tree_diverged", ticket };
|
|
60
|
+
}
|
|
61
|
+
if (roundDones.length === 0) {
|
|
62
|
+
// Build done, no round completed → resume at round 1 against implement output.
|
|
63
|
+
return {
|
|
64
|
+
mode: "resume_loop",
|
|
65
|
+
round: 1,
|
|
66
|
+
reviewInput: buildDone.reviewInput ?? "",
|
|
67
|
+
priorRounds: [],
|
|
68
|
+
priorUsage: buildDone.usage ?? EMPTY_RUN_USAGE,
|
|
69
|
+
...handle(runStart),
|
|
70
|
+
ticket,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const maxRound = Math.max(...roundDones.map((r) => r.round ?? 0));
|
|
74
|
+
const lastRound = roundDones.find((r) => (r.round ?? 0) === maxRound) ?? buildDone;
|
|
75
|
+
const priorRounds = roundDones
|
|
76
|
+
.slice()
|
|
77
|
+
.sort((a, b) => (a.round ?? 0) - (b.round ?? 0))
|
|
78
|
+
.map((r) => ({ round: r.round ?? 0, lensResults: [], findings: r.findings ?? [] }));
|
|
79
|
+
return {
|
|
80
|
+
mode: "resume_loop",
|
|
81
|
+
round: maxRound + 1,
|
|
82
|
+
reviewInput: lastRound.reviewInput ?? "",
|
|
83
|
+
priorRounds,
|
|
84
|
+
priorUsage: lastRound.usage ?? buildDone.usage ?? EMPTY_RUN_USAGE,
|
|
85
|
+
...handle(runStart),
|
|
86
|
+
ticket,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { mode: "fresh" };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** The record carrying the highest `round`. */
|
|
94
|
+
function maxByRound(records) {
|
|
95
|
+
return records.reduce((a, b) => ((b.round ?? 0) >= (a.round ?? 0) ? b : a));
|
|
96
|
+
}
|
|
97
|
+
/** Pull the optional backend handle off the run_start record (omit undefined keys). */
|
|
98
|
+
function handle(runStart) {
|
|
99
|
+
const out = {};
|
|
100
|
+
if (runStart.runId)
|
|
101
|
+
out.runId = runStart.runId;
|
|
102
|
+
if (runStart.workItemId)
|
|
103
|
+
out.workItemId = runStart.workItemId;
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=resume.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 8 (/go summary prints a per-stage, server-priced cost table): a pure
|
|
3
|
+
* formatter over `GET /api/yagni-code/spend?runId=`'s response (see
|
|
4
|
+
* costHud.ts's SpendResponse/SpendRow mirror for the shape this reads).
|
|
5
|
+
*
|
|
6
|
+
* Kept out of costHud.ts deliberately: that module's `formatServerCostLines`
|
|
7
|
+
* renders a SESSION's spend grouped by rate tier (for /cost); this renders ONE
|
|
8
|
+
* RUN's spend grouped by PIPELINE STAGE (for /go's end-of-run summary), and the
|
|
9
|
+
* grouping/merge rules below (fold the three review lenses into one line,
|
|
10
|
+
* mixed-tier tie-break) are specific enough to this call site that sharing a
|
|
11
|
+
* module would just make costHud.ts harder to read for its own surface.
|
|
12
|
+
*
|
|
13
|
+
* Caller labels are minted by runner.ts as `go:<stage.id>` (or
|
|
14
|
+
* `go:<stage.id>:<lens>` for the fanned-out review stage) — see
|
|
15
|
+
* `callerLabel` in runner.ts. This module's `stageKeyForCaller` is the inverse
|
|
16
|
+
* mapping back to a display row.
|
|
17
|
+
*/
|
|
18
|
+
import type { SpendResponse } from "../costHud.js";
|
|
19
|
+
/**
|
|
20
|
+
* Render /go's end-of-run, server-priced cost table. Pure, no em-dashes,
|
|
21
|
+
* multi-line (joined with "\n"), leading space on the header/total lines so it
|
|
22
|
+
* reads as a continuation of the preceding recap sentence (matches
|
|
23
|
+
* runCostNote's own leading-space convention in goCommand.ts).
|
|
24
|
+
*
|
|
25
|
+
* Example:
|
|
26
|
+
* ```
|
|
27
|
+
* Run cost (server):
|
|
28
|
+
* map standard $0.05
|
|
29
|
+
* plan peak $0.31
|
|
30
|
+
* implement standard $0.12
|
|
31
|
+
* review peak $0.34
|
|
32
|
+
* fix standard $0.04
|
|
33
|
+
* Run: $0.84 billed. $2.71 at frontier list. 69% saved.
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatRunCostTable(spend: SpendResponse): string;
|
|
37
|
+
//# sourceMappingURL=runCostTable.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 8 (/go summary prints a per-stage, server-priced cost table): a pure
|
|
3
|
+
* formatter over `GET /api/yagni-code/spend?runId=`'s response (see
|
|
4
|
+
* costHud.ts's SpendResponse/SpendRow mirror for the shape this reads).
|
|
5
|
+
*
|
|
6
|
+
* Kept out of costHud.ts deliberately: that module's `formatServerCostLines`
|
|
7
|
+
* renders a SESSION's spend grouped by rate tier (for /cost); this renders ONE
|
|
8
|
+
* RUN's spend grouped by PIPELINE STAGE (for /go's end-of-run summary), and the
|
|
9
|
+
* grouping/merge rules below (fold the three review lenses into one line,
|
|
10
|
+
* mixed-tier tie-break) are specific enough to this call site that sharing a
|
|
11
|
+
* module would just make costHud.ts harder to read for its own surface.
|
|
12
|
+
*
|
|
13
|
+
* Caller labels are minted by runner.ts as `go:<stage.id>` (or
|
|
14
|
+
* `go:<stage.id>:<lens>` for the fanned-out review stage) — see
|
|
15
|
+
* `callerLabel` in runner.ts. This module's `stageKeyForCaller` is the inverse
|
|
16
|
+
* mapping back to a display row.
|
|
17
|
+
*/
|
|
18
|
+
import { usd } from "../costHud.js";
|
|
19
|
+
import { PIPELINE_V1 } from "./stages.js";
|
|
20
|
+
/**
|
|
21
|
+
* Pipeline stages in the order /go actually runs them, DERIVED from
|
|
22
|
+
* PIPELINE_V1 (stages.ts's single source of truth) rather than a hand-copied
|
|
23
|
+
* list — a future stage added there self-registers here in order, with
|
|
24
|
+
* nothing in this file to update or drift out of sync.
|
|
25
|
+
*/
|
|
26
|
+
const STAGE_ORDER = PIPELINE_V1.map((s) => s.id);
|
|
27
|
+
/**
|
|
28
|
+
* Map a `GET /spend` caller label onto its display stage, or undefined for a
|
|
29
|
+
* caller this table does not recognize (surfaced instead as its own row,
|
|
30
|
+
* verbatim, after the known stages — see {@link buildRows}). `go:review` and
|
|
31
|
+
* every `go:review:<lens>` caller fold onto the single "review" row: the three
|
|
32
|
+
* adversarial lenses are one conceptual review pass, and a per-lens
|
|
33
|
+
* breakdown would just be noise in an end-of-run summary.
|
|
34
|
+
*
|
|
35
|
+
* Derives the mapping from STAGE_ORDER (advisor is the one non-pipeline
|
|
36
|
+
* exception, matched exactly) rather than hand-listing each `go:<id>`
|
|
37
|
+
* caller, so a stage added to PIPELINE_V1 needs no update here either: strip
|
|
38
|
+
* the `go:` prefix, take the first `:`-delimited segment (folds `go:review`
|
|
39
|
+
* and every `go:review:<lens>` onto the same "review" segment), and check it
|
|
40
|
+
* against the known stage ids.
|
|
41
|
+
*/
|
|
42
|
+
function stageKeyForCaller(caller) {
|
|
43
|
+
if (caller === "advisor")
|
|
44
|
+
return "advisor";
|
|
45
|
+
if (!caller.startsWith("go:"))
|
|
46
|
+
return undefined;
|
|
47
|
+
const segment = caller.slice(3).split(":")[0];
|
|
48
|
+
return STAGE_ORDER.includes(segment) ? segment : undefined;
|
|
49
|
+
}
|
|
50
|
+
const sumSell = (rows) => rows.reduce((acc, r) => acc + r.sellMillicents, 0);
|
|
51
|
+
/**
|
|
52
|
+
* The tier shown for a (possibly multi-row) line: the rows' COMMON tier when
|
|
53
|
+
* they all billed under the same one, else the tier of whichever contributing
|
|
54
|
+
* row spent the most (a re-ask retry on `efficient`, or a server-side reroute,
|
|
55
|
+
* can leave one caller split across two tiers — the biggest-spend tier is the
|
|
56
|
+
* more honest single label than an arbitrary first-seen one).
|
|
57
|
+
*/
|
|
58
|
+
function bucketTier(rows) {
|
|
59
|
+
const first = rows[0];
|
|
60
|
+
if (!first)
|
|
61
|
+
return "";
|
|
62
|
+
if (rows.every((r) => r.rateTier === first.rateTier))
|
|
63
|
+
return first.rateTier;
|
|
64
|
+
let biggest = first;
|
|
65
|
+
for (const r of rows) {
|
|
66
|
+
if (r.sellMillicents > biggest.sellMillicents)
|
|
67
|
+
biggest = r;
|
|
68
|
+
}
|
|
69
|
+
return biggest.rateTier;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Group spend rows into display rows: known pipeline stages (in pipeline
|
|
73
|
+
* order, review merged), then advisor when present, then every unrecognized
|
|
74
|
+
* caller (e.g. `subagent:foo`, `unattributed`, a bare `driver`) as its own
|
|
75
|
+
* row, sorted by spend descending (ties broken alphabetically, so output is
|
|
76
|
+
* deterministic). A stage with zero recorded dispatches is omitted rather
|
|
77
|
+
* than shown at $0.00: /go always runs map/plan/implement, but review/fix are
|
|
78
|
+
* absent on a run that stopped before reaching them (e.g. `no_changes`).
|
|
79
|
+
*/
|
|
80
|
+
function buildRows(spendRows) {
|
|
81
|
+
const known = new Map();
|
|
82
|
+
const unknown = new Map();
|
|
83
|
+
for (const row of spendRows) {
|
|
84
|
+
const key = stageKeyForCaller(row.caller);
|
|
85
|
+
const bucket = key ?? row.caller;
|
|
86
|
+
const target = key ? known : unknown;
|
|
87
|
+
const list = target.get(bucket) ?? [];
|
|
88
|
+
list.push(row);
|
|
89
|
+
target.set(bucket, list);
|
|
90
|
+
}
|
|
91
|
+
const rows = [];
|
|
92
|
+
for (const stage of STAGE_ORDER) {
|
|
93
|
+
const stageRows = known.get(stage);
|
|
94
|
+
if (!stageRows || stageRows.length === 0)
|
|
95
|
+
continue;
|
|
96
|
+
rows.push({ label: stage, tier: bucketTier(stageRows), sellMillicents: sumSell(stageRows) });
|
|
97
|
+
}
|
|
98
|
+
const advisorRows = known.get("advisor");
|
|
99
|
+
if (advisorRows && advisorRows.length > 0) {
|
|
100
|
+
rows.push({ label: "advisor", tier: bucketTier(advisorRows), sellMillicents: sumSell(advisorRows) });
|
|
101
|
+
}
|
|
102
|
+
const unknownRows = [...unknown.entries()]
|
|
103
|
+
.map(([caller, r]) => ({ label: caller, tier: bucketTier(r), sellMillicents: sumSell(r) }))
|
|
104
|
+
.sort((a, b) => b.sellMillicents - a.sellMillicents || a.label.localeCompare(b.label));
|
|
105
|
+
rows.push(...unknownRows);
|
|
106
|
+
return rows;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* `Run: $A billed. $B at frontier list. NN% saved.` — the "at frontier
|
|
110
|
+
* list" + savings clause only renders when `savingsPercent` resolved (typeof
|
|
111
|
+
* guard, matching formatServerCostLines's own malformed-response caution: a
|
|
112
|
+
* network response is untyped at runtime).
|
|
113
|
+
*
|
|
114
|
+
* `counterfactualIncomplete` and a numeric `savingsPercent` are MUTUALLY
|
|
115
|
+
* EXCLUSIVE by construction on the server (yagniCodeSpend.ts sets
|
|
116
|
+
* `savingsPercent` null whenever `counterfactualIncomplete` is true — there is
|
|
117
|
+
* no honest percentage to report when some billed spend fell on a tier with no
|
|
118
|
+
* Anthropic list-price counterfactual). So `counterfactualIncomplete` is
|
|
119
|
+
* checked on the non-number branch, not as a caveat appended to the number
|
|
120
|
+
* branch (that would be unreachable dead code, and would silently drop the
|
|
121
|
+
* whole savings clause on a partial counterfactual with no explanation).
|
|
122
|
+
*/
|
|
123
|
+
function totalLine(spend) {
|
|
124
|
+
const billed = `$${usd(spend.totalSellMillicents)} billed.`;
|
|
125
|
+
if (typeof spend.savingsPercent !== "number") {
|
|
126
|
+
return spend.counterfactualIncomplete
|
|
127
|
+
? ` Run: ${billed} Savings not shown: some spend billed on an unpriced tier.`
|
|
128
|
+
: ` Run: ${billed}`;
|
|
129
|
+
}
|
|
130
|
+
return ` Run: ${billed} $${usd(spend.totalCounterfactualMillicents)} at frontier list. ${spend.savingsPercent}% saved.`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Render /go's end-of-run, server-priced cost table. Pure, no em-dashes,
|
|
134
|
+
* multi-line (joined with "\n"), leading space on the header/total lines so it
|
|
135
|
+
* reads as a continuation of the preceding recap sentence (matches
|
|
136
|
+
* runCostNote's own leading-space convention in goCommand.ts).
|
|
137
|
+
*
|
|
138
|
+
* Example:
|
|
139
|
+
* ```
|
|
140
|
+
* Run cost (server):
|
|
141
|
+
* map standard $0.05
|
|
142
|
+
* plan peak $0.31
|
|
143
|
+
* implement standard $0.12
|
|
144
|
+
* review peak $0.34
|
|
145
|
+
* fix standard $0.04
|
|
146
|
+
* Run: $0.84 billed. $2.71 at frontier list. 69% saved.
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export function formatRunCostTable(spend) {
|
|
150
|
+
const rows = buildRows(spend.rows);
|
|
151
|
+
const labelWidth = rows.reduce((m, r) => Math.max(m, r.label.length), 0);
|
|
152
|
+
const tierWidth = rows.reduce((m, r) => Math.max(m, r.tier.length), 0);
|
|
153
|
+
const lines = [" Run cost (server):"];
|
|
154
|
+
for (const r of rows) {
|
|
155
|
+
lines.push(` ${r.label.padEnd(labelWidth + 2)}${r.tier.padEnd(tierWidth + 2)}$${usd(r.sellMillicents)}`);
|
|
156
|
+
}
|
|
157
|
+
lines.push(totalLine(spend));
|
|
158
|
+
if (spend.unbilledSellMillicents > 0) {
|
|
159
|
+
// Matches formatServerCostLines' + YagniCodeUsageView.tsx's wording for
|
|
160
|
+
// the same figure.
|
|
161
|
+
lines.push(` Includes $${usd(spend.unbilledSellMillicents)} not yet billed (insufficient credits or a billing error).`);
|
|
162
|
+
}
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=runCostTable.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The /go run registry (spec §3b — detached-by-default).
|
|
3
|
+
*
|
|
4
|
+
* Detached runs outlive the prompt that started them, so /go needs one place
|
|
5
|
+
* that answers "what is running / finished / interrupted?": a module-scoped
|
|
6
|
+
* IN-MEMORY registry (the live truth for THIS process: in-flight guard,
|
|
7
|
+
* MAX_CONCURRENT_RUNS, the completion promise) mirrored to an append-only
|
|
8
|
+
* NDJSON file at `~/.yagni-code/registry.ndjson` (dir 0700, file 0600 —
|
|
9
|
+
* mirrors checkpoint.ts) so a LATER session can list finished runs, spot an
|
|
10
|
+
* interrupted one, and resume into its worktree.
|
|
11
|
+
*
|
|
12
|
+
* The mirror is append-only and torn-line-tolerant (same crash model as
|
|
13
|
+
* checkpoint.ts): every state change appends the FULL row again and readers
|
|
14
|
+
* fold last-write-wins by runId, so a torn final write costs at most the last
|
|
15
|
+
* update, never the file. All writes are fail-soft — registry bookkeeping must
|
|
16
|
+
* never break /go.
|
|
17
|
+
*
|
|
18
|
+
* Liveness is decided by the PURE {@link classifyRunLiveness}: a non-terminal
|
|
19
|
+
* row is `in_flight` when this process holds it, otherwise the run's checkpoint
|
|
20
|
+
* journal decides — recent activity (< 30 min) means it is probably running in
|
|
21
|
+
* another process (`active_elsewhere`), stale means `interrupted` (a resume
|
|
22
|
+
* candidate).
|
|
23
|
+
*/
|
|
24
|
+
import type { CheckpointRecord, StopReason } from "./types.js";
|
|
25
|
+
/** Bound on simultaneously in-flight /go runs in one process (spec §3b). */
|
|
26
|
+
export declare const MAX_CONCURRENT_RUNS = 3;
|
|
27
|
+
/**
|
|
28
|
+
* A non-terminal row whose journal has been quiet this long is treated as
|
|
29
|
+
* INTERRUPTED (its process died) rather than still running elsewhere. Sits
|
|
30
|
+
* comfortably past the 5-min run heartbeat and the server reaper's 30-min
|
|
31
|
+
* staleness window so the two views agree.
|
|
32
|
+
*/
|
|
33
|
+
export declare const JOURNAL_STALE_MS: number;
|
|
34
|
+
/**
|
|
35
|
+
* One registry row (the spec's frozen shape). `status` is `running` until the
|
|
36
|
+
* run reaches a terminal outcome, then the honest stop reason; `cleaned` marks
|
|
37
|
+
* a terminal row whose worktree /go-clean has removed. `runId` is the LOCAL
|
|
38
|
+
* run identity (minted before the backend run row exists — the worktree dir and
|
|
39
|
+
* UI key derive from its first 8 chars), not the backend runs-row id.
|
|
40
|
+
*/
|
|
41
|
+
export interface RunRegistryRow {
|
|
42
|
+
runId: string;
|
|
43
|
+
ticket: string;
|
|
44
|
+
sessionKey: string;
|
|
45
|
+
repo?: string;
|
|
46
|
+
/** Absent for an in-place (--here) run. */
|
|
47
|
+
worktreePath?: string;
|
|
48
|
+
branch?: string;
|
|
49
|
+
status: RunRegistryStatus;
|
|
50
|
+
startedAt: number;
|
|
51
|
+
commitSha?: string;
|
|
52
|
+
prUrl?: string;
|
|
53
|
+
}
|
|
54
|
+
export type RunRegistryStatus = "running" | "cleaned" | StopReason;
|
|
55
|
+
/** Everything except `running` is terminal (the run is not in flight anywhere). */
|
|
56
|
+
export declare function isTerminalStatus(status: RunRegistryStatus): boolean;
|
|
57
|
+
/** How a registry row relates to live execution right now. */
|
|
58
|
+
export type RunLiveness = "in_flight" | "active_elsewhere" | "interrupted" | "terminal";
|
|
59
|
+
/**
|
|
60
|
+
* PURE liveness verdict for one row. `inFlight` is this process's in-memory
|
|
61
|
+
* truth; `lastJournalTs` is the newest checkpoint-journal timestamp for the
|
|
62
|
+
* row's sessionKey (fall back to `startedAt` when the run never journaled).
|
|
63
|
+
*/
|
|
64
|
+
export declare function classifyRunLiveness(row: RunRegistryRow, opts: {
|
|
65
|
+
inFlight: boolean;
|
|
66
|
+
lastJournalTs?: number;
|
|
67
|
+
now: number;
|
|
68
|
+
}): RunLiveness;
|
|
69
|
+
/** The newest checkpoint timestamp in a run's journal (0 when empty). */
|
|
70
|
+
export declare function lastJournalTs(records: CheckpointRecord[]): number;
|
|
71
|
+
/**
|
|
72
|
+
* Fold raw NDJSON mirror lines into rows, last-write-wins by runId, preserving
|
|
73
|
+
* first-seen order. Pure + torn-line-tolerant: unparseable or shape-less lines
|
|
74
|
+
* are dropped (the classic crash artifact), never fatal.
|
|
75
|
+
*/
|
|
76
|
+
export declare function foldRegistryLines(lines: string[]): RunRegistryRow[];
|
|
77
|
+
export declare function _setRegistryHomeForTest(dir: string | null): void;
|
|
78
|
+
/** The append-only NDJSON mirror file. */
|
|
79
|
+
export declare function registryFile(): string;
|
|
80
|
+
/** Where detached run worktrees live (never inside the repo — spec §6). */
|
|
81
|
+
export declare function worktreesDir(): string;
|
|
82
|
+
/** Test seam: clear the in-memory half (simulates a process restart; the mirror survives). */
|
|
83
|
+
export declare function _resetRunRegistryForTest(): void;
|
|
84
|
+
/** Mark a run in-flight in this process and mirror the row. Never throws. */
|
|
85
|
+
export declare function beginRun(row: RunRegistryRow): void;
|
|
86
|
+
/** Attach the detached completion promise to an in-flight run (fail-soft no-op when absent). */
|
|
87
|
+
export declare function trackRunPromise(runId: string, promise: Promise<void>): void;
|
|
88
|
+
/**
|
|
89
|
+
* Settle an in-flight run: merge the terminal patch, drop it from the in-memory
|
|
90
|
+
* active set, and mirror the final row. Safe to call for an unknown runId: the
|
|
91
|
+
* patch lands against the last known mirror row when one exists, or against the
|
|
92
|
+
* patch itself when the caller supplied a full row (the /go-clean path, whose
|
|
93
|
+
* rows may come through an injected registry read).
|
|
94
|
+
*/
|
|
95
|
+
export declare function settleRun(runId: string, patch: Partial<RunRegistryRow> & {
|
|
96
|
+
status: RunRegistryStatus;
|
|
97
|
+
}): void;
|
|
98
|
+
/** How many runs this process currently has in flight. */
|
|
99
|
+
export declare function activeRunCount(): number;
|
|
100
|
+
/** The in-flight row for a ticket in THIS process, if any (the same-ticket guard). */
|
|
101
|
+
export declare function findActiveRunByTicket(ticket: string): RunRegistryRow | undefined;
|
|
102
|
+
/** Is this runId in flight in THIS process? */
|
|
103
|
+
export declare function isRunInFlight(runId: string): boolean;
|
|
104
|
+
/** Test seam: await every in-flight run's completion promise. */
|
|
105
|
+
export declare function _awaitActiveRunsForTest(): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* The merged registry view: the NDJSON mirror folded last-write-wins, with this
|
|
108
|
+
* process's in-memory rows overlaid (they are the fresher truth when a mirror
|
|
109
|
+
* write was lost). Never throws; an unreadable mirror yields the in-memory rows.
|
|
110
|
+
*/
|
|
111
|
+
export declare function loadRegistryRows(): RunRegistryRow[];
|
|
112
|
+
//# sourceMappingURL=runRegistry.d.ts.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The /go run registry (spec §3b — detached-by-default).
|
|
3
|
+
*
|
|
4
|
+
* Detached runs outlive the prompt that started them, so /go needs one place
|
|
5
|
+
* that answers "what is running / finished / interrupted?": a module-scoped
|
|
6
|
+
* IN-MEMORY registry (the live truth for THIS process: in-flight guard,
|
|
7
|
+
* MAX_CONCURRENT_RUNS, the completion promise) mirrored to an append-only
|
|
8
|
+
* NDJSON file at `~/.yagni-code/registry.ndjson` (dir 0700, file 0600 —
|
|
9
|
+
* mirrors checkpoint.ts) so a LATER session can list finished runs, spot an
|
|
10
|
+
* interrupted one, and resume into its worktree.
|
|
11
|
+
*
|
|
12
|
+
* The mirror is append-only and torn-line-tolerant (same crash model as
|
|
13
|
+
* checkpoint.ts): every state change appends the FULL row again and readers
|
|
14
|
+
* fold last-write-wins by runId, so a torn final write costs at most the last
|
|
15
|
+
* update, never the file. All writes are fail-soft — registry bookkeeping must
|
|
16
|
+
* never break /go.
|
|
17
|
+
*
|
|
18
|
+
* Liveness is decided by the PURE {@link classifyRunLiveness}: a non-terminal
|
|
19
|
+
* row is `in_flight` when this process holds it, otherwise the run's checkpoint
|
|
20
|
+
* journal decides — recent activity (< 30 min) means it is probably running in
|
|
21
|
+
* another process (`active_elsewhere`), stale means `interrupted` (a resume
|
|
22
|
+
* candidate).
|
|
23
|
+
*/
|
|
24
|
+
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { codeStateHome } from "../stateHome.js";
|
|
27
|
+
/** Bound on simultaneously in-flight /go runs in one process (spec §3b). */
|
|
28
|
+
export const MAX_CONCURRENT_RUNS = 3;
|
|
29
|
+
/**
|
|
30
|
+
* A non-terminal row whose journal has been quiet this long is treated as
|
|
31
|
+
* INTERRUPTED (its process died) rather than still running elsewhere. Sits
|
|
32
|
+
* comfortably past the 5-min run heartbeat and the server reaper's 30-min
|
|
33
|
+
* staleness window so the two views agree.
|
|
34
|
+
*/
|
|
35
|
+
export const JOURNAL_STALE_MS = 30 * 60_000;
|
|
36
|
+
/** Everything except `running` is terminal (the run is not in flight anywhere). */
|
|
37
|
+
export function isTerminalStatus(status) {
|
|
38
|
+
return status !== "running";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* PURE liveness verdict for one row. `inFlight` is this process's in-memory
|
|
42
|
+
* truth; `lastJournalTs` is the newest checkpoint-journal timestamp for the
|
|
43
|
+
* row's sessionKey (fall back to `startedAt` when the run never journaled).
|
|
44
|
+
*/
|
|
45
|
+
export function classifyRunLiveness(row, opts) {
|
|
46
|
+
if (isTerminalStatus(row.status))
|
|
47
|
+
return "terminal";
|
|
48
|
+
if (opts.inFlight)
|
|
49
|
+
return "in_flight";
|
|
50
|
+
const last = opts.lastJournalTs ?? row.startedAt;
|
|
51
|
+
return opts.now - last > JOURNAL_STALE_MS ? "interrupted" : "active_elsewhere";
|
|
52
|
+
}
|
|
53
|
+
/** The newest checkpoint timestamp in a run's journal (0 when empty). */
|
|
54
|
+
export function lastJournalTs(records) {
|
|
55
|
+
let max = 0;
|
|
56
|
+
for (const r of records) {
|
|
57
|
+
if (typeof r?.ts === "number" && r.ts > max)
|
|
58
|
+
max = r.ts;
|
|
59
|
+
}
|
|
60
|
+
return max;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Fold raw NDJSON mirror lines into rows, last-write-wins by runId, preserving
|
|
64
|
+
* first-seen order. Pure + torn-line-tolerant: unparseable or shape-less lines
|
|
65
|
+
* are dropped (the classic crash artifact), never fatal.
|
|
66
|
+
*/
|
|
67
|
+
export function foldRegistryLines(lines) {
|
|
68
|
+
const byId = new Map();
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
if (!line.trim())
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
const row = JSON.parse(line);
|
|
74
|
+
if (!row || typeof row.runId !== "string" || typeof row.ticket !== "string")
|
|
75
|
+
continue;
|
|
76
|
+
if (typeof row.sessionKey !== "string" || typeof row.status !== "string")
|
|
77
|
+
continue;
|
|
78
|
+
byId.set(row.runId, row);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* torn/garbage line — drop it, keep the rest */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...byId.values()];
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// State-dir seams (mirrors checkpoint.ts `_setCheckpointHomeForTest`).
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
let homeOverride = null;
|
|
90
|
+
export function _setRegistryHomeForTest(dir) {
|
|
91
|
+
homeOverride = dir;
|
|
92
|
+
}
|
|
93
|
+
function registryHome() {
|
|
94
|
+
return codeStateHome(homeOverride);
|
|
95
|
+
}
|
|
96
|
+
/** The append-only NDJSON mirror file. */
|
|
97
|
+
export function registryFile() {
|
|
98
|
+
return join(registryHome(), "registry.ndjson");
|
|
99
|
+
}
|
|
100
|
+
/** Where detached run worktrees live (never inside the repo — spec §6). */
|
|
101
|
+
export function worktreesDir() {
|
|
102
|
+
return join(registryHome(), "worktrees");
|
|
103
|
+
}
|
|
104
|
+
const active = new Map();
|
|
105
|
+
/** Test seam: clear the in-memory half (simulates a process restart; the mirror survives). */
|
|
106
|
+
export function _resetRunRegistryForTest() {
|
|
107
|
+
active.clear();
|
|
108
|
+
}
|
|
109
|
+
/** Fail-soft append of one full row to the mirror (self-heals a torn previous write). */
|
|
110
|
+
function appendRow(row) {
|
|
111
|
+
try {
|
|
112
|
+
mkdirSync(registryHome(), { recursive: true, mode: 0o700 });
|
|
113
|
+
const file = registryFile();
|
|
114
|
+
let prefix = "";
|
|
115
|
+
try {
|
|
116
|
+
const existing = readFileSync(file);
|
|
117
|
+
if (existing.length > 0 && existing[existing.length - 1] !== 0x0a)
|
|
118
|
+
prefix = "\n";
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
/* no file yet — nothing to heal */
|
|
122
|
+
}
|
|
123
|
+
appendFileSync(file, `${prefix}${JSON.stringify(row)}\n`, { mode: 0o600 });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* fail-soft: registry bookkeeping must never break /go */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Mark a run in-flight in this process and mirror the row. Never throws. */
|
|
130
|
+
export function beginRun(row) {
|
|
131
|
+
active.set(row.runId, { row });
|
|
132
|
+
appendRow(row);
|
|
133
|
+
}
|
|
134
|
+
/** Attach the detached completion promise to an in-flight run (fail-soft no-op when absent). */
|
|
135
|
+
export function trackRunPromise(runId, promise) {
|
|
136
|
+
const entry = active.get(runId);
|
|
137
|
+
if (entry)
|
|
138
|
+
entry.promise = promise;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Settle an in-flight run: merge the terminal patch, drop it from the in-memory
|
|
142
|
+
* active set, and mirror the final row. Safe to call for an unknown runId: the
|
|
143
|
+
* patch lands against the last known mirror row when one exists, or against the
|
|
144
|
+
* patch itself when the caller supplied a full row (the /go-clean path, whose
|
|
145
|
+
* rows may come through an injected registry read).
|
|
146
|
+
*/
|
|
147
|
+
export function settleRun(runId, patch) {
|
|
148
|
+
const entry = active.get(runId);
|
|
149
|
+
const base = entry?.row ??
|
|
150
|
+
loadRegistryRows().find((r) => r.runId === runId) ??
|
|
151
|
+
(isFullRow(patch) ? patch : undefined);
|
|
152
|
+
active.delete(runId);
|
|
153
|
+
if (!base)
|
|
154
|
+
return;
|
|
155
|
+
appendRow({ ...base, ...patch, runId: base.runId ?? runId });
|
|
156
|
+
}
|
|
157
|
+
/** Does a settle patch carry the whole row shape (usable as its own base)? */
|
|
158
|
+
function isFullRow(patch) {
|
|
159
|
+
return (typeof patch.runId === "string" &&
|
|
160
|
+
typeof patch.ticket === "string" &&
|
|
161
|
+
typeof patch.sessionKey === "string" &&
|
|
162
|
+
typeof patch.startedAt === "number");
|
|
163
|
+
}
|
|
164
|
+
/** How many runs this process currently has in flight. */
|
|
165
|
+
export function activeRunCount() {
|
|
166
|
+
return active.size;
|
|
167
|
+
}
|
|
168
|
+
/** The in-flight row for a ticket in THIS process, if any (the same-ticket guard). */
|
|
169
|
+
export function findActiveRunByTicket(ticket) {
|
|
170
|
+
for (const entry of active.values()) {
|
|
171
|
+
if (entry.row.ticket === ticket)
|
|
172
|
+
return entry.row;
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
/** Is this runId in flight in THIS process? */
|
|
177
|
+
export function isRunInFlight(runId) {
|
|
178
|
+
return active.has(runId);
|
|
179
|
+
}
|
|
180
|
+
/** Test seam: await every in-flight run's completion promise. */
|
|
181
|
+
export async function _awaitActiveRunsForTest() {
|
|
182
|
+
await Promise.all([...active.values()].map((e) => e.promise ?? Promise.resolve()));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The merged registry view: the NDJSON mirror folded last-write-wins, with this
|
|
186
|
+
* process's in-memory rows overlaid (they are the fresher truth when a mirror
|
|
187
|
+
* write was lost). Never throws; an unreadable mirror yields the in-memory rows.
|
|
188
|
+
*/
|
|
189
|
+
export function loadRegistryRows() {
|
|
190
|
+
let rows = [];
|
|
191
|
+
try {
|
|
192
|
+
rows = foldRegistryLines(readFileSync(registryFile(), "utf8").split("\n"));
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
rows = [];
|
|
196
|
+
}
|
|
197
|
+
const byId = new Map(rows.map((r) => [r.runId, r]));
|
|
198
|
+
for (const entry of active.values())
|
|
199
|
+
byId.set(entry.row.runId, entry.row);
|
|
200
|
+
return [...byId.values()];
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=runRegistry.js.map
|