@coreplane/switchboard 1.242.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/config/config.example.yaml +20 -1
- package/dist/assets/deploy/cloudflare-memory/worker.ts +234 -29
- package/dist/assets/deploy/cloudflare-resident/gc.ts +8 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +483 -75
- 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/project.json +3 -3
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +31 -35
- package/dist/assets/src/config/profile.ts +68 -3
- package/dist/assets/src/core/authz/policy.ts +4 -0
- package/dist/assets/src/core/authz/types.ts +10 -0
- package/dist/assets/src/core/budgets.ts +349 -0
- package/dist/assets/src/core/coordinator/contract.ts +32 -0
- package/dist/assets/src/core/coordinator/driver.ts +75 -22
- package/dist/assets/src/core/costs.ts +9 -76
- package/dist/assets/src/core/delivery.ts +1 -1
- package/dist/assets/src/core/modelPricing.ts +212 -0
- package/dist/assets/src/core/prDescriptionTypes.ts +29 -24
- package/dist/assets/src/core/reviewVerdict.ts +7 -0
- package/dist/assets/src/core/runEvents.ts +72 -5
- package/dist/assets/src/core/runFriction.ts +3 -1
- package/dist/assets/src/core/runRecord.ts +78 -0
- package/dist/assets/src/core/runUsage.ts +158 -47
- package/dist/assets/src/core/schedules.ts +3 -0
- package/dist/assets/src/core/ship/contract.ts +2 -1
- package/dist/assets/src/core/ship/coordinator.ts +312 -97
- package/dist/assets/src/core/ship/handoff.ts +9 -0
- 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/bashTimeout.ts +8 -5
- package/dist/assets/src/execution/residentRefresh.ts +52 -7
- package/dist/assets/src/execution/residentSteps.ts +1 -1
- 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 +432 -429
- 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-BHCSpsPf.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-DTMBgnIW.js → ResidentDetailPage-C5USV_8P.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CSKisWnX.js +1 -0
- 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-DBaChcJZ.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-CqVi9-Ac.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-ELoXHlFt.js → StatusDot-_Q_bmReh.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-BoeFwYP2.js → Tooltip-Cafsgd4m.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-CdoBZwtf.js +1 -0
- 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-BU5UivXC.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/format-BldUwl_R.js +1 -0
- 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-BmK74Vp1.js +1 -0
- 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-g7ml86LM.js → sseReplay-DaRtc7nI.js} +5 -5
- 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 +5843 -2246
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/CostsPage-CtmKhhOF.js +0 -2
- package/dist/assets/web/dist/assets/DeliveryPage-n1tz5I_v.js +0 -1
- package/dist/assets/web/dist/assets/HomePage-QtH1EwYF.js +0 -2
- package/dist/assets/web/dist/assets/PendingTurnRow-BZA_vQt3.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CaDvXhzJ.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-mgyLW0oV.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-DypJYMQa.js +0 -6
- package/dist/assets/web/dist/assets/RunsIndexPage-DYraPoWD.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DXD2gLJk.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-BIGio8Y0.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-jhCrWW3i.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-BD1VT8o8.js +0 -1
- package/dist/assets/web/dist/assets/localIso-L06jV29p.js +0 -1
- package/dist/assets/web/dist/assets/main-B2fX10aW.css +0 -1
- package/dist/assets/web/dist/assets/main-D4EA1g6n.js +0 -28
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
anthropicTokensCostUsd,
|
|
3
|
+
parseModelPrices,
|
|
4
|
+
type AnthropicTokens,
|
|
5
|
+
type ModelPriceTable,
|
|
6
|
+
} from "./modelPricing.js";
|
|
1
7
|
import { systemClock } from "./trace/clock.js";
|
|
2
8
|
// Spend report: what a group of deployed pieces ("switchboard" = the bot
|
|
3
9
|
// Worker + its containers, the resident/sandbox/memory Workers) costs per day,
|
|
@@ -68,6 +74,8 @@ export interface CostsConfig {
|
|
|
68
74
|
/** Env var holding an Anthropic Admin API key (sk-ant-admin…). Optional feature. */
|
|
69
75
|
anthropicAdminKeyEnv: string;
|
|
70
76
|
groups: Record<string, CostGroupConfig>;
|
|
77
|
+
/** `costs.prices`: the operator's per-million rates by `<provider>/<model>`, over the Anthropic list (item 4b). */
|
|
78
|
+
prices: ModelPriceTable;
|
|
71
79
|
/** The snapshot the page and the twins serve (src/core/costsSnapshot.ts). */
|
|
72
80
|
snapshot: {
|
|
73
81
|
/** Hours between two reads of the billing sources. */
|
|
@@ -155,6 +163,7 @@ export function parseCostsConfig(raw: unknown): CostsConfig | undefined {
|
|
|
155
163
|
anthropicAdminKeyEnv:
|
|
156
164
|
typeof r.anthropicAdminKeyEnv === "string" ? r.anthropicAdminKeyEnv : DEFAULT_ANTHROPIC_ADMIN_ENV,
|
|
157
165
|
groups,
|
|
166
|
+
prices: parseModelPrices(r.prices),
|
|
158
167
|
snapshot: snapshotConfig(r.snapshot),
|
|
159
168
|
};
|
|
160
169
|
}
|
|
@@ -691,82 +700,6 @@ export function buildCostReport(
|
|
|
691
700
|
};
|
|
692
701
|
}
|
|
693
702
|
|
|
694
|
-
// ---- Anthropic list prices ----------------------------------------------------------------
|
|
695
|
-
|
|
696
|
-
/** USD per million tokens of one kind, one model family. */
|
|
697
|
-
export interface AnthropicModelPrice {
|
|
698
|
-
input: number;
|
|
699
|
-
output: number;
|
|
700
|
-
cacheWrite5m: number;
|
|
701
|
-
cacheWrite1h: number;
|
|
702
|
-
cacheRead: number;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/** Anthropic list prices per model family (platform.claude.com/docs/en/about-claude/pricing;
|
|
706
|
-
* re-check when it moves). Keyed by the family id the usage report spells — a dated
|
|
707
|
-
* release (`claude-haiku-4-5-20251001`) resolves to its family through
|
|
708
|
-
* `anthropicPriceOf`. Only what the estimate needs: the open day priced at
|
|
709
|
-
* the rate it will bill at; the cost report remains the invoice. */
|
|
710
|
-
export const ANTHROPIC_PRICES: Record<string, AnthropicModelPrice> = {
|
|
711
|
-
"claude-fable-5-1": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 0.25 },
|
|
712
|
-
"claude-mythos-5-1": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 0.25 },
|
|
713
|
-
"claude-fable-5": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 1 },
|
|
714
|
-
"claude-mythos-5": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 1 },
|
|
715
|
-
"claude-opus-5": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
716
|
-
"claude-opus-4-8": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
717
|
-
"claude-opus-4-7": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
718
|
-
"claude-opus-4-6": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
719
|
-
"claude-opus-4-5": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
720
|
-
"claude-opus-4-1": { input: 15, output: 75, cacheWrite5m: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
721
|
-
"claude-opus-4": { input: 15, output: 75, cacheWrite5m: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
722
|
-
"claude-sonnet-5": { input: 2, output: 10, cacheWrite5m: 2.5, cacheWrite1h: 4, cacheRead: 0.2 },
|
|
723
|
-
"claude-sonnet-4-6": { input: 3, output: 15, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
724
|
-
"claude-sonnet-4-5": { input: 3, output: 15, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
725
|
-
"claude-sonnet-4": { input: 3, output: 15, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
726
|
-
"claude-haiku-4-5": { input: 1, output: 5, cacheWrite5m: 1.25, cacheWrite1h: 2, cacheRead: 0.1 },
|
|
727
|
-
"claude-haiku-3-5": { input: 0.8, output: 4, cacheWrite5m: 1, cacheWrite1h: 1.6, cacheRead: 0.08 },
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
const DATED_RELEASE_SUFFIX = /^-\d{8}$/;
|
|
731
|
-
|
|
732
|
-
/** The family prices of a model id: the id itself, or the id less a dated
|
|
733
|
-
* release suffix (`-YYYYMMDD`). Nothing else counts as "the same family" —
|
|
734
|
-
* `claude-fable-5-1` is not `claude-fable-5` with a suffix, and its cache
|
|
735
|
-
* reads bill differently. Unknown → undefined, never a guess. */
|
|
736
|
-
export function anthropicPriceOf(modelId: string): AnthropicModelPrice | undefined {
|
|
737
|
-
const exact = ANTHROPIC_PRICES[modelId];
|
|
738
|
-
if (exact) return exact;
|
|
739
|
-
for (const family of Object.keys(ANTHROPIC_PRICES)) {
|
|
740
|
-
if (modelId.startsWith(family) && DATED_RELEASE_SUFFIX.test(modelId.slice(family.length)))
|
|
741
|
-
return ANTHROPIC_PRICES[family];
|
|
742
|
-
}
|
|
743
|
-
return undefined;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/** Token counts of one usage-report row, in the report's own kinds. */
|
|
747
|
-
export interface AnthropicTokens {
|
|
748
|
-
uncachedInput: number;
|
|
749
|
-
output: number;
|
|
750
|
-
cacheRead: number;
|
|
751
|
-
cacheWrite5m: number;
|
|
752
|
-
cacheWrite1h: number;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/** What those tokens cost at the family's list prices; undefined for a model
|
|
756
|
-
* the table does not know (the caller reports the tokens, never $0). */
|
|
757
|
-
export function anthropicTokensCostUsd(modelId: string, t: AnthropicTokens): number | undefined {
|
|
758
|
-
const p = anthropicPriceOf(modelId);
|
|
759
|
-
if (!p) return undefined;
|
|
760
|
-
return (
|
|
761
|
-
(t.uncachedInput * p.input +
|
|
762
|
-
t.output * p.output +
|
|
763
|
-
t.cacheRead * p.cacheRead +
|
|
764
|
-
t.cacheWrite5m * p.cacheWrite5m +
|
|
765
|
-
t.cacheWrite1h * p.cacheWrite1h) /
|
|
766
|
-
1_000_000
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
703
|
// ---- range ----------------------------------------------------------------------------
|
|
771
704
|
|
|
772
705
|
const DEFAULT_DAYS = 30;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { snapshotAgeText } from "./snapshotAge.js";
|
|
2
|
+
import { DAY_MS } from "./budgets.js";
|
|
2
3
|
import { systemClock } from "./trace/clock.js";
|
|
3
4
|
|
|
4
5
|
// Delivery indicators: what the run history and the pull requests' own facts
|
|
@@ -225,7 +226,6 @@ export function weekIncomplete(report: Pick<DeliveryReport, "truncated" | "compl
|
|
|
225
226
|
|
|
226
227
|
// ---- the arithmetic ---------------------------------------------------------------------
|
|
227
228
|
|
|
228
|
-
const DAY_MS = 86_400_000;
|
|
229
229
|
const HOUR_MS = 3_600_000;
|
|
230
230
|
|
|
231
231
|
/** Add `n` calendar days to a UTC date (`YYYY-MM-DD`). */
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { ModelUsage, RunUsage } from "./runUsage.js";
|
|
2
|
+
|
|
3
|
+
// The price of a model's tokens (docs/reference/specs/costs.md): one table
|
|
4
|
+
// serves every dollar of token arithmetic — the open day's estimate from
|
|
5
|
+
// Anthropic's hourly usage report and the by-user report's run tokens. The
|
|
6
|
+
// list prices are Anthropic's, keyed by family and copied from the pricing
|
|
7
|
+
// page; re-check them when the page moves.
|
|
8
|
+
|
|
9
|
+
/** USD per million tokens of one kind, one model family. */
|
|
10
|
+
export interface AnthropicModelPrice {
|
|
11
|
+
input: number;
|
|
12
|
+
output: number;
|
|
13
|
+
cacheWrite5m: number;
|
|
14
|
+
cacheWrite1h: number;
|
|
15
|
+
cacheRead: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Anthropic list prices per model family (platform.claude.com/docs/en/about-claude/pricing;
|
|
19
|
+
* re-check when it moves). Keyed by the family id the usage report spells — a dated
|
|
20
|
+
* release (`claude-haiku-4-5-20251001`) resolves to its family through
|
|
21
|
+
* `anthropicPriceOf`. Only what the estimate needs: the open day priced at
|
|
22
|
+
* the rate it will bill at; the cost report remains the invoice. */
|
|
23
|
+
export const ANTHROPIC_PRICES: Record<string, AnthropicModelPrice> = {
|
|
24
|
+
"claude-fable-5-1": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 0.25 },
|
|
25
|
+
"claude-mythos-5-1": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 0.25 },
|
|
26
|
+
"claude-fable-5": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 1 },
|
|
27
|
+
"claude-mythos-5": { input: 10, output: 50, cacheWrite5m: 12.5, cacheWrite1h: 20, cacheRead: 1 },
|
|
28
|
+
"claude-opus-5": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
29
|
+
"claude-opus-4-8": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
30
|
+
"claude-opus-4-7": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
31
|
+
"claude-opus-4-6": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
32
|
+
"claude-opus-4-5": { input: 5, output: 25, cacheWrite5m: 6.25, cacheWrite1h: 10, cacheRead: 0.5 },
|
|
33
|
+
"claude-opus-4-1": { input: 15, output: 75, cacheWrite5m: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
34
|
+
"claude-opus-4": { input: 15, output: 75, cacheWrite5m: 18.75, cacheWrite1h: 30, cacheRead: 1.5 },
|
|
35
|
+
"claude-sonnet-5": { input: 2, output: 10, cacheWrite5m: 2.5, cacheWrite1h: 4, cacheRead: 0.2 },
|
|
36
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
37
|
+
"claude-sonnet-4-5": { input: 3, output: 15, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
38
|
+
"claude-sonnet-4": { input: 3, output: 15, cacheWrite5m: 3.75, cacheWrite1h: 6, cacheRead: 0.3 },
|
|
39
|
+
"claude-haiku-4-5": { input: 1, output: 5, cacheWrite5m: 1.25, cacheWrite1h: 2, cacheRead: 0.1 },
|
|
40
|
+
"claude-haiku-3-5": { input: 0.8, output: 4, cacheWrite5m: 1, cacheWrite1h: 1.6, cacheRead: 0.08 },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const DATED_RELEASE_SUFFIX = /^-\d{8}$/;
|
|
44
|
+
|
|
45
|
+
/** The family prices of a model id: the id itself, or the id less a dated
|
|
46
|
+
* release suffix (`-YYYYMMDD`). Nothing else counts as "the same family" —
|
|
47
|
+
* `claude-fable-5-1` is not `claude-fable-5` with a suffix, and its cache
|
|
48
|
+
* reads bill differently. Unknown → undefined, never a guess. */
|
|
49
|
+
export function anthropicPriceOf(modelId: string): AnthropicModelPrice | undefined {
|
|
50
|
+
const exact = ANTHROPIC_PRICES[modelId];
|
|
51
|
+
if (exact) return exact;
|
|
52
|
+
for (const family of Object.keys(ANTHROPIC_PRICES)) {
|
|
53
|
+
if (modelId.startsWith(family) && DATED_RELEASE_SUFFIX.test(modelId.slice(family.length)))
|
|
54
|
+
return ANTHROPIC_PRICES[family];
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Token counts of one usage-report row, in the report's own kinds. */
|
|
60
|
+
export interface AnthropicTokens {
|
|
61
|
+
uncachedInput: number;
|
|
62
|
+
output: number;
|
|
63
|
+
cacheRead: number;
|
|
64
|
+
cacheWrite5m: number;
|
|
65
|
+
cacheWrite1h: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** What those tokens cost at the family's list prices; undefined for a model
|
|
69
|
+
* the table does not know (the caller reports the tokens, never $0). */
|
|
70
|
+
export function anthropicTokensCostUsd(modelId: string, t: AnthropicTokens): number | undefined {
|
|
71
|
+
const p = anthropicPriceOf(modelId);
|
|
72
|
+
if (!p) return undefined;
|
|
73
|
+
return (
|
|
74
|
+
(t.uncachedInput * p.input +
|
|
75
|
+
t.output * p.output +
|
|
76
|
+
t.cacheRead * p.cacheRead +
|
|
77
|
+
t.cacheWrite5m * p.cacheWrite5m +
|
|
78
|
+
t.cacheWrite1h * p.cacheWrite1h) /
|
|
79
|
+
1_000_000
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---- the operator's table: `costs.prices` over the list (costs.md item 4b) --------------
|
|
84
|
+
|
|
85
|
+
/** USD per million tokens of each kind a run's spans count, for one `<provider>/<model>` ref. */
|
|
86
|
+
export interface ModelPrice {
|
|
87
|
+
input: number;
|
|
88
|
+
output: number;
|
|
89
|
+
cacheRead: number;
|
|
90
|
+
cacheWrite: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `costs.prices`: the exact ref as the spans name it → its rates. */
|
|
94
|
+
export type ModelPriceTable = Readonly<Record<string, ModelPrice>>;
|
|
95
|
+
|
|
96
|
+
/** No table configured: the list alone prices. */
|
|
97
|
+
export const NO_PRICES: ModelPriceTable = Object.freeze({});
|
|
98
|
+
|
|
99
|
+
const PRICE_KINDS = ["input", "output", "cacheRead", "cacheWrite"] as const;
|
|
100
|
+
|
|
101
|
+
const isRate = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v) && v >= 0;
|
|
102
|
+
|
|
103
|
+
/** `costs.prices` as config spells it. Absent → the empty table. A key is a
|
|
104
|
+
* `<provider>/<model>` ref; a value names all four kinds as finite dollars per
|
|
105
|
+
* million ≥ 0 (a kind left out would price at $0 in silence); anything else
|
|
106
|
+
* is refused by name. */
|
|
107
|
+
export function parseModelPrices(raw: unknown): ModelPriceTable {
|
|
108
|
+
if (raw === undefined || raw === null) return NO_PRICES;
|
|
109
|
+
if (typeof raw !== "object" || Array.isArray(raw))
|
|
110
|
+
throw new Error("costs.prices must be a mapping of <provider>/<model> → rates");
|
|
111
|
+
const out: Record<string, ModelPrice> = {};
|
|
112
|
+
for (const [ref, value] of Object.entries(raw as Record<string, unknown>)) {
|
|
113
|
+
const slash = ref.indexOf("/");
|
|
114
|
+
if (slash <= 0 || slash === ref.length - 1)
|
|
115
|
+
throw new Error(`costs.prices.${ref} must be keyed <provider>/<model>, the ref a run's spans name`);
|
|
116
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
117
|
+
throw new Error(
|
|
118
|
+
`costs.prices.${ref} must be a mapping of { input, output, cacheRead, cacheWrite } in USD per million tokens`,
|
|
119
|
+
);
|
|
120
|
+
const v = value as Record<string, unknown>;
|
|
121
|
+
const price: Partial<ModelPrice> = {};
|
|
122
|
+
for (const kind of PRICE_KINDS) {
|
|
123
|
+
if (!isRate(v[kind]))
|
|
124
|
+
throw new Error(`costs.prices.${ref}.${kind} must be a finite number of USD per million tokens, 0 or more`);
|
|
125
|
+
price[kind] = v[kind];
|
|
126
|
+
}
|
|
127
|
+
out[ref] = price as ModelPrice;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The price of a ref: the configured table first (the exact ref), else the
|
|
133
|
+
* Anthropic list by family after the provider prefix is dropped — cache
|
|
134
|
+
* writes at the 5-minute rate, the one cache-write count a span carries —
|
|
135
|
+
* and undefined for a model neither knows (the caller reports the tokens,
|
|
136
|
+
* never $0). */
|
|
137
|
+
export function modelPriceOf(ref: string, prices: ModelPriceTable = NO_PRICES): ModelPrice | undefined {
|
|
138
|
+
const configured = prices[ref];
|
|
139
|
+
if (configured) return configured;
|
|
140
|
+
const list = anthropicPriceOf(modelIdOf(ref));
|
|
141
|
+
if (!list) return undefined;
|
|
142
|
+
return { input: list.input, output: list.output, cacheRead: list.cacheRead, cacheWrite: list.cacheWrite5m };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** What a model's counted tokens cost at a price, USD. */
|
|
146
|
+
export function modelUsageUsd(m: ModelUsage, p: ModelPrice): number {
|
|
147
|
+
return (
|
|
148
|
+
(m.inputTokens * p.input +
|
|
149
|
+
m.outputTokens * p.output +
|
|
150
|
+
m.cacheReadTokens * p.cacheRead +
|
|
151
|
+
m.cacheWriteTokens * p.cacheWrite) /
|
|
152
|
+
1_000_000
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ---- a run's tokens, priced -----------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
/** One model's tokens, priced; `usd` is null for a model the price table does not know. */
|
|
159
|
+
export interface PricedModelUsage extends ModelUsage {
|
|
160
|
+
usd: number | null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** `anthropic/claude-fable-5` → `claude-fable-5`: the spans name the provider, the price table the model. */
|
|
164
|
+
export const modelIdOf = (ref: string): string => (ref.includes("/") ? ref.slice(ref.indexOf("/") + 1) : ref);
|
|
165
|
+
|
|
166
|
+
/** A run's dollars as every surface prints them (costs.md item 4c): cents from a
|
|
167
|
+
* dollar up (`$1.24`), a tenth of a cent below that (`$0.038`), and `<$0.001`
|
|
168
|
+
* under that — a run that spent anything never reads as `$0.000`; `$0.00` is a
|
|
169
|
+
* run with no turns and nothing else. */
|
|
170
|
+
export function formatUsd(usd: number): string {
|
|
171
|
+
if (usd === 0) return "$0.00";
|
|
172
|
+
if (usd < 0.001) return "<$0.001";
|
|
173
|
+
return `$${usd.toFixed(usd >= 1 ? 2 : 3)}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** What one run cost (costs.md item 4c): the dollars when every model it ran
|
|
177
|
+
* on has a price, null when one has none — a total that left a model's tokens
|
|
178
|
+
* out would understate the run — and $0 for a run with no turns at all.
|
|
179
|
+
* `byModel` says which model was unpriced. */
|
|
180
|
+
export interface RunCost {
|
|
181
|
+
usd: number | null;
|
|
182
|
+
byModel: Record<string, PricedModelUsage>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function runCostOf(usage: RunUsage, prices: ModelPriceTable = NO_PRICES): RunCost {
|
|
186
|
+
const priced = llmUsdOfUsage(usage, prices);
|
|
187
|
+
const unpriced = Object.values(priced.byModel).some((m) => m.usd === null);
|
|
188
|
+
return { usd: unpriced ? null : priced.usd, byModel: priced.byModel };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** A usage priced through the table (`modelPriceOf`): dollars for the models a
|
|
192
|
+
* price is known for, and the tokens of the ones it is not (never $0 in silence). */
|
|
193
|
+
export function llmUsdOfUsage(
|
|
194
|
+
usage: RunUsage,
|
|
195
|
+
prices: ModelPriceTable = NO_PRICES,
|
|
196
|
+
): {
|
|
197
|
+
usd: number;
|
|
198
|
+
unpricedTokens: number;
|
|
199
|
+
byModel: Record<string, PricedModelUsage>;
|
|
200
|
+
} {
|
|
201
|
+
let usd = 0;
|
|
202
|
+
let unpricedTokens = 0;
|
|
203
|
+
const byModel: Record<string, PricedModelUsage> = {};
|
|
204
|
+
for (const [ref, m] of Object.entries(usage.byModel)) {
|
|
205
|
+
const price = modelPriceOf(ref, prices);
|
|
206
|
+
const priced = price ? modelUsageUsd(m, price) : undefined;
|
|
207
|
+
if (priced === undefined) unpricedTokens += m.inputTokens + m.outputTokens + m.cacheReadTokens + m.cacheWriteTokens;
|
|
208
|
+
else usd += priced;
|
|
209
|
+
byModel[ref] = { ...m, usd: priced ?? null };
|
|
210
|
+
}
|
|
211
|
+
return { usd, unpricedTokens, byModel };
|
|
212
|
+
}
|
|
@@ -6,49 +6,54 @@
|
|
|
6
6
|
// types and enforces (via its annotated parse return) that the zod output
|
|
7
7
|
// stays assignable to them. Add a field here first, then to the schema.
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* the PR head. The sha is NOT stored here — it is supplied at render
|
|
11
|
-
|
|
9
|
+
/** The lines a pointer sends the reader to: a path + inclusive 1-based line
|
|
10
|
+
* range in the PR head. The sha is NOT stored here — it is supplied at render
|
|
11
|
+
* time. */
|
|
12
|
+
export interface PrAnchor {
|
|
12
13
|
path: string;
|
|
13
14
|
from: number;
|
|
14
15
|
to: number;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
/** One
|
|
18
|
-
*
|
|
19
|
-
export interface
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
anchor:
|
|
18
|
+
/** One row of the map's "Where to look": a linked label, one sentence, an
|
|
19
|
+
* optional risk (rendered as ⚠), and the lines the label links to. */
|
|
20
|
+
export interface Pointer {
|
|
21
|
+
label: string;
|
|
22
|
+
text: string;
|
|
23
|
+
risk?: string;
|
|
24
|
+
anchor: PrAnchor;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
export interface
|
|
27
|
+
/** An anchor with the sha its permalink was rendered at — what a reader gets
|
|
28
|
+
* back from a rendered body (or from a submitted object at the head it was
|
|
29
|
+
* rendered for), so a surface can tell whether the pointers are at the head
|
|
30
|
+
* it is looking at. */
|
|
31
|
+
export interface RenderedPrAnchor extends PrAnchor {
|
|
31
32
|
sha: string;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/** A
|
|
35
|
-
export interface
|
|
36
|
-
anchor:
|
|
35
|
+
/** A pointer whose anchor carries its render sha. Assignable to `Pointer`. */
|
|
36
|
+
export interface RenderedPointer extends Pointer {
|
|
37
|
+
anchor: RenderedPrAnchor;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
/** The PR description as data (docs/decisions/0050): the map above the fold
|
|
41
|
+
* (tldr, why, pointers, feedbackWanted, risk, verified), every field capped
|
|
42
|
+
* so the map's size does not grow with the diff, and the collapsed half
|
|
43
|
+
* (decisions, validation, agentNotes) below it. */
|
|
39
44
|
export interface PrDescription {
|
|
40
45
|
/** The PR title's single source. Metadata for the PR's own title field —
|
|
41
46
|
* never rendered into the body (GitHub shows the title itself). */
|
|
42
47
|
title: string;
|
|
43
48
|
tldr: string;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
why: string;
|
|
50
|
+
pointers: Pointer[];
|
|
51
|
+
feedbackWanted: string;
|
|
52
|
+
risk: string;
|
|
53
|
+
verified: string;
|
|
48
54
|
decisions: { title: string; rationale: string }[];
|
|
49
|
-
risks: string;
|
|
50
55
|
validation: {
|
|
51
|
-
summary?: string;
|
|
52
56
|
criteria: { criterion: string; proof: string }[];
|
|
53
57
|
};
|
|
58
|
+
agentNotes?: string;
|
|
54
59
|
}
|
|
@@ -163,6 +163,13 @@ export function formatFinding(f: Finding): string {
|
|
|
163
163
|
return `[${f.severity}] ${f.id} ${location} — ${f.title}`;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/** One compact disposition line — `id: fixed|declined[ — note]` — the coding
|
|
167
|
+
* side's answer to a finding, as ship's re-review turn and the thread's
|
|
168
|
+
* artifacts block both render it. */
|
|
169
|
+
export function formatDisposition(d: FindingDisposition): string {
|
|
170
|
+
return `${d.findingId}: ${d.disposition}${d.note ? ` — ${d.note}` : ""}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
166
173
|
/**
|
|
167
174
|
* Build the body posted to GitHub: the deterministic verdict line, the
|
|
168
175
|
* compact findings list (when present), a blank line, then the model's
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Types only, and from the zod-free module deliberately: this file is part of
|
|
2
2
|
// the node-free contract the memory Worker and web app compile with their own
|
|
3
3
|
// tsconfigs — importing prDescription.ts would drag zod into those graphs.
|
|
4
|
-
import type { PrDescription,
|
|
4
|
+
import type { PrDescription, RenderedPointer } from "./prDescriptionTypes.js";
|
|
5
5
|
import type { HarnessScope } from "./harness/scope.js";
|
|
6
6
|
|
|
7
7
|
/** The `pr_description` review artifact minus the event envelope
|
|
@@ -26,8 +26,11 @@ export interface PrDescriptionArtifact {
|
|
|
26
26
|
/** The body as rendered (submitted) or as GitHub holds it (parsed), capped. */
|
|
27
27
|
body: string;
|
|
28
28
|
tldr?: string;
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
why?: string;
|
|
30
|
+
/** The map's "Where to look", each anchor stamped with the sha its permalink
|
|
31
|
+
* was rendered at. (A record written under the previous contract carries
|
|
32
|
+
* this array as `tour`; the line parser accepts either name.) */
|
|
33
|
+
pointers: RenderedPointer[];
|
|
31
34
|
decisions: { title: string; rationale: string }[];
|
|
32
35
|
/** Nothing missing or malformed — always true for `submitted`. */
|
|
33
36
|
complete: boolean;
|
|
@@ -193,6 +196,20 @@ export type RunNoteKind =
|
|
|
193
196
|
* item 7): the summary names the tool and the rule; the model read the same
|
|
194
197
|
* reason as the tool's result. Published by the bot's authorize route. */
|
|
195
198
|
| "tool_refused"
|
|
199
|
+
/** An OpenCode tool settled under a step this loop never saw start and the
|
|
200
|
+
* settle was set aside (harness.md item 13): an earlier execution's late
|
|
201
|
+
* result — the pinned binary's ordinary shape after a hung call's interrupt
|
|
202
|
+
* — or a step lost with the tailer's stream. The summary names the call and
|
|
203
|
+
* the step. Information, not a failure of the harness: nothing ran that the
|
|
204
|
+
* gate did not decide. Published by the OpenCode bridge in its own mode. */
|
|
205
|
+
| "settle_set_aside"
|
|
206
|
+
/** OpenCode's store refill carried a permission over something other than a
|
|
207
|
+
* tool (a directory outside the project, a repeating session) for a call
|
|
208
|
+
* the record never saw, and the store held no part yet to name the call's
|
|
209
|
+
* tool by: the call was opened under the permission's own name. The summary
|
|
210
|
+
* names the call and the step. Information, not a failure: the tool ran
|
|
211
|
+
* under the gate's decision either way. Published by the OpenCode bridge. */
|
|
212
|
+
| "tool_unnamed"
|
|
196
213
|
/** A ship coding child's budget ended with work still in the tree: the run
|
|
197
214
|
* loop committed and pushed it to the unit's branch (or says plainly that
|
|
198
215
|
* there was nothing to push), so a re-issue starts from the partial work
|
|
@@ -232,6 +249,8 @@ export const RUN_NOTE_KINDS = [
|
|
|
232
249
|
"harness_error",
|
|
233
250
|
"policy_refusal",
|
|
234
251
|
"tool_refused",
|
|
252
|
+
"settle_set_aside",
|
|
253
|
+
"tool_unnamed",
|
|
235
254
|
"budget_salvage",
|
|
236
255
|
"stuck_loop",
|
|
237
256
|
] as const satisfies readonly RunNoteKind[];
|
|
@@ -290,7 +309,17 @@ export function sanitizeActor(actor: RunActor): RunActor {
|
|
|
290
309
|
* settles a round: caps end the pipeline BETWEEN rounds, visible as the
|
|
291
310
|
* absence of a next `started` boundary plus the answer's cap report. */
|
|
292
311
|
export type ShipRoundOutcome =
|
|
293
|
-
|
|
312
|
+
| "started"
|
|
313
|
+
| "pr_opened"
|
|
314
|
+
| "completed"
|
|
315
|
+
| "approve"
|
|
316
|
+
| "request_changes"
|
|
317
|
+
| "no_verdict"
|
|
318
|
+
| "aborted"
|
|
319
|
+
| "stopped"
|
|
320
|
+
/** The coding round ended at its lease with the unit unfinished and the row
|
|
321
|
+
* showing progress, and the grant renewed: the next segment opens (decision 0046). */
|
|
322
|
+
| "continued";
|
|
294
323
|
|
|
295
324
|
/**
|
|
296
325
|
* One event in a run's stream. `seq` is stamped by `RunRegistry.publish` — a
|
|
@@ -388,6 +417,17 @@ export type RouteInputObject2 = { readonly [key: string]: RouteInputLeafOrList |
|
|
|
388
417
|
export type RouteInputObject3 = { readonly [key: string]: RouteInputLeafOrList | RouteInputObject2 };
|
|
389
418
|
export type RouteInputValue = RouteInputLeafOrList | RouteInputObject3;
|
|
390
419
|
|
|
420
|
+
/** How a door decision about a state change ended (docs/decisions/0044-a-routed-write-is-confirmed-in-proportion-to-its-blast-radius.md):
|
|
421
|
+
* `hand_back` — the router bound a state-changing command and the door answered
|
|
422
|
+
* the line to paste, nothing invoked; `offered` — the same decision on a
|
|
423
|
+
* channel that can show a confirmation: the line, its risk and a button, a
|
|
424
|
+
* row stored in the config object, nothing invoked; `confirmed` — the stored
|
|
425
|
+
* input ran at the click, as the requester, through the typed line's path;
|
|
426
|
+
* `pasted` — the typed line that followed a hand-back in its thread, with the
|
|
427
|
+
* same receipt, ran. A routed read carries no outcome: it is not a decision
|
|
428
|
+
* about a state change. */
|
|
429
|
+
export type RouteOutcome = "hand_back" | "offered" | "confirmed" | "pasted";
|
|
430
|
+
|
|
391
431
|
export type RunEvent =
|
|
392
432
|
/** `callId` is the provider's tool_use id — the explicit pair key between a
|
|
393
433
|
* call and its result (live-view item 13); the runner stamps it on both. */
|
|
@@ -551,6 +591,10 @@ export type RunEvent =
|
|
|
551
591
|
* the instance's units. A child's instance rides the record's
|
|
552
592
|
* `parentInstanceId`, never here. */
|
|
553
593
|
instanceId?: string;
|
|
594
|
+
/** The grant the ship request carried (decision 0046, the renewable
|
|
595
|
+
* lease): renewals and a cost cap — what a renewal could spend. Written
|
|
596
|
+
* on the pipeline's own record beside `instanceId`; absent elsewhere. */
|
|
597
|
+
grant?: { renewals: number; costCapUsd?: number };
|
|
554
598
|
seq?: number;
|
|
555
599
|
at?: number;
|
|
556
600
|
}
|
|
@@ -673,6 +717,20 @@ export type RunEvent =
|
|
|
673
717
|
seq?: number;
|
|
674
718
|
at?: number;
|
|
675
719
|
}
|
|
720
|
+
/** The run's lease as the harness started it (docs/reference/specs/run-history.md
|
|
721
|
+
* item 2; decision 0046): when the wall clock began, when the lease ends,
|
|
722
|
+
* and when the loop ends — the lease's end less the write-up and the
|
|
723
|
+
* post-step the lease holds back — so a record says how long the run had
|
|
724
|
+
* and where its loop was cut. Published by the harness once, at the first
|
|
725
|
+
* start of the loop; a resumed run carries the original. Head material,
|
|
726
|
+
* like `run_meta`. Additive: unknown → ignored. */
|
|
727
|
+
| { type: "lease"; startedAt: number; endsAt: number; loopEndsAt: number; seq?: number; at?: number }
|
|
728
|
+
/** A head the run pushed (docs/reference/specs/run-history.md item 2; decision
|
|
729
|
+
* 0046): the branch and the sha the coding post-step observed on the remote
|
|
730
|
+
* (`by: "push"`), or the budget-end salvage pushed (`by: "salvage"`),
|
|
731
|
+
* whether or not a pull request follows — the fact renewal reads. Published
|
|
732
|
+
* straight to the registry like `pr_opened`. Additive: unknown → ignored. */
|
|
733
|
+
| { type: "pushed_head"; ref: string; sha: string; by: "push" | "salvage"; seq?: number; at?: number }
|
|
676
734
|
/** The coordinator tag as a fact of the run (docs/reference/specs/run-history.md
|
|
677
735
|
* item 48a): the instance the run is a child of, the unit its idempotency
|
|
678
736
|
* key named, and the base branch its pull request targets — published by
|
|
@@ -734,7 +792,14 @@ export type RunEvent =
|
|
|
734
792
|
* (`command`), `command` the id the model called, `input` the bound input
|
|
735
793
|
* (redacted, each value capped) and `receipt` the chat form the reply led
|
|
736
794
|
* with (`routed: <chat form>`, redacted and capped at `ROUTE_RECEIPT_CAP`);
|
|
737
|
-
* how the invoke ended is the run's own status and `answer`.
|
|
795
|
+
* how the invoke ended is the run's own status and `answer`. A door
|
|
796
|
+
* decision about a state change (record 0044) is a command run too, with
|
|
797
|
+
* `outcome` saying which: a hand-back invoked nothing and its `answer` is
|
|
798
|
+
* the line to paste; an offer invoked nothing and its `answer` is the offer
|
|
799
|
+
* as the channel shows it; a confirmation is the stored input run at the
|
|
800
|
+
* click, its `answer` the command's own text as a routed read's is; a paste is
|
|
801
|
+
* the typed line that followed a hand-back, whatever its command,
|
|
802
|
+
* `handBackRunId` naming the hand-back's record. */
|
|
738
803
|
| {
|
|
739
804
|
type: "route";
|
|
740
805
|
preset: string;
|
|
@@ -745,6 +810,8 @@ export type RunEvent =
|
|
|
745
810
|
command?: string;
|
|
746
811
|
input?: { readonly [key: string]: RouteInputValue };
|
|
747
812
|
receipt?: string;
|
|
813
|
+
outcome?: RouteOutcome;
|
|
814
|
+
handBackRunId?: string;
|
|
748
815
|
seq?: number;
|
|
749
816
|
at?: number;
|
|
750
817
|
}
|
|
@@ -425,7 +425,9 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
425
425
|
ev.type === "review_posted" ||
|
|
426
426
|
ev.type === "ship_round" ||
|
|
427
427
|
ev.type === "route" ||
|
|
428
|
-
ev.type === "reference"
|
|
428
|
+
ev.type === "reference" ||
|
|
429
|
+
ev.type === "lease" ||
|
|
430
|
+
ev.type === "pushed_head"
|
|
429
431
|
) {
|
|
430
432
|
sideFactEvents++;
|
|
431
433
|
return;
|