@coreplane/switchboard 1.260.3 → 1.260.5

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 (144) hide show
  1. package/dist/assets/deploy/cloudflare/package.json +2 -2
  2. package/dist/assets/deploy/cloudflare-docs/package.json +1 -1
  3. package/dist/assets/deploy/cloudflare-memory/package.json +2 -2
  4. package/dist/assets/deploy/cloudflare-memory/worker.ts +205 -8
  5. package/dist/assets/deploy/cloudflare-resident/Dockerfile +11 -2
  6. package/dist/assets/deploy/cloudflare-resident/package.json +2 -2
  7. package/dist/assets/deploy/cloudflare-resident/worker.ts +124 -40
  8. package/dist/assets/deploy/cloudflare-sandbox/package.json +2 -2
  9. package/dist/assets/package-lock.json +712 -97
  10. package/dist/assets/package.json +7 -6
  11. package/dist/assets/project.json +4 -0
  12. package/dist/assets/source.json +3 -3
  13. package/dist/assets/src/core/budgets.ts +5 -0
  14. package/dist/assets/src/core/coordinator/contract.ts +5 -0
  15. package/dist/assets/src/core/coordinator/driver.ts +118 -29
  16. package/dist/assets/src/core/pipelineStanding.ts +2 -0
  17. package/dist/assets/src/core/plane/decide.ts +19 -2
  18. package/dist/assets/src/core/provider.ts +380 -0
  19. package/dist/assets/src/core/refusal.ts +1 -0
  20. package/dist/assets/src/core/runEvents.ts +8 -0
  21. package/dist/assets/src/core/runFriction.ts +1 -1
  22. package/dist/assets/src/core/runLedger/types.ts +10 -1
  23. package/dist/assets/src/core/runRecord.ts +31 -0
  24. package/dist/assets/src/core/ship/contract.ts +3 -1
  25. package/dist/assets/src/core/ship/coordinator.ts +518 -56
  26. package/dist/assets/src/core/ship/renewal.ts +3 -1
  27. package/dist/assets/src/core/types.ts +6 -0
  28. package/dist/assets/src/execution/bashTimeout.ts +1 -1
  29. package/dist/assets/src/execution/residentRebind.ts +15 -0
  30. package/dist/assets/src/execution/residentReuse.ts +23 -2
  31. package/dist/assets/src/execution/sandboxErrors.ts +6 -6
  32. package/dist/assets/web/dist/.vite/manifest.json +505 -505
  33. package/dist/assets/web/dist/assets/{AppShell-CSXDCV4x.js → AppShell-DPJVhUGP.js} +1 -1
  34. package/dist/assets/web/dist/assets/{CostChart-DsuPS2g7.js → CostChart-4TeDdbvJ.js} +1 -1
  35. package/dist/assets/web/dist/assets/CostsPage-8HDgvuWB.js +1 -0
  36. package/dist/assets/web/dist/assets/{DeliveryPage-62qCSLvk.js → DeliveryPage-Cf1joUyA.js} +1 -1
  37. package/dist/assets/web/dist/assets/{HomePage-Dk3HRBc3.js → HomePage-DZEAnCCv.js} +1 -1
  38. package/dist/assets/web/dist/assets/{InputMenu-BdZXnibG.js → InputMenu-BRV35UWP.js} +1 -1
  39. package/dist/assets/web/dist/assets/{MetricsPage-o5fSaCTe.js → MetricsPage-BQBuIuYU.js} +1 -1
  40. package/dist/assets/web/dist/assets/{NotFoundPage-DHuWyjlq.js → NotFoundPage-CDjMfCcj.js} +1 -1
  41. package/dist/assets/web/dist/assets/{PendingTurnRow-CeTGOXUy.js → PendingTurnRow-4B8JwETU.js} +1 -1
  42. package/dist/assets/web/dist/assets/{PlanePage-RD0M6O6W.js → PlanePage-CHU2szQs.js} +1 -1
  43. package/dist/assets/web/dist/assets/{ResidentDetailPage-Dl2KId95.js → ResidentDetailPage-L5SRcN_O.js} +1 -1
  44. package/dist/assets/web/dist/assets/{ResidentsIndexPage-DBy43I1R.js → ResidentsIndexPage-BIFf8qyE.js} +1 -1
  45. package/dist/assets/web/dist/assets/RunFoldRow-DzPIshJx.js +1 -0
  46. package/dist/assets/web/dist/assets/{RunRoutePage-Du1HqLZs.js → RunRoutePage-Cru5IVux.js} +3 -3
  47. package/dist/assets/web/dist/assets/{RunsIndexPage-BZphy3Hr.js → RunsIndexPage-CBkdPAdj.js} +1 -1
  48. package/dist/assets/web/dist/assets/{RunsTabs-kHbbxVHx.js → RunsTabs-C7Ua3XX5.js} +1 -1
  49. package/dist/assets/web/dist/assets/{ScheduledPage-B7jh898t.js → ScheduledPage-D_R55BDk.js} +1 -1
  50. package/dist/assets/web/dist/assets/{SettingSelect-BfID5nF4.js → SettingSelect-CEeA4iuC.js} +1 -1
  51. package/dist/assets/web/dist/assets/{SettingsPage-3uLEsiiE.js → SettingsPage-DlmZvAvf.js} +1 -1
  52. package/dist/assets/web/dist/assets/{SilentTurn-CKxSxnQC.js → SilentTurn-PWMDMbMG.js} +1 -1
  53. package/dist/assets/web/dist/assets/{SlackMark-CtiHrNjr.js → SlackMark-DB0MGiF5.js} +1 -1
  54. package/dist/assets/web/dist/assets/{StatusDot-x7vcK0iE.js → StatusDot-Bq0mYMFo.js} +1 -1
  55. package/dist/assets/web/dist/assets/{Tooltip-wVXCWhMp.js → Tooltip-Bs6zd7yE.js} +1 -1
  56. package/dist/assets/web/dist/assets/{UnitRoutePage-V5BXK7FH.js → UnitRoutePage-BMGEZCvn.js} +1 -1
  57. package/dist/assets/web/dist/assets/{angular-html-C8b5YTHf.js → angular-html-BRRhsdQE.js} +1 -1
  58. package/dist/assets/web/dist/assets/{angular-ts-C3f4WYNe.js → angular-ts-BFj-mqCm.js} +1 -1
  59. package/dist/assets/web/dist/assets/{apl-DSHxUJc3.js → apl-CsBuy1IL.js} +1 -1
  60. package/dist/assets/web/dist/assets/{astro-BSGCCBP6.js → astro-cVaqxBZq.js} +1 -1
  61. package/dist/assets/web/dist/assets/{blade-DFeYrMKk.js → blade-MEP5mWha.js} +1 -1
  62. package/dist/assets/web/dist/assets/{browser-B6T5hjfR.js → browser-CCImen5O.js} +1 -1
  63. package/dist/assets/web/dist/assets/budgets-0szUBIMv.js +1 -0
  64. package/dist/assets/web/dist/assets/{c-L_QOnFVv.js → c-D4NGQ_Pd.js} +1 -1
  65. package/dist/assets/web/dist/assets/{chapel-DLQWUwhu.js → chapel-BkWImsIy.js} +1 -1
  66. package/dist/assets/web/dist/assets/{cobol-1DlMmrEO.js → cobol-RPDSPSET.js} +1 -1
  67. package/dist/assets/web/dist/assets/{coffee-BTB6os6N.js → coffee-DZ5z7oVX.js} +1 -1
  68. package/dist/assets/web/dist/assets/{cpp-BvJI2z3u.js → cpp-CrBtIaRZ.js} +1 -1
  69. package/dist/assets/web/dist/assets/{crystal-B11wGW_h.js → crystal-DJesYi6f.js} +1 -1
  70. package/dist/assets/web/dist/assets/{css-BiIWx5Pw.js → css-DO9mjau-.js} +1 -1
  71. package/dist/assets/web/dist/assets/{dist-C-XGnvM4.js → dist-EkKVbIM-.js} +12 -12
  72. package/dist/assets/web/dist/assets/{edge-nSglEXaD.js → edge-C3VzbSW0.js} +1 -1
  73. package/dist/assets/web/dist/assets/{elixir-x7o8qMl8.js → elixir-BkD_Vhfe.js} +1 -1
  74. package/dist/assets/web/dist/assets/{elm-DGrRyqTQ.js → elm-KIM0mFPk.js} +1 -1
  75. package/dist/assets/web/dist/assets/{erb--_dh58Nw.js → erb-D2ruCqb2.js} +1 -1
  76. package/dist/assets/web/dist/assets/{eventSource-Bj_dZf6M.js → eventSource-Cv0AFlMi.js} +1 -1
  77. package/dist/assets/web/dist/assets/{git-rebase-DbpUXwyg.js → git-rebase-CnfbQTxA.js} +1 -1
  78. package/dist/assets/web/dist/assets/{glimmer-js-BG0CuZG5.js → glimmer-js-CbiwFXWH.js} +1 -1
  79. package/dist/assets/web/dist/assets/{glimmer-ts-CQIl5qyR.js → glimmer-ts-DV8ii0Pv.js} +1 -1
  80. package/dist/assets/web/dist/assets/{glsl-9PJNQTcw.js → glsl-CgmznWkA.js} +1 -1
  81. package/dist/assets/web/dist/assets/{graphql-CzVTzjEI.js → graphql-BK-bBYfH.js} +1 -1
  82. package/dist/assets/web/dist/assets/{hack-BhCmAz3Q.js → hack-DnwTTP5n.js} +1 -1
  83. package/dist/assets/web/dist/assets/{haml-BmI12COR.js → haml-DAVJsWwT.js} +1 -1
  84. package/dist/assets/web/dist/assets/{handlebars-Cib-ISTS.js → handlebars-voOVfxFL.js} +1 -1
  85. package/dist/assets/web/dist/assets/{html-D3lDapvI.js → html-3FTqwaPk.js} +1 -1
  86. package/dist/assets/web/dist/assets/{html-derivative-DRPY8vOK.js → html-derivative-CSlhLDQn.js} +1 -1
  87. package/dist/assets/web/dist/assets/{http-B9VttK6m.js → http-Vx4ilPfS.js} +1 -1
  88. package/dist/assets/web/dist/assets/{hurl-C0saNJYj.js → hurl-DxyZDE3R.js} +1 -1
  89. package/dist/assets/web/dist/assets/{indexRow-Dxg7C8s9.js → indexRow-5s3hNzZ9.js} +1 -1
  90. package/dist/assets/web/dist/assets/{java-BCFbtYAj.js → java-nWqdjNJG.js} +1 -1
  91. package/dist/assets/web/dist/assets/{javascript-nwBjnrhh.js → javascript-Dr82HOwp.js} +1 -1
  92. package/dist/assets/web/dist/assets/{jinja-CMYO9byN.js → jinja-BhjLrPOt.js} +1 -1
  93. package/dist/assets/web/dist/assets/{jison-Cko8HnE_.js → jison-DilGIUa8.js} +1 -1
  94. package/dist/assets/web/dist/assets/{json-Dzv__YsX.js → json-CrOcvmhJ.js} +1 -1
  95. package/dist/assets/web/dist/assets/{jsx-CTFxagih.js → jsx-BW_RX-fo.js} +1 -1
  96. package/dist/assets/web/dist/assets/{julia-_Xvten1-.js → julia-CWH071DB.js} +1 -1
  97. package/dist/assets/web/dist/assets/{just-bLps4qOu.js → just-DkhhoWBy.js} +1 -1
  98. package/dist/assets/web/dist/assets/{latex-D6UaYsUP.js → latex-cuHJ4TUT.js} +1 -1
  99. package/dist/assets/web/dist/assets/{liquid-CoDIeULa.js → liquid-DG6afYQN.js} +1 -1
  100. package/dist/assets/web/dist/assets/{lua-D1KFRnVf.js → lua-C5CMnCPS.js} +1 -1
  101. package/dist/assets/web/dist/assets/{main-DAL--hyj.js → main-Disot0n_.js} +3 -3
  102. package/dist/assets/web/dist/assets/{marko-B5N5XcOw.js → marko-Dy2_DuC4.js} +1 -1
  103. package/dist/assets/web/dist/assets/{mdc-uxzjQO3q.js → mdc-BMKb_EeA.js} +1 -1
  104. package/dist/assets/web/dist/assets/{nginx-BMJxs2TK.js → nginx-og0RgMKf.js} +1 -1
  105. package/dist/assets/web/dist/assets/{nim-DqQ3-8p1.js → nim-CQxTQQP0.js} +1 -1
  106. package/dist/assets/web/dist/assets/{org-DsY6LK59.js → org-b5lkq_Pl.js} +1 -1
  107. package/dist/assets/web/dist/assets/{perl--pq9iJFp.js → perl-D9X5_i_J.js} +1 -1
  108. package/dist/assets/web/dist/assets/{php-RHCMbsM2.js → php-CwUxvLX0.js} +1 -1
  109. package/dist/assets/web/dist/assets/{pug-CFNq_FCp.js → pug-uOIOExr0.js} +1 -1
  110. package/dist/assets/web/dist/assets/{qml-C9Acx9E8.js → qml-g5RoADf-.js} +1 -1
  111. package/dist/assets/web/dist/assets/{r-DxlU8FBd.js → r-5LdAopD3.js} +1 -1
  112. package/dist/assets/web/dist/assets/{razor-CLJ2kKih.js → razor-DwrvFLYQ.js} +1 -1
  113. package/dist/assets/web/dist/assets/{regexp-CzmdGoEp.js → regexp-dGtAaxC-.js} +1 -1
  114. package/dist/assets/web/dist/assets/{rst-D9yuxBET.js → rst-wFdW0brr.js} +1 -1
  115. package/dist/assets/web/dist/assets/{ruby-CIRcEvnD.js → ruby-DPglN6I9.js} +1 -1
  116. package/dist/assets/web/dist/assets/{sas-LOl-uUPh.js → sas-CUbCs40d.js} +1 -1
  117. package/dist/assets/web/dist/assets/{scss-BX2_sZHY.js → scss-B-DUiI1c.js} +1 -1
  118. package/dist/assets/web/dist/assets/{shellscript-4d-QvKkY.js → shellscript-lGOV7Ram.js} +1 -1
  119. package/dist/assets/web/dist/assets/{shellsession-DhB16UGy.js → shellsession-BDWtAAjr.js} +1 -1
  120. package/dist/assets/web/dist/assets/{soy-Cm1xTUBr.js → soy-zod_dcOy.js} +1 -1
  121. package/dist/assets/web/dist/assets/{sql-BsfcBJdu.js → sql-BG0HSSoM.js} +1 -1
  122. package/dist/assets/web/dist/assets/{sseReplay-BqgOggyQ.js → sseReplay-9gcMpylx.js} +1 -1
  123. package/dist/assets/web/dist/assets/{stata-Dwat3BSw.js → stata-BVUMLp1W.js} +1 -1
  124. package/dist/assets/web/dist/assets/{surrealql-P4rtxzAW.js → surrealql-DiSUbHKF.js} +1 -1
  125. package/dist/assets/web/dist/assets/{svelte-DTcsi8S8.js → svelte-DZenT45Y.js} +1 -1
  126. package/dist/assets/web/dist/assets/{templ-D-kP0Tg5.js → templ-BWKRNM0-.js} +1 -1
  127. package/dist/assets/web/dist/assets/{tex-D-4vuedK.js → tex-CbMzBlkK.js} +1 -1
  128. package/dist/assets/web/dist/assets/{ts-tags-jEEWYxsu.js → ts-tags-CZsVorDN.js} +1 -1
  129. package/dist/assets/web/dist/assets/{tsx-CFnlrVzT.js → tsx-R1r3mH4W.js} +1 -1
  130. package/dist/assets/web/dist/assets/{twig-D5rGrPjs.js → twig-BjPMyjKH.js} +1 -1
  131. package/dist/assets/web/dist/assets/{typescript-nYIQ35or.js → typescript-Bx-66VN_.js} +1 -1
  132. package/dist/assets/web/dist/assets/{typst-3-UX8dna.js → typst-fW037Eo6.js} +1 -1
  133. package/dist/assets/web/dist/assets/{vue-CLIPIX03.js → vue-D1V_GNxW.js} +1 -1
  134. package/dist/assets/web/dist/assets/{vue-html-Ci9CpkHU.js → vue-html-C-4pTZ-s.js} +1 -1
  135. package/dist/assets/web/dist/assets/{vue-vine-DL5pEYYk.js → vue-vine-C-QsX3hR.js} +1 -1
  136. package/dist/assets/web/dist/assets/{wallClock-DmaC1sKA.js → wallClock-x0hjr0J_.js} +1 -1
  137. package/dist/assets/web/dist/assets/{xml-bO80qikC.js → xml-BJTJXPgC.js} +1 -1
  138. package/dist/assets/web/dist/assets/{xsl-BPze60Ej.js → xsl-Xc9lukqe.js} +1 -1
  139. package/dist/assets/web/dist/assets/{yaml-BQUSon_o.js → yaml-Dw-FMsQa.js} +1 -1
  140. package/dist/cli.js +1988 -546
  141. package/package.json +4 -4
  142. package/dist/assets/web/dist/assets/CostsPage-BaeWnm-o.js +0 -1
  143. package/dist/assets/web/dist/assets/RunFoldRow-CwHDL-Mo.js +0 -1
  144. package/dist/assets/web/dist/assets/budgets-KNNkT4PZ.js +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.260.3",
3
+ "version": "1.260.5",
4
4
  "private": true,
5
5
  "description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
6
6
  "license": "Apache-2.0",
@@ -39,9 +39,9 @@
39
39
  "cli": "tsx src/cli.ts",
40
40
  "verify": "npm run verify:root && npm run verify --workspaces --if-present && npm run check:site",
41
41
  "verify:root": "npm run check:consistency && npm run typecheck && npm run lint && npm run format:check && npm test && npm run check:dist",
42
- "check:consistency": "npm run check:lockfile && npm run check:sandbox-pair && npm run skills:check && npm run check:deps-drift && npm run licenses:check && npm run docs:check && npm run pr-title:check && npm run specs:check && npm run decisions:check && npm run hygiene:check && npm run vocabulary:check && npm run check:project-facts && npm run check:registry-drift && npm run agents:check && npm run clock:check && npm run screenshots:check",
42
+ "check:consistency": "npm run check:lockfile && npm run check:sandbox-pair && npm run skills:check && npm run check:deps-drift && npm run licenses:check && npm run docs:check && npm run pr-title:check && npm run specs:check && npm run decisions:check && npm run hygiene:check && npm run vocabulary:check && npm run user-message:check && npm run check:project-facts && npm run check:registry-drift && npm run agents:check && npm run clock:check && npm run screenshots:check",
43
43
  "ci:gate": "node scripts/ci-gate.mjs",
44
- "fix": "npm run docs:gen && npm run pr-title:gen && npm run agents:gen && npm run clock:gen && npm run hygiene:gen && npm run vocabulary:gen && npm run deploy:gen && npm run skills:sync && npm run lint:fix && npm run format",
44
+ "fix": "npm run docs:gen && npm run pr-title:gen && npm run agents:gen && npm run clock:gen && npm run hygiene:gen && npm run vocabulary:gen && npm run user-message:check -- --write && npm run deploy:gen && npm run skills:sync && npm run lint:fix && npm run format",
45
45
  "deploy:gen": "npm run --silent cli -- deploy init",
46
46
  "deploy:check": "npm run --silent cli -- deploy init --check",
47
47
  "check:lockfile": "node scripts/check-lockfile.mjs",
@@ -75,6 +75,7 @@
75
75
  "hygiene:gen": "node scripts/public-hygiene.mjs --write",
76
76
  "vocabulary:check": "node scripts/vocabulary-check.mjs",
77
77
  "vocabulary:gen": "node scripts/vocabulary-check.mjs --write",
78
+ "user-message:check": "node scripts/user-message-check.mjs",
78
79
  "docs:changed": "node scripts/docs-changed.mjs",
79
80
  "deploy:targets": "node scripts/deploy-targets.mjs",
80
81
  "docs:dev": "npm run dev -w docs --",
@@ -88,11 +89,11 @@
88
89
  "@earendil-works/pi-ai": "0.85.1",
89
90
  "@slack/bolt": "^5.1.0",
90
91
  "aws4fetch": "^1.0.20",
91
- "e2b": "^2.46.1",
92
+ "e2b": "^2.49.1",
92
93
  "mdast-util-from-markdown": "^2.0.3",
93
94
  "undici": "^8.10.2",
94
- "yaml": "^2.7.0",
95
- "zod": "^4.5.4"
95
+ "yaml": "^2.9.1",
96
+ "zod": "^4.6.5"
96
97
  },
97
98
  "devDependencies": {
98
99
  "@earendil-works/pi-coding-agent": "0.85.1",
@@ -183,6 +183,10 @@
183
183
  "does": "Records the remaining internal words; refuses growth unless `-- --force`.",
184
184
  "when": "Part of `fix`."
185
185
  },
186
+ "user-message:check": {
187
+ "does": "No user-facing statement delegates recovery.",
188
+ "when": "Part of `check:consistency`; the baseline only shrinks."
189
+ },
186
190
  "agents:gen": {
187
191
  "does": "Writes the Commands table in AGENTS.md from `package.json` and this file.",
188
192
  "when": "After adding or changing a script; part of `fix`."
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.260.3",
3
- "commit": "9f64917c9c2520b2797f3d5e6a73494879408e26",
4
- "builtAt": "2026-09-22T05:04:08.797Z"
2
+ "version": "1.260.5",
3
+ "commit": "3b665eb6f1bb239a78981014dae4bc88a50264a7",
4
+ "builtAt": "2026-09-22T23:49:01.800Z"
5
5
  }
@@ -150,6 +150,11 @@ export function intakeReceiptRetentionMs(catchUpWindowMs: number): number {
150
150
  * prompt one bounds the ratio the gate is judged by. */
151
151
  export const INTAKE_RECOVERY_WINDOW_MS = 10 * MINUTE_MS;
152
152
 
153
+ /** How long one process owns the right to post a stored intake-failure
154
+ * sentence. A rejected post releases immediately; this bound only recovers a
155
+ * process that died after the atomic claim and before it could finish it. */
156
+ export const INTAKE_DELIVERY_CLAIM_MS = 5 * MINUTE_MS;
157
+
153
158
  /** The presets that run the tool loop, and the one pipeline preset. */
154
159
  export const LOOP_PRESETS = [
155
160
  "general",
@@ -489,6 +489,10 @@ export interface CoordinatorUnit {
489
489
  * — no pre-check, no branch, no round 0. A task string's row only; written by
490
490
  * the hand-off, read by the driver into the machine's input. */
491
491
  resume?: { pr: number; headSha?: string; url?: string };
492
+ /** The decision-record number reserved at admission for this unit. A unit
493
+ * that writes a record carries it through every attempt and briefs its child
494
+ * as `record: NNNN`; the child never scans the directory for a number. */
495
+ record?: string;
492
496
  /** The head the unit's own coding child last pushed, recorded when the unit
493
497
  * ended `review_pending` (the wall clock capped after the pull request was
494
498
  * opened or updated): the next attempt's pre-check starts at the review
@@ -659,6 +663,7 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
659
663
  if (r.issue !== undefined && !isFinite(r.issue)) return false;
660
664
  if (r.pr !== undefined && !isPr(r.pr)) return false;
661
665
  if (r.resume !== undefined && !isResume(r.resume)) return false;
666
+ if (r.record !== undefined && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
662
667
  if (r.lastPush !== undefined && !isText(r.lastPush)) return false;
663
668
  if (r.segments !== undefined && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
664
669
  if (r.idle !== undefined && !isUnitIdle(r.idle)) return false;
@@ -53,6 +53,7 @@ import {
53
53
  type UnitStatus,
54
54
  renderUnitReport,
55
55
  reenterApprovedRebase,
56
+ reconcileTerminalPr,
56
57
  settleUnit,
57
58
  startUnit,
58
59
  type ChildFacts,
@@ -110,6 +111,7 @@ export type CoordinatorStepRoute =
110
111
  | "branch"
111
112
  | "spawn"
112
113
  | "read-record"
114
+ | "steer"
113
115
  | "pr-check"
114
116
  | "round"
115
117
  | "unit-end"
@@ -334,11 +336,25 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
334
336
  // The hard stop's mark, as the bot's answer carries it (record 0060; issue
335
337
  // 1924): a finished child's unit ends stopped on it.
336
338
  const stopped = a.body.stopped === true ? { stopped: true as const } : {};
339
+ const pullRequest = isRecord(a.body.pullRequest)
340
+ ? prCheckReturn(step, {
341
+ status: a.status,
342
+ body: { ok: true, ...a.body.pullRequest, at: a.body.at },
343
+ }).pr
344
+ : undefined;
337
345
  // The interrupted child restarted from its request (issue 1903): the bot
338
346
  // answers the live successor's id, and the machine keeps the wait on it.
339
347
  const restarted = typeof a.body.restartedAs === "string" ? { restartedAs: a.body.restartedAs } : {};
340
348
  if (!run.finished)
341
- return { type: "read-record", step, run: { finished: false }, ...stopped, ...restarted, at: a.body.at };
349
+ return {
350
+ type: "read-record",
351
+ step,
352
+ run: { finished: false },
353
+ ...(pullRequest !== undefined ? { pullRequest } : {}),
354
+ ...stopped,
355
+ ...restarted,
356
+ at: a.body.at,
357
+ };
342
358
  if (typeof run.status !== "string") throw new UnreadableAnswer("read-record", a, "status");
343
359
  // The typed artifacts as the bot's record carries them — shape-checked where
344
360
  // they were written (the run record's validator), read here as they are.
@@ -367,6 +383,7 @@ function readRecordReturn(step: string, a: BotAnswer): StepReturn {
367
383
  type: "read-record",
368
384
  step,
369
385
  ...stopped,
386
+ ...(pullRequest !== undefined ? { pullRequest } : {}),
370
387
  run: {
371
388
  finished: true,
372
389
  status: run.status as Extract<ChildFacts, { finished: true }>["status"],
@@ -435,8 +452,8 @@ function entryHumanGate(v: unknown):
435
452
  };
436
453
  }
437
454
 
438
- function prCheckReturn(step: string, a: BotAnswer): StepReturn {
439
- const { ok, state, prNumber, url, headSha, sha, mergedAt, at } = a.body;
455
+ function prCheckReturn(step: string, a: BotAnswer): Extract<StepReturn, { type: "pr-check" }> {
456
+ const { ok, state, prNumber, url, headSha, sha, mergedAt, mergedBy, closedBy, at } = a.body;
440
457
  if (ok === true && state === "none") {
441
458
  const { unrecovered, aheadOfBase, prClosed } = a.body;
442
459
  return {
@@ -464,6 +481,7 @@ function prCheckReturn(step: string, a: BotAnswer): StepReturn {
464
481
  prNumber,
465
482
  url,
466
483
  ...(typeof headSha === "string" ? { headSha } : {}),
484
+ ...(typeof a.body.headBranchExists === "boolean" ? { headBranchExists: a.body.headBranchExists } : {}),
467
485
  // The entry facts (issue 1689): the branch's own tip and whether the
468
486
  // bot's approval stands at the head, read at the unit-start's pre-check
469
487
  // so a re-issued plan resumes at review or at the merge decision.
@@ -494,10 +512,36 @@ function prCheckReturn(step: string, a: BotAnswer): StepReturn {
494
512
  typeof sha === "string" &&
495
513
  typeof mergedAt === "string"
496
514
  )
497
- return { type: "pr-check", step, pr: { state: "merged", prNumber, url, sha, mergedAt }, at };
515
+ return {
516
+ type: "pr-check",
517
+ step,
518
+ pr: {
519
+ state: "merged",
520
+ prNumber,
521
+ url,
522
+ sha,
523
+ mergedAt,
524
+ ...(typeof mergedBy === "string" ? { mergedBy } : {}),
525
+ },
526
+ at,
527
+ };
528
+ if (
529
+ ok === true &&
530
+ state === "closed" &&
531
+ typeof prNumber === "number" &&
532
+ typeof url === "string" &&
533
+ typeof closedBy === "string"
534
+ )
535
+ return { type: "pr-check", step, pr: { state: "closed", prNumber, url, closedBy }, at };
498
536
  throw new UnreadableAnswer("pr-check", a, "state");
499
537
  }
500
538
 
539
+ function steerReturn(step: string, a: BotAnswer): Extract<StepReturn, { type: "steer" }> {
540
+ if (a.body.ok === true && (a.body.outcome === "steered" || a.body.outcome === "not_live"))
541
+ return { type: "steer", step, outcome: a.body.outcome, at: a.body.at };
542
+ throw new UnreadableAnswer("steer", a, "outcome");
543
+ }
544
+
501
545
  /** The round's checks read as the bot answered it (record 0055): the runs at
502
546
  * the reviewed head, or none when GitHub could not be read — the machine
503
547
  * treats an absent read as pending and asks again at the chunk's end. A retry
@@ -510,6 +554,14 @@ function checksReturn(step: string, a: BotAnswer): StepReturn {
510
554
  type: "checks",
511
555
  step,
512
556
  ...(isRoundChecks(checks) ? { checks } : {}),
557
+ ...(isRecord(a.body.pullRequest)
558
+ ? {
559
+ pullRequest: prCheckReturn(step, {
560
+ status: a.status,
561
+ body: { ok: true, ...a.body.pullRequest, at },
562
+ }).pr,
563
+ }
564
+ : {}),
513
565
  // The pull request is a draft (issue 2063): the machine's table holds the
514
566
  // unit for the ready event instead of merging or ending without a cause.
515
567
  ...(draft === true ? { draft: true } : {}),
@@ -531,11 +583,31 @@ const isRoundChecks = (v: unknown): v is RoundChecks =>
531
583
  (v.expected === undefined || (Array.isArray(v.expected) && v.expected.every((n: unknown) => typeof n === "string")));
532
584
 
533
585
  function mergeReturn(step: string, a: BotAnswer): StepReturn {
534
- const { ok, outcome, by, sha, mergedAt, reason, at } = a.body;
586
+ const { ok, outcome, by, sha, mergedAt, mergedBy, reason, at } = a.body;
535
587
  // The door found the pull request already merged after the approval: the
536
588
  // merge commit and the time ride the answer, and the unit ends `by: other`.
537
589
  if (ok === true && outcome === "merged" && by === "other" && typeof sha === "string" && typeof mergedAt === "string")
538
- return { type: "merge", step, outcome: "merged", by: "other", sha, mergedAt, at };
590
+ return {
591
+ type: "merge",
592
+ step,
593
+ outcome: "merged",
594
+ by: "other",
595
+ sha,
596
+ mergedAt,
597
+ ...(typeof mergedBy === "string" ? { mergedBy } : {}),
598
+ at,
599
+ };
600
+ if (ok === true && outcome === "recheck" && isRecord(a.body.pullRequest))
601
+ return {
602
+ type: "merge",
603
+ step,
604
+ outcome: "recheck",
605
+ pullRequest: prCheckReturn(step, {
606
+ status: a.status,
607
+ body: { ok: true, ...a.body.pullRequest, at },
608
+ }).pr,
609
+ at,
610
+ };
539
611
  if (ok === true && outcome === "merged" && typeof sha === "string")
540
612
  return { type: "merge", step, outcome: "merged", sha, at };
541
613
  if (
@@ -678,6 +750,16 @@ async function perform(
678
750
  await step.do(action.step, STEP_CONFIG, () => call(bot, "read-record", { ...tag, runId: action.runId })),
679
751
  ),
680
752
  );
753
+ case "steer":
754
+ return steerReturn(
755
+ action.step,
756
+ answerOf(
757
+ "steer",
758
+ await step.do(action.step, STEP_CONFIG, () =>
759
+ call(bot, "steer", { ...tag, runId: action.runId, reason: action.reason }),
760
+ ),
761
+ ),
762
+ );
681
763
  case "pr-check":
682
764
  // `recover` rides only after a dead coding child: the bot opens the pull
683
765
  // request from the pushed branch itself instead of answering `none`.
@@ -806,7 +888,7 @@ async function tellStepThrew(
806
888
  : `in round ${at.round.index} (\`${at.step}\`)`;
807
889
  const report =
808
890
  `⚠️ The runner failed ${where}: ${line}\n\n` +
809
- "Re-issue `agent:ship` in this thread to continue — a pull request already approved with green checks resumes at the checks step, never at a fresh coding round.";
891
+ "The unit remains bound to this thread; the next reply continues it, and a pull request already approved with green checks resumes at the checks step, never at a fresh coding round.";
810
892
  const body = {
811
893
  ...tag,
812
894
  ending: {
@@ -939,11 +1021,17 @@ async function runUnit(
939
1021
  factsStep,
940
1022
  answerOf(
941
1023
  "pr-check",
942
- await step.do(factsStep, STEP_CONFIG, () => call(bot, "pr-check", { ...tag, checks: true })),
1024
+ await step.do(factsStep, STEP_CONFIG, () =>
1025
+ call(bot, "pr-check", {
1026
+ ...tag,
1027
+ checks: true,
1028
+ ...(state.pr !== undefined ? { pr: state.pr.number } : {}),
1029
+ }),
1030
+ ),
943
1031
  ),
944
1032
  );
945
- if (check.type === "pr-check" && check.pr.state === "merged")
946
- endFacts = { merged: { sha: check.pr.sha, mergedAt: check.pr.mergedAt } };
1033
+ if (check.type === "pr-check" && (check.pr.state === "merged" || check.pr.state === "closed"))
1034
+ state = reconcileTerminalPr(state, check.pr);
947
1035
  else if (check.type === "pr-check" && check.pr.state === "open")
948
1036
  endFacts = {
949
1037
  ...(check.pr.autoMergeEnabled !== undefined ? { autoMergeEnabled: check.pr.autoMergeEnabled } : {}),
@@ -973,6 +1061,7 @@ async function runUnit(
973
1061
  state = reenterApprovedRebase(state);
974
1062
  continue pipeline;
975
1063
  }
1064
+ const ending = state.ending ?? note.ending;
976
1065
  // The last coding child's run is named so the bot can put its handoff
977
1066
  // — the deviations it recorded — on the unit's board issue beside the
978
1067
  // ending (agent-ship item 14).
@@ -981,20 +1070,20 @@ async function runUnit(
981
1070
  // Two copies (routing-and-config item 28): the full report for the
982
1071
  // row and the board, and the thread's at the request's verbosity.
983
1072
  ending: {
984
- kind: note.ending.kind,
1073
+ kind: ending.kind,
985
1074
  report: renderUnitReport(state, endFacts),
986
1075
  threadReport: renderUnitReport(state, endFacts, state.input.verbosity ?? DEFAULT_VERBOSITY),
987
1076
  // An idle ending carries its continuation facts (record 0051): the
988
1077
  // bot writes them on the row's `idle` in place of an ending, with
989
1078
  // the coding run id it already receives below.
990
- ...(note.ending.kind === "idle"
1079
+ ...(ending.kind === "idle"
991
1080
  ? {
992
- why: note.ending.why,
993
- renewalsLeft: note.ending.renewalsLeft,
994
- ...(note.ending.from !== undefined ? { from: note.ending.from } : {}),
995
- spendUsd: note.ending.spendUsd,
996
- ...(note.ending.handoff !== undefined ? { handoff: note.ending.handoff } : {}),
997
- ...(note.ending.humanGate !== undefined ? { humanGate: note.ending.humanGate } : {}),
1081
+ why: ending.why,
1082
+ renewalsLeft: ending.renewalsLeft,
1083
+ ...(ending.from !== undefined ? { from: ending.from } : {}),
1084
+ spendUsd: ending.spendUsd,
1085
+ ...(ending.handoff !== undefined ? { handoff: ending.handoff } : {}),
1086
+ ...(ending.humanGate !== undefined ? { humanGate: ending.humanGate } : {}),
998
1087
  }
999
1088
  : {}),
1000
1089
  },
@@ -1002,21 +1091,21 @@ async function runUnit(
1002
1091
  // A review_pending ending names the child's own last push so the next
1003
1092
  // attempt's pre-check can start at the review round (the row's lastPush)
1004
1093
  // — an idled one the same, off the idle's `from` (record 0051).
1005
- ...(note.ending.kind === "review_pending" && note.ending.headSha !== undefined
1006
- ? { headSha: note.ending.headSha }
1007
- : note.ending.kind === "idle" && note.ending.why === "review_pending" && note.ending.from !== undefined
1008
- ? { headSha: note.ending.from }
1094
+ ...(ending.kind === "review_pending" && ending.headSha !== undefined
1095
+ ? { headSha: ending.headSha }
1096
+ : ending.kind === "idle" && ending.why === "review_pending" && ending.from !== undefined
1097
+ ? { headSha: ending.from }
1009
1098
  : {}),
1010
1099
  ...(state.lastCodingRunId !== undefined ? { codingRunId: state.lastCodingRunId } : {}),
1011
1100
  // A continued ending is a segment's end, not the unit's: the bot
1012
1101
  // writes the renewal as a row keyed by the next segment's index
1013
1102
  // (decision 0046), so a runner reclaimed here never renews twice.
1014
- ...(note.ending.kind === "continued"
1103
+ ...(ending.kind === "continued"
1015
1104
  ? {
1016
1105
  segment: {
1017
- index: note.ending.segment,
1018
- ...(note.ending.from !== undefined ? { from: note.ending.from } : {}),
1019
- runId: note.ending.runId,
1106
+ index: ending.segment,
1107
+ ...(ending.from !== undefined ? { from: ending.from } : {}),
1108
+ runId: ending.runId,
1020
1109
  },
1021
1110
  }
1022
1111
  : {}),
@@ -1041,14 +1130,14 @@ async function runUnit(
1041
1130
 
1042
1131
  /** The report of a unit the hard stop ended before it ran (record 0060; issue 1924). */
1043
1132
  function stoppedReport(unit: string): string {
1044
- return `⏹ Stopped: the pipeline's hosted parent was hard-stopped, so ${unit} was ended without running. Re-issue the plan naming the remaining units to run them.`;
1133
+ return `⏹ Stopped: the pipeline's hosted parent was hard-stopped, so ${unit} was ended without running. The original plan remains the durable task for any later pipeline.`;
1045
1134
  }
1046
1135
 
1047
1136
  function blockedReport(unit: string, dep: string, depEnding: string): string {
1048
1137
  if (depEnding === "blocked")
1049
- return `⛔ Blocked: ${unit} waits on ${dep}, which is blocked itself. Re-issue the plan naming the remaining units once it is resolved.`;
1138
+ return `⛔ Blocked: ${unit} waits on ${dep}, which is blocked itself. A later pipeline recognizes both units once the dependency is resolved.`;
1050
1139
  const person = depEnding === "merge_ready";
1051
- return `⛔ Blocked: ${unit} waits on ${dep}, which ended ${depEnding}${person ? " — a person's merge" : ""}. Re-issue the plan naming the remaining units once it is ${person ? "merged" : "resolved"}.`;
1140
+ return `⛔ Blocked: ${unit} waits on ${dep}, which ended ${depEnding}${person ? " — a person's merge" : ""}. A later pipeline recognizes the dependency once it is ${person ? "merged" : "resolved"}.`;
1052
1141
  }
1053
1142
 
1054
1143
  /** End a unit that never entered `runUnit` under the same failure net. */
@@ -116,6 +116,7 @@ export const ROUND_STAGE = {
116
116
  * issue 2100) — closes the unit the same way. */
117
117
  export const ENDING_STAGE = {
118
118
  merged: "merged",
119
+ closed: "ended",
119
120
  already_landed: "merged",
120
121
  merge_ready: "merge-ready",
121
122
  held: "ended",
@@ -144,6 +145,7 @@ export const ENDING_STAGE = {
144
145
  * 2100), so a new ending kind fails the build here. */
145
146
  export const ENDING_WORDS = {
146
147
  merged: "merged",
148
+ closed: "closed",
147
149
  already_landed: "merged",
148
150
  merge_ready: "merge-ready",
149
151
  held: "held",
@@ -10,6 +10,7 @@
10
10
  // literal where `clock:check` expects it.)
11
11
 
12
12
  import { PLANE, minutesToMs } from "../budgets.js";
13
+ import type { ProviderFailureCause } from "../provider.js";
13
14
  import type { PlaneFinding } from "./findings.js";
14
15
 
15
16
  // ---- endings and their causes (record 0064, "Endings and the watches") ------------------------
@@ -139,6 +140,9 @@ export interface PlaneLevelRow {
139
140
  side: "below" | "above";
140
141
  reportedAt: number;
141
142
  generation: string;
143
+ /** A provider row's typed down cause; absent on resident rows, provider-up,
144
+ * and reports from an older bot generation. */
145
+ cause?: ProviderFailureCause;
142
146
  }
143
147
 
144
148
  /** The side a resident's level reads for an observer that knows the current
@@ -300,7 +304,13 @@ export type PlaneEvent =
300
304
  /** A provider's level as the model proxy reported it: `up` on a relayed
301
305
  * success, `down` on a failure past its one retry. `up` re-issues every
302
306
  * turn held parked on the provider, once each. */
303
- | { kind: "provider_level"; at: number; provider: string; level: "up" | "down" }
307
+ | {
308
+ kind: "provider_level";
309
+ at: number;
310
+ provider: string;
311
+ level: "up" | "down";
312
+ cause?: ProviderFailureCause;
313
+ }
304
314
  /** A run parked on its provider (record 0064): the harness holds the turn,
305
315
  * the lease keeps counting, and the provider's next `up` steers it once. */
306
316
  | { kind: "park"; at: number; runId: string; provider: string }
@@ -692,7 +702,13 @@ function onHeartbeat(
692
702
  * then walks the queue for anything waiting on `provider_up`. */
693
703
  function onProviderLevel(
694
704
  state: PlaneState,
695
- event: { kind: "provider_level"; at: number; provider: string; level: "up" | "down" },
705
+ event: {
706
+ kind: "provider_level";
707
+ at: number;
708
+ provider: string;
709
+ level: "up" | "down";
710
+ cause?: ProviderFailureCause;
711
+ },
696
712
  ): PlaneDecision {
697
713
  const row: PlaneLevelRow = {
698
714
  resident: event.provider,
@@ -700,6 +716,7 @@ function onProviderLevel(
700
716
  side: event.level === "up" ? "below" : "above",
701
717
  reportedAt: event.at,
702
718
  generation: "",
719
+ ...(event.level === "down" && event.cause !== undefined ? { cause: event.cause } : {}),
703
720
  };
704
721
  const levels = [...state.levels.filter((l) => !(l.resident === event.provider && l.name === "provider")), row];
705
722
  const writes: PlaneWrite[] = [{ table: "plane_levels", op: "put", row }];