@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
|
@@ -19,19 +19,83 @@
|
|
|
19
19
|
// a fix round can reference each finding by its stable id and answer it with
|
|
20
20
|
// a typed disposition (`parseDispositionsInput` below). Findings validate
|
|
21
21
|
// fail-closed PER finding — a malformed finding drops with a note, a
|
|
22
|
-
// malformed array drops the whole field
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
22
|
+
// malformed array drops the whole field.
|
|
23
|
+
//
|
|
24
|
+
// THE SEVERITY GATE (docs/reference/specs/agent-review.md item 5a): an
|
|
25
|
+
// `approve` carrying a finding at or above the severity to address — the
|
|
26
|
+
// level in force for the run, `minor` by default — is downgraded to
|
|
27
|
+
// `request_changes` HERE, where the verdict is parsed, so the posted body can
|
|
28
|
+
// never begin with `LGTM:` over a finding the loop would have to act on. The
|
|
29
|
+
// gate is keyed on the RAW entries' declared severity, so a finding that
|
|
30
|
+
// fails validation for another reason still poisons the approve. The same
|
|
31
|
+
// ladder is what ship's coordinator holds a posted approve to (agent-ship.md
|
|
32
|
+
// item 9) — that check is now defense in depth behind this one.
|
|
26
33
|
|
|
27
34
|
import { normalizeHead } from "./reviewedHead.js";
|
|
28
35
|
import { redactSecrets } from "./redact.js";
|
|
29
36
|
|
|
30
37
|
export type ReviewVerdictKind = "approve" | "request_changes";
|
|
31
38
|
|
|
39
|
+
/** The severity ladder, most to least severe. One list serves the findings a
|
|
40
|
+
* reviewer submits and the level the loop addresses: a finding's severity is
|
|
41
|
+
* compared to the level in force on this ladder, so nothing outside it can
|
|
42
|
+
* reach a gate — the parser drops an entry with any other severity (`fyi`,
|
|
43
|
+
* none) with a note. */
|
|
32
44
|
export const FINDING_SEVERITIES = ["blocking", "major", "minor", "nit"] as const;
|
|
33
45
|
export type FindingSeverity = (typeof FINDING_SEVERITIES)[number];
|
|
34
46
|
|
|
47
|
+
/** The severity to address (the loop's gate on a round): an approve carrying a
|
|
48
|
+
* finding at or above this level is not an approve. Resolved once per run —
|
|
49
|
+
* the request's `severity:` directive over the user's scope over the
|
|
50
|
+
* channel's over the org's `review.addressSeverity` — and handed to the
|
|
51
|
+
* verdict parser and ship's coordinator alike. */
|
|
52
|
+
export const ADDRESS_SEVERITIES = FINDING_SEVERITIES;
|
|
53
|
+
export type AddressSeverity = FindingSeverity;
|
|
54
|
+
/** Where the level in force came from, most specific wins: a `severity:`
|
|
55
|
+
* directive on the request (`run`), the user's or the channel's config scope,
|
|
56
|
+
* the org's `review.addressSeverity` (or its default). */
|
|
57
|
+
export type AddressSeveritySource = "org" | "channel" | "user" | "run";
|
|
58
|
+
export const DEFAULT_ADDRESS_SEVERITY: AddressSeverity = "minor";
|
|
59
|
+
export const isAddressSeverity = (v: unknown): v is AddressSeverity =>
|
|
60
|
+
(ADDRESS_SEVERITIES as readonly unknown[]).includes(v);
|
|
61
|
+
|
|
62
|
+
const severityRank = (s: AddressSeverity): number => ADDRESS_SEVERITIES.indexOf(s);
|
|
63
|
+
/** Whether a declared severity sits at or above the level in force on the
|
|
64
|
+
* ladder — the one comparison every gate makes. A value outside the ladder is
|
|
65
|
+
* never at or above anything. */
|
|
66
|
+
export function severityAtOrAbove(severity: unknown, level: AddressSeverity): severity is AddressSeverity {
|
|
67
|
+
return isAddressSeverity(severity) && severityRank(severity) <= severityRank(level);
|
|
68
|
+
}
|
|
69
|
+
/** The findings a gate acts on at `level`: at or above it. */
|
|
70
|
+
export function findingsAtOrAbove(findings: readonly Finding[], level: AddressSeverity): Finding[] {
|
|
71
|
+
return findings.filter((f) => severityAtOrAbove(f.severity, level));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The level in force and the layer that set it: the request's
|
|
75
|
+
* `severity:` directive wins, then the user's scope, the channel's, the org's
|
|
76
|
+
* `review.addressSeverity` — the default counts as the org's. Resolved once
|
|
77
|
+
* per run by the dispatcher (every review's verdict parser reads it) and once
|
|
78
|
+
* per ship request by the hand-off, which writes it on the instance beside
|
|
79
|
+
* `merge` and hands it to each review child as its `severity:` directive. */
|
|
80
|
+
export function resolveAddressSeverity(layers: {
|
|
81
|
+
org?: AddressSeverity;
|
|
82
|
+
channel?: AddressSeverity;
|
|
83
|
+
user?: AddressSeverity;
|
|
84
|
+
run?: AddressSeverity;
|
|
85
|
+
}): { level: AddressSeverity; source: AddressSeveritySource } {
|
|
86
|
+
if (layers.run !== undefined) return { level: layers.run, source: "run" };
|
|
87
|
+
if (layers.user !== undefined) return { level: layers.user, source: "user" };
|
|
88
|
+
if (layers.channel !== undefined) return { level: layers.channel, source: "channel" };
|
|
89
|
+
return { level: layers.org ?? DEFAULT_ADDRESS_SEVERITY, source: "org" };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The gate's one sentence — `finding(s) <id> (<severity>)[, …] at or above
|
|
93
|
+
* <level>, the severity to address` — as the downgraded summary carries it into
|
|
94
|
+
* the posted body and the tool's ack repeats it to the model. */
|
|
95
|
+
export function downgradeNote(d: { findings: readonly string[]; level: AddressSeverity }): string {
|
|
96
|
+
return `finding${d.findings.length === 1 ? "" : "s"} ${d.findings.join(", ")} at or above ${d.level}, the severity to address`;
|
|
97
|
+
}
|
|
98
|
+
|
|
35
99
|
export interface Finding {
|
|
36
100
|
/** Stable id the reviewer assigned in order ("F1", "F2", …) — dispositions
|
|
37
101
|
* reference findings by this id. */
|
|
@@ -63,50 +127,63 @@ export interface ReviewVerdict {
|
|
|
63
127
|
* reasons) — surfaced in the tool ack so the model can resubmit, never
|
|
64
128
|
* rendered into the posted body. */
|
|
65
129
|
droppedFindings?: string[];
|
|
130
|
+
/** Parse note: the approve was downgraded by the severity gate — the gated
|
|
131
|
+
* findings as `id (severity)` and the level in force. Surfaced in the tool
|
|
132
|
+
* ack so the model knows its verdict changed; the summary carries the same
|
|
133
|
+
* words into the posted body, so this never rides a record. */
|
|
134
|
+
downgraded?: { findings: string[]; level: AddressSeverity };
|
|
66
135
|
}
|
|
67
136
|
|
|
68
137
|
export const LGTM_TOKEN = "LGTM:";
|
|
69
138
|
export const CHANGES_TOKEN = "Changes requested:";
|
|
70
139
|
export const NO_VERDICT_LINE = "No verdict submitted — not approving.";
|
|
71
140
|
|
|
72
|
-
/** Parse an arbitrary tool input into a verdict, or null when it is not one.
|
|
73
|
-
|
|
141
|
+
/** Parse an arbitrary tool input into a verdict, or null when it is not one.
|
|
142
|
+
* `addressSeverity` is the level in force for the run (default `minor`): an
|
|
143
|
+
* approve carrying a finding at or above it is parsed as `request_changes`. */
|
|
144
|
+
export function parseVerdictInput(
|
|
145
|
+
input: Record<string, unknown>,
|
|
146
|
+
opts: { addressSeverity?: AddressSeverity } = {},
|
|
147
|
+
): ReviewVerdict | null {
|
|
74
148
|
const verdict = input.verdict;
|
|
75
149
|
if (verdict !== "approve" && verdict !== "request_changes") return null;
|
|
150
|
+
const level = opts.addressSeverity ?? DEFAULT_ADDRESS_SEVERITY;
|
|
76
151
|
const summary = typeof input.summary === "string" ? oneLine(input.summary) : "";
|
|
77
152
|
const head = normalizeHead(input.head);
|
|
78
153
|
const out: ReviewVerdict = { verdict, summary };
|
|
79
154
|
if (head) out.head = head;
|
|
80
155
|
if (input.findings !== undefined) {
|
|
81
|
-
const parsed = parseFindings(input.findings);
|
|
156
|
+
const parsed = parseFindings(input.findings, level);
|
|
82
157
|
if (parsed.findings) out.findings = parsed.findings;
|
|
83
158
|
if (parsed.dropped.length) out.droppedFindings = parsed.dropped;
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
// `LGTM:` over a
|
|
87
|
-
// declared severity, not the validated survivors — a
|
|
88
|
-
// itself failed validation and dropped still poisons the approve.
|
|
89
|
-
if (out.verdict === "approve" && parsed.
|
|
159
|
+
// The severity gate, fail-closed: an approve carrying a finding at or
|
|
160
|
+
// above the level in force downgrades, so the posted body can never start
|
|
161
|
+
// with `LGTM:` over a finding the loop has to address. Keyed on the RAW
|
|
162
|
+
// entries' declared severity, not the validated survivors — a gated entry
|
|
163
|
+
// that itself failed validation and dropped still poisons the approve.
|
|
164
|
+
if (out.verdict === "approve" && parsed.gated.length > 0) {
|
|
90
165
|
out.verdict = "request_changes";
|
|
91
|
-
out.
|
|
92
|
-
|
|
93
|
-
);
|
|
166
|
+
out.downgraded = { findings: parsed.gated, level };
|
|
167
|
+
out.summary = oneLine(`${out.summary} [downgraded from approve: ${downgradeNote(out.downgraded)}]`);
|
|
94
168
|
}
|
|
95
169
|
}
|
|
96
170
|
return out;
|
|
97
171
|
}
|
|
98
172
|
|
|
99
173
|
/** Fail-closed per finding: each malformed entry drops with a note naming its
|
|
100
|
-
* index (and id when readable); a non-array drops the whole field.
|
|
101
|
-
*
|
|
102
|
-
* valid or not — for the approve downgrade above. */
|
|
103
|
-
function parseFindings(
|
|
174
|
+
* index (and id when readable); a non-array drops the whole field. `gated`
|
|
175
|
+
* labels every raw entry whose declared severity sits at or above `level` —
|
|
176
|
+
* valid or not — as `id (severity)`, for the approve downgrade above. */
|
|
177
|
+
function parseFindings(
|
|
178
|
+
value: unknown,
|
|
179
|
+
level: AddressSeverity,
|
|
180
|
+
): { findings?: Finding[]; dropped: string[]; gated: string[] } {
|
|
104
181
|
if (!Array.isArray(value)) {
|
|
105
|
-
return { dropped: ["findings: dropped — not an array"],
|
|
182
|
+
return { dropped: ["findings: dropped — not an array"], gated: [] };
|
|
106
183
|
}
|
|
107
184
|
const findings: Finding[] = [];
|
|
108
185
|
const dropped: string[] = [];
|
|
109
|
-
const
|
|
186
|
+
const gated: string[] = [];
|
|
110
187
|
value.forEach((raw, i) => {
|
|
111
188
|
const parsed = parseFinding(raw);
|
|
112
189
|
if (parsed.finding) {
|
|
@@ -116,9 +193,9 @@ function parseFindings(value: unknown): { findings?: Finding[]; dropped: string[
|
|
|
116
193
|
}
|
|
117
194
|
const label = parsed.finding?.id ?? parsed.id ?? `findings[${i}]`;
|
|
118
195
|
const r = typeof raw === "object" && raw !== null ? (raw as Record<string, unknown>) : undefined;
|
|
119
|
-
if (r?.severity
|
|
196
|
+
if (severityAtOrAbove(r?.severity, level)) gated.push(`${label} (${r?.severity})`);
|
|
120
197
|
});
|
|
121
|
-
return { findings, dropped,
|
|
198
|
+
return { findings, dropped, gated };
|
|
122
199
|
}
|
|
123
200
|
|
|
124
201
|
function parseFinding(
|
|
@@ -158,6 +158,13 @@ export type RunNoteKind =
|
|
|
158
158
|
* after the model's last turn — the release itself runs after the record
|
|
159
159
|
* is sealed — and set on the card's label too, so the loss is never silent. */
|
|
160
160
|
| "work_left_behind"
|
|
161
|
+
/** The run's ending may have left a command running in its workspace — a
|
|
162
|
+
* call cut by the ending's abort or interrupt, or open when the run failed —
|
|
163
|
+
* so the release tears the workspace down rather than pair it for the
|
|
164
|
+
* thread's next run (harness.md item 13). The summary names the calls.
|
|
165
|
+
* Written by the run loop once the harness session has ended and before the
|
|
166
|
+
* record is sealed; the release itself runs after. */
|
|
167
|
+
| "workspace_torn_down"
|
|
161
168
|
/** A coding run submitted a PR description but the post-step opened no
|
|
162
169
|
* pull request because the branch it observed IS the base the pull
|
|
163
170
|
* request would target (docs/reference/specs/pr-description.md item 5) —
|
|
@@ -196,11 +203,34 @@ export type RunNoteKind =
|
|
|
196
203
|
* item 7): the summary names the tool and the rule; the model read the same
|
|
197
204
|
* reason as the tool's result. Published by the bot's authorize route. */
|
|
198
205
|
| "tool_refused"
|
|
206
|
+
/** An OpenCode tool settled under a step this loop never saw start and the
|
|
207
|
+
* settle was set aside (harness.md item 13): an earlier execution's late
|
|
208
|
+
* result — the pinned binary's ordinary shape after a hung call's interrupt
|
|
209
|
+
* — or a step lost with the tailer's stream. The summary names the call and
|
|
210
|
+
* the step. Information, not a failure of the harness: nothing ran that the
|
|
211
|
+
* gate did not decide. Published by the OpenCode bridge in its own mode. */
|
|
212
|
+
| "settle_set_aside"
|
|
213
|
+
/** OpenCode's store refill carried a permission over something other than a
|
|
214
|
+
* tool (a directory outside the project, a repeating session) for a call
|
|
215
|
+
* the record never saw, and the store held no part yet to name the call's
|
|
216
|
+
* tool by: the call was opened under the permission's own name. The summary
|
|
217
|
+
* names the call and the step. Information, not a failure: the tool ran
|
|
218
|
+
* under the gate's decision either way. Published by the OpenCode bridge. */
|
|
219
|
+
| "tool_unnamed"
|
|
220
|
+
/** An `external_directory` ask answered once its call's line was already on
|
|
221
|
+
* the record (harness.md item 13): the line cannot be amended, so the
|
|
222
|
+
* directory the call reached is said here, naming the call. Published by
|
|
223
|
+
* the OpenCode bridge. */
|
|
224
|
+
| "directory_reached"
|
|
199
225
|
/** A ship coding child's budget ended with work still in the tree: the run
|
|
200
226
|
* loop committed and pushed it to the unit's branch (or says plainly that
|
|
201
227
|
* there was nothing to push), so a re-issue starts from the partial work
|
|
202
228
|
* (docs/reference/specs/agent-ship.md item 8). */
|
|
203
229
|
| "budget_salvage"
|
|
230
|
+
/** The loop's end found a tool call in flight and ended it, so the write-up
|
|
231
|
+
* keeps its allowance (decision 0046, unit seven; harness-pi item 6). The
|
|
232
|
+
* summary names the tools. Published by the harness beside the budget note. */
|
|
233
|
+
| "tool_cut"
|
|
204
234
|
/** The native loop's stuck-loop guard: the same tool call failed identically
|
|
205
235
|
* six times in a row and the run was forced into its write-up. Written by
|
|
206
236
|
* no loop since that loop's deletion; a record from before it may carry it. */
|
|
@@ -211,6 +241,7 @@ export type RunNoteKind =
|
|
|
211
241
|
export const RUN_NOTE_KINDS = [
|
|
212
242
|
"wrap_up",
|
|
213
243
|
"time_budget_exhausted",
|
|
244
|
+
"tool_cut",
|
|
214
245
|
"turn_budget_exhausted",
|
|
215
246
|
"sandbox_dead",
|
|
216
247
|
"fleet_busy",
|
|
@@ -229,12 +260,16 @@ export const RUN_NOTE_KINDS = [
|
|
|
229
260
|
"cold_sandbox",
|
|
230
261
|
"rebind_refused",
|
|
231
262
|
"work_left_behind",
|
|
263
|
+
"workspace_torn_down",
|
|
232
264
|
"pr_not_opened",
|
|
233
265
|
"review_not_posted",
|
|
234
266
|
"compacted",
|
|
235
267
|
"harness_error",
|
|
236
268
|
"policy_refusal",
|
|
237
269
|
"tool_refused",
|
|
270
|
+
"settle_set_aside",
|
|
271
|
+
"tool_unnamed",
|
|
272
|
+
"directory_reached",
|
|
238
273
|
"budget_salvage",
|
|
239
274
|
"stuck_loop",
|
|
240
275
|
] as const satisfies readonly RunNoteKind[];
|
|
@@ -293,7 +328,17 @@ export function sanitizeActor(actor: RunActor): RunActor {
|
|
|
293
328
|
* settles a round: caps end the pipeline BETWEEN rounds, visible as the
|
|
294
329
|
* absence of a next `started` boundary plus the answer's cap report. */
|
|
295
330
|
export type ShipRoundOutcome =
|
|
296
|
-
|
|
331
|
+
| "started"
|
|
332
|
+
| "pr_opened"
|
|
333
|
+
| "completed"
|
|
334
|
+
| "approve"
|
|
335
|
+
| "request_changes"
|
|
336
|
+
| "no_verdict"
|
|
337
|
+
| "aborted"
|
|
338
|
+
| "stopped"
|
|
339
|
+
/** The coding round ended at its lease with the unit unfinished and the row
|
|
340
|
+
* showing progress, and the grant renewed: the next segment opens (decision 0046). */
|
|
341
|
+
| "continued";
|
|
297
342
|
|
|
298
343
|
/**
|
|
299
344
|
* One event in a run's stream. `seq` is stamped by `RunRegistry.publish` — a
|
|
@@ -393,10 +438,14 @@ export type RouteInputValue = RouteInputLeafOrList | RouteInputObject3;
|
|
|
393
438
|
|
|
394
439
|
/** 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
440
|
* `hand_back` — the router bound a state-changing command and the door answered
|
|
396
|
-
* the line to paste, nothing invoked; `
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
|
|
441
|
+
* the line to paste, nothing invoked; `offered` — the same decision on a
|
|
442
|
+
* channel that can show a confirmation: the line, its risk and a button, a
|
|
443
|
+
* row stored in the config object, nothing invoked; `confirmed` — the stored
|
|
444
|
+
* input ran at the click, as the requester, through the typed line's path;
|
|
445
|
+
* `pasted` — the typed line that followed a hand-back in its thread, with the
|
|
446
|
+
* same receipt, ran. A routed read carries no outcome: it is not a decision
|
|
447
|
+
* about a state change. */
|
|
448
|
+
export type RouteOutcome = "hand_back" | "offered" | "confirmed" | "pasted";
|
|
400
449
|
|
|
401
450
|
export type RunEvent =
|
|
402
451
|
/** `callId` is the provider's tool_use id — the explicit pair key between a
|
|
@@ -430,7 +479,12 @@ export type RunEvent =
|
|
|
430
479
|
* executors' shared `exit N:` prefix, 0 for a clean run; absent when the code
|
|
431
480
|
* was not numeric). `output` is the tool's text — control-stripped, redacted,
|
|
432
481
|
* capped at TOOL_OUTPUT_CAP — for the run page's expandable card; the status
|
|
433
|
-
* card and the friction analyzer keep reading `summary`.
|
|
482
|
+
* card and the friction analyzer keep reading `summary`. `cut` marks the
|
|
483
|
+
* result of a call the run's ending cut rather than settled — pi's abort (the
|
|
484
|
+
* tool's own end after it), OpenCode's interrupt (a call still open when its
|
|
485
|
+
* loop left), a session's end (`closeOpenSpans`): the command behind it may
|
|
486
|
+
* still be running, and the workspace's release reads it so (`callsInFlight`,
|
|
487
|
+
* harness.md item 13). */
|
|
434
488
|
| {
|
|
435
489
|
type: "tool_result";
|
|
436
490
|
tool: string;
|
|
@@ -440,6 +494,7 @@ export type RunEvent =
|
|
|
440
494
|
exitCode?: number;
|
|
441
495
|
output?: string;
|
|
442
496
|
infra?: true;
|
|
497
|
+
cut?: true;
|
|
443
498
|
spanId?: string;
|
|
444
499
|
logIndex?: number;
|
|
445
500
|
seq?: number;
|
|
@@ -561,6 +616,10 @@ export type RunEvent =
|
|
|
561
616
|
* the instance's units. A child's instance rides the record's
|
|
562
617
|
* `parentInstanceId`, never here. */
|
|
563
618
|
instanceId?: string;
|
|
619
|
+
/** The grant the ship request carried (decision 0046, the renewable
|
|
620
|
+
* lease): renewals and a cost cap — what a renewal could spend. Written
|
|
621
|
+
* on the pipeline's own record beside `instanceId`; absent elsewhere. */
|
|
622
|
+
grant?: { renewals: number; costCapUsd?: number };
|
|
564
623
|
seq?: number;
|
|
565
624
|
at?: number;
|
|
566
625
|
}
|
|
@@ -691,6 +750,12 @@ export type RunEvent =
|
|
|
691
750
|
* start of the loop; a resumed run carries the original. Head material,
|
|
692
751
|
* like `run_meta`. Additive: unknown → ignored. */
|
|
693
752
|
| { type: "lease"; startedAt: number; endsAt: number; loopEndsAt: number; seq?: number; at?: number }
|
|
753
|
+
/** A head the run pushed (docs/reference/specs/run-history.md item 2; decision
|
|
754
|
+
* 0046): the branch and the sha the coding post-step observed on the remote
|
|
755
|
+
* (`by: "push"`), or the budget-end salvage pushed (`by: "salvage"`),
|
|
756
|
+
* whether or not a pull request follows — the fact renewal reads. Published
|
|
757
|
+
* straight to the registry like `pr_opened`. Additive: unknown → ignored. */
|
|
758
|
+
| { type: "pushed_head"; ref: string; sha: string; by: "push" | "salvage"; seq?: number; at?: number }
|
|
694
759
|
/** The coordinator tag as a fact of the run (docs/reference/specs/run-history.md
|
|
695
760
|
* item 48a): the instance the run is a child of, the unit its idempotency
|
|
696
761
|
* key named, and the base branch its pull request targets — published by
|
|
@@ -755,8 +820,11 @@ export type RunEvent =
|
|
|
755
820
|
* how the invoke ended is the run's own status and `answer`. A door
|
|
756
821
|
* decision about a state change (record 0044) is a command run too, with
|
|
757
822
|
* `outcome` saying which: a hand-back invoked nothing and its `answer` is
|
|
758
|
-
* the line to paste;
|
|
759
|
-
*
|
|
823
|
+
* the line to paste; an offer invoked nothing and its `answer` is the offer
|
|
824
|
+
* as the channel shows it; a confirmation is the stored input run at the
|
|
825
|
+
* click, its `answer` the command's own text as a routed read's is; a paste is
|
|
826
|
+
* the typed line that followed a hand-back, whatever its command,
|
|
827
|
+
* `handBackRunId` naming the hand-back's record. */
|
|
760
828
|
| {
|
|
761
829
|
type: "route";
|
|
762
830
|
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 {
|
|
@@ -272,6 +291,40 @@ export function pushedBranchesOf(events: readonly RunEvent[]): PushedBranch[] {
|
|
|
272
291
|
return [...byRef].map(([ref, pr]) => ({ ref, pr }));
|
|
273
292
|
}
|
|
274
293
|
|
|
294
|
+
/** The tool calls a run's ending may have left running in its workspace, in
|
|
295
|
+
* the record's order — what the workspace's release reads to tear the
|
|
296
|
+
* workspace down rather than wait for an idle the command would refuse, and
|
|
297
|
+
* what its note names (harness.md item 13). A call whose result is marked
|
|
298
|
+
* `cut` — pi's abort, OpenCode's interrupt, a session's end — was ended, not
|
|
299
|
+
* settled, whatever the run's status, and stays cut whatever lands for it
|
|
300
|
+
* later — an unmarked result (a post-turn's bridge reads the earlier
|
|
301
|
+
* execution's aborted settle as its own), or the call announced again under
|
|
302
|
+
* the same id; a call with no result at
|
|
303
|
+
* all was left open by a run that failed, was interrupted or hard-stopped,
|
|
304
|
+
* while on a run that completed or stopped softly it is a relayed tool that
|
|
305
|
+
* ran in the bot or a result lost to a transport gap, nothing running. Calls
|
|
306
|
+
* and results pair by id; a result for a call the record never opened counts
|
|
307
|
+
* for nothing. */
|
|
308
|
+
export function callsInFlight(
|
|
309
|
+
events: readonly RunEvent[],
|
|
310
|
+
status: RunStatus,
|
|
311
|
+
): Extract<RunEvent, { type: "tool_call" }>[] {
|
|
312
|
+
const calls = new Map<
|
|
313
|
+
string,
|
|
314
|
+
{ call: Extract<RunEvent, { type: "tool_call" }>; state: "open" | "cut" | "settled" }
|
|
315
|
+
>();
|
|
316
|
+
for (const e of events) {
|
|
317
|
+
if (e.type === "tool_call" && e.callId !== undefined) {
|
|
318
|
+
if (calls.get(e.callId)?.state !== "cut") calls.set(e.callId, { call: e, state: "open" });
|
|
319
|
+
} else if (e.type === "tool_result" && e.callId !== undefined) {
|
|
320
|
+
const seen = calls.get(e.callId);
|
|
321
|
+
if (seen !== undefined && seen.state !== "cut") seen.state = e.cut === true ? "cut" : "settled";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const orderly = status === "completed" || status === "stopped_soft";
|
|
325
|
+
return [...calls.values()].filter((c) => c.state === "cut" || (c.state === "open" && !orderly)).map((c) => c.call);
|
|
326
|
+
}
|
|
327
|
+
|
|
275
328
|
/** The router's decision as a record carries it — the same fields the
|
|
276
329
|
* `route` event and the ledger row's `meta.route` carry (routing-and-config
|
|
277
330
|
* item 21). */
|
|
@@ -798,6 +851,18 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
798
851
|
if (r.profile !== undefined && !isRunProfileRecord(r.profile)) return false;
|
|
799
852
|
// The pull request the run reached (item 2): a positive integer and a URL, or absent.
|
|
800
853
|
if (r.pr !== undefined && !isRunPullRequestShape(r.pr)) return false;
|
|
854
|
+
if (
|
|
855
|
+
r.pushed !== undefined &&
|
|
856
|
+
!(
|
|
857
|
+
Array.isArray(r.pushed) &&
|
|
858
|
+
r.pushed.every((h) => {
|
|
859
|
+
if (typeof h !== "object" || h === null) return false;
|
|
860
|
+
const { ref, sha } = h as Record<string, unknown>;
|
|
861
|
+
return typeof ref === "string" && ref.length > 0 && typeof sha === "string" && /^[0-9a-f]{7,40}$/.test(sha);
|
|
862
|
+
})
|
|
863
|
+
)
|
|
864
|
+
)
|
|
865
|
+
return false;
|
|
801
866
|
// A parent is named by a run id (item 46): the same shape as the record's own.
|
|
802
867
|
if (r.parentRunId !== undefined && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
|
|
803
868
|
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
|
|