@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.
- package/dist/assets/deploy/cloudflare-memory/backgroundTasks.ts +27 -3
- package/dist/assets/deploy/cloudflare-memory/testDiagnostics.ts +227 -0
- package/dist/assets/deploy/cloudflare-memory/testDiagnosticsProtocol.ts +22 -0
- package/dist/assets/deploy/cloudflare-memory/testDiagnosticsReporter.ts +88 -0
- package/dist/assets/deploy/cloudflare-memory/tsconfig.json +14 -2
- package/dist/assets/deploy/cloudflare-resident/Dockerfile +11 -2
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +4 -3
- package/dist/assets/project.json +4 -0
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +6 -5
- package/dist/assets/src/core/coordinator/contract.ts +48 -2
- package/dist/assets/src/core/coordinator/driver.ts +40 -5
- package/dist/assets/src/core/reviewVerdict.ts +15 -5
- package/dist/assets/src/core/runFriction.ts +1 -1
- package/dist/assets/src/core/ship/contract.ts +2 -2
- package/dist/assets/src/core/ship/coordinator.ts +236 -108
- package/dist/assets/src/core/ship/renewal.ts +3 -1
- package/dist/assets/src/execution/bashTimeout.ts +1 -1
- package/dist/assets/src/execution/sandboxErrors.ts +6 -6
- package/dist/assets/web/dist/.vite/manifest.json +488 -488
- package/dist/assets/web/dist/assets/{AppShell-CSXDCV4x.js → AppShell-DPJVhUGP.js} +1 -1
- package/dist/assets/web/dist/assets/{CostChart-DsuPS2g7.js → CostChart-4TeDdbvJ.js} +1 -1
- package/dist/assets/web/dist/assets/CostsPage-8HDgvuWB.js +1 -0
- package/dist/assets/web/dist/assets/{DeliveryPage-62qCSLvk.js → DeliveryPage-BaemWKW8.js} +1 -1
- package/dist/assets/web/dist/assets/{HomePage-Dk3HRBc3.js → HomePage-fgKQHLaO.js} +1 -1
- package/dist/assets/web/dist/assets/{InputMenu-BdZXnibG.js → InputMenu-BRV35UWP.js} +1 -1
- package/dist/assets/web/dist/assets/{MetricsPage-o5fSaCTe.js → MetricsPage-BQBuIuYU.js} +1 -1
- package/dist/assets/web/dist/assets/{NotFoundPage-DHuWyjlq.js → NotFoundPage-CDjMfCcj.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-CeTGOXUy.js → PendingTurnRow-vuWiDzvz.js} +1 -1
- package/dist/assets/web/dist/assets/{PlanePage-RD0M6O6W.js → PlanePage-DXMJf8FB.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-Dl2KId95.js → ResidentDetailPage-DdC8yU_S.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-DBy43I1R.js → ResidentsIndexPage-CSM-KODv.js} +1 -1
- package/dist/assets/web/dist/assets/{RunFoldRow-CwHDL-Mo.js → RunFoldRow-Csik3wD6.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-Du1HqLZs.js → RunRoutePage-jo6NZxsP.js} +3 -3
- package/dist/assets/web/dist/assets/{RunsIndexPage-BZphy3Hr.js → RunsIndexPage-Dx1Y4sSq.js} +1 -1
- package/dist/assets/web/dist/assets/{RunsTabs-kHbbxVHx.js → RunsTabs-C7Ua3XX5.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-B7jh898t.js → ScheduledPage-BuB_NZyR.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingSelect-BfID5nF4.js → SettingSelect-CEeA4iuC.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-3uLEsiiE.js → SettingsPage-Cn96jB6Q.js} +1 -1
- package/dist/assets/web/dist/assets/{SilentTurn-CKxSxnQC.js → SilentTurn-D2cJfpEq.js} +1 -1
- package/dist/assets/web/dist/assets/{SlackMark-CtiHrNjr.js → SlackMark-DB0MGiF5.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-x7vcK0iE.js → StatusDot-DDHUCPVJ.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-wVXCWhMp.js → Tooltip-B4VIHkAI.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-V5BXK7FH.js → UnitRoutePage-C-2DfjLI.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-C8b5YTHf.js → angular-html-BRRhsdQE.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-C3f4WYNe.js → angular-ts-BFj-mqCm.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-DSHxUJc3.js → apl-CsBuy1IL.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-BSGCCBP6.js → astro-cVaqxBZq.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-DFeYrMKk.js → blade-MEP5mWha.js} +1 -1
- package/dist/assets/web/dist/assets/{browser-B6T5hjfR.js → browser-CCImen5O.js} +1 -1
- package/dist/assets/web/dist/assets/{c-L_QOnFVv.js → c-D4NGQ_Pd.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-DLQWUwhu.js → chapel-BkWImsIy.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-1DlMmrEO.js → cobol-RPDSPSET.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-BTB6os6N.js → coffee-DZ5z7oVX.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-BvJI2z3u.js → cpp-CrBtIaRZ.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-B11wGW_h.js → crystal-DJesYi6f.js} +1 -1
- package/dist/assets/web/dist/assets/{css-BiIWx5Pw.js → css-DO9mjau-.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-C-XGnvM4.js → dist-Cf1CmCSK.js} +2 -2
- package/dist/assets/web/dist/assets/{edge-nSglEXaD.js → edge-C3VzbSW0.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-x7o8qMl8.js → elixir-BkD_Vhfe.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DGrRyqTQ.js → elm-KIM0mFPk.js} +1 -1
- package/dist/assets/web/dist/assets/{erb--_dh58Nw.js → erb-D2ruCqb2.js} +1 -1
- package/dist/assets/web/dist/assets/{eventSource-Bj_dZf6M.js → eventSource-Cv0AFlMi.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-DbpUXwyg.js → git-rebase-CnfbQTxA.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-BG0CuZG5.js → glimmer-js-CbiwFXWH.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-CQIl5qyR.js → glimmer-ts-DV8ii0Pv.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-9PJNQTcw.js → glsl-CgmznWkA.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-CzVTzjEI.js → graphql-BK-bBYfH.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-BhCmAz3Q.js → hack-DnwTTP5n.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-BmI12COR.js → haml-DAVJsWwT.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-Cib-ISTS.js → handlebars-voOVfxFL.js} +1 -1
- package/dist/assets/web/dist/assets/{html-D3lDapvI.js → html-3FTqwaPk.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DRPY8vOK.js → html-derivative-CSlhLDQn.js} +1 -1
- package/dist/assets/web/dist/assets/{http-B9VttK6m.js → http-Vx4ilPfS.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-C0saNJYj.js → hurl-DxyZDE3R.js} +1 -1
- package/dist/assets/web/dist/assets/{java-BCFbtYAj.js → java-nWqdjNJG.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-nwBjnrhh.js → javascript-Dr82HOwp.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-CMYO9byN.js → jinja-BhjLrPOt.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-Cko8HnE_.js → jison-DilGIUa8.js} +1 -1
- package/dist/assets/web/dist/assets/{json-Dzv__YsX.js → json-CrOcvmhJ.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CTFxagih.js → jsx-BW_RX-fo.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-_Xvten1-.js → julia-CWH071DB.js} +1 -1
- package/dist/assets/web/dist/assets/{just-bLps4qOu.js → just-DkhhoWBy.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-D6UaYsUP.js → latex-cuHJ4TUT.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-CoDIeULa.js → liquid-DG6afYQN.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-D1KFRnVf.js → lua-C5CMnCPS.js} +1 -1
- package/dist/assets/web/dist/assets/{main-DAL--hyj.js → main-DxPqaFu9.js} +3 -3
- package/dist/assets/web/dist/assets/{marko-B5N5XcOw.js → marko-Dy2_DuC4.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-uxzjQO3q.js → mdc-BMKb_EeA.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-BMJxs2TK.js → nginx-og0RgMKf.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-DqQ3-8p1.js → nim-CQxTQQP0.js} +1 -1
- package/dist/assets/web/dist/assets/{org-DsY6LK59.js → org-b5lkq_Pl.js} +1 -1
- package/dist/assets/web/dist/assets/{perl--pq9iJFp.js → perl-D9X5_i_J.js} +1 -1
- package/dist/assets/web/dist/assets/{php-RHCMbsM2.js → php-CwUxvLX0.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-CFNq_FCp.js → pug-uOIOExr0.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-C9Acx9E8.js → qml-g5RoADf-.js} +1 -1
- package/dist/assets/web/dist/assets/{r-DxlU8FBd.js → r-5LdAopD3.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-CLJ2kKih.js → razor-DwrvFLYQ.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-CzmdGoEp.js → regexp-dGtAaxC-.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-D9yuxBET.js → rst-wFdW0brr.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-CIRcEvnD.js → ruby-DPglN6I9.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-LOl-uUPh.js → sas-CUbCs40d.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-BX2_sZHY.js → scss-B-DUiI1c.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-4d-QvKkY.js → shellscript-lGOV7Ram.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-DhB16UGy.js → shellsession-BDWtAAjr.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-Cm1xTUBr.js → soy-zod_dcOy.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-BsfcBJdu.js → sql-BG0HSSoM.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-BqgOggyQ.js → sseReplay-BzoeVxZB.js} +1 -1
- package/dist/assets/web/dist/assets/{stata-Dwat3BSw.js → stata-BVUMLp1W.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-P4rtxzAW.js → surrealql-DiSUbHKF.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-DTcsi8S8.js → svelte-DZenT45Y.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-D-kP0Tg5.js → templ-BWKRNM0-.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-D-4vuedK.js → tex-CbMzBlkK.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-jEEWYxsu.js → ts-tags-CZsVorDN.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-CFnlrVzT.js → tsx-R1r3mH4W.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-D5rGrPjs.js → twig-BjPMyjKH.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-nYIQ35or.js → typescript-Bx-66VN_.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-3-UX8dna.js → typst-fW037Eo6.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-CLIPIX03.js → vue-D1V_GNxW.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-Ci9CpkHU.js → vue-html-C-4pTZ-s.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-DL5pEYYk.js → vue-vine-C-QsX3hR.js} +1 -1
- package/dist/assets/web/dist/assets/{wallClock-DmaC1sKA.js → wallClock-x0hjr0J_.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-bO80qikC.js → xml-BJTJXPgC.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-BPze60Ej.js → xsl-Xc9lukqe.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BQUSon_o.js → yaml-Dw-FMsQa.js} +1 -1
- package/dist/cli.js +505 -253
- package/package.json +1 -1
- 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.
|
|
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
|
|
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
|
|
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
|
|
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 = `\`${
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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
|
|
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} ${
|
|
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
|
|
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) => `| ${
|
|
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
|
-
|
|
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
|
|
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 `
|
|
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}
|
|
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
|
-
|
|
17469
|
-
const
|
|
17470
|
-
|
|
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
|
-
|
|
17473
|
-
|
|
17474
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
17475
|
-
});
|
|
17499
|
+
candidate = new URL(url);
|
|
17500
|
+
requested = new URL(base);
|
|
17476
17501
|
} catch {
|
|
17477
|
-
return
|
|
17502
|
+
return false;
|
|
17478
17503
|
}
|
|
17479
|
-
if (
|
|
17480
|
-
|
|
17481
|
-
if (
|
|
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
|
|
19961
|
-
if (
|
|
19962
|
-
for (const [ref, m] of Object.entries(
|
|
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(
|
|
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(
|
|
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 = "
|
|
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
|
|
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: "
|
|
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 ? "
|
|
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
|
|
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(", ")}
|
|
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 = "
|
|
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: "
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
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;
|
|
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
|
|
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: `
|
|
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;
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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`,
|
|
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: "
|
|
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" ?
|
|
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
|
|
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
|
|
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
|
-
`
|
|
33040
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
35319
|
-
|
|
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.
|
|
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
|
-
|
|
35470
|
-
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
36416
|
+
function boundaryOutcome(scope) {
|
|
36336
36417
|
switch (scope) {
|
|
36337
36418
|
case "channel":
|
|
36338
|
-
return
|
|
36419
|
+
return "Switchboard left this channel's boundary unchanged";
|
|
36339
36420
|
case "user":
|
|
36340
|
-
return
|
|
36421
|
+
return "Switchboard left your boundary and overrides unchanged";
|
|
36341
36422
|
case "defaults":
|
|
36342
|
-
return
|
|
36423
|
+
return "Switchboard left the installation's default boundary unchanged";
|
|
36343
36424
|
case "directive":
|
|
36344
|
-
return "
|
|
36425
|
+
return "Switchboard applied this message's budget directive";
|
|
36345
36426
|
case "parent":
|
|
36346
|
-
return
|
|
36427
|
+
return "Switchboard left the parent run's remaining budget unchanged";
|
|
36347
36428
|
}
|
|
36348
36429
|
}
|
|
36349
|
-
function
|
|
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
|
|
36367
|
-
return `\u{1F6AB} \`${agentName}\` needs a \`${refusal.needs}\` credential; ${boundaryOf(refusal.scope)} caps runs at \`${refusal.cap}\`. ${
|
|
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
|
|
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}. ${
|
|
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
|
|
36377
|
-
|
|
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
|
|
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
|
|
38959
|
-
QUESTION_EXPIRED_LINE = "this question expired
|
|
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
|
|
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
|
|
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
|
|
40665
|
-
const text =
|
|
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
|
|
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
|
|
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) => `| ${
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
46283
|
-
const
|
|
46284
|
-
|
|
46285
|
-
|
|
46286
|
-
|
|
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
|
|
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
|
|
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 = "
|
|
46341
|
-
TURN_ADVICE = "
|
|
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
|
|
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
|
-
|
|
52553
|
-
|
|
52554
|
-
|
|
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
|
|
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})
|
|
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
|
|
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
|
|
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
|
|
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}
|
|
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
|
|
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
|
|
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("<", "<").replaceAll(">", ">").replaceAll("&", "&").trim();
|
|
62639
|
-
const detail = "
|
|
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;
|
|
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
|
|
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, "
|
|
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
|
|
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, "
|
|
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, "
|
|
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
|
|
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.
|
|
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(
|
|
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
|
|
68334
|
-
return "
|
|
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
|
|
68880
|
-
const owned = /* @__PURE__ */ new
|
|
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.
|
|
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
|
-
|
|
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
|
|
68992
|
+
const recovered = await recoverRunnerOwnedPullOwners(activeRunnerInstances, instances);
|
|
68929
68993
|
this.recovered.clear();
|
|
68930
|
-
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
70595
|
-
const requesterWoke =
|
|
70596
|
-
const stopperWoke =
|
|
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 (
|
|
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:
|
|
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:
|
|
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});
|
|
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 =
|
|
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
|
-
|
|
70841
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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 = {}) {
|