@coreplane/switchboard 1.243.0 → 1.244.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-memory/worker.ts +148 -0
- package/dist/assets/deploy/cloudflare-resident/gc.ts +8 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +433 -117
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +13 -2
- 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 +1 -1
- package/dist/assets/src/core/authz/types.ts +10 -0
- package/dist/assets/src/core/budgets.ts +36 -0
- package/dist/assets/src/core/coordinator/contract.ts +32 -0
- package/dist/assets/src/core/coordinator/driver.ts +75 -12
- package/dist/assets/src/core/delivery.ts +1 -1
- package/dist/assets/src/core/runEvents.ts +50 -7
- package/dist/assets/src/core/runFriction.ts +2 -1
- package/dist/assets/src/core/runRecord.ts +31 -0
- package/dist/assets/src/core/ship/contract.ts +2 -1
- package/dist/assets/src/core/ship/coordinator.ts +211 -13
- package/dist/assets/src/core/ship/renewal.ts +155 -0
- package/dist/assets/src/core/trace/attrs.ts +11 -0
- package/dist/assets/src/core/trace/types.ts +4 -3
- package/dist/assets/src/execution/residentRefresh.ts +24 -4
- package/dist/assets/src/execution/residentText.ts +8 -3
- package/dist/assets/src/mcp/registry.ts +3 -0
- package/dist/assets/web/dist/.vite/manifest.json +417 -417
- package/dist/assets/web/dist/assets/{AppShell-IDMGG6yi.js → AppShell-Ck4hSZDi.js} +1 -1
- package/dist/assets/web/dist/assets/CostsPage-DvhjDMka.js +2 -0
- package/dist/assets/web/dist/assets/DeliveryPage-DBlI_Yq2.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-XFWPxDiJ.js +2 -0
- package/dist/assets/web/dist/assets/{NotFoundPage-C97EhJcQ.js → NotFoundPage-CiGrVOyv.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-BT9RhFZ7.js → PendingTurnRow-BHCSpsPf.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-DACalNVF.js → ResidentDetailPage-C5USV_8P.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-SHPdu6uZ.js → ResidentsIndexPage-CSKisWnX.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-BPMQxTcl.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-CZe9Yodw.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-BfA333Qy.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-BUfdk0lH.js → RunsTabs-BWRHzhtT.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-DJ8HiCPt.js → ScheduledPage-DBaChcJZ.js} +1 -1
- package/dist/assets/web/dist/assets/SettingsPage-CqVi9-Ac.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-Dw0T1M-P.js → StatusDot-_Q_bmReh.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-BbLuIAiS.js → Tooltip-Cafsgd4m.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-DicUG96U.js → UnitRoutePage-CdoBZwtf.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-oBNfPJR0.js → angular-html-Dail07rr.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-BvNwsyWA.js → angular-ts-BReExio8.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-CNUdRlYf.js → apl-DhNO6Yyx.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-Zb0NriSe.js → astro-BYotTY4C.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-qPRVheqq.js → blade-mUxTtD1X.js} +1 -1
- package/dist/assets/web/dist/assets/{c-D8Awx4YO.js → c-DhuiwcSQ.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-Bt72Mhsx.js → chapel-GD2vFtRi.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-BOBacexg.js → cobol-BhFDwkes.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-E4u0liHW.js → coffee-DWUgbE0r.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-q2sLNlul.js → cpp-CO9XFjGW.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-DyWqUnlb.js → crystal-B9pVurpn.js} +1 -1
- package/dist/assets/web/dist/assets/{css-CQY0hFsD.js → css-CFVVV6lI.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-twkFmSUY.js → dist-BtPs2AkQ.js} +2 -2
- package/dist/assets/web/dist/assets/{edge-C1MwhJkX.js → edge-BDhIDQIb.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-Bb3YbHfn.js → elixir-DG3J8WoD.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DAN9IGQw.js → elm-FiOdiaOE.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-BEB8Xlsj.js → erb-vxbtW503.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-tqpjRfxO.js → git-rebase-DINz8M3H.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-Ccbo65zR.js → glimmer-js-yann6r9i.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-B6WBVMpE.js → glimmer-ts-CXTbrZn_.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-tRec3Fcu.js → glsl-DNYC-hq0.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-P8kbxT4F.js → graphql-CwzhHzi9.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-H9Zhkagy.js → hack-BBXF1cwR.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DtEnpn7Z.js → haml-oEqBcsTy.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-DkgPfoAz.js → handlebars-YE5thkT1.js} +1 -1
- package/dist/assets/web/dist/assets/{html-D30RXpIs.js → html-Dp4jRQ1j.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DwozLrEx.js → html-derivative-DCT1GYtQ.js} +1 -1
- package/dist/assets/web/dist/assets/{http-DXuzBAPm.js → http-BPcGbmzJ.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-d1UUJIt_.js → hurl-CQnNHwyd.js} +1 -1
- package/dist/assets/web/dist/assets/{indexRow-B_s5tKyq.js → indexRow-BmK74Vp1.js} +1 -1
- package/dist/assets/web/dist/assets/{java-DL0gWf34.js → java-Dm8Ovj6W.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-Cl7vavnS.js → javascript-Btm9oJK9.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-CfQOWMX9.js → jinja-VZYjWF_U.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-KdYirlqm.js → jison-Dcmjxa9x.js} +1 -1
- package/dist/assets/web/dist/assets/{json-C9cDQ-Qj.js → json-DoWiOGZ8.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CRx5NItd.js → jsx-Dvmo55ma.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-CmsQsZQl.js → julia-d8AtfV26.js} +1 -1
- package/dist/assets/web/dist/assets/{just-CsM3Q8TE.js → just-Cc5EStA-.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BXCh5YRX.js → latex-B5fKKIH7.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-BF2vwK8p.js → liquid-DUYI9JND.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-DcMBATrl.js → lua-BhMwKWpw.js} +1 -1
- package/dist/assets/web/dist/assets/main-C4366PZF.css +1 -0
- package/dist/assets/web/dist/assets/main-DlTNAC6c.js +28 -0
- package/dist/assets/web/dist/assets/{marko-88MndKvG.js → marko-Cnf2cPAi.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-DJ4kVAd8.js → mdc-FDDXoir9.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-CP6mRgtV.js → nginx-q-8U_1jR.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-QQfj3fpF.js → nim-DIA0TUWs.js} +1 -1
- package/dist/assets/web/dist/assets/{org-Bke3eBzc.js → org-CR88hy7S.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-Bkh0N7Kz.js → perl-Bkd8N7ja.js} +1 -1
- package/dist/assets/web/dist/assets/{php-Dnv1Piya.js → php-BOs84mvy.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-D6peFFI7.js → pug-BbpZBb3w.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-D8PEs-C-.js → qml-D2jQpjFE.js} +1 -1
- package/dist/assets/web/dist/assets/{r-B1EL9b_j.js → r-DKzcAQ22.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BsMTIh2b.js → razor-SOQMWcC9.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-HhvC8spD.js → regexp-B6KK4W8d.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-D5paAxpg.js → rst-kPSg-NPf.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-BrQwhLrl.js → ruby-cKS-JYLH.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-afot2B1o.js → sas-DNxzE5gs.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-Efm-mwuG.js → scss-DGcHQ5OO.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-BK0Vv5fT.js → shellscript-CRZeZBa5.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-BGqlMC7N.js → shellsession-DSNEL0HX.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-M0b4UwGM.js → soy-zisNbkDO.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-sxe6IE9j.js → sql-MG06KMMb.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-C9m_EB8J.js → sseReplay-DaRtc7nI.js} +4 -4
- package/dist/assets/web/dist/assets/{stata-nPF_ddLP.js → stata-CB_wKwv9.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-D_GrC6u7.js → surrealql-DYdSt7re.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-DwL1AtNP.js → svelte-DHO-F7c7.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-7s7LTDkc.js → templ-CUL5L4Lr.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-Bx-5fMxe.js → tex-_JNEKjVW.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-DUMJnke_.js → ts-tags--9EpEV_8.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-BN8biPDe.js → tsx-BcNq2x2u.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-8nIu84TN.js → twig-EsY5Dk_D.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BooSPq_S.js → typescript-CyA_HiUH.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-CTBiBsem.js → typst-SzaHlDt4.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-C6Ft4Lea.js → vue-D3cn5acV.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-Ba36dD5D.js → vue-html-6MxhUxOa.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-NkFexVo2.js → vue-vine-qJnqhgo9.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-C_THnHXZ.js → xml-DyRpC7xv.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-D8G5xqjY.js → xsl-BXMRPAsg.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-jAMIJzge.js → yaml-BNmPCtAN.js} +1 -1
- package/dist/cli.js +2990 -706
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/CostsPage-CwXOmkeQ.js +0 -2
- package/dist/assets/web/dist/assets/DeliveryPage-n1tz5I_v.js +0 -1
- package/dist/assets/web/dist/assets/HomePage-PRxjQiGG.js +0 -2
- package/dist/assets/web/dist/assets/RunFoldRow-0SdOmOr5.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-BaFS2p8I.js +0 -9
- package/dist/assets/web/dist/assets/RunsIndexPage-DYI-iALj.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-DLiN5IgY.js +0 -1
- package/dist/assets/web/dist/assets/main-B4kEF3Sg.css +0 -1
- package/dist/assets/web/dist/assets/main-d-w-tIKt.js +0 -28
|
@@ -30,11 +30,23 @@
|
|
|
30
30
|
// arrive in the input rather than from the agent registry.
|
|
31
31
|
|
|
32
32
|
import type { ShipRoundOutcome } from "../runEvents.js";
|
|
33
|
+
import type { Handoff } from "./handoff.js";
|
|
34
|
+
import { progressOf, renderRenewal, renewalDecision, type PushedHeadFact, type RenewalDecision } from "./renewal.js";
|
|
33
35
|
import type { RunStatus } from "../runRecord.js";
|
|
34
36
|
import { normalizeHead, sameCommit } from "../reviewedHead.js";
|
|
35
37
|
import { formatFinding, type Finding, type FindingDisposition, type ReviewVerdictKind } from "../reviewVerdict.js";
|
|
36
38
|
import { parsePlanUnit, planUnitIds } from "./contract.js";
|
|
37
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
carve,
|
|
41
|
+
DEFAULT_GRANT,
|
|
42
|
+
loopPosition,
|
|
43
|
+
MINUTE_MS,
|
|
44
|
+
SHIP_WAIT,
|
|
45
|
+
type Carve,
|
|
46
|
+
type Grant,
|
|
47
|
+
type GrantSource,
|
|
48
|
+
type Loop,
|
|
49
|
+
} from "../budgets.js";
|
|
38
50
|
|
|
39
51
|
const MIN = MINUTE_MS;
|
|
40
52
|
|
|
@@ -391,7 +403,14 @@ export function presetOf(kind: RoundKind): ChildPreset {
|
|
|
391
403
|
* turn: the unit's contract, or the review turn from the pull request and the
|
|
392
404
|
* prior rounds' records, or the findings message from the review run's verdict. */
|
|
393
405
|
export type Brief =
|
|
394
|
-
| {
|
|
406
|
+
| {
|
|
407
|
+
kind: "contract";
|
|
408
|
+
unit: string;
|
|
409
|
+
rebase: { branch: string; onto: string };
|
|
410
|
+
/** A renewal's segment (decision 0046): the child continues the previous
|
|
411
|
+
* segment's work from `from`, briefed with that run's write-up and handoff. */
|
|
412
|
+
continue?: { segment: number; from?: string; previousRunId?: string };
|
|
413
|
+
}
|
|
395
414
|
| {
|
|
396
415
|
kind: "review";
|
|
397
416
|
unit: string;
|
|
@@ -455,6 +474,13 @@ export type ChildFacts =
|
|
|
455
474
|
/** The dispositions a coding run recorded, as it submitted them: the machine matches them to the round's findings. */
|
|
456
475
|
dispositions?: FindingDisposition[];
|
|
457
476
|
handoff?: boolean;
|
|
477
|
+
/** The renewal's facts (decision 0046): the heads the run pushed, when
|
|
478
|
+
* its lease began, what it cost (null when a model had no price) and
|
|
479
|
+
* the handoff's lists — progress is read off these, never asked. */
|
|
480
|
+
pushed?: PushedHeadFact[];
|
|
481
|
+
leaseStartedAt?: number;
|
|
482
|
+
costUsd?: number | null;
|
|
483
|
+
handoffLists?: Handoff;
|
|
458
484
|
};
|
|
459
485
|
|
|
460
486
|
/** What heads the unit's branch on GitHub: nothing, an open pull request, or —
|
|
@@ -524,7 +550,38 @@ export type UnitEnding =
|
|
|
524
550
|
/** A changes-requested review posted this round before the stop. */
|
|
525
551
|
postedReview?: boolean;
|
|
526
552
|
}
|
|
527
|
-
| {
|
|
553
|
+
| {
|
|
554
|
+
kind: "aborted";
|
|
555
|
+
reason: string;
|
|
556
|
+
round?: RoundRef;
|
|
557
|
+
reviewRounds: number;
|
|
558
|
+
finalReply?: string;
|
|
559
|
+
/** A round-0 end without a pull request that was judged for renewal and
|
|
560
|
+
* refused: the decision and the card's sentence (decision 0046). */
|
|
561
|
+
renewal?: { decision: Extract<RenewalDecision, { renew: false }>; line: string };
|
|
562
|
+
}
|
|
563
|
+
/** The coding round ended at its lease with the unit unfinished, the row
|
|
564
|
+
* showed progress and the grant renewed: this segment is over and the next
|
|
565
|
+
* opens in the same thread from `from` under a fresh lease, the coding
|
|
566
|
+
* child's write-up as its request (decision 0046, Renewal). */
|
|
567
|
+
| {
|
|
568
|
+
kind: "continued";
|
|
569
|
+
round: RoundRef;
|
|
570
|
+
/** The coding child whose write-up and handoff the continuation is briefed with. */
|
|
571
|
+
runId: string;
|
|
572
|
+
/** The segment the renewal opens (the first segment is 1). */
|
|
573
|
+
segment: number;
|
|
574
|
+
from?: string;
|
|
575
|
+
renewalsLeft: number;
|
|
576
|
+
/** The session's spend so far, this segment's children included; null when a run's model had no price. */
|
|
577
|
+
spendUsd: number | null;
|
|
578
|
+
handoff?: Handoff;
|
|
579
|
+
/** The coding child's write-up: the checkpoint the continuation is briefed with. */
|
|
580
|
+
finalReply?: string;
|
|
581
|
+
line: string;
|
|
582
|
+
reviewRounds: number;
|
|
583
|
+
spent: ShipBudgetSpent;
|
|
584
|
+
}
|
|
528
585
|
| { kind: "no_verdict"; round: RoundRef; reviewRounds: number; finalReply?: string }
|
|
529
586
|
| { kind: "interrupted"; round: RoundRef; runId: string; reviewRounds: number }
|
|
530
587
|
| { kind: "refused"; refusal: string; message?: string; round: RoundRef; reviewRounds: number };
|
|
@@ -562,6 +619,16 @@ export function findingsAtOrAbove(findings: readonly Finding[], level: AddressSe
|
|
|
562
619
|
return findings.filter((f) => severityRank(f.severity) <= severityRank(level));
|
|
563
620
|
}
|
|
564
621
|
|
|
622
|
+
export interface UnitSession {
|
|
623
|
+
segment: number;
|
|
624
|
+
renewalsSpent: number;
|
|
625
|
+
spendUsd: number | null;
|
|
626
|
+
continueFrom?: string;
|
|
627
|
+
/** The previous segment's coding run: its write-up and handoff brief the continuation. */
|
|
628
|
+
previousRunId?: string;
|
|
629
|
+
previousHandoff?: Handoff;
|
|
630
|
+
}
|
|
631
|
+
|
|
565
632
|
export interface UnitPipelineInput {
|
|
566
633
|
unit: { id: string; branch: string };
|
|
567
634
|
repo: string;
|
|
@@ -578,6 +645,18 @@ export interface UnitPipelineInput {
|
|
|
578
645
|
addressSeverity?: AddressSeverity;
|
|
579
646
|
/** Which layer set the level in force; named in the round header and the ending. */
|
|
580
647
|
addressSeveritySource?: AddressSeveritySource;
|
|
648
|
+
/** The grant the request carried (decision 0046, the renewable lease):
|
|
649
|
+
* renewals and a cost cap, and which layer granted it — named in the report;
|
|
650
|
+
* absent reads as zero renewals and no cap, the org's. Nothing here spends
|
|
651
|
+
* it yet: the renewal decision is the segment's end, not this unit's. */
|
|
652
|
+
grant?: Grant;
|
|
653
|
+
grantSource?: GrantSource;
|
|
654
|
+
/** The segment this pipeline runs (decision 0046, Renewal): absent for the
|
|
655
|
+
* first; a renewal's carries its number, the renewals spent before it, the
|
|
656
|
+
* session's spend so far, the sha it continues from and the previous
|
|
657
|
+
* segment's handoff. Every step name of a later segment is prefixed with
|
|
658
|
+
* it, so the Workflow's durable steps never collide across segments. */
|
|
659
|
+
session?: UnitSession;
|
|
581
660
|
/** The instance's mark (agent-ship item 16): a generated one-unit plan — a
|
|
582
661
|
* `plan` with an id and no `path` — whose unit runs in the requesting
|
|
583
662
|
* thread and is re-issued with the request's own text, never a plan path. */
|
|
@@ -606,6 +685,10 @@ type Phase =
|
|
|
606
685
|
runId: string;
|
|
607
686
|
childHead?: string;
|
|
608
687
|
finalReply?: string;
|
|
688
|
+
/** The renewal's facts off the child's record (decision 0046): its pushed heads, its lease's start, its handoff. */
|
|
689
|
+
childPushed?: PushedHeadFact[];
|
|
690
|
+
childLeaseStartedAt?: number;
|
|
691
|
+
childHandoff?: Handoff;
|
|
609
692
|
/** The coding child died (`failed` or `interrupted`) after it may have
|
|
610
693
|
* pushed: the pr-check recovers a pushed branch by opening its pull
|
|
611
694
|
* request; with nothing pushed the unit ends with the child's own reason. */
|
|
@@ -639,6 +722,9 @@ export interface UnitPipelineState {
|
|
|
639
722
|
readonly lastCodingRunId?: string;
|
|
640
723
|
/** How the budget went so far, accrued as each answer moves the clock. */
|
|
641
724
|
readonly spentMs: ShipBudgetSpent;
|
|
725
|
+
/** The session's dollars so far: the input's from earlier segments plus each
|
|
726
|
+
* child's cost as its record is read; null once any run's cost is unknown. */
|
|
727
|
+
readonly spendUsd: number | null;
|
|
642
728
|
readonly ending?: UnitEnding;
|
|
643
729
|
}
|
|
644
730
|
|
|
@@ -674,6 +760,7 @@ export function openUnitPipeline(input: UnitPipelineInput, at: number): UnitPipe
|
|
|
674
760
|
phase: { at: "pre-check" },
|
|
675
761
|
reviewRounds: 0,
|
|
676
762
|
spentMs: { coding: 0, review: 0, waiting: 0 },
|
|
763
|
+
spendUsd: input.session?.spendUsd ?? 0,
|
|
677
764
|
findingsByRound: {},
|
|
678
765
|
dispositionsByRound: {},
|
|
679
766
|
reviewRunByRound: {},
|
|
@@ -713,12 +800,33 @@ function roundCarve(s: UnitPipelineState, round: RoundRef): Carve {
|
|
|
713
800
|
return carve(remainingMs(s), { kind, index: loopPosition(loopOf(s), kind, round.index) }, loopOf(s));
|
|
714
801
|
}
|
|
715
802
|
|
|
716
|
-
|
|
803
|
+
/** The prefix every step of this pipeline is named under: the unit id, and for
|
|
804
|
+
* a renewal's segment the segment too (`U10/s2/…`), so the Workflow's durable
|
|
805
|
+
* step cache never hands segment two the answers of segment one. */
|
|
806
|
+
export const stepPrefixOf = (unit: string, session: UnitSession | undefined): string =>
|
|
807
|
+
session !== undefined && session.segment > 1 ? `${unit}/s${session.segment}` : unit;
|
|
808
|
+
const stepPrefix = (s: UnitPipelineState) => stepPrefixOf(s.input.unit.id, s.input.session);
|
|
809
|
+
const roundStep = (s: UnitPipelineState, round: RoundRef) => `${stepPrefix(s)}/${round.index}/${round.kind}`;
|
|
717
810
|
|
|
718
811
|
function briefFor(s: UnitPipelineState, round: RoundRef): Brief {
|
|
719
812
|
const unit = s.input.unit.id;
|
|
720
|
-
if (round.kind === "coding")
|
|
721
|
-
|
|
813
|
+
if (round.kind === "coding") {
|
|
814
|
+
const session = s.input.session;
|
|
815
|
+
return {
|
|
816
|
+
kind: "contract",
|
|
817
|
+
unit,
|
|
818
|
+
rebase: { branch: s.input.unit.branch, onto: s.input.base },
|
|
819
|
+
...(session !== undefined && session.segment > 1
|
|
820
|
+
? {
|
|
821
|
+
continue: {
|
|
822
|
+
segment: session.segment,
|
|
823
|
+
...(session.continueFrom !== undefined ? { from: session.continueFrom } : {}),
|
|
824
|
+
...(session.previousRunId !== undefined ? { previousRunId: session.previousRunId } : {}),
|
|
825
|
+
},
|
|
826
|
+
}
|
|
827
|
+
: {}),
|
|
828
|
+
};
|
|
829
|
+
}
|
|
722
830
|
const pr = s.pr!.number;
|
|
723
831
|
if (round.kind === "findings") return { kind: "findings", unit, pr, reviewRunId: s.reviewRunByRound[round.index]! };
|
|
724
832
|
const priorReview = s.reviewRunByRound[round.index - 1];
|
|
@@ -754,7 +862,7 @@ export function matchDispositions(
|
|
|
754
862
|
/** The step the machine is at. Pure over the state: asked before every step
|
|
755
863
|
* and again after a replay, it names the same step for the same state. */
|
|
756
864
|
export function nextAction(s: UnitPipelineState): CoordinatorAction {
|
|
757
|
-
const unit = s
|
|
865
|
+
const unit = stepPrefix(s);
|
|
758
866
|
const p = s.phase;
|
|
759
867
|
switch (p.at) {
|
|
760
868
|
case "pre-check":
|
|
@@ -815,6 +923,14 @@ interface Transition {
|
|
|
815
923
|
|
|
816
924
|
const ENDED: Phase = { at: "ended" };
|
|
817
925
|
|
|
926
|
+
/** The session's dollars after one more child: unknown (null) once any run's
|
|
927
|
+
* cost is — a total that left a run out would understate the spend a cap
|
|
928
|
+
* judges — and a record without the field counts as unknown too. */
|
|
929
|
+
function addSpend(sum: number | null, cost: number | null | undefined): number | null {
|
|
930
|
+
if (sum === null || cost === null || cost === undefined) return null;
|
|
931
|
+
return sum + cost;
|
|
932
|
+
}
|
|
933
|
+
|
|
818
934
|
function end(s: UnitPipelineState, ending: UnitEnding, notes: CoordinatorNote[] = []): Transition {
|
|
819
935
|
return { state: { ...s, phase: ENDED, ending }, notes: [...notes, { type: "ended", ending }] };
|
|
820
936
|
}
|
|
@@ -885,6 +1001,7 @@ function settleCoding(
|
|
|
885
1001
|
// round's findings enter the state (agent-ship item 6).
|
|
886
1002
|
let next: UnitPipelineState = {
|
|
887
1003
|
...s,
|
|
1004
|
+
spendUsd: addSpend(s.spendUsd, facts.costUsd),
|
|
888
1005
|
...(facts.pr !== undefined ? { pr: { number: facts.pr.number, url: facts.pr.url } } : {}),
|
|
889
1006
|
...(round.kind === "findings" && facts.dispositions !== undefined
|
|
890
1007
|
? {
|
|
@@ -922,6 +1039,9 @@ function settleCoding(
|
|
|
922
1039
|
runId,
|
|
923
1040
|
...(facts.headSha !== undefined ? { childHead: facts.headSha } : {}),
|
|
924
1041
|
...(facts.finalReply !== undefined ? { finalReply: facts.finalReply } : {}),
|
|
1042
|
+
...(facts.pushed !== undefined ? { childPushed: facts.pushed } : {}),
|
|
1043
|
+
...(facts.leaseStartedAt !== undefined ? { childLeaseStartedAt: facts.leaseStartedAt } : {}),
|
|
1044
|
+
...(facts.handoffLists !== undefined ? { childHandoff: facts.handoffLists } : {}),
|
|
925
1045
|
},
|
|
926
1046
|
};
|
|
927
1047
|
return { state: next, notes: [] };
|
|
@@ -961,6 +1081,7 @@ function settleReview(
|
|
|
961
1081
|
const verdict = facts.verdict;
|
|
962
1082
|
const next: UnitPipelineState = {
|
|
963
1083
|
...s,
|
|
1084
|
+
spendUsd: addSpend(s.spendUsd, facts.costUsd),
|
|
964
1085
|
findingsByRound: { ...s.findingsByRound, [round.index]: verdict.findings },
|
|
965
1086
|
...(facts.reviewHead !== undefined ? { lastReviewHead: facts.reviewHead } : {}),
|
|
966
1087
|
...(verdict.summary !== undefined ? { lastVerdictSummary: verdict.summary } : {}),
|
|
@@ -1122,15 +1243,72 @@ function settlePrCheck(s: UnitPipelineState, phase: Extract<Phase, { at: "pr-che
|
|
|
1122
1243
|
[roundNote(round, "aborted")],
|
|
1123
1244
|
);
|
|
1124
1245
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1246
|
+
// Round 0 ended without a pull request: the segment is over with the unit
|
|
1247
|
+
// unfinished, and the grant decides whether the next opens (decision
|
|
1248
|
+
// 0046, Renewal). Progress is read off the child's record — a head pushed
|
|
1249
|
+
// to the unit's branch since the segment started, or a handoff that moved —
|
|
1250
|
+
// never off its words; the decision then asks the grant's count, the cap
|
|
1251
|
+
// and the fit, in that order, and a refusal names the clause. A plain
|
|
1252
|
+
// abort keeps its old shape when nothing was pushed under a grant of zero:
|
|
1253
|
+
// a clarifying question is not a stop to explain.
|
|
1254
|
+
if (round.index === 0) {
|
|
1255
|
+
const grant = s.input.grant ?? DEFAULT_GRANT;
|
|
1256
|
+
const session = s.input.session;
|
|
1257
|
+
const progress = progressOf({
|
|
1258
|
+
branch: s.input.unit.branch,
|
|
1259
|
+
pushed: phase.childPushed ?? [],
|
|
1260
|
+
...(session?.continueFrom !== undefined ? { startHead: session.continueFrom } : {}),
|
|
1261
|
+
...(phase.childLeaseStartedAt !== undefined ? { leaseStartedAt: phase.childLeaseStartedAt } : {}),
|
|
1262
|
+
...(session?.previousHandoff !== undefined && phase.childHandoff !== undefined
|
|
1263
|
+
? { handoff: { previous: session.previousHandoff, current: phase.childHandoff } }
|
|
1264
|
+
: {}),
|
|
1265
|
+
});
|
|
1266
|
+
const decision = renewalDecision({
|
|
1267
|
+
grant,
|
|
1268
|
+
renewalsSpent: session?.renewalsSpent ?? 0,
|
|
1269
|
+
spendUsd: s.spendUsd,
|
|
1270
|
+
progress,
|
|
1271
|
+
pipeline: s.input.caps,
|
|
1272
|
+
});
|
|
1273
|
+
const line = renderRenewal(decision, grant);
|
|
1274
|
+
if (decision.renew)
|
|
1275
|
+
return end(
|
|
1276
|
+
s,
|
|
1277
|
+
{
|
|
1278
|
+
kind: "continued",
|
|
1279
|
+
round,
|
|
1280
|
+
runId: phase.runId,
|
|
1281
|
+
segment: decision.segment,
|
|
1282
|
+
...(decision.from !== undefined ? { from: decision.from } : {}),
|
|
1283
|
+
renewalsLeft: decision.renewalsLeft,
|
|
1284
|
+
spendUsd: s.spendUsd,
|
|
1285
|
+
...(phase.childHandoff !== undefined ? { handoff: phase.childHandoff } : {}),
|
|
1286
|
+
...(phase.finalReply !== undefined ? { finalReply: phase.finalReply } : {}),
|
|
1287
|
+
line,
|
|
1288
|
+
reviewRounds: s.reviewRounds,
|
|
1289
|
+
spent: s.spentMs,
|
|
1290
|
+
},
|
|
1291
|
+
[roundNote(round, "continued")],
|
|
1292
|
+
);
|
|
1293
|
+
const judged = grant.renewals > 0 || progress.progressed;
|
|
1294
|
+
return end(
|
|
1295
|
+
s,
|
|
1296
|
+
{
|
|
1297
|
+
kind: "aborted",
|
|
1298
|
+
reason: `⚠️ Ship ended at round 0: the coding round ended without opening a pull request (a clarifying question, a budget write-up, an unproven push or a description-less push ends the pipeline here). No review round ran.`,
|
|
1299
|
+
round,
|
|
1300
|
+
reviewRounds: s.reviewRounds,
|
|
1301
|
+
...(phase.finalReply !== undefined ? { finalReply: phase.finalReply } : {}),
|
|
1302
|
+
...(judged ? { renewal: { decision, line } } : {}),
|
|
1303
|
+
},
|
|
1304
|
+
[roundNote(round, "aborted")],
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1129
1307
|
return end(
|
|
1130
1308
|
s,
|
|
1131
1309
|
{
|
|
1132
1310
|
kind: "aborted",
|
|
1133
|
-
reason,
|
|
1311
|
+
reason: `⚠️ Round ${round.index}'s findings step left no open pull request heading \`${s.input.unit.branch}\` — the pull request was closed out from under the pipeline and none was reopened, so there is nothing to re-review.`,
|
|
1134
1312
|
round,
|
|
1135
1313
|
reviewRounds: s.reviewRounds,
|
|
1136
1314
|
...(phase.finalReply !== undefined ? { finalReply: phase.finalReply } : {}),
|
|
@@ -1475,6 +1653,12 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1475
1653
|
// stated decision, never a silent one.
|
|
1476
1654
|
const level = s.input.addressSeverity ?? DEFAULT_ADDRESS_SEVERITY;
|
|
1477
1655
|
const levelLine = `Severity addressed: ${level} and above (set by ${s.input.addressSeveritySource ?? "org"}).`;
|
|
1656
|
+
// The grant as the instance carries it (decision 0046): spent of granted,
|
|
1657
|
+
// the cap when one is set, and who granted it — zero of zero until a scope
|
|
1658
|
+
// or a directive says otherwise, and nothing spends it before the renewal
|
|
1659
|
+
// decision exists.
|
|
1660
|
+
const grant = s.input.grant ?? DEFAULT_GRANT;
|
|
1661
|
+
const grantLine = `Renewals: 0 of ${grant.renewals} spent${grant.costCapUsd !== undefined ? `, cost cap $${grant.costCapUsd}` : ""} (granted by ${s.input.grantSource ?? "org"}).`;
|
|
1478
1662
|
const lastFindings = s.findingsByRound[e.reviewRounds] ?? [];
|
|
1479
1663
|
const skipped = lastFindings.filter((f) => !findingsAtOrAbove([f], level).length);
|
|
1480
1664
|
const skippedLine =
|
|
@@ -1490,6 +1674,7 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1490
1674
|
`✅ Merged after ${rounds}: ${e.pr.url} (squash \`${e.sha.slice(0, 7)}\`) — merged by the plan runner under \`plan:merge\`: the review approved at this head and the guards were green.`,
|
|
1491
1675
|
verdictLine,
|
|
1492
1676
|
levelLine,
|
|
1677
|
+
grantLine,
|
|
1493
1678
|
...(skippedLine ? [skippedLine] : []),
|
|
1494
1679
|
declinedLine,
|
|
1495
1680
|
].join("\n");
|
|
@@ -1498,6 +1683,7 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1498
1683
|
`✅ Merge-ready after ${rounds}: ${e.pr.url}`,
|
|
1499
1684
|
verdictLine,
|
|
1500
1685
|
levelLine,
|
|
1686
|
+
grantLine,
|
|
1501
1687
|
...(skippedLine ? [skippedLine] : []),
|
|
1502
1688
|
declinedLine,
|
|
1503
1689
|
// What the driver read at the approved head when it composed this
|
|
@@ -1543,7 +1729,19 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1543
1729
|
reissue,
|
|
1544
1730
|
]);
|
|
1545
1731
|
case "aborted":
|
|
1546
|
-
return join([
|
|
1732
|
+
return join([
|
|
1733
|
+
e.finalReply,
|
|
1734
|
+
e.reason,
|
|
1735
|
+
e.renewal !== undefined ? `🔁 Not renewed: ${e.renewal.line}.` : undefined,
|
|
1736
|
+
`⚠️ Ship aborted after ${rounds}.`,
|
|
1737
|
+
reissue,
|
|
1738
|
+
]);
|
|
1739
|
+
case "continued":
|
|
1740
|
+
return join([
|
|
1741
|
+
e.finalReply,
|
|
1742
|
+
`🔁 Segment ${e.segment - 1} ended at its lease with the unit unfinished — ${e.line}. Segment ${e.segment} opens in this thread${e.from !== undefined ? ` from \`${e.from.slice(0, 7)}\`` : ""} under a fresh ${s.input.caps.maxMinutes}-minute lease, with this segment's write-up as its request; ${e.renewalsLeft} renewal${e.renewalsLeft === 1 ? "" : "s"} remain${e.spendUsd !== null ? `, $${e.spendUsd.toFixed(2)} spent so far` : ""}.`,
|
|
1743
|
+
budgetSplitLine(e.spent, s.input.caps.maxMinutes),
|
|
1744
|
+
]);
|
|
1547
1745
|
case "no_verdict":
|
|
1548
1746
|
return join([
|
|
1549
1747
|
`⚠️ Review round ${e.round.index} ended without a submitted verdict (budget, refusal, or stop) — ship never converts that into a request for changes, so no findings step ran.`,
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// The renewal decision (decision 0046, Renewal: the grant decides, the lease
|
|
2
|
+
// continues). A lease is sized by the infrastructure; the problem is bounded by
|
|
3
|
+
// the grant. When a segment ends with its unit unfinished, the runner reads
|
|
4
|
+
// progress off the row — never off a model's word — and renews only when three
|
|
5
|
+
// things hold: the row shows progress, the grant has a renewal left and the
|
|
6
|
+
// spend is under its cap, and the pipeline's ask still passes the fit.
|
|
7
|
+
// Otherwise it stops and names the clause that failed; a person's reply spends
|
|
8
|
+
// a renewal by hand. Pure and node-free: the unit machine (a Worker) and the
|
|
9
|
+
// bot's routes compute the same decision from the same facts.
|
|
10
|
+
|
|
11
|
+
import { fit, type Grant, type Pipeline } from "../budgets.js";
|
|
12
|
+
import type { Handoff } from "./handoff.js";
|
|
13
|
+
|
|
14
|
+
/** A head the run pushed, as the record carries it (run-history item 2). */
|
|
15
|
+
export interface PushedHeadFact {
|
|
16
|
+
ref: string;
|
|
17
|
+
sha: string;
|
|
18
|
+
at?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ProgressInput {
|
|
22
|
+
/** The unit's branch: a push to any other ref is not the unit's progress. */
|
|
23
|
+
branch: string;
|
|
24
|
+
/** The heads the segment's coding run pushed (`RunRecord.pushed`). */
|
|
25
|
+
pushed: readonly PushedHeadFact[];
|
|
26
|
+
/** The head the segment started from — the previous segment's recorded sha.
|
|
27
|
+
* Absent on a fresh branch, where any push is progress. */
|
|
28
|
+
startHead?: string;
|
|
29
|
+
/** When the segment's lease began; a push stamped before it is not this segment's. */
|
|
30
|
+
leaseStartedAt?: number;
|
|
31
|
+
/** The previous segment's handoff and this one's, when both exist. */
|
|
32
|
+
handoff?: { previous: Handoff; current: Handoff };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type Progress =
|
|
36
|
+
| { progressed: true; by: "push"; sha: string }
|
|
37
|
+
| { progressed: true; by: "handoff" }
|
|
38
|
+
| { progressed: false; why: string };
|
|
39
|
+
|
|
40
|
+
/** Two shas name one commit when the shorter is a prefix of the longer. */
|
|
41
|
+
const sameSha = (a: string, b: string): boolean => {
|
|
42
|
+
const x = a.toLowerCase();
|
|
43
|
+
const y = b.toLowerCase();
|
|
44
|
+
return x.length <= y.length ? y.startsWith(x) : x.startsWith(y);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Progress is a fact the row records after unit eight and the runner reads
|
|
48
|
+
* without a model: the last head pushed to the unit's branch that differs from
|
|
49
|
+
* the head the segment started at and is not older than the lease; failing
|
|
50
|
+
* that, a handoff whose follow-ups shrank or whose deviations grew. */
|
|
51
|
+
export function progressOf(input: ProgressInput): Progress {
|
|
52
|
+
const last = [...input.pushed].reverse().find((h) => h.ref === input.branch);
|
|
53
|
+
if (last !== undefined) {
|
|
54
|
+
const newerThanLease =
|
|
55
|
+
input.leaseStartedAt === undefined || last.at === undefined || last.at >= input.leaseStartedAt;
|
|
56
|
+
const moved = input.startHead === undefined || !sameSha(last.sha, input.startHead);
|
|
57
|
+
if (newerThanLease && moved) return { progressed: true, by: "push", sha: last.sha };
|
|
58
|
+
}
|
|
59
|
+
const pushWhy = `no head newer than the lease's start was pushed to \`${input.branch}\``;
|
|
60
|
+
if (input.handoff === undefined) return { progressed: false, why: pushWhy };
|
|
61
|
+
const { previous, current } = input.handoff;
|
|
62
|
+
if (current.followUps.length < previous.followUps.length || current.deviations.length > previous.deviations.length)
|
|
63
|
+
return { progressed: true, by: "handoff" };
|
|
64
|
+
return { progressed: false, why: `${pushWhy} and the handoff is unchanged` };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RenewalInput {
|
|
68
|
+
grant: Grant;
|
|
69
|
+
/** Renewals already spent under this request: the segments beyond the first. */
|
|
70
|
+
renewalsSpent: number;
|
|
71
|
+
/** Dollars spent under this request so far; null when a run's model had no
|
|
72
|
+
* price, since a total that left a model's tokens out would understate it. */
|
|
73
|
+
spendUsd: number | null;
|
|
74
|
+
progress: Progress;
|
|
75
|
+
/** The pipeline the next segment would run: its fit is re-asserted before every segment. */
|
|
76
|
+
pipeline: Pipeline;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type RenewalWhy = "no_progress" | "grant_exhausted" | "cost_cap" | "unfit";
|
|
80
|
+
|
|
81
|
+
export type RenewalDecision =
|
|
82
|
+
| { renew: true; segment: number; from?: string; renewalsLeft: number }
|
|
83
|
+
| { renew: false; why: RenewalWhy; detail: string; renewalsLeft: number };
|
|
84
|
+
|
|
85
|
+
const usd = (n: number): string => `$${n.toFixed(2)}`;
|
|
86
|
+
|
|
87
|
+
/** The decision, in the order the record states the clauses: progress, the
|
|
88
|
+
* grant's count, the cap, the fit. `segment` is the one the renewal opens
|
|
89
|
+
* (the first segment is 1); `from` the sha it continues from when a push made
|
|
90
|
+
* the progress. */
|
|
91
|
+
export function renewalDecision(input: RenewalInput): RenewalDecision {
|
|
92
|
+
const renewalsLeft = Math.max(0, input.grant.renewals - input.renewalsSpent);
|
|
93
|
+
if (!input.progress.progressed) return { renew: false, why: "no_progress", detail: input.progress.why, renewalsLeft };
|
|
94
|
+
if (renewalsLeft === 0) {
|
|
95
|
+
const detail =
|
|
96
|
+
input.grant.renewals === 0
|
|
97
|
+
? "the grant holds no renewals"
|
|
98
|
+
: `the grant's ${input.grant.renewals} renewal${input.grant.renewals === 1 ? "" : "s"} are spent`;
|
|
99
|
+
return { renew: false, why: "grant_exhausted", detail, renewalsLeft };
|
|
100
|
+
}
|
|
101
|
+
const cap = input.grant.costCapUsd;
|
|
102
|
+
if (cap !== undefined) {
|
|
103
|
+
if (input.spendUsd === null)
|
|
104
|
+
return {
|
|
105
|
+
renew: false,
|
|
106
|
+
why: "cost_cap",
|
|
107
|
+
detail: `spend is unknown (a model had no price) under the grant's cap of $${cap}`,
|
|
108
|
+
renewalsLeft,
|
|
109
|
+
};
|
|
110
|
+
if (input.spendUsd >= cap)
|
|
111
|
+
return {
|
|
112
|
+
renew: false,
|
|
113
|
+
why: "cost_cap",
|
|
114
|
+
detail: `spend ${usd(input.spendUsd)} reached the grant's cap of $${cap}`,
|
|
115
|
+
renewalsLeft,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const held = fit(input.pipeline);
|
|
119
|
+
if (!held.ok)
|
|
120
|
+
return {
|
|
121
|
+
renew: false,
|
|
122
|
+
why: "unfit",
|
|
123
|
+
detail: `a ${held.have}-minute segment cannot hold the ship loop (${input.pipeline.maxRounds} review rounds need ${held.need} min)`,
|
|
124
|
+
renewalsLeft,
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
renew: true,
|
|
128
|
+
segment: input.renewalsSpent + 2,
|
|
129
|
+
...(input.progress.by === "push" ? { from: input.progress.sha } : {}),
|
|
130
|
+
renewalsLeft: renewalsLeft - 1,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The card's words (the record's trace, steps 4 and 9): a renewal names its
|
|
135
|
+
* number of the grant's and the sha it continues from; a stop names the
|
|
136
|
+
* clause and, when renewals remain, the reply that spends one by hand. */
|
|
137
|
+
export function renderRenewal(decision: RenewalDecision, grant: Grant): string {
|
|
138
|
+
if (decision.renew)
|
|
139
|
+
return `renewal ${decision.segment - 1} of ${grant.renewals}, continues ${decision.from !== undefined ? decision.from.slice(0, 7) : "the branch's head"}`;
|
|
140
|
+
const holds =
|
|
141
|
+
decision.renewalsLeft > 0
|
|
142
|
+
? `grant holds ${decision.renewalsLeft} renewal${decision.renewalsLeft === 1 ? "" : "s"}`
|
|
143
|
+
: "the grant holds no renewals";
|
|
144
|
+
switch (decision.why) {
|
|
145
|
+
case "no_progress":
|
|
146
|
+
return decision.renewalsLeft > 0
|
|
147
|
+
? `no progress in the last lease; ${holds}; reply continue to spend one`
|
|
148
|
+
: `no progress in the last lease; ${holds}`;
|
|
149
|
+
case "cost_cap":
|
|
150
|
+
return decision.renewalsLeft > 0 ? `${decision.detail}; ${holds} unspent` : decision.detail;
|
|
151
|
+
case "grant_exhausted":
|
|
152
|
+
case "unfit":
|
|
153
|
+
return decision.detail;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -34,6 +34,14 @@ export interface AttrDomain {
|
|
|
34
34
|
/** The `<provider>/<model>` that took the turn — a ship run's children answer
|
|
35
35
|
* on different models, and the run page badges the switch per step. */
|
|
36
36
|
model: string;
|
|
37
|
+
/** The tools a proxied call offered the model (model-proxy.md item 6): how
|
|
38
|
+
* many, their names sorted and comma-joined, and the request's `tool_choice`
|
|
39
|
+
* by its word — `auto` when tools came and nothing was said, `none` when no
|
|
40
|
+
* tools came or the request said so, `any` for OpenAI's `required`, `tool`
|
|
41
|
+
* for a named tool. */
|
|
42
|
+
tools: number;
|
|
43
|
+
toolNames: string;
|
|
44
|
+
toolChoice: "auto" | "none" | "any" | "tool";
|
|
37
45
|
stopReason: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | "other";
|
|
38
46
|
inputTokens: number;
|
|
39
47
|
outputTokens: number;
|
|
@@ -159,6 +167,9 @@ const ATTR_TYPE: Record<SpanAttrKey, "string" | "number" | "boolean"> = {
|
|
|
159
167
|
backend: "string",
|
|
160
168
|
command: "string",
|
|
161
169
|
model: "string",
|
|
170
|
+
tools: "number",
|
|
171
|
+
toolNames: "string",
|
|
172
|
+
toolChoice: "string",
|
|
162
173
|
stopReason: "string",
|
|
163
174
|
inputTokens: "number",
|
|
164
175
|
outputTokens: "number",
|
|
@@ -70,9 +70,10 @@ export interface TraceOptions {
|
|
|
70
70
|
span?: Span;
|
|
71
71
|
/** Which door the call came through when it was not the surface's own
|
|
72
72
|
* grammar: `route` for a command the request router bound from prose
|
|
73
|
-
* (record 0036)
|
|
74
|
-
*
|
|
75
|
-
|
|
73
|
+
* (record 0036), `confirm` for a stored input run at a confirmation's
|
|
74
|
+
* click (record 0044). Copied onto the registry's audit line, never read
|
|
75
|
+
* by a handler. Absent for a typed command. */
|
|
76
|
+
source?: "route" | "confirm";
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export interface Span {
|
|
@@ -186,6 +186,22 @@ export function killStaleBuildProcessesCommand(user: string, dir: string): strin
|
|
|
186
186
|
* failure however it died. */
|
|
187
187
|
const INTERRUPTION_SIGNATURE = /\bexit 143\b|Session terminated|SIGTERM|^restore-interrupted:/;
|
|
188
188
|
|
|
189
|
+
/** The wordings git gives when the MIRROR itself is broken: its `origin`
|
|
190
|
+
* remote gone from `config`, the repository not a repository, an object store
|
|
191
|
+
* with bad, corrupt or unreadable objects, a local file it cannot open. None
|
|
192
|
+
* of these is GitHub's doing, and none is cured by waiting for GitHub. */
|
|
193
|
+
const MIRROR_FAILURE_WORDING =
|
|
194
|
+
/'origin' does not appear to be a git repository|not a git repository|\bbad object\b|\bcorrupt\b|unable to read (?:tree|object|commit|blob)|object file .* is empty|cannot open \.git\/|unable to open object|missing object/i;
|
|
195
|
+
|
|
196
|
+
/** Whether a failed mirror fetch failed on the mirror (the resident's own
|
|
197
|
+
* disk — item 67 counts it) rather than on GitHub (`github-unreachable`, a
|
|
198
|
+
* serviceable reason that parks on repeat, item 16b). GitHub is the default:
|
|
199
|
+
* a message names the mirror only through the wordings above, so an unknown
|
|
200
|
+
* failure keeps the reading it always had. */
|
|
201
|
+
export function fetchFailureIsMirrors(message: string): boolean {
|
|
202
|
+
return MIRROR_FAILURE_WORDING.test(message);
|
|
203
|
+
}
|
|
204
|
+
|
|
189
205
|
/** Message wording of the Sandbox SDK's runtime-replacement error family — the
|
|
190
206
|
* container went away UNDER a live SDK call, so the failure never reaches the
|
|
191
207
|
* shell-kill signature above: a step that dies this way surfaces as
|
|
@@ -339,10 +355,14 @@ export const WAKE_PORT_READY_MS = 3 * 60_000;
|
|
|
339
355
|
* SDK by a test. */
|
|
340
356
|
export const SDK_RUNTIME_RECORD_KEY = "currentRuntimeIdentity";
|
|
341
357
|
|
|
342
|
-
/** The DOMException the SDK's connect abort raises
|
|
343
|
-
* `
|
|
344
|
-
* own `
|
|
345
|
-
|
|
358
|
+
/** The DOMException the SDK's connect abort raises — `fetchUpgradeAttempt`
|
|
359
|
+
* calls `controller.abort()` with no reason when `DEFAULT_CONNECT_TIMEOUT_MS`
|
|
360
|
+
* passes, so the runtime's own `AbortError` with the message exactly `The
|
|
361
|
+
* operation was aborted`. Anchored on the WHOLE message, so a command's own
|
|
362
|
+
* `Aborted (core dumped)` never matches and neither does a `TimeoutError`'s
|
|
363
|
+
* `The operation was aborted due to timeout` — the text `AbortSignal.timeout`
|
|
364
|
+
* raises, a route's own bound on a slow GitHub, not a silent runtime. */
|
|
365
|
+
export const RUNTIME_UNREACHABLE_WORDING = /^The operation was aborted\.?$/;
|
|
346
366
|
|
|
347
367
|
/** Is this one link of an error's cause chain the SDK's connect abort? By
|
|
348
368
|
* name first (the DOMException's `AbortError`), by the DOMException's message
|
|
@@ -38,7 +38,12 @@ export function residentErrorText(text: string): string {
|
|
|
38
38
|
return redactAndCap(stripAnsi(text), RESIDENT_ERROR_CAP);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
/** The free-text fields a resident body carries: `error` (the reply), `reason`
|
|
42
|
+
* (the answer's own word or a lifecycle reason), `stateReason` (the lifecycle
|
|
43
|
+
* reason beside `state` on a thread route's 503 — the same remote text
|
|
44
|
+
* `reason` used to carry there, item 62), `summary`. A field added to a
|
|
45
|
+
* resident answer that carries remote output is added HERE first. */
|
|
46
|
+
const SANITIZED_FIELDS = ["error", "reason", "stateReason", "summary"] as const;
|
|
42
47
|
|
|
43
48
|
/** How deep the sanitizer descends. The deepest resident shape today is
|
|
44
49
|
* `/residents` → `residents[]` → `live` → `reason` (depth 3); the bound exists
|
|
@@ -48,8 +53,8 @@ const SANITIZE_DEPTH = 4;
|
|
|
48
53
|
/** A parsed resident body with its free-text fields made safe, at every level:
|
|
49
54
|
* `/residents` nests each resident's `state`/`reason` (or an `error`) under
|
|
50
55
|
* `residents[].live`, and `repo list` renders those. In each plain object only
|
|
51
|
-
* `error`, `reason` and `summary` are touched (when strings) —
|
|
52
|
-
* reply-sized cap, the
|
|
56
|
+
* `error`, `reason`, `stateReason` and `summary` are touched (when strings) —
|
|
57
|
+
* `error` at the reply-sized cap, the others at the card-sized one; `stderr` is
|
|
53
58
|
* rewritten only when it mirrors `error` (the thread routes' failure shape
|
|
54
59
|
* copies the error into stderr). Every other field — `needs`, `state`,
|
|
55
60
|
* `stdout`, `status`, bindings, numbers — passes through untouched. Arrays are
|
|
@@ -238,6 +238,9 @@ export interface McpServerView {
|
|
|
238
238
|
promotedFromName?: string;
|
|
239
239
|
/** A user row's owner (the id after `user:` in `scopeKey`) as a display name, when the lookup answered. */
|
|
240
240
|
ownerName?: string;
|
|
241
|
+
/** A channel row's channel (the id after `channel:` in `scopeKey`) as its name without the hash,
|
|
242
|
+
* when the lookup answered. */
|
|
243
|
+
channelName?: string;
|
|
241
244
|
/** A higher tier holds this name, so runs use that entry and this one is idle (`mcpServersFor`'s
|
|
242
245
|
* `shadowedBy`, the winning scope key — `org`, or `channel:<id>`); absent when this entry serves. */
|
|
243
246
|
shadowedBy?: string;
|