@coreplane/switchboard 1.245.0 → 1.246.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/dist/assets/deploy/cloudflare-resident/worker.ts +9 -10
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +17 -1
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +31 -7
- package/dist/assets/src/core/budgets.ts +18 -0
- package/dist/assets/src/core/coordinator/contract.ts +29 -4
- package/dist/assets/src/core/coordinator/driver.ts +7 -1
- package/dist/assets/src/core/reviewVerdict.ts +138 -6
- package/dist/assets/src/core/runEvents.ts +29 -1
- package/dist/assets/src/core/runRecord.ts +38 -16
- package/dist/assets/src/core/ship/coordinator.ts +23 -2
- package/dist/assets/src/core/ship/renewal.ts +10 -3
- package/dist/assets/src/execution/bashTimeout.ts +46 -2
- package/dist/assets/src/execution/residentRefresh.ts +9 -5
- package/dist/assets/src/execution/sandboxErrors.ts +9 -4
- package/dist/assets/web/dist/.vite/manifest.json +468 -468
- package/dist/assets/web/dist/assets/{AppShell-qqs_p96-.js → AppShell-B4EUiSwP.js} +1 -1
- package/dist/assets/web/dist/assets/{CostsPage-Dd9aXxxI.js → CostsPage-D8VjAtuH.js} +1 -1
- package/dist/assets/web/dist/assets/DeliveryPage-DpQi5XUI.js +1 -0
- package/dist/assets/web/dist/assets/{HomePage-VLTFopnl.js → HomePage-CvKuIGqg.js} +1 -1
- package/dist/assets/web/dist/assets/{InputMenu-lQP3ia9D.js → InputMenu-DmqAxkn4.js} +1 -1
- package/dist/assets/web/dist/assets/{NotFoundPage-8ViskpN3.js → NotFoundPage-DDogyiXA.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-D2gHef5v.js → PendingTurnRow-BoSLlR6j.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-nWLN8BOd.js → ResidentDetailPage-CiBdbzkE.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-D3Q48CJW.js → ResidentsIndexPage-DgtHtd9K.js} +1 -1
- package/dist/assets/web/dist/assets/{RunFoldRow-CHy_IfZG.js → RunFoldRow-IF4xbz-3.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-C6kX5om4.js → RunRoutePage-DHYa7rHK.js} +3 -3
- package/dist/assets/web/dist/assets/RunsIndexPage-aWhNTv8r.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-WyeG5tKb.js → RunsTabs-CY_tdGTU.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-B8D5AU4d.js → ScheduledPage-gPEBZjcx.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingSelect-UTHsqUUS.js → SettingSelect-VDanrTZi.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-BoW7Zjzt.js → SettingsPage-CFoltSRD.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-BGHureCJ.js → StatusDot-DA3eTVrT.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-CiP8qPd7.js → Tooltip-BgbCkM12.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-2zEeRBP1.js → UnitRoutePage-DibegaJJ.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-qXhb9ti8.js → angular-html-D_3slZqf.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-DzvdyoUZ.js → angular-ts-D1Fv2cpN.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-Ck7Ru2Sm.js → apl-60Nm_trN.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-CwehvReb.js → astro-CvyaM3Q9.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-WSyVbSFK.js → blade-yan3VBls.js} +1 -1
- package/dist/assets/web/dist/assets/{c-DMHI1wDz.js → c-Ddgepap2.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-BF8AYK8_.js → chapel-CgNY6dxC.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-UAfImuWu.js → cobol-C7q82cdP.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-D54hi96d.js → coffee-DgRMF_ni.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-BRWfLgBc.js → cpp-B8Lf-aeo.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-CSqC5deT.js → crystal-CkA_dhEO.js} +1 -1
- package/dist/assets/web/dist/assets/{css-D4ZuRFGk.js → css-Cyu-C_oA.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-73ZMn_Mg.js → dist-DS-gMjLZ.js} +2 -2
- package/dist/assets/web/dist/assets/{durationTone-D51enaGR.js → durationTone-l6aVrHb8.js} +1 -1
- package/dist/assets/web/dist/assets/{edge-DDPtNbZc.js → edge-BxV3rKNI.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-BTQkQfsJ.js → elixir-pNSEK39d.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-B_PkLzGZ.js → elm-zgwWE33W.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-DfjIMVM_.js → erb-yzcYAHp_.js} +1 -1
- package/dist/assets/web/dist/assets/{eventSource-By0B_w4S.js → eventSource-Ce94ho6M.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-DP8wmn7K.js → git-rebase-Dg7Ous1-.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-G12VK2gK.js → glimmer-js-CbZEwX27.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-D3XiSI8E.js → glimmer-ts-D3NIqALy.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-DOKAhMj-.js → glsl-y4-3WrI2.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-CJB_fmON.js → graphql-D5d0al7K.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-BHqtRWap.js → hack-D8fD6kEl.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DwotyarS.js → haml-BWeFFc-V.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-B6fDaA3U.js → handlebars-gIm5FeE_.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-C5bpQmmI.js → html-derivative-B3ySSBrQ.js} +1 -1
- package/dist/assets/web/dist/assets/{html-s52taj36.js → html-fXJTVLSM.js} +1 -1
- package/dist/assets/web/dist/assets/{http-99zC935r.js → http-DrJfQUmw.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-Bgk-4-0x.js → hurl-Bvbnsf55.js} +1 -1
- package/dist/assets/web/dist/assets/{java-CuV_c5I0.js → java-CUSpb-i-.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-CZlOvSMQ.js → javascript-BWRH5fbY.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-BuXeZ4Tu.js → jinja-D3Ymci8y.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-DIuP0hkG.js → jison-Gt9SO7ZC.js} +1 -1
- package/dist/assets/web/dist/assets/{json-CZ74vfoD.js → json-Cfcblhgn.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-BNjRs8Vj.js → jsx-C0366fFM.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-OCE-fFOg.js → julia-CyBOTbxS.js} +1 -1
- package/dist/assets/web/dist/assets/{just-CtvD87WF.js → just-FPXZcIOd.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-C3kf86OR.js → latex-Bd-c88v0.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-B1WI--Kp.js → liquid-5tu9Hc0k.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-DtoT0gKg.js → lua-BZ5fdPP8.js} +1 -1
- package/dist/assets/web/dist/assets/{main-ClluP-Qf.js → main-B4A6x82d.js} +3 -3
- package/dist/assets/web/dist/assets/{marko-BE2MC6Yt.js → marko-C_mmj4p0.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-BwO4weXJ.js → mdc-CtaNqWjY.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-BTuOgGVC.js → nginx-Ddm5Mb8J.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-CiIUM-oV.js → nim-COhfroYB.js} +1 -1
- package/dist/assets/web/dist/assets/{org-L78DEWB-.js → org-CCHFgmTb.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-KqHOm4Ft.js → perl-CXzOE-Z-.js} +1 -1
- package/dist/assets/web/dist/assets/{php-C41XbEkn.js → php-1V6jAIhU.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-B12-4i-k.js → pug-uS37efts.js} +1 -1
- package/dist/assets/web/dist/assets/{qml--fw2Up5c.js → qml-Bj1A0Ben.js} +1 -1
- package/dist/assets/web/dist/assets/{r-Db1JBXrC.js → r-CMbKbLsS.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BYpVBgbu.js → razor-DVRq_Ndk.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-BMqGLSPc.js → regexp-AbaOr4oB.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-B63n7-Sa.js → rst-Dq_QWgOF.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-D3VI8UYv.js → ruby-CVSsAj7T.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-DxRf8ZAR.js → sas-C-dGfxUi.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-DRRA6d6A.js → scss-DxALxYPA.js} +1 -1
- package/dist/assets/web/dist/assets/{seed-DFfT5S_N.js → seed-BhxEVasY.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-CoOBe3_y.js → shellscript--qXcvQ5L.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-DOV1HYxf.js → shellsession-DeqnW8Ux.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-Acpmpp1W.js → soy-DreoUIMt.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-DeT9N-fA.js → sql-o3i2VTG6.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-DPXVxhL5.js → sseReplay-qF-gpbAY.js} +1 -1
- package/dist/assets/web/dist/assets/{stata-CbxXAMO4.js → stata-CGAT41H7.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-BdKbi4hc.js → surrealql-CBU0QuGV.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-tAcjDM7R.js → svelte-CfHaLZiG.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-CObINNJj.js → templ-D02MUcma.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-BYwpZT1J.js → tex-DjprHYXl.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-BciREL5R.js → ts-tags-DftDOzP-.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-CRiv3Pr3.js → tsx-BOdvPohJ.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-DuZZ1Ej0.js → twig-B_yLS56B.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-jkHz_5HZ.js → typescript-CdPCa4nU.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-DOac4Gal.js → typst-C1oIFsUl.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-CgOX5wXA.js → vue-CcszJEX8.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-Duu99VNw.js → vue-html-DcpxPcuT.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-Bmj_wvRJ.js → vue-vine-D26bIKGj.js} +1 -1
- package/dist/assets/web/dist/assets/{wallClock-DD_fPuSY.js → wallClock-DPTuP2FD.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-BtUIVeWH.js → xml-D8zl6W5P.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-wX71n1E5.js → xsl-D86Bii28.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-CdLdXvmz.js → yaml-g6Px4KFU.js} +1 -1
- package/dist/cli.js +1750 -566
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/DeliveryPage-D90FTjYq.js +0 -1
- package/dist/assets/web/dist/assets/RunsIndexPage-DjxGj1Q5.js +0 -1
|
@@ -291,6 +291,16 @@ export function pushedBranchesOf(events: readonly RunEvent[]): PushedBranch[] {
|
|
|
291
291
|
return [...byRef].map(([ref, pr]) => ({ ref, pr }));
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/** A call a run's ending may have left running in its workspace (harness.md
|
|
295
|
+
* item 13): the id the record pairs by, the tool, and the line the
|
|
296
|
+
* `workspace_torn_down` note names it by — the `tool_call`'s own summary
|
|
297
|
+
* while the record holds it, else the tool and the id. */
|
|
298
|
+
export interface CallInFlight {
|
|
299
|
+
callId: string;
|
|
300
|
+
tool: string;
|
|
301
|
+
summary: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
294
304
|
/** The tool calls a run's ending may have left running in its workspace, in
|
|
295
305
|
* the record's order — what the workspace's release reads to tear the
|
|
296
306
|
* workspace down rather than wait for an idle the command would refuse, and
|
|
@@ -299,26 +309,38 @@ export function pushedBranchesOf(events: readonly RunEvent[]): PushedBranch[] {
|
|
|
299
309
|
* settled, whatever the run's status, and stays cut whatever lands for it
|
|
300
310
|
* later — an unmarked result (a post-turn's bridge reads the earlier
|
|
301
311
|
* execution's aborted settle as its own), or the call announced again under
|
|
302
|
-
* the same id; a call with no result at
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
>();
|
|
312
|
+
* the same id; a call with no result at all was left open by a run that
|
|
313
|
+
* failed, was interrupted or hard-stopped, while on a run that completed or
|
|
314
|
+
* stopped softly it is a relayed tool that ran in the bot or a result lost to
|
|
315
|
+
* a transport gap, nothing running. Calls and results pair by id. The events
|
|
316
|
+
* are the registry's bounded backlog (`runRegistry/backlog.ts` drops the
|
|
317
|
+
* oldest past its count or byte bound), so on a long run the line of a
|
|
318
|
+
* command that hung early may be gone while its cut result — the ending's,
|
|
319
|
+
* landing last — survives: a cut result for a call the record no longer
|
|
320
|
+
* holds is the harness's own word that the call was ended and not settled,
|
|
321
|
+
* and counts by itself, named by its tool and id; an unmarked result for a
|
|
322
|
+
* call never opened counts for nothing. */
|
|
323
|
+
export function callsInFlight(events: readonly RunEvent[], status: RunStatus): CallInFlight[] {
|
|
324
|
+
const calls = new Map<string, { call: CallInFlight; state: "open" | "cut" | "settled" }>();
|
|
316
325
|
for (const e of events) {
|
|
317
326
|
if (e.type === "tool_call" && e.callId !== undefined) {
|
|
318
|
-
|
|
327
|
+
const seen = calls.get(e.callId);
|
|
328
|
+
if (seen?.state === "cut") {
|
|
329
|
+
// Announced again after its cut — or after an orphan cut, the line the
|
|
330
|
+
// backlog evicted now written again: the line is the record's, the
|
|
331
|
+
// state stays the cut's.
|
|
332
|
+
seen.call.tool = e.tool;
|
|
333
|
+
seen.call.summary = e.summary;
|
|
334
|
+
} else calls.set(e.callId, { call: { callId: e.callId, tool: e.tool, summary: e.summary }, state: "open" });
|
|
319
335
|
} else if (e.type === "tool_result" && e.callId !== undefined) {
|
|
320
336
|
const seen = calls.get(e.callId);
|
|
321
|
-
if (seen
|
|
337
|
+
if (seen === undefined) {
|
|
338
|
+
if (e.cut === true)
|
|
339
|
+
calls.set(e.callId, {
|
|
340
|
+
call: { callId: e.callId, tool: e.tool, summary: `${e.tool} (call ${e.callId})` },
|
|
341
|
+
state: "cut",
|
|
342
|
+
});
|
|
343
|
+
} else if (seen.state !== "cut") seen.state = e.cut === true ? "cut" : "settled";
|
|
322
344
|
}
|
|
323
345
|
}
|
|
324
346
|
const orderly = status === "completed" || status === "stopped_soft";
|
|
@@ -600,8 +600,22 @@ export type UnitEnding =
|
|
|
600
600
|
/** What a transition tells the driver beyond the next action: a round boundary
|
|
601
601
|
* the card draws (`shipRoundHeader`) and the run stream records, and the end. */
|
|
602
602
|
export type CoordinatorNote =
|
|
603
|
-
| {
|
|
603
|
+
| {
|
|
604
|
+
type: "round";
|
|
605
|
+
index: number;
|
|
606
|
+
agent: ChildPreset;
|
|
607
|
+
outcome: ShipRoundOutcome;
|
|
608
|
+
/** The severity gate fired on this approve (agent-ship item 9): the
|
|
609
|
+
* findings at or above the level in force, as `id (severity)`. The
|
|
610
|
+
* child's own verdict parser holds an approve to the same level
|
|
611
|
+
* (agent-review item 5a), so this should never be set — when it is, the
|
|
612
|
+
* verdict was parsed at another level (a lost directive, an older
|
|
613
|
+
* record, a harness around `submit_verdict`) and the row, the card and
|
|
614
|
+
* the log say so instead of routing silently into the findings step. */
|
|
615
|
+
gate?: { level: AddressSeverity; findings: string[] };
|
|
616
|
+
}
|
|
604
617
|
| { type: "ended"; ending: UnitEnding };
|
|
618
|
+
type RoundNote = Extract<CoordinatorNote, { type: "round" }>;
|
|
605
619
|
|
|
606
620
|
/** How the pipeline's budget went, in ms: the coding rounds' (round 0 and the
|
|
607
621
|
* findings steps), the review rounds', and everything else (branching,
|
|
@@ -939,7 +953,10 @@ function end(s: UnitPipelineState, ending: UnitEnding, notes: CoordinatorNote[]
|
|
|
939
953
|
return { state: { ...s, phase: ENDED, ending }, notes: [...notes, { type: "ended", ending }] };
|
|
940
954
|
}
|
|
941
955
|
|
|
942
|
-
|
|
956
|
+
/** A gated finding as the gate note names it: `F1 (minor)`. */
|
|
957
|
+
const gateLabel = (f: Finding): string => `${f.id} (${f.severity})`;
|
|
958
|
+
|
|
959
|
+
const roundNote = (round: RoundRef, outcome: ShipRoundOutcome): RoundNote => ({
|
|
943
960
|
type: "round",
|
|
944
961
|
index: round.index,
|
|
945
962
|
agent: presetOf(round.kind),
|
|
@@ -1114,6 +1131,10 @@ function settleReview(
|
|
|
1114
1131
|
const level = next.input.addressSeverity ?? DEFAULT_ADDRESS_SEVERITY;
|
|
1115
1132
|
const gated = findingsAtOrAbove(verdict.findings, level);
|
|
1116
1133
|
if (gated.length > 0) {
|
|
1134
|
+
// The gate fired — which the child's parser should have made impossible
|
|
1135
|
+
// (agent-review item 5a): the round note carries what it caught, so the
|
|
1136
|
+
// mismatch is seen and not just routed around.
|
|
1137
|
+
notes[0] = { ...roundNote(round, verdict.verdict), gate: { level, findings: gated.map(gateLabel) } };
|
|
1117
1138
|
if (mode !== undefined)
|
|
1118
1139
|
return end(
|
|
1119
1140
|
next,
|
|
@@ -24,8 +24,12 @@ export interface ProgressInput {
|
|
|
24
24
|
/** The heads the segment's coding run pushed (`RunRecord.pushed`). */
|
|
25
25
|
pushed: readonly PushedHeadFact[];
|
|
26
26
|
/** The head the segment started from — the previous segment's recorded sha.
|
|
27
|
-
* Absent on a fresh branch, where
|
|
27
|
+
* Absent on a fresh branch, where the base head stands in for it. */
|
|
28
28
|
startHead?: string;
|
|
29
|
+
/** The base branch's head the unit's branch was created at. On a fresh
|
|
30
|
+
* branch (no `startHead`) a push of this head is the branch's creation,
|
|
31
|
+
* not the unit's progress; with neither known, any push is progress. */
|
|
32
|
+
baseHead?: string;
|
|
29
33
|
/** When the segment's lease began; a push stamped before it is not this segment's. */
|
|
30
34
|
leaseStartedAt?: number;
|
|
31
35
|
/** The previous segment's handoff and this one's, when both exist. */
|
|
@@ -53,7 +57,10 @@ export function progressOf(input: ProgressInput): Progress {
|
|
|
53
57
|
if (last !== undefined) {
|
|
54
58
|
const newerThanLease =
|
|
55
59
|
input.leaseStartedAt === undefined || last.at === undefined || last.at >= input.leaseStartedAt;
|
|
56
|
-
|
|
60
|
+
// A fresh branch starts at the base head, so that head is the bar a push
|
|
61
|
+
// must move past when no segment recorded a start.
|
|
62
|
+
const startHead = input.startHead ?? input.baseHead;
|
|
63
|
+
const moved = startHead === undefined || !sameSha(last.sha, startHead);
|
|
57
64
|
if (newerThanLease && moved) return { progressed: true, by: "push", sha: last.sha };
|
|
58
65
|
}
|
|
59
66
|
const pushWhy = `no head newer than the lease's start was pushed to \`${input.branch}\``;
|
|
@@ -95,7 +102,7 @@ export function renewalDecision(input: RenewalInput): RenewalDecision {
|
|
|
95
102
|
const detail =
|
|
96
103
|
input.grant.renewals === 0
|
|
97
104
|
? "the grant holds no renewals"
|
|
98
|
-
: `the grant's ${input.grant.renewals} renewal${input.grant.renewals === 1 ? "" : "s"}
|
|
105
|
+
: `the grant's ${input.grant.renewals} renewal${input.grant.renewals === 1 ? " is" : "s are"} spent`;
|
|
99
106
|
return { renew: false, why: "grant_exhausted", detail, renewalsLeft };
|
|
100
107
|
}
|
|
101
108
|
const cap = input.grant.costCapUsd;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Deliberately free of node: imports so wrangler can bundle it into Workers.
|
|
5
5
|
// The numbers are rows of src/core/budgets.ts (docs/decisions/0046); this file
|
|
6
6
|
// keeps their names for the readers that learned them here.
|
|
7
|
-
import { ALLOWANCES, BASH_COMMAND, MINUTE_MS } from "../core/budgets.js";
|
|
7
|
+
import { ALLOWANCES, ATTACH_REQUEST_MIN_MS, BASH_COMMAND, MINUTE_MS } from "../core/budgets.js";
|
|
8
8
|
|
|
9
9
|
/** Default per-command budget when the caller passes no timeoutMs. */
|
|
10
10
|
export const BASH_TIMEOUT_MS = BASH_COMMAND.defaultMinutes * MINUTE_MS;
|
|
@@ -50,8 +50,10 @@ export function clampBashTimeout(requested: unknown): number {
|
|
|
50
50
|
export type RunBudget =
|
|
51
51
|
{ kind: "unchanged" } | { kind: "clipped"; timeoutMs: number; note: string } | { kind: "exhausted"; note: string };
|
|
52
52
|
|
|
53
|
+
/** Whole seconds for the notes below, never negative: the two clip notes phrase the reserve alike. */
|
|
54
|
+
const secs = (ms: number): number => Math.max(0, Math.round(ms / 1000));
|
|
55
|
+
|
|
53
56
|
export function bashBudgetWithinRun(wantedMs: number, remainingMs: number): RunBudget {
|
|
54
|
-
const secs = (ms: number) => Math.max(0, Math.round(ms / 1000));
|
|
55
57
|
const cap = Math.trunc(remainingMs - RUN_DEADLINE_RESERVE_MS);
|
|
56
58
|
if (cap < BASH_TIMEOUT_MIN_MS) {
|
|
57
59
|
return {
|
|
@@ -69,6 +71,48 @@ export function bashBudgetWithinRun(wantedMs: number, remainingMs: number): RunB
|
|
|
69
71
|
};
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
/** The bound on an attach request an executor opens while its run has
|
|
75
|
+
* `remainingMs` of wall clock left (`ResidentExecutorOptions.remainingMs`):
|
|
76
|
+
* `bounded` by the attach's own default, `BASH_TIMEOUT_MS` — an attach may
|
|
77
|
+
* clone and install deps — clipped to what the run has left less the same
|
|
78
|
+
* write-up reserve a command keeps back, so an attach never holds a run past
|
|
79
|
+
* its lease and the model keeps its write-up; never raised above the default.
|
|
80
|
+
* Inside the reserve, or with less than an attach's floor past it
|
|
81
|
+
* (`ATTACH_REQUEST_MIN_MS`: a bound too short to clone would be cut and
|
|
82
|
+
* struck as a rollout), `exhausted`: the request is not opened at all — the
|
|
83
|
+
* resident would run the attach to its end server-side for a run that is
|
|
84
|
+
* ending, and the caller would get a deadline it could read as waitable — and
|
|
85
|
+
* the note names the run's clock and the bound that refused (the reserve, or
|
|
86
|
+
* the floor past it): it is the one sentence every reader carries, the
|
|
87
|
+
* executor's refusal and the relaunch's note alike. The command's own budget
|
|
88
|
+
* never bounds an attach: the attach is not the command, and the command's
|
|
89
|
+
* one-second floor is the model's to choose. */
|
|
90
|
+
export type AttachBound = { kind: "bounded"; timeoutMs: number } | { kind: "exhausted"; note: string };
|
|
91
|
+
|
|
92
|
+
export function attachBoundWithinRun(remainingMs: number): AttachBound {
|
|
93
|
+
const left = Math.trunc(remainingMs - RUN_DEADLINE_RESERVE_MS);
|
|
94
|
+
// The one sentence every reader of an exhausted bound carries — the
|
|
95
|
+
// executor's refusal, the relaunch's note — worded by the bound that refused.
|
|
96
|
+
if (left <= 0) {
|
|
97
|
+
return {
|
|
98
|
+
kind: "exhausted",
|
|
99
|
+
note:
|
|
100
|
+
`the run has ${secs(remainingMs)}s of wall clock left, inside the ${secs(RUN_DEADLINE_RESERVE_MS)}s ` +
|
|
101
|
+
"write-up reserve, so no attach was opened",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (left < ATTACH_REQUEST_MIN_MS) {
|
|
105
|
+
return {
|
|
106
|
+
kind: "exhausted",
|
|
107
|
+
note:
|
|
108
|
+
`the run has ${secs(remainingMs)}s of wall clock left, only ${secs(left)}s past the ` +
|
|
109
|
+
`${secs(RUN_DEADLINE_RESERVE_MS)}s write-up reserve — under the ${secs(ATTACH_REQUEST_MIN_MS)}s an attach needs — ` +
|
|
110
|
+
"so no attach was opened",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return { kind: "bounded", timeoutMs: Math.min(BASH_TIMEOUT_MS, left) };
|
|
114
|
+
}
|
|
115
|
+
|
|
72
116
|
/** The line a timed-out command shows the model: names the limit that fired
|
|
73
117
|
* and the knob that raises it, so the model can self-correct (re-run with a
|
|
74
118
|
* larger timeoutMs, split the command, or background it) instead of guessing
|
|
@@ -180,11 +180,15 @@ export function killStaleBuildProcessesCommand(user: string, dir: string): strin
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/** Signature of a step killed from OUTSIDE its own budget: the exit status of
|
|
183
|
-
* SIGTERM (128 + 15)
|
|
184
|
-
* a
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
|
|
183
|
+
* SIGTERM (128 + 15) — as a shell reports it (`exit 143`) or as the platform
|
|
184
|
+
* words a container that died under a short exec ("Container exited with
|
|
185
|
+
* unexpected exit code: 143"; seen live on the wake path's ready-stamp probe
|
|
186
|
+
* when a stop landed on it) — bash's "Session terminated" on a killed login
|
|
187
|
+
* shell, or a tool naming the signal. Only 143: the platform's same wording
|
|
188
|
+
* with any other code (1, 137, 139) is a genuine crash and stays a failure.
|
|
189
|
+
* A bare "killed" is NOT enough — compilers and OOM messages say it too —
|
|
190
|
+
* and a step the cycle itself timed out is a real failure however it died. */
|
|
191
|
+
const INTERRUPTION_SIGNATURE = /\bexit 143\b|exit code: 143\b|Session terminated|SIGTERM|^restore-interrupted:/;
|
|
188
192
|
|
|
189
193
|
/** The wordings git gives when the MIRROR itself is broken: its `origin`
|
|
190
194
|
* remote gone from `config`, the repository not a repository, an object store
|
|
@@ -154,10 +154,15 @@ export const SANDBOX_STARTING_EXPLANATION =
|
|
|
154
154
|
"the thread's sandbox container is starting (image pull, boot, runtime) — nothing ran yet; the request is re-sent once it is up";
|
|
155
155
|
|
|
156
156
|
/** The executor waits at most this long for a container to start, whatever
|
|
157
|
-
* the command's own budget: the platform's own start allowances (
|
|
158
|
-
* instance, 90 s for the port) plus a slow image pull fit inside it,
|
|
159
|
-
* 60 s command is never killed by a two-minute start it did not cause.
|
|
160
|
-
|
|
157
|
+
* the command's own budget: the platform's own start allowances (the
|
|
158
|
+
* instance grant, 90 s for the port) plus a slow image pull fit inside it,
|
|
159
|
+
* and a 60 s command is never killed by a two-minute start it did not cause.
|
|
160
|
+
* Ten minutes, not five: under a midday burst the platform admitted about
|
|
161
|
+
* seven starts at once and granted the rest 2.5 to 5 minutes later — two
|
|
162
|
+
* threads of twenty-four died at the five-minute mark with their container
|
|
163
|
+
* a minute away. A run that waits ten minutes for its container starts; one
|
|
164
|
+
* that dies at five does not, and nothing else in the run was in progress. */
|
|
165
|
+
export const SANDBOX_START_WAIT_MAX_MS = 10 * 60_000;
|
|
161
166
|
|
|
162
167
|
/** Backoff between re-sends while a container starts: 5 s, 10 s, then 15 s.
|
|
163
168
|
* A start takes tens of seconds, not minutes, so the poll is denser than the
|