@coreplane/switchboard 1.244.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-resident/threadErr.ts +337 -0
- package/dist/assets/deploy/cloudflare-resident/tsconfig.json +1 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +31 -262
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +5 -10
- 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 +4 -4
- package/dist/assets/src/config/profile.ts +17 -1
- package/dist/assets/src/core/authz/types.ts +8 -0
- package/dist/assets/src/core/budgets.ts +25 -0
- package/dist/assets/src/core/reviewVerdict.ts +101 -24
- package/dist/assets/src/core/runEvents.ts +26 -1
- package/dist/assets/src/core/runRecord.ts +34 -0
- package/dist/assets/src/core/ship/coordinator.ts +25 -21
- package/dist/assets/src/execution/residentRefresh.ts +25 -6
- package/dist/assets/web/dist/.vite/manifest.json +481 -462
- 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-BPMQxTcl.js → RunFoldRow-CHy_IfZG.js} +1 -1
- 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-Dail07rr.js → angular-html-qXhb9ti8.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-BReExio8.js → angular-ts-DzvdyoUZ.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-DhNO6Yyx.js → apl-Ck7Ru2Sm.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-BYotTY4C.js → astro-CwehvReb.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-mUxTtD1X.js → blade-WSyVbSFK.js} +1 -1
- package/dist/assets/web/dist/assets/{c-DhuiwcSQ.js → c-DMHI1wDz.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-GD2vFtRi.js → chapel-BF8AYK8_.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-BhFDwkes.js → cobol-UAfImuWu.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-DWUgbE0r.js → coffee-D54hi96d.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-CO9XFjGW.js → cpp-BRWfLgBc.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-B9pVurpn.js → crystal-CSqC5deT.js} +1 -1
- package/dist/assets/web/dist/assets/{css-CFVVV6lI.js → css-D4ZuRFGk.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BtPs2AkQ.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-BDhIDQIb.js → edge-DDPtNbZc.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-DG3J8WoD.js → elixir-BTQkQfsJ.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-FiOdiaOE.js → elm-B_PkLzGZ.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-vxbtW503.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-DINz8M3H.js → git-rebase-DP8wmn7K.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-yann6r9i.js → glimmer-js-G12VK2gK.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-CXTbrZn_.js → glimmer-ts-D3XiSI8E.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-DNYC-hq0.js → glsl-DOKAhMj-.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-CwzhHzi9.js → graphql-CJB_fmON.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-BBXF1cwR.js → hack-BHqtRWap.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-oEqBcsTy.js → haml-DwotyarS.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-YE5thkT1.js → handlebars-B6fDaA3U.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DCT1GYtQ.js → html-derivative-C5bpQmmI.js} +1 -1
- package/dist/assets/web/dist/assets/{html-Dp4jRQ1j.js → html-s52taj36.js} +1 -1
- package/dist/assets/web/dist/assets/{http-BPcGbmzJ.js → http-99zC935r.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-CQnNHwyd.js → hurl-Bgk-4-0x.js} +1 -1
- package/dist/assets/web/dist/assets/{java-Dm8Ovj6W.js → java-CuV_c5I0.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-Btm9oJK9.js → javascript-CZlOvSMQ.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-VZYjWF_U.js → jinja-BuXeZ4Tu.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-Dcmjxa9x.js → jison-DIuP0hkG.js} +1 -1
- package/dist/assets/web/dist/assets/{json-DoWiOGZ8.js → json-CZ74vfoD.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-Dvmo55ma.js → jsx-BNjRs8Vj.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-d8AtfV26.js → julia-OCE-fFOg.js} +1 -1
- package/dist/assets/web/dist/assets/{just-Cc5EStA-.js → just-CtvD87WF.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-B5fKKIH7.js → latex-C3kf86OR.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-DUYI9JND.js → liquid-B1WI--Kp.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-BhMwKWpw.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-Cnf2cPAi.js → marko-BE2MC6Yt.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-FDDXoir9.js → mdc-BwO4weXJ.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-q-8U_1jR.js → nginx-BTuOgGVC.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-DIA0TUWs.js → nim-CiIUM-oV.js} +1 -1
- package/dist/assets/web/dist/assets/{org-CR88hy7S.js → org-L78DEWB-.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-Bkd8N7ja.js → perl-KqHOm4Ft.js} +1 -1
- package/dist/assets/web/dist/assets/{php-BOs84mvy.js → php-C41XbEkn.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-BbpZBb3w.js → pug-B12-4i-k.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-D2jQpjFE.js → qml--fw2Up5c.js} +1 -1
- package/dist/assets/web/dist/assets/{r-DKzcAQ22.js → r-Db1JBXrC.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-SOQMWcC9.js → razor-BYpVBgbu.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-B6KK4W8d.js → regexp-BMqGLSPc.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-kPSg-NPf.js → rst-B63n7-Sa.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-cKS-JYLH.js → ruby-D3VI8UYv.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-DNxzE5gs.js → sas-DxRf8ZAR.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-DGcHQ5OO.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-CRZeZBa5.js → shellscript-CoOBe3_y.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-DSNEL0HX.js → shellsession-DOV1HYxf.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-zisNbkDO.js → soy-Acpmpp1W.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-MG06KMMb.js → sql-DeT9N-fA.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-DaRtc7nI.js → sseReplay-DPXVxhL5.js} +3 -3
- package/dist/assets/web/dist/assets/{stata-CB_wKwv9.js → stata-CbxXAMO4.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-DYdSt7re.js → surrealql-BdKbi4hc.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-DHO-F7c7.js → svelte-tAcjDM7R.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-CUL5L4Lr.js → templ-CObINNJj.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-_JNEKjVW.js → tex-BYwpZT1J.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags--9EpEV_8.js → ts-tags-BciREL5R.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-BcNq2x2u.js → tsx-CRiv3Pr3.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-EsY5Dk_D.js → twig-DuZZ1Ej0.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-CyA_HiUH.js → typescript-jkHz_5HZ.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-SzaHlDt4.js → typst-DOac4Gal.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-D3cn5acV.js → vue-CgOX5wXA.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-6MxhUxOa.js → vue-html-Duu99VNw.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-qJnqhgo9.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-DyRpC7xv.js → xml-BtUIVeWH.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-BXMRPAsg.js → xsl-wX71n1E5.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BNmPCtAN.js → yaml-CdLdXvmz.js} +1 -1
- package/dist/cli.js +1474 -684
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/AppShell-Ck4hSZDi.js +0 -1
- package/dist/assets/web/dist/assets/CostsPage-DvhjDMka.js +0 -2
- package/dist/assets/web/dist/assets/DeliveryPage-DBlI_Yq2.js +0 -1
- package/dist/assets/web/dist/assets/HomePage-XFWPxDiJ.js +0 -2
- package/dist/assets/web/dist/assets/NotFoundPage-CiGrVOyv.js +0 -1
- package/dist/assets/web/dist/assets/PendingTurnRow-BHCSpsPf.js +0 -1
- package/dist/assets/web/dist/assets/ResidentDetailPage-C5USV_8P.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CSKisWnX.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-CZe9Yodw.js +0 -9
- package/dist/assets/web/dist/assets/RunsIndexPage-BfA333Qy.js +0 -1
- package/dist/assets/web/dist/assets/RunsTabs-BWRHzhtT.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DBaChcJZ.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-CqVi9-Ac.js +0 -1
- package/dist/assets/web/dist/assets/StatusDot-_Q_bmReh.js +0 -1
- package/dist/assets/web/dist/assets/Tooltip-Cafsgd4m.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-CdoBZwtf.js +0 -1
- package/dist/assets/web/dist/assets/main-C4366PZF.css +0 -1
- package/dist/assets/web/dist/assets/main-DlTNAC6c.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) —
|
|
@@ -210,11 +217,20 @@ export type RunNoteKind =
|
|
|
210
217
|
* names the call and the step. Information, not a failure: the tool ran
|
|
211
218
|
* under the gate's decision either way. Published by the OpenCode bridge. */
|
|
212
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"
|
|
213
225
|
/** A ship coding child's budget ended with work still in the tree: the run
|
|
214
226
|
* loop committed and pushed it to the unit's branch (or says plainly that
|
|
215
227
|
* there was nothing to push), so a re-issue starts from the partial work
|
|
216
228
|
* (docs/reference/specs/agent-ship.md item 8). */
|
|
217
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"
|
|
218
234
|
/** The native loop's stuck-loop guard: the same tool call failed identically
|
|
219
235
|
* six times in a row and the run was forced into its write-up. Written by
|
|
220
236
|
* no loop since that loop's deletion; a record from before it may carry it. */
|
|
@@ -225,6 +241,7 @@ export type RunNoteKind =
|
|
|
225
241
|
export const RUN_NOTE_KINDS = [
|
|
226
242
|
"wrap_up",
|
|
227
243
|
"time_budget_exhausted",
|
|
244
|
+
"tool_cut",
|
|
228
245
|
"turn_budget_exhausted",
|
|
229
246
|
"sandbox_dead",
|
|
230
247
|
"fleet_busy",
|
|
@@ -243,6 +260,7 @@ export const RUN_NOTE_KINDS = [
|
|
|
243
260
|
"cold_sandbox",
|
|
244
261
|
"rebind_refused",
|
|
245
262
|
"work_left_behind",
|
|
263
|
+
"workspace_torn_down",
|
|
246
264
|
"pr_not_opened",
|
|
247
265
|
"review_not_posted",
|
|
248
266
|
"compacted",
|
|
@@ -251,6 +269,7 @@ export const RUN_NOTE_KINDS = [
|
|
|
251
269
|
"tool_refused",
|
|
252
270
|
"settle_set_aside",
|
|
253
271
|
"tool_unnamed",
|
|
272
|
+
"directory_reached",
|
|
254
273
|
"budget_salvage",
|
|
255
274
|
"stuck_loop",
|
|
256
275
|
] as const satisfies readonly RunNoteKind[];
|
|
@@ -460,7 +479,12 @@ export type RunEvent =
|
|
|
460
479
|
* executors' shared `exit N:` prefix, 0 for a clean run; absent when the code
|
|
461
480
|
* was not numeric). `output` is the tool's text — control-stripped, redacted,
|
|
462
481
|
* capped at TOOL_OUTPUT_CAP — for the run page's expandable card; the status
|
|
463
|
-
* 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). */
|
|
464
488
|
| {
|
|
465
489
|
type: "tool_result";
|
|
466
490
|
tool: string;
|
|
@@ -470,6 +494,7 @@ export type RunEvent =
|
|
|
470
494
|
exitCode?: number;
|
|
471
495
|
output?: string;
|
|
472
496
|
infra?: true;
|
|
497
|
+
cut?: true;
|
|
473
498
|
spanId?: string;
|
|
474
499
|
logIndex?: number;
|
|
475
500
|
seq?: number;
|
|
@@ -291,6 +291,40 @@ export function pushedBranchesOf(events: readonly RunEvent[]): PushedBranch[] {
|
|
|
291
291
|
return [...byRef].map(([ref, pr]) => ({ ref, pr }));
|
|
292
292
|
}
|
|
293
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
|
+
|
|
294
328
|
/** The router's decision as a record carries it — the same fields the
|
|
295
329
|
* `route` event and the ledger row's `meta.route` carry (routing-and-config
|
|
296
330
|
* item 21). */
|
|
@@ -34,7 +34,18 @@ import type { Handoff } from "./handoff.js";
|
|
|
34
34
|
import { progressOf, renderRenewal, renewalDecision, type PushedHeadFact, type RenewalDecision } from "./renewal.js";
|
|
35
35
|
import type { RunStatus } from "../runRecord.js";
|
|
36
36
|
import { normalizeHead, sameCommit } from "../reviewedHead.js";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
ADDRESS_SEVERITIES,
|
|
39
|
+
DEFAULT_ADDRESS_SEVERITY,
|
|
40
|
+
findingsAtOrAbove,
|
|
41
|
+
formatFinding,
|
|
42
|
+
isAddressSeverity,
|
|
43
|
+
type AddressSeverity,
|
|
44
|
+
type AddressSeveritySource,
|
|
45
|
+
type Finding,
|
|
46
|
+
type FindingDisposition,
|
|
47
|
+
type ReviewVerdictKind,
|
|
48
|
+
} from "../reviewVerdict.js";
|
|
38
49
|
import { parsePlanUnit, planUnitIds } from "./contract.js";
|
|
39
50
|
import {
|
|
40
51
|
carve,
|
|
@@ -598,26 +609,19 @@ export type CoordinatorNote =
|
|
|
598
609
|
* person can see whether the cap or the child is the problem. */
|
|
599
610
|
export type ShipBudgetSpent = Readonly<Record<"coding" | "review" | "waiting", number>>;
|
|
600
611
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
export
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
/** The findings the gate acts on at `level`: at or above it. Every parsed
|
|
615
|
-
* finding carries one of the ladder's four levels — the verdict parser
|
|
616
|
-
* (`parseVerdictInput`) drops an entry with any other severity, `fyi` or none,
|
|
617
|
-
* with a note — so nothing outside the ladder ever reaches the gate. */
|
|
618
|
-
export function findingsAtOrAbove(findings: readonly Finding[], level: AddressSeverity): Finding[] {
|
|
619
|
-
return findings.filter((f) => severityRank(f.severity) <= severityRank(level));
|
|
620
|
-
}
|
|
612
|
+
// The severity to address (agent-ship item 9's gate) is the verdict parser's
|
|
613
|
+
// ladder (src/core/reviewVerdict.ts): the same level the parser holds a
|
|
614
|
+
// submitted approve to, so a posted approve can carry a gated finding only
|
|
615
|
+
// when it was parsed at a different level — the check below is defense in
|
|
616
|
+
// depth behind the parser's. Re-exported here for the runner's callers.
|
|
617
|
+
export {
|
|
618
|
+
ADDRESS_SEVERITIES,
|
|
619
|
+
DEFAULT_ADDRESS_SEVERITY,
|
|
620
|
+
findingsAtOrAbove,
|
|
621
|
+
isAddressSeverity,
|
|
622
|
+
type AddressSeverity,
|
|
623
|
+
type AddressSeveritySource,
|
|
624
|
+
};
|
|
621
625
|
|
|
622
626
|
export interface UnitSession {
|
|
623
627
|
segment: number;
|
|
@@ -241,14 +241,17 @@ export function fetchFailureIsMirrors(message: string): boolean {
|
|
|
241
241
|
export const RUNTIME_MOVED_WORDING =
|
|
242
242
|
/previous runtime incarnation|interrupted because the runtime changed|runtime identity is no longer active|sandbox lifetime is no longer current|platform was updating the sandbox runtime|no longer identifies pid/i;
|
|
243
243
|
/** The platform's words for a container that is down under a command (see
|
|
244
|
-
* `RUNTIME_MOVED_WORDING`): the spawn refusal of a stopped container
|
|
245
|
-
* binding's not-running refusal,
|
|
244
|
+
* `RUNTIME_MOVED_WORDING`): the spawn refusal of a stopped container, the
|
|
245
|
+
* binding's not-running refusal, and the SDK's "The container just exited"
|
|
246
|
+
* for a container that died under a live exec (a roll landing mid-step; the
|
|
247
|
+
* bot side reads the same words as a roll, residentWake's
|
|
248
|
+
* CONTAINER_GONE_WORDING), each anchored to the full phrase so a genuine
|
|
246
249
|
* crash ("container exited with unexpected exit code") and the readiness
|
|
247
250
|
* probe ("the container is not listening") never match. The harness's
|
|
248
251
|
* container seam reads the same two as a container down under its one more
|
|
249
252
|
* command — a wait, never a judgement (harness.md item 6). */
|
|
250
253
|
export const STOPPED_CONTAINER_WORDING =
|
|
251
|
-
/process supervisor is closed|container is not running, consider calling start/i;
|
|
254
|
+
/process supervisor is closed|container is not running, consider calling start|the container just exited/i;
|
|
252
255
|
export const RUNTIME_REPLACEMENT_WORDING = new RegExp(
|
|
253
256
|
`${RUNTIME_MOVED_WORDING.source}|${STOPPED_CONTAINER_WORDING.source}`,
|
|
254
257
|
"i",
|
|
@@ -358,12 +361,28 @@ export const SDK_RUNTIME_RECORD_KEY = "currentRuntimeIdentity";
|
|
|
358
361
|
/** The DOMException the SDK's connect abort raises — `fetchUpgradeAttempt`
|
|
359
362
|
* calls `controller.abort()` with no reason when `DEFAULT_CONNECT_TIMEOUT_MS`
|
|
360
363
|
* passes, so the runtime's own `AbortError` with the message exactly `The
|
|
361
|
-
* operation was aborted`. Anchored on the WHOLE message
|
|
362
|
-
* `Aborted (core dumped)` never matches and neither does a `TimeoutError`'s
|
|
364
|
+
* operation was aborted`. Anchored on the WHOLE message: a command's own
|
|
365
|
+
* `Aborted (core dumped)` never matches, and neither does a `TimeoutError`'s
|
|
363
366
|
* `The operation was aborted due to timeout` — the text `AbortSignal.timeout`
|
|
364
|
-
* raises, a route's own bound on a slow GitHub, not a silent runtime.
|
|
367
|
+
* raises, a route's own bound on a slow GitHub, not a silent runtime. A
|
|
368
|
+
* wrapper is read by its `cause`, where the DOMException rides whole; a
|
|
369
|
+
* wrapper that only pasted the sentence into a longer message is not the
|
|
370
|
+
* signal. */
|
|
365
371
|
export const RUNTIME_UNREACHABLE_WORDING = /^The operation was aborted\.?$/;
|
|
366
372
|
|
|
373
|
+
/** `err` and its `cause` chain, bounded like the SDK's own `selfAndCauses`
|
|
374
|
+
* walker: the SDK wraps platform errors, so the telling message can sit one or
|
|
375
|
+
* two links down. The one walker for every reader of a throw's chain — the
|
|
376
|
+
* resident Worker's classifiers and its thread-error builders, the sandbox
|
|
377
|
+
* Worker's — so the depth bound and the shape are decided once. */
|
|
378
|
+
export function* selfAndCauses(err: unknown): Generator<unknown> {
|
|
379
|
+
let current = err;
|
|
380
|
+
for (let depth = 0; depth < 8 && current != null; depth++) {
|
|
381
|
+
yield current;
|
|
382
|
+
current = typeof current === "object" ? (current as { cause?: unknown }).cause : undefined;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
367
386
|
/** Is this one link of an error's cause chain the SDK's connect abort? By
|
|
368
387
|
* name first (the DOMException's `AbortError`), by the DOMException's message
|
|
369
388
|
* when a wrapper copied only that. Applied by the Worker to the SPAWN-phase
|