@coreplane/switchboard 1.239.0 → 1.241.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 (156) hide show
  1. package/dist/assets/config/config.example.yaml +32 -7
  2. package/dist/assets/deploy/cloudflare/worker.ts +77 -18
  3. package/dist/assets/deploy/cloudflare-memory/worker.ts +115 -1
  4. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +7 -1
  5. package/dist/assets/deploy/cloudflare-resident/worker.ts +224 -70
  6. package/dist/assets/deploy/secrets.manifest.json +7 -1
  7. package/dist/assets/package-lock.json +207 -3
  8. package/dist/assets/package.json +2 -1
  9. package/dist/assets/source.json +3 -3
  10. package/dist/assets/src/agents/registry.ts +27 -8
  11. package/dist/assets/src/core/authz/actor.ts +38 -2
  12. package/dist/assets/src/core/authz/grants.ts +20 -9
  13. package/dist/assets/src/core/authz/policy.ts +6 -0
  14. package/dist/assets/src/core/coordinator/contract.ts +49 -1
  15. package/dist/assets/src/core/coordinator/driver.ts +30 -1
  16. package/dist/assets/src/core/coordinator/instancesRoute.ts +72 -0
  17. package/dist/assets/src/core/costs.ts +1119 -0
  18. package/dist/assets/src/core/costsSnapshotStore.ts +209 -0
  19. package/dist/assets/src/core/delivery.ts +4 -11
  20. package/dist/assets/src/core/deliverySnapshotStore.ts +12 -28
  21. package/dist/assets/src/core/ingressTokens.ts +27 -13
  22. package/dist/assets/src/core/runLedger/types.ts +3 -0
  23. package/dist/assets/src/core/runRecord.ts +10 -3
  24. package/dist/assets/src/core/schedules.ts +2 -1
  25. package/dist/assets/src/core/ship/coordinator.ts +92 -7
  26. package/dist/assets/src/core/snapshotAge.ts +30 -0
  27. package/dist/assets/src/core/stateWorkerRef.ts +41 -0
  28. package/dist/assets/src/core/trace/attrs.ts +1 -1
  29. package/dist/assets/src/execution/bindingPurge.ts +2 -2
  30. package/dist/assets/src/execution/residentAutoRebuild.ts +73 -0
  31. package/dist/assets/src/execution/residentRefresh.ts +25 -3
  32. package/dist/assets/src/execution/restoreWaiters.ts +61 -0
  33. package/dist/assets/src/mcp/registry.ts +7 -0
  34. package/dist/assets/web/dist/.vite/manifest.json +509 -427
  35. package/dist/assets/web/dist/assets/AppShell-DEy5jRuS.js +1 -0
  36. package/dist/assets/web/dist/assets/CostsPage-BuHD-Bv7.js +2 -0
  37. package/dist/assets/web/dist/assets/DeliveryPage-BGpPr5xr.js +1 -0
  38. package/dist/assets/web/dist/assets/HomePage-dKBvWh-E.js +2 -0
  39. package/dist/assets/web/dist/assets/NotFoundPage-DtmQM2gD.js +1 -0
  40. package/dist/assets/web/dist/assets/PendingTurnRow-CrEAWx5b.js +1 -0
  41. package/dist/assets/web/dist/assets/{ResidentDetailPage-CrfCd8fx.js → ResidentDetailPage-C-48fxat.js} +1 -1
  42. package/dist/assets/web/dist/assets/ResidentsIndexPage-CBBIjPOa.js +1 -0
  43. package/dist/assets/web/dist/assets/RunFoldRow-B6vxjcBI.js +1 -0
  44. package/dist/assets/web/dist/assets/RunRoutePage-CQjd3Cz-.js +6 -0
  45. package/dist/assets/web/dist/assets/RunsIndexPage-Cy9G6D0p.js +1 -0
  46. package/dist/assets/web/dist/assets/{RunsTabs-XpCqeWu4.js → RunsTabs-CtNGNnhN.js} +1 -1
  47. package/dist/assets/web/dist/assets/ScheduledPage-Ci1Ygqnc.js +1 -0
  48. package/dist/assets/web/dist/assets/SettingsPage-DxmJvAmT.js +1 -0
  49. package/dist/assets/web/dist/assets/SlackMark-VDNs7Vjh.js +1 -0
  50. package/dist/assets/web/dist/assets/{StatusDot-DkXALvKf.js → StatusDot-DPWE1JmO.js} +1 -1
  51. package/dist/assets/web/dist/assets/{Tooltip-DtLIyi5P.js → Tooltip-DKhSRH9t.js} +1 -1
  52. package/dist/assets/web/dist/assets/UnitRoutePage-DEN5bSQf.js +1 -0
  53. package/dist/assets/web/dist/assets/{angular-html-DYtV9uIN.js → angular-html-DgSK1qvr.js} +1 -1
  54. package/dist/assets/web/dist/assets/{angular-ts-DF2P25Zp.js → angular-ts-D3gNdiSG.js} +1 -1
  55. package/dist/assets/web/dist/assets/{apl-Ff96wxko.js → apl-CkHCYM8I.js} +1 -1
  56. package/dist/assets/web/dist/assets/{astro-LbBnZna-.js → astro-DLm45axt.js} +1 -1
  57. package/dist/assets/web/dist/assets/{blade-DltuxeBW.js → blade-BKa-VE-c.js} +1 -1
  58. package/dist/assets/web/dist/assets/{c-DBg7yuPx.js → c-C8NCNWai.js} +1 -1
  59. package/dist/assets/web/dist/assets/{chapel-C83hUAds.js → chapel-DqDQ7IKH.js} +1 -1
  60. package/dist/assets/web/dist/assets/{cobol-DB0yV_2D.js → cobol-j4vCD6AJ.js} +1 -1
  61. package/dist/assets/web/dist/assets/{coffee-Bpih6Kzl.js → coffee-C6oDVH-0.js} +1 -1
  62. package/dist/assets/web/dist/assets/{cpp-CU00ZeRn.js → cpp-Bd3A1Rtc.js} +1 -1
  63. package/dist/assets/web/dist/assets/{crystal-f8ImJkux.js → crystal-CUOgOw0d.js} +1 -1
  64. package/dist/assets/web/dist/assets/{css-4VrC4ISo.js → css-RYljyv7G.js} +1 -1
  65. package/dist/assets/web/dist/assets/delivery-CTdOqpVK.js +1 -0
  66. package/dist/assets/web/dist/assets/{dist-DtA7JiH5.js → dist-BnJXKHCY.js} +2 -2
  67. package/dist/assets/web/dist/assets/durationTone-DXG-3R7_.js +1 -0
  68. package/dist/assets/web/dist/assets/{edge-BwFDzfS_.js → edge-BJhNdQDc.js} +1 -1
  69. package/dist/assets/web/dist/assets/{elixir-wwIYIpJm.js → elixir-g1AWOAfT.js} +1 -1
  70. package/dist/assets/web/dist/assets/{elm-DyDuM3v3.js → elm-CER5e4Dz.js} +1 -1
  71. package/dist/assets/web/dist/assets/{erb-DFJBEVrh.js → erb-D5aOmMQf.js} +1 -1
  72. package/dist/assets/web/dist/assets/eventSource-tRprhqm7.js +1 -0
  73. package/dist/assets/web/dist/assets/favicon-D1hkyC5j.js +1 -0
  74. package/dist/assets/web/dist/assets/{git-rebase-CuDv-44X.js → git-rebase-D6rfV8jp.js} +1 -1
  75. package/dist/assets/web/dist/assets/{glimmer-js-ZFnrm4J3.js → glimmer-js-BvDH3-mC.js} +1 -1
  76. package/dist/assets/web/dist/assets/{glimmer-ts-XY4Yqshp.js → glimmer-ts-DChtNs9V.js} +1 -1
  77. package/dist/assets/web/dist/assets/{glsl-xnR8efx0.js → glsl-BHlsWrxf.js} +1 -1
  78. package/dist/assets/web/dist/assets/{graphql-D-gO5tnP.js → graphql-BsaBHb53.js} +1 -1
  79. package/dist/assets/web/dist/assets/{hack-CSxbW4ax.js → hack-0lbDosCd.js} +1 -1
  80. package/dist/assets/web/dist/assets/{haml-D4ar42QT.js → haml-BKtco2vr.js} +1 -1
  81. package/dist/assets/web/dist/assets/{handlebars-CMLbDE7o.js → handlebars-BE_cj04z.js} +1 -1
  82. package/dist/assets/web/dist/assets/{html-CVekU4Fs.js → html-BNcr8EVd.js} +1 -1
  83. package/dist/assets/web/dist/assets/{html-derivative-St3HHw_N.js → html-derivative-BsJt2Kei.js} +1 -1
  84. package/dist/assets/web/dist/assets/{http-Mon0DWHS.js → http-BR5P8ER_.js} +1 -1
  85. package/dist/assets/web/dist/assets/{hurl-Cbl5IvBs.js → hurl-CtHalpWS.js} +1 -1
  86. package/dist/assets/web/dist/assets/{localIso-CNA-bhS-.js → indexFormat-B-pd4r7Y.js} +1 -1
  87. package/dist/assets/web/dist/assets/{indexRow-FLJu5mR9.js → indexRow-BD1VT8o8.js} +1 -1
  88. package/dist/assets/web/dist/assets/{java-Bprck1l-.js → java-BXVdhN11.js} +1 -1
  89. package/dist/assets/web/dist/assets/{javascript-C73G1-UR.js → javascript-CyoShgNQ.js} +1 -1
  90. package/dist/assets/web/dist/assets/{jinja-C4DuRxYJ.js → jinja-BQYZzHex.js} +1 -1
  91. package/dist/assets/web/dist/assets/{jison-B2U03GEC.js → jison-DqrUa1Eq.js} +1 -1
  92. package/dist/assets/web/dist/assets/{json-CvTZUDzx.js → json-CQiTF9Jj.js} +1 -1
  93. package/dist/assets/web/dist/assets/{jsx-DMWKCzwo.js → jsx-Y16MkCYQ.js} +1 -1
  94. package/dist/assets/web/dist/assets/{julia-Czn7bV_3.js → julia-zn16YONe.js} +1 -1
  95. package/dist/assets/web/dist/assets/{just-BBxpNfvC.js → just-DcVeN_MN.js} +1 -1
  96. package/dist/assets/web/dist/assets/{latex-Bw9yg5mr.js → latex-BNNoF-WP.js} +1 -1
  97. package/dist/assets/web/dist/assets/{liquid-BRCjaydE.js → liquid-DAi6qHzn.js} +1 -1
  98. package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
  99. package/dist/assets/web/dist/assets/{lua-DUu2KIC0.js → lua-BswS0axw.js} +1 -1
  100. package/dist/assets/web/dist/assets/main-B3is4sk6.js +28 -0
  101. package/dist/assets/web/dist/assets/main-DP_zSemY.css +1 -0
  102. package/dist/assets/web/dist/assets/{marko-CZJZIlSQ.js → marko-C-4hkmQZ.js} +1 -1
  103. package/dist/assets/web/dist/assets/{mdc-BmClq6fd.js → mdc-0ZaHUkS7.js} +1 -1
  104. package/dist/assets/web/dist/assets/{nginx-Bmt3xSEm.js → nginx-DBDg5tOR.js} +1 -1
  105. package/dist/assets/web/dist/assets/{nim-Da-i0utK.js → nim-BVMz49Yw.js} +1 -1
  106. package/dist/assets/web/dist/assets/{org-BvAre2qP.js → org-1hO_K6ya.js} +1 -1
  107. package/dist/assets/web/dist/assets/{perl-v9WwFFtB.js → perl-CVRQLvVk.js} +1 -1
  108. package/dist/assets/web/dist/assets/{php-DTU4Zyze.js → php-K9nCrB9n.js} +1 -1
  109. package/dist/assets/web/dist/assets/{pug-DhTwEqTO.js → pug-CTxOcmO3.js} +1 -1
  110. package/dist/assets/web/dist/assets/{qml-BUD9AC_c.js → qml-CmCkfG5q.js} +1 -1
  111. package/dist/assets/web/dist/assets/{r-CAnu56n7.js → r-pP47Xn1X.js} +1 -1
  112. package/dist/assets/web/dist/assets/{razor-Dn0P4gAq.js → razor-BpW6r3tC.js} +1 -1
  113. package/dist/assets/web/dist/assets/{regexp-CZyBRaVW.js → regexp-CINgdY4N.js} +1 -1
  114. package/dist/assets/web/dist/assets/{rst-Ckyd5UsY.js → rst-BZIUEq2Q.js} +1 -1
  115. package/dist/assets/web/dist/assets/{ruby-BBQ62fsx.js → ruby-nzAOxz6r.js} +1 -1
  116. package/dist/assets/web/dist/assets/{sas-CW35hMxG.js → sas-QIS1bFth.js} +1 -1
  117. package/dist/assets/web/dist/assets/{scss-CpV8pF4J.js → scss-BuhBBVNt.js} +1 -1
  118. package/dist/assets/web/dist/assets/settingsApi-priOPiwu.js +1 -0
  119. package/dist/assets/web/dist/assets/{shellscript-CRJvsmE1.js → shellscript-CQc1vXbk.js} +1 -1
  120. package/dist/assets/web/dist/assets/{shellsession-CyriUZxf.js → shellsession-CoubCAUv.js} +1 -1
  121. package/dist/assets/web/dist/assets/{soy-D5QzYUeg.js → soy-CPRzlder.js} +1 -1
  122. package/dist/assets/web/dist/assets/{sql-CUBf_Mse.js → sql-W9krb8-9.js} +1 -1
  123. package/dist/assets/web/dist/assets/sseReplay-yji8a1wM.js +9 -0
  124. package/dist/assets/web/dist/assets/{stata-B-4n4ITz.js → stata-CuISJEC0.js} +1 -1
  125. package/dist/assets/web/dist/assets/{surrealql-RuWZBLtr.js → surrealql-CSet7584.js} +1 -1
  126. package/dist/assets/web/dist/assets/{svelte-CO5YzCsK.js → svelte-ChXTSYwK.js} +1 -1
  127. package/dist/assets/web/dist/assets/{templ-DbkWVaBF.js → templ-Bm55v62k.js} +1 -1
  128. package/dist/assets/web/dist/assets/{tex-IuxoEcId.js → tex-CICMX8Gj.js} +1 -1
  129. package/dist/assets/web/dist/assets/{ts-tags-g6dGQPjd.js → ts-tags-CA1UzWyB.js} +1 -1
  130. package/dist/assets/web/dist/assets/{tsx-WnXwiyH2.js → tsx-Dy04HNbv.js} +1 -1
  131. package/dist/assets/web/dist/assets/{twig-Lx1G7PnY.js → twig-BBHsVnVD.js} +1 -1
  132. package/dist/assets/web/dist/assets/{typescript-BQd7biLF.js → typescript-Dvc-wVBT.js} +1 -1
  133. package/dist/assets/web/dist/assets/{typst-D2lXPHK7.js → typst-Dvlqx3_q.js} +1 -1
  134. package/dist/assets/web/dist/assets/{vue-html-DZPaRte4.js → vue-html-D4YxT3An.js} +1 -1
  135. package/dist/assets/web/dist/assets/{vue-vine-DbmZCmu-.js → vue-vine-0YE7uQlH.js} +1 -1
  136. package/dist/assets/web/dist/assets/{vue-4wVQD9y9.js → vue-zWi1MNU2.js} +1 -1
  137. package/dist/assets/web/dist/assets/{xml-DV16szyo.js → xml-gm-iksZB.js} +1 -1
  138. package/dist/assets/web/dist/assets/{xsl-eo5-1sk0.js → xsl-D_W5BcrY.js} +1 -1
  139. package/dist/assets/web/dist/assets/{yaml-BXdX8HZf.js → yaml-CHmZ21wZ.js} +1 -1
  140. package/dist/cli.js +6014 -2362
  141. package/package.json +1 -1
  142. package/dist/assets/web/dist/assets/AppShell-MYHNxJks.js +0 -1
  143. package/dist/assets/web/dist/assets/CostsPage-DKk9OjB0.js +0 -2
  144. package/dist/assets/web/dist/assets/DeliveryPage-Kz6js49i.js +0 -1
  145. package/dist/assets/web/dist/assets/NotFoundPage-BXLrQQzy.js +0 -1
  146. package/dist/assets/web/dist/assets/ResidentsIndexPage-BdW0k2h3.js +0 -1
  147. package/dist/assets/web/dist/assets/RunFoldRow-CbTd9SiS.js +0 -9
  148. package/dist/assets/web/dist/assets/RunRoutePage-br0H-8Pz.js +0 -6
  149. package/dist/assets/web/dist/assets/RunsIndexPage-Bnzb15zH.js +0 -1
  150. package/dist/assets/web/dist/assets/ScheduledPage-NLhjzHAz.js +0 -1
  151. package/dist/assets/web/dist/assets/SettingsPage-D-i0uVw-.js +0 -1
  152. package/dist/assets/web/dist/assets/UnitRoutePage-DGXukEAH.js +0 -1
  153. package/dist/assets/web/dist/assets/durationTone-CYMux25J.js +0 -1
  154. package/dist/assets/web/dist/assets/favicon-CSt-qDvc.js +0 -1
  155. package/dist/assets/web/dist/assets/main-CFHF2RvQ.css +0 -1
  156. package/dist/assets/web/dist/assets/main-Dht0in4v.js +0 -28
@@ -0,0 +1,41 @@
1
+ // The one state Worker (deploy/cloudflare-memory/), as the config names it: any
2
+ // `*.worker` block may carry its base URL and the env var holding its bearer,
3
+ // and a capability that keeps a cache there (a delivery snapshot, a costs
4
+ // snapshot) reads whichever block names it — a cache earns no block of its own.
5
+ //
6
+ // Node-free on purpose: the snapshot stores that import this are shared with
7
+ // the Worker by relative path.
8
+
9
+ /** A `{ baseUrl, tokenEnv? }` reference to the state Worker, as every `*.worker` config block spells it. */
10
+ export interface StateWorkerRef {
11
+ baseUrl: string;
12
+ /** Env var holding the bearer; default MEMORY_TOKEN. */
13
+ tokenEnv?: string;
14
+ }
15
+
16
+ /** The config blocks that may name the state Worker — one Worker, referenced from each capability that uses it. */
17
+ export interface StateWorkerBlocks {
18
+ runtimeOverrides?: { worker?: StateWorkerRef };
19
+ runHistory?: { worker?: StateWorkerRef };
20
+ schedules?: { worker?: StateWorkerRef };
21
+ memory?: { worker?: StateWorkerRef };
22
+ }
23
+
24
+ /** The env var holding the state Worker's bearer when a block names none. */
25
+ export const DEFAULT_STATE_WORKER_TOKEN_ENV = "MEMORY_TOKEN";
26
+
27
+ /** The slice of the process's `Secrets` a store builder reads (src/secrets.ts; named
28
+ * structurally so the builders stay free of the bot's Node-only imports for the Worker). */
29
+ export interface SecretReader {
30
+ named(name: string): { reveal(): string } | undefined;
31
+ }
32
+
33
+ /** The first `*.worker` block that names the state Worker, in the order the blocks are listed above. */
34
+ export function stateWorkerOf(config: StateWorkerBlocks): StateWorkerRef | undefined {
35
+ return [config.runtimeOverrides, config.runHistory, config.schedules, config.memory].find(
36
+ (block) => block?.worker?.baseUrl,
37
+ )?.worker;
38
+ }
39
+
40
+ /** The names a `*.worker` block may carry the state Worker under, for a warning that says where to put one. */
41
+ export const STATE_WORKER_BLOCK_NAMES = "runHistory.worker, runtimeOverrides.worker, schedules.worker or memory.worker";
@@ -8,7 +8,7 @@
8
8
  * field. */
9
9
 
10
10
  export type Backend = "local" | "resident" | "sandbox" | "e2b";
11
- export type Channel = "slack" | "http" | "mcp" | "cli";
11
+ export type Channel = "slack" | "http" | "mcp" | "cli" | "web";
12
12
 
13
13
  /** Every attribute key any span may carry, with its value domain. */
14
14
  export interface AttrDomain {
@@ -14,8 +14,8 @@ export interface PurgeableBinding {
14
14
  export type PurgeDecision = { ok: true; purge: string[]; keptLive: string[] } | { ok: false; error: string };
15
15
 
16
16
  /** Thread-key namespaces real channels mint (docs/reference/specs/http-ingress.md item 1,
17
- * slack, mcp, the CLI). A purge is for synthetic keys only. */
18
- const PRODUCTION_NAMESPACES = new Set(["slack", "http", "mcp", "cli"]);
17
+ * slack, mcp, the CLI, the web chat). A purge is for synthetic keys only. */
18
+ const PRODUCTION_NAMESPACES = new Set(["slack", "http", "mcp", "cli", "web"]);
19
19
 
20
20
  /** A prefix must be a whole namespace (`load:`) or longer (`load:r1:`), never
21
21
  * empty, never a bare partial namespace, never a production namespace. */
@@ -0,0 +1,73 @@
1
+ // A resident `down` on a reason only a rebuild can escape is rebuilt on that
2
+ // transition, under a budget (docs/reference/specs/resident-repos.md item 36).
3
+ // Pure: the Worker's `goDown` and the watchdog's backstop both call
4
+ // `autoRebuildDecision` over the history row they persist
5
+ // (`resident:autoRebuilds`, ISO instants of the auto-rebuilds this resident
6
+ // has had), so the transition and the backstop cannot disagree.
7
+
8
+ /** The `down` reasons a rebuild is the only exit from: a down resident runs no
9
+ * cycle, and these say the snapshot (or the container that would restore it)
10
+ * cannot be used — never a provision failure, which would loop against the
11
+ * same broken build. `runtime-unreachable` is item 64's last rung: a recreated
12
+ * container that did not answer either. */
13
+ export const REHYDRATION_FAILURE_RE = /^(r2-restore-failed|snapshot-stamp-mismatch|no-snapshot|runtime-unreachable)/;
14
+
15
+ /** How many auto-rebuilds one resident gets inside one window. A rebuild is a
16
+ * clone, an install and a build (minutes to half an hour) that holds the cap
17
+ * slot and writes a fresh snapshot; a resident that comes back `warm` and
18
+ * goes `down` again on the same class of reason is flapping on something a
19
+ * rebuild does not fix, and the third time it stays down where a person can
20
+ * see it. */
21
+ export const AUTO_REBUILD_BUDGET = 2;
22
+ export const AUTO_REBUILD_WINDOW_MS = 24 * 60 * 60_000;
23
+
24
+ /** The stamp a spent budget leaves on the reason — the one mark the decision
25
+ * reads back so the watchdog's passes never judge the same down twice. */
26
+ const SPENT_MARK = " — auto-rebuild budget spent (";
27
+
28
+ export type AutoRebuildDecision =
29
+ /** Rebuild now; `history` is what to persist (pruned, this instant appended); `reason` is the rebuild's. */
30
+ | { action: "rebuild"; history: string[]; reason: string }
31
+ /** Stay down; `reason` is the down reason stamped with the budget; `history` is pruned. */
32
+ | { action: "budget-spent"; history: string[]; reason: string }
33
+ | { action: "not-eligible"; why: "reason" | "already-spent" };
34
+
35
+ /** A `down` reason the transition (or the watchdog) may rebuild from: a
36
+ * rehydration failure that no spent budget has stamped yet. */
37
+ export function isAutoRebuildEligible(reason: string): boolean {
38
+ return REHYDRATION_FAILURE_RE.test(reason) && !reason.includes(SPENT_MARK);
39
+ }
40
+
41
+ /** Judge one `down`. `history` are the ISO instants of this resident's past
42
+ * auto-rebuilds; entries outside the window (or unparseable) are dropped
43
+ * before counting and before writing back. */
44
+ export function autoRebuildDecision(input: {
45
+ reason: string;
46
+ history: readonly string[];
47
+ now: number;
48
+ }): AutoRebuildDecision {
49
+ if (!REHYDRATION_FAILURE_RE.test(input.reason)) return { action: "not-eligible", why: "reason" };
50
+ if (input.reason.includes(SPENT_MARK)) return { action: "not-eligible", why: "already-spent" };
51
+ const windowStart = input.now - AUTO_REBUILD_WINDOW_MS;
52
+ const kept = input.history
53
+ .map((iso) => ({ iso, at: Date.parse(iso) }))
54
+ .filter((e) => Number.isFinite(e.at) && e.at > windowStart)
55
+ .sort((a, b) => a.at - b.at);
56
+ const hours = Math.round(AUTO_REBUILD_WINDOW_MS / 3_600_000);
57
+ if (kept.length >= AUTO_REBUILD_BUDGET) {
58
+ // No reopen instant on the stamp: a stamped row is never judged again and
59
+ // a down resident runs no cycle, so nothing resumes when the window
60
+ // passes — only a person's rebuild recovers it, and the stamp says so.
61
+ return {
62
+ action: "budget-spent",
63
+ history: kept.map((e) => e.iso),
64
+ reason: `${input.reason}${SPENT_MARK}${AUTO_REBUILD_BUDGET} in ${hours} h) — repo rebuild resets it`,
65
+ };
66
+ }
67
+ const history = [...kept.map((e) => e.iso), new Date(input.now).toISOString()];
68
+ return {
69
+ action: "rebuild",
70
+ history,
71
+ reason: `auto-rebuild (${history.length} of ${AUTO_REBUILD_BUDGET} in ${hours} h): ${input.reason}`,
72
+ };
73
+ }
@@ -180,7 +180,7 @@ export function killStaleBuildProcessesCommand(user: string, dir: string): strin
180
180
  * a tool naming the signal. A bare "killed" is NOT enough — compilers and
181
181
  * OOM messages say it too — and a step the cycle itself timed out is a real
182
182
  * failure however it died. */
183
- const INTERRUPTION_SIGNATURE = /\bexit 143\b|Session terminated|SIGTERM/;
183
+ const INTERRUPTION_SIGNATURE = /\bexit 143\b|Session terminated|SIGTERM|^restore-interrupted:/;
184
184
 
185
185
  /** Message wording of the Sandbox SDK's runtime-replacement error family — the
186
186
  * container went away UNDER a live SDK call, so the failure never reaches the
@@ -396,12 +396,34 @@ export function isRuntimeUnreachableReason(reason: string): boolean {
396
396
  * means the stream ran on before the replacement. */
397
397
  export function restoreFailureDisposition(
398
398
  message: string,
399
- facts: { runtimeReplaced?: boolean } = {},
399
+ facts: { runtimeReplaced?: boolean; runtimeActive?: boolean } = {},
400
400
  ): { action: "interrupted"; reason: string } | { action: "down"; reason: string } {
401
401
  const timedOut = /\(timed out\)/.test(message);
402
- if (!timedOut && (facts.runtimeReplaced === true || RUNTIME_REPLACEMENT_WORDING.test(message))) {
402
+ if (timedOut) {
403
+ return {
404
+ action: "down",
405
+ reason: `r2-restore-failed: ${message} — container stopped so the transfer cannot land on a rebuild`,
406
+ };
407
+ }
408
+ // The kill signature the instance step's classifier reads
409
+ // (INTERRUPTION_SIGNATURE): a container roll SIGTERMs the extract, whose
410
+ // exec comes back a normal `exit 143` result — the SDK's replacement
411
+ // wording only reaches the execs that follow. Same event, same reading.
412
+ if (
413
+ facts.runtimeReplaced === true ||
414
+ RUNTIME_REPLACEMENT_WORDING.test(message) ||
415
+ INTERRUPTION_SIGNATURE.test(message)
416
+ ) {
403
417
  return { action: "interrupted", reason: `restore-interrupted: ${message}` };
404
418
  }
419
+ // The Worker's probe after the failure: a runtime that is no longer active
420
+ // took the disk the extract wrote to with it, whatever the exit code said.
421
+ if (facts.runtimeActive === false) {
422
+ return {
423
+ action: "interrupted",
424
+ reason: `restore-interrupted: ${message} — the container stopped under the restore`,
425
+ };
426
+ }
405
427
  return {
406
428
  action: "down",
407
429
  reason: `r2-restore-failed: ${message} — container stopped so the transfer cannot land on a rebuild`,
@@ -0,0 +1,61 @@
1
+ /** The ledger of held `/await-restore` requests (docs/reference/specs/execution.md
2
+ * item 27): each waiter is one resolver held in memory until the resident's
3
+ * lifecycle state leaves `restoring`, when `publish` answers every held
4
+ * request at once with the state it landed on. Event-driven by construction —
5
+ * no polling and no retry timer anywhere: the one publish is the state
6
+ * transition itself (`setResidentState`). In-DO memory only: a Durable Object
7
+ * restart drops the held RPCs with the waiters, and the bot's own request
8
+ * deadline turns that into a named cold fallback, never a hang. */
9
+ export interface RestoreOutcome {
10
+ /** The lifecycle state the resident landed on when the restore span ended. */
11
+ state: string;
12
+ reason: string;
13
+ }
14
+
15
+ /** One registered waiter: the outcome it will be answered with, and the way
16
+ * to withdraw it when the caller learns it need not wait after all. */
17
+ export interface RestoreHold {
18
+ outcome: Promise<RestoreOutcome>;
19
+ /** Take the waiter off the ledger; a no-op once a publish answered it. */
20
+ withdraw(): void;
21
+ }
22
+
23
+ export class RestoreWaiters {
24
+ private waiters: Array<(outcome: RestoreOutcome) => void> = [];
25
+
26
+ /** How many requests are held right now (the ledger's size, for tests and logs). */
27
+ get size(): number {
28
+ return this.waiters.length;
29
+ }
30
+
31
+ /** Register a waiter for the next publish. Register BEFORE reading the
32
+ * state you are waiting to leave: a transition that lands between the read
33
+ * and the registration would otherwise publish to a ledger this request is
34
+ * not on yet, and hold it for a transition that may never come. A caller
35
+ * whose read then says the wait is over withdraws. */
36
+ hold(): RestoreHold {
37
+ let resolve!: (outcome: RestoreOutcome) => void;
38
+ const outcome = new Promise<RestoreOutcome>((r) => (resolve = r));
39
+ this.waiters.push(resolve);
40
+ return {
41
+ outcome,
42
+ withdraw: () => {
43
+ const at = this.waiters.indexOf(resolve);
44
+ if (at !== -1) this.waiters.splice(at, 1);
45
+ },
46
+ };
47
+ }
48
+
49
+ /** Hold one request until the next publish. */
50
+ wait(): Promise<RestoreOutcome> {
51
+ return this.hold().outcome;
52
+ }
53
+
54
+ /** Answer every held request with the state the resident landed on and
55
+ * empty the ledger; a publish with nothing held is a no-op. */
56
+ publish(outcome: RestoreOutcome): void {
57
+ const held = this.waiters;
58
+ this.waiters = [];
59
+ for (const resolve of held) resolve(outcome);
60
+ }
61
+ }
@@ -234,6 +234,13 @@ export interface McpServerView {
234
234
  addedByName?: string;
235
235
  addedAt?: number;
236
236
  promotedFrom?: string;
237
+ /** `promotedFrom` as a display name, when the lookup answered. */
238
+ promotedFromName?: string;
239
+ /** A user row's owner (the id after `user:` in `scopeKey`) as a display name, when the lookup answered. */
240
+ ownerName?: string;
241
+ /** A higher tier holds this name, so runs use that entry and this one is idle (`mcpServersFor`'s
242
+ * `shadowedBy`, the winning scope key — `org`, or `channel:<id>`); absent when this entry serves. */
243
+ shadowedBy?: string;
237
244
  }
238
245
 
239
246
  export function serverView(