@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
|
@@ -588,9 +588,20 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
588
588
|
await Promise.race([settled, new Promise<void>((r) => setTimeout(r, maxMs))]);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
/** `du -sk` over the paths, summed, in KiB; null when nothing could be
|
|
591
|
+
/** `du -sk` over the paths, summed, in KiB; null when nothing could be
|
|
592
|
+
* measured — including a `du` that did not answer inside its own limit
|
|
593
|
+
* (a mount mid-restore under load): the judge treats a sample the
|
|
594
|
+
* container could not take as no evidence either way, never as the
|
|
595
|
+
* restore's failure. The restore's own promise still reports a runtime
|
|
596
|
+
* that went away. */
|
|
592
597
|
private async duKiB(paths: string[]): Promise<number | null> {
|
|
593
|
-
|
|
598
|
+
let r: { stdout: string; exitCode: number };
|
|
599
|
+
try {
|
|
600
|
+
r = await this.runRoot(["du", "-sk", ...paths], 30_000);
|
|
601
|
+
} catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
if (r.exitCode === 124) return null;
|
|
594
605
|
let total = 0;
|
|
595
606
|
let seen = false;
|
|
596
607
|
for (const line of r.stdout.split("\n")) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.244.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.244.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -20445,7 +20445,7 @@
|
|
|
20445
20445
|
},
|
|
20446
20446
|
"packages/switchboard": {
|
|
20447
20447
|
"name": "@coreplane/switchboard",
|
|
20448
|
-
"version": "1.
|
|
20448
|
+
"version": "1.244.0",
|
|
20449
20449
|
"license": "Apache-2.0",
|
|
20450
20450
|
"dependencies": {
|
|
20451
20451
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.244.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/dist/assets/source.json
CHANGED
|
@@ -225,7 +225,7 @@ Keep notes with the \`notes\` tool: one short document, replaced whole each time
|
|
|
225
225
|
// project's most expensive checks first; the rule is the runner's to hold, not
|
|
226
226
|
// a line every requester remembers to paste. Stack-agnostic on purpose — the
|
|
227
227
|
// classes are by duration, the project's own scripts and CI say which is which.
|
|
228
|
-
export const CHECKS_BY_COST = `CHECKS BY COST — push before the expensive ones. Every check you might run has a cost class: seconds (a formatter or a linter on the files you touched, one test file, a docs, link or spec check, the typecheck of one package) or minutes (the whole test suite, a build, a dependency install, an end-to-end or full verification). Know a command's class before you run it — from the project's own scripts and CI configuration, from how long it took last time, or by the class above when you have nothing better. Prove each change with the cheapest check that can prove it, matched to the change's scope: a documentation change gets the documentation checks, one module gets its own tests, a shared type gets the typecheck. As soon as the change exists and those checks pass, commit and push — the pushed branch is the deliverable, and an unpushed tree does not survive the run's end. Only then run the expensive checks, once, and fix forward with further commits and pushes. Never start an operation whose expected duration does not fit the time you have left minus what a commit, a push and the description need: push what there is and say plainly what is unverified instead. The description's validation names exactly what ran; what did not run is CI's to gate, and you say so.`;
|
|
228
|
+
export const CHECKS_BY_COST = `CHECKS BY COST — push before the expensive ones. Every check you might run has a cost class: seconds (a formatter or a linter on the files you touched, one test file, a docs, link or spec check, the typecheck of one package) or minutes (the whole test suite, a build, a dependency install, an end-to-end or full verification). Know a command's class before you run it — from the project's own scripts and CI configuration, from how long it took last time, or by the class above when you have nothing better. Prove each change with the cheapest check that can prove it, matched to the change's scope: a documentation change gets the documentation checks, one module gets its own tests, a shared type gets the typecheck. As soon as the change exists and those checks pass, commit and push — the pushed branch is the deliverable, and an unpushed tree does not survive the run's end. Only then run the expensive checks, once, and fix forward with further commits and pushes. Never start an operation whose expected duration does not fit the time you have left minus what a commit, a push and the description need: push what there is and say plainly what is unverified instead. At the wind-down note, commit and push what compiles, say what does not, then answer. The description's validation names exactly what ran; what did not run is CI's to gate, and you say so.`;
|
|
229
229
|
|
|
230
230
|
const CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
|
|
231
231
|
|
|
@@ -196,4 +196,14 @@ export interface ChannelDirectory {
|
|
|
196
196
|
/** Every channel the actor is in, platform-namespaced — what the resolver puts on
|
|
197
197
|
* `Actor.memberOf`; `unknown` when the adapter cannot say (fail-closed). */
|
|
198
198
|
channelsOf(actorId: string): Promise<ReadonlySet<string> | "unknown">;
|
|
199
|
+
/** Every channel the bot itself is in, with its visibility — the channels a person can
|
|
200
|
+
* pick a scope or an MCP tier for (settings-page.md item 7); `unknown` when the adapter
|
|
201
|
+
* cannot list them, and the surfaces fall back to the channels that already carry a scope. */
|
|
202
|
+
channels(): Promise<readonly ListedChannel[] | "unknown">;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** One channel the bot is in, as the directory lists it. */
|
|
206
|
+
export interface ListedChannel {
|
|
207
|
+
id: string;
|
|
208
|
+
visibility: ChannelVisibility;
|
|
199
209
|
}
|
|
@@ -18,6 +18,18 @@
|
|
|
18
18
|
|
|
19
19
|
export const MINUTE_MS = 60_000;
|
|
20
20
|
|
|
21
|
+
/** One calendar day in milliseconds: the unit the daily cost and delivery ranges
|
|
22
|
+
* step by (`dayOf`, the day count of a range). A day is not a lease, but it is
|
|
23
|
+
* a duration, and every duration is read from this table rather than written
|
|
24
|
+
* as a literal where it is used. */
|
|
25
|
+
export const DAY_MS = 24 * 60 * MINUTE_MS;
|
|
26
|
+
|
|
27
|
+
/** How long the confirmation a routed write is offered as stays pending
|
|
28
|
+
* (docs/decisions/0044; docs/reference/specs/routing-and-config.md item 25):
|
|
29
|
+
* the connect ticket's ten minutes. The bot passes it to the config object,
|
|
30
|
+
* which stamps the expiry on its own clock. */
|
|
31
|
+
export const CONFIRMATION_TTL_MS = 10 * MINUTE_MS;
|
|
32
|
+
|
|
21
33
|
/** The presets that run the tool loop, and the one pipeline preset. */
|
|
22
34
|
export const LOOP_PRESETS = ["general", "coding", "review", "research", "explore", "conductor"] as const;
|
|
23
35
|
export type LoopPreset = (typeof LOOP_PRESETS)[number];
|
|
@@ -311,3 +323,27 @@ export function fit(pipeline: Pipeline): { ok: boolean; need: number; have: numb
|
|
|
311
323
|
const need = ALLOWANCES.provision + ASKS.coding + reserveMinutes({ kind: "coding", index: 0 }, pipeline);
|
|
312
324
|
return { ok: pipeline.maxMinutes >= need, need, have: pipeline.maxMinutes };
|
|
313
325
|
}
|
|
326
|
+
|
|
327
|
+
// ---- the grant (decision 0046, Renewal: the grant decides, the lease continues) ----
|
|
328
|
+
|
|
329
|
+
/** What the request authorizes for the whole problem beyond one lease: a count
|
|
330
|
+
* of renewals and a cost cap in dollars. A lease is sized by the infrastructure
|
|
331
|
+
* that must outlive it; the grant is sized by the person — a scope's word or
|
|
332
|
+
* the request's `renewals:` directive — and the runner spends it one segment at
|
|
333
|
+
* a time. Zero renewals and no cap by default, so nothing renews until someone
|
|
334
|
+
* says so; `costCapUsd` absent is no cap, never zero. */
|
|
335
|
+
export interface Grant {
|
|
336
|
+
renewals: number;
|
|
337
|
+
costCapUsd?: number;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Which layer's word the grant is: the request's directive, the user's scope,
|
|
341
|
+
* the channel's, or the org's `ship.grant` — the default counts as the org's. */
|
|
342
|
+
export type GrantSource = "org" | "channel" | "user" | "run";
|
|
343
|
+
|
|
344
|
+
export const DEFAULT_GRANT: Grant = { renewals: 0 };
|
|
345
|
+
|
|
346
|
+
/** The most renewals one request may carry: thirteen segments of the ship
|
|
347
|
+
* preset's ask are a day, the longest problem a person hands over in one
|
|
348
|
+
* message before a plan should carry it. */
|
|
349
|
+
export const GRANT_RENEWALS_MAX = 12;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// parent record the spawn route reads the requester from, and the names the
|
|
9
9
|
// routes decide on.
|
|
10
10
|
//
|
|
11
|
+
import type { Grant, GrantSource } from "../budgets.js";
|
|
11
12
|
import type { AddressSeverity, AddressSeveritySource } from "../ship/coordinator.js";
|
|
12
13
|
|
|
13
14
|
// A coordinator is a Workflow instance in the shim Worker whose children are
|
|
@@ -181,6 +182,13 @@ export interface CoordinatorInstance {
|
|
|
181
182
|
* the machine reads one value; absent reads as the default (`minor`, org). */
|
|
182
183
|
addressSeverity?: AddressSeverity;
|
|
183
184
|
addressSeveritySource?: AddressSeveritySource;
|
|
185
|
+
/** The grant (decision 0046, the renewable lease): the renewals and cost cap
|
|
186
|
+
* the request carries, resolved once by the ship fork (directive count >
|
|
187
|
+
* user > channel > org) and written here beside `merge`; absent reads as
|
|
188
|
+
* zero renewals and no cap, the org's. Nothing renews until the renewal
|
|
189
|
+
* decision reads it. */
|
|
190
|
+
grant?: Grant;
|
|
191
|
+
grantSource?: GrantSource;
|
|
184
192
|
/** The pipeline's caps as the profile gate clipped them: the rounds cap and the wall clock per unit. */
|
|
185
193
|
caps?: { maxRounds: number; maxMinutes: number };
|
|
186
194
|
/** The status card in the requesting thread, when the channel has one — what
|
|
@@ -195,6 +203,13 @@ export interface CoordinatorInstance {
|
|
|
195
203
|
attempt?: number;
|
|
196
204
|
}
|
|
197
205
|
|
|
206
|
+
export interface UnitSegment {
|
|
207
|
+
index: number;
|
|
208
|
+
from?: string;
|
|
209
|
+
runId?: string;
|
|
210
|
+
at: number;
|
|
211
|
+
}
|
|
212
|
+
|
|
198
213
|
/** One unit of the plan an instance runs (a task string is a generated plan of
|
|
199
214
|
* one unit, `U1`): its branch, the units it waits on, and — as the runner
|
|
200
215
|
* reaches it — its thread, its pull request, the round boundaries the card
|
|
@@ -229,6 +244,13 @@ export interface CoordinatorUnit {
|
|
|
229
244
|
* opened or updated): the next attempt's pre-check starts at the review
|
|
230
245
|
* round when the open pull request still heads exactly here. */
|
|
231
246
|
lastPush?: string;
|
|
247
|
+
/** The renewals the unit spent (decision 0046, Renewal): one row per segment
|
|
248
|
+
* the grant opened after the first, keyed by the segment's index — written
|
|
249
|
+
* by `unit-end` on a `continued` ending before the segment runs, so a runner
|
|
250
|
+
* reclaimed between a segment's end and its renewal finds the row and never
|
|
251
|
+
* renews the same segment twice. `from` is the sha the segment continues
|
|
252
|
+
* from, `runId` the coding run whose write-up briefs it. */
|
|
253
|
+
segments?: UnitSegment[];
|
|
232
254
|
/** The round boundaries the coordinator reported, oldest first (the `ship_round` vocabulary). */
|
|
233
255
|
rounds: Array<{ index: number; agent: string; outcome: string; at: number }>;
|
|
234
256
|
/** How the unit ended: the ending's kind and the thread's report, when it has. */
|
|
@@ -281,6 +303,15 @@ export function isCoordinatorInstance(v: unknown): v is CoordinatorInstance {
|
|
|
281
303
|
}
|
|
282
304
|
|
|
283
305
|
/** Structural check on a unit row from outside the process. */
|
|
306
|
+
const isSegment = (v: unknown): boolean =>
|
|
307
|
+
isObject(v) &&
|
|
308
|
+
typeof v.index === "number" &&
|
|
309
|
+
Number.isInteger(v.index) &&
|
|
310
|
+
v.index >= 2 &&
|
|
311
|
+
(v.from === undefined || isText(v.from)) &&
|
|
312
|
+
(v.runId === undefined || isText(v.runId)) &&
|
|
313
|
+
typeof v.at === "number";
|
|
314
|
+
|
|
284
315
|
export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
|
|
285
316
|
if (!isObject(v)) return false;
|
|
286
317
|
const r = v;
|
|
@@ -293,6 +324,7 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
|
|
|
293
324
|
if (r.pr !== undefined && !isPr(r.pr)) return false;
|
|
294
325
|
if (r.resume !== undefined && !isResume(r.resume)) return false;
|
|
295
326
|
if (r.lastPush !== undefined && !isText(r.lastPush)) return false;
|
|
327
|
+
if (r.segments !== undefined && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
|
|
296
328
|
if (
|
|
297
329
|
!Array.isArray(r.rounds) ||
|
|
298
330
|
r.rounds.length > MAX_ROUNDS ||
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
// plan's re-issue. A task string's ship branch waits for a person. Node-free:
|
|
34
34
|
// the shim Worker imports this by relative path.
|
|
35
35
|
|
|
36
|
+
import { DEFAULT_GRANT, GRANT_RENEWALS_MAX, type Grant, type GrantSource } from "../budgets.js";
|
|
36
37
|
import {
|
|
37
38
|
applyReturn,
|
|
38
39
|
cursorFinished,
|
|
@@ -55,6 +56,8 @@ import {
|
|
|
55
56
|
type StepReturn,
|
|
56
57
|
type UnitEnding,
|
|
57
58
|
type UnitPipelineState,
|
|
59
|
+
stepPrefixOf,
|
|
60
|
+
type UnitSession,
|
|
58
61
|
} from "../ship/coordinator.js";
|
|
59
62
|
import { checksSettledEventType, isCoordinatorUnit, runFinishedEventType, type CoordinatorUnit } from "./contract.js";
|
|
60
63
|
|
|
@@ -164,6 +167,9 @@ interface PlanFacts {
|
|
|
164
167
|
/** The severity to address, beside `merge`: the level an approve's findings are held to, and which layer set it. */
|
|
165
168
|
addressSeverity: AddressSeverity;
|
|
166
169
|
addressSeveritySource: AddressSeveritySource;
|
|
170
|
+
/** The grant beside them (decision 0046): what a renewal could spend, and which layer granted it. */
|
|
171
|
+
grant: Grant;
|
|
172
|
+
grantSource: GrantSource;
|
|
167
173
|
/** The instance's mark as the plan route answers it: a generated one-unit plan (a `plan` with no `path`). */
|
|
168
174
|
generated: boolean;
|
|
169
175
|
repo: string;
|
|
@@ -176,6 +182,21 @@ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === "obj
|
|
|
176
182
|
const isMinutes = (v: unknown): v is Record<string, number> =>
|
|
177
183
|
isRecord(v) && Object.values(v).every((n) => typeof n === "number" && Number.isFinite(n));
|
|
178
184
|
|
|
185
|
+
/** The grant as the plan route answers it; anything unreadable — a count
|
|
186
|
+
* outside the module's ceiling included — is the default: nothing renews on a guess. */
|
|
187
|
+
function readGrant(raw: unknown): Grant {
|
|
188
|
+
if (
|
|
189
|
+
!isRecord(raw) ||
|
|
190
|
+
typeof raw.renewals !== "number" ||
|
|
191
|
+
!Number.isInteger(raw.renewals) ||
|
|
192
|
+
raw.renewals < 0 ||
|
|
193
|
+
raw.renewals > GRANT_RENEWALS_MAX
|
|
194
|
+
)
|
|
195
|
+
return DEFAULT_GRANT;
|
|
196
|
+
const cap = raw.costCapUsd;
|
|
197
|
+
return { renewals: raw.renewals, ...(typeof cap === "number" && cap > 0 ? { costCapUsd: cap } : {}) };
|
|
198
|
+
}
|
|
199
|
+
|
|
179
200
|
function readPlan(a: BotAnswer): PlanFacts {
|
|
180
201
|
const b = a.body;
|
|
181
202
|
if (b.ok !== true) throw new UnreadableAnswer("plan", a, "not ok");
|
|
@@ -192,6 +213,9 @@ function readPlan(a: BotAnswer): PlanFacts {
|
|
|
192
213
|
b.addressSeveritySource === "run" || b.addressSeveritySource === "user" || b.addressSeveritySource === "channel"
|
|
193
214
|
? b.addressSeveritySource
|
|
194
215
|
: "org",
|
|
216
|
+
grant: readGrant(b.grant),
|
|
217
|
+
grantSource:
|
|
218
|
+
b.grantSource === "run" || b.grantSource === "user" || b.grantSource === "channel" ? b.grantSource : "org",
|
|
195
219
|
generated: b.generated === true,
|
|
196
220
|
repo: b.repo,
|
|
197
221
|
base: b.base,
|
|
@@ -252,6 +276,10 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
252
276
|
reviewHead,
|
|
253
277
|
dispositions,
|
|
254
278
|
handoff,
|
|
279
|
+
pushed,
|
|
280
|
+
leaseStartedAt,
|
|
281
|
+
costUsd,
|
|
282
|
+
handoffLists,
|
|
255
283
|
} = facts;
|
|
256
284
|
return {
|
|
257
285
|
type: "read-record",
|
|
@@ -269,6 +297,11 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
269
297
|
...(reviewHead !== undefined ? { reviewHead } : {}),
|
|
270
298
|
...(dispositions !== undefined ? { dispositions } : {}),
|
|
271
299
|
...(handoff !== undefined ? { handoff } : {}),
|
|
300
|
+
// The renewal's facts (decision 0046), as the record carries them.
|
|
301
|
+
...(Array.isArray(pushed) ? { pushed } : {}),
|
|
302
|
+
...(typeof leaseStartedAt === "number" ? { leaseStartedAt } : {}),
|
|
303
|
+
...(typeof costUsd === "number" || costUsd === null ? { costUsd } : {}),
|
|
304
|
+
...(handoffLists !== undefined ? { handoffLists } : {}),
|
|
272
305
|
},
|
|
273
306
|
at: a.body.at,
|
|
274
307
|
};
|
|
@@ -457,11 +490,16 @@ async function runUnit(
|
|
|
457
490
|
instanceId: string,
|
|
458
491
|
node: PlanUnitNode,
|
|
459
492
|
plan: PlanFacts,
|
|
493
|
+
session?: UnitSession,
|
|
460
494
|
): Promise<UnitEnding> {
|
|
461
495
|
const unit = node.id;
|
|
496
|
+
// A renewal's segment names its steps under the segment (`U10/s2/…`), so
|
|
497
|
+
// the Workflow's durable step cache never answers segment two with segment
|
|
498
|
+
// one's results (decision 0046).
|
|
499
|
+
const prefix = stepPrefixOf(unit, session);
|
|
462
500
|
const tag = { parentInstanceId: instanceId, unit };
|
|
463
501
|
const start = readUnitStart(
|
|
464
|
-
answerOf("unit-start", await step.do(`${
|
|
502
|
+
answerOf("unit-start", await step.do(`${prefix}/start`, STEP_CONFIG, () => call(bot, "unit-start", tag))),
|
|
465
503
|
);
|
|
466
504
|
// A resume at review (agent-ship item 10) rides the unit's row: the pull
|
|
467
505
|
// request of ship's own the requester named opens the pipeline at its first
|
|
@@ -484,9 +522,12 @@ async function runUnit(
|
|
|
484
522
|
merge: plan.merge,
|
|
485
523
|
addressSeverity: plan.addressSeverity,
|
|
486
524
|
addressSeveritySource: plan.addressSeveritySource,
|
|
525
|
+
grant: plan.grant,
|
|
526
|
+
grantSource: plan.grantSource,
|
|
487
527
|
generated: plan.generated,
|
|
488
528
|
...(resume !== undefined ? { resume } : {}),
|
|
489
529
|
...(lastPush !== undefined ? { lastPush } : {}),
|
|
530
|
+
...(session !== undefined ? { session } : {}),
|
|
490
531
|
},
|
|
491
532
|
start.at,
|
|
492
533
|
);
|
|
@@ -499,7 +540,7 @@ async function runUnit(
|
|
|
499
540
|
for (const note of transition.notes) {
|
|
500
541
|
if (note.type === "round") {
|
|
501
542
|
const body = { ...tag, index: note.index, agent: note.agent, outcome: note.outcome };
|
|
502
|
-
await step.do(`${
|
|
543
|
+
await step.do(`${prefix}/note/${++notes}`, STEP_CONFIG, () => call(bot, "round", body));
|
|
503
544
|
} else {
|
|
504
545
|
// A merge_ready ending names the pull request as it is at the APPROVED
|
|
505
546
|
// head (agent-ship item 9): one more pr-check reads the facts fresh —
|
|
@@ -511,10 +552,10 @@ async function runUnit(
|
|
|
511
552
|
if (note.ending.kind === "merge_ready") {
|
|
512
553
|
try {
|
|
513
554
|
const check = prCheckReturn(
|
|
514
|
-
`${
|
|
555
|
+
`${prefix}/end/pr-facts`,
|
|
515
556
|
answerOf(
|
|
516
557
|
"pr-check",
|
|
517
|
-
await step.do(`${
|
|
558
|
+
await step.do(`${prefix}/end/pr-facts`, STEP_CONFIG, () => call(bot, "pr-check", tag)),
|
|
518
559
|
),
|
|
519
560
|
);
|
|
520
561
|
if (check.type === "pr-check" && check.pr.state === "merged")
|
|
@@ -538,8 +579,20 @@ async function runUnit(
|
|
|
538
579
|
? { headSha: note.ending.headSha }
|
|
539
580
|
: {}),
|
|
540
581
|
...(state.lastCodingRunId !== undefined ? { codingRunId: state.lastCodingRunId } : {}),
|
|
582
|
+
// A continued ending is a segment's end, not the unit's: the bot
|
|
583
|
+
// writes the renewal as a row keyed by the next segment's index
|
|
584
|
+
// (decision 0046), so a runner reclaimed here never renews twice.
|
|
585
|
+
...(note.ending.kind === "continued"
|
|
586
|
+
? {
|
|
587
|
+
segment: {
|
|
588
|
+
index: note.ending.segment,
|
|
589
|
+
...(note.ending.from !== undefined ? { from: note.ending.from } : {}),
|
|
590
|
+
runId: note.ending.runId,
|
|
591
|
+
},
|
|
592
|
+
}
|
|
593
|
+
: {}),
|
|
541
594
|
};
|
|
542
|
-
await step.do(`${
|
|
595
|
+
await step.do(`${prefix}/end`, STEP_CONFIG, () => call(bot, "unit-end", body));
|
|
543
596
|
}
|
|
544
597
|
}
|
|
545
598
|
}
|
|
@@ -573,13 +626,23 @@ async function walk(step: StepRunner, bot: CoordinatorBot, instanceId: string):
|
|
|
573
626
|
const [next] = readyUnits(graph, cursor);
|
|
574
627
|
if (next === undefined) break;
|
|
575
628
|
cursor = startUnit(graph, cursor, next);
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
)
|
|
629
|
+
const node = graph.units.find((u) => u.id === next)!;
|
|
630
|
+
let ending = await runUnit(step, bot, instanceId, node, plan);
|
|
631
|
+
// The lease continues while the grant renews (decision 0046): each
|
|
632
|
+
// `continued` ending opens the next segment of the same unit — a fresh
|
|
633
|
+
// pipeline under a fresh lease, from the recorded sha, briefed with the
|
|
634
|
+
// previous segment's write-up — until the unit ends some other way.
|
|
635
|
+
while (ending.kind === "continued") {
|
|
636
|
+
const c = ending;
|
|
637
|
+
ending = await runUnit(step, bot, instanceId, node, plan, {
|
|
638
|
+
segment: c.segment,
|
|
639
|
+
renewalsSpent: c.segment - 1,
|
|
640
|
+
spendUsd: c.spendUsd,
|
|
641
|
+
...(c.from !== undefined ? { continueFrom: c.from } : {}),
|
|
642
|
+
previousRunId: c.runId,
|
|
643
|
+
...(c.handoff !== undefined ? { previousHandoff: c.handoff } : {}),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
583
646
|
endings[next] = ending.kind;
|
|
584
647
|
cursor = settleUnit(graph, cursor, next, ending.kind === "merged" ? "done" : "failed");
|
|
585
648
|
}
|
|
@@ -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`). */
|
|
@@ -196,6 +196,20 @@ export type RunNoteKind =
|
|
|
196
196
|
* item 7): the summary names the tool and the rule; the model read the same
|
|
197
197
|
* reason as the tool's result. Published by the bot's authorize route. */
|
|
198
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"
|
|
199
213
|
/** A ship coding child's budget ended with work still in the tree: the run
|
|
200
214
|
* loop committed and pushed it to the unit's branch (or says plainly that
|
|
201
215
|
* there was nothing to push), so a re-issue starts from the partial work
|
|
@@ -235,6 +249,8 @@ export const RUN_NOTE_KINDS = [
|
|
|
235
249
|
"harness_error",
|
|
236
250
|
"policy_refusal",
|
|
237
251
|
"tool_refused",
|
|
252
|
+
"settle_set_aside",
|
|
253
|
+
"tool_unnamed",
|
|
238
254
|
"budget_salvage",
|
|
239
255
|
"stuck_loop",
|
|
240
256
|
] as const satisfies readonly RunNoteKind[];
|
|
@@ -293,7 +309,17 @@ export function sanitizeActor(actor: RunActor): RunActor {
|
|
|
293
309
|
* settles a round: caps end the pipeline BETWEEN rounds, visible as the
|
|
294
310
|
* absence of a next `started` boundary plus the answer's cap report. */
|
|
295
311
|
export type ShipRoundOutcome =
|
|
296
|
-
|
|
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";
|
|
297
323
|
|
|
298
324
|
/**
|
|
299
325
|
* One event in a run's stream. `seq` is stamped by `RunRegistry.publish` — a
|
|
@@ -393,10 +419,14 @@ export type RouteInputValue = RouteInputLeafOrList | RouteInputObject3;
|
|
|
393
419
|
|
|
394
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):
|
|
395
421
|
* `hand_back` — the router bound a state-changing command and the door answered
|
|
396
|
-
* the line to paste, nothing invoked; `
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
|
|
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";
|
|
400
430
|
|
|
401
431
|
export type RunEvent =
|
|
402
432
|
/** `callId` is the provider's tool_use id — the explicit pair key between a
|
|
@@ -561,6 +591,10 @@ export type RunEvent =
|
|
|
561
591
|
* the instance's units. A child's instance rides the record's
|
|
562
592
|
* `parentInstanceId`, never here. */
|
|
563
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 };
|
|
564
598
|
seq?: number;
|
|
565
599
|
at?: number;
|
|
566
600
|
}
|
|
@@ -691,6 +725,12 @@ export type RunEvent =
|
|
|
691
725
|
* start of the loop; a resumed run carries the original. Head material,
|
|
692
726
|
* like `run_meta`. Additive: unknown → ignored. */
|
|
693
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 }
|
|
694
734
|
/** The coordinator tag as a fact of the run (docs/reference/specs/run-history.md
|
|
695
735
|
* item 48a): the instance the run is a child of, the unit its idempotency
|
|
696
736
|
* key named, and the base branch its pull request targets — published by
|
|
@@ -755,8 +795,11 @@ export type RunEvent =
|
|
|
755
795
|
* how the invoke ended is the run's own status and `answer`. A door
|
|
756
796
|
* decision about a state change (record 0044) is a command run too, with
|
|
757
797
|
* `outcome` saying which: a hand-back invoked nothing and its `answer` is
|
|
758
|
-
* the line to paste;
|
|
759
|
-
*
|
|
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. */
|
|
760
803
|
| {
|
|
761
804
|
type: "route";
|
|
762
805
|
preset: string;
|
|
@@ -426,7 +426,8 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
|
|
|
426
426
|
ev.type === "ship_round" ||
|
|
427
427
|
ev.type === "route" ||
|
|
428
428
|
ev.type === "reference" ||
|
|
429
|
-
ev.type === "lease"
|
|
429
|
+
ev.type === "lease" ||
|
|
430
|
+
ev.type === "pushed_head"
|
|
430
431
|
) {
|
|
431
432
|
sideFactEvents++;
|
|
432
433
|
return;
|
|
@@ -213,6 +213,10 @@ export interface RunRecord {
|
|
|
213
213
|
* `lease` event, folded at the assembly. Present only on a run whose
|
|
214
214
|
* harness started a loop; a record written before the event lacks it. */
|
|
215
215
|
lease?: RunLease;
|
|
216
|
+
/** The heads the run pushed (item 2; decision 0046): one per branch, the
|
|
217
|
+
* last `pushed_head` event's sha winning — the fact a renewal reads to know
|
|
218
|
+
* the run made progress. Present only on a run that pushed. */
|
|
219
|
+
pushed?: PushedHead[];
|
|
216
220
|
/** What the run cost in tokens, per model, summed from its `model.turn`
|
|
217
221
|
* spans at finish (`usageOfEvents`; docs/reference/specs/costs.md, cost by user).
|
|
218
222
|
* Every record written since carries it (zero turns included); one written
|
|
@@ -238,6 +242,21 @@ export interface RunLease {
|
|
|
238
242
|
loopEndsAt: number;
|
|
239
243
|
}
|
|
240
244
|
|
|
245
|
+
/** A head a run pushed, as the record names it (item 2): the branch and the sha. */
|
|
246
|
+
export interface PushedHead {
|
|
247
|
+
ref: string;
|
|
248
|
+
sha: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** The heads a run's events say it pushed — one per branch, the last event's
|
|
252
|
+
* sha winning, in first-seen order — or nothing when it pushed none. */
|
|
253
|
+
export function pushedHeadsOf(events: readonly RunEvent[]): PushedHead[] | undefined {
|
|
254
|
+
const byRef = new Map<string, string>();
|
|
255
|
+
for (const e of events) if (e.type === "pushed_head") byRef.set(e.ref, e.sha);
|
|
256
|
+
if (byRef.size === 0) return undefined;
|
|
257
|
+
return [...byRef].map(([ref, sha]) => ({ ref, sha }));
|
|
258
|
+
}
|
|
259
|
+
|
|
241
260
|
/** The lease a run's events say its harness started — the first `lease`
|
|
242
261
|
* event; a resumed run's later generations publish none — or nothing. */
|
|
243
262
|
export function leaseOfEvents(events: readonly RunEvent[]): RunLease | undefined {
|
|
@@ -798,6 +817,18 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
798
817
|
if (r.profile !== undefined && !isRunProfileRecord(r.profile)) return false;
|
|
799
818
|
// The pull request the run reached (item 2): a positive integer and a URL, or absent.
|
|
800
819
|
if (r.pr !== undefined && !isRunPullRequestShape(r.pr)) return false;
|
|
820
|
+
if (
|
|
821
|
+
r.pushed !== undefined &&
|
|
822
|
+
!(
|
|
823
|
+
Array.isArray(r.pushed) &&
|
|
824
|
+
r.pushed.every((h) => {
|
|
825
|
+
if (typeof h !== "object" || h === null) return false;
|
|
826
|
+
const { ref, sha } = h as Record<string, unknown>;
|
|
827
|
+
return typeof ref === "string" && ref.length > 0 && typeof sha === "string" && /^[0-9a-f]{7,40}$/.test(sha);
|
|
828
|
+
})
|
|
829
|
+
)
|
|
830
|
+
)
|
|
831
|
+
return false;
|
|
801
832
|
// A parent is named by a run id (item 46): the same shape as the record's own.
|
|
802
833
|
if (r.parentRunId !== undefined && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
|
|
803
834
|
return false;
|
|
@@ -431,7 +431,8 @@ function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
|
|
|
431
431
|
`Push the branch as soon as the change exists and its cheapest proving checks pass — before the project's ` +
|
|
432
432
|
`full verification, which runs after that push with any fix as a further commit; an unpushed tree does not ` +
|
|
433
433
|
`survive the run's end. Right before each push, fetch ${onto} again and rebase once more if it moved while ` +
|
|
434
|
-
`you worked, so the pull request is not born conflicting
|
|
434
|
+
`you worked, so the pull request is not born conflicting. At the wind-down note, commit and push what compiles, ` +
|
|
435
|
+
`say what does not, then answer.`
|
|
435
436
|
);
|
|
436
437
|
}
|
|
437
438
|
|