@coreplane/switchboard 1.260.2 → 1.260.4

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 (129) hide show
  1. package/dist/assets/deploy/cloudflare-memory/backgroundTasks.ts +27 -3
  2. package/dist/assets/deploy/cloudflare-memory/testDiagnostics.ts +227 -0
  3. package/dist/assets/deploy/cloudflare-memory/testDiagnosticsProtocol.ts +22 -0
  4. package/dist/assets/deploy/cloudflare-memory/testDiagnosticsReporter.ts +88 -0
  5. package/dist/assets/deploy/cloudflare-memory/tsconfig.json +14 -2
  6. package/dist/assets/deploy/cloudflare-resident/Dockerfile +11 -2
  7. package/dist/assets/package-lock.json +3 -3
  8. package/dist/assets/package.json +4 -3
  9. package/dist/assets/project.json +4 -0
  10. package/dist/assets/source.json +3 -3
  11. package/dist/assets/src/agents/registry.ts +6 -5
  12. package/dist/assets/src/core/coordinator/contract.ts +48 -2
  13. package/dist/assets/src/core/coordinator/driver.ts +40 -5
  14. package/dist/assets/src/core/reviewVerdict.ts +15 -5
  15. package/dist/assets/src/core/runFriction.ts +1 -1
  16. package/dist/assets/src/core/ship/contract.ts +2 -2
  17. package/dist/assets/src/core/ship/coordinator.ts +236 -108
  18. package/dist/assets/src/core/ship/renewal.ts +3 -1
  19. package/dist/assets/src/execution/bashTimeout.ts +1 -1
  20. package/dist/assets/src/execution/sandboxErrors.ts +6 -6
  21. package/dist/assets/web/dist/.vite/manifest.json +488 -488
  22. package/dist/assets/web/dist/assets/{AppShell-CSXDCV4x.js → AppShell-DPJVhUGP.js} +1 -1
  23. package/dist/assets/web/dist/assets/{CostChart-DsuPS2g7.js → CostChart-4TeDdbvJ.js} +1 -1
  24. package/dist/assets/web/dist/assets/CostsPage-8HDgvuWB.js +1 -0
  25. package/dist/assets/web/dist/assets/{DeliveryPage-62qCSLvk.js → DeliveryPage-BaemWKW8.js} +1 -1
  26. package/dist/assets/web/dist/assets/{HomePage-Dk3HRBc3.js → HomePage-fgKQHLaO.js} +1 -1
  27. package/dist/assets/web/dist/assets/{InputMenu-BdZXnibG.js → InputMenu-BRV35UWP.js} +1 -1
  28. package/dist/assets/web/dist/assets/{MetricsPage-o5fSaCTe.js → MetricsPage-BQBuIuYU.js} +1 -1
  29. package/dist/assets/web/dist/assets/{NotFoundPage-DHuWyjlq.js → NotFoundPage-CDjMfCcj.js} +1 -1
  30. package/dist/assets/web/dist/assets/{PendingTurnRow-CeTGOXUy.js → PendingTurnRow-vuWiDzvz.js} +1 -1
  31. package/dist/assets/web/dist/assets/{PlanePage-RD0M6O6W.js → PlanePage-DXMJf8FB.js} +1 -1
  32. package/dist/assets/web/dist/assets/{ResidentDetailPage-Dl2KId95.js → ResidentDetailPage-DdC8yU_S.js} +1 -1
  33. package/dist/assets/web/dist/assets/{ResidentsIndexPage-DBy43I1R.js → ResidentsIndexPage-CSM-KODv.js} +1 -1
  34. package/dist/assets/web/dist/assets/{RunFoldRow-CwHDL-Mo.js → RunFoldRow-Csik3wD6.js} +1 -1
  35. package/dist/assets/web/dist/assets/{RunRoutePage-Du1HqLZs.js → RunRoutePage-jo6NZxsP.js} +3 -3
  36. package/dist/assets/web/dist/assets/{RunsIndexPage-BZphy3Hr.js → RunsIndexPage-Dx1Y4sSq.js} +1 -1
  37. package/dist/assets/web/dist/assets/{RunsTabs-kHbbxVHx.js → RunsTabs-C7Ua3XX5.js} +1 -1
  38. package/dist/assets/web/dist/assets/{ScheduledPage-B7jh898t.js → ScheduledPage-BuB_NZyR.js} +1 -1
  39. package/dist/assets/web/dist/assets/{SettingSelect-BfID5nF4.js → SettingSelect-CEeA4iuC.js} +1 -1
  40. package/dist/assets/web/dist/assets/{SettingsPage-3uLEsiiE.js → SettingsPage-Cn96jB6Q.js} +1 -1
  41. package/dist/assets/web/dist/assets/{SilentTurn-CKxSxnQC.js → SilentTurn-D2cJfpEq.js} +1 -1
  42. package/dist/assets/web/dist/assets/{SlackMark-CtiHrNjr.js → SlackMark-DB0MGiF5.js} +1 -1
  43. package/dist/assets/web/dist/assets/{StatusDot-x7vcK0iE.js → StatusDot-DDHUCPVJ.js} +1 -1
  44. package/dist/assets/web/dist/assets/{Tooltip-wVXCWhMp.js → Tooltip-B4VIHkAI.js} +1 -1
  45. package/dist/assets/web/dist/assets/{UnitRoutePage-V5BXK7FH.js → UnitRoutePage-C-2DfjLI.js} +1 -1
  46. package/dist/assets/web/dist/assets/{angular-html-C8b5YTHf.js → angular-html-BRRhsdQE.js} +1 -1
  47. package/dist/assets/web/dist/assets/{angular-ts-C3f4WYNe.js → angular-ts-BFj-mqCm.js} +1 -1
  48. package/dist/assets/web/dist/assets/{apl-DSHxUJc3.js → apl-CsBuy1IL.js} +1 -1
  49. package/dist/assets/web/dist/assets/{astro-BSGCCBP6.js → astro-cVaqxBZq.js} +1 -1
  50. package/dist/assets/web/dist/assets/{blade-DFeYrMKk.js → blade-MEP5mWha.js} +1 -1
  51. package/dist/assets/web/dist/assets/{browser-B6T5hjfR.js → browser-CCImen5O.js} +1 -1
  52. package/dist/assets/web/dist/assets/{c-L_QOnFVv.js → c-D4NGQ_Pd.js} +1 -1
  53. package/dist/assets/web/dist/assets/{chapel-DLQWUwhu.js → chapel-BkWImsIy.js} +1 -1
  54. package/dist/assets/web/dist/assets/{cobol-1DlMmrEO.js → cobol-RPDSPSET.js} +1 -1
  55. package/dist/assets/web/dist/assets/{coffee-BTB6os6N.js → coffee-DZ5z7oVX.js} +1 -1
  56. package/dist/assets/web/dist/assets/{cpp-BvJI2z3u.js → cpp-CrBtIaRZ.js} +1 -1
  57. package/dist/assets/web/dist/assets/{crystal-B11wGW_h.js → crystal-DJesYi6f.js} +1 -1
  58. package/dist/assets/web/dist/assets/{css-BiIWx5Pw.js → css-DO9mjau-.js} +1 -1
  59. package/dist/assets/web/dist/assets/{dist-C-XGnvM4.js → dist-Cf1CmCSK.js} +2 -2
  60. package/dist/assets/web/dist/assets/{edge-nSglEXaD.js → edge-C3VzbSW0.js} +1 -1
  61. package/dist/assets/web/dist/assets/{elixir-x7o8qMl8.js → elixir-BkD_Vhfe.js} +1 -1
  62. package/dist/assets/web/dist/assets/{elm-DGrRyqTQ.js → elm-KIM0mFPk.js} +1 -1
  63. package/dist/assets/web/dist/assets/{erb--_dh58Nw.js → erb-D2ruCqb2.js} +1 -1
  64. package/dist/assets/web/dist/assets/{eventSource-Bj_dZf6M.js → eventSource-Cv0AFlMi.js} +1 -1
  65. package/dist/assets/web/dist/assets/{git-rebase-DbpUXwyg.js → git-rebase-CnfbQTxA.js} +1 -1
  66. package/dist/assets/web/dist/assets/{glimmer-js-BG0CuZG5.js → glimmer-js-CbiwFXWH.js} +1 -1
  67. package/dist/assets/web/dist/assets/{glimmer-ts-CQIl5qyR.js → glimmer-ts-DV8ii0Pv.js} +1 -1
  68. package/dist/assets/web/dist/assets/{glsl-9PJNQTcw.js → glsl-CgmznWkA.js} +1 -1
  69. package/dist/assets/web/dist/assets/{graphql-CzVTzjEI.js → graphql-BK-bBYfH.js} +1 -1
  70. package/dist/assets/web/dist/assets/{hack-BhCmAz3Q.js → hack-DnwTTP5n.js} +1 -1
  71. package/dist/assets/web/dist/assets/{haml-BmI12COR.js → haml-DAVJsWwT.js} +1 -1
  72. package/dist/assets/web/dist/assets/{handlebars-Cib-ISTS.js → handlebars-voOVfxFL.js} +1 -1
  73. package/dist/assets/web/dist/assets/{html-D3lDapvI.js → html-3FTqwaPk.js} +1 -1
  74. package/dist/assets/web/dist/assets/{html-derivative-DRPY8vOK.js → html-derivative-CSlhLDQn.js} +1 -1
  75. package/dist/assets/web/dist/assets/{http-B9VttK6m.js → http-Vx4ilPfS.js} +1 -1
  76. package/dist/assets/web/dist/assets/{hurl-C0saNJYj.js → hurl-DxyZDE3R.js} +1 -1
  77. package/dist/assets/web/dist/assets/{java-BCFbtYAj.js → java-nWqdjNJG.js} +1 -1
  78. package/dist/assets/web/dist/assets/{javascript-nwBjnrhh.js → javascript-Dr82HOwp.js} +1 -1
  79. package/dist/assets/web/dist/assets/{jinja-CMYO9byN.js → jinja-BhjLrPOt.js} +1 -1
  80. package/dist/assets/web/dist/assets/{jison-Cko8HnE_.js → jison-DilGIUa8.js} +1 -1
  81. package/dist/assets/web/dist/assets/{json-Dzv__YsX.js → json-CrOcvmhJ.js} +1 -1
  82. package/dist/assets/web/dist/assets/{jsx-CTFxagih.js → jsx-BW_RX-fo.js} +1 -1
  83. package/dist/assets/web/dist/assets/{julia-_Xvten1-.js → julia-CWH071DB.js} +1 -1
  84. package/dist/assets/web/dist/assets/{just-bLps4qOu.js → just-DkhhoWBy.js} +1 -1
  85. package/dist/assets/web/dist/assets/{latex-D6UaYsUP.js → latex-cuHJ4TUT.js} +1 -1
  86. package/dist/assets/web/dist/assets/{liquid-CoDIeULa.js → liquid-DG6afYQN.js} +1 -1
  87. package/dist/assets/web/dist/assets/{lua-D1KFRnVf.js → lua-C5CMnCPS.js} +1 -1
  88. package/dist/assets/web/dist/assets/{main-DAL--hyj.js → main-DxPqaFu9.js} +3 -3
  89. package/dist/assets/web/dist/assets/{marko-B5N5XcOw.js → marko-Dy2_DuC4.js} +1 -1
  90. package/dist/assets/web/dist/assets/{mdc-uxzjQO3q.js → mdc-BMKb_EeA.js} +1 -1
  91. package/dist/assets/web/dist/assets/{nginx-BMJxs2TK.js → nginx-og0RgMKf.js} +1 -1
  92. package/dist/assets/web/dist/assets/{nim-DqQ3-8p1.js → nim-CQxTQQP0.js} +1 -1
  93. package/dist/assets/web/dist/assets/{org-DsY6LK59.js → org-b5lkq_Pl.js} +1 -1
  94. package/dist/assets/web/dist/assets/{perl--pq9iJFp.js → perl-D9X5_i_J.js} +1 -1
  95. package/dist/assets/web/dist/assets/{php-RHCMbsM2.js → php-CwUxvLX0.js} +1 -1
  96. package/dist/assets/web/dist/assets/{pug-CFNq_FCp.js → pug-uOIOExr0.js} +1 -1
  97. package/dist/assets/web/dist/assets/{qml-C9Acx9E8.js → qml-g5RoADf-.js} +1 -1
  98. package/dist/assets/web/dist/assets/{r-DxlU8FBd.js → r-5LdAopD3.js} +1 -1
  99. package/dist/assets/web/dist/assets/{razor-CLJ2kKih.js → razor-DwrvFLYQ.js} +1 -1
  100. package/dist/assets/web/dist/assets/{regexp-CzmdGoEp.js → regexp-dGtAaxC-.js} +1 -1
  101. package/dist/assets/web/dist/assets/{rst-D9yuxBET.js → rst-wFdW0brr.js} +1 -1
  102. package/dist/assets/web/dist/assets/{ruby-CIRcEvnD.js → ruby-DPglN6I9.js} +1 -1
  103. package/dist/assets/web/dist/assets/{sas-LOl-uUPh.js → sas-CUbCs40d.js} +1 -1
  104. package/dist/assets/web/dist/assets/{scss-BX2_sZHY.js → scss-B-DUiI1c.js} +1 -1
  105. package/dist/assets/web/dist/assets/{shellscript-4d-QvKkY.js → shellscript-lGOV7Ram.js} +1 -1
  106. package/dist/assets/web/dist/assets/{shellsession-DhB16UGy.js → shellsession-BDWtAAjr.js} +1 -1
  107. package/dist/assets/web/dist/assets/{soy-Cm1xTUBr.js → soy-zod_dcOy.js} +1 -1
  108. package/dist/assets/web/dist/assets/{sql-BsfcBJdu.js → sql-BG0HSSoM.js} +1 -1
  109. package/dist/assets/web/dist/assets/{sseReplay-BqgOggyQ.js → sseReplay-BzoeVxZB.js} +1 -1
  110. package/dist/assets/web/dist/assets/{stata-Dwat3BSw.js → stata-BVUMLp1W.js} +1 -1
  111. package/dist/assets/web/dist/assets/{surrealql-P4rtxzAW.js → surrealql-DiSUbHKF.js} +1 -1
  112. package/dist/assets/web/dist/assets/{svelte-DTcsi8S8.js → svelte-DZenT45Y.js} +1 -1
  113. package/dist/assets/web/dist/assets/{templ-D-kP0Tg5.js → templ-BWKRNM0-.js} +1 -1
  114. package/dist/assets/web/dist/assets/{tex-D-4vuedK.js → tex-CbMzBlkK.js} +1 -1
  115. package/dist/assets/web/dist/assets/{ts-tags-jEEWYxsu.js → ts-tags-CZsVorDN.js} +1 -1
  116. package/dist/assets/web/dist/assets/{tsx-CFnlrVzT.js → tsx-R1r3mH4W.js} +1 -1
  117. package/dist/assets/web/dist/assets/{twig-D5rGrPjs.js → twig-BjPMyjKH.js} +1 -1
  118. package/dist/assets/web/dist/assets/{typescript-nYIQ35or.js → typescript-Bx-66VN_.js} +1 -1
  119. package/dist/assets/web/dist/assets/{typst-3-UX8dna.js → typst-fW037Eo6.js} +1 -1
  120. package/dist/assets/web/dist/assets/{vue-CLIPIX03.js → vue-D1V_GNxW.js} +1 -1
  121. package/dist/assets/web/dist/assets/{vue-html-Ci9CpkHU.js → vue-html-C-4pTZ-s.js} +1 -1
  122. package/dist/assets/web/dist/assets/{vue-vine-DL5pEYYk.js → vue-vine-C-QsX3hR.js} +1 -1
  123. package/dist/assets/web/dist/assets/{wallClock-DmaC1sKA.js → wallClock-x0hjr0J_.js} +1 -1
  124. package/dist/assets/web/dist/assets/{xml-bO80qikC.js → xml-BJTJXPgC.js} +1 -1
  125. package/dist/assets/web/dist/assets/{xsl-BPze60Ej.js → xsl-Xc9lukqe.js} +1 -1
  126. package/dist/assets/web/dist/assets/{yaml-BQUSon_o.js → yaml-Dw-FMsQa.js} +1 -1
  127. package/dist/cli.js +505 -253
  128. package/package.json +1 -1
  129. package/dist/assets/web/dist/assets/CostsPage-BaeWnm-o.js +0 -1
package/dist/cli.js CHANGED
@@ -1874,7 +1874,7 @@ function bashBudgetWithinRun(wantedMs, remainingMs) {
1874
1874
  if (cap < BASH_TIMEOUT_MIN_MS) {
1875
1875
  return {
1876
1876
  kind: "exhausted",
1877
- note: `run budget exhausted \u2014 ${secs(remainingMs)}s of wall clock left, inside the ${secs(RUN_DEADLINE_RESERVE_MS)}s write-up reserve, so the command was not run; write up what you have now`
1877
+ note: `the run budget is exhausted \u2014 ${secs(remainingMs)}s of wall clock left, inside the ${secs(RUN_DEADLINE_RESERVE_MS)}s write-up reserve, so the command was not run; write up what you have now`
1878
1878
  };
1879
1879
  }
1880
1880
  if (cap >= wantedMs) return { kind: "unchanged" };
@@ -2094,7 +2094,7 @@ function renderFirstInstruction(rebase) {
2094
2094
  const branch2 = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
2095
2095
  const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
2096
2096
  const gates = `${FAST_GATES_POINTER} ${GATE_RECEIPTS}`;
2097
- return `Rebase ${branch2} onto ${onto} before any other work \u2014 the parent unit has merged and the base has moved; the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. Push the branch as soon as the change exists and the fast gates pass \u2014 the project's full verification is CI's gate, run there after the push with any fix as a further commit; an unpushed tree does not survive the run's end. ${gates} ${REBASE_POINTER} At the wind-down note, commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`;
2097
+ return `Rebase ${branch2} onto ${onto} before any other work \u2014 the parent unit has merged and the base has moved; the only writes are your own on that branch. Push the branch as soon as the change exists and the fast gates pass \u2014 the project's full verification is CI's gate and runs there after the push, with any fix as a further commit; an unpushed tree does not survive the run's end. ${gates} ${REBASE_POINTER} At the wind-down note, commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`;
2098
2098
  }
2099
2099
  function renderSpecRow(row2, dropText) {
2100
2100
  const head = `#### ${row2.spec} item ${row2.item}`;
@@ -2313,7 +2313,7 @@ EVERY PR includes one that already exists when you push \u2014 opened by a perso
2313
2313
  - **For agents** (\`agentNotes\`, optional, \u22642000, collapsed): what a reviewing agent needs that a person does not \u2014 the rebase you did, generated files to skip, the command that reproduces the bug.`;
2314
2314
  NEVER_MERGE = `NEVER merge a pull request and NEVER approve one \u2014 no merge or approve command, no merge/approve API call, no pushing to the default branch. Your deliverable is the pushed branch plus the submitted description; Switchboard's own GitHub writes are the PR open/edit, never an approval or a merge \u2014 a human decides what merges.`;
2315
2315
  CONTRACT_HEADINGS_LIST = Object.values(CONTRACT_SECTION_HEADINGS).map((h) => `\`${h}\``).join(", ");
2316
- UNIT_CONTRACT = `UNIT CONTRACT: when your first user turn carries a \`${CONTRACT_HEADING}\` block \u2014 its sub-headings, in this order: ${CONTRACT_HEADINGS_LIST} \u2014 it is the contract for one plan unit, rendered by Switchboard from the plan itself, and it outranks any free-text task beside it. Do its first instruction first: the rebase of the unit's branch onto the merged parent (a conflict ends the unit \u2014 report it and stop; never resolve it by force). Then implement the unit's section as written: every test scenario it lists is added as a test, every spec row it names is updated so its proof binding resolves, the agent rules are followed, and no guard it names is weakened. The review is handed the same block and checks the diff against it: a test scenario the unit listed and the diff did not add is a finding at minor severity \u2014 the same severity as a spec contradiction. Never edit the plan record itself; where the unit is wrong or a criterion could not be proven, say so in the handoff and in your final message.`;
2316
+ UNIT_CONTRACT = `UNIT CONTRACT: when your first user turn carries a \`${CONTRACT_HEADING}\` block \u2014 its sub-headings, in this order: ${CONTRACT_HEADINGS_LIST} \u2014 it is the contract for one plan unit, rendered by Switchboard from the plan itself, and it outranks any free-text task beside it. Do its first instruction first: the rebase of the unit's branch onto the merged parent. Then implement the unit's section as written: every test scenario it lists is added as a test, every spec row it names is updated so its proof binding resolves, the agent rules are followed, and no guard it names is weakened. The review is handed the same block and checks the diff against it: a test scenario the unit listed and the diff did not add is a finding at minor severity \u2014 the same severity as a spec contradiction. Never edit the plan record itself; where the unit is wrong or a criterion could not be proven, say so in the handoff and in your final message.`;
2317
2317
  UNIT_HANDOFF = `UNIT HANDOFF: when your first user turn carries a \`${CONTRACT_HEADING}\` block, call the submit_handoff tool once, after submit_pr_description and before your final message, with the typed handoff \u2014 deviations: where you departed from the unit as written (from, to, why); followUps: what you found and did not do, and where it belongs (what, where); unproven: which of the unit's test scenarios or criteria you could not prove, and why (criterion, why); landed (optional): what of the unit was already on the base when you began, and the pull request or commit that carries it (what, where) \u2014 when the whole unit is already there, push nothing of your own and open no pull request; the handoff's landed rows end the unit done. Switchboard records it on the run and posts it to the unit's board issue, where a person decides each row's disposition; you never edit the plan's ledger yourself. An empty handoff is submitted as three empty lists, never skipped \u2014 a missing handoff reads as an unfinished run, not as nothing to say. Without a \`${CONTRACT_HEADING}\` block, do not call it.`;
2318
2318
  IMAGE_TOOLCHAIN = `Node 24 with npm and pnpm; python3, make and g++ (native modules build); ffmpeg (frames out of a video \u2014 \`ffmpeg -i in.mp4 -vf fps=1 f_%03d.png\` \u2014 and video out of frames or a recording); and a headless Chromium through Playwright \u2014 \`playwright screenshot <url> out.png\`, \`playwright pdf <url> out.pdf\`, or \`require('playwright')\` for a scripted page and \`recordVideo\``;
2319
2319
  SANDBOX_TOOLCHAIN = `The sandbox image carries ${IMAGE_TOOLCHAIN}; and Docker (the engine starts on the first \`docker\` call).`;
@@ -2330,7 +2330,7 @@ Keep notes with the \`notes\` tool: one short document, replaced whole each time
2330
2330
  CHECKS_BY_COST = `CHECKS BY COST \u2014 push before the expensive ones. Every check you might run has a cost class: seconds (a formatter or a linter on the files you touched, one test file, a docs, link or spec check, the typecheck of one package) or minutes (the whole test suite, a build, a dependency install, an end-to-end or full verification). Know a command's class before you run it \u2014 from the project's own scripts and CI configuration, from how long it took last time, or by the class above when you have nothing better. Prove each change with the cheapest check that can prove it, matched to the change's scope and scoped to the changed set \u2014 the tests nearest your change, the touched project's typecheck, the changed files' formatting, never the whole tree: a documentation change gets the documentation checks, one module gets its own tests, a shared type gets the typecheck. Every CI pipeline runs the tests, the types, the formatting and the full verification on your push, so you never run them again: you validate and fix your own change before pushing, at the changed-set scope. Passing the full test suite and the full typecheck is NOT part of your criteria: CI is that gate and the only place they run \u2014 on a shared machine they cost minutes that every other run pays for. As soon as the change exists and those checks pass, commit and push \u2014 the pushed branch is the deliverable, and an unpushed tree does not survive the run's end. Beyond the changed set, use judgement about what this change needs rather than a checklist, fixing forward with further commits and pushes. Never start an operation whose expected duration does not fit the time you have left minus what a commit, a push and the description need: push what there is and say plainly what is unverified instead. At the wind-down note, commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS} The description's validation names exactly what ran; what did not run is CI's to gate, and you say so.`;
2331
2331
  TOUCHED_TESTS_COMMAND = "`npx vitest run` on the test files you touched, by name,";
2332
2332
  FAST_GATES_BEFORE_PUSH = `THE FAST GATES, before every push \u2014 each scoped to the changed set, never the whole project: ${TOUCHED_TESTS_COMMAND} once (never \`--changed\`, never a directory: on a moving base that is most of the suite), \`tsc --noEmit -p\` the touched tsconfig under \`NODE_OPTIONS=--max-old-space-size=6144\`, \`npx prettier --check\` on the changed files, \`npm run hygiene:check\` and \`npm run specs:check\` \u2014 then your judgement on what else this change needs, not a longer checklist. The full verification is CI's gate \u2014 \`npm run verify\` runs there on your push, never here: push a head early and let CI judge it, fixing forward with further commits and pushes.`;
2333
- REBASE_BEFORE_PUSH = "REBASE BEFORE EVERY PUSH \u2014 always, not configurable. Immediately before each push: fetch your base branch, rebase your branch onto it, resolve any conflict with the context you already have (the repository's AGENTS.md says how a generated file is regenerated \u2014 regenerate it, never hand-merge it), re-run THE FAST GATES on the rebased tree, and only then push. Every head that reaches review is then current with its base when it lands, and no unit ends merge-ready behind a sibling that merged first.";
2333
+ REBASE_BEFORE_PUSH = "REBASE BEFORE EVERY PUSH \u2014 always, not configurable. Immediately before each push: fetch your base branch, rebase your branch onto it, and resolve any conflict with the context you already have in one bounded model round \u2014 the thread's context and the repository's AGENTS.md (which says how a generated file is regenerated: regenerate it, never hand-merge it). If you cannot resolve it inside that bound, end the round; otherwise re-run THE FAST GATES on the rebased tree, and only then push. Every head that reaches review is then current with its base when it lands, and no unit ends merge-ready behind a sibling that merged first.";
2334
2334
  CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
2335
2335
 
2336
2336
  You work inside a dedicated workspace directory with bash, read_file, and write_file tools. ${SANDBOX_TOOLCHAIN}
@@ -3007,7 +3007,7 @@ function renewalDecision(input) {
3007
3007
  };
3008
3008
  }
3009
3009
  function renderRenewal(decision, grant2, options = {}) {
3010
- const recourse = options.idle ? "reply in this thread to continue" : "re-issue the request to try again";
3010
+ const recourse = options.idle ? "the next reply in this thread continues the unit" : "the next reply in this thread continues the original task";
3011
3011
  const senders = options.senders?.length ? `, with ${options.senders.length} message${options.senders.length === 1 ? "" : "s"} from ${options.senders.join(", ")}` : "";
3012
3012
  if (decision.renew)
3013
3013
  return `budget renewed, ${decision.segment - 1} of ${grant2.renewals}, continues ${decision.from !== void 0 ? decision.from.slice(0, 7) : "the branch's head"}${senders}`;
@@ -3162,6 +3162,9 @@ var init_redact = __esm({
3162
3162
  function severityAtOrAbove(severity, level) {
3163
3163
  return isAddressSeverity(severity) && severityRank(severity) <= severityRank(level);
3164
3164
  }
3165
+ function findingsAtOrAbove(findings, level) {
3166
+ return findings.filter((f) => severityAtOrAbove(f.severity, level));
3167
+ }
3165
3168
  function resolveAddressSeverity(layers) {
3166
3169
  if (layers.run !== void 0) return { level: layers.run, source: "run" };
3167
3170
  if (layers.user !== void 0) return { level: layers.user, source: "user" };
@@ -3274,7 +3277,7 @@ function findingCounts(findings) {
3274
3277
  }
3275
3278
  function whereCell(f, target) {
3276
3279
  const location = f.line !== void 0 ? `${f.file}:${f.line}` : f.file;
3277
- const label = `\`${cell(location)}\``;
3280
+ const label = `\`${escapeMarkdownTableCell(location)}\``;
3278
3281
  if (!target || !isRepoPath(f.file)) return label;
3279
3282
  const path = encodeURI(f.file).replace(/#/g, "%23").replace(/\?/g, "%3F");
3280
3283
  const url = `https://github.com/${target.repo}/blob/${target.head}/${path}${f.line !== void 0 ? `#L${f.line}` : ""}`;
@@ -3290,6 +3293,7 @@ function verdictMarker(verdict, target) {
3290
3293
  id: f.id,
3291
3294
  severity: f.severity,
3292
3295
  file: f.file,
3296
+ title: f.title,
3293
3297
  ...f.line !== void 0 ? { line: f.line } : {},
3294
3298
  ...f.humanGated ? { humanGated: true } : {}
3295
3299
  }))
@@ -3311,7 +3315,9 @@ function buildReviewPostBody(answer, verdict, target) {
3311
3315
  [
3312
3316
  "| Severity | Finding | Where |",
3313
3317
  "| --- | --- | --- |",
3314
- ...findings.map((f) => `| ${f.severity} | **${cell(f.id)}** ${cell(f.title)} | ${whereCell(f, target)} |`)
3318
+ ...findings.map(
3319
+ (f) => `| ${f.severity} | **${escapeMarkdownTableCell(f.id)}** ${escapeMarkdownTableCell(f.title)} | ${whereCell(f, target)} |`
3320
+ )
3315
3321
  ].join("\n")
3316
3322
  );
3317
3323
  }
@@ -3412,7 +3418,7 @@ function redactVerdict(v, redact = redactSecrets) {
3412
3418
  function redactDispositions(dispositions, redact = redactSecrets) {
3413
3419
  return dispositions.map((d) => ({ findingId: d.findingId, disposition: d.disposition, note: redact(d.note) }));
3414
3420
  }
3415
- var FINDING_SEVERITIES, ADDRESS_SEVERITIES, DEFAULT_ADDRESS_SEVERITY, isAddressSeverity, severityRank, LGTM_TOKEN, CHANGES_TOKEN, NO_VERDICT_LINE, cell, isRepoPath, VERDICT_KINDS, DISPOSITION_KINDS, isRecordLike, REVIEW_POST_HEAD;
3421
+ var FINDING_SEVERITIES, ADDRESS_SEVERITIES, DEFAULT_ADDRESS_SEVERITY, isAddressSeverity, severityRank, LGTM_TOKEN, CHANGES_TOKEN, NO_VERDICT_LINE, escapeMarkdownTableCell, unescapeMarkdownTableCell, isRepoPath, VERDICT_KINDS, DISPOSITION_KINDS, isRecordLike, REVIEW_POST_HEAD;
3416
3422
  var init_reviewVerdict = __esm({
3417
3423
  "../../src/core/reviewVerdict.ts"() {
3418
3424
  "use strict";
@@ -3427,7 +3433,8 @@ var init_reviewVerdict = __esm({
3427
3433
  LGTM_TOKEN = "LGTM:";
3428
3434
  CHANGES_TOKEN = "Changes requested:";
3429
3435
  NO_VERDICT_LINE = "No verdict submitted \u2014 not approving.";
3430
- cell = (text) => oneLine2(text).replace(/\|/g, "\\|");
3436
+ escapeMarkdownTableCell = (text) => oneLine2(text).replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
3437
+ unescapeMarkdownTableCell = (text) => text.replace(/\\([\\|])/g, "$1");
3431
3438
  isRepoPath = (file) => /^[^\s`]+$/.test(file) && !/^[a-z][a-z0-9+.-]*:\/\//i.test(file);
3432
3439
  VERDICT_KINDS = ["approve", "request_changes"];
3433
3440
  DISPOSITION_KINDS = ["fixed", "declined"];
@@ -3448,9 +3455,9 @@ function interruptionCauseOfWords(words) {
3448
3455
  }
3449
3456
  function shipInterruptedNote(prUrl3, cause, idle = false) {
3450
3457
  const stands = prUrl3 ? `Its work stands on GitHub: ${prUrl3}.` : "Whatever it pushed stands on its pipeline branch; no PR was opened yet.";
3451
- const reissue = idle ? "To continue, reply in this thread to continue." : prUrl3 ? `To continue the review loop, re-issue \`agent:ship\` in this thread with only the PR URL (${prUrl3}).` : "To continue, re-issue `agent:ship` in this thread with the task \u2014 round 0 runs again on the same branch.";
3458
+ const continuation = idle ? "The next reply in this thread continues the unit." : prUrl3 ? `The pipeline kept its task, branch and pull request; the next reply in this thread continues the review loop at ${prUrl3}.` : "The pipeline kept its task and branch; the next reply in this thread starts round 0 again on that branch.";
3452
3459
  const opening = cause === "container_replaced" ? "\u26A0\uFE0F The resident container running this pipeline's child was replaced (a deploy's image swap) and the child could not resume, so the pipeline stopped." : cause === "sandbox_fault" ? "\u26A0\uFE0F The sandbox running this pipeline's child failed and the child could not resume, so the pipeline stopped." : cause === "bot_restart" ? "\u26A0\uFE0F The bot restarted while this ship pipeline was running, so the pipeline stopped." : "\u26A0\uFE0F This ship pipeline's child was interrupted and could not resume, so the pipeline stopped.";
3453
- return `${opening} ${stands} ${reissue}`;
3460
+ return `${opening} ${stands} ${continuation}`;
3454
3461
  }
3455
3462
  function planIdOf(planPath) {
3456
3463
  const base = planPath.split("/").pop() ?? "";
@@ -4728,7 +4735,7 @@ function analyzeRunFriction(events, opts = {}) {
4728
4735
  findings.push({
4729
4736
  category: "infra_failure",
4730
4737
  severity: "high",
4731
- summary: `no result for tool call (run ended mid-tool): ${event.summary}`,
4738
+ summary: `no result for tool call (the run ended mid-tool): ${event.summary}`,
4732
4739
  tool: event.tool,
4733
4740
  eventIndex: index,
4734
4741
  ...durationMs !== void 0 ? { durationMs } : {},
@@ -5299,7 +5306,7 @@ function redactHandoff(h, redact = redactSecrets) {
5299
5306
  function line(s) {
5300
5307
  return s.replace(/\s*\n\s*/g, " ");
5301
5308
  }
5302
- function cell2(s) {
5309
+ function cell(s) {
5303
5310
  return line(s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|"));
5304
5311
  }
5305
5312
  function entryLines(h) {
@@ -5331,7 +5338,7 @@ function prLink(pr) {
5331
5338
  function renderHandoffLedgerRows(h, ctx) {
5332
5339
  const link = prLink(ctx.pr);
5333
5340
  const source2 = `${ctx.unitId} handoff${link ? ` (${link})` : ""}`;
5334
- return entryLines(h).map((e) => `| ${cell2(e.row)} | ${cell2(source2)} | open |`).join("\n");
5341
+ return entryLines(h).map((e) => `| ${cell(e.row)} | ${cell(source2)} | open |`).join("\n");
5335
5342
  }
5336
5343
  function renderHandoffComment(h, ctx) {
5337
5344
  if (isEmptyHandoff(h)) return void 0;
@@ -5514,6 +5521,18 @@ function isCoordinatorUnit(v) {
5514
5521
  if (r.startedAt !== void 0 && !isFinite(r.startedAt)) return false;
5515
5522
  return true;
5516
5523
  }
5524
+ async function sendUnitNudge(workflow, key) {
5525
+ const instance = key.instanceId;
5526
+ if (!workflow) return { kind: "no-binding", instance };
5527
+ const type = unitNudgeEventType(key);
5528
+ try {
5529
+ const handle2 = await workflow.get(instance);
5530
+ await handle2.sendEvent({ type, payload: {} });
5531
+ return { kind: "sent", instance, type };
5532
+ } catch (err2) {
5533
+ return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
5534
+ }
5535
+ }
5517
5536
  async function sendChecksSettled(workflow, instance, headSha, settledAt) {
5518
5537
  if (!workflow) return { kind: "no-binding", instance };
5519
5538
  const type = checksSettledEventType(headSha);
@@ -5550,12 +5569,13 @@ async function sendChildSignal(workflow, signal2) {
5550
5569
  return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
5551
5570
  }
5552
5571
  }
5553
- var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, UNIT_PATTERN, UNIT_KEY_PATTERN, CHILD_INTERRUPTED_EVENT_PREFIX, CHILD_RESUMED_EVENT_PREFIX, THREAD_EVENT_MAX_BYTES, serializedBytes, isThreadEventStagedFile, isThreadEventAttachment, isThreadEventMode, UNIT_NUDGE_EVENT_PREFIX, CHECKS_SETTLED_EVENT_PREFIX, PULL_MERGED_EVENT_PREFIX, IDLE_WHY_MAX, REPO_SLUG, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isCount, isObject, isPr, isResume, isThread, isRoundGate, isUnitIdle, isSegment, isUnitWakeAnswer;
5572
+ var COORDINATOR_IDENTITY, COORDINATOR_STEP_ACTION, PLAN_MERGE_ACTION, COORDINATOR_STEP_PATH_PREFIX, INSTANCE_ID_PATTERN, STEP_NAME_PATTERN, IDEMPOTENCY_KEY_PATTERN, UNIT_PATTERN, UNIT_KEY_PATTERN, CHILD_INTERRUPTED_EVENT_PREFIX, CHILD_RESUMED_EVENT_PREFIX, THREAD_EVENT_MAX_BYTES, serializedBytes, isThreadEventStagedFile, isThreadEventAttachment, isThreadEventMode, UNIT_NUDGE_EVENT_PREFIX, CHECKS_SETTLED_EVENT_PREFIX, PULL_MERGED_EVENT_PREFIX, IDLE_WHY_MAX, REPO_SLUG, MAX_TEXT, MAX_REPORT, MAX_ROUNDS, isText, isOptionalText, isFinite, isCount, isObject, isPr, isResume, isThread, isRoundGate, isHumanGatePending, isUnitIdle, isSegment, isUnitWakeAnswer;
5554
5573
  var init_contract2 = __esm({
5555
5574
  "../../src/core/coordinator/contract.ts"() {
5556
5575
  "use strict";
5557
5576
  init_budgets();
5558
5577
  init_verbosity();
5578
+ init_reviewVerdict();
5559
5579
  init_coordinator();
5560
5580
  init_handoff();
5561
5581
  COORDINATOR_IDENTITY = "coordinator";
@@ -5591,13 +5611,14 @@ var init_contract2 = __esm({
5591
5611
  isResume = (v) => isObject(v) && isFinite(v.pr) && (v.headSha === void 0 || isText(v.headSha)) && (v.url === void 0 || isText(v.url, 2048));
5592
5612
  isThread = (v) => isObject(v) && isText(v.threadKey) && isOptionalText(v.sourceUrl);
5593
5613
  isRoundGate = (v) => isObject(v) && isAddressSeverity(v.level) && Array.isArray(v.findings) && v.findings.every((f) => typeof f === "string");
5594
- isUnitIdle = (v) => isObject(v) && isText(v.why, IDLE_WHY_MAX) && isFinite(v.at) && isCount(v.renewalsLeft) && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && (v.spendUsd === null || isFinite(v.spendUsd)) && (v.handoff === void 0 || isHandoffShape(v.handoff)) && isCount(v.wakes);
5614
+ isHumanGatePending = (v) => isObject(v) && isObject(v.pr) && typeof v.pr.number === "number" && Number.isInteger(v.pr.number) && v.pr.number > 0 && isText(v.pr.url) && typeof v.round === "number" && Number.isInteger(v.round) && v.round >= 1 && Array.isArray(v.findings) && v.findings.every((finding) => isFindingShape(finding)) && (v.verdict === "approve" || v.verdict === "request_changes") && (v.reviewRunId === void 0 || isText(v.reviewRunId)) && (v.headSha === void 0 || isText(v.headSha)) && (v.askedAt === void 0 || isFinite(v.askedAt));
5615
+ isUnitIdle = (v) => isObject(v) && isText(v.why, IDLE_WHY_MAX) && isFinite(v.at) && isCount(v.renewalsLeft) && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && (v.spendUsd === null || isFinite(v.spendUsd)) && (v.handoff === void 0 || isHandoffShape(v.handoff)) && (v.humanGate === void 0 || isHumanGatePending(v.humanGate)) && isCount(v.wakes);
5595
5616
  isSegment = (v) => isObject(v) && typeof v.index === "number" && Number.isInteger(v.index) && v.index >= 2 && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && typeof v.at === "number";
5596
5617
  isUnitWakeAnswer = (v) => {
5597
5618
  if (!isObject(v)) return false;
5598
5619
  if (v.kind === "answered") return typeof v.reply === "string";
5599
5620
  if (v.kind === "stopped" || v.kind === "expired") return true;
5600
- return v.kind === "segment" && typeof v.index === "number" && Number.isInteger(v.index) && v.index >= 1 && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && (v.spendUsd === null || isFinite(v.spendUsd)) && (v.handoff === void 0 || isHandoffShape(v.handoff)) && Array.isArray(v.texts) && v.texts.every((text) => typeof text === "string") && Array.isArray(v.senders) && v.senders.every((sender) => isText(sender)) && (v.leaseMs === void 0 || isFinite(v.leaseMs) && v.leaseMs > 0);
5621
+ return v.kind === "segment" && typeof v.index === "number" && Number.isInteger(v.index) && v.index >= 1 && (v.from === void 0 || isText(v.from)) && (v.runId === void 0 || isText(v.runId)) && (v.spendUsd === null || isFinite(v.spendUsd)) && (v.handoff === void 0 || isHandoffShape(v.handoff)) && Array.isArray(v.texts) && v.texts.every((text) => typeof text === "string") && Array.isArray(v.senders) && v.senders.every((sender) => isText(sender)) && (v.leaseMs === void 0 || isFinite(v.leaseMs) && v.leaseMs > 0) && (v.humanGate === void 0 || isHumanGatePending(v.humanGate));
5601
5622
  };
5602
5623
  }
5603
5624
  });
@@ -11659,13 +11680,13 @@ function fleetBusyRunEndedLine(run2, thread, err2) {
11659
11680
  });
11660
11681
  }
11661
11682
  function fleetBusyExhaustedMessage(waitedMs) {
11662
- return `sandbox fleet busy \u2014 no free per-thread sandbox after waiting ${Math.round(waitedMs / 1e3)}s (the fleet's max_instances is reached); try again in a few minutes`;
11683
+ return `this is a bug: the sandbox fleet had no free per-thread sandbox after waiting ${Math.round(waitedMs / 1e3)}s (the fleet's max_instances is reached), and no automatic queue remained`;
11663
11684
  }
11664
11685
  function isWaitReason(reason2) {
11665
11686
  return reason2 === FLEET_BUSY_REASON || reason2 === SANDBOX_STARTING_REASON || reason2 === RUNTIME_BUSY_REASON;
11666
11687
  }
11667
11688
  function startWaitExhaustedMessage(waitedMs) {
11668
- return `sandbox not ready \u2014 the thread's container did not finish starting within ${Math.round(waitedMs / 1e3)}s; try again in a few minutes`;
11689
+ return `this is a bug: the thread's sandbox did not finish starting within ${Math.round(waitedMs / 1e3)}s, and no automatic start wait remained`;
11669
11690
  }
11670
11691
  function runtimeBusyExhaustedMessage(waitedMs) {
11671
11692
  return `sandbox busy \u2014 the thread's container did not accept a connection within ${Math.round(waitedMs / 1e3)}s (the platform refused every connect of the wait; a command saturating its cores is one cause, an idle container has met it too); retry`;
@@ -15727,9 +15748,7 @@ var init_resident = __esm({
15727
15748
  * the factory binds the thread to it (loudly) instead of asking. Undefined
15728
15749
  * from a Worker predating that field → the dispatcher asks as before. */
15729
15750
  constructor(resource, defaultRef) {
15730
- super(
15731
- `the ${resource} resident needs a branch for this thread: no ref is bound yet. Name the branch to work on (e.g. "on main") and try again.`
15732
- );
15751
+ super(`Which branch should the ${resource} resident use for this thread (for example, "main")?`);
15733
15752
  this.resource = resource;
15734
15753
  this.defaultRef = defaultRef;
15735
15754
  this.name = "ResidentNeedsRefError";
@@ -16259,7 +16278,7 @@ var init_resident = __esm({
16259
16278
  const callTimeoutMs = opts.callTimeoutMs ?? BASH_TIMEOUT_MS;
16260
16279
  const stillGone = (data) => classifyError(
16261
16280
  new ExecInfraError(
16262
- `resident ${route2}: worktree still unavailable after a re-attach (${String(data.error ?? "")}) \u2014 the resident may be mid-restore; try again shortly.`,
16281
+ `this is a bug: resident ${route2} still had no worktree after its automatic re-attach (${String(data.error ?? "")}); no further restore wait was scheduled`,
16263
16282
  "refused"
16264
16283
  ),
16265
16284
  { kind: "infra", code: "attach" }
@@ -17465,20 +17484,83 @@ async function fetchPullRequestTitleBody(pr) {
17465
17484
  if (!data || typeof data.title !== "string") return void 0;
17466
17485
  return { title: data.title, body: typeof data.body === "string" ? data.body : "" };
17467
17486
  }
17468
- async function fetchPullRequestReviews(pr) {
17469
- const token2 = await resolveGithubToken().catch(() => null);
17470
- let res;
17487
+ function nextGithubPage(link) {
17488
+ for (const entry of link?.split(",") ?? []) {
17489
+ const target = /<([^>]+)>/.exec(entry)?.[1];
17490
+ const relations = /(?:^|;)\s*rel="([^"]+)"/.exec(entry)?.[1]?.split(/\s+/) ?? [];
17491
+ if (target !== void 0 && relations.includes("next")) return target;
17492
+ }
17493
+ return void 0;
17494
+ }
17495
+ function isGithubListPage(url, base) {
17496
+ let candidate;
17497
+ let requested;
17471
17498
  try {
17472
- res = await fetch(`https://api.github.com/repos/${pr.repo}/pulls/${pr.number}/reviews?per_page=100`, {
17473
- headers: apiHeaders(token2),
17474
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
17475
- });
17499
+ candidate = new URL(url);
17500
+ requested = new URL(base);
17476
17501
  } catch {
17477
- return void 0;
17502
+ return false;
17478
17503
  }
17479
- if (!res.ok) return void 0;
17480
- const rows = await res.json().catch(() => null);
17481
- if (!Array.isArray(rows)) return void 0;
17504
+ if (candidate.origin !== requested.origin || candidate.username !== "" || candidate.password !== "" || candidate.hash !== "")
17505
+ return false;
17506
+ if (candidate.pathname === requested.pathname) return true;
17507
+ const requestedParts = requested.pathname.split("/").filter(Boolean);
17508
+ const candidateParts = candidate.pathname.split("/").filter(Boolean);
17509
+ const resourcePath = requestedParts.slice(3);
17510
+ return requestedParts.length >= 4 && requestedParts[0] === "repos" && candidateParts.length === resourcePath.length + 2 && candidateParts[0] === "repositories" && /^\d+$/.test(candidateParts[1] ?? "") && candidateParts.slice(2).every((part, index) => part === resourcePath[index]);
17511
+ }
17512
+ async function fetchGithubListPages(base, token2) {
17513
+ const rows = [];
17514
+ const seen = /* @__PURE__ */ new Set();
17515
+ let url = `${base}?per_page=100`;
17516
+ while (url !== void 0) {
17517
+ if (!isGithubListPage(url, base) || seen.has(url)) return void 0;
17518
+ seen.add(url);
17519
+ let res;
17520
+ try {
17521
+ res = await fetch(url, {
17522
+ headers: apiHeaders(token2),
17523
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
17524
+ });
17525
+ } catch {
17526
+ return void 0;
17527
+ }
17528
+ if (!res.ok) return void 0;
17529
+ const page2 = await res.json().catch(() => null);
17530
+ if (!Array.isArray(page2)) return void 0;
17531
+ rows.push(...page2);
17532
+ url = nextGithubPage(res.headers.get("link"));
17533
+ }
17534
+ return rows;
17535
+ }
17536
+ async function fetchPullRequestComments(pr) {
17537
+ const token2 = await resolveGithubToken().catch(() => null);
17538
+ const base = `https://api.github.com/repos/${pr.repo}/issues/${pr.number}/comments`;
17539
+ const rows = await fetchGithubListPages(base, token2);
17540
+ if (rows === void 0) return void 0;
17541
+ return rows.flatMap((raw) => {
17542
+ const row2 = raw;
17543
+ if (typeof row2.id !== "number" || typeof row2.user?.login !== "string" || typeof row2.user.type !== "string" || typeof row2.created_at !== "string" || typeof row2.body !== "string")
17544
+ return [];
17545
+ return [
17546
+ {
17547
+ id: row2.id,
17548
+ author: {
17549
+ login: row2.user.login,
17550
+ ...typeof row2.user.id === "number" ? { id: row2.user.id } : {},
17551
+ type: row2.user.type
17552
+ },
17553
+ createdAt: row2.created_at,
17554
+ body: row2.body
17555
+ }
17556
+ ];
17557
+ });
17558
+ }
17559
+ async function fetchPullRequestReviews(pr) {
17560
+ const token2 = await resolveGithubToken().catch(() => null);
17561
+ const base = `https://api.github.com/repos/${pr.repo}/pulls/${pr.number}/reviews`;
17562
+ const rows = await fetchGithubListPages(base, token2);
17563
+ if (rows === void 0) return void 0;
17482
17564
  return rows.flatMap((r) => {
17483
17565
  const row2 = r;
17484
17566
  if (typeof row2.state !== "string") return [];
@@ -17492,6 +17574,7 @@ async function fetchPullRequestReviews(pr) {
17492
17574
  } : {},
17493
17575
  state: row2.state,
17494
17576
  ...typeof row2.commit_id === "string" ? { commitId: row2.commit_id } : {},
17577
+ ...typeof row2.submitted_at === "string" ? { submittedAt: row2.submitted_at } : {},
17495
17578
  body: typeof row2.body === "string" ? row2.body : ""
17496
17579
  }
17497
17580
  ];
@@ -19957,11 +20040,11 @@ function resolveRange(daysParam, now = new Date(systemClock())) {
19957
20040
  function buildBillerTieOuts(billers, invoices, cells, range) {
19958
20041
  return billers.map(({ name, invoice }) => {
19959
20042
  const attributed2 = /* @__PURE__ */ new Map();
19960
- for (const cell4 of cells) {
19961
- if (cell4.day < range.from || cell4.day > range.to) continue;
19962
- for (const [ref, m] of Object.entries(cell4.usage.byModel)) {
20043
+ for (const cell3 of cells) {
20044
+ if (cell3.day < range.from || cell3.day > range.to) continue;
20045
+ for (const [ref, m] of Object.entries(cell3.usage.byModel)) {
19963
20046
  if (billerOfRef(ref) !== name) continue;
19964
- const day2 = attributed2.get(cell4.day) ?? { usd: 0, fee: 0, hasFee: false, unpricedTokens: 0 };
20047
+ const day2 = attributed2.get(cell3.day) ?? { usd: 0, fee: 0, hasFee: false, unpricedTokens: 0 };
19965
20048
  if (typeof m.usd === "number") {
19966
20049
  day2.usd += m.usd;
19967
20050
  if (m.feeUsd !== void 0) {
@@ -19971,7 +20054,7 @@ function buildBillerTieOuts(billers, invoices, cells, range) {
19971
20054
  } else {
19972
20055
  day2.unpricedTokens += m.inputTokens + m.outputTokens + m.cacheReadTokens + m.cacheWriteTokens;
19973
20056
  }
19974
- attributed2.set(cell4.day, day2);
20057
+ attributed2.set(cell3.day, day2);
19975
20058
  }
19976
20059
  }
19977
20060
  const invoiced = /* @__PURE__ */ new Map();
@@ -21444,7 +21527,7 @@ var init_metricsService = __esm({
21444
21527
  init_budgets();
21445
21528
  init_metrics();
21446
21529
  init_clock();
21447
- METRICS_OFF_MESSAGE = "Run metrics aren't configured \u2014 set metrics.dataset in config (beside the costs block's Cloudflare account and analytics token) to enable this view.";
21530
+ METRICS_OFF_MESSAGE = "Metrics by run aren't configured \u2014 metrics.dataset in config (beside the costs block's Cloudflare account and analytics token) enables this view.";
21448
21531
  NullMetricsService = class {
21449
21532
  report() {
21450
21533
  return Promise.reject(new Error(METRICS_OFF_MESSAGE));
@@ -22960,7 +23043,7 @@ var init_artifacts = __esm({
22960
23043
  if (!read.ok)
22961
23044
  throw new CommandError(
22962
23045
  "unavailable",
22963
- `the rules were applied to ${bucket} but reading them back failed \u2014 ${read.problem}; run \`artifacts lifecycle\` again to confirm`
23046
+ `this is a bug: the rules were applied to ${bucket}, but their read-back failed (${read.problem}) and no automatic confirmation was completed`
22964
23047
  );
22965
23048
  const match = rulesMatch(read.value, rules);
22966
23049
  if (!match.ok)
@@ -23480,7 +23563,7 @@ var init_config4 = __esm({
23480
23563
  destructive: destructiveBeyondMe,
23481
23564
  risk: configRisk
23482
23565
  },
23483
- describe: "Drop every runtime override of a channel (gated), of yourself (your GitHub binding stays \u2014 it is an identity admin's write), or of a thread (gated like the channel); `config clear user --user <id>` removes one person's GitHub binding (identity admins). Static config.yaml values show through again.",
23566
+ describe: "Drops every runtime override of a channel (gated), of yourself (your GitHub binding stays \u2014 it is an identity admin's write), or of a thread (gated like the channel); `config clear user --user <id>` removes one person's GitHub binding (identity admins). Static config.yaml values show through again.",
23484
23567
  render: (output) => {
23485
23568
  const scope = output.scope;
23486
23569
  return scope === "user" ? "Cleared the user's GitHub binding." : `Cleared ${who(scope)} overrides.`;
@@ -23724,7 +23807,7 @@ function renderCostsBy(output, surface = "text") {
23724
23807
  const columns = surface === "chat" ? [] : [
23725
23808
  `${String(o.dimension).padEnd(40)} ${count.padStart(6)} ${"LLM".padStart(10)}${cloud ? "cloud".padStart(10) : ""} ${"total".padStart(10)} share`
23726
23809
  ];
23727
- const where = coverage2.historyOn === false ? "run history is off \u2014 no runs to attribute" : `runs from ${String(coverage2.from)}${coverage2.clamped === true ? ` (earlier days are past the history's ${String(coverage2.retentionDays)}-day window)` : ""}${typeof o.pending === "number" && o.pending > 0 ? ` \xB7 ${o.pending} run(s) still being priced` : ""}`;
23810
+ const where = coverage2.historyOn === false ? "history is off \u2014 no runs to attribute" : `runs from ${String(coverage2.from)}${coverage2.clamped === true ? ` (earlier days are past the history's ${String(coverage2.retentionDays)}-day window)` : ""}${typeof o.pending === "number" && o.pending > 0 ? ` \xB7 ${o.pending} run(s) still being priced` : ""}`;
23728
23811
  const tieOut = typeof rec2.comparedDays === "number" && rec2.comparedDays > 0 ? `LLM attributed ${money(rec2.attributedLlmUsd)} of ${money(rec2.workspaceLlmUsd)} on the workspace over ${rec2.comparedDays} day(s)` + (typeof rec2.unattributedLlmUsd === "number" && rec2.unattributedLlmUsd < 0 ? ` \xB7 ${money(-rec2.unattributedLlmUsd)} more attributed than the workspace figure` : ` \xB7 ${money(rec2.unattributedLlmUsd)} unattributed`) : "no day in range has a workspace LLM figure to compare against";
23729
23812
  const cloudLine = cloud ? ` \xB7 cloud allocated ${money(rec2.cloudAllocatedUsd)}${typeof rec2.cloudUnallocatedUsd === "number" && rec2.cloudUnallocatedUsd > 0 ? ` \xB7 ${money(rec2.cloudUnallocatedUsd)} on days with no runs` : ""}` : "";
23730
23813
  return [
@@ -23767,7 +23850,7 @@ var init_costs2 = __esm({
23767
23850
  if (message4 === COSTS_OFF_MESSAGE) throw new CommandError("unavailable", message4);
23768
23851
  throw new CommandError(
23769
23852
  "busy",
23770
- `costs snapshot not taken: ${message4} \u2014 the previous snapshot still serves; try again`
23853
+ `costs snapshot not taken: ${message4} \u2014 the previous snapshot still serves, and the scheduled loop will take the next snapshot`
23771
23854
  );
23772
23855
  }
23773
23856
  }
@@ -24203,7 +24286,7 @@ ${formatDeployResults(result.results, result.notAttempted)}`
24203
24286
  if (drift.length > 0)
24204
24287
  throw new CommandError(
24205
24288
  "conflict",
24206
- `Worker configs are not the render of their templates \u2014 ${drift.map((f) => `${f.path} (${f.status})`).join(", ")}; run \`npm run deploy:gen\` and commit the result`
24289
+ `this is a bug: Worker configs are not the render of their templates \u2014 ${drift.map((f) => `${f.path} (${f.status})`).join(", ")} \u2014 and deploy did not repair or commit the generated files automatically`
24207
24290
  );
24208
24291
  const output = { profile: { origin: loaded.origin, path: loaded.path }, files };
24209
24292
  return output;
@@ -24618,7 +24701,7 @@ function patternSignature(f) {
24618
24701
  return "slow_model_turn:model_turn";
24619
24702
  case "infra_failure": {
24620
24703
  if (summary.startsWith("sandbox dead")) return "infra_failure:sandbox_dead";
24621
- const mid = /^no result for tool call \(run ended mid-tool\):\s*/.exec(summary);
24704
+ const mid = /^no result for tool call \(the run ended mid-tool\):\s*/.exec(summary);
24622
24705
  if (mid) return `infra_failure:mid-tool ${commandSignature(summary.slice(mid[0].length))}`;
24623
24706
  const during = /^exec infrastructure failed during\s*/.exec(summary);
24624
24707
  return `infra_failure:${commandSignature(during ? summary.slice(during[0].length) : summary)}`;
@@ -25180,7 +25263,7 @@ async function recentRecords(ledger, query) {
25180
25263
  function inProgressHint(diagnosis, finished) {
25181
25264
  if (!finished) return void 0;
25182
25265
  const midTool = diagnosis.findings.some(
25183
- (f) => f.category === "infra_failure" && f.summary.includes("run ended mid-tool")
25266
+ (f) => f.category === "infra_failure" && f.summary.includes("the run ended mid-tool")
25184
25267
  );
25185
25268
  return midTool ? "(hint: the stream ends on a tool call with no result \u2014 if this capture was taken mid-run, pass --in-progress)" : void 0;
25186
25269
  }
@@ -25201,7 +25284,7 @@ var init_friction = __esm({
25201
25284
  defineCommand9 = commandDefiner();
25202
25285
  positiveInt2 = z12.coerce.number().int().positive();
25203
25286
  repoSlug = z12.string().refine((s) => /^[\w.-]+\/[\w.-]+$/.test(s), "expected an owner/name slug");
25204
- NO_LEDGER_MESSAGE = "Run history is not configured in this deployment (`runHistory`), so there are no recent runs to analyze.";
25287
+ NO_LEDGER_MESSAGE = "History is not configured in this deployment (`runHistory`), so there are no recent runs to analyze.";
25205
25288
  NO_REPO_MESSAGE = "Set `selfImprovement.repo` (an `owner/name`) in config.yaml to tell `friction propose` where to file issues.";
25206
25289
  asJson2 = (r) => r;
25207
25290
  render = (output) => formatSelfImprovementReport(output);
@@ -25253,7 +25336,7 @@ var init_friction = __esm({
25253
25336
  openWorld: true,
25254
25337
  risk: (input) => dryRunRequested(input) ? PLAN_ONLY_RISK : "files issues on the tracker"
25255
25338
  },
25256
- describe: "Run the self-improvement step: cluster recent friction, dedupe against open issues, file the top proposals as labeled issues.",
25339
+ describe: "Clusters recent friction, deduplicates against open issues, and files the top proposals as labeled issues.",
25257
25340
  render,
25258
25341
  handler: async ({ options, caller, deps }) => {
25259
25342
  const ledger = await ledgerOf(deps);
@@ -25842,15 +25925,15 @@ function planCallback(ticket, identity2, now) {
25842
25925
  function refusalMessage(r) {
25843
25926
  switch (r.kind) {
25844
25927
  case "not_authorizing":
25845
- return "This sign-in did not start from a connect link, or the link was opened again since. Open the connect link and try again.";
25928
+ return "This is a bug: this sign-in did not start from a live connect link, and no fresh connect link was opened automatically.";
25846
25929
  case "oauth_failed":
25847
25930
  return `Sign-in with the server did not complete: ${r.reason}.`;
25848
25931
  case "not_found":
25849
- return "This connect link is not known. Ask for a new one with `mcp connect <name>`.";
25932
+ return "This is a bug: this connect link is not known, and no fresh connect link was opened automatically.";
25850
25933
  case "expired":
25851
- return "This connect link has expired (links last 10 minutes). Ask for a new one with `mcp connect <name>`.";
25934
+ return "This is a bug: this connect link expired after 10 minutes, and no fresh connect link was opened automatically.";
25852
25935
  case "used":
25853
- return "This connect link was already used. If the credential needs replacing, ask for a new one with `mcp connect <name>`.";
25936
+ return "This connect link was already used; no credential was changed by this visit.";
25854
25937
  case "cancelled":
25855
25938
  return "This connect link was cancelled.";
25856
25939
  case "wrong_identity":
@@ -25903,7 +25986,7 @@ async function readCapped2(res, max, signal2) {
25903
25986
  let total = 0;
25904
25987
  try {
25905
25988
  for (; ; ) {
25906
- if (signal2?.aborted) throw new McpError("timeout", "run aborted");
25989
+ if (signal2?.aborted) throw new McpError("timeout", "the run was aborted");
25907
25990
  const { value, done } = await reader.read();
25908
25991
  if (done) break;
25909
25992
  if (value) {
@@ -26399,7 +26482,7 @@ function needsRefresh(cred, now) {
26399
26482
  async function refreshCredential(fetchImpl, cred, now) {
26400
26483
  if (!cred.refreshToken)
26401
26484
  throw new OAuthError(
26402
- "the access token expired and the server issued no refresh token \u2014 run `mcp connect` to sign in again"
26485
+ "this is a bug: the access token expired, the server issued no refresh token, and no fresh sign-in was opened automatically"
26403
26486
  );
26404
26487
  const tokens = await tokenRequest(fetchImpl, cred.tokenEndpoint, {
26405
26488
  grant_type: "refresh_token",
@@ -27616,7 +27699,7 @@ var init_service = __esm({
27616
27699
  } catch (err2) {
27617
27700
  return { name: r.name, unavailable: `secret store: ${err2 instanceof Error ? err2.message : String(err2)}` };
27618
27701
  }
27619
- if (!sealed) return { name: r.name, unavailable: "no credential stored \u2014 run `mcp connect`" };
27702
+ if (!sealed) return { name: r.name, unavailable: "no credential is stored, so this server is unavailable" };
27620
27703
  try {
27621
27704
  const stored = parseStoredCredential(await openCredential(this.opts.key, sealed));
27622
27705
  if (stored.kind === "bearer") return { spec: { ...base, auth: { type: "bearer", token: stored.token } } };
@@ -27841,7 +27924,7 @@ var init_mcp = __esm({
27841
27924
  // Org-only by definition: it changes what every run in the org can reach,
27842
27925
  // so it is destructive at its every input (record 0057's audit rule).
27843
27926
  annotations: { destructive: true, risk: () => "adds or moves a server every run in the scope can use" },
27844
- describe: "Re-issue a person's MCP server in the org tier (admins): the same name, URL and auth, added by you; a bearer/oauth server gets a fresh org connect link for you to complete \u2014 the person's credential is never copied.",
27927
+ describe: "Promote a person's MCP server into the org tier (admins): the same name, URL and auth, added by you; a bearer/oauth server gets a fresh org connect link for you to complete \u2014 the person's credential is never copied.",
27845
27928
  render: renderAdd,
27846
27929
  settle: (output, { deps }) => settleConnect(deps, output),
27847
27930
  handler: async ({ args, options, caller, deps }) => {
@@ -29679,7 +29762,7 @@ async function fencedTarget(args) {
29679
29762
  if (args.refusePending && checks.total === 0)
29680
29763
  throw new CommandError(
29681
29764
  "conflict",
29682
- `no check reported at the head of ${name} \u2014 nothing was done; retry once CI registers (a repository with no CI stays a person's click on GitHub)`
29765
+ `no check reported at the head of ${name} \u2014 nothing was done; this is a bug when the repository has CI because no automatic wait or event re-fire was scheduled (a repository with no CI stays a person's click on GitHub)`
29683
29766
  );
29684
29767
  if (checks.failed.length > 0)
29685
29768
  throw new CommandError(
@@ -29872,7 +29955,7 @@ var init_metrics2 = __esm({
29872
29955
  const message4 = err2 instanceof Error ? err2.message : String(err2);
29873
29956
  if (message4 === METRICS_OFF_MESSAGE) throw new CommandError("unavailable", message4);
29874
29957
  const kind = err2 instanceof Error ? err2.name : "Error";
29875
- throw new CommandError("unavailable", `run metrics unavailable: ${kind}: ${message4}`);
29958
+ throw new CommandError("unavailable", `metrics by run unavailable: ${kind}: ${message4}`);
29876
29959
  }
29877
29960
  }
29878
29961
  });
@@ -29907,7 +29990,7 @@ function runLine(row2, now, surface) {
29907
29990
  }
29908
29991
  function prLine(row2, surface) {
29909
29992
  const name = `${row2.pr.repo}#${row2.pr.number}`;
29910
- const owner = row2.owner.unitKey ?? (row2.owner.runId ? `run ${shortId(row2.owner.runId)}` : "a person");
29993
+ const owner = row2.owner.unitKey ?? (row2.owner.runId ? `the run ${shortId(row2.owner.runId)}` : "a person");
29911
29994
  const health = healthWords(row2.health) || "-";
29912
29995
  if (surface === "chat") return `\u2022 ${name} \u2014 ${health} \xB7 ${owner}`;
29913
29996
  return `${name.padEnd(32)} ${health.padEnd(24)} ${owner}`.trimEnd();
@@ -30399,7 +30482,7 @@ function defineOp(op) {
30399
30482
  // agent, or the exec grant a token was minted with (policy.ts).
30400
30483
  resource: () => ({ type: "agent", name: "coding" }),
30401
30484
  effect: "write",
30402
- describe: `Run the repo's onboarded ${op} command with zero model turns (needs coding-agent access; the ref must be a plausible branch).`,
30485
+ describe: `Executes the repo's onboarded ${op} command with zero model turns (needs coding-agent access; the ref must be a plausible branch).`,
30403
30486
  render: renderOp,
30404
30487
  handler: async ({ args, caller, deps, span }) => {
30405
30488
  if (!await deps.repo.canUseRepo(caller.id, args.slug))
@@ -30445,7 +30528,7 @@ ${line4}` : ""}`);
30445
30528
  case "error":
30446
30529
  throw new CommandError(
30447
30530
  "unavailable",
30448
- result.transient === true ? `${result.message} \u2014 the resident was unavailable for a moment; re-run the command` : result.message
30531
+ result.transient === true ? `${result.message} \u2014 the resident was unavailable for a moment; Switchboard ended the command as unavailable` : result.message
30449
30532
  );
30450
30533
  }
30451
30534
  }
@@ -30633,7 +30716,7 @@ ${line4}`
30633
30716
  `\u2022 ${n(w.backupObjects)} snapshot backup object(s) in R2${ids}`,
30634
30717
  `\u2022 ${n(w.r2Objects)} object(s) under the resident's R2 prefix`,
30635
30718
  `\u2022 ${n(w.threadBindings)} thread binding(s) and the container (currently \`${String(w.container ?? "?")}\`)`,
30636
- `Nothing was changed. Run \`repo offboard ${slug}\` to execute.`
30719
+ `Nothing was changed. Without \`--dry-run\`, \`repo offboard ${slug}\` performs this removal.`
30637
30720
  ].join("\n");
30638
30721
  }
30639
30722
  const errors = Array.isArray(o.errors) ? o.errors : [];
@@ -30673,7 +30756,7 @@ ${line4}`
30673
30756
  snap ? `\u2022 discard the snapshot from ${String(snap.createdAt ?? "?")} (${n(discards.backupObjects)} backup object(s); ids ${String(snap.mirrorBackupId ?? "?")}, ${String(snap.checkoutBackupId ?? "?")})` : "\u2022 discard no snapshot (none recorded)",
30674
30757
  `\u2022 reprovision from scratch on \`${String(reprov.defaultRef ?? "?")}\` (budget ${n(reprov.provisioningTimeoutMs)}ms)`,
30675
30758
  `\u2022 keep the registry record and ${n(keeps.threadBindings)} thread binding(s)`,
30676
- `Nothing was changed. Run \`repo rebuild ${slug}\` to execute.`
30759
+ `Nothing was changed. Without \`--dry-run\`, \`repo rebuild ${slug}\` performs this rebuild.`
30677
30760
  ].join("\n");
30678
30761
  }
30679
30762
  return `\u{1F504} Rebuilding \`${slug}\`: discarded ${n(o.backupObjectsDeleted)} backup object(s); reprovisioning from scratch on \`${String(reprov.defaultRef ?? "?")}\` (state \`onboarding\` \u2014 I'll report here when it is warm or has failed; \`repo list\` shows the live state meanwhile).`;
@@ -31619,7 +31702,7 @@ function newestFinished(a, b) {
31619
31702
  }
31620
31703
  function hostedSealAnswer(units, runnerStopped) {
31621
31704
  const lines = units.map((u) => {
31622
- const how = u.ending ? u.ending.kind : u.threadKey !== void 0 ? "no ending was recorded \u2014 re-issue `agent:ship` in its thread to continue" : "not started";
31705
+ const how = u.ending ? u.ending.kind : u.threadKey !== void 0 ? "no ending was recorded \u2014 the next reply in its thread continues the unit" : "not started";
31623
31706
  return `${u.unit} \u2014 ${how}${u.pr ? ` \u2014 ${u.pr.url}` : ""}`;
31624
31707
  });
31625
31708
  return [
@@ -32260,10 +32343,10 @@ async function abridgerOf(deps) {
32260
32343
  async function assertVisible(deps, id, caller) {
32261
32344
  const runs = await deps.review.runs();
32262
32345
  const res = await runs.getRun(id);
32263
- if (!res.ok) throw new CommandError("not_found", "run not found");
32346
+ if (!res.ok) throw new CommandError("not_found", "no run found");
32264
32347
  const actor = caller.actor;
32265
32348
  if (!authorize(actor, "runs:read", runResource(res.value)).allow)
32266
- throw new CommandError("not_found", "run not found", "policy");
32349
+ throw new CommandError("not_found", "no run found", "policy");
32267
32350
  }
32268
32351
  function refused2(err2) {
32269
32352
  if (err2 instanceof AbridgeRefusal) throw new CommandError(err2.code, err2.message);
@@ -32283,13 +32366,13 @@ var init_review = __esm({
32283
32366
  init_runsService();
32284
32367
  defineCommand19 = commandDefiner();
32285
32368
  runId = z22.string().regex(RUN_ID_PATTERN);
32286
- ABRIDGE_OFF_MESSAGE = "The abridged reading diff is off in this deployment: it needs the `meat` binary on the bot host, the Anthropic provider's credential, `review.readingDiff.provider` not `off`, and run history to store it.";
32369
+ ABRIDGE_OFF_MESSAGE = "The abridged reading diff is off in this deployment: it needs the `meat` binary on the bot host, the Anthropic provider's credential, `review.readingDiff.provider` not `off`, plus history to store it.";
32287
32370
  reviewAbridge = defineCommand19({
32288
32371
  id: "review.abridge",
32289
32372
  // Hidden unless the abridging can happen here (the binary, the credential,
32290
32373
  // the switch) AND there is a record to append to.
32291
32374
  enabledWhen: (caps) => caps.runHistory && caps.readingDiffAbridge,
32292
- args: [{ name: "id", schema: runId, describe: "run id of a finished PR review" }],
32375
+ args: [{ name: "id", schema: runId, describe: "id of a finished PR review run" }],
32293
32376
  options: z22.object({
32294
32377
  model: z22.string().min(1).optional().describe("meat's model (default review.readingDiff.meatModel; without either the abridge fails naming the key)"),
32295
32378
  force: flag.optional().describe("recompute even when an abridged diff is already stored, or retry a failed one"),
@@ -32329,7 +32412,7 @@ import { z as z23 } from "zod";
32329
32412
  function unwrap2(res, what = "run") {
32330
32413
  if (res.ok) return res.value;
32331
32414
  if (res.error === "hosted") throw new CommandError("conflict", HOSTED_STOP_REFUSAL);
32332
- throw new CommandError(res.error, res.error === "not_found" ? `${what} not found` : "run already finished");
32415
+ throw new CommandError(res.error, res.error === "not_found" ? `no ${what} found` : "the run already finished");
32333
32416
  }
32334
32417
  async function getVisibleRun(runs, id, caller, action, deps, commandId, opts = {}) {
32335
32418
  const view = unwrap2(await runs.getRun(id, opts));
@@ -32337,7 +32420,7 @@ async function getVisibleRun(runs, id, caller, action, deps, commandId, opts = {
32337
32420
  const decision = authorize(actor, action, runResource(view));
32338
32421
  if (!decision.allow) {
32339
32422
  (deps.denied ?? logDenied)({ commandId, actorId: actor.id, action, reason: decision.reason });
32340
- throw new CommandError("not_found", "run not found");
32423
+ throw new CommandError("not_found", "no run found");
32341
32424
  }
32342
32425
  return view;
32343
32426
  }
@@ -32468,7 +32551,7 @@ var init_runs = __esm({
32468
32551
  defineCommand20 = commandDefiner();
32469
32552
  runId2 = z23.string().regex(RUN_ID_PATTERN);
32470
32553
  positiveInt3 = z23.coerce.number().int().positive();
32471
- idArg = { name: "id", schema: runId2, describe: "run id" };
32554
+ idArg = { name: "id", schema: runId2, describe: "id of the run" };
32472
32555
  HOSTED_STOP_REFUSAL = "this run is a pipeline; its units run in their own threads, so a soft stop ends nothing; `--mode hard` is the escape: it seals the run failed and releases the thread";
32473
32556
  NO_RUNS_NAME_PR = "no runs name this pull request";
32474
32557
  SESSION_SEARCH_DEFAULT_HITS = 10;
@@ -33036,8 +33119,8 @@ function capabilityLine(c) {
33036
33119
  `execution ${c.execution}`,
33037
33120
  `github ${onOff(c.github)}`,
33038
33121
  `memory ${onOff(c.memory)}`,
33039
- `run history ${onOff(c.runHistory)}`,
33040
- `run ledger ${onOff(c.runLedger)}`,
33122
+ `history ${onOff(c.runHistory)}`,
33123
+ `ledger ${onOff(c.runLedger)}`,
33041
33124
  `mcp ${onOff(c.mcp)}`,
33042
33125
  `costs ${onOff(c.costs)}`,
33043
33126
  `schedules ${onOff(c.schedules)}`,
@@ -34988,7 +35071,7 @@ function activityLine(e) {
34988
35071
  case "answer":
34989
35072
  return "answer ready";
34990
35073
  case "run_meta":
34991
- return "run context recorded";
35074
+ return "context for the run recorded";
34992
35075
  // published straight to the registry too — never arrives here
34993
35076
  case "lease":
34994
35077
  return "lease started";
@@ -35278,13 +35361,13 @@ var init_reply = __esm({
35278
35361
  live_agent_allowlist: (p) => `\u{1F6AB} You're not on the allowlist for the \`${p.agent}\` agent, whose run is in flight in this thread. Ask ${p.adminsHint} for access.`,
35279
35362
  elsewhere_agent_allowlist: (p) => `\u{1F6AB} You're not on the allowlist for the \`${p.agent}\` agent, whose run is in flight in this thread. Ask ${p.adminsHint} for access.`,
35280
35363
  repo_not_visible: (p) => `\u{1F4E6} \`${p.slug}\` is not a repository this installation can see \u2014 GitHub answered 404 \u2014 so I did not start ${aRun(p.agent)} for it. The repository is outside the Switchboard GitHub App installation (\`github_repos\` lists the reachable ones), or the name is wrong.`,
35281
- repo_unverified: (p) => p.via === "github" ? `\u26A0\uFE0F I couldn't verify \`${p.slug}\` against GitHub \u2014 it didn't answer \u2014 so I did not start ${aRun(p.agent)} rather than guess which repository you meant. Try again in a minute.` : `\u26A0\uFE0F I couldn't verify that \`${p.slug}\` is an onboarded repo \u2014 the resident registry didn't answer \u2014 so I did not start a *${p.agent}* run rather than guess which repo you meant. Try again in a minute, or name the repository by URL (https://github.com/${p.slug}) to run in a cold per-thread sandbox.`,
35364
+ repo_unverified: (p) => p.via === "github" ? `\u26A0\uFE0F This is a bug: I couldn't verify \`${p.slug}\` against GitHub because it did not answer, so I did not start ${aRun(p.agent)} and no automatic retry was scheduled.` : `\u26A0\uFE0F This is a bug: I couldn't verify that \`${p.slug}\` is an onboarded repo because the resident registry did not answer, so I did not start a *${p.agent}* run and no automatic fallback was started.`,
35282
35365
  repo_not_onboarded: (p) => `\u{1F4E6} \`${p.slug}\` is not onboarded as a resident, so I did not start a *${p.agent}* run for it. ${p.onboardHint} for a warm, deps-ready environment, or name the repository by URL (https://github.com/${p.slug}) to run in a cold per-thread sandbox.`,
35283
35366
  repo_access: (p) => `\u{1F6AB} You're not on the allowlist for the \`${p.repo}\` repo environment. Ask ${p.adminsHint} for access.`,
35284
- which_branch: (p) => `\u{1F33F} Which branch of \`${p.repo}\` should this thread work on? No branch is bound yet \u2014 reply naming one (e.g. "on main" or "on branch fix/login") and I'll pick it up from there.`,
35367
+ which_branch: (p) => `\u{1F33F} Which branch of \`${p.repo}\` should this thread work on? No branch is bound yet; a branch token such as \`branch:main\` will bind it.`,
35285
35368
  ship_thread_live: () => "\u{1F6AB} A pipeline is already running in this thread \u2014 one pipeline per thread. Follow the one in flight here, or start this one in a thread of its own.",
35286
35369
  pipeline_thread_owned: (p) => `\u{1F6A6} This thread belongs to the live *${p.agent}* pipeline runner \u2014 nothing runs beside it here. ` + (p.units.length > 0 ? `Reply in the unit's own thread instead: ${p.units.map((u) => `${u.unit}${u.threadKey !== void 0 ? ` (\`${u.threadKey}\`)` : ""}`).join(", ")}.` : "Reply in the unit's own thread instead."),
35287
- ship_budget: (p) => `\u{1F6AB} Ship cannot start under a ${p.maxMinutes}-minute budget: the loop it allows (${p.maxRounds} review rounds) needs ${p.need} minutes \u2014 ${p.provision} to provision, the coding child's ${p.coding}, and the reserve for the rounds after it at their floors. Widen the budget or the boundary that clipped it, or run \`agent:coding\` for a single pass without the review loop.`
35370
+ ship_budget: (p) => `\u{1F6AB} Ship cannot start under a ${p.maxMinutes}-minute budget: the loop it allows (${p.maxRounds} review rounds) needs ${p.need} minutes \u2014 ${p.provision} to provision, the coding child's ${p.coding}, and the reserve for the rounds after it at their floors. Switchboard left the budget and boundary unchanged and did not start either the review loop or a single coding pass.`
35288
35371
  };
35289
35372
  LONG_COMMAND_REPLY_CHARS = 3e3;
35290
35373
  }
@@ -35307,6 +35390,10 @@ function slugOf(token2) {
35307
35390
  if (!OWNER_RE.test(owner) || !NAME_RE.test(name) || /^\.+$/.test(name)) return void 0;
35308
35391
  return `${owner}/${name}`.toLowerCase();
35309
35392
  }
35393
+ function headBranchRef(text) {
35394
+ const match = /^\s*(?:(?:<@[^>\s]+>|[a-z]+:\S+)\s+)*(?:in\s+\S+\s+)?on\s+(?:the\s+)?branch\s+(\S+?)\s*:/i.exec(text);
35395
+ return match ? validRef(stripPunct(match[1])) : void 0;
35396
+ }
35310
35397
  function extractSignals(rawText) {
35311
35398
  const text = unwrapSlack(rawText);
35312
35399
  const out = {};
@@ -35315,8 +35402,9 @@ function extractSignals(rawText) {
35315
35402
  const slug = slugOf(`${stripPunct(prUrl3[1])}/${stripPunct(prUrl3[2])}`);
35316
35403
  if (slug) out.pr = { repo: slug, number: Number(prUrl3[3]) };
35317
35404
  }
35318
- const kw = /(?:^|\s)on\s+(?:the\s+)?branch\s+(\S+)/i.exec(text) ?? /(?:^|\s)branch[:=](\S+)/i.exec(text);
35319
- if (kw) out.ref = validRef(stripPunct(kw[1]));
35405
+ out.ref = headBranchRef(text);
35406
+ const branchToken = /(?:^|\s)branch:(\S+)/i.exec(text);
35407
+ if (!out.ref && branchToken) out.ref = validRef(stripPunct(branchToken[1]));
35320
35408
  const treeUrl = /https?:\/\/(?:www\.)?github\.com\/([^/\s]+)\/([^/\s#?]+)\/tree\/([^\s?#]+)/i.exec(text);
35321
35409
  if (treeUrl) {
35322
35410
  const slug = slugOf(`${stripPunct(treeUrl[1])}/${stripPunct(treeUrl[2])}`);
@@ -35360,9 +35448,7 @@ function extractSignals(rawText) {
35360
35448
  out.addressed = { name: t.toLowerCase() };
35361
35449
  }
35362
35450
  if (prev === "on") {
35363
- if (!out.ref && WELL_KNOWN_REFS.has(t)) out.ref = t;
35364
- else if (!out.onSlug && slugOf(t) && validRef(t)) out.onSlug = t;
35365
- else if (!out.ref && t.includes("/") && !slugOf(t)) out.ref = validRef(t);
35451
+ if (!out.onSlug && slugOf(t) && validRef(t)) out.onSlug = t;
35366
35452
  continue;
35367
35453
  }
35368
35454
  const prShort = /^([^/#\s]+)\/([^/#\s]+)#(\d+)$/.exec(t);
@@ -35465,13 +35551,9 @@ async function resolveRepoContext(msg, history = [], probe, residentSlugs, recor
35465
35551
  if (!repo && s.repo && await vet(s.repo)) repo = s.repo;
35466
35552
  let ref = s.ref;
35467
35553
  let refFromPr = false;
35468
- if (s.onSlug) {
35469
- if (repo && !ref) {
35470
- if (slugOf(s.onSlug) !== repo) ref = s.onSlug;
35471
- } else if (!repo) {
35472
- const cand = slugOf(s.onSlug);
35473
- if (cand && await vet(cand)) repo = cand;
35474
- }
35554
+ if (s.onSlug && !repo) {
35555
+ const cand = slugOf(s.onSlug);
35556
+ if (cand && await vet(cand)) repo = cand;
35475
35557
  }
35476
35558
  if (repo) {
35477
35559
  rejected = void 0;
@@ -35688,7 +35770,7 @@ function prFacts(title, body) {
35688
35770
  if (last >= 55296 && last <= 56319) end--;
35689
35771
  return { title, body: body.slice(0, end), truncated: true };
35690
35772
  }
35691
- var PR_BODY_CAP, OWNER_RE, NAME_RE, WELL_KNOWN_REFS, DETERMINERS, CODE_SPAN, threadSignalsCache, COMPARE_FILES_CAP;
35773
+ var PR_BODY_CAP, OWNER_RE, NAME_RE, DETERMINERS, CODE_SPAN, threadSignalsCache, COMPARE_FILES_CAP;
35692
35774
  var init_repoContext = __esm({
35693
35775
  "../../src/core/repoContext.ts"() {
35694
35776
  "use strict";
@@ -35699,7 +35781,6 @@ var init_repoContext = __esm({
35699
35781
  PR_BODY_CAP = 65536;
35700
35782
  OWNER_RE = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
35701
35783
  NAME_RE = /^[A-Za-z0-9._-]{1,100}$/;
35702
- WELL_KNOWN_REFS = /* @__PURE__ */ new Set(["main", "master", "develop", "trunk"]);
35703
35784
  DETERMINERS = /* @__PURE__ */ new Set(["the", "a", "an", "our", "my", "this"]);
35704
35785
  CODE_SPAN = /```[\s\S]*?```|`[^`\n]*`/g;
35705
35786
  threadSignalsCache = /* @__PURE__ */ new WeakMap();
@@ -35981,7 +36062,7 @@ function checkPrHeadPreflight(input) {
35981
36062
  return {
35982
36063
  ok: false,
35983
36064
  where,
35984
- reply: `\u{1F500} Review of ${where} not started: GitHub did not give me a usable head commit for the PR (the lookup failed, or answered without a well-formed SHA), so I cannot pin a review to it. Re-send the request in a moment; if it keeps failing, look at the PR on GitHub and at the bot's GitHub App credentials.`
36065
+ reply: `\u{1F500} Review of ${where} not started: GitHub did not give me a usable head commit for the PR (the lookup failed, or answered without a well-formed SHA), so I cannot pin a review to it. This is a bug: no automatic head lookup retry was scheduled, and nothing was posted to GitHub.`
35985
36066
  };
35986
36067
  }
35987
36068
  async function guardAttachedHead(input) {
@@ -35996,7 +36077,7 @@ async function guardAttachedHead(input) {
35996
36077
  console.log(`[review] ${input.logKey} not started: ${namedSource} unreadable, PR head ${expected} (${where})`);
35997
36078
  return {
35998
36079
  outcome: "refused",
35999
- reply: `\u{1F500} Review of ${where} not started: ${namedSource} could not be read, so the workspace cannot be verified against PR head ${expected}. This infrastructure failure is not a finding and nothing was posted to GitHub; re-send the request after the workspace backend recovers.`
36080
+ reply: `\u{1F500} Review of ${where} not started: ${namedSource} could not be read, so the workspace cannot be verified against PR head ${expected}. This is a bug: the infrastructure failure was not retried automatically. It is not a finding, and nothing was posted to GitHub.`
36000
36081
  };
36001
36082
  }
36002
36083
  if (sameCommit(expected, attached)) return { outcome: "verified" };
@@ -36010,7 +36091,7 @@ async function guardAttachedHead(input) {
36010
36091
  console.log(`[review] ${input.logKey} not started: ${namedSource} ${attached}, PR head ${expected} (${where})`);
36011
36092
  return {
36012
36093
  outcome: "refused",
36013
- reply: `\u{1F500} Review of ${where} not started: the ${namedSource} is at ${attached}, but the PR head is ${expected} \u2014 the branch moved while the workspace was being prepared (a push or force-push). This infrastructure mismatch is not a finding and nothing was posted to GitHub; re-send the request to review the new head.`
36094
+ reply: `\u{1F500} Review of ${where} not started: the ${namedSource} is at ${attached}, but the PR head is ${expected} \u2014 the branch moved while the workspace was being prepared (a push or force-push). This is a bug: the workspace was not reprovisioned automatically at the new head. It is not a finding, and nothing was posted to GitHub.`
36014
36095
  };
36015
36096
  }
36016
36097
  function makeSystemComposer(input) {
@@ -36332,56 +36413,34 @@ function boundaryOf(scope) {
36332
36413
  return "the parent run's remaining budget";
36333
36414
  }
36334
36415
  }
36335
- function wayForward(scope, axis, needs, adminsHint) {
36416
+ function boundaryOutcome(scope) {
36336
36417
  switch (scope) {
36337
36418
  case "channel":
36338
- return `run it in a channel that allows \`${needs}\`, or ask ${adminsHint} to raise this channel's boundary`;
36419
+ return "Switchboard left this channel's boundary unchanged";
36339
36420
  case "user":
36340
- return `raise your own boundary with \`config set me --boundary.${axis} ${needs}\`, or drop your overrides with \`config clear me\``;
36421
+ return "Switchboard left your boundary and overrides unchanged";
36341
36422
  case "defaults":
36342
- return `ask ${adminsHint} to raise \`defaults.boundary\` in the configuration`;
36423
+ return "Switchboard left the installation's default boundary unchanged";
36343
36424
  case "directive":
36344
- return "send the message again without the budget directive";
36425
+ return "Switchboard applied this message's budget directive";
36345
36426
  case "parent":
36346
- return `spawn it from a run with at least ${needs} left`;
36427
+ return "Switchboard left the parent run's remaining budget unchanged";
36347
36428
  }
36348
36429
  }
36349
- function minutesWayForward(scope, needs, adminsHint) {
36350
- switch (scope) {
36351
- case "directive":
36352
- return `send the message again with \`budget:${needs}\` or more`;
36353
- case "user":
36354
- return `raise your own boundary with \`config set me --boundary.maxMinutes ${needs}\`, or drop your overrides with \`config clear me\``;
36355
- case "channel":
36356
- return `run it in a channel whose boundary allows ${needs} minutes, or ask ${adminsHint} to raise this channel's boundary`;
36357
- case "defaults":
36358
- return `ask ${adminsHint} to raise \`defaults.boundary\` in the configuration`;
36359
- case "parent":
36360
- return `spawn it from a run with at least ${needs} minutes left`;
36361
- }
36362
- }
36363
- function profileRefusalReply(agentName, refusal, adminsHint) {
36364
- const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
36430
+ function profileRefusalReply(agentName, refusal, _adminsHint) {
36365
36431
  if (refusal.axis === "identity") {
36366
- const how2 = wayForward(refusal.scope, "maxIdentity", refusal.needs, adminsHint);
36367
- return `\u{1F6AB} \`${agentName}\` needs a \`${refusal.needs}\` credential; ${boundaryOf(refusal.scope)} caps runs at \`${refusal.cap}\`. ${capitalize(how2)}.`;
36432
+ const outcome = boundaryOutcome(refusal.scope);
36433
+ return `\u{1F6AB} \`${agentName}\` needs a \`${refusal.needs}\` credential; ${boundaryOf(refusal.scope)} caps runs at \`${refusal.cap}\`. ${outcome} and did not start the run.`;
36368
36434
  }
36369
36435
  if (refusal.axis === "minutes") {
36370
- const how2 = minutesWayForward(refusal.scope, refusal.needs, adminsHint);
36371
- return `\u{1F6AB} \`${agentName}\` needs at least ${refusal.needs} minutes \u2014 a turn, then its write-up and post-step \u2014 and ${boundaryOf(refusal.scope)} gives it ${refusal.have}. ${capitalize(how2)}.`;
36436
+ const outcome = boundaryOutcome(refusal.scope);
36437
+ return `\u{1F6AB} \`${agentName}\` needs at least ${refusal.needs} minutes \u2014 a turn, then its write-up and post-step \u2014 and ${boundaryOf(refusal.scope)} gives it ${refusal.have}. ${outcome} and did not start the run.`;
36372
36438
  }
36373
36439
  const scopes = refusal.scopes.map(boundaryOf);
36374
36440
  const who2 = scopes.length > 1 ? `${scopes.join(" and ")} allow` : `${scopes[0] ?? "the boundary"} allows`;
36375
36441
  const allowed2 = refusal.allowed.map((m) => `\`${m}\``).join(", ");
36376
- const how = refusal.scopes.map(
36377
- (scope) => wayForward(
36378
- scope,
36379
- "machines",
36380
- scope === "user" ? `<classes including ${refusal.needs}>` : refusal.needs,
36381
- adminsHint
36382
- )
36383
- ).join("; ");
36384
- return `\u{1F6AB} \`${agentName}\` runs on a \`${refusal.needs}\` machine; ${who2} only ${allowed2}. ${capitalize(how)}.`;
36442
+ const outcomes = refusal.scopes.map(boundaryOutcome).join("; ");
36443
+ return `\u{1F6AB} \`${agentName}\` runs on a \`${refusal.needs}\` machine; ${who2} only ${allowed2}. ${outcomes}; it did not start the run.`;
36385
36444
  }
36386
36445
  function escapeRegExp2(s) {
36387
36446
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -36875,7 +36934,7 @@ function steerAck(live, now) {
36875
36934
  }
36876
36935
  function refusalReply(live, decision, now) {
36877
36936
  return `\u23F3 A *${live.agent}* run is already in flight in this thread (${elapsed(live, now)} in).${linkSuffix(live)}
36878
- An \`agent:${decision.requestedAgent}\` request cannot start beside it \u2014 one run per thread. Wait for it to finish and re-send, or start a new thread.`;
36937
+ An \`agent:${decision.requestedAgent}\` request cannot start beside it \u2014 one run per thread \u2014 so this request was not started.`;
36879
36938
  }
36880
36939
  function followUpPrompt(inputs, opts = {}) {
36881
36940
  const body = inputs.map((i) => attributedText(attributed(i))).map((line4) => inputs.length > 1 ? `- ${line4}` : line4).join("\n");
@@ -37315,13 +37374,13 @@ async function steerRun2(deps, sender, target, text) {
37315
37374
  if (live && live.runId === target.runId) {
37316
37375
  live.inbox.push(followUpOf(msg, text, at, { ledgerSeq, ...sender.from ? { from: sender.from } : {} }));
37317
37376
  console.log(
37318
- `[steer] ${sender.from ? `run ${sender.from.runId}` : sender.userId} \u2192 ${target.agent} run ${target.runId} in ${target.threadKey} (${live.inbox.size} pending${ledgerSeq !== void 0 ? `, durable seq ${ledgerSeq}` : ""})`
37377
+ `[steer] ${sender.from ? `the run ${sender.from.runId}` : sender.userId} \u2192 the ${target.agent} run ${target.runId} in ${target.threadKey} (${live.inbox.size} pending${ledgerSeq !== void 0 ? `, durable seq ${ledgerSeq}` : ""})`
37319
37378
  );
37320
37379
  return { kind: "steered", where: "here", at, ...ledgerSeq !== void 0 ? { ledgerSeq } : {} };
37321
37380
  }
37322
37381
  if (ledgerSeq !== void 0) {
37323
37382
  console.log(
37324
- `[steer] ${sender.from ? `run ${sender.from.runId}` : sender.userId} \u2192 ${target.agent} run ${target.runId} live on another generation (durable seq ${ledgerSeq})`
37383
+ `[steer] ${sender.from ? `the run ${sender.from.runId}` : sender.userId} \u2192 the ${target.agent} run ${target.runId} live on another generation (durable seq ${ledgerSeq})`
37325
37384
  );
37326
37385
  return { kind: "steered", where: "elsewhere", at, ledgerSeq };
37327
37386
  }
@@ -37337,7 +37396,7 @@ function createSteerSender(deps) {
37337
37396
  return {
37338
37397
  async send(runId3, words, caller) {
37339
37398
  const run2 = deps.runs.getById(runId3);
37340
- if (!run2) throw new CommandError("not_found", `run ${runId3} is not known here.`);
37399
+ if (!run2) throw new CommandError("not_found", `the run ${runId3} is not known here.`);
37341
37400
  const owner = authorizeSteerOwner({
37342
37401
  caller: { ids: caller.actor.self ?? [caller.actor.id], grants: effectiveGrants(caller.actor) },
37343
37402
  target: {
@@ -37352,7 +37411,7 @@ function createSteerSender(deps) {
37352
37411
  const ended = run2.finished || run2.threadKey === void 0 || run2.agent === void 0;
37353
37412
  if (ended) {
37354
37413
  const when = run2.finishedAt !== void 0 ? `at ${new Date(run2.finishedAt).toISOString()}` : "before this steer arrived";
37355
- throw new CommandError("conflict", `run ${runId3} ended ${when}; nothing to steer.`);
37414
+ throw new CommandError("conflict", `the run ${runId3} ended ${when}; nothing to steer.`);
37356
37415
  }
37357
37416
  const out = await steerRun2(
37358
37417
  deps,
@@ -37383,7 +37442,7 @@ function createSteerSender(deps) {
37383
37442
  case "not_live":
37384
37443
  throw new CommandError(
37385
37444
  "conflict",
37386
- `run ${runId3} ended while the steer was being delivered; nothing to steer.`
37445
+ `the run ${runId3} ended while the steer was being delivered; nothing to steer.`
37387
37446
  );
37388
37447
  }
37389
37448
  }
@@ -38955,11 +39014,11 @@ var init_confirm = __esm({
38955
39014
  init_runOwner();
38956
39015
  init_commandRun();
38957
39016
  init_route();
38958
- OFFER_EXPIRED_LINE = "this offer expired; its ten minutes passed \u2014 type the line to run it";
38959
- QUESTION_EXPIRED_LINE = "this question expired; its day passed \u2014 type the line to run it";
39017
+ OFFER_EXPIRED_LINE = "this offer expired after ten minutes; nothing ran, and a later request may receive a fresh confirmation";
39018
+ QUESTION_EXPIRED_LINE = "this question expired after one day; nothing ran, and a later request may receive a fresh question";
38960
39019
  OFFER_FOREIGN_LINE = "only the requester can confirm this";
38961
39020
  OFFER_USED_LINE = "this offer was already used";
38962
- OFFER_UNREADABLE_LINE = "the confirmation could not be read; type the line to run it";
39021
+ OFFER_UNREADABLE_LINE = "this is a bug: the confirmation could not be read, so nothing ran and no replacement offer was minted";
38963
39022
  OFFER_CANCELLED_LINE = "Cancelled; nothing ran";
38964
39023
  CONFIRMED_REASON = "confirmed after offer";
38965
39024
  REFUSED_REASON = "refused after offer";
@@ -39366,7 +39425,7 @@ function resolveTarget2(deps, ctx) {
39366
39425
  throw new RefusalError(
39367
39426
  refusalOf(
39368
39427
  "model_card_refused",
39369
- `Model "${resolved.modelRef}" speaks the openai-responses wire, which the "opencode" harness cannot speak yet: run it on pi, or use an openai-chat block.`
39428
+ `Model "${resolved.modelRef}" speaks the openai-responses wire, which the "opencode" harness cannot speak yet; this request needs the pi harness or an openai-chat block.`
39370
39429
  )
39371
39430
  );
39372
39431
  }
@@ -40661,8 +40720,8 @@ async function executeOperatorDecision(deps, ctx) {
40661
40720
  await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
40662
40721
  return answered;
40663
40722
  }
40664
- const cell4 = decideExecution({ kind: "run_command", confirm: "at_or_above", mintable: false }, surface);
40665
- const text = cell4.cell === "refuse" && cell4.names === "typed_form" ? renderHandBackLine(bind.line) : mint !== void 0 && mint.kind === "unshowable" ? UNSHOWABLE_LINE : STORE_UNREACHABLE_LINE;
40723
+ const cell3 = decideExecution({ kind: "run_command", confirm: "at_or_above", mintable: false }, surface);
40724
+ const text = cell3.cell === "refuse" && cell3.names === "typed_form" ? renderHandBackLine(bind.line) : mint !== void 0 && mint.kind === "unshowable" ? UNSHOWABLE_LINE : STORE_UNREACHABLE_LINE;
40666
40725
  await io.reply(`${verbose ? `${receipt}
40667
40726
  ` : ""}${text}`);
40668
40727
  await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
@@ -43049,7 +43108,7 @@ async function spawnChild(deps, parent, request2) {
43049
43108
  if (AGENTS[request2.preset]?.identity === "write") {
43050
43109
  return refused7(
43051
43110
  "spawn_identity",
43052
- `\`${request2.preset}\` runs as a \`write\` identity \u2014 it pushes branches and opens pull requests \u2014 and a spawned child never writes: it reads this conversation and reports; the person who asked starts that work by hand with \`agent:${request2.preset}\``
43111
+ `\`${request2.preset}\` runs as a \`write\` identity \u2014 it pushes branches and opens pull requests \u2014 so this run was not started: spawned children read this conversation and report, but never write`
43053
43112
  );
43054
43113
  }
43055
43114
  const childPreset = LOOP_PRESETS.includes(request2.preset) ? request2.preset : "general";
@@ -44108,7 +44167,7 @@ function encodeGithubPathSegments(path) {
44108
44167
  function anchorUrl(ctx, a) {
44109
44168
  return `https://github.com/${ctx.repo}/blob/${ctx.headSha}/${encodeGithubPathSegments(a.path)}#L${a.from}-L${a.to}`;
44110
44169
  }
44111
- function cell3(s) {
44170
+ function cell2(s) {
44112
44171
  return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ");
44113
44172
  }
44114
44173
  function linkLabel(s) {
@@ -44146,7 +44205,7 @@ function renderPrDescriptionMarkdown(desc, ctx) {
44146
44205
  ...details(FOLD_SUMMARIES.validation(desc.validation.criteria.length), [
44147
44206
  "| Criterion | Proof |",
44148
44207
  "|---|---|",
44149
- ...desc.validation.criteria.map((c) => `| ${cell3(c.criterion)} | ${cell3(c.proof)} |`)
44208
+ ...desc.validation.criteria.map((c) => `| ${cell2(c.criterion)} | ${cell2(c.proof)} |`)
44150
44209
  ])
44151
44210
  );
44152
44211
  const agentLines = [
@@ -46265,28 +46324,32 @@ function windDownAnswer(ending, maxMinutes, facts2) {
46265
46324
  return unlabelledAnswer(ending.text, ending.writeUpFailed, ending.ended, facts2);
46266
46325
  }
46267
46326
  }
46268
- function established(facts2, advice) {
46269
- const then = advice ? ` \u2014 ${advice}` : "";
46327
+ function established(facts2) {
46270
46328
  const w = facts2?.workspace;
46271
46329
  let tree = "";
46272
- if (w?.kind === "unmeasured") tree = `The workspace could not be measured, so work may sit unpushed there${then}.`;
46330
+ if (w?.kind === "unmeasured") tree = "The workspace could not be measured, so work may sit unpushed there.";
46273
46331
  else if (w?.kind === "clean")
46274
46332
  tree = `The tree was clean${w.branch ? ` and \`${w.branch}\` held no unpushed commits` : " with no unpushed commits"}${w.head ? ` \u2014 its head \`${shortSha2(w.head)}\` is on the remote` : ""}.`;
46275
46333
  else if (w?.kind === "salvaged")
46276
46334
  tree = `What the tree held was pushed to \`${w.branch}\`${w.head ? ` at \`${shortSha2(w.head)}\`` : ""} by the budget salvage, unreviewed \u2014 a follow-up starts from it.`;
46277
46335
  else if (w?.kind === "left")
46278
- tree = w.fate === "kept" ? `${counted(w)} sit in the workspace, kept for this thread until it idles out \u2014 a follow-up here reuses them.` : w.fate === "discarded" ? `${counted(w)} were left in the tree and discarded at the run's end${then}.` : `${counted(w)} were left in the tree, which is torn down since a command may still be running in it${then}.`;
46336
+ tree = w.fate === "kept" ? `${counted(w)} sit in the workspace, kept for this thread until it idles out \u2014 a follow-up here reuses them.` : w.fate === "discarded" ? `${counted(w)} were left in the tree and discarded at the run's end.` : `${counted(w)} were left in the tree, which is torn down since a command may still be running in it.`;
46279
46337
  const description = facts2?.description === "submitted" ? "The PR description was submitted." : facts2?.description === "not_submitted" ? "No PR description was submitted." : "";
46280
46338
  return [tree, description].filter((s) => s !== "").join(" ");
46281
46339
  }
46282
- function nothingWritten(facts2, advice) {
46283
- const known = established(facts2, advice);
46284
- if (known) return ` ${known}`;
46285
- if (facts2?.workspace?.kind === "none") return advice ? ` ${advice.charAt(0).toUpperCase()}${advice.slice(1)}.` : "";
46286
- return ` Partial work may exist in the workspace${advice ? ` \u2014 ${advice}` : ""}.`;
46340
+ function gapApplies(facts2) {
46341
+ const w = facts2?.workspace;
46342
+ return w === void 0 || w.kind === "unread" || w.kind === "unmeasured" || w.kind === "none" || w.kind === "left" && w.fate !== "kept";
46343
+ }
46344
+ function nothingWritten(facts2, gap) {
46345
+ const gapSentence = gap && gapApplies(facts2) ? `${gap.charAt(0).toUpperCase()}${gap.slice(1)}.` : "";
46346
+ const known = established(facts2);
46347
+ if (known) return ` ${[gapSentence, known].filter((s) => s !== "").join(" ")}`;
46348
+ if (facts2?.workspace?.kind === "none") return gapSentence ? ` ${gapSentence}` : "";
46349
+ return ` Partial work may exist in the workspace${gap ? ` \u2014 ${gap}` : ""}.`;
46287
46350
  }
46288
46351
  function beforeFindings(facts2) {
46289
- const known = established(facts2, void 0);
46352
+ const known = established(facts2);
46290
46353
  return known ? `. ${known} Findings so far:` : " \u2014 findings so far:";
46291
46354
  }
46292
46355
  function elapsedMinutes(ms) {
@@ -46310,10 +46373,10 @@ var init_windDown = __esm({
46310
46373
  MODEL_CALL_IN_FLIGHT = "a model call was in flight";
46311
46374
  timeBudgetNote = (doing) => doing ? `the loop's time is up while ${doing} \u2014 writing up findings so far inside the lease` : "the loop's time is up \u2014 writing up findings so far inside the lease";
46312
46375
  toolCutNote = (doing) => `the loop's end cut the command in flight (${doing}) so the write-up keeps its allowance`;
46313
- commandPastLoopEndRefusal = (askedSecs, leftSecs) => `budget \u2014 this command asked for a ${askedSecs} s timeout and the loop ends in ${leftSecs} s, so it could never finish: re-issue it with a timeout inside the ${leftSecs} s left if it finishes sooner, or push what you have and write up \u2014 the full verification is CI's.`;
46376
+ commandPastLoopEndRefusal = (askedSecs, leftSecs) => `budget \u2014 this command asked for a ${askedSecs} s timeout and the loop ends in ${leftSecs} s, so it could never finish. A timeout inside the ${leftSecs} s left can still finish; otherwise the current work and write-up stand, and CI owns full verification.`;
46314
46377
  turnGuardNote = (pace) => `turn guard fired: ${pace}, a pace that looks like a loop \u2014 writing up findings so far`;
46315
46378
  softStopNote = () => "soft stop \u2014 no further steps, writing up findings so far";
46316
- hardStopNote = () => "hard stop \u2014 run aborted, no summary written";
46379
+ hardStopNote = () => "hard stop \u2014 the run was aborted, no summary written";
46317
46380
  windDownFailureNote = (error, closes = "run") => `the model call failed during the wind-down (${error}); the ${closes} closes with its findings so far`;
46318
46381
  finaleWaitNote = (reason2, doing, interruptUnanswered, closes = "run") => `the finale bound ended the wait${doing ? ` while ${doing}` : ""}${interruptUnanswered ? ", the loop-end interrupt unanswered" : ""} (${reason2}); the ${closes} closes with its findings so far`;
46319
46382
  finaleTimedOutNote = (closes = "run") => `finale timed out \u2014 closing the ${closes} without a write-up`;
@@ -46337,14 +46400,14 @@ var init_windDown = __esm({
46337
46400
  wrapUpName = (kind) => kind === "time" ? "time-budget" : kind === "turns" ? "turn-guard" : "soft-stop";
46338
46401
  finaleAbortReason = (boundMs) => `aborted at the finale bound (${elapsedMinutes(boundMs)})`;
46339
46402
  noWriteUp = (failed, onTool) => failed ? onTool ? `; the finale bound ended the wait on a tool call (${failed}), so no write-up came` : `; the model call failed during the wind-down (${failed}), so no write-up came` : "";
46340
- TIME_ADVICE = "narrow the task and try again";
46341
- TURN_ADVICE = "look for a retry loop in the run's events before trying again";
46403
+ TIME_ADVICE = "this is a bug: the task outlived its run budget and no automatic continuation was scheduled";
46404
+ TURN_ADVICE = "this is a bug: a retry loop spent the turn guard and no automatic recovery was scheduled";
46342
46405
  shortSha2 = (sha) => sha.slice(0, 7);
46343
46406
  counted = (w) => `${w.uncommitted} uncommitted change(s) and ${w.unpushed} unpushed commit(s)`;
46344
46407
  timeBudgetAnswer = (text, maxMinutes, writeUpFailed, facts2, writeUpFailedOnTool) => text ? `\u26A0\uFE0F _Hit the ${maxMinutes}-minute budget before finishing${beforeFindings(facts2)}_
46345
46408
 
46346
46409
  ${text}` : `Stopped at the ${maxMinutes}-minute budget without finishing${noWriteUp(writeUpFailed, writeUpFailedOnTool)}.${nothingWritten(facts2, TIME_ADVICE)}`;
46347
- turnGuardAnswer = (text, pace, writeUpFailed, facts2, writeUpFailedOnTool) => text ? `\u26A0\uFE0F _Stopped after ${pace} \u2014 that pace looks like a loop${established(facts2, void 0) ? beforeFindings(facts2) : "; findings so far:"}_
46410
+ turnGuardAnswer = (text, pace, writeUpFailed, facts2, writeUpFailedOnTool) => text ? `\u26A0\uFE0F _Stopped after ${pace} \u2014 that pace looks like a loop${established(facts2) ? beforeFindings(facts2) : "; findings so far:"}_
46348
46411
 
46349
46412
  ${text}` : `Stopped after ${pace} \u2014 that pace looks like a loop \u2014 without finishing${noWriteUp(writeUpFailed, writeUpFailedOnTool)}.${nothingWritten(facts2, TURN_ADVICE)}`;
46350
46413
  unlabelledAnswer = (text, writeUpFailed, ended = "failed", facts2) => {
@@ -50645,6 +50708,11 @@ async function salvageBudgetPush(executor, opts, span) {
50645
50708
  nothing: `the coding child ended with nothing to preserve: the tree is clean and \`${opts.branch}\` holds no unpushed commits`,
50646
50709
  pushedLead: "the coding child ended with interrupted work",
50647
50710
  failedLead: `the interrupted-work push to \`${opts.branch}\` failed`
50711
+ } : opts.cue === "completion" ? {
50712
+ commit: "wip: preserve unfinished coding work \u2014 work in progress, not reviewed",
50713
+ nothing: `the coding child ended with nothing to preserve: the tree is clean and \`${opts.branch}\` holds no unpushed commits`,
50714
+ pushedLead: "the coding child ended with unfinished work",
50715
+ failedLead: `the unfinished-work push to \`${opts.branch}\` failed`
50648
50716
  } : {
50649
50717
  commit: "wip: committed at the budget wind-down \u2014 work in progress, not reviewed",
50650
50718
  nothing: nothingToSalvageNote(opts.branch),
@@ -50653,7 +50721,7 @@ async function salvageBudgetPush(executor, opts, span) {
50653
50721
  };
50654
50722
  try {
50655
50723
  const dirty = (await run2("git status --porcelain")).trim() !== "";
50656
- const endingCheckpoint = opts.cue !== "compaction";
50724
+ const endingCheckpoint = opts.cue !== "compaction" && opts.cue !== "completion";
50657
50725
  if (dirty) {
50658
50726
  await run2("git add -A");
50659
50727
  await run2(`git commit -m ${shellQuote(words.commit)}`);
@@ -51618,8 +51686,6 @@ async function runLoop(deps, ctx) {
51618
51686
  const preserveCodingChildWork = async (cue) => {
51619
51687
  if (workSalvageAttempted || !isCodingPrRun || coordinator === void 0) return;
51620
51688
  if (!workspaceObserved) await observeWorkspaceNow();
51621
- workSalvageAttempted = true;
51622
- if (cue === "ending") endingSalvageAttempted = true;
51623
51689
  const target = salvageTargetOf({
51624
51690
  pushedBranch: pushes.branch(),
51625
51691
  checkedOut: observedCheckedOut,
@@ -51629,6 +51695,10 @@ async function runLoop(deps, ctx) {
51629
51695
  cue === "budget" ? "run.budget_salvage" : "run.work_salvage",
51630
51696
  (span) => salvageBudgetPush(executor, { branch: target.branch, cue }, span)
51631
51697
  );
51698
+ const cleanCompletion = cue === "completion" && !salvaged.pushed && (/ended with nothing to preserve/i.test(salvaged.summary) || "skipped" in target && (observedUncommitted ?? 0) === 0 && (observedUnpushed ?? 0) === 0);
51699
+ if (cleanCompletion) return;
51700
+ workSalvageAttempted = true;
51701
+ if (cue === "ending" || cue === "completion") endingSalvageAttempted = true;
51632
51702
  onEvent({
51633
51703
  type: "run_note",
51634
51704
  kind: cue === "budget" ? "budget_salvage" : "work_salvage",
@@ -52068,6 +52138,7 @@ async function runLoop(deps, ctx) {
52068
52138
  if (endingNeedsPreservation && run2.control.requested === "hard") await endHarness();
52069
52139
  if (isCodingPrRun && !tailSkipped()) await observeWorkspaceNow();
52070
52140
  if (endingNeedsPreservation) await preserveCodingChildWork(budgetEnded ? "budget" : "ending");
52141
+ else if (isCodingPrRun && coordinator !== void 0 && !tailSkipped()) await preserveCodingChildWork("completion");
52071
52142
  let descriptionTurnRan = false;
52072
52143
  if (isCodingPrRun && !tailSkipped() && !endingSalvageAttempted && prDescription === void 0) {
52073
52144
  const turnTarget = await descriptionTurnTarget({
@@ -52103,6 +52174,7 @@ async function runLoop(deps, ctx) {
52103
52174
  if (!run2.control.hardSignal.aborted) await observeWorkspaceNow();
52104
52175
  }
52105
52176
  }
52177
+ if (descriptionTurnRan) await preserveCodingChildWork("completion");
52106
52178
  await endHarness();
52107
52179
  const leftBehind = isCodingPrRun && !tailSkipped() && !workSalvageAttempted ? workLeftBehindOf({ uncommittedChanges: observedUncommitted, unpushedCommits: observedUnpushed }) : void 0;
52108
52180
  const endingFacts = () => {
@@ -52448,14 +52520,6 @@ function shipUnitText(requestText, repo) {
52448
52520
  t = t.replace(new RegExp(`^\\s*in\\s+${escapeRegExp3(repo)}\\s*:?`, "i"), " ");
52449
52521
  return t.replace(/\s+/g, " ").trim();
52450
52522
  }
52451
- function refNamedExplicitly(requestText, ref) {
52452
- const text = requestText.replace(SLACK_LINK, " $1 ");
52453
- const strip = (s) => s.replace(/^[("'`<[{*]+/, "").replace(/[)"'`>\]}.,;:!?*]+$/, "");
52454
- const kw = /(?:^|\s)on\s+(?:the\s+)?branch\s+(\S+)/i.exec(text) ?? /(?:^|\s)branch[:=](\S+)/i.exec(text);
52455
- if (kw && strip(kw[1]) === ref) return true;
52456
- const treeUrl = /https?:\/\/(?:www\.)?github\.com\/[^/\s]+\/[^/\s#?]+\/tree\/([^\s?#]+)/i.exec(text);
52457
- return treeUrl !== void 0 && treeUrl !== null && strip(treeUrl[1]) === ref;
52458
- }
52459
52523
  function noRepoGuess(input) {
52460
52524
  const candidates = input.repoCandidates ?? [];
52461
52525
  if (candidates.length === 0) return void 0;
@@ -52549,18 +52613,9 @@ async function shipPreflight(input) {
52549
52613
  );
52550
52614
  }
52551
52615
  const ownBase = facts2.baseRef ?? repoCtx.baseRef;
52552
- if (ownBase !== void 0 && input.refExists) {
52553
- const exists = await input.refExists(repo, ownBase).catch(() => void 0);
52554
- if (exists === false) {
52555
- return refuse2(
52556
- "ship_preflight_base_missing",
52557
- "base ref missing",
52558
- "not started (base ref missing)",
52559
- `\u{1F6AB} ${where}'s base branch \`${ownBase}\` does not exist on \`${repo}\` \u2014 refusing fail-closed. Retarget the pull request at an existing branch first.`
52560
- );
52561
- }
52562
- }
52563
- const base = resolveBaseRef([ownBase], info?.defaultBranch);
52616
+ const ownBaseExists = ownBase !== void 0 && input.refExists ? await input.refExists(repo, ownBase).catch(() => void 0) : void 0;
52617
+ const baseFallback = ownBase !== void 0 && ownBaseExists === false ? { requested: ownBase } : void 0;
52618
+ const base = resolveBaseRef([ownBaseExists === false ? void 0 : ownBase], info?.defaultBranch);
52564
52619
  const autoMerge = facts2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: facts2.autoMergeEnabled } : {};
52565
52620
  if (task) {
52566
52621
  return {
@@ -52570,6 +52625,7 @@ async function shipPreflight(input) {
52570
52625
  branch: branch2,
52571
52626
  base,
52572
52627
  adopt: { pr: repoCtx.pr, ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {} },
52628
+ ...baseFallback !== void 0 ? { baseFallback } : {},
52573
52629
  ...autoMerge
52574
52630
  }
52575
52631
  };
@@ -52585,6 +52641,7 @@ async function shipPreflight(input) {
52585
52641
  headSha: facts2.headSha ?? repoCtx.headSha,
52586
52642
  ...facts2.htmlUrl !== void 0 ? { url: facts2.htmlUrl } : {}
52587
52643
  },
52644
+ ...baseFallback !== void 0 ? { baseFallback } : {},
52588
52645
  ...autoMerge
52589
52646
  }
52590
52647
  };
@@ -52610,20 +52667,11 @@ async function shipPreflight(input) {
52610
52667
  const ref = repoCtx.ref && !repoCtx.refFromPr && !isUnitBranch(repoCtx.ref) && repoCtx.ref.toLowerCase() !== repo.toLowerCase() ? repoCtx.ref : void 0;
52611
52668
  if (ref !== void 0 && input.refExists) {
52612
52669
  const exists = await input.refExists(repo, ref).catch(() => void 0);
52613
- if (exists === false) {
52614
- if (refNamedExplicitly(input.requestText, ref)) {
52615
- return refuse2(
52616
- "ship_preflight_base_missing",
52617
- "base ref missing",
52618
- "not started (base ref missing)",
52619
- `\u{1F6AB} The branch \`${ref}\` does not exist on \`${repo}\` \u2014 nothing was started. Name an existing branch, or drop it to run on the default branch.`
52620
- );
52621
- }
52670
+ if (exists === false)
52622
52671
  return {
52623
52672
  ok: true,
52624
52673
  entry: { repo, base: info?.defaultBranch, baseFallback: { requested: ref } }
52625
52674
  };
52626
- }
52627
52675
  }
52628
52676
  return {
52629
52677
  ok: true,
@@ -52840,12 +52888,12 @@ async function handOffToCoordinator(deps, input) {
52840
52888
  if (status3.kind === "unanswered")
52841
52889
  return refused5(
52842
52890
  "plan_runner_state_unknown",
52843
- `\u26A0\uFE0F The plan runner could not tell whether \`${latest.id}\` still runs: ${status3.reason}. Nothing ran; re-issue the request to try again.`
52891
+ `\u26A0\uFE0F This is a bug: the plan runner could not tell whether \`${latest.id}\` still runs (${status3.reason}), so nothing ran and no automatic state retry was scheduled.`
52844
52892
  );
52845
52893
  if (status3.kind === "status" && RUNNING.has(status3.status))
52846
52894
  return refused5(
52847
52895
  "plan_runner_live",
52848
- `\u{1F6AB} A runner for ${where} is still running (\`${latest.id}\`, status: ${status3.status}): wait for it to end \u2014 or terminate it in the Workflows dashboard \u2014 before re-issuing.`
52896
+ `\u{1F6AB} A runner for ${where} is still running (\`${latest.id}\`, status: ${status3.status}), so this request started no second runner.`
52849
52897
  );
52850
52898
  if (status3.kind === "status" && !ENDED.has(status3.status))
52851
52899
  return refused5(
@@ -52909,7 +52957,7 @@ async function start(deps, input, instance, units, where, write) {
52909
52957
  );
52910
52958
  return refused5(
52911
52959
  "plan_runner_conflict",
52912
- `\u{1F6AB} A runner for \`${instance.id}\` was just recorded by another request \u2014 re-issue in a minute if it did not start.`
52960
+ `\u{1F6AB} A runner for \`${instance.id}\` was just recorded by another request, so this request started nothing; the recorded runner owns the pipeline.`
52913
52961
  );
52914
52962
  }
52915
52963
  const rows = await deps.instances.putUnits(units);
@@ -52967,7 +53015,7 @@ async function start(deps, input, instance, units, where, write) {
52967
53015
  log(`[ship] ${input.msg.threadKey}: the plan runner ${instance.id} could not be started \u2014 ${answer.reason}`);
52968
53016
  return refused5(
52969
53017
  "plan_start_failed",
52970
- `\u26A0\uFE0F The plan runner could not be started: ${answer.reason}. Nothing ran; re-issue the request to try again.`
53018
+ `\u26A0\uFE0F This is a bug: the plan runner could not be started (${answer.reason}), nothing ran, and no automatic start retry was scheduled.`
52971
53019
  );
52972
53020
  }
52973
53021
  }
@@ -53550,7 +53598,7 @@ function settleThread(deps, ctx) {
53550
53598
  console.log(`[dispatch] ${msg.threadKey} ${fromRuns} steer(s) from a parent run never read \u2014 not run fresh`);
53551
53599
  const stopMode = stopCounts ? control?.requested : void 0;
53552
53600
  if (pending2.length > 0 && stopMode) {
53553
- console.log(`[dispatch] ${msg.threadKey} ${pending2.length} follow-up(s) dropped: run stopped (${stopMode})`);
53601
+ console.log(`[dispatch] ${msg.threadKey} ${pending2.length} follow-up(s) dropped: the run stopped (${stopMode})`);
53554
53602
  return { kind: "dropped", stopMode, pending: pending2 };
53555
53603
  }
53556
53604
  if (pending2.length > 0 && admitted) {
@@ -53601,7 +53649,7 @@ var init_settle = __esm({
53601
53649
  init_requestTrace();
53602
53650
  init_threadAdmission();
53603
53651
  init_admission();
53604
- FOLLOW_UP_DROPPED_BY_STOP = "\u26D4 The run this was folded into was stopped before it read this follow-up, so it was not run. Re-send it to run it fresh.";
53652
+ FOLLOW_UP_DROPPED_BY_STOP = "\u26D4 This is a bug: the run was stopped before it read this folded follow-up, and the follow-up was not replayed as a fresh request.";
53605
53653
  fromPerson = (p) => p.from === void 0 && p.io !== void 0;
53606
53654
  }
53607
53655
  });
@@ -53624,7 +53672,7 @@ function carriedCoordinatorTag(row2, events) {
53624
53672
  }
53625
53673
  function lostWorkspaceNote(why, restarts) {
53626
53674
  const lost = `resumed after a restart: the run's workspace could not be re-attached (${why})`;
53627
- return restarts ? `${lost}; the run restarts from its request under the same run id` : `${lost}, and the row's request cannot be read, so the run ends here; re-send it to run it again`;
53675
+ return restarts ? `${lost}; the run restarts from its request under the same run id` : `${lost}; this is a bug: the row's request cannot be read, so the run ends here and no replacement run starts`;
53628
53676
  }
53629
53677
  function carriedRunIdentity(registry, runId3, note) {
53630
53678
  const snap = registry.snapshotById(runId3);
@@ -53669,7 +53717,7 @@ async function abandonLostWorkspace(ctx) {
53669
53717
  shell.close({
53670
53718
  kind: "not_started",
53671
53719
  icon: "\u{1F501}",
53672
- reason: restored ? "workspace lost across the restart; restarting from the request" : "workspace lost across the restart; re-send to run again",
53720
+ reason: restored ? "workspace lost across the restart; restarting from the request" : "this is a bug: the workspace was lost across the restart, the request could not be read, and no replacement run starts",
53673
53721
  ...closeLines(clock4(), false)
53674
53722
  })
53675
53723
  );
@@ -54063,7 +54111,7 @@ async function answerUnitOwnedThread(deps, ctx) {
54063
54111
  ...msg.userName !== void 0 ? { senderName: msg.userName } : {},
54064
54112
  text: ctx.text,
54065
54113
  ...attachments.length > 0 ? { attachments } : {},
54066
- mode: "steer",
54114
+ mode: owner.unit.idle !== void 0 ? "wake" : "steer",
54067
54115
  at
54068
54116
  }).catch((err2) => {
54069
54117
  console.warn(
@@ -54347,7 +54395,7 @@ async function dispatch(deps, msg, io, opts = {}) {
54347
54395
  const original = await shipRequestOf(runsService, owner.run.id);
54348
54396
  if (original === void 0) {
54349
54397
  await io.reply(
54350
- "This ship pipeline ended, but its original task could not be read; what task should this thread re-issue?"
54398
+ "This ship pipeline ended, but its original task could not be read. What was the pipeline's original task?"
54351
54399
  );
54352
54400
  await recordPendingOperator();
54353
54401
  return ended;
@@ -54361,7 +54409,7 @@ async function dispatch(deps, msg, io, opts = {}) {
54361
54409
  ({ sticky, resolved, agentSource } = settled);
54362
54410
  agentSource = "sticky";
54363
54411
  }
54364
- if (owner.kind === "unit" && directives.agent === void 0) {
54412
+ if (owner.kind === "unit" && (directives.agent === void 0 || owner.unit.idle?.humanGate !== void 0)) {
54365
54413
  if ((await authorizeAgent(deps, { msg, io, refuse: refuse3, agentName: "coding" })).kind === "refused") {
54366
54414
  await recordPendingOperator();
54367
54415
  return ended;
@@ -62636,7 +62684,7 @@ function findOrphanedCards(input) {
62636
62684
  }
62637
62685
  function interruptedCardFrame(cardText) {
62638
62686
  const label = cardText.replace(LIVE_GLYPH_PREFIX, "").replace(SHUTDOWN_NOTICE_SUFFIX, "").replace(ACTIVITY_SUFFIX, "").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&").trim();
62639
- const detail = "The bot restarted (a deploy) while this run was in flight, so the run was lost and this card stopped updating. Re-send your request to run it again.";
62687
+ const detail = "This is a bug: the bot restarted while this run was in flight, but the run could not be resumed from the ledger. The card stopped updating and no replacement run was started.";
62640
62688
  return { title: `\u274C interrupted \xB7 ${label}`, detail };
62641
62689
  }
62642
62690
  async function catchUpMissedMentions(opts) {
@@ -63793,7 +63841,7 @@ var init_slack = __esm({
63793
63841
  PLATFORM3 = "slack";
63794
63842
  SLACK_MSG_LIMIT = 3500;
63795
63843
  SLACK_SECTION_LIMIT = 3e3;
63796
- CLICK_FAILED_LINE = "this click could not be handled; type the line to run it";
63844
+ CLICK_FAILED_LINE = "this click could not be handled; nothing ran, and the command line remains visible in the offer";
63797
63845
  LOADING_PHRASES = [
63798
63846
  "is patching you through\u2026",
63799
63847
  "is untangling the cords\u2026",
@@ -65372,7 +65420,7 @@ function serveEvents(subscribe, sink, onLive) {
65372
65420
  );
65373
65421
  if (!subscribed) {
65374
65422
  sink.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
65375
- sink.write("run not found");
65423
+ sink.write("no run found");
65376
65424
  sink.end();
65377
65425
  return;
65378
65426
  }
@@ -65663,7 +65711,7 @@ function createLiveViewHandler(deps) {
65663
65711
  };
65664
65712
  const liveTokens = () => new Map(index.listActive().map((s) => [s.id, s.token]));
65665
65713
  const notFoundPage = (req, res, viewer) => {
65666
- deps.page(req, res, 404, viewer, "Run not found", {
65714
+ deps.page(req, res, 404, viewer, "No run found", {
65667
65715
  page: "runNotFound",
65668
65716
  retentionDays: deps.retention ? deps.retention.retentionDays : null
65669
65717
  });
@@ -65861,7 +65909,7 @@ function createLiveViewHandler(deps) {
65861
65909
  }
65862
65910
  const result = access.requestStop(mode);
65863
65911
  if (!result.ok) {
65864
- if (result.reason === "finished") text(res, 409, "run already finished");
65912
+ if (result.reason === "finished") text(res, 409, "the run already finished");
65865
65913
  else if (result.reason === "hosted") text(res, 409, HOSTED_STOP);
65866
65914
  else text(res, 404, NOT_FOUND2);
65867
65915
  return true;
@@ -65895,13 +65943,13 @@ function createLiveViewHandler(deps) {
65895
65943
  return;
65896
65944
  }
65897
65945
  if (found.value.finished) {
65898
- text(res, 409, "run already finished");
65946
+ text(res, 409, "the run already finished");
65899
65947
  return;
65900
65948
  }
65901
65949
  const stopped = await service.stopRun(route2.id, mode, { kind: "access", id: actor.id });
65902
65950
  if (!stopped.ok) {
65903
65951
  if (stopped.error === "hosted") text(res, 409, HOSTED_STOP);
65904
- else if (stopped.error === "conflict") text(res, 409, "run already finished");
65952
+ else if (stopped.error === "conflict") text(res, 409, "the run already finished");
65905
65953
  else text(res, 404, NOT_FOUND2);
65906
65954
  return;
65907
65955
  }
@@ -66036,7 +66084,7 @@ var init_liveView = __esm({
66036
66084
  INDEX_PAGE_SIZE = 25;
66037
66085
  RUNS_GET = { type: "command", id: "runs.get" };
66038
66086
  RUNS_STOP = { type: "command", id: "runs.stop" };
66039
- NOT_FOUND2 = "run not found";
66087
+ NOT_FOUND2 = "no run found";
66040
66088
  HOSTED_STOP = "this run is a pipeline; its units run in their own threads, so a stop here ends nothing; an operator's hard stop seals it failed and releases the thread";
66041
66089
  TEXT = { "content-type": "text/plain; charset=utf-8" };
66042
66090
  JSON_NO_STORE2 = { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" };
@@ -66954,7 +67002,7 @@ function createWebChatHandler(deps) {
66954
67002
  const threadKey = threadKeyFor(sub, route2.id);
66955
67003
  readTurns(threadKey, ctx.actor).then(async (open2) => {
66956
67004
  if (open2.runs.length === 0 && !ownLane(sub, threadKey)) {
66957
- deps.page(req, res, 404, ctx.actor, "Run not found", { page: "runNotFound", retentionDays });
67005
+ deps.page(req, res, 404, ctx.actor, "No run found", { page: "runNotFound", retentionDays });
66958
67006
  return;
66959
67007
  }
66960
67008
  const seed = await seedFor(ctx, route2.id, threadKey, open2);
@@ -67236,14 +67284,14 @@ function createMetricsViewHandler(service, page2) {
67236
67284
  json2(res, report2);
67237
67285
  return;
67238
67286
  }
67239
- page2(req, res, 200, ctx.actor, "Run metrics", { page: "metrics", report: report2 });
67287
+ page2(req, res, 200, ctx.actor, "Metrics by run", { page: "metrics", report: report2 });
67240
67288
  }).catch((err2) => {
67241
67289
  const message4 = err2 instanceof Error ? err2.message : String(err2);
67242
67290
  if (message4 === METRICS_OFF_MESSAGE) {
67243
67291
  plain3(res, 503, METRICS_OFF_MESSAGE);
67244
67292
  return;
67245
67293
  }
67246
- plain3(res, 503, `run metrics unavailable: ${err2 instanceof Error ? err2.name : "Error"}`);
67294
+ plain3(res, 503, `metrics by run unavailable: ${err2 instanceof Error ? err2.name : "Error"}`);
67247
67295
  });
67248
67296
  return true;
67249
67297
  };
@@ -68053,7 +68101,7 @@ async function complete(svc, nonce, identity2, token2, res) {
68053
68101
  res,
68054
68102
  400,
68055
68103
  "Token rejected by the server",
68056
- `<p class="err">${esc(warning ?? "the server rejected the token")}</p><p>Nothing was stored. Check the token and try again.</p>${server ? entryForm(server, nonce) : ""}`
68104
+ `<p class="err">${esc(warning ?? "the server rejected the token")}</p><p>Nothing was stored. This is a bug: the token was refused and no fresh sign-in was opened automatically.</p>${server ? entryForm(server, nonce) : ""}`
68057
68105
  );
68058
68106
  }
68059
68107
  return connected(res, server, toolCount, warning);
@@ -68201,7 +68249,12 @@ function page(res, status3, title, body, head = "") {
68201
68249
  }
68202
68250
  function failure(res, err2) {
68203
68251
  console.error(`[mcp-connect] ${err2 instanceof Error ? err2.message : String(err2)}`);
68204
- page(res, 503, "Registry unavailable", "<p>The MCP registry could not be reached. Try again in a moment.</p>");
68252
+ page(
68253
+ res,
68254
+ 503,
68255
+ "Registry unavailable",
68256
+ "<p>This is a bug: the MCP registry could not be reached and no automatic retry was scheduled.</p>"
68257
+ );
68205
68258
  }
68206
68259
  function esc(s) {
68207
68260
  return s.replace(
@@ -68330,8 +68383,8 @@ function closureNote(agent, prUrl3, cause) {
68330
68383
  if (agent === "ship")
68331
68384
  return shipInterruptedNote(prUrl3, cause === "resident_replaced" ? "container_replaced" : "bot_restart");
68332
68385
  if (cause !== void 0)
68333
- return `This run ended \u2014 ${endingCauseWords(cause)} \u2014 and it could not be resumed, so this card stopped updating. Re-send your request to run it again.`;
68334
- return "The bot restarted while this run was in flight and it could not be resumed, so this card stopped updating. Re-send your request to run it again.";
68386
+ return `This is a bug: this run ended \u2014 ${endingCauseWords(cause)} \u2014 but it could not be resumed from the ledger. This card stopped updating and no replacement run was started.`;
68387
+ return "This is a bug: the bot restarted while this run was in flight, but the run could not be resumed from the ledger. This card stopped updating and no replacement run was started.";
68335
68388
  }
68336
68389
  function prUrlOf(events) {
68337
68390
  let url;
@@ -68876,14 +68929,14 @@ var init_resumeLaunch = __esm({
68876
68929
  });
68877
68930
 
68878
68931
  // ../../src/core/runnerOwnership.ts
68879
- async function recoverRunnerOwnedPulls(instanceIds, instances) {
68880
- const owned = /* @__PURE__ */ new Set();
68932
+ async function recoverRunnerOwnedPullOwners(instanceIds, instances) {
68933
+ const owned = /* @__PURE__ */ new Map();
68881
68934
  for (const instanceId of new Set(instanceIds)) {
68882
68935
  const instance = await instances.get(instanceId);
68883
68936
  if (instance === null) continue;
68884
68937
  for (const unit of await instances.listUnits(instanceId)) {
68885
68938
  if (unit.pr !== void 0 && unit.ending === void 0)
68886
- owned.add(runnerOwnedPullKey(instance.repo, unit.pr.number));
68939
+ owned.set(runnerOwnedPullKey(instance.repo, unit.pr.number), { instanceId, unit: unit.unit });
68887
68940
  }
68888
68941
  }
68889
68942
  return owned;
@@ -68896,24 +68949,35 @@ var init_runnerOwnership = __esm({
68896
68949
  RunnerOwnershipFence = class {
68897
68950
  claimed = /* @__PURE__ */ new Set();
68898
68951
  recovered = /* @__PURE__ */ new Set();
68952
+ claimedOwners = /* @__PURE__ */ new Map();
68953
+ recoveredOwners = /* @__PURE__ */ new Map();
68899
68954
  localRecoveredInstances = /* @__PURE__ */ new Set();
68900
68955
  recoveryComplete;
68901
68956
  constructor(requiresRecovery) {
68902
68957
  this.recoveryComplete = !requiresRecovery;
68903
68958
  }
68904
- claim(repo, prNumber) {
68905
- this.claimed.add(runnerOwnedPullKey(repo, prNumber));
68959
+ claim(repo, prNumber, owner) {
68960
+ const key = runnerOwnedPullKey(repo, prNumber);
68961
+ this.claimed.add(key);
68962
+ if (owner !== void 0) this.claimedOwners.set(key, owner);
68906
68963
  }
68907
68964
  release(repo, prNumber) {
68908
68965
  const key = runnerOwnedPullKey(repo, prNumber);
68909
68966
  this.claimed.delete(key);
68910
68967
  this.recovered.delete(key);
68968
+ this.claimedOwners.delete(key);
68969
+ this.recoveredOwners.delete(key);
68911
68970
  }
68912
68971
  owns(repo, prNumber) {
68913
68972
  if (!this.recoveryComplete) throw new Error("runner ownership recovery is still in progress");
68914
68973
  const key = runnerOwnedPullKey(repo, prNumber);
68915
68974
  return this.claimed.has(key) || this.recovered.has(key);
68916
68975
  }
68976
+ owner(repo, prNumber) {
68977
+ if (!this.recoveryComplete) throw new Error("runner ownership recovery is still in progress");
68978
+ const key = runnerOwnedPullKey(repo, prNumber);
68979
+ return this.claimedOwners.get(key) ?? this.recoveredOwners.get(key);
68980
+ }
68917
68981
  async recover(outcome, instances) {
68918
68982
  for (const run2 of outcome.resumable) {
68919
68983
  if (run2.kind === "rehost" && run2.hosting !== void 0) this.localRecoveredInstances.add(run2.hosting.instanceId);
@@ -68925,9 +68989,13 @@ var init_runnerOwnership = __esm({
68925
68989
  for (const run2 of outcome.liveElsewhere) {
68926
68990
  if (run2.hosting !== void 0) activeRunnerInstances.add(run2.hosting.instanceId);
68927
68991
  }
68928
- const recovered = await recoverRunnerOwnedPulls(activeRunnerInstances, instances);
68992
+ const recovered = await recoverRunnerOwnedPullOwners(activeRunnerInstances, instances);
68929
68993
  this.recovered.clear();
68930
- for (const key of recovered) this.recovered.add(key);
68994
+ this.recoveredOwners.clear();
68995
+ for (const [key, owner] of recovered) {
68996
+ this.recovered.add(key);
68997
+ this.recoveredOwners.set(key, owner);
68998
+ }
68931
68999
  this.recoveryComplete = true;
68932
69000
  }
68933
69001
  };
@@ -69400,10 +69468,14 @@ ${cont.texts.join("\n\n")}`);
69400
69468
  }
69401
69469
  function findingsRequest(input) {
69402
69470
  const findings = input.findings.map(formatFinding).join("\n") || "(the review listed no structured findings, address its prose)";
69471
+ const answers = input.answers !== void 0 && input.answers.length > 0 ? `
69472
+
69473
+ A person answered the human-gated finding. Treat the finding and this answer together as the fix brief:
69474
+ ${input.answers.join("\n\n")}` : "";
69403
69475
  return `The review of ${input.where} requested changes. Load the \`address-review-findings\` skill and address every finding below, nits included: record one disposition per finding with submit_dispositions (fixed or declined, with a note), squash to coherent commits, resubmit the pull request description with submit_pr_description, and push the branch. Never merge and never approve.
69404
69476
 
69405
69477
  Findings:
69406
- ${findings}
69478
+ ${findings}${answers}
69407
69479
 
69408
69480
  Review:
69409
69481
  ${input.review}`;
@@ -69422,7 +69494,8 @@ ${task}` : task;
69422
69494
  case "review": {
69423
69495
  let prior;
69424
69496
  if (brief.prior !== void 0) {
69425
- const findings = [...(await facts(readers, brief.prior.reviewRunId)).findings ?? [], ...brief.checks ?? []];
69497
+ const reviewFindings = brief.prior.findings ?? (brief.prior.reviewRunId !== void 0 ? (await facts(readers, brief.prior.reviewRunId)).findings ?? [] : []);
69498
+ const findings = [...reviewFindings, ...brief.checks ?? []];
69426
69499
  const recorded = brief.prior.codingRunId !== void 0 ? (await facts(readers, brief.prior.codingRunId)).dispositions ?? [] : [];
69427
69500
  const { matched, dropped } = matchDispositions(findings, recorded);
69428
69501
  prior = { findings, dispositions: matched, dropped };
@@ -69440,13 +69513,14 @@ ${task}` : task;
69440
69513
  ${turn}`, contract };
69441
69514
  }
69442
69515
  case "findings": {
69443
- const review = await facts(readers, brief.reviewRunId);
69516
+ const review = brief.reviewRunId !== void 0 ? brief.findings !== void 0 ? await readers.readRunFacts(brief.reviewRunId) ?? {} : await facts(readers, brief.reviewRunId) : {};
69444
69517
  return {
69445
69518
  preset: "coding",
69446
69519
  prompt: findingsRequest({
69447
69520
  where,
69448
- findings: [...review.findings ?? [], ...brief.checks ?? []],
69449
- review: review.finalReply ?? ""
69521
+ findings: [...brief.findings ?? review.findings ?? [], ...brief.checks ?? []],
69522
+ review: review.finalReply ?? "",
69523
+ ...brief.answers !== void 0 ? { answers: brief.answers } : {}
69450
69524
  }),
69451
69525
  ref: unit.branch
69452
69526
  };
@@ -69455,7 +69529,7 @@ ${turn}`, contract };
69455
69529
  return {
69456
69530
  preset: "coding",
69457
69531
  ref: unit.branch,
69458
- prompt: `The approved pull request ${prUrl(instance.repo, brief.pr)} conflicts with \`${brief.base}\` at reviewed head \`${brief.headSha}\`. Rebase \`${unit.branch}\` onto the latest \`${brief.base}\`, resolve only the conflicts git left using the thread and repository rules, run the changed-set fast gates, and push with lease. Resubmit the pull request description at the pushed head. Never merge and never approve; the pipeline re-reviews the changed patch.`
69532
+ prompt: `The approved pull request ${prUrl(instance.repo, brief.pr)} conflicts with \`${brief.base}\` at reviewed head \`${brief.headSha}\`. Rebase \`${unit.branch}\` onto the latest \`${brief.base}\`, resolve only the conflicts git left using the thread and repository rules, and make the lease-protected push only after the changed-set fast gates pass. Resubmit the pull request description at the pushed head. Never merge and never approve; the pipeline re-reviews the changed patch.`
69459
69533
  };
69460
69534
  default: {
69461
69535
  const unknown = brief;
@@ -69465,7 +69539,7 @@ ${turn}`, contract };
69465
69539
  }
69466
69540
  async function facts(readers, runId3) {
69467
69541
  const read = await readers.readRunFacts(runId3);
69468
- if (!read) throw new Error(`run ${runId3} is not in the run history`);
69542
+ if (!read) throw new Error(`the run ${runId3} is not in history`);
69469
69543
  return read;
69470
69544
  }
69471
69545
  var SPECS_DIR2, RULES_FILES2, isGenerated, prUrl;
@@ -69920,6 +69994,10 @@ ${foldThreadEvents(folded)}` };
69920
69994
  at
69921
69995
  });
69922
69996
  }
69997
+ function reviewAskedAtOf(events) {
69998
+ const posted = [...events ?? []].reverse().find((event) => event.type === "review_posted");
69999
+ return posted?.type === "review_posted" && typeof posted.at === "number" ? posted.at : void 0;
70000
+ }
69923
70001
  function finalReplyOf2(events) {
69924
70002
  const last = [...events ?? []].reverse().find((e) => e.type === "answer");
69925
70003
  return last && last.type === "answer" ? last.text : void 0;
@@ -69954,6 +70032,74 @@ async function reviewPostedAt(deps, pr, verdict, head) {
69954
70032
  (r) => r.author?.login === self.login && (r.author.id === void 0 || r.author.id === self.id) && r.commitId !== void 0 && sameCommit(r.commitId.toLowerCase(), head) && r.body.startsWith(token2)
69955
70033
  );
69956
70034
  }
70035
+ function legacyPostedFinding(value, body) {
70036
+ if (isFindingShape(value)) return value;
70037
+ if (typeof value !== "object" || value === null) return void 0;
70038
+ const row2 = value;
70039
+ if (row2.title !== void 0 || typeof row2.id !== "string" || typeof row2.severity !== "string") return void 0;
70040
+ const prefix = `| ${row2.severity} | **${escapeMarkdownTableCell(row2.id)}** `;
70041
+ const header4 = "| Severity | Finding | Where |\n| --- | --- | --- |\n";
70042
+ const tableStart = body.indexOf(header4);
70043
+ if (tableStart < 0) return void 0;
70044
+ const table = body.slice(tableStart + header4.length).split("\n\n", 1)[0];
70045
+ const titles = table.split("\n").filter((line4) => line4.startsWith(prefix)).flatMap((line4) => {
70046
+ const tail = line4.slice(prefix.length);
70047
+ const boundary = tail.lastIndexOf(" | ");
70048
+ return boundary > 0 ? [unescapeMarkdownTableCell(tail.slice(0, boundary)).trim()] : [];
70049
+ }).filter(Boolean);
70050
+ if (titles.length !== 1) return void 0;
70051
+ const recovered = { ...row2, title: titles[0] };
70052
+ return isFindingShape(recovered) ? recovered : void 0;
70053
+ }
70054
+ async function postedHumanGateAnswer(deps, pr, requester, addressSeverity) {
70055
+ if (deps.fetchPrComments === void 0) return void 0;
70056
+ const [reviews, comments, self] = await Promise.all([
70057
+ deps.fetchPrReviews(pr).catch(() => void 0),
70058
+ deps.fetchPrComments(pr).catch(() => void 0),
70059
+ deps.selfIdentity().catch(() => void 0)
70060
+ ]);
70061
+ if (reviews === void 0 || comments === void 0 || self === void 0) return void 0;
70062
+ const own = reviews.filter(
70063
+ (review) => review.author?.login === self.login && (review.author.id === void 0 || review.author.id === self.id) && review.submittedAt !== void 0 && Number.isFinite(Date.parse(review.submittedAt))
70064
+ ).sort((a, b) => Date.parse(a.submittedAt) - Date.parse(b.submittedAt)).at(-1);
70065
+ if (own === void 0) return void 0;
70066
+ const marker = /<!-- switchboard:verdict (\{[^\n]*\}) -->/.exec(own.body);
70067
+ if (marker === null) return void 0;
70068
+ let payload;
70069
+ try {
70070
+ payload = JSON.parse(marker[1]);
70071
+ } catch {
70072
+ return void 0;
70073
+ }
70074
+ if (typeof payload !== "object" || payload === null) return void 0;
70075
+ const row2 = payload;
70076
+ if (row2.verdict !== "approve" && row2.verdict !== "request_changes") return void 0;
70077
+ if (!Array.isArray(row2.findings)) return void 0;
70078
+ const postedFindings = row2.findings.map((finding) => legacyPostedFinding(finding, own.body));
70079
+ if (!postedFindings.every((finding) => finding !== void 0)) return void 0;
70080
+ const findings = row2.verdict === "approve" ? findingsAtOrAbove(postedFindings, addressSeverity) : postedFindings;
70081
+ if (findings.length === 0 || !findings.every((finding) => finding.humanGated === true)) return void 0;
70082
+ const candidates = comments.filter(
70083
+ (comment) => comment.author.type === "User" && comment.body.trim() !== "" && Number.isFinite(Date.parse(comment.createdAt)) && Date.parse(comment.createdAt) >= githubSecondStart(Date.parse(own.submittedAt))
70084
+ ).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
70085
+ let answer;
70086
+ for (const candidate of candidates) {
70087
+ const authorized = await deps.commenterAuthorized(requester, candidate.author).catch(() => false);
70088
+ if (authorized) {
70089
+ answer = candidate;
70090
+ break;
70091
+ }
70092
+ }
70093
+ if (answer === void 0) return void 0;
70094
+ return {
70095
+ round: 1,
70096
+ findings,
70097
+ verdict: row2.verdict,
70098
+ answer: answer.body.trim(),
70099
+ author: answer.author.login,
70100
+ commentId: String(answer.id)
70101
+ };
70102
+ }
69957
70103
  async function reviewPostedAtPatiently(deps, pr, verdict, head) {
69958
70104
  const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve13) => setTimeout(resolve13, ms)));
69959
70105
  let answer;
@@ -69986,6 +70132,7 @@ async function readRecord(body, deps) {
69986
70132
  const full = await deps.runs.getRun(body.runId, { include: "messages" });
69987
70133
  const record2 = full.ok ? full.value : view;
69988
70134
  const finalReply = finalReplyOf2(record2.events);
70135
+ const reviewAskedAt = reviewAskedAtOf(record2.events);
69989
70136
  const pr = prOpenedOf(record2.events);
69990
70137
  const instanceRow = await deps.instances.get(id.value);
69991
70138
  if (view.status === "interrupted") {
@@ -70027,6 +70174,7 @@ async function readRecord(body, deps) {
70027
70174
  ...pr !== void 0 ? { pr } : {},
70028
70175
  ...record2.verdict !== void 0 ? { verdict: record2.verdict } : {},
70029
70176
  ...record2.reviewHead !== void 0 ? { reviewHead: record2.reviewHead } : {},
70177
+ ...reviewAskedAt !== void 0 ? { reviewAskedAt } : {},
70030
70178
  ...posted !== void 0 ? { reviewPosted: posted.reviewPosted } : {},
70031
70179
  ...posted?.reviewPostReason !== void 0 ? { reviewPostReason: posted.reviewPostReason } : {},
70032
70180
  ...record2.dispositions !== void 0 ? { dispositions: record2.dispositions } : {},
@@ -70172,11 +70320,20 @@ async function prCheck(body, deps) {
70172
70320
  const open2 = await deps.findOpenPrByHead(instance.repo, branch2);
70173
70321
  if (open2) {
70174
70322
  await remember({ number: open2.number, url: open2.htmlUrl });
70175
- deps.runnerOwnership?.claim(instance.repo, open2.number);
70323
+ deps.runnerOwnership?.claim(instance.repo, open2.number, {
70324
+ instanceId: instance.id,
70325
+ unit: unit.row?.unit ?? body.unit
70326
+ });
70176
70327
  const entryFacts = entry ? await deps.fetchPrFacts({ repo: instance.repo, number: open2.number }).catch(() => void 0) : void 0;
70177
70328
  const branchHead = entryFacts?.headSha;
70178
70329
  const entryHead = branchHead ?? open2.headSha;
70179
70330
  const approved = entry && entryHead !== void 0 ? await reviewPostedAt(deps, { repo: instance.repo, number: open2.number }, "approve", entryHead) : void 0;
70331
+ const humanGate = entry ? await postedHumanGateAnswer(
70332
+ deps,
70333
+ { repo: instance.repo, number: open2.number },
70334
+ instance.userId,
70335
+ instance.addressSeverity ?? DEFAULT_ADDRESS_SEVERITY
70336
+ ) : void 0;
70180
70337
  const entryChecks = entry && entryHead !== void 0 ? await deps.fetchCommitChecks(instance.repo, entryHead).catch(() => void 0) : void 0;
70181
70338
  const checks = body.checks === true && open2.headSha !== void 0 ? await deps.fetchCommitChecks(instance.repo, open2.headSha).catch(() => void 0) : entryChecks;
70182
70339
  const prRef = { repo: instance.repo, number: open2.number };
@@ -70192,6 +70349,7 @@ async function prCheck(body, deps) {
70192
70349
  ...open2.headSha !== void 0 ? { headSha: open2.headSha } : {},
70193
70350
  ...branchHead !== void 0 ? { branchHead } : {},
70194
70351
  ...approved !== void 0 ? { approved } : {},
70352
+ ...humanGate !== void 0 ? { humanGate } : {},
70195
70353
  // The pull request's own auto-merge fact (agent-ship item 9), so a
70196
70354
  // merge_ready ending can name it at the approved head.
70197
70355
  ...open2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: open2.autoMergeEnabled } : {},
@@ -70551,6 +70709,7 @@ function parseIdle(ending, runId3, at) {
70551
70709
  ...typeof runId3 === "string" && RUN_ID_PATTERN.test(runId3) ? { runId: runId3 } : {},
70552
70710
  spendUsd: typeof ending.spendUsd === "number" && Number.isFinite(ending.spendUsd) ? ending.spendUsd : null,
70553
70711
  ...isHandoffShape(ending.handoff) ? { handoff: ending.handoff } : {},
70712
+ ...isHumanGatePending(ending.humanGate) ? { humanGate: ending.humanGate } : {},
70554
70713
  wakes: 0
70555
70714
  };
70556
70715
  }
@@ -70590,20 +70749,37 @@ async function unitWake(body, deps) {
70590
70749
  }
70591
70750
  const events = await deps.instances.listEvents({ instanceId: instance.id, unit: row2.unit }, true);
70592
70751
  const idle = row2.idle;
70752
+ const askedAt = idle.humanGate?.askedAt;
70753
+ const wakeEvents = askedAt !== void 0 ? events.filter(
70754
+ (event) => event.id?.startsWith("github:issue-comment:") ? event.at >= githubSecondStart(askedAt) : event.at > askedAt
70755
+ ) : events;
70593
70756
  const senderOf = (e) => e.senderName ?? e.sender;
70594
- const senders = [...new Set(events.map(senderOf))];
70595
- const requesterWoke = events.some((event) => event.sender === instance.userId);
70596
- const stopperWoke = events.some((event) => event.mode === "interrupt");
70757
+ const senders = [...new Set(wakeEvents.map(senderOf))];
70758
+ const requesterWoke = wakeEvents.some((event) => event.sender === instance.userId);
70759
+ const stopperWoke = wakeEvents.some((event) => event.mode === "interrupt");
70597
70760
  const grantFact = deps.shipGrantFor?.(instance) ?? {
70598
70761
  grant: instance.grant ?? DEFAULT_GRANT,
70599
70762
  source: instance.grantSource ?? "org"
70600
70763
  };
70601
70764
  const renewalsSpent = row2.segments?.length ?? 0;
70602
70765
  let answer;
70603
- if (events.length === 0) {
70766
+ if (wakeEvents.length === 0) {
70604
70767
  answer = { kind: "answered", reply: "Nothing new was waiting for this unit." };
70605
70768
  } else if (idle.wakes + 1 >= IDLE_WAKES_MAX) {
70606
70769
  answer = { kind: "expired" };
70770
+ } else if (idle.humanGate !== void 0) {
70771
+ answer = {
70772
+ kind: "segment",
70773
+ index: row2.segments?.at(-1)?.index ?? 1,
70774
+ ...idle.from !== void 0 ? { from: idle.from } : {},
70775
+ ...idle.runId !== void 0 ? { runId: idle.runId } : {},
70776
+ spendUsd: idle.spendUsd,
70777
+ ...idle.handoff !== void 0 ? { handoff: idle.handoff } : {},
70778
+ texts: wakeEvents.map(attributedText),
70779
+ senders,
70780
+ leaseMs: minutesToMs(instance.caps?.maxMinutes ?? resolveShipCaps(void 0).maxMinutes),
70781
+ humanGate: idle.humanGate
70782
+ };
70607
70783
  } else {
70608
70784
  const segmentStart = row2.segments?.at(-1)?.at ?? row2.startedAt ?? idle.at;
70609
70785
  const leaseMs = Math.max(
@@ -70618,7 +70794,7 @@ async function unitWake(body, deps) {
70618
70794
  ...idle.runId !== void 0 ? { runId: idle.runId } : {},
70619
70795
  spendUsd: idle.spendUsd,
70620
70796
  ...idle.handoff !== void 0 ? { handoff: idle.handoff } : {},
70621
- texts: events.map(attributedText),
70797
+ texts: wakeEvents.map(attributedText),
70622
70798
  senders,
70623
70799
  leaseMs
70624
70800
  };
@@ -70643,18 +70819,18 @@ async function unitWake(body, deps) {
70643
70819
  ...idle.runId !== void 0 ? { runId: idle.runId } : {},
70644
70820
  spendUsd: idle.spendUsd,
70645
70821
  ...idle.handoff !== void 0 ? { handoff: idle.handoff } : {},
70646
- texts: events.map(attributedText),
70822
+ texts: wakeEvents.map(attributedText),
70647
70823
  senders
70648
70824
  };
70649
70825
  } else {
70650
70826
  answer = {
70651
70827
  kind: "answered",
70652
- reply: `${renderRenewal(decision, grantFact.grant, { idle: true })} (grant from ${grantFact.source}); run \`runs stop ${instance.id}:${row2.unit}\` to end this idle unit.`
70828
+ reply: `${renderRenewal(decision, grantFact.grant, { idle: true })} (grant from ${grantFact.source}); the idle unit remains open, and \`runs stop ${instance.id}:${row2.unit}\` is its stop command.`
70653
70829
  };
70654
70830
  }
70655
70831
  }
70656
70832
  }
70657
- const countedIdle = events.length > 0 ? { ...idle, wakes: idle.wakes + 1 } : idle;
70833
+ const countedIdle = wakeEvents.length > 0 ? { ...idle, wakes: idle.wakes + 1 } : idle;
70658
70834
  const segments2 = row2.segments ?? [];
70659
70835
  const updated = {
70660
70836
  ...row2,
@@ -70837,12 +71013,14 @@ ${rendered}` : "");
70837
71013
  )
70838
71014
  );
70839
71015
  }
70840
- if (ending.kind === "held" && updated.pr !== void 0) {
70841
- await deps.github.commentIssue(instance.repo, updated.pr.number, `**Plan runner \u2014 ${row2.unit} held**
71016
+ const parkedHumanGate = ending.kind === "idle" && idle?.humanGate !== void 0;
71017
+ if ((ending.kind === "held" || parkedHumanGate) && updated.pr !== void 0) {
71018
+ const state = parkedHumanGate ? "waiting for a person" : "held";
71019
+ await deps.github.commentIssue(instance.repo, updated.pr.number, `**Plan runner \u2014 ${row2.unit} ${state}**
70842
71020
 
70843
71021
  ${ending.report}`).catch(
70844
71022
  (err2) => (deps.log ?? console.warn)(
70845
- `[coordinator] ${instance.id} ${row2.unit}: the held report could not be posted on the pull request: ${describe11(err2)}`
71023
+ `[coordinator] ${instance.id} ${row2.unit}: the human-gated report could not be posted on the pull request: ${describe11(err2)}`
70846
71024
  )
70847
71025
  );
70848
71026
  }
@@ -70877,7 +71055,10 @@ async function rebaseStep(body, deps) {
70877
71055
  if (!row2) return json3(404, { ok: false, error: "unknown_unit", at });
70878
71056
  if (row2.pr?.number !== void 0 && row2.pr.number !== body.prNumber)
70879
71057
  return json3(409, { ok: false, error: "pull_request_moved", at });
70880
- deps.runnerOwnership?.claim(instance.repo, body.prNumber);
71058
+ deps.runnerOwnership?.claim(instance.repo, body.prNumber, {
71059
+ instanceId: instance.id,
71060
+ unit: row2.unit
71061
+ });
70881
71062
  if (deps.runnerRebase === void 0)
70882
71063
  return json3(200, { ok: true, outcome: "refused", reason: "the runner's rebase resolver is unavailable", at });
70883
71064
  try {
@@ -71034,7 +71215,7 @@ async function merge(body, deps, subject2) {
71034
71215
  const enqueue = async () => {
71035
71216
  if (deps.enqueuePullRequest === void 0)
71036
71217
  return refused7(
71037
- `\`${facts2.baseRef ?? "the base"}\` takes changes only through a merge queue and the door cannot enqueue \u2014 enqueue ${where} by hand (\`gh pr merge --auto\`); the approved work stands`
71218
+ `\`${facts2.baseRef ?? "the base"}\` takes changes only through a merge queue and the door cannot enqueue \u2014 this is a bug: automatic merge-queue enqueue is unavailable; the approved work stands`
71038
71219
  );
71039
71220
  let queued;
71040
71221
  try {
@@ -71133,7 +71314,7 @@ async function checksStep(body, deps) {
71133
71314
  function planSummary(units, generated = false) {
71134
71315
  const lines = units.map((u) => {
71135
71316
  const kind = u.ending ? u.ending.kind : u.threadKey ? "unfinished" : "not started";
71136
- const how = u.ending ? endingWordOf(u.ending.kind) : u.threadKey ? "no ending was recorded \u2014 re-issue `agent:ship` in its thread to continue" : kind;
71317
+ const how = u.ending ? endingWordOf(u.ending.kind) : u.threadKey ? "no ending was recorded \u2014 the next reply in its thread continues the unit" : kind;
71137
71318
  const pr = u.pr ? ` \u2014 ${u.pr.url}` : "";
71138
71319
  return generated ? `${ENDING_ICON[kind] ?? "\u2022"} ${how}${pr}` : `${ENDING_ICON[kind] ?? "\u2022"} ${u.unit} \u2014 ${how}${pr}`;
71139
71320
  });
@@ -71324,7 +71505,7 @@ function createAdminCoordinatorHandler(deps) {
71324
71505
  })();
71325
71506
  };
71326
71507
  }
71327
- var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe11, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
71508
+ var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe11, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, ROUND_OUTCOMES, ENDING_ICON, STEPS;
71328
71509
  var init_adminCoordinator = __esm({
71329
71510
  "../../src/channels/adminCoordinator.ts"() {
71330
71511
  "use strict";
@@ -71376,6 +71557,7 @@ var init_adminCoordinator = __esm({
71376
71557
  EVERY_RUN = { kind: "all" };
71377
71558
  REVIEW_POSTED_CHECKS = 3;
71378
71559
  REVIEW_POSTED_RECHECK_MS = 2e3;
71560
+ githubSecondStart = (at) => Math.floor(at / SECOND_MS) * SECOND_MS;
71379
71561
  ROUND_OUTCOMES = [
71380
71562
  "started",
71381
71563
  "pr_opened",
@@ -71479,6 +71661,56 @@ async function handleCheckRunIntake(headers, rawBody, deps) {
71479
71661
  body: { ok: true, settled: true, sent: sends.filter((s) => s.kind === "sent").length, of: instances.length }
71480
71662
  };
71481
71663
  }
71664
+ async function handleIssueCommentIntake(headers, rawBody, deps) {
71665
+ if (deps.secret === void 0 || deps.secret === "") return { status: 503, body: { error: "disabled" } };
71666
+ if (headers.signature === void 0 || !await verifyWebhookSignature(deps.secret, rawBody, headers.signature))
71667
+ return { status: 401, body: { error: "unauthorized" } };
71668
+ if (headers.event !== "issue_comment") return { status: 200, body: { ok: true, ignored: "event" } };
71669
+ let payload;
71670
+ try {
71671
+ payload = JSON.parse(rawBody);
71672
+ } catch {
71673
+ return { status: 400, body: { error: "invalid_json" } };
71674
+ }
71675
+ if (typeof payload !== "object" || payload === null) return { status: 400, body: { error: "invalid_body" } };
71676
+ const p = payload;
71677
+ if (p.action !== "created") return { status: 200, body: { ok: true, ignored: "action" } };
71678
+ if (p.issue?.pull_request === void 0) return { status: 200, body: { ok: true, ignored: "issue" } };
71679
+ const repo = p.repository?.full_name;
71680
+ const prNumber = p.issue.number;
71681
+ const comment = p.comment;
71682
+ if (typeof repo !== "string" || repo === "" || typeof prNumber !== "number" || !Number.isInteger(prNumber) || typeof comment?.id !== "number" || typeof comment.body !== "string" || typeof comment.created_at !== "string" || typeof comment.user?.login !== "string" || typeof comment.user.type !== "string")
71683
+ return { status: 400, body: { error: "invalid_body" } };
71684
+ if (comment.user.type !== "User") return { status: 200, body: { ok: true, ignored: "sender" } };
71685
+ let owner;
71686
+ try {
71687
+ owner = deps.ownerOf(repo, prNumber);
71688
+ } catch {
71689
+ return { status: 503, body: { error: "ownership_unavailable" } };
71690
+ }
71691
+ if (owner === void 0) return { status: 200, body: { ok: true, ignored: "unowned" } };
71692
+ const instance = await deps.instances.get(owner.instanceId);
71693
+ if (instance === null) return { status: 200, body: { ok: true, ignored: "ended" } };
71694
+ const authorized = await deps.commenterAuthorized(instance.userId, {
71695
+ login: comment.user.login,
71696
+ ...typeof comment.user.id === "number" ? { id: comment.user.id } : {}
71697
+ }).catch(() => false);
71698
+ if (!authorized) return { status: 200, body: { ok: true, ignored: "sender" } };
71699
+ const row2 = (await deps.instances.listUnits(owner.instanceId)).find((unit) => unit.unit === owner.unit);
71700
+ if (row2 === void 0 || row2.ending !== void 0) return { status: 200, body: { ok: true, ignored: "ended" } };
71701
+ const created = Date.parse(comment.created_at);
71702
+ const appended = await deps.instances.appendEvent(owner, {
71703
+ id: `github:issue-comment:${comment.id}`,
71704
+ sender: `github:${typeof comment.user.id === "number" ? comment.user.id : comment.user.login}`,
71705
+ senderName: comment.user.login,
71706
+ text: comment.body,
71707
+ mode: row2.idle !== void 0 ? "wake" : "steer",
71708
+ at: Number.isFinite(created) ? created : deps.now()
71709
+ });
71710
+ if (!appended.ok) return { status: 503, body: { error: "store_unavailable" } };
71711
+ const sent = await sendUnitNudge(deps.workflow, owner);
71712
+ return { status: 200, body: { ok: true, appended: true, seq: appended.seq, nudge: sent.kind } };
71713
+ }
71482
71714
  async function handlePushIntake(headers, rawBody, deps) {
71483
71715
  if (deps.secret === void 0 || deps.secret === "") return { status: 503, body: { error: "disabled" } };
71484
71716
  if (headers.signature === void 0 || !await verifyWebhookSignature(deps.secret, rawBody, headers.signature))
@@ -71528,7 +71760,14 @@ function createGithubWebhookHandler(deps) {
71528
71760
  event: one(req.headers[GITHUB_EVENT_HEADER]),
71529
71761
  signature: one(req.headers[GITHUB_SIGNATURE_HEADER])
71530
71762
  };
71531
- const result = headers.event === "push" ? await handlePushIntake(headers, body.body, { secret: deps.secret, watch: deps.watch }) : await handleCheckRunIntake(headers, body.body, deps);
71763
+ const result = headers.event === "push" ? await handlePushIntake(headers, body.body, { secret: deps.secret, watch: deps.watch }) : headers.event === "issue_comment" && deps.ownerOf !== void 0 && deps.instances !== void 0 && deps.commenterAuthorized !== void 0 && deps.now !== void 0 ? await handleIssueCommentIntake(headers, body.body, {
71764
+ secret: deps.secret,
71765
+ ownerOf: deps.ownerOf,
71766
+ instances: deps.instances,
71767
+ commenterAuthorized: deps.commenterAuthorized,
71768
+ workflow: deps.workflow,
71769
+ now: deps.now
71770
+ }) : await handleCheckRunIntake(headers, body.body, deps);
71532
71771
  answer(result.status, result.body);
71533
71772
  };
71534
71773
  }
@@ -71786,14 +72025,17 @@ async function sweepOne(pr, deps, owner) {
71786
72025
  return at("skipped", "deferred \u2014 its pipeline runner owns the rebase");
71787
72026
  const dirty = pr.mergeableState === "dirty";
71788
72027
  if (!dirty && (pr.mergeableState === "unknown" || pr.mergeableState === ""))
71789
- return at("skipped", "mergeability still computing \u2014 run the sweep again in a minute");
72028
+ return at("skipped", "mergeability still computing \u2014 no rebase was attempted");
71790
72029
  if (!dirty) return at("skipped", "skipped, already current");
71791
72030
  try {
71792
72031
  const rebased = await deps.git.rebase(pr);
71793
72032
  const decision = rebased.kind === "conflict" ? owner === "runner" ? { action: "model-round", file: rebased.file } : decideSweep({ dirty, rebase: rebased, modelRoundSpent: await deps.effects.modelRoundSpent(pr) }) : decideSweep({ dirty, rebase: rebased, patchUnchanged: await deps.git.patchUnchanged(pr, rebased.newHead) });
71794
72033
  switch (decision.action) {
71795
72034
  case "end":
71796
- return at("conflict", `conflict in ${decision.file}, its fix round is spent \u2014 rebase it by hand`);
72035
+ return at(
72036
+ "conflict",
72037
+ `this is a bug: the conflict in ${decision.file} remained after the sweep's fix round, and no automatic recovery remains`
72038
+ );
71797
72039
  case "model-round": {
71798
72040
  if (owner === "runner")
71799
72041
  return at("conflict", `conflict in ${decision.file}, the pipeline runner's fix round will resolve it`);
@@ -72598,9 +72840,16 @@ async function runBot() {
72598
72840
  );
72599
72841
  }
72600
72842
  });
72843
+ const commenterAuthorized = async (requester, author) => {
72844
+ const binding = await bindingOf(requester, config);
72845
+ return binding !== void 0 && author.id === binding.id && author.login.toLowerCase() === binding.login.toLowerCase();
72846
+ };
72601
72847
  const githubWebhook = createGithubWebhookHandler({
72602
72848
  secret: processSecrets.get("GITHUB_WEBHOOK_SECRET")?.reveal(),
72603
72849
  watch: mergeWatch,
72850
+ ownerOf: (repo, prNumber) => runnerOwnership.owner(repo, prNumber),
72851
+ instances: coordinatorInstances,
72852
+ commenterAuthorized,
72604
72853
  checksSettled: async (repo, headSha) => {
72605
72854
  const checks = await fetchCommitChecks(repo, headSha);
72606
72855
  return checks !== void 0 && checks.total > 0 && checks.pending.length === 0;
@@ -72662,6 +72911,8 @@ async function runBot() {
72662
72911
  github: deps.githubApi ?? new RestGithubApi(),
72663
72912
  createBranchRef,
72664
72913
  fetchPrReviews: fetchPullRequestReviews,
72914
+ fetchPrComments: fetchPullRequestComments,
72915
+ commenterAuthorized,
72665
72916
  // The merge step (record 0031's merge grant): the pull request as GitHub has it, the checks at its head, the squash.
72666
72917
  fetchPrFacts: fetchPullRequestFacts,
72667
72918
  fetchCommitChecks,
@@ -73288,6 +73539,7 @@ var init_index = __esm({
73288
73539
  init_pullSweepWiring();
73289
73540
  init_githubApi();
73290
73541
  init_githubApp();
73542
+ init_authorBinding();
73291
73543
  init_identityRewrite();
73292
73544
  init_run2();
73293
73545
  init_record();
@@ -73581,7 +73833,7 @@ function askExitCode(finished) {
73581
73833
  function missingBotConfig(configPath) {
73582
73834
  return new CommandError(
73583
73835
  "unavailable",
73584
- `bot config not found at ${configPath} \u2014 set SWITCHBOARD_CONFIG to a config file or run from a checkout with config/config.yaml (deploy, env, friction analyze need none)`
73836
+ `bot config not found at ${configPath} \u2014 the command stopped before work began; the config path comes from SWITCHBOARD_CONFIG or defaults to config/config.yaml (deploy, env, friction analyze need no bot config)`
73585
73837
  );
73586
73838
  }
73587
73839
  async function loadBotConfig(configPath, overridesPath, opts = {}) {