@coreplane/switchboard 1.244.0 → 1.246.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 +40 -272
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +22 -11
- 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 +34 -10
- 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 +43 -0
- package/dist/assets/src/core/coordinator/contract.ts +29 -4
- package/dist/assets/src/core/coordinator/driver.ts +7 -1
- package/dist/assets/src/core/reviewVerdict.ts +239 -30
- package/dist/assets/src/core/runEvents.ts +55 -2
- package/dist/assets/src/core/runRecord.ts +56 -0
- package/dist/assets/src/core/ship/coordinator.ts +48 -23
- package/dist/assets/src/core/ship/renewal.ts +10 -3
- package/dist/assets/src/execution/bashTimeout.ts +46 -2
- package/dist/assets/src/execution/residentRefresh.ts +34 -11
- package/dist/assets/src/execution/sandboxErrors.ts +9 -4
- package/dist/assets/web/dist/.vite/manifest.json +481 -462
- package/dist/assets/web/dist/assets/AppShell-B4EUiSwP.js +1 -0
- package/dist/assets/web/dist/assets/CostsPage-D8VjAtuH.js +2 -0
- package/dist/assets/web/dist/assets/DeliveryPage-DpQi5XUI.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-CvKuIGqg.js +2 -0
- package/dist/assets/web/dist/assets/InputMenu-DmqAxkn4.js +1 -0
- package/dist/assets/web/dist/assets/NotFoundPage-DDogyiXA.js +1 -0
- package/dist/assets/web/dist/assets/PendingTurnRow-BoSLlR6j.js +1 -0
- package/dist/assets/web/dist/assets/ResidentDetailPage-CiBdbzkE.js +1 -0
- package/dist/assets/web/dist/assets/ResidentsIndexPage-DgtHtd9K.js +1 -0
- package/dist/assets/web/dist/assets/{RunFoldRow-BPMQxTcl.js → RunFoldRow-IF4xbz-3.js} +1 -1
- package/dist/assets/web/dist/assets/RunRoutePage-DHYa7rHK.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-aWhNTv8r.js +1 -0
- package/dist/assets/web/dist/assets/RunsTabs-CY_tdGTU.js +1 -0
- package/dist/assets/web/dist/assets/ScheduledPage-gPEBZjcx.js +1 -0
- package/dist/assets/web/dist/assets/SettingSelect-VDanrTZi.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-CFoltSRD.js +1 -0
- package/dist/assets/web/dist/assets/StatusDot-DA3eTVrT.js +1 -0
- package/dist/assets/web/dist/assets/Tooltip-BgbCkM12.js +1 -0
- package/dist/assets/web/dist/assets/UnitRoutePage-DibegaJJ.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-Dail07rr.js → angular-html-D_3slZqf.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-BReExio8.js → angular-ts-D1Fv2cpN.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-DhNO6Yyx.js → apl-60Nm_trN.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-BYotTY4C.js → astro-CvyaM3Q9.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-mUxTtD1X.js → blade-yan3VBls.js} +1 -1
- package/dist/assets/web/dist/assets/{c-DhuiwcSQ.js → c-Ddgepap2.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-GD2vFtRi.js → chapel-CgNY6dxC.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-BhFDwkes.js → cobol-C7q82cdP.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-DWUgbE0r.js → coffee-DgRMF_ni.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-CO9XFjGW.js → cpp-B8Lf-aeo.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-B9pVurpn.js → crystal-CkA_dhEO.js} +1 -1
- package/dist/assets/web/dist/assets/{css-CFVVV6lI.js → css-Cyu-C_oA.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BtPs2AkQ.js → dist-DS-gMjLZ.js} +2 -2
- package/dist/assets/web/dist/assets/{durationTone-BobbycC-.js → durationTone-l6aVrHb8.js} +1 -1
- package/dist/assets/web/dist/assets/{edge-BDhIDQIb.js → edge-BxV3rKNI.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-DG3J8WoD.js → elixir-pNSEK39d.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-FiOdiaOE.js → elm-zgwWE33W.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-vxbtW503.js → erb-yzcYAHp_.js} +1 -1
- package/dist/assets/web/dist/assets/{eventSource-tRprhqm7.js → eventSource-Ce94ho6M.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-DINz8M3H.js → git-rebase-Dg7Ous1-.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-yann6r9i.js → glimmer-js-CbZEwX27.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-CXTbrZn_.js → glimmer-ts-D3NIqALy.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-DNYC-hq0.js → glsl-y4-3WrI2.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-CwzhHzi9.js → graphql-D5d0al7K.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-BBXF1cwR.js → hack-D8fD6kEl.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-oEqBcsTy.js → haml-BWeFFc-V.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-YE5thkT1.js → handlebars-gIm5FeE_.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DCT1GYtQ.js → html-derivative-B3ySSBrQ.js} +1 -1
- package/dist/assets/web/dist/assets/{html-Dp4jRQ1j.js → html-fXJTVLSM.js} +1 -1
- package/dist/assets/web/dist/assets/{http-BPcGbmzJ.js → http-DrJfQUmw.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-CQnNHwyd.js → hurl-Bvbnsf55.js} +1 -1
- package/dist/assets/web/dist/assets/{java-Dm8Ovj6W.js → java-CUSpb-i-.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-Btm9oJK9.js → javascript-BWRH5fbY.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-VZYjWF_U.js → jinja-D3Ymci8y.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-Dcmjxa9x.js → jison-Gt9SO7ZC.js} +1 -1
- package/dist/assets/web/dist/assets/{json-DoWiOGZ8.js → json-Cfcblhgn.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-Dvmo55ma.js → jsx-C0366fFM.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-d8AtfV26.js → julia-CyBOTbxS.js} +1 -1
- package/dist/assets/web/dist/assets/{just-Cc5EStA-.js → just-FPXZcIOd.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-B5fKKIH7.js → latex-Bd-c88v0.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-DUYI9JND.js → liquid-5tu9Hc0k.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-BhMwKWpw.js → lua-BZ5fdPP8.js} +1 -1
- package/dist/assets/web/dist/assets/main-B4A6x82d.js +28 -0
- package/dist/assets/web/dist/assets/main-CpkwzHX6.css +1 -0
- package/dist/assets/web/dist/assets/{marko-Cnf2cPAi.js → marko-C_mmj4p0.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-FDDXoir9.js → mdc-CtaNqWjY.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-q-8U_1jR.js → nginx-Ddm5Mb8J.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-DIA0TUWs.js → nim-COhfroYB.js} +1 -1
- package/dist/assets/web/dist/assets/{org-CR88hy7S.js → org-CCHFgmTb.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-Bkd8N7ja.js → perl-CXzOE-Z-.js} +1 -1
- package/dist/assets/web/dist/assets/{php-BOs84mvy.js → php-1V6jAIhU.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-BbpZBb3w.js → pug-uS37efts.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-D2jQpjFE.js → qml-Bj1A0Ben.js} +1 -1
- package/dist/assets/web/dist/assets/{r-DKzcAQ22.js → r-CMbKbLsS.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-SOQMWcC9.js → razor-DVRq_Ndk.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-B6KK4W8d.js → regexp-AbaOr4oB.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-kPSg-NPf.js → rst-Dq_QWgOF.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-cKS-JYLH.js → ruby-CVSsAj7T.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-DNxzE5gs.js → sas-C-dGfxUi.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-DGcHQ5OO.js → scss-DxALxYPA.js} +1 -1
- package/dist/assets/web/dist/assets/seed-BhxEVasY.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--qXcvQ5L.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-DSNEL0HX.js → shellsession-DeqnW8Ux.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-zisNbkDO.js → soy-DreoUIMt.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-MG06KMMb.js → sql-o3i2VTG6.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-DaRtc7nI.js → sseReplay-qF-gpbAY.js} +3 -3
- package/dist/assets/web/dist/assets/{stata-CB_wKwv9.js → stata-CGAT41H7.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-DYdSt7re.js → surrealql-CBU0QuGV.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-DHO-F7c7.js → svelte-CfHaLZiG.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-CUL5L4Lr.js → templ-D02MUcma.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-_JNEKjVW.js → tex-DjprHYXl.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags--9EpEV_8.js → ts-tags-DftDOzP-.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-BcNq2x2u.js → tsx-BOdvPohJ.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-EsY5Dk_D.js → twig-B_yLS56B.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-CyA_HiUH.js → typescript-CdPCa4nU.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-SzaHlDt4.js → typst-C1oIFsUl.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-D3cn5acV.js → vue-CcszJEX8.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-6MxhUxOa.js → vue-html-DcpxPcuT.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-qJnqhgo9.js → vue-vine-D26bIKGj.js} +1 -1
- package/dist/assets/web/dist/assets/wallClock-DPTuP2FD.js +1 -0
- package/dist/assets/web/dist/assets/{xml-DyRpC7xv.js → xml-D8zl6W5P.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-BXMRPAsg.js → xsl-D86Bii28.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BNmPCtAN.js → yaml-g6Px4KFU.js} +1 -1
- package/dist/cli.js +3023 -1049
- 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
|
@@ -91,7 +91,13 @@ defaults:
|
|
|
91
91
|
# written (one Opus-class call per review; meat caches by model + diff). `off`
|
|
92
92
|
# records no reading diff at all. `SWITCHBOARD_READING_DIFF=git|meat|off` in
|
|
93
93
|
# the environment overrides `provider` on a deployed bot. Needs `runHistory`.
|
|
94
|
+
# `addressSeverity` is the severity to address (docs/reference/specs/agent-review.md
|
|
95
|
+
# item 5a): a review's approve carrying a finding at or above it is posted as
|
|
96
|
+
# `Changes requested:`, never `LGTM:`, and agent:ship holds its rounds to the same
|
|
97
|
+
# level. Per channel/user: config set … --review.addressSeverity; per run:
|
|
98
|
+
# severity:<level>.
|
|
94
99
|
# review:
|
|
100
|
+
# addressSeverity: minor # blocking | major | minor (default) | nit
|
|
95
101
|
# readingDiff:
|
|
96
102
|
# provider: git # git (default) | meat | off
|
|
97
103
|
# meatModel: claude-opus-5 # meat's -model; Opus-class is the floor that actually abridges
|
|
@@ -342,9 +348,6 @@ workspaceDir: ./workspaces
|
|
|
342
348
|
# ship:
|
|
343
349
|
# maxRounds: 3 # review rounds per unit
|
|
344
350
|
# maxMinutes: 120 # the ship preset's wall-clock budget in minutes (the registry's default)
|
|
345
|
-
# addressSeverity: minor # the severity an approve's findings are held to (blocking|major|minor|nit);
|
|
346
|
-
# # a finding at or above it sends the round into a fix step instead of merge-ready.
|
|
347
|
-
# # Per channel/user: config set … --ship.addressSeverity; per run: severity:<level>.
|
|
348
351
|
|
|
349
352
|
# Which harness each preset's runs are driven by, deployment-wide
|
|
350
353
|
# (docs/reference/specs/harness.md item 8): a mapping of preset to a harness's
|
|
@@ -76,6 +76,7 @@ export interface Env {
|
|
|
76
76
|
SLACK_APP_TOKEN: string;
|
|
77
77
|
ANTHROPIC_API_KEY: string;
|
|
78
78
|
OPENAI_API_KEY?: string;
|
|
79
|
+
OPENROUTER_API_KEY?: string;
|
|
79
80
|
E2B_API_KEY?: string;
|
|
80
81
|
SANDBOX_TOKEN?: string; // cloudflare execution: bearer for the sandbox Worker
|
|
81
82
|
RESIDENT_OPERATOR_TOKEN?: string; // resident repos: operator bearer for the resident Worker
|
|
@@ -113,6 +114,7 @@ export interface Env {
|
|
|
113
114
|
* are forwarded only when set, so the bot sees "not configured" as absence. */
|
|
114
115
|
const FORWARDED_OPTIONAL = [
|
|
115
116
|
"OPENAI_API_KEY",
|
|
117
|
+
"OPENROUTER_API_KEY",
|
|
116
118
|
"E2B_API_KEY",
|
|
117
119
|
"SANDBOX_TOKEN",
|
|
118
120
|
"RESIDENT_OPERATOR_TOKEN",
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// The thread data plane's error shapes, and the builders that answer a throw
|
|
2
|
+
// no route named. Pure over `(err, route)`: the predicates only the Worker can
|
|
3
|
+
// supply — the pinned SDK's (its package imports `cloudflare:workers`, which
|
|
4
|
+
// plain Node cannot load) and the resident's own replacement classifier and
|
|
5
|
+
// vouch, which read the SDK's typed classes — are handed in
|
|
6
|
+
// (`threadErrBuilders`), so this module runs under plain Node
|
|
7
|
+
// (threadErr.test.ts, with the SDK's real sentences) where the entry runs only
|
|
8
|
+
// under workerd and its scans can read the wiring alone.
|
|
9
|
+
import {
|
|
10
|
+
isRuntimeUnreachableReason,
|
|
11
|
+
isRuntimeUnreachableSignal,
|
|
12
|
+
selfAndCauses,
|
|
13
|
+
} from "../../src/execution/residentRefresh.js";
|
|
14
|
+
import type { ResidentLifecycleState } from "../../src/execution/residentState.js";
|
|
15
|
+
import type { ResidentStep } from "../../src/execution/residentStepTrace.js";
|
|
16
|
+
|
|
17
|
+
// The cause-chain walker has one home, beside the wording lists; the Worker
|
|
18
|
+
// takes it from here with the rest of the thread data plane's shapes.
|
|
19
|
+
export { selfAndCauses };
|
|
20
|
+
|
|
21
|
+
/** The routes whose pending Durable Object call `threadRejectionErr` answers for. */
|
|
22
|
+
export type ThreadDataRoute = "/exec" | "/read" | "/write";
|
|
23
|
+
|
|
24
|
+
/** The message of whatever was thrown. */
|
|
25
|
+
export const messageOf = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
26
|
+
|
|
27
|
+
/** Named, RPC-cloneable error shape for the thread data plane. The Worker
|
|
28
|
+
* maps `status` to the HTTP status; extra fields (`needs`, `state`,
|
|
29
|
+
* `reason`) ride along into the body. */
|
|
30
|
+
export interface ThreadErr {
|
|
31
|
+
error: string;
|
|
32
|
+
status: number;
|
|
33
|
+
/** Beside `state` on a 503 that carries the lifecycle state: the lifecycle
|
|
34
|
+
* REASON (`getStatus().reason`), kept apart from `reason`, which is the
|
|
35
|
+
* answer's own word (`mirror-busy`, `disk-pressure`, `image-stale`). The
|
|
36
|
+
* client reads the pair to decide whether the resident is coming back
|
|
37
|
+
* (execution.md item 9); reading `reason` there would take a busy mirror on
|
|
38
|
+
* a degraded-but-serviceable resident for a repo failure. */
|
|
39
|
+
stateReason?: string;
|
|
40
|
+
/** On the 500 for a throw no route named (`catchAllErr`, at the fetch
|
|
41
|
+
* handler's catch-all, a streamed route's rejection, a route's own catch
|
|
42
|
+
* around its body, the thread data plane's last resort): whether the throw
|
|
43
|
+
* it wrapped was the platform's own transient (`isTransientPlatformThrow`)
|
|
44
|
+
* — a re-probe clears it — or a deterministic throw in the route, judged at
|
|
45
|
+
* once. */
|
|
46
|
+
transient?: boolean;
|
|
47
|
+
/** The steps the request ran before it failed (docs/reference/specs/tracing.md item 19):
|
|
48
|
+
* a failed attach's trace is the one that says which step blew the budget. */
|
|
49
|
+
trace?: ResidentStep[];
|
|
50
|
+
needs?: string;
|
|
51
|
+
/** With needs:"ref" — the resident's default branch, so the caller can bind by default. */
|
|
52
|
+
defaultRef?: string;
|
|
53
|
+
state?: ResidentLifecycleState;
|
|
54
|
+
reason?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Where a replacement or a reset was met: the command's spawn or its collect
|
|
58
|
+
* inside the Durable Object, or the Worker's own call into it (`call`: a
|
|
59
|
+
* rejected stub, before the method could answer — a command on `/exec`, a
|
|
60
|
+
* read or a write on the file routes, so the wording says "request" there). */
|
|
61
|
+
export type FailurePhase = "spawn" | "collect" | "call";
|
|
62
|
+
|
|
63
|
+
/** The clause naming the moment, and the subject whose outcome is spoken of. */
|
|
64
|
+
function met(phase: FailurePhase): { while: string; whose: string } {
|
|
65
|
+
if (phase === "spawn") return { while: "this command was starting", whose: "the command's" };
|
|
66
|
+
if (phase === "collect") return { while: "this command was running", whose: "the command's" };
|
|
67
|
+
return { while: "this request was pending at the Worker", whose: "the request's" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The resident runtime (the Sandbox SDK's control session to the container)
|
|
71
|
+
* was replaced while a command was in flight — in practice a `wrangler deploy`
|
|
72
|
+
* swapping this DO's isolate mid-run (which otherwise surfaces as a fake
|
|
73
|
+
* "OOM" on the run). `phase` says where the SDK failed: `"spawn"` (the start
|
|
74
|
+
* RPC itself; the SDK never proves the process did NOT start), `"collect"` (a
|
|
75
|
+
* `StaleProcessHandleError` on an already-running process), or `"call"` (the
|
|
76
|
+
* Worker's stub call rejected before the method answered). In every case the
|
|
77
|
+
* command may have run, so the resident never re-issues it; the thread routes
|
|
78
|
+
* answer the NAMED `runtime-replaced` error and the client decides (idempotent
|
|
79
|
+
* read/write retry; exec is handed to the model). */
|
|
80
|
+
export class RuntimeReplacedError extends Error {
|
|
81
|
+
constructor(
|
|
82
|
+
readonly phase: FailurePhase,
|
|
83
|
+
readonly cause: unknown,
|
|
84
|
+
/** Whether the replacement was KNOWN where the failure was classified
|
|
85
|
+
* (`replacementKnown`): the SDK vouched the runtime moved, or a restore is
|
|
86
|
+
* under way for this resident. One decision, made once at the exec choke
|
|
87
|
+
* point, and it gates the word on `/exec` alone (`replacedExecAnswer`) —
|
|
88
|
+
* the incarnation swap is unconditional there. Unknown, the failure only
|
|
89
|
+
* says the container is down or the transport was lost — a merely asleep
|
|
90
|
+
* or starting container, or a network blip, say the same — so the answer
|
|
91
|
+
* is the SDK's words and the harness's one more command decides. At the
|
|
92
|
+
* Worker's call the SDK's vouch is the one half the Worker can make
|
|
93
|
+
* (`sdkVouchesRuntimeMoved` judges text, which survives the stub
|
|
94
|
+
* boundary); the restore half is the DO's alone. */
|
|
95
|
+
readonly known: boolean,
|
|
96
|
+
) {
|
|
97
|
+
const m = met(phase);
|
|
98
|
+
super(
|
|
99
|
+
`runtime-replaced: the resident runtime was replaced (a deploy) while ${m.while}; ${
|
|
100
|
+
phase === "call" ? "its outcome is unknown" : "its output is lost"
|
|
101
|
+
} (${messageOf(cause)})`,
|
|
102
|
+
);
|
|
103
|
+
this.name = "RuntimeReplacedError";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The resident's own Durable Object was reset while a command was in flight —
|
|
108
|
+
* a `wrangler deploy` of the Worker code (not the container image) supersedes
|
|
109
|
+
* this DO's isolate, so the SDK's control session to the container is lost
|
|
110
|
+
* mid-command (`isDurableObjectCodeUpdateReset`). This is NOT a runtime
|
|
111
|
+
* replacement: the container and every process the run holds in it are exactly
|
|
112
|
+
* as they were — only the DO that was driving them reset. The command's
|
|
113
|
+
* outcome is unknown (the reset may have raced its start or its finish), so
|
|
114
|
+
* the resident never re-issues it here; the thread routes answer the NAMED
|
|
115
|
+
* `control-reset` error and the client re-sends an idempotent op or resolves a
|
|
116
|
+
* write by pi's echo (docs/reference/specs/harness-pi.md item 16). Distinct
|
|
117
|
+
* from `RuntimeReplacedError` precisely so the harness never mistakes a DO
|
|
118
|
+
* reset over a live pi for a replaced container and orphans that pi. */
|
|
119
|
+
export class ControlResetError extends Error {
|
|
120
|
+
constructor(
|
|
121
|
+
readonly phase: FailurePhase,
|
|
122
|
+
readonly cause: unknown,
|
|
123
|
+
) {
|
|
124
|
+
const m = met(phase);
|
|
125
|
+
super(
|
|
126
|
+
`control-reset: the resident's Durable Object was reset (a deploy) while ${m.while}; ` +
|
|
127
|
+
`the container and its processes are as they were; ${m.whose} outcome is unknown (${messageOf(cause)})`,
|
|
128
|
+
);
|
|
129
|
+
this.name = "ControlResetError";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The named ThreadErr every thread route (exec/read/write) answers for a
|
|
134
|
+
* runtime replacement, so the client can classify it (409 like the other
|
|
135
|
+
* recoverable thread states; `reason` is the discriminator). On `/exec` the
|
|
136
|
+
* answer goes through `replacedExecAnswer` first: the word only when the
|
|
137
|
+
* replacement was known where `run()` classified the failure. On `/read` and
|
|
138
|
+
* `/write` the word stays unconditional on purpose: those routes reach the
|
|
139
|
+
* same `run()` and can meet the same down wordings, but there the word drives
|
|
140
|
+
* the client's re-attach-and-retry (the attach is what wakes a container that
|
|
141
|
+
* is asleep or starting), never a verdict — nothing relaunches on it, and the
|
|
142
|
+
* harness never reads or writes through them (its seam runs every operation
|
|
143
|
+
* as an `/exec` script). Gating them would trade a spare re-attach for a read
|
|
144
|
+
* that fails outright while the container starts. */
|
|
145
|
+
export function runtimeReplacedErr(err: RuntimeReplacedError): ThreadErr {
|
|
146
|
+
return { error: err.message, status: 409, reason: "runtime-replaced" };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The named ThreadErr a DO code-update reset answers with — its own `reason`
|
|
150
|
+
* the client keys on, distinct from `runtime-replaced`: the container is
|
|
151
|
+
* unchanged and the command's outcome is unknown, so the client re-sends an
|
|
152
|
+
* idempotent op or resolves a write by echo (harness-pi item 16), never the
|
|
153
|
+
* replaced verdict. */
|
|
154
|
+
export function controlResetErr(err: ControlResetError): ThreadErr {
|
|
155
|
+
return { error: err.message, status: 409, reason: "control-reset" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The platform's transient sentences the pinned SDK's own predicate does not
|
|
159
|
+
* name. `isPlatformTransientError` covers the code-update reset, a lost
|
|
160
|
+
* connection, the storage-startup reset (`internal error while starting up
|
|
161
|
+
* durable object storage caused object to be reset`) and the typed
|
|
162
|
+
* `retryable` flag, so those are read from the SDK, never from a table of
|
|
163
|
+
* ours. Four remain, each anchored to its sentence so a git or GitHub failure
|
|
164
|
+
* that embeds the same words never matches:
|
|
165
|
+
* - the platform's bare `internal error` — workerd's own catch-all when a
|
|
166
|
+
* stub call died for a platform-internal reason — anchored to the WHOLE
|
|
167
|
+
* message, where git's `fatal: internal error` is not, and read on the
|
|
168
|
+
* TOP-LEVEL throw alone (`PLATFORM_INTERNAL_ERROR_WORDING`, never on the
|
|
169
|
+
* cause chain): a Worker's own failed outbound `fetch()` carries exactly
|
|
170
|
+
* that message too, so a route's wrapped GitHub or mirror subrequest
|
|
171
|
+
* failure — the fetch error as the route's `cause` — is that route's
|
|
172
|
+
* deterministic failure, never a blip a re-probe clears; the SDK's
|
|
173
|
+
* predicate types the bare message only where workerd set its `retryable`
|
|
174
|
+
* flag on it, and nothing in the SDK's source or a live receipt says it
|
|
175
|
+
* always does (an assumption about the platform, named);
|
|
176
|
+
* - the memory-limit reset, `Durable Object's isolate exceeded its memory
|
|
177
|
+
* limit and was reset.` (the platform's text as an assumption too, not in
|
|
178
|
+
* the SDK's source; its predicate carries no pattern for it);
|
|
179
|
+
* - the Durable Object reset by a storage operation that did not complete
|
|
180
|
+
* (`Durable Object storage operation exceeded timeout which caused object
|
|
181
|
+
* to be reset.`, an assumption about the text carried from the first
|
|
182
|
+
* typing of the catch-all — anchored to the sentence's tail, so a route's
|
|
183
|
+
* own `storage operation failed` is not it);
|
|
184
|
+
* - the Durable Object overloaded. The SDK's retry predicate EXCLUDES that
|
|
185
|
+
* sentence (`isErrorRetryable`: an in-process retry only adds to a queue
|
|
186
|
+
* that is full); it is typed transient here because the client's
|
|
187
|
+
* `worker-unavailable` is a re-probe after the harness's bounded wait
|
|
188
|
+
* (`replacedVerdict`, `PROBE_WAIT_MAX_MS`), never a tight retry, and a full
|
|
189
|
+
* queue drains. */
|
|
190
|
+
export const TRANSIENT_PLATFORM_WORDING =
|
|
191
|
+
/exceeded its memory limit and was reset|storage operation exceeded timeout which caused object to be reset|durable object is overloaded/i;
|
|
192
|
+
|
|
193
|
+
/** The platform's bare `internal error`, the whole message — read on the
|
|
194
|
+
* top-level throw only (see above): one link down it is a Worker's own failed
|
|
195
|
+
* subrequest, wrapped by the route that made it. */
|
|
196
|
+
export const PLATFORM_INTERNAL_ERROR_WORDING = /^internal error\.?$/i;
|
|
197
|
+
|
|
198
|
+
/** What only the Worker knows about a throw, handed in so the rules below stay
|
|
199
|
+
* pure: the pinned SDK's predicates (`isDurableObjectCodeUpdateReset` as
|
|
200
|
+
* `isControlReset`, `isPlatformTransientError`) and the resident's own
|
|
201
|
+
* replacement classifier and vouch, which read the SDK's typed classes
|
|
202
|
+
* beside the wording lists this module could import. */
|
|
203
|
+
export interface ThrowPredicates {
|
|
204
|
+
/** The SDK's own code-update reset predicate. */
|
|
205
|
+
isControlReset(err: unknown): boolean;
|
|
206
|
+
/** The resident's replacement classifier: the SDK's typed classes, an RPC
|
|
207
|
+
* transport loss, or the replacement wording anywhere in the cause chain. */
|
|
208
|
+
isRuntimeReplacement(err: unknown): boolean;
|
|
209
|
+
/** The resident's vouch that the runtime MOVED (the SDK's typed classes and
|
|
210
|
+
* `RUNTIME_MOVED_WORDING`), never a mere down wording. */
|
|
211
|
+
sdkVouchesRuntimeMoved(err: unknown): boolean;
|
|
212
|
+
/** The SDK's own platform-transient predicate. */
|
|
213
|
+
isPlatformTransientError(err: unknown): boolean;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** What a caller may already know of a throw, so the verdict does not walk the
|
|
217
|
+
* cause chain for it again: `threadRejectionErr` settles the reset and the
|
|
218
|
+
* replacement before it reaches the typed 500 and hands both in. */
|
|
219
|
+
export interface KnownVerdicts {
|
|
220
|
+
controlReset?: boolean;
|
|
221
|
+
runtimeReplacement?: boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface ThreadErrBuilders {
|
|
225
|
+
/** Whether a throw no route named is the platform's own transient — the
|
|
226
|
+
* Durable Object reset by a deploy, the runtime replaced under the call,
|
|
227
|
+
* the SDK's own platform-transient signal, the container's runtime
|
|
228
|
+
* unreachable, or a remainder sentence — against a deterministic throw in
|
|
229
|
+
* the route itself (a bug, a bad argument). The client reads the answer's
|
|
230
|
+
* `transient` field to re-probe the first and judge the second at once
|
|
231
|
+
* (execution.md item 9), never the words. `known` skips a predicate the
|
|
232
|
+
* caller settled. */
|
|
233
|
+
isTransientPlatformThrow(err: unknown, known?: KnownVerdicts): boolean;
|
|
234
|
+
/** The 500 for a throw no route named, at whichever catch met it — the fetch
|
|
235
|
+
* handler's catch-all, a streamed route's rejection mapper, a route's own
|
|
236
|
+
* catch around its body (`prefix`: `attach-failed`, `op-failed`), the thread
|
|
237
|
+
* data plane's last resort: the words, and whether the throw was the
|
|
238
|
+
* platform's transient (`transient`), typed here so the client decides by a
|
|
239
|
+
* field and never by which catch met the throw. A failure a route DID name
|
|
240
|
+
* — a step that failed, a mirror held — is that route's own answer and
|
|
241
|
+
* never comes here. */
|
|
242
|
+
catchAllErr(err: unknown, prefix?: string, known?: KnownVerdicts): ThreadErr;
|
|
243
|
+
/** A thread data-plane route's pending Durable Object call that REJECTED —
|
|
244
|
+
* the stub, not the method: the DO reset by a code update before or while
|
|
245
|
+
* the method ran, the runtime replaced or unreachable, a storage operation
|
|
246
|
+
* that did not complete — answered as the DO answers the same fact when it
|
|
247
|
+
* catches it inside (`execThreadImpl`, the file methods). A control reset is
|
|
248
|
+
* the DO's own word, `control-reset` on a 409: the container and its
|
|
249
|
+
* processes are as they were and the outcome is unknown, so the client
|
|
250
|
+
* resolves it by its own rule and never waits on it as the resident
|
|
251
|
+
* unavailable. A runtime replacement is answered by route, as the methods
|
|
252
|
+
* answer it, with the DO's own gate (`replacementKnown`) applied as far as
|
|
253
|
+
* it reaches: its first half, the SDK vouching the runtime moved, judges the
|
|
254
|
+
* moved sentences that survive the stub boundary as text, so it is made
|
|
255
|
+
* here by the same rule; its other half, a restore under way
|
|
256
|
+
* (`knowsContainerGone`), is the DO's alone. So on `/exec` the word is said
|
|
257
|
+
* where the SDK vouched and WITHHELD where only the DO could have — that
|
|
258
|
+
* gate's unknown branch, the SDK's words on a 409 with no word, for the
|
|
259
|
+
* harness's one more command to judge; on `/read` and `/write` the word is
|
|
260
|
+
* said either way (`runtimeReplacedErr`, unconditional there on purpose),
|
|
261
|
+
* since it drives the client's one re-attach-and-retry and never a verdict.
|
|
262
|
+
* Anything else is the typed 500. */
|
|
263
|
+
threadRejectionErr(err: unknown, route: ThreadDataRoute): ThreadErr;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The builders over the predicates only the Worker can supply. */
|
|
267
|
+
export function threadErrBuilders(p: ThrowPredicates): ThreadErrBuilders {
|
|
268
|
+
const isTransientPlatformThrow = (err: unknown, known: KnownVerdicts = {}): boolean => {
|
|
269
|
+
const controlReset = known.controlReset ?? p.isControlReset(err);
|
|
270
|
+
const runtimeReplacement = known.runtimeReplacement ?? p.isRuntimeReplacement(err);
|
|
271
|
+
if (controlReset || runtimeReplacement || p.isPlatformTransientError(err)) return true;
|
|
272
|
+
// The platform's bare `internal error`: the top-level throw alone — a
|
|
273
|
+
// wrapped one is a route's own failed subrequest (a GitHub or mirror fetch).
|
|
274
|
+
if (PLATFORM_INTERNAL_ERROR_WORDING.test(messageOf(err))) return true;
|
|
275
|
+
// One walk of ours (the SDK's predicates above walk their own): the
|
|
276
|
+
// container's runtime unreachable — the SDK's connect abort by its
|
|
277
|
+
// `AbortError` name or its sentence (`isRuntimeUnreachableSignal`), or the
|
|
278
|
+
// resident's own renamed word `runtime-unreachable:` (`run()` renames the
|
|
279
|
+
// abort before it escapes a method, so that word is what crosses the stub;
|
|
280
|
+
// `isRuntimeUnreachableReason`) — and the remainder sentences.
|
|
281
|
+
for (const link of selfAndCauses(err)) {
|
|
282
|
+
if (isRuntimeUnreachableSignal(link)) return true;
|
|
283
|
+
const message = messageOf(link);
|
|
284
|
+
if (isRuntimeUnreachableReason(message) || TRANSIENT_PLATFORM_WORDING.test(message)) return true;
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
};
|
|
288
|
+
const catchAllErr = (err: unknown, prefix?: string, known?: KnownVerdicts): ThreadErr => {
|
|
289
|
+
const words = messageOf(err);
|
|
290
|
+
return {
|
|
291
|
+
error: prefix ? `${prefix}: ${words}` : words,
|
|
292
|
+
status: 500,
|
|
293
|
+
transient: isTransientPlatformThrow(err, known),
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
const threadRejectionErr = (err: unknown, route: ThreadDataRoute): ThreadErr => {
|
|
297
|
+
const controlReset = p.isControlReset(err);
|
|
298
|
+
if (controlReset) return controlResetErr(new ControlResetError("call", err));
|
|
299
|
+
const runtimeReplacement = p.isRuntimeReplacement(err);
|
|
300
|
+
if (runtimeReplacement) {
|
|
301
|
+
const vouched = p.sdkVouchesRuntimeMoved(err);
|
|
302
|
+
if (route === "/exec" && !vouched) return { error: messageOf(err), status: 409 };
|
|
303
|
+
return runtimeReplacedErr(new RuntimeReplacedError("call", err, vouched));
|
|
304
|
+
}
|
|
305
|
+
// The two verdicts just settled ride into the typed 500, so its walk of the
|
|
306
|
+
// cause chain is for the transient alone.
|
|
307
|
+
return catchAllErr(err, undefined, { controlReset, runtimeReplacement });
|
|
308
|
+
};
|
|
309
|
+
return { isTransientPlatformThrow, catchAllErr, threadRejectionErr };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** /exec's failure document, in the item-3 dual shape (`error` beside
|
|
313
|
+
* `stdout: ""`, `stderr`, `exitCode: 127`) so old and new executors both
|
|
314
|
+
* render it. The ThreadErr's fields ride beside the words, as the JSON routes
|
|
315
|
+
* carry them: `needs`; the lifecycle pair (`state`, `stateReason`); the
|
|
316
|
+
* answer's own word (`reason`, kept independent of `state` — `runtimeReplacedErr()`
|
|
317
|
+
* sets `reason: "runtime-replaced"` with NO state, and the client's
|
|
318
|
+
* deploy-vs-dead-transport check reads it); the answer's `status`; the
|
|
319
|
+
* catch-all's `transient`. The client types a streamed failure by these fields
|
|
320
|
+
* (execution.md item 9), and a document that dropped them would make every
|
|
321
|
+
* /exec failure a deterministic answer over HTTP 200. One builder for both of
|
|
322
|
+
* the stream's paths: a failure the Durable Object named, and a pending
|
|
323
|
+
* result that rejected. */
|
|
324
|
+
export function execFailureDocument(failure: ThreadErr): object {
|
|
325
|
+
return {
|
|
326
|
+
error: failure.error,
|
|
327
|
+
...(failure.needs ? { needs: failure.needs } : {}),
|
|
328
|
+
...(failure.state ? { state: failure.state } : {}),
|
|
329
|
+
...(typeof failure.stateReason === "string" ? { stateReason: failure.stateReason } : {}),
|
|
330
|
+
...(failure.reason ? { reason: failure.reason } : {}),
|
|
331
|
+
status: failure.status,
|
|
332
|
+
...(typeof failure.transient === "boolean" ? { transient: failure.transient } : {}),
|
|
333
|
+
stdout: "",
|
|
334
|
+
stderr: failure.error,
|
|
335
|
+
exitCode: 127,
|
|
336
|
+
};
|
|
337
|
+
}
|