@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.
Files changed (145) hide show
  1. package/dist/assets/config/config.example.yaml +6 -3
  2. package/dist/assets/deploy/cloudflare/worker.ts +2 -0
  3. package/dist/assets/deploy/cloudflare-resident/threadErr.ts +337 -0
  4. package/dist/assets/deploy/cloudflare-resident/tsconfig.json +1 -1
  5. package/dist/assets/deploy/cloudflare-resident/worker.ts +31 -262
  6. package/dist/assets/deploy/cloudflare-sandbox/worker.ts +5 -10
  7. package/dist/assets/deploy/secrets.manifest.json +12 -0
  8. package/dist/assets/package-lock.json +3 -3
  9. package/dist/assets/package.json +1 -1
  10. package/dist/assets/source.json +3 -3
  11. package/dist/assets/src/agents/registry.ts +4 -4
  12. package/dist/assets/src/config/profile.ts +17 -1
  13. package/dist/assets/src/core/authz/types.ts +8 -0
  14. package/dist/assets/src/core/budgets.ts +25 -0
  15. package/dist/assets/src/core/reviewVerdict.ts +101 -24
  16. package/dist/assets/src/core/runEvents.ts +26 -1
  17. package/dist/assets/src/core/runRecord.ts +34 -0
  18. package/dist/assets/src/core/ship/coordinator.ts +25 -21
  19. package/dist/assets/src/execution/residentRefresh.ts +25 -6
  20. package/dist/assets/web/dist/.vite/manifest.json +481 -462
  21. package/dist/assets/web/dist/assets/AppShell-qqs_p96-.js +1 -0
  22. package/dist/assets/web/dist/assets/CostsPage-Dd9aXxxI.js +2 -0
  23. package/dist/assets/web/dist/assets/DeliveryPage-D90FTjYq.js +1 -0
  24. package/dist/assets/web/dist/assets/HomePage-VLTFopnl.js +2 -0
  25. package/dist/assets/web/dist/assets/InputMenu-lQP3ia9D.js +1 -0
  26. package/dist/assets/web/dist/assets/NotFoundPage-8ViskpN3.js +1 -0
  27. package/dist/assets/web/dist/assets/PendingTurnRow-D2gHef5v.js +1 -0
  28. package/dist/assets/web/dist/assets/ResidentDetailPage-nWLN8BOd.js +1 -0
  29. package/dist/assets/web/dist/assets/ResidentsIndexPage-D3Q48CJW.js +1 -0
  30. package/dist/assets/web/dist/assets/{RunFoldRow-BPMQxTcl.js → RunFoldRow-CHy_IfZG.js} +1 -1
  31. package/dist/assets/web/dist/assets/RunRoutePage-C6kX5om4.js +9 -0
  32. package/dist/assets/web/dist/assets/RunsIndexPage-DjxGj1Q5.js +1 -0
  33. package/dist/assets/web/dist/assets/RunsTabs-WyeG5tKb.js +1 -0
  34. package/dist/assets/web/dist/assets/ScheduledPage-B8D5AU4d.js +1 -0
  35. package/dist/assets/web/dist/assets/SettingSelect-UTHsqUUS.js +1 -0
  36. package/dist/assets/web/dist/assets/SettingsPage-BoW7Zjzt.js +1 -0
  37. package/dist/assets/web/dist/assets/StatusDot-BGHureCJ.js +1 -0
  38. package/dist/assets/web/dist/assets/Tooltip-CiP8qPd7.js +1 -0
  39. package/dist/assets/web/dist/assets/UnitRoutePage-2zEeRBP1.js +1 -0
  40. package/dist/assets/web/dist/assets/{angular-html-Dail07rr.js → angular-html-qXhb9ti8.js} +1 -1
  41. package/dist/assets/web/dist/assets/{angular-ts-BReExio8.js → angular-ts-DzvdyoUZ.js} +1 -1
  42. package/dist/assets/web/dist/assets/{apl-DhNO6Yyx.js → apl-Ck7Ru2Sm.js} +1 -1
  43. package/dist/assets/web/dist/assets/{astro-BYotTY4C.js → astro-CwehvReb.js} +1 -1
  44. package/dist/assets/web/dist/assets/{blade-mUxTtD1X.js → blade-WSyVbSFK.js} +1 -1
  45. package/dist/assets/web/dist/assets/{c-DhuiwcSQ.js → c-DMHI1wDz.js} +1 -1
  46. package/dist/assets/web/dist/assets/{chapel-GD2vFtRi.js → chapel-BF8AYK8_.js} +1 -1
  47. package/dist/assets/web/dist/assets/{cobol-BhFDwkes.js → cobol-UAfImuWu.js} +1 -1
  48. package/dist/assets/web/dist/assets/{coffee-DWUgbE0r.js → coffee-D54hi96d.js} +1 -1
  49. package/dist/assets/web/dist/assets/{cpp-CO9XFjGW.js → cpp-BRWfLgBc.js} +1 -1
  50. package/dist/assets/web/dist/assets/{crystal-B9pVurpn.js → crystal-CSqC5deT.js} +1 -1
  51. package/dist/assets/web/dist/assets/{css-CFVVV6lI.js → css-D4ZuRFGk.js} +1 -1
  52. package/dist/assets/web/dist/assets/{dist-BtPs2AkQ.js → dist-73ZMn_Mg.js} +2 -2
  53. package/dist/assets/web/dist/assets/{durationTone-BobbycC-.js → durationTone-D51enaGR.js} +1 -1
  54. package/dist/assets/web/dist/assets/{edge-BDhIDQIb.js → edge-DDPtNbZc.js} +1 -1
  55. package/dist/assets/web/dist/assets/{elixir-DG3J8WoD.js → elixir-BTQkQfsJ.js} +1 -1
  56. package/dist/assets/web/dist/assets/{elm-FiOdiaOE.js → elm-B_PkLzGZ.js} +1 -1
  57. package/dist/assets/web/dist/assets/{erb-vxbtW503.js → erb-DfjIMVM_.js} +1 -1
  58. package/dist/assets/web/dist/assets/{eventSource-tRprhqm7.js → eventSource-By0B_w4S.js} +1 -1
  59. package/dist/assets/web/dist/assets/{git-rebase-DINz8M3H.js → git-rebase-DP8wmn7K.js} +1 -1
  60. package/dist/assets/web/dist/assets/{glimmer-js-yann6r9i.js → glimmer-js-G12VK2gK.js} +1 -1
  61. package/dist/assets/web/dist/assets/{glimmer-ts-CXTbrZn_.js → glimmer-ts-D3XiSI8E.js} +1 -1
  62. package/dist/assets/web/dist/assets/{glsl-DNYC-hq0.js → glsl-DOKAhMj-.js} +1 -1
  63. package/dist/assets/web/dist/assets/{graphql-CwzhHzi9.js → graphql-CJB_fmON.js} +1 -1
  64. package/dist/assets/web/dist/assets/{hack-BBXF1cwR.js → hack-BHqtRWap.js} +1 -1
  65. package/dist/assets/web/dist/assets/{haml-oEqBcsTy.js → haml-DwotyarS.js} +1 -1
  66. package/dist/assets/web/dist/assets/{handlebars-YE5thkT1.js → handlebars-B6fDaA3U.js} +1 -1
  67. package/dist/assets/web/dist/assets/{html-derivative-DCT1GYtQ.js → html-derivative-C5bpQmmI.js} +1 -1
  68. package/dist/assets/web/dist/assets/{html-Dp4jRQ1j.js → html-s52taj36.js} +1 -1
  69. package/dist/assets/web/dist/assets/{http-BPcGbmzJ.js → http-99zC935r.js} +1 -1
  70. package/dist/assets/web/dist/assets/{hurl-CQnNHwyd.js → hurl-Bgk-4-0x.js} +1 -1
  71. package/dist/assets/web/dist/assets/{java-Dm8Ovj6W.js → java-CuV_c5I0.js} +1 -1
  72. package/dist/assets/web/dist/assets/{javascript-Btm9oJK9.js → javascript-CZlOvSMQ.js} +1 -1
  73. package/dist/assets/web/dist/assets/{jinja-VZYjWF_U.js → jinja-BuXeZ4Tu.js} +1 -1
  74. package/dist/assets/web/dist/assets/{jison-Dcmjxa9x.js → jison-DIuP0hkG.js} +1 -1
  75. package/dist/assets/web/dist/assets/{json-DoWiOGZ8.js → json-CZ74vfoD.js} +1 -1
  76. package/dist/assets/web/dist/assets/{jsx-Dvmo55ma.js → jsx-BNjRs8Vj.js} +1 -1
  77. package/dist/assets/web/dist/assets/{julia-d8AtfV26.js → julia-OCE-fFOg.js} +1 -1
  78. package/dist/assets/web/dist/assets/{just-Cc5EStA-.js → just-CtvD87WF.js} +1 -1
  79. package/dist/assets/web/dist/assets/{latex-B5fKKIH7.js → latex-C3kf86OR.js} +1 -1
  80. package/dist/assets/web/dist/assets/{liquid-DUYI9JND.js → liquid-B1WI--Kp.js} +1 -1
  81. package/dist/assets/web/dist/assets/{lua-BhMwKWpw.js → lua-DtoT0gKg.js} +1 -1
  82. package/dist/assets/web/dist/assets/main-ClluP-Qf.js +28 -0
  83. package/dist/assets/web/dist/assets/main-CpkwzHX6.css +1 -0
  84. package/dist/assets/web/dist/assets/{marko-Cnf2cPAi.js → marko-BE2MC6Yt.js} +1 -1
  85. package/dist/assets/web/dist/assets/{mdc-FDDXoir9.js → mdc-BwO4weXJ.js} +1 -1
  86. package/dist/assets/web/dist/assets/{nginx-q-8U_1jR.js → nginx-BTuOgGVC.js} +1 -1
  87. package/dist/assets/web/dist/assets/{nim-DIA0TUWs.js → nim-CiIUM-oV.js} +1 -1
  88. package/dist/assets/web/dist/assets/{org-CR88hy7S.js → org-L78DEWB-.js} +1 -1
  89. package/dist/assets/web/dist/assets/{perl-Bkd8N7ja.js → perl-KqHOm4Ft.js} +1 -1
  90. package/dist/assets/web/dist/assets/{php-BOs84mvy.js → php-C41XbEkn.js} +1 -1
  91. package/dist/assets/web/dist/assets/{pug-BbpZBb3w.js → pug-B12-4i-k.js} +1 -1
  92. package/dist/assets/web/dist/assets/{qml-D2jQpjFE.js → qml--fw2Up5c.js} +1 -1
  93. package/dist/assets/web/dist/assets/{r-DKzcAQ22.js → r-Db1JBXrC.js} +1 -1
  94. package/dist/assets/web/dist/assets/{razor-SOQMWcC9.js → razor-BYpVBgbu.js} +1 -1
  95. package/dist/assets/web/dist/assets/{regexp-B6KK4W8d.js → regexp-BMqGLSPc.js} +1 -1
  96. package/dist/assets/web/dist/assets/{rst-kPSg-NPf.js → rst-B63n7-Sa.js} +1 -1
  97. package/dist/assets/web/dist/assets/{ruby-cKS-JYLH.js → ruby-D3VI8UYv.js} +1 -1
  98. package/dist/assets/web/dist/assets/{sas-DNxzE5gs.js → sas-DxRf8ZAR.js} +1 -1
  99. package/dist/assets/web/dist/assets/{scss-DGcHQ5OO.js → scss-DRRA6d6A.js} +1 -1
  100. package/dist/assets/web/dist/assets/seed-DFfT5S_N.js +6 -0
  101. package/dist/assets/web/dist/assets/{settingsApi-priOPiwu.js → settingsApi-Dm6eaQcu.js} +1 -1
  102. package/dist/assets/web/dist/assets/{shellscript-CRZeZBa5.js → shellscript-CoOBe3_y.js} +1 -1
  103. package/dist/assets/web/dist/assets/{shellsession-DSNEL0HX.js → shellsession-DOV1HYxf.js} +1 -1
  104. package/dist/assets/web/dist/assets/{soy-zisNbkDO.js → soy-Acpmpp1W.js} +1 -1
  105. package/dist/assets/web/dist/assets/{sql-MG06KMMb.js → sql-DeT9N-fA.js} +1 -1
  106. package/dist/assets/web/dist/assets/{sseReplay-DaRtc7nI.js → sseReplay-DPXVxhL5.js} +3 -3
  107. package/dist/assets/web/dist/assets/{stata-CB_wKwv9.js → stata-CbxXAMO4.js} +1 -1
  108. package/dist/assets/web/dist/assets/{surrealql-DYdSt7re.js → surrealql-BdKbi4hc.js} +1 -1
  109. package/dist/assets/web/dist/assets/{svelte-DHO-F7c7.js → svelte-tAcjDM7R.js} +1 -1
  110. package/dist/assets/web/dist/assets/{templ-CUL5L4Lr.js → templ-CObINNJj.js} +1 -1
  111. package/dist/assets/web/dist/assets/{tex-_JNEKjVW.js → tex-BYwpZT1J.js} +1 -1
  112. package/dist/assets/web/dist/assets/{ts-tags--9EpEV_8.js → ts-tags-BciREL5R.js} +1 -1
  113. package/dist/assets/web/dist/assets/{tsx-BcNq2x2u.js → tsx-CRiv3Pr3.js} +1 -1
  114. package/dist/assets/web/dist/assets/{twig-EsY5Dk_D.js → twig-DuZZ1Ej0.js} +1 -1
  115. package/dist/assets/web/dist/assets/{typescript-CyA_HiUH.js → typescript-jkHz_5HZ.js} +1 -1
  116. package/dist/assets/web/dist/assets/{typst-SzaHlDt4.js → typst-DOac4Gal.js} +1 -1
  117. package/dist/assets/web/dist/assets/{vue-D3cn5acV.js → vue-CgOX5wXA.js} +1 -1
  118. package/dist/assets/web/dist/assets/{vue-html-6MxhUxOa.js → vue-html-Duu99VNw.js} +1 -1
  119. package/dist/assets/web/dist/assets/{vue-vine-qJnqhgo9.js → vue-vine-Bmj_wvRJ.js} +1 -1
  120. package/dist/assets/web/dist/assets/wallClock-DD_fPuSY.js +1 -0
  121. package/dist/assets/web/dist/assets/{xml-DyRpC7xv.js → xml-BtUIVeWH.js} +1 -1
  122. package/dist/assets/web/dist/assets/{xsl-BXMRPAsg.js → xsl-wX71n1E5.js} +1 -1
  123. package/dist/assets/web/dist/assets/{yaml-BNmPCtAN.js → yaml-CdLdXvmz.js} +1 -1
  124. package/dist/cli.js +1474 -684
  125. package/package.json +1 -1
  126. package/dist/assets/web/dist/assets/AppShell-Ck4hSZDi.js +0 -1
  127. package/dist/assets/web/dist/assets/CostsPage-DvhjDMka.js +0 -2
  128. package/dist/assets/web/dist/assets/DeliveryPage-DBlI_Yq2.js +0 -1
  129. package/dist/assets/web/dist/assets/HomePage-XFWPxDiJ.js +0 -2
  130. package/dist/assets/web/dist/assets/NotFoundPage-CiGrVOyv.js +0 -1
  131. package/dist/assets/web/dist/assets/PendingTurnRow-BHCSpsPf.js +0 -1
  132. package/dist/assets/web/dist/assets/ResidentDetailPage-C5USV_8P.js +0 -1
  133. package/dist/assets/web/dist/assets/ResidentsIndexPage-CSKisWnX.js +0 -1
  134. package/dist/assets/web/dist/assets/RunRoutePage-CZe9Yodw.js +0 -9
  135. package/dist/assets/web/dist/assets/RunsIndexPage-BfA333Qy.js +0 -1
  136. package/dist/assets/web/dist/assets/RunsTabs-BWRHzhtT.js +0 -1
  137. package/dist/assets/web/dist/assets/ScheduledPage-DBaChcJZ.js +0 -1
  138. package/dist/assets/web/dist/assets/SettingsPage-CqVi9-Ac.js +0 -1
  139. package/dist/assets/web/dist/assets/StatusDot-_Q_bmReh.js +0 -1
  140. package/dist/assets/web/dist/assets/Tooltip-Cafsgd4m.js +0 -1
  141. package/dist/assets/web/dist/assets/UnitRoutePage-CdoBZwtf.js +0 -1
  142. package/dist/assets/web/dist/assets/main-C4366PZF.css +0 -1
  143. package/dist/assets/web/dist/assets/main-DlTNAC6c.js +0 -28
  144. package/dist/assets/web/dist/assets/seed-DeKSHrt2.js +0 -6
  145. 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
+ }
@@ -15,5 +15,5 @@
15
15
  "noEmit": true,
16
16
  "skipLibCheck": true
17
17
  },
18
- "include": ["worker.ts", "refresh.ts", "shared.ts", "node-async-hooks.d.ts"]
18
+ "include": ["worker.ts", "refresh.ts", "shared.ts", "threadErr.ts", "node-async-hooks.d.ts"]
19
19
  }