@coreplane/switchboard 1.246.0 → 1.247.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 (144) hide show
  1. package/dist/assets/config/config.example.yaml +1 -1
  2. package/dist/assets/deploy/cloudflare-memory/worker.ts +14 -4
  3. package/dist/assets/deploy/cloudflare-resident/worker.ts +1 -1
  4. package/dist/assets/package-lock.json +3 -3
  5. package/dist/assets/package.json +5 -3
  6. package/dist/assets/project.json +10 -2
  7. package/dist/assets/source.json +3 -3
  8. package/dist/assets/src/agents/registry.ts +1 -1
  9. package/dist/assets/src/core/budgets.ts +19 -7
  10. package/dist/assets/src/core/runEvents.ts +5 -1
  11. package/dist/assets/src/core/ship/contract.ts +7 -0
  12. package/dist/assets/src/core/ship/coordinator.ts +1 -1
  13. package/dist/assets/src/core/trace/attrs.ts +9 -0
  14. package/dist/assets/web/dist/.vite/manifest.json +474 -485
  15. package/dist/assets/web/dist/assets/AppShell-DAtHiuI6.js +1 -0
  16. package/dist/assets/web/dist/assets/CostsPage-BoKzYa4B.js +2 -0
  17. package/dist/assets/web/dist/assets/DeliveryPage-NP4g6bQd.js +1 -0
  18. package/dist/assets/web/dist/assets/HomePage-DYxC0izY.js +2 -0
  19. package/dist/assets/web/dist/assets/InputMenu-C6aPpe30.js +1 -0
  20. package/dist/assets/web/dist/assets/NotFoundPage-DtE-GgTk.js +1 -0
  21. package/dist/assets/web/dist/assets/PendingTurnRow-BuRre8it.js +1 -0
  22. package/dist/assets/web/dist/assets/ResidentDetailPage-DLIpWYOc.js +1 -0
  23. package/dist/assets/web/dist/assets/ResidentsIndexPage-6LipuDjR.js +1 -0
  24. package/dist/assets/web/dist/assets/RunFoldRow-V-iSy64e.js +1 -0
  25. package/dist/assets/web/dist/assets/RunRoutePage-DUalB1u2.js +9 -0
  26. package/dist/assets/web/dist/assets/RunsIndexPage-B9Ba1KdD.js +1 -0
  27. package/dist/assets/web/dist/assets/RunsTabs-YSbUu5py.js +1 -0
  28. package/dist/assets/web/dist/assets/ScheduledPage-KdjLtD_7.js +1 -0
  29. package/dist/assets/web/dist/assets/SettingSelect-BIzAsLk1.js +1 -0
  30. package/dist/assets/web/dist/assets/SettingsPage-IT5l_NaL.js +1 -0
  31. package/dist/assets/web/dist/assets/StatusDot-DBHAl4Il.js +1 -0
  32. package/dist/assets/web/dist/assets/Tooltip-_LEjptLV.js +1 -0
  33. package/dist/assets/web/dist/assets/UnitRoutePage-DPBsvGPR.js +1 -0
  34. package/dist/assets/web/dist/assets/{angular-html-D_3slZqf.js → angular-html-BjeQZdCq.js} +1 -1
  35. package/dist/assets/web/dist/assets/{angular-ts-D1Fv2cpN.js → angular-ts-ZGdORrL2.js} +1 -1
  36. package/dist/assets/web/dist/assets/{apl-60Nm_trN.js → apl-DhvV_X93.js} +1 -1
  37. package/dist/assets/web/dist/assets/{astro-CvyaM3Q9.js → astro-DiRSE4Ug.js} +1 -1
  38. package/dist/assets/web/dist/assets/{blade-yan3VBls.js → blade-D8Fr5Wvq.js} +1 -1
  39. package/dist/assets/web/dist/assets/{seed-BhxEVasY.js → browser-B6T5hjfR.js} +1 -1
  40. package/dist/assets/web/dist/assets/{c-Ddgepap2.js → c-BwbC64D4.js} +1 -1
  41. package/dist/assets/web/dist/assets/{chapel-CgNY6dxC.js → chapel-DgmqRK97.js} +1 -1
  42. package/dist/assets/web/dist/assets/{cobol-C7q82cdP.js → cobol-DJod2RNK.js} +1 -1
  43. package/dist/assets/web/dist/assets/{coffee-DgRMF_ni.js → coffee-D1Y2CgBY.js} +1 -1
  44. package/dist/assets/web/dist/assets/{cpp-B8Lf-aeo.js → cpp-CsO97YOM.js} +1 -1
  45. package/dist/assets/web/dist/assets/{crystal-CkA_dhEO.js → crystal-CJ7m2tOc.js} +1 -1
  46. package/dist/assets/web/dist/assets/{css-Cyu-C_oA.js → css-B2M-NKoj.js} +1 -1
  47. package/dist/assets/web/dist/assets/{dist-DS-gMjLZ.js → dist-BcYPGOBL.js} +2 -2
  48. package/dist/assets/web/dist/assets/durationTone-U_rOqo6r.js +1 -0
  49. package/dist/assets/web/dist/assets/{edge-BxV3rKNI.js → edge-CFVW-m9B.js} +1 -1
  50. package/dist/assets/web/dist/assets/{elixir-pNSEK39d.js → elixir-B2mtRgCr.js} +1 -1
  51. package/dist/assets/web/dist/assets/{elm-zgwWE33W.js → elm-2teVAwCi.js} +1 -1
  52. package/dist/assets/web/dist/assets/{erb-yzcYAHp_.js → erb-LAbeNqen.js} +1 -1
  53. package/dist/assets/web/dist/assets/eventSource-Bj_dZf6M.js +1 -0
  54. package/dist/assets/web/dist/assets/{git-rebase-Dg7Ous1-.js → git-rebase-DnhzH_sp.js} +1 -1
  55. package/dist/assets/web/dist/assets/{glimmer-js-CbZEwX27.js → glimmer-js-DPKjXf1-.js} +1 -1
  56. package/dist/assets/web/dist/assets/{glimmer-ts-D3NIqALy.js → glimmer-ts-CABXnt5z.js} +1 -1
  57. package/dist/assets/web/dist/assets/{glsl-y4-3WrI2.js → glsl-Dz4-3-gJ.js} +1 -1
  58. package/dist/assets/web/dist/assets/{graphql-D5d0al7K.js → graphql-ByWUD1DL.js} +1 -1
  59. package/dist/assets/web/dist/assets/{hack-D8fD6kEl.js → hack-CdmOK2K-.js} +1 -1
  60. package/dist/assets/web/dist/assets/{haml-BWeFFc-V.js → haml-DzegMpd2.js} +1 -1
  61. package/dist/assets/web/dist/assets/{handlebars-gIm5FeE_.js → handlebars-B5NeiP0e.js} +1 -1
  62. package/dist/assets/web/dist/assets/{html-fXJTVLSM.js → html-Zg_NHLv9.js} +1 -1
  63. package/dist/assets/web/dist/assets/{html-derivative-B3ySSBrQ.js → html-derivative-DZf9JNzh.js} +1 -1
  64. package/dist/assets/web/dist/assets/{http-DrJfQUmw.js → http-CVFrQZeN.js} +1 -1
  65. package/dist/assets/web/dist/assets/{hurl-Bvbnsf55.js → hurl-L5brWAJe.js} +1 -1
  66. package/dist/assets/web/dist/assets/{java-CUSpb-i-.js → java-hFphyZ4R.js} +1 -1
  67. package/dist/assets/web/dist/assets/{javascript-BWRH5fbY.js → javascript-CAJO1WKX.js} +1 -1
  68. package/dist/assets/web/dist/assets/{jinja-D3Ymci8y.js → jinja-N4A4srXI.js} +1 -1
  69. package/dist/assets/web/dist/assets/{jison-Gt9SO7ZC.js → jison-D3BZa_iU.js} +1 -1
  70. package/dist/assets/web/dist/assets/{json-Cfcblhgn.js → json-DxD1Qh8W.js} +1 -1
  71. package/dist/assets/web/dist/assets/{jsx-C0366fFM.js → jsx-CHhag69S.js} +1 -1
  72. package/dist/assets/web/dist/assets/{julia-CyBOTbxS.js → julia-CrhkZ6Tt.js} +1 -1
  73. package/dist/assets/web/dist/assets/{just-FPXZcIOd.js → just-D0LFOuhq.js} +1 -1
  74. package/dist/assets/web/dist/assets/{latex-Bd-c88v0.js → latex-BHAlwCXK.js} +1 -1
  75. package/dist/assets/web/dist/assets/{liquid-5tu9Hc0k.js → liquid-CkaLfCWh.js} +1 -1
  76. package/dist/assets/web/dist/assets/{lua-BZ5fdPP8.js → lua-B1h4aAzP.js} +1 -1
  77. package/dist/assets/web/dist/assets/main-DUfSE0dj.js +28 -0
  78. package/dist/assets/web/dist/assets/main-zbP_dTjR.css +1 -0
  79. package/dist/assets/web/dist/assets/{marko-C_mmj4p0.js → marko-D6CG4hvX.js} +1 -1
  80. package/dist/assets/web/dist/assets/{mdc-CtaNqWjY.js → mdc-CVQzimAU.js} +1 -1
  81. package/dist/assets/web/dist/assets/{nginx-Ddm5Mb8J.js → nginx-h-9Ir54h.js} +1 -1
  82. package/dist/assets/web/dist/assets/{nim-COhfroYB.js → nim-DHVI30sp.js} +1 -1
  83. package/dist/assets/web/dist/assets/{org-CCHFgmTb.js → org-AasAXfMF.js} +1 -1
  84. package/dist/assets/web/dist/assets/{perl-CXzOE-Z-.js → perl-BVi5aTEy.js} +1 -1
  85. package/dist/assets/web/dist/assets/{php-1V6jAIhU.js → php-qOkCeUKr.js} +1 -1
  86. package/dist/assets/web/dist/assets/{pug-uS37efts.js → pug-8TnUGvEX.js} +1 -1
  87. package/dist/assets/web/dist/assets/{qml-Bj1A0Ben.js → qml-CpJbhoxn.js} +1 -1
  88. package/dist/assets/web/dist/assets/{r-CMbKbLsS.js → r-JkUNcAJW.js} +1 -1
  89. package/dist/assets/web/dist/assets/{razor-DVRq_Ndk.js → razor-CtU4ws5a.js} +1 -1
  90. package/dist/assets/web/dist/assets/{regexp-AbaOr4oB.js → regexp-J2zA6ceN.js} +1 -1
  91. package/dist/assets/web/dist/assets/{rst-Dq_QWgOF.js → rst-CMCvf-yZ.js} +1 -1
  92. package/dist/assets/web/dist/assets/{ruby-CVSsAj7T.js → ruby-ChLBY_iR.js} +1 -1
  93. package/dist/assets/web/dist/assets/{sas-C-dGfxUi.js → sas-cwn4x2VY.js} +1 -1
  94. package/dist/assets/web/dist/assets/{scss-DxALxYPA.js → scss-jSrJlzLI.js} +1 -1
  95. package/dist/assets/web/dist/assets/{shellscript--qXcvQ5L.js → shellscript-D5nAd0gs.js} +1 -1
  96. package/dist/assets/web/dist/assets/{shellsession-DeqnW8Ux.js → shellsession-BZY4-Tdh.js} +1 -1
  97. package/dist/assets/web/dist/assets/{soy-DreoUIMt.js → soy-COppPOKr.js} +1 -1
  98. package/dist/assets/web/dist/assets/{sql-o3i2VTG6.js → sql-Blc_6dOA.js} +1 -1
  99. package/dist/assets/web/dist/assets/{sseReplay-qF-gpbAY.js → sseReplay-DXC7kGbN.js} +3 -3
  100. package/dist/assets/web/dist/assets/{stata-CGAT41H7.js → stata-DRv3OY-h.js} +1 -1
  101. package/dist/assets/web/dist/assets/{surrealql-CBU0QuGV.js → surrealql-qMv_eeJ6.js} +1 -1
  102. package/dist/assets/web/dist/assets/{svelte-CfHaLZiG.js → svelte-d1Edb6Rz.js} +1 -1
  103. package/dist/assets/web/dist/assets/{templ-D02MUcma.js → templ-CUMSIOXG.js} +1 -1
  104. package/dist/assets/web/dist/assets/{tex-DjprHYXl.js → tex-BGZQA-Ei.js} +1 -1
  105. package/dist/assets/web/dist/assets/{ts-tags-DftDOzP-.js → ts-tags-Z9ulxVsq.js} +1 -1
  106. package/dist/assets/web/dist/assets/{tsx-BOdvPohJ.js → tsx-oI4bI3Vf.js} +1 -1
  107. package/dist/assets/web/dist/assets/{twig-B_yLS56B.js → twig-D_RXwBIX.js} +1 -1
  108. package/dist/assets/web/dist/assets/{typescript-CdPCa4nU.js → typescript-BgqQDYoN.js} +1 -1
  109. package/dist/assets/web/dist/assets/{typst-C1oIFsUl.js → typst-5MW-KRDd.js} +1 -1
  110. package/dist/assets/web/dist/assets/{vue-CcszJEX8.js → vue-CZhHHsPm.js} +1 -1
  111. package/dist/assets/web/dist/assets/{vue-html-DcpxPcuT.js → vue-html-DlYy2d8v.js} +1 -1
  112. package/dist/assets/web/dist/assets/{vue-vine-D26bIKGj.js → vue-vine-CJDnx-qn.js} +1 -1
  113. package/dist/assets/web/dist/assets/wallClock-DmaC1sKA.js +1 -0
  114. package/dist/assets/web/dist/assets/{xml-D8zl6W5P.js → xml-BRBDk65Q.js} +1 -1
  115. package/dist/assets/web/dist/assets/{xsl-D86Bii28.js → xsl-BlCykPxo.js} +1 -1
  116. package/dist/assets/web/dist/assets/{yaml-g6Px4KFU.js → yaml-BqW48szf.js} +1 -1
  117. package/dist/cli.js +1120 -672
  118. package/package.json +1 -1
  119. package/dist/assets/web/dist/assets/AppShell-B4EUiSwP.js +0 -1
  120. package/dist/assets/web/dist/assets/CostsPage-D8VjAtuH.js +0 -2
  121. package/dist/assets/web/dist/assets/DeliveryPage-DpQi5XUI.js +0 -1
  122. package/dist/assets/web/dist/assets/HomePage-CvKuIGqg.js +0 -2
  123. package/dist/assets/web/dist/assets/InputMenu-DmqAxkn4.js +0 -1
  124. package/dist/assets/web/dist/assets/NotFoundPage-DDogyiXA.js +0 -1
  125. package/dist/assets/web/dist/assets/PendingTurnRow-BoSLlR6j.js +0 -1
  126. package/dist/assets/web/dist/assets/ResidentDetailPage-CiBdbzkE.js +0 -1
  127. package/dist/assets/web/dist/assets/ResidentsIndexPage-DgtHtd9K.js +0 -1
  128. package/dist/assets/web/dist/assets/RunFoldRow-IF4xbz-3.js +0 -1
  129. package/dist/assets/web/dist/assets/RunRoutePage-DHYa7rHK.js +0 -9
  130. package/dist/assets/web/dist/assets/RunsIndexPage-aWhNTv8r.js +0 -1
  131. package/dist/assets/web/dist/assets/RunsTabs-CY_tdGTU.js +0 -1
  132. package/dist/assets/web/dist/assets/ScheduledPage-gPEBZjcx.js +0 -1
  133. package/dist/assets/web/dist/assets/SettingSelect-VDanrTZi.js +0 -1
  134. package/dist/assets/web/dist/assets/SettingsPage-CFoltSRD.js +0 -1
  135. package/dist/assets/web/dist/assets/StatusDot-DA3eTVrT.js +0 -1
  136. package/dist/assets/web/dist/assets/Tooltip-BgbCkM12.js +0 -1
  137. package/dist/assets/web/dist/assets/UnitRoutePage-DibegaJJ.js +0 -1
  138. package/dist/assets/web/dist/assets/durationTone-l6aVrHb8.js +0 -1
  139. package/dist/assets/web/dist/assets/eventSource-Ce94ho6M.js +0 -1
  140. package/dist/assets/web/dist/assets/favicon-D1hkyC5j.js +0 -1
  141. package/dist/assets/web/dist/assets/main-B4A6x82d.js +0 -28
  142. package/dist/assets/web/dist/assets/main-CpkwzHX6.css +0 -1
  143. package/dist/assets/web/dist/assets/residentsModel-By1fThvk.js +0 -1
  144. package/dist/assets/web/dist/assets/wallClock-DPTuP2FD.js +0 -1
@@ -347,7 +347,7 @@ workspaceDir: ./workspaces
347
347
  # `ship` fails the load by name.
348
348
  # ship:
349
349
  # maxRounds: 3 # review rounds per unit
350
- # maxMinutes: 120 # the ship preset's wall-clock budget in minutes (the registry's default)
350
+ # maxMinutes: 240 # the ship preset's wall-clock budget in minutes (the registry's default)
351
351
 
352
352
  # Which harness each preset's runs are driven by, deployment-wide
353
353
  # (docs/reference/specs/harness.md item 8): a mapping of preset to a harness's
@@ -641,8 +641,18 @@ interface ConfirmationRow extends ConfirmationInput {
641
641
  }
642
642
  /** Why a consume or a cancel refused: the row is gone (`used`), past its expiry (`expired`), or someone else's (`foreign`). */
643
643
  type ConfirmationRefusal = "used" | "expired" | "foreign";
644
- type ConfirmationOutcome = { row: ConfirmationRow } | { refused: ConfirmationRefusal };
644
+ /** A refusal names the row where one still exists — `expired` (deleted here)
645
+ * and `foreign` (kept) — so the bot can record the click's refusal against
646
+ * the command that was bound (record 0054); `used` has no row to name. */
647
+ type ConfirmationOutcome = { row: ConfirmationRow } | { refused: ConfirmationRefusal; row?: ConfirmationRow };
645
648
  type ConfirmationCancelOutcome = { ok: true } | { refused: Exclude<ConfirmationRefusal, "expired"> };
649
+ /** The consume log's word: a refusal may carry the row it names (expired,
650
+ * foreign), so the refusal is the discriminant, never the row's presence. The
651
+ * parameter is the declared union, where the narrowing holds; the stub's
652
+ * return type narrows to `never` across `in`. */
653
+ function consumeWord(outcome: ConfirmationOutcome): string {
654
+ return "refused" in outcome ? outcome.refused : "consumed";
655
+ }
646
656
 
647
657
  function isJsonObject(v: unknown): v is Record<string, unknown> {
648
658
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -718,9 +728,9 @@ export class ConfigDO extends DurableObject<Env> {
718
728
  if (!stored) return { refused: "used" };
719
729
  if (stored.expiresAt <= now) {
720
730
  this.sql.exec(`DELETE FROM confirmations WHERE id = ?`, id);
721
- return { refused: "expired" };
731
+ return { refused: "expired", row: stored };
722
732
  }
723
- if (!actorIds.includes(stored.requester)) return { refused: "foreign" };
733
+ if (!actorIds.includes(stored.requester)) return { refused: "foreign", row: stored };
724
734
  this.sql.exec(`DELETE FROM confirmations WHERE id = ?`, id);
725
735
  return { row: stored };
726
736
  });
@@ -1233,7 +1243,7 @@ async function handleConfig(pathname: string, body: unknown, env: Env): Promise<
1233
1243
  const click = confirmationClickOf(b);
1234
1244
  if (click instanceof Response) return click;
1235
1245
  const outcome = await dO.consumeConfirmation(click.id, click.actorIds, systemClock());
1236
- console.log(`[config/confirmations/consume] ${click.id} ${"row" in outcome ? "consumed" : outcome.refused}`);
1246
+ console.log(`[config/confirmations/consume] ${click.id} ${consumeWord(outcome)}`);
1237
1247
  return json(outcome);
1238
1248
  }
1239
1249
  case "/config/confirmations/cancel": {
@@ -628,7 +628,7 @@ const ATTACH_MUTEX_WAIT_MS = 60_000;
628
628
  * 20-minute ceiling (`BASH_TIMEOUT_MAX_MS`) — clamped server-side by
629
629
  * `clampBashTimeout` in handleExec, never trusting the client's number. The
630
630
  * heartbeat keeps every HTTP hop alive for the whole budget; the ceiling
631
- * stays far inside the bot's 45-min run budget. Work beyond 20 minutes
631
+ * stays far inside the bot's 90-min coding budget. Work beyond 20 minutes
632
632
  * belongs in background jobs.
633
633
  *
634
634
  * /op runs (test/build) get the flat 20-minute ceiling — the deterministic op
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.246.0",
3
+ "version": "1.247.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "switchboard",
9
- "version": "1.246.0",
9
+ "version": "1.247.0",
10
10
  "license": "Apache-2.0",
11
11
  "workspaces": [
12
12
  "web",
@@ -20445,7 +20445,7 @@
20445
20445
  },
20446
20446
  "packages/switchboard": {
20447
20447
  "name": "@coreplane/switchboard",
20448
- "version": "1.246.0",
20448
+ "version": "1.247.0",
20449
20449
  "license": "Apache-2.0",
20450
20450
  "dependencies": {
20451
20451
  "@earendil-works/pi-ai": "0.85.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.246.0",
3
+ "version": "1.247.0",
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,14 +39,16 @@
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 licenses:check && npm run docs:check && npm run specs:check && npm run decisions:check && npm run hygiene:check && npm run check:project-facts && 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 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 check:project-facts && 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 agents:gen && npm run clock:gen && npm run hygiene: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 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",
48
48
  "check:sandbox-pair": "node scripts/check-sandbox-pair.mjs",
49
49
  "check:pr-title": "node scripts/check-pr-title.mjs",
50
+ "pr-title:gen": "node scripts/pr-title-vocabulary.mjs --write",
51
+ "pr-title:check": "node scripts/pr-title-vocabulary.mjs",
50
52
  "check:project-facts": "node scripts/check-project-facts.mjs",
51
53
  "agents:gen": "tsx scripts/agents-gen.ts",
52
54
  "clock:gen": "tsx scripts/clock-allowlist.mts --write",
@@ -100,8 +100,16 @@
100
100
  "when": "After bumping either half of a pair."
101
101
  },
102
102
  "check:pr-title": {
103
- "does": "Judges one PR title as the changelog line it becomes: grammar, type, scope, the migration note behind `!`.",
104
- "when": "`-- \"feat(scope): …\"` before opening a PR; CI's `title` check runs it."
103
+ "does": "Judges one PR title as the changelog line: grammar, type, scope, the `!` migration note.",
104
+ "when": "`-- \"feat(scope): …\"` before opening a PR; CI's `title` check."
105
+ },
106
+ "pr-title:gen": {
107
+ "does": "Writes the title gate's types and scopes from the release config and the code map.",
108
+ "when": "Part of `fix`."
109
+ },
110
+ "pr-title:check": {
111
+ "does": "The committed title vocabulary equals its two sources.",
112
+ "when": "Part of `check:consistency`."
105
113
  },
106
114
  "check:dist": {
107
115
  "does": "Builds, then proves the compiled entry points and the web bundle the image needs exist.",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.246.0",
3
- "commit": "62e4e9ad464820900b07bed176e140af6682c598",
4
- "builtAt": "2026-09-18T01:06:13.947Z"
2
+ "version": "1.247.0",
3
+ "commit": "713b83c9db0b6f3f10ef6d2cc1a46714b9a23c3c",
4
+ "builtAt": "2026-09-18T03:41:18.440Z"
5
5
  }
@@ -143,7 +143,7 @@ export function statusCardRule(examples = '"Implement the fix", "Run the test su
143
143
  );
144
144
  }
145
145
 
146
- const PR_DESCRIPTION_TEMPLATE = `PR description — submit it with the submit_pr_description tool for EVERY PR (this is the default, not something to wait to be asked for). Switchboard renders the GitHub body from the object you submit, so never author PR-body markdown yourself. Before submitting, judge your title with the ${PR_TITLE_GUARD} gate — \`npm run check:pr-title -- "<title>"\` — and submit only a title it accepts; the same gate refuses the PR in CI. The body is a fixed-size MAP for the reader with everything for agents collapsed under it; every field is capped in visible characters (a link's URL is not counted) and the tool refuses an object over a cap naming the field and the count — cut and resubmit. Prose is unwrapped — no hard line breaks inside a paragraph. Always hyperlink the triggering issue/request. Never fabricate validation — state exactly what you ran and the real result. BEFORE authoring the pointers, load the \`pr-description\` skill with use_skill — it defines how to choose at most seven pointers, the mechanical anchor rules and what goes below the fold; follow it for every PR.
146
+ const PR_DESCRIPTION_TEMPLATE = `PR description — submit it with the submit_pr_description tool for EVERY PR (this is the default, not something to wait to be asked for). Switchboard renders the GitHub body from the object you submit, so never author PR-body markdown yourself. Before submitting, judge your title with the ${PR_TITLE_GUARD} gate — \`npm run check:pr-title -- "<title>"\` — and submit only a title it accepts; the same gate refuses the PR in CI, and on Switchboard's own repository the tool refuses the same titles with the gate's own sentence, so fix the title and resubmit rather than pushing on. The body is a fixed-size MAP for the reader with everything for agents collapsed under it; every field is capped in visible characters (a link's URL is not counted) and the tool refuses an object over a cap naming the field and the count — cut and resubmit. Prose is unwrapped — no hard line breaks inside a paragraph. Always hyperlink the triggering issue/request. Never fabricate validation — state exactly what you ran and the real result. BEFORE authoring the pointers, load the \`pr-description\` skill with use_skill — it defines how to choose at most seven pointers, the mechanical anchor rules and what goes below the fold; follow it for every PR.
147
147
  EVERY PR includes one that already exists when you push — opened by a person, by dependabot, or by an earlier run. After EVERY push to such a PR: read its current title and body (\`github_issue_get\` with the PR number works for pull requests; \`gh pr view\` where gh exists), judge them against the change as it now stands at the pushed head, and submit the object that describes the PR as it is NOW — carry forward what the existing body says that is still true (a dependency bump's release notes belong in why), add what you changed, and anchor the pointers at the new head. Switchboard replaces the PR's title and body with your rendering. A description that describes an earlier state of its branch is a bug; "it is someone else's PR" is never a reason to leave it.
148
148
  - **title** (≤72 characters in all): the changelog line — \`type(scope): what a reader can now do or expect\`, one change, one clause, present tense. The type and the scope spend the same budget as the description, so the description is short; the body carries the rest. The tool refuses a longer title naming the count, and so does the gate in CI.
149
149
  - **TL;DR** (\`tldr\`, rendered first, ≤300): two sentences for a reader with zero context — what this PR does and why it matters.
@@ -68,12 +68,18 @@ export type Preset = LoopPreset | "ship";
68
68
 
69
69
  /** What each preset asks for when nothing above it is tighter, in minutes. The
70
70
  * pipeline's (`ship`) is the wall clock one segment of its loop runs under;
71
- * a deployment's `ship.maxMinutes` replaces it, held to `fit` below. */
71
+ * a deployment's `ship.maxMinutes` replaces it, held to `fit` below. Coding's
72
+ * is sized by the ledger's ship children, not its standalone runs: a child
73
+ * that pushes at minute 29 and then runs the repo's whole gate (seven to ten
74
+ * minutes on a resident) was cut at 45 one run in twelve, so the ask is
75
+ * double the 90th percentile (31.5) with the gate inside it. Ship's holds
76
+ * its own loop at the default rounds with a fix round at that ask, not at
77
+ * its floor (`fit` proves the floor case; the ask leaves room above it). */
72
78
  export const ASKS: Readonly<Record<Preset, number>> = {
73
79
  general: 5,
74
- coding: 45,
80
+ coding: 90,
75
81
  review: 25,
76
- ship: 120,
82
+ ship: 240,
77
83
  research: 8,
78
84
  explore: 120,
79
85
  conductor: 120,
@@ -87,10 +93,13 @@ export type RoundKind = "coding" | "review" | "fix" | "merge";
87
93
  * falls under the floor is refused rather than dispatched: a two-minute
88
94
  * review or fix costs an attach and a model turn and finishes nothing.
89
95
  * Review's is the ledger's 90th percentile of completed reviews (5.1 min over
90
- * 181); coding's and the merge wait's are guesses until the ledger says. */
96
+ * 181); coding's is the least a fix round can run the repo's gate in — the
97
+ * gate alone takes seven to ten minutes on a resident, and a fix that cannot
98
+ * run it finishes nothing its contract asks; the merge wait's is a guess
99
+ * until the ledger says. */
91
100
  export const PRESET_FLOORS: Readonly<Record<LoopPreset, number>> = {
92
101
  general: 2,
93
- coding: 10,
102
+ coding: 15,
94
103
  review: 5,
95
104
  research: 3,
96
105
  explore: 15,
@@ -387,6 +396,9 @@ export type GrantSource = "org" | "channel" | "user" | "run";
387
396
  export const DEFAULT_GRANT: Grant = { renewals: 0 };
388
397
 
389
398
  /** The most renewals one request may carry: thirteen segments of the ship
390
- * preset's ask are a day, the longest problem a person hands over in one
391
- * message before a plan should carry it. */
399
+ * preset's ask are two days. The cap bounds a COUNT a person types
400
+ * (`renewals:`) or a scope holds; it did not halve when the ask doubled,
401
+ * since every grant already configured is a count of segments and a day's
402
+ * hand-over is now `renewals: 5` — the longest problem a person hands over
403
+ * in one message before a plan should carry it. */
392
404
  export const GRANT_RENEWALS_MAX = 12;
@@ -462,7 +462,7 @@ export type RouteInputValue = RouteInputLeafOrList | RouteInputObject3;
462
462
  * `pasted` — the typed line that followed a hand-back in its thread, with the
463
463
  * same receipt, ran. A routed read carries no outcome: it is not a decision
464
464
  * about a state change. */
465
- export type RouteOutcome = "hand_back" | "offered" | "confirmed" | "pasted";
465
+ export type RouteOutcome = "hand_back" | "offered" | "confirmed" | "pasted" | "refused";
466
466
 
467
467
  export type RunEvent =
468
468
  /** `callId` is the provider's tool_use id — the explicit pair key between a
@@ -864,6 +864,10 @@ export type RunEvent =
864
864
  input?: { readonly [key: string]: RouteInputValue };
865
865
  receipt?: string;
866
866
  outcome?: RouteOutcome;
867
+ /** The refusal's code (src/core/refusal.ts) when `outcome` is `refused`
868
+ * (record 0054): a refusal after a command was bound is a run
869
+ * record, and the door report counts it by cause and code. */
870
+ refusalCode?: string;
867
871
  handBackRunId?: string;
868
872
  seq?: number;
869
873
  at?: number;
@@ -129,6 +129,13 @@ export const CONTRACT_SECTION_HEADINGS = {
129
129
  * repository's rules file (~15k characters) and a long unit both inside it. */
130
130
  export const DEFAULT_CONTRACT_MAX_CHARS = 48_000;
131
131
 
132
+ /** The most of a plan a reader hands the parsers: ten times the tool clip
133
+ * (`MAX_FILE_CHARS`, `src/execution/githubApi.ts`), which a plan that grows by
134
+ * amendment passes — the program plan crossed 400k characters and the seed
135
+ * then saw its first 25 units alone. A plan past this bound is refused by
136
+ * name, never parsed short. */
137
+ export const PLAN_MAX_CHARS = 2_000_000;
138
+
132
139
  const UNIT_HEADING = /^### (U\d+)\. (.+?)\s*$/;
133
140
  const ANY_HEADING = /^#{2,3} /;
134
141
  const BULLET = /^- \*\*([^*]+)\*\*:(?:\s?(.*))?$/;
@@ -754,7 +754,7 @@ export const WAIT_MARGIN_MS = SHIP_WAIT.marginMinutes * MIN;
754
754
  * one chunk of the runner's time, not the child's whole budget. Five minutes
755
755
  * is the machine's one cadence for asking the bot what it cannot be told (the
756
756
  * margin and the merge poll are the same number), and it keeps a round to a
757
- * few steps: a coding child's 45 minutes are ten waits and ten reads. */
757
+ * few steps: a coding child's 90 minutes are eighteen waits and eighteen reads. */
758
758
  export const WAIT_CHUNK_MS = SHIP_WAIT.chunkMinutes * MIN;
759
759
  /** The merge wait is a round of its own: its minutes are carved from the
760
760
  * pipeline's remainder when the door is first asked (the merge wait's ask and
@@ -26,6 +26,11 @@ export interface AttrDomain {
26
26
  requester: "message" | "relay-footer" | "thread-parent" | "bot";
27
27
  // dispatch.* / run.* / post.*
28
28
  outcome: string;
29
+ /** The refusal's code (src/core/refusal.ts) — on the `dispatch.refuse` span
30
+ * and the request's root, so refusals are countable from the trace alone. */
31
+ refusal: string;
32
+ /** The refusal's cause, from the one code→cause table in src/core/refusal.ts. */
33
+ cause: "request" | "policy" | "system";
29
34
  count: number;
30
35
  backend: Backend;
31
36
  // run.command
@@ -114,6 +119,8 @@ export type SpanAttrs = { readonly [K in SpanAttrKey]?: AttrDomain[K] };
114
119
  const IDENTIFIER_KEYS: ReadonlySet<SpanAttrKey> = new Set<SpanAttrKey>([
115
120
  "runId",
116
121
  "outcome",
122
+ "refusal",
123
+ "cause",
117
124
  "command",
118
125
  "route",
119
126
  "host",
@@ -163,6 +170,8 @@ const ATTR_TYPE: Record<SpanAttrKey, "string" | "number" | "boolean"> = {
163
170
  dedupe: "string",
164
171
  requester: "string",
165
172
  outcome: "string",
173
+ refusal: "string",
174
+ cause: "string",
166
175
  count: "number",
167
176
  backend: "string",
168
177
  command: "string",