@coreplane/switchboard 1.243.0 → 1.245.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/config/config.example.yaml +6 -3
- package/dist/assets/deploy/cloudflare/worker.ts +2 -0
- 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/threadErr.ts +337 -0
- package/dist/assets/deploy/cloudflare-resident/tsconfig.json +1 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +258 -173
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +18 -12
- package/dist/assets/deploy/secrets.manifest.json +12 -0
- 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 +5 -5
- package/dist/assets/src/config/profile.ts +17 -1
- package/dist/assets/src/core/authz/types.ts +18 -0
- package/dist/assets/src/core/budgets.ts +61 -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/reviewVerdict.ts +101 -24
- package/dist/assets/src/core/runEvents.ts +76 -8
- package/dist/assets/src/core/runFriction.ts +2 -1
- package/dist/assets/src/core/runRecord.ts +65 -0
- package/dist/assets/src/core/ship/contract.ts +2 -1
- package/dist/assets/src/core/ship/coordinator.ts +235 -33
- 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 +46 -7
- 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 +491 -472
- package/dist/assets/web/dist/assets/AppShell-qqs_p96-.js +1 -0
- package/dist/assets/web/dist/assets/CostsPage-Dd9aXxxI.js +2 -0
- package/dist/assets/web/dist/assets/DeliveryPage-D90FTjYq.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-VLTFopnl.js +2 -0
- package/dist/assets/web/dist/assets/InputMenu-lQP3ia9D.js +1 -0
- package/dist/assets/web/dist/assets/NotFoundPage-8ViskpN3.js +1 -0
- package/dist/assets/web/dist/assets/PendingTurnRow-D2gHef5v.js +1 -0
- package/dist/assets/web/dist/assets/ResidentDetailPage-nWLN8BOd.js +1 -0
- package/dist/assets/web/dist/assets/ResidentsIndexPage-D3Q48CJW.js +1 -0
- package/dist/assets/web/dist/assets/RunFoldRow-CHy_IfZG.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-C6kX5om4.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-DjxGj1Q5.js +1 -0
- package/dist/assets/web/dist/assets/RunsTabs-WyeG5tKb.js +1 -0
- package/dist/assets/web/dist/assets/ScheduledPage-B8D5AU4d.js +1 -0
- package/dist/assets/web/dist/assets/SettingSelect-UTHsqUUS.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-BoW7Zjzt.js +1 -0
- package/dist/assets/web/dist/assets/StatusDot-BGHureCJ.js +1 -0
- package/dist/assets/web/dist/assets/Tooltip-CiP8qPd7.js +1 -0
- package/dist/assets/web/dist/assets/UnitRoutePage-2zEeRBP1.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-oBNfPJR0.js → angular-html-qXhb9ti8.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-BvNwsyWA.js → angular-ts-DzvdyoUZ.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-CNUdRlYf.js → apl-Ck7Ru2Sm.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-Zb0NriSe.js → astro-CwehvReb.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-qPRVheqq.js → blade-WSyVbSFK.js} +1 -1
- package/dist/assets/web/dist/assets/{c-D8Awx4YO.js → c-DMHI1wDz.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-Bt72Mhsx.js → chapel-BF8AYK8_.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-BOBacexg.js → cobol-UAfImuWu.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-E4u0liHW.js → coffee-D54hi96d.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-q2sLNlul.js → cpp-BRWfLgBc.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-DyWqUnlb.js → crystal-CSqC5deT.js} +1 -1
- package/dist/assets/web/dist/assets/{css-CQY0hFsD.js → css-D4ZuRFGk.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-twkFmSUY.js → dist-73ZMn_Mg.js} +2 -2
- package/dist/assets/web/dist/assets/{durationTone-BobbycC-.js → durationTone-D51enaGR.js} +1 -1
- package/dist/assets/web/dist/assets/{edge-C1MwhJkX.js → edge-DDPtNbZc.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-Bb3YbHfn.js → elixir-BTQkQfsJ.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DAN9IGQw.js → elm-B_PkLzGZ.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-BEB8Xlsj.js → erb-DfjIMVM_.js} +1 -1
- package/dist/assets/web/dist/assets/{eventSource-tRprhqm7.js → eventSource-By0B_w4S.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-tqpjRfxO.js → git-rebase-DP8wmn7K.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-Ccbo65zR.js → glimmer-js-G12VK2gK.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-B6WBVMpE.js → glimmer-ts-D3XiSI8E.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-tRec3Fcu.js → glsl-DOKAhMj-.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-P8kbxT4F.js → graphql-CJB_fmON.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-H9Zhkagy.js → hack-BHqtRWap.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DtEnpn7Z.js → haml-DwotyarS.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-DkgPfoAz.js → handlebars-B6fDaA3U.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DwozLrEx.js → html-derivative-C5bpQmmI.js} +1 -1
- package/dist/assets/web/dist/assets/{html-D30RXpIs.js → html-s52taj36.js} +1 -1
- package/dist/assets/web/dist/assets/{http-DXuzBAPm.js → http-99zC935r.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-d1UUJIt_.js → hurl-Bgk-4-0x.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-CuV_c5I0.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-Cl7vavnS.js → javascript-CZlOvSMQ.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-CfQOWMX9.js → jinja-BuXeZ4Tu.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-KdYirlqm.js → jison-DIuP0hkG.js} +1 -1
- package/dist/assets/web/dist/assets/{json-C9cDQ-Qj.js → json-CZ74vfoD.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CRx5NItd.js → jsx-BNjRs8Vj.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-CmsQsZQl.js → julia-OCE-fFOg.js} +1 -1
- package/dist/assets/web/dist/assets/{just-CsM3Q8TE.js → just-CtvD87WF.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BXCh5YRX.js → latex-C3kf86OR.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-BF2vwK8p.js → liquid-B1WI--Kp.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-DcMBATrl.js → lua-DtoT0gKg.js} +1 -1
- package/dist/assets/web/dist/assets/main-ClluP-Qf.js +28 -0
- package/dist/assets/web/dist/assets/main-CpkwzHX6.css +1 -0
- package/dist/assets/web/dist/assets/{marko-88MndKvG.js → marko-BE2MC6Yt.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-DJ4kVAd8.js → mdc-BwO4weXJ.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-CP6mRgtV.js → nginx-BTuOgGVC.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-QQfj3fpF.js → nim-CiIUM-oV.js} +1 -1
- package/dist/assets/web/dist/assets/{org-Bke3eBzc.js → org-L78DEWB-.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-Bkh0N7Kz.js → perl-KqHOm4Ft.js} +1 -1
- package/dist/assets/web/dist/assets/{php-Dnv1Piya.js → php-C41XbEkn.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-D6peFFI7.js → pug-B12-4i-k.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-D8PEs-C-.js → qml--fw2Up5c.js} +1 -1
- package/dist/assets/web/dist/assets/{r-B1EL9b_j.js → r-Db1JBXrC.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BsMTIh2b.js → razor-BYpVBgbu.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-HhvC8spD.js → regexp-BMqGLSPc.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-D5paAxpg.js → rst-B63n7-Sa.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-BrQwhLrl.js → ruby-D3VI8UYv.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-afot2B1o.js → sas-DxRf8ZAR.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-Efm-mwuG.js → scss-DRRA6d6A.js} +1 -1
- package/dist/assets/web/dist/assets/seed-DFfT5S_N.js +6 -0
- package/dist/assets/web/dist/assets/{settingsApi-priOPiwu.js → settingsApi-Dm6eaQcu.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-BK0Vv5fT.js → shellscript-CoOBe3_y.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-BGqlMC7N.js → shellsession-DOV1HYxf.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-M0b4UwGM.js → soy-Acpmpp1W.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-sxe6IE9j.js → sql-DeT9N-fA.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-C9m_EB8J.js → sseReplay-DPXVxhL5.js} +5 -5
- package/dist/assets/web/dist/assets/{stata-nPF_ddLP.js → stata-CbxXAMO4.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-D_GrC6u7.js → surrealql-BdKbi4hc.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-DwL1AtNP.js → svelte-tAcjDM7R.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-7s7LTDkc.js → templ-CObINNJj.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-Bx-5fMxe.js → tex-BYwpZT1J.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-DUMJnke_.js → ts-tags-BciREL5R.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-BN8biPDe.js → tsx-CRiv3Pr3.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-8nIu84TN.js → twig-DuZZ1Ej0.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BooSPq_S.js → typescript-jkHz_5HZ.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-CTBiBsem.js → typst-DOac4Gal.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-C6Ft4Lea.js → vue-CgOX5wXA.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-Ba36dD5D.js → vue-html-Duu99VNw.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-NkFexVo2.js → vue-vine-Bmj_wvRJ.js} +1 -1
- package/dist/assets/web/dist/assets/wallClock-DD_fPuSY.js +1 -0
- package/dist/assets/web/dist/assets/{xml-C_THnHXZ.js → xml-BtUIVeWH.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-D8G5xqjY.js → xsl-wX71n1E5.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-jAMIJzge.js → yaml-CdLdXvmz.js} +1 -1
- package/dist/cli.js +4330 -1256
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/AppShell-IDMGG6yi.js +0 -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/NotFoundPage-C97EhJcQ.js +0 -1
- package/dist/assets/web/dist/assets/PendingTurnRow-BT9RhFZ7.js +0 -1
- package/dist/assets/web/dist/assets/ResidentDetailPage-DACalNVF.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-SHPdu6uZ.js +0 -1
- 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/RunsTabs-BUfdk0lH.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DJ8HiCPt.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-DLiN5IgY.js +0 -1
- package/dist/assets/web/dist/assets/StatusDot-Dw0T1M-P.js +0 -1
- package/dist/assets/web/dist/assets/Tooltip-BbLuIAiS.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DicUG96U.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
- package/dist/assets/web/dist/assets/seed-DeKSHrt2.js +0 -6
- package/dist/assets/web/dist/assets/wallClock-61NpVkY6.js +0 -1
|
@@ -30,11 +30,34 @@
|
|
|
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
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
ADDRESS_SEVERITIES,
|
|
39
|
+
DEFAULT_ADDRESS_SEVERITY,
|
|
40
|
+
findingsAtOrAbove,
|
|
41
|
+
formatFinding,
|
|
42
|
+
isAddressSeverity,
|
|
43
|
+
type AddressSeverity,
|
|
44
|
+
type AddressSeveritySource,
|
|
45
|
+
type Finding,
|
|
46
|
+
type FindingDisposition,
|
|
47
|
+
type ReviewVerdictKind,
|
|
48
|
+
} from "../reviewVerdict.js";
|
|
36
49
|
import { parsePlanUnit, planUnitIds } from "./contract.js";
|
|
37
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
carve,
|
|
52
|
+
DEFAULT_GRANT,
|
|
53
|
+
loopPosition,
|
|
54
|
+
MINUTE_MS,
|
|
55
|
+
SHIP_WAIT,
|
|
56
|
+
type Carve,
|
|
57
|
+
type Grant,
|
|
58
|
+
type GrantSource,
|
|
59
|
+
type Loop,
|
|
60
|
+
} from "../budgets.js";
|
|
38
61
|
|
|
39
62
|
const MIN = MINUTE_MS;
|
|
40
63
|
|
|
@@ -391,7 +414,14 @@ export function presetOf(kind: RoundKind): ChildPreset {
|
|
|
391
414
|
* turn: the unit's contract, or the review turn from the pull request and the
|
|
392
415
|
* prior rounds' records, or the findings message from the review run's verdict. */
|
|
393
416
|
export type Brief =
|
|
394
|
-
| {
|
|
417
|
+
| {
|
|
418
|
+
kind: "contract";
|
|
419
|
+
unit: string;
|
|
420
|
+
rebase: { branch: string; onto: string };
|
|
421
|
+
/** A renewal's segment (decision 0046): the child continues the previous
|
|
422
|
+
* segment's work from `from`, briefed with that run's write-up and handoff. */
|
|
423
|
+
continue?: { segment: number; from?: string; previousRunId?: string };
|
|
424
|
+
}
|
|
395
425
|
| {
|
|
396
426
|
kind: "review";
|
|
397
427
|
unit: string;
|
|
@@ -455,6 +485,13 @@ export type ChildFacts =
|
|
|
455
485
|
/** The dispositions a coding run recorded, as it submitted them: the machine matches them to the round's findings. */
|
|
456
486
|
dispositions?: FindingDisposition[];
|
|
457
487
|
handoff?: boolean;
|
|
488
|
+
/** The renewal's facts (decision 0046): the heads the run pushed, when
|
|
489
|
+
* its lease began, what it cost (null when a model had no price) and
|
|
490
|
+
* the handoff's lists — progress is read off these, never asked. */
|
|
491
|
+
pushed?: PushedHeadFact[];
|
|
492
|
+
leaseStartedAt?: number;
|
|
493
|
+
costUsd?: number | null;
|
|
494
|
+
handoffLists?: Handoff;
|
|
458
495
|
};
|
|
459
496
|
|
|
460
497
|
/** What heads the unit's branch on GitHub: nothing, an open pull request, or —
|
|
@@ -524,7 +561,38 @@ export type UnitEnding =
|
|
|
524
561
|
/** A changes-requested review posted this round before the stop. */
|
|
525
562
|
postedReview?: boolean;
|
|
526
563
|
}
|
|
527
|
-
| {
|
|
564
|
+
| {
|
|
565
|
+
kind: "aborted";
|
|
566
|
+
reason: string;
|
|
567
|
+
round?: RoundRef;
|
|
568
|
+
reviewRounds: number;
|
|
569
|
+
finalReply?: string;
|
|
570
|
+
/** A round-0 end without a pull request that was judged for renewal and
|
|
571
|
+
* refused: the decision and the card's sentence (decision 0046). */
|
|
572
|
+
renewal?: { decision: Extract<RenewalDecision, { renew: false }>; line: string };
|
|
573
|
+
}
|
|
574
|
+
/** The coding round ended at its lease with the unit unfinished, the row
|
|
575
|
+
* showed progress and the grant renewed: this segment is over and the next
|
|
576
|
+
* opens in the same thread from `from` under a fresh lease, the coding
|
|
577
|
+
* child's write-up as its request (decision 0046, Renewal). */
|
|
578
|
+
| {
|
|
579
|
+
kind: "continued";
|
|
580
|
+
round: RoundRef;
|
|
581
|
+
/** The coding child whose write-up and handoff the continuation is briefed with. */
|
|
582
|
+
runId: string;
|
|
583
|
+
/** The segment the renewal opens (the first segment is 1). */
|
|
584
|
+
segment: number;
|
|
585
|
+
from?: string;
|
|
586
|
+
renewalsLeft: number;
|
|
587
|
+
/** The session's spend so far, this segment's children included; null when a run's model had no price. */
|
|
588
|
+
spendUsd: number | null;
|
|
589
|
+
handoff?: Handoff;
|
|
590
|
+
/** The coding child's write-up: the checkpoint the continuation is briefed with. */
|
|
591
|
+
finalReply?: string;
|
|
592
|
+
line: string;
|
|
593
|
+
reviewRounds: number;
|
|
594
|
+
spent: ShipBudgetSpent;
|
|
595
|
+
}
|
|
528
596
|
| { kind: "no_verdict"; round: RoundRef; reviewRounds: number; finalReply?: string }
|
|
529
597
|
| { kind: "interrupted"; round: RoundRef; runId: string; reviewRounds: number }
|
|
530
598
|
| { kind: "refused"; refusal: string; message?: string; round: RoundRef; reviewRounds: number };
|
|
@@ -541,25 +609,28 @@ export type CoordinatorNote =
|
|
|
541
609
|
* person can see whether the cap or the child is the problem. */
|
|
542
610
|
export type ShipBudgetSpent = Readonly<Record<"coding" | "review" | "waiting", number>>;
|
|
543
611
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
export
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
612
|
+
// The severity to address (agent-ship item 9's gate) is the verdict parser's
|
|
613
|
+
// ladder (src/core/reviewVerdict.ts): the same level the parser holds a
|
|
614
|
+
// submitted approve to, so a posted approve can carry a gated finding only
|
|
615
|
+
// when it was parsed at a different level — the check below is defense in
|
|
616
|
+
// depth behind the parser's. Re-exported here for the runner's callers.
|
|
617
|
+
export {
|
|
618
|
+
ADDRESS_SEVERITIES,
|
|
619
|
+
DEFAULT_ADDRESS_SEVERITY,
|
|
620
|
+
findingsAtOrAbove,
|
|
621
|
+
isAddressSeverity,
|
|
622
|
+
type AddressSeverity,
|
|
623
|
+
type AddressSeveritySource,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
export interface UnitSession {
|
|
627
|
+
segment: number;
|
|
628
|
+
renewalsSpent: number;
|
|
629
|
+
spendUsd: number | null;
|
|
630
|
+
continueFrom?: string;
|
|
631
|
+
/** The previous segment's coding run: its write-up and handoff brief the continuation. */
|
|
632
|
+
previousRunId?: string;
|
|
633
|
+
previousHandoff?: Handoff;
|
|
563
634
|
}
|
|
564
635
|
|
|
565
636
|
export interface UnitPipelineInput {
|
|
@@ -578,6 +649,18 @@ export interface UnitPipelineInput {
|
|
|
578
649
|
addressSeverity?: AddressSeverity;
|
|
579
650
|
/** Which layer set the level in force; named in the round header and the ending. */
|
|
580
651
|
addressSeveritySource?: AddressSeveritySource;
|
|
652
|
+
/** The grant the request carried (decision 0046, the renewable lease):
|
|
653
|
+
* renewals and a cost cap, and which layer granted it — named in the report;
|
|
654
|
+
* absent reads as zero renewals and no cap, the org's. Nothing here spends
|
|
655
|
+
* it yet: the renewal decision is the segment's end, not this unit's. */
|
|
656
|
+
grant?: Grant;
|
|
657
|
+
grantSource?: GrantSource;
|
|
658
|
+
/** The segment this pipeline runs (decision 0046, Renewal): absent for the
|
|
659
|
+
* first; a renewal's carries its number, the renewals spent before it, the
|
|
660
|
+
* session's spend so far, the sha it continues from and the previous
|
|
661
|
+
* segment's handoff. Every step name of a later segment is prefixed with
|
|
662
|
+
* it, so the Workflow's durable steps never collide across segments. */
|
|
663
|
+
session?: UnitSession;
|
|
581
664
|
/** The instance's mark (agent-ship item 16): a generated one-unit plan — a
|
|
582
665
|
* `plan` with an id and no `path` — whose unit runs in the requesting
|
|
583
666
|
* thread and is re-issued with the request's own text, never a plan path. */
|
|
@@ -606,6 +689,10 @@ type Phase =
|
|
|
606
689
|
runId: string;
|
|
607
690
|
childHead?: string;
|
|
608
691
|
finalReply?: string;
|
|
692
|
+
/** The renewal's facts off the child's record (decision 0046): its pushed heads, its lease's start, its handoff. */
|
|
693
|
+
childPushed?: PushedHeadFact[];
|
|
694
|
+
childLeaseStartedAt?: number;
|
|
695
|
+
childHandoff?: Handoff;
|
|
609
696
|
/** The coding child died (`failed` or `interrupted`) after it may have
|
|
610
697
|
* pushed: the pr-check recovers a pushed branch by opening its pull
|
|
611
698
|
* request; with nothing pushed the unit ends with the child's own reason. */
|
|
@@ -639,6 +726,9 @@ export interface UnitPipelineState {
|
|
|
639
726
|
readonly lastCodingRunId?: string;
|
|
640
727
|
/** How the budget went so far, accrued as each answer moves the clock. */
|
|
641
728
|
readonly spentMs: ShipBudgetSpent;
|
|
729
|
+
/** The session's dollars so far: the input's from earlier segments plus each
|
|
730
|
+
* child's cost as its record is read; null once any run's cost is unknown. */
|
|
731
|
+
readonly spendUsd: number | null;
|
|
642
732
|
readonly ending?: UnitEnding;
|
|
643
733
|
}
|
|
644
734
|
|
|
@@ -674,6 +764,7 @@ export function openUnitPipeline(input: UnitPipelineInput, at: number): UnitPipe
|
|
|
674
764
|
phase: { at: "pre-check" },
|
|
675
765
|
reviewRounds: 0,
|
|
676
766
|
spentMs: { coding: 0, review: 0, waiting: 0 },
|
|
767
|
+
spendUsd: input.session?.spendUsd ?? 0,
|
|
677
768
|
findingsByRound: {},
|
|
678
769
|
dispositionsByRound: {},
|
|
679
770
|
reviewRunByRound: {},
|
|
@@ -713,12 +804,33 @@ function roundCarve(s: UnitPipelineState, round: RoundRef): Carve {
|
|
|
713
804
|
return carve(remainingMs(s), { kind, index: loopPosition(loopOf(s), kind, round.index) }, loopOf(s));
|
|
714
805
|
}
|
|
715
806
|
|
|
716
|
-
|
|
807
|
+
/** The prefix every step of this pipeline is named under: the unit id, and for
|
|
808
|
+
* a renewal's segment the segment too (`U10/s2/…`), so the Workflow's durable
|
|
809
|
+
* step cache never hands segment two the answers of segment one. */
|
|
810
|
+
export const stepPrefixOf = (unit: string, session: UnitSession | undefined): string =>
|
|
811
|
+
session !== undefined && session.segment > 1 ? `${unit}/s${session.segment}` : unit;
|
|
812
|
+
const stepPrefix = (s: UnitPipelineState) => stepPrefixOf(s.input.unit.id, s.input.session);
|
|
813
|
+
const roundStep = (s: UnitPipelineState, round: RoundRef) => `${stepPrefix(s)}/${round.index}/${round.kind}`;
|
|
717
814
|
|
|
718
815
|
function briefFor(s: UnitPipelineState, round: RoundRef): Brief {
|
|
719
816
|
const unit = s.input.unit.id;
|
|
720
|
-
if (round.kind === "coding")
|
|
721
|
-
|
|
817
|
+
if (round.kind === "coding") {
|
|
818
|
+
const session = s.input.session;
|
|
819
|
+
return {
|
|
820
|
+
kind: "contract",
|
|
821
|
+
unit,
|
|
822
|
+
rebase: { branch: s.input.unit.branch, onto: s.input.base },
|
|
823
|
+
...(session !== undefined && session.segment > 1
|
|
824
|
+
? {
|
|
825
|
+
continue: {
|
|
826
|
+
segment: session.segment,
|
|
827
|
+
...(session.continueFrom !== undefined ? { from: session.continueFrom } : {}),
|
|
828
|
+
...(session.previousRunId !== undefined ? { previousRunId: session.previousRunId } : {}),
|
|
829
|
+
},
|
|
830
|
+
}
|
|
831
|
+
: {}),
|
|
832
|
+
};
|
|
833
|
+
}
|
|
722
834
|
const pr = s.pr!.number;
|
|
723
835
|
if (round.kind === "findings") return { kind: "findings", unit, pr, reviewRunId: s.reviewRunByRound[round.index]! };
|
|
724
836
|
const priorReview = s.reviewRunByRound[round.index - 1];
|
|
@@ -754,7 +866,7 @@ export function matchDispositions(
|
|
|
754
866
|
/** The step the machine is at. Pure over the state: asked before every step
|
|
755
867
|
* and again after a replay, it names the same step for the same state. */
|
|
756
868
|
export function nextAction(s: UnitPipelineState): CoordinatorAction {
|
|
757
|
-
const unit = s
|
|
869
|
+
const unit = stepPrefix(s);
|
|
758
870
|
const p = s.phase;
|
|
759
871
|
switch (p.at) {
|
|
760
872
|
case "pre-check":
|
|
@@ -815,6 +927,14 @@ interface Transition {
|
|
|
815
927
|
|
|
816
928
|
const ENDED: Phase = { at: "ended" };
|
|
817
929
|
|
|
930
|
+
/** The session's dollars after one more child: unknown (null) once any run's
|
|
931
|
+
* cost is — a total that left a run out would understate the spend a cap
|
|
932
|
+
* judges — and a record without the field counts as unknown too. */
|
|
933
|
+
function addSpend(sum: number | null, cost: number | null | undefined): number | null {
|
|
934
|
+
if (sum === null || cost === null || cost === undefined) return null;
|
|
935
|
+
return sum + cost;
|
|
936
|
+
}
|
|
937
|
+
|
|
818
938
|
function end(s: UnitPipelineState, ending: UnitEnding, notes: CoordinatorNote[] = []): Transition {
|
|
819
939
|
return { state: { ...s, phase: ENDED, ending }, notes: [...notes, { type: "ended", ending }] };
|
|
820
940
|
}
|
|
@@ -885,6 +1005,7 @@ function settleCoding(
|
|
|
885
1005
|
// round's findings enter the state (agent-ship item 6).
|
|
886
1006
|
let next: UnitPipelineState = {
|
|
887
1007
|
...s,
|
|
1008
|
+
spendUsd: addSpend(s.spendUsd, facts.costUsd),
|
|
888
1009
|
...(facts.pr !== undefined ? { pr: { number: facts.pr.number, url: facts.pr.url } } : {}),
|
|
889
1010
|
...(round.kind === "findings" && facts.dispositions !== undefined
|
|
890
1011
|
? {
|
|
@@ -922,6 +1043,9 @@ function settleCoding(
|
|
|
922
1043
|
runId,
|
|
923
1044
|
...(facts.headSha !== undefined ? { childHead: facts.headSha } : {}),
|
|
924
1045
|
...(facts.finalReply !== undefined ? { finalReply: facts.finalReply } : {}),
|
|
1046
|
+
...(facts.pushed !== undefined ? { childPushed: facts.pushed } : {}),
|
|
1047
|
+
...(facts.leaseStartedAt !== undefined ? { childLeaseStartedAt: facts.leaseStartedAt } : {}),
|
|
1048
|
+
...(facts.handoffLists !== undefined ? { childHandoff: facts.handoffLists } : {}),
|
|
925
1049
|
},
|
|
926
1050
|
};
|
|
927
1051
|
return { state: next, notes: [] };
|
|
@@ -961,6 +1085,7 @@ function settleReview(
|
|
|
961
1085
|
const verdict = facts.verdict;
|
|
962
1086
|
const next: UnitPipelineState = {
|
|
963
1087
|
...s,
|
|
1088
|
+
spendUsd: addSpend(s.spendUsd, facts.costUsd),
|
|
964
1089
|
findingsByRound: { ...s.findingsByRound, [round.index]: verdict.findings },
|
|
965
1090
|
...(facts.reviewHead !== undefined ? { lastReviewHead: facts.reviewHead } : {}),
|
|
966
1091
|
...(verdict.summary !== undefined ? { lastVerdictSummary: verdict.summary } : {}),
|
|
@@ -1122,15 +1247,72 @@ function settlePrCheck(s: UnitPipelineState, phase: Extract<Phase, { at: "pr-che
|
|
|
1122
1247
|
[roundNote(round, "aborted")],
|
|
1123
1248
|
);
|
|
1124
1249
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1250
|
+
// Round 0 ended without a pull request: the segment is over with the unit
|
|
1251
|
+
// unfinished, and the grant decides whether the next opens (decision
|
|
1252
|
+
// 0046, Renewal). Progress is read off the child's record — a head pushed
|
|
1253
|
+
// to the unit's branch since the segment started, or a handoff that moved —
|
|
1254
|
+
// never off its words; the decision then asks the grant's count, the cap
|
|
1255
|
+
// and the fit, in that order, and a refusal names the clause. A plain
|
|
1256
|
+
// abort keeps its old shape when nothing was pushed under a grant of zero:
|
|
1257
|
+
// a clarifying question is not a stop to explain.
|
|
1258
|
+
if (round.index === 0) {
|
|
1259
|
+
const grant = s.input.grant ?? DEFAULT_GRANT;
|
|
1260
|
+
const session = s.input.session;
|
|
1261
|
+
const progress = progressOf({
|
|
1262
|
+
branch: s.input.unit.branch,
|
|
1263
|
+
pushed: phase.childPushed ?? [],
|
|
1264
|
+
...(session?.continueFrom !== undefined ? { startHead: session.continueFrom } : {}),
|
|
1265
|
+
...(phase.childLeaseStartedAt !== undefined ? { leaseStartedAt: phase.childLeaseStartedAt } : {}),
|
|
1266
|
+
...(session?.previousHandoff !== undefined && phase.childHandoff !== undefined
|
|
1267
|
+
? { handoff: { previous: session.previousHandoff, current: phase.childHandoff } }
|
|
1268
|
+
: {}),
|
|
1269
|
+
});
|
|
1270
|
+
const decision = renewalDecision({
|
|
1271
|
+
grant,
|
|
1272
|
+
renewalsSpent: session?.renewalsSpent ?? 0,
|
|
1273
|
+
spendUsd: s.spendUsd,
|
|
1274
|
+
progress,
|
|
1275
|
+
pipeline: s.input.caps,
|
|
1276
|
+
});
|
|
1277
|
+
const line = renderRenewal(decision, grant);
|
|
1278
|
+
if (decision.renew)
|
|
1279
|
+
return end(
|
|
1280
|
+
s,
|
|
1281
|
+
{
|
|
1282
|
+
kind: "continued",
|
|
1283
|
+
round,
|
|
1284
|
+
runId: phase.runId,
|
|
1285
|
+
segment: decision.segment,
|
|
1286
|
+
...(decision.from !== undefined ? { from: decision.from } : {}),
|
|
1287
|
+
renewalsLeft: decision.renewalsLeft,
|
|
1288
|
+
spendUsd: s.spendUsd,
|
|
1289
|
+
...(phase.childHandoff !== undefined ? { handoff: phase.childHandoff } : {}),
|
|
1290
|
+
...(phase.finalReply !== undefined ? { finalReply: phase.finalReply } : {}),
|
|
1291
|
+
line,
|
|
1292
|
+
reviewRounds: s.reviewRounds,
|
|
1293
|
+
spent: s.spentMs,
|
|
1294
|
+
},
|
|
1295
|
+
[roundNote(round, "continued")],
|
|
1296
|
+
);
|
|
1297
|
+
const judged = grant.renewals > 0 || progress.progressed;
|
|
1298
|
+
return end(
|
|
1299
|
+
s,
|
|
1300
|
+
{
|
|
1301
|
+
kind: "aborted",
|
|
1302
|
+
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.`,
|
|
1303
|
+
round,
|
|
1304
|
+
reviewRounds: s.reviewRounds,
|
|
1305
|
+
...(phase.finalReply !== undefined ? { finalReply: phase.finalReply } : {}),
|
|
1306
|
+
...(judged ? { renewal: { decision, line } } : {}),
|
|
1307
|
+
},
|
|
1308
|
+
[roundNote(round, "aborted")],
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1129
1311
|
return end(
|
|
1130
1312
|
s,
|
|
1131
1313
|
{
|
|
1132
1314
|
kind: "aborted",
|
|
1133
|
-
reason,
|
|
1315
|
+
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
1316
|
round,
|
|
1135
1317
|
reviewRounds: s.reviewRounds,
|
|
1136
1318
|
...(phase.finalReply !== undefined ? { finalReply: phase.finalReply } : {}),
|
|
@@ -1475,6 +1657,12 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1475
1657
|
// stated decision, never a silent one.
|
|
1476
1658
|
const level = s.input.addressSeverity ?? DEFAULT_ADDRESS_SEVERITY;
|
|
1477
1659
|
const levelLine = `Severity addressed: ${level} and above (set by ${s.input.addressSeveritySource ?? "org"}).`;
|
|
1660
|
+
// The grant as the instance carries it (decision 0046): spent of granted,
|
|
1661
|
+
// the cap when one is set, and who granted it — zero of zero until a scope
|
|
1662
|
+
// or a directive says otherwise, and nothing spends it before the renewal
|
|
1663
|
+
// decision exists.
|
|
1664
|
+
const grant = s.input.grant ?? DEFAULT_GRANT;
|
|
1665
|
+
const grantLine = `Renewals: 0 of ${grant.renewals} spent${grant.costCapUsd !== undefined ? `, cost cap $${grant.costCapUsd}` : ""} (granted by ${s.input.grantSource ?? "org"}).`;
|
|
1478
1666
|
const lastFindings = s.findingsByRound[e.reviewRounds] ?? [];
|
|
1479
1667
|
const skipped = lastFindings.filter((f) => !findingsAtOrAbove([f], level).length);
|
|
1480
1668
|
const skippedLine =
|
|
@@ -1490,6 +1678,7 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1490
1678
|
`✅ 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
1679
|
verdictLine,
|
|
1492
1680
|
levelLine,
|
|
1681
|
+
grantLine,
|
|
1493
1682
|
...(skippedLine ? [skippedLine] : []),
|
|
1494
1683
|
declinedLine,
|
|
1495
1684
|
].join("\n");
|
|
@@ -1498,6 +1687,7 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1498
1687
|
`✅ Merge-ready after ${rounds}: ${e.pr.url}`,
|
|
1499
1688
|
verdictLine,
|
|
1500
1689
|
levelLine,
|
|
1690
|
+
grantLine,
|
|
1501
1691
|
...(skippedLine ? [skippedLine] : []),
|
|
1502
1692
|
declinedLine,
|
|
1503
1693
|
// What the driver read at the approved head when it composed this
|
|
@@ -1543,7 +1733,19 @@ export function renderUnitReport(s: UnitPipelineState, facts?: MergeReadyFacts):
|
|
|
1543
1733
|
reissue,
|
|
1544
1734
|
]);
|
|
1545
1735
|
case "aborted":
|
|
1546
|
-
return join([
|
|
1736
|
+
return join([
|
|
1737
|
+
e.finalReply,
|
|
1738
|
+
e.reason,
|
|
1739
|
+
e.renewal !== undefined ? `🔁 Not renewed: ${e.renewal.line}.` : undefined,
|
|
1740
|
+
`⚠️ Ship aborted after ${rounds}.`,
|
|
1741
|
+
reissue,
|
|
1742
|
+
]);
|
|
1743
|
+
case "continued":
|
|
1744
|
+
return join([
|
|
1745
|
+
e.finalReply,
|
|
1746
|
+
`🔁 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` : ""}.`,
|
|
1747
|
+
budgetSplitLine(e.spent, s.input.caps.maxMinutes),
|
|
1748
|
+
]);
|
|
1547
1749
|
case "no_verdict":
|
|
1548
1750
|
return join([
|
|
1549
1751
|
`⚠️ 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 {
|