@coreplane/switchboard 1.242.0 → 1.244.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/config/config.example.yaml +20 -1
- package/dist/assets/deploy/cloudflare-memory/worker.ts +234 -29
- package/dist/assets/deploy/cloudflare-resident/gc.ts +8 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +483 -75
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +13 -2
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/project.json +3 -3
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +31 -35
- package/dist/assets/src/config/profile.ts +68 -3
- package/dist/assets/src/core/authz/policy.ts +4 -0
- package/dist/assets/src/core/authz/types.ts +10 -0
- package/dist/assets/src/core/budgets.ts +349 -0
- package/dist/assets/src/core/coordinator/contract.ts +32 -0
- package/dist/assets/src/core/coordinator/driver.ts +75 -22
- package/dist/assets/src/core/costs.ts +9 -76
- package/dist/assets/src/core/delivery.ts +1 -1
- package/dist/assets/src/core/modelPricing.ts +212 -0
- package/dist/assets/src/core/prDescriptionTypes.ts +29 -24
- package/dist/assets/src/core/reviewVerdict.ts +7 -0
- package/dist/assets/src/core/runEvents.ts +72 -5
- package/dist/assets/src/core/runFriction.ts +3 -1
- package/dist/assets/src/core/runRecord.ts +78 -0
- package/dist/assets/src/core/runUsage.ts +158 -47
- package/dist/assets/src/core/schedules.ts +3 -0
- package/dist/assets/src/core/ship/contract.ts +2 -1
- package/dist/assets/src/core/ship/coordinator.ts +312 -97
- package/dist/assets/src/core/ship/handoff.ts +9 -0
- package/dist/assets/src/core/ship/renewal.ts +155 -0
- package/dist/assets/src/core/trace/attrs.ts +11 -0
- package/dist/assets/src/core/trace/types.ts +4 -3
- package/dist/assets/src/execution/bashTimeout.ts +8 -5
- package/dist/assets/src/execution/residentRefresh.ts +52 -7
- package/dist/assets/src/execution/residentSteps.ts +1 -1
- package/dist/assets/src/execution/residentText.ts +8 -3
- package/dist/assets/src/mcp/registry.ts +3 -0
- package/dist/assets/web/dist/.vite/manifest.json +432 -429
- package/dist/assets/web/dist/assets/{AppShell-IDMGG6yi.js → AppShell-Ck4hSZDi.js} +1 -1
- package/dist/assets/web/dist/assets/CostsPage-DvhjDMka.js +2 -0
- package/dist/assets/web/dist/assets/DeliveryPage-DBlI_Yq2.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-XFWPxDiJ.js +2 -0
- package/dist/assets/web/dist/assets/{NotFoundPage-C97EhJcQ.js → NotFoundPage-CiGrVOyv.js} +1 -1
- package/dist/assets/web/dist/assets/PendingTurnRow-BHCSpsPf.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-DTMBgnIW.js → ResidentDetailPage-C5USV_8P.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CSKisWnX.js +1 -0
- package/dist/assets/web/dist/assets/RunFoldRow-BPMQxTcl.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-CZe9Yodw.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-BfA333Qy.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-BUfdk0lH.js → RunsTabs-BWRHzhtT.js} +1 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DBaChcJZ.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-CqVi9-Ac.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-ELoXHlFt.js → StatusDot-_Q_bmReh.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-BoeFwYP2.js → Tooltip-Cafsgd4m.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-CdoBZwtf.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-oBNfPJR0.js → angular-html-Dail07rr.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-BvNwsyWA.js → angular-ts-BReExio8.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-CNUdRlYf.js → apl-DhNO6Yyx.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-Zb0NriSe.js → astro-BYotTY4C.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-qPRVheqq.js → blade-mUxTtD1X.js} +1 -1
- package/dist/assets/web/dist/assets/{c-D8Awx4YO.js → c-DhuiwcSQ.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-Bt72Mhsx.js → chapel-GD2vFtRi.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-BOBacexg.js → cobol-BhFDwkes.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-E4u0liHW.js → coffee-DWUgbE0r.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-q2sLNlul.js → cpp-CO9XFjGW.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-DyWqUnlb.js → crystal-B9pVurpn.js} +1 -1
- package/dist/assets/web/dist/assets/{css-CQY0hFsD.js → css-CFVVV6lI.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BU5UivXC.js → dist-BtPs2AkQ.js} +2 -2
- package/dist/assets/web/dist/assets/{edge-C1MwhJkX.js → edge-BDhIDQIb.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-Bb3YbHfn.js → elixir-DG3J8WoD.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DAN9IGQw.js → elm-FiOdiaOE.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-BEB8Xlsj.js → erb-vxbtW503.js} +1 -1
- package/dist/assets/web/dist/assets/format-BldUwl_R.js +1 -0
- package/dist/assets/web/dist/assets/{git-rebase-tqpjRfxO.js → git-rebase-DINz8M3H.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-Ccbo65zR.js → glimmer-js-yann6r9i.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-B6WBVMpE.js → glimmer-ts-CXTbrZn_.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-tRec3Fcu.js → glsl-DNYC-hq0.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-P8kbxT4F.js → graphql-CwzhHzi9.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-H9Zhkagy.js → hack-BBXF1cwR.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DtEnpn7Z.js → haml-oEqBcsTy.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-DkgPfoAz.js → handlebars-YE5thkT1.js} +1 -1
- package/dist/assets/web/dist/assets/{html-D30RXpIs.js → html-Dp4jRQ1j.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DwozLrEx.js → html-derivative-DCT1GYtQ.js} +1 -1
- package/dist/assets/web/dist/assets/{http-DXuzBAPm.js → http-BPcGbmzJ.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-d1UUJIt_.js → hurl-CQnNHwyd.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-BmK74Vp1.js +1 -0
- package/dist/assets/web/dist/assets/{java-DL0gWf34.js → java-Dm8Ovj6W.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-Cl7vavnS.js → javascript-Btm9oJK9.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-CfQOWMX9.js → jinja-VZYjWF_U.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-KdYirlqm.js → jison-Dcmjxa9x.js} +1 -1
- package/dist/assets/web/dist/assets/{json-C9cDQ-Qj.js → json-DoWiOGZ8.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CRx5NItd.js → jsx-Dvmo55ma.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-CmsQsZQl.js → julia-d8AtfV26.js} +1 -1
- package/dist/assets/web/dist/assets/{just-CsM3Q8TE.js → just-Cc5EStA-.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BXCh5YRX.js → latex-B5fKKIH7.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-BF2vwK8p.js → liquid-DUYI9JND.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-DcMBATrl.js → lua-BhMwKWpw.js} +1 -1
- package/dist/assets/web/dist/assets/main-C4366PZF.css +1 -0
- package/dist/assets/web/dist/assets/main-DlTNAC6c.js +28 -0
- package/dist/assets/web/dist/assets/{marko-88MndKvG.js → marko-Cnf2cPAi.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-DJ4kVAd8.js → mdc-FDDXoir9.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-CP6mRgtV.js → nginx-q-8U_1jR.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-QQfj3fpF.js → nim-DIA0TUWs.js} +1 -1
- package/dist/assets/web/dist/assets/{org-Bke3eBzc.js → org-CR88hy7S.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-Bkh0N7Kz.js → perl-Bkd8N7ja.js} +1 -1
- package/dist/assets/web/dist/assets/{php-Dnv1Piya.js → php-BOs84mvy.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-D6peFFI7.js → pug-BbpZBb3w.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-D8PEs-C-.js → qml-D2jQpjFE.js} +1 -1
- package/dist/assets/web/dist/assets/{r-B1EL9b_j.js → r-DKzcAQ22.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BsMTIh2b.js → razor-SOQMWcC9.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-HhvC8spD.js → regexp-B6KK4W8d.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-D5paAxpg.js → rst-kPSg-NPf.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-BrQwhLrl.js → ruby-cKS-JYLH.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-afot2B1o.js → sas-DNxzE5gs.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-Efm-mwuG.js → scss-DGcHQ5OO.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-BK0Vv5fT.js → shellscript-CRZeZBa5.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-BGqlMC7N.js → shellsession-DSNEL0HX.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-M0b4UwGM.js → soy-zisNbkDO.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-sxe6IE9j.js → sql-MG06KMMb.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-g7ml86LM.js → sseReplay-DaRtc7nI.js} +5 -5
- package/dist/assets/web/dist/assets/{stata-nPF_ddLP.js → stata-CB_wKwv9.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-D_GrC6u7.js → surrealql-DYdSt7re.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-DwL1AtNP.js → svelte-DHO-F7c7.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-7s7LTDkc.js → templ-CUL5L4Lr.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-Bx-5fMxe.js → tex-_JNEKjVW.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-DUMJnke_.js → ts-tags--9EpEV_8.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-BN8biPDe.js → tsx-BcNq2x2u.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-8nIu84TN.js → twig-EsY5Dk_D.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BooSPq_S.js → typescript-CyA_HiUH.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-CTBiBsem.js → typst-SzaHlDt4.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-C6Ft4Lea.js → vue-D3cn5acV.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-Ba36dD5D.js → vue-html-6MxhUxOa.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-NkFexVo2.js → vue-vine-qJnqhgo9.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-C_THnHXZ.js → xml-DyRpC7xv.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-D8G5xqjY.js → xsl-BXMRPAsg.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-jAMIJzge.js → yaml-BNmPCtAN.js} +1 -1
- package/dist/cli.js +5843 -2246
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/CostsPage-CtmKhhOF.js +0 -2
- package/dist/assets/web/dist/assets/DeliveryPage-n1tz5I_v.js +0 -1
- package/dist/assets/web/dist/assets/HomePage-QtH1EwYF.js +0 -2
- package/dist/assets/web/dist/assets/PendingTurnRow-BZA_vQt3.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CaDvXhzJ.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-mgyLW0oV.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-DypJYMQa.js +0 -6
- package/dist/assets/web/dist/assets/RunsIndexPage-DYraPoWD.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DXD2gLJk.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-BIGio8Y0.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-jhCrWW3i.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-BD1VT8o8.js +0 -1
- package/dist/assets/web/dist/assets/localIso-L06jV29p.js +0 -1
- package/dist/assets/web/dist/assets/main-B2fX10aW.css +0 -1
- package/dist/assets/web/dist/assets/main-D4EA1g6n.js +0 -28
|
@@ -588,9 +588,20 @@ export class SwitchboardSandbox extends Sandbox<Env> {
|
|
|
588
588
|
await Promise.race([settled, new Promise<void>((r) => setTimeout(r, maxMs))]);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
/** `du -sk` over the paths, summed, in KiB; null when nothing could be
|
|
591
|
+
/** `du -sk` over the paths, summed, in KiB; null when nothing could be
|
|
592
|
+
* measured — including a `du` that did not answer inside its own limit
|
|
593
|
+
* (a mount mid-restore under load): the judge treats a sample the
|
|
594
|
+
* container could not take as no evidence either way, never as the
|
|
595
|
+
* restore's failure. The restore's own promise still reports a runtime
|
|
596
|
+
* that went away. */
|
|
592
597
|
private async duKiB(paths: string[]): Promise<number | null> {
|
|
593
|
-
|
|
598
|
+
let r: { stdout: string; exitCode: number };
|
|
599
|
+
try {
|
|
600
|
+
r = await this.runRoot(["du", "-sk", ...paths], 30_000);
|
|
601
|
+
} catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
if (r.exitCode === 124) return null;
|
|
594
605
|
let total = 0;
|
|
595
606
|
let seen = false;
|
|
596
607
|
for (const line of r.stdout.split("\n")) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.244.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.244.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -20445,7 +20445,7 @@
|
|
|
20445
20445
|
},
|
|
20446
20446
|
"packages/switchboard": {
|
|
20447
20447
|
"name": "@coreplane/switchboard",
|
|
20448
|
-
"version": "1.
|
|
20448
|
+
"version": "1.244.0",
|
|
20449
20449
|
"license": "Apache-2.0",
|
|
20450
20450
|
"dependencies": {
|
|
20451
20451
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.244.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/dist/assets/project.json
CHANGED
|
@@ -168,11 +168,11 @@
|
|
|
168
168
|
"when": "Part of `check:consistency`."
|
|
169
169
|
},
|
|
170
170
|
"clock:gen": {
|
|
171
|
-
"does": "Regenerates
|
|
171
|
+
"does": "Regenerates both clock allowlists from the tree: wall-clock reads (empty) and duration literals outside `src/core/budgets.ts`.",
|
|
172
172
|
"when": "Part of `fix`."
|
|
173
173
|
},
|
|
174
174
|
"clock:check": {
|
|
175
|
-
"does": "No production file reads the wall clock directly
|
|
175
|
+
"does": "No production file reads the wall clock directly or gained a duration literal outside `src/core/budgets.ts`; both allowlists match the tree.",
|
|
176
176
|
"when": "Part of `check:consistency`."
|
|
177
177
|
},
|
|
178
178
|
"docs:changed": {
|
|
@@ -212,7 +212,7 @@
|
|
|
212
212
|
"when": "Part of `check:consistency`."
|
|
213
213
|
},
|
|
214
214
|
"load": {
|
|
215
|
-
"does": "Load harness: `-- history|resident|sandbox|e2e|cards|provider|pi|route`.",
|
|
215
|
+
"does": "Load harness: `-- history|resident|sandbox|e2e|cards|provider|pi|route|door`.",
|
|
216
216
|
"when": "Capacity receipts (docs/reference/specs/load-harness.md)."
|
|
217
217
|
}
|
|
218
218
|
}
|
package/dist/assets/source.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Agent definitions. An agent is a system prompt + toolset + machine class + wall-clock budget.
|
|
2
2
|
import type { Effort } from "../effort.js";
|
|
3
3
|
import { BASH_TIMEOUT_MAX_MS } from "../execution/bashTimeout.js";
|
|
4
|
+
import { ASKS, RUNAWAY_TURNS_PER_MINUTE, runawayTurnCap, type LoopPreset } from "../core/budgets.js";
|
|
4
5
|
import { CONTRACT_HEADING, CONTRACT_SECTION_HEADINGS, PR_TITLE_GUARD } from "../core/ship/contract.js";
|
|
5
6
|
// Which model runs it is resolved separately by the config layers, so any
|
|
6
7
|
// agent can run on any configured provider/model.
|
|
@@ -41,24 +42,16 @@ export function machineNeedsRepo(machine: MachineClass): boolean {
|
|
|
41
42
|
export const IDENTITIES = ["none", "read", "write"] as const;
|
|
42
43
|
export type Identity = (typeof IDENTITIES)[number];
|
|
43
44
|
|
|
44
|
-
/** The
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
|
|
49
|
-
* says so (docs/reference/specs/harness-pi.md item 15). */
|
|
50
|
-
export const RUNAWAY_TURNS_PER_MINUTE = 6;
|
|
51
|
-
|
|
52
|
-
/** The turn cap a wall clock implies: `maxMinutes × RUNAWAY_TURNS_PER_MINUTE`.
|
|
53
|
-
* Every preset that runs the loop derives its `maxTurns` from this, so the
|
|
54
|
-
* cap is never a number a good run reaches — the minutes are the budget. */
|
|
55
|
-
export function runawayTurnCap(maxMinutes: number): number {
|
|
56
|
-
return maxMinutes * RUNAWAY_TURNS_PER_MINUTE;
|
|
57
|
-
}
|
|
45
|
+
/** The wall clocks live in `src/core/budgets.ts` (docs/decisions/0046): a
|
|
46
|
+
* preset's ask, the turn cap derived from it and every allowance are rows
|
|
47
|
+
* there, and this registry reads them. Re-exported for the readers that
|
|
48
|
+
* learned them here. */
|
|
49
|
+
export { RUNAWAY_TURNS_PER_MINUTE, runawayTurnCap };
|
|
58
50
|
|
|
59
|
-
/** A loop-running preset's budget as one fact
|
|
60
|
-
* guard derived from it. */
|
|
61
|
-
function loopBudget(
|
|
51
|
+
/** A loop-running preset's budget as one fact read from the module: the wall
|
|
52
|
+
* clock it asks for, and the runaway guard derived from it. */
|
|
53
|
+
function loopBudget(preset: LoopPreset): Pick<AgentDef, "maxMinutes" | "maxTurns"> {
|
|
54
|
+
const maxMinutes = ASKS[preset];
|
|
62
55
|
return { maxMinutes, maxTurns: runawayTurnCap(maxMinutes) };
|
|
63
56
|
}
|
|
64
57
|
|
|
@@ -150,15 +143,18 @@ export function statusCardRule(examples = '"Implement the fix", "Run the test su
|
|
|
150
143
|
);
|
|
151
144
|
}
|
|
152
145
|
|
|
153
|
-
const PR_DESCRIPTION_TEMPLATE = `PR description — submit it with the submit_pr_description tool for EVERY PR (this is the default, not something to wait to be asked for). Switchboard renders the GitHub body from the object you submit, so never author PR-body markdown yourself. Before submitting, judge your title with the ${PR_TITLE_GUARD} gate — \`npm run check:pr-title -- "<title>"\` — and submit only a title it accepts; the same gate refuses the PR in CI.
|
|
154
|
-
EVERY PR includes one that already exists when you push — opened by a person, by dependabot, or by an earlier run. After EVERY push to such a PR: read its current title and body (\`github_issue_get\` with the PR number works for pull requests; \`gh pr view\` where gh exists), judge them against the change as it now stands at the pushed head, and submit the object that describes the PR as it is NOW — carry forward what the existing body says that is still true (a dependency bump's release notes belong in
|
|
146
|
+
const PR_DESCRIPTION_TEMPLATE = `PR description — submit it with the submit_pr_description tool for EVERY PR (this is the default, not something to wait to be asked for). Switchboard renders the GitHub body from the object you submit, so never author PR-body markdown yourself. Before submitting, judge your title with the ${PR_TITLE_GUARD} gate — \`npm run check:pr-title -- "<title>"\` — and submit only a title it accepts; the same gate refuses the PR in CI. The body is a fixed-size MAP for the reader with everything for agents collapsed under it; every field is capped in visible characters (a link's URL is not counted) and the tool refuses an object over a cap naming the field and the count — cut and resubmit. Prose is unwrapped — no hard line breaks inside a paragraph. Always hyperlink the triggering issue/request. Never fabricate validation — state exactly what you ran and the real result. BEFORE authoring the pointers, load the \`pr-description\` skill with use_skill — it defines how to choose at most seven pointers, the mechanical anchor rules and what goes below the fold; follow it for every PR.
|
|
147
|
+
EVERY PR includes one that already exists when you push — opened by a person, by dependabot, or by an earlier run. After EVERY push to such a PR: read its current title and body (\`github_issue_get\` with the PR number works for pull requests; \`gh pr view\` where gh exists), judge them against the change as it now stands at the pushed head, and submit the object that describes the PR as it is NOW — carry forward what the existing body says that is still true (a dependency bump's release notes belong in why), add what you changed, and anchor the pointers at the new head. Switchboard replaces the PR's title and body with your rendering. A description that describes an earlier state of its branch is a bug; "it is someone else's PR" is never a reason to leave it.
|
|
155
148
|
- **title**: the PR title — one line naming the change, specific enough to pick out of a PR list.
|
|
156
|
-
- **TL;DR** (\`tldr\`, rendered first): two sentences for a
|
|
157
|
-
- **
|
|
158
|
-
- **
|
|
159
|
-
- **
|
|
160
|
-
- **
|
|
161
|
-
- **
|
|
149
|
+
- **TL;DR** (\`tldr\`, rendered first, ≤300): two sentences for a reader with zero context — what this PR does and why it matters.
|
|
150
|
+
- **Why** (\`why\`, ≤400): the problem and the motivation, with the triggering issue/request, the record and the stack position hyperlinked. Why, never what: the diff shows what.
|
|
151
|
+
- **Where to look** (\`pointers\`, 1 to 7): the files a reviewer would open first, in reading order, each { label ≤60, text ≤160, optional risk ≤100, anchor } with the anchor a { path, from, to } line range at your pushed head, rendered as a link (never embedded code). One pointer per idea, never per file; when the change has more ideas than seven, keep the seven whose mistake would cost most.
|
|
152
|
+
- **Feedback wanted** (\`feedbackWanted\`, ≤200): the one or two things you want the reviewer's judgement on.
|
|
153
|
+
- **Risk** (\`risk\`, ≤300): what breaks if this is wrong, the blast radius, the rollback; over 400 changed lines, say so and name the split you considered.
|
|
154
|
+
- **Verified** (\`verified\`, ≤200): one line for a person — which suites ran and passed, what is still human-gated.
|
|
155
|
+
- **Decisions** (\`decisions\`, 0 to 10, collapsed): non-obvious choices as { title, rationale ≤400 } — the alternative rejected and the fact that decided it.
|
|
156
|
+
- **Validation** (\`validation\`, 1 to 30 criteria, collapsed): what you tested and the actual results as { criterion ≤200, proof ≤300 } rows (test ids, commands run, pass/fail), plus how the reviewer can verify it.
|
|
157
|
+
- **For agents** (\`agentNotes\`, optional, ≤2000, collapsed): what a reviewing agent needs that a person does not — the rebase you did, generated files to skip, the command that reproduces the bug.`;
|
|
162
158
|
|
|
163
159
|
// Both coding prompts carry this verbatim: coding runs hold a write-scoped
|
|
164
160
|
// token where a merge is one command away, so the boundary is spelled out the
|
|
@@ -229,7 +225,7 @@ Keep notes with the \`notes\` tool: one short document, replaced whole each time
|
|
|
229
225
|
// project's most expensive checks first; the rule is the runner's to hold, not
|
|
230
226
|
// a line every requester remembers to paste. Stack-agnostic on purpose — the
|
|
231
227
|
// classes are by duration, the project's own scripts and CI say which is which.
|
|
232
|
-
export const CHECKS_BY_COST = `CHECKS BY COST — 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 — 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: a documentation change gets the documentation checks, one module gets its own tests, a shared type gets the typecheck. As soon as the change exists and those checks pass, commit and push — the pushed branch is the deliverable, and an unpushed tree does not survive the run's end. Only then run the expensive checks, once, and fix 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. The description's validation names exactly what ran; what did not run is CI's to gate, and you say so.`;
|
|
228
|
+
export const CHECKS_BY_COST = `CHECKS BY COST — 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 — 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: a documentation change gets the documentation checks, one module gets its own tests, a shared type gets the typecheck. As soon as the change exists and those checks pass, commit and push — the pushed branch is the deliverable, and an unpushed tree does not survive the run's end. Only then run the expensive checks, once, and fix 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. The description's validation names exactly what ran; what did not run is CI's to gate, and you say so.`;
|
|
233
229
|
|
|
234
230
|
const CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
|
|
235
231
|
|
|
@@ -521,7 +517,7 @@ Your tools work without a workspace: the GitHub tools — \`github_repos\` (the
|
|
|
521
517
|
|
|
522
518
|
${statusCardRule('"Read the issue and its thread", "Post the comment"')} A one-step answer needs no checklist; post one when the request has steps the person would wait on.
|
|
523
519
|
|
|
524
|
-
You cannot run commands, clone repositories, edit code, or review pull requests, and you cannot search the web. Other Switchboard agents can: for code
|
|
520
|
+
You cannot run commands, clone repositories, edit code, or review pull requests, and you cannot search the web. Other Switchboard agents can: for a code change or a pull request tell the user to re-send with \`agent:ship\` (it makes the change, opens the PR and loops review); for a PR review, \`agent:review\`; for a web-research question, \`agent:research\` (e.g. "\`agent:ship in acme/api: fix the failing login test\`", "\`agent:research compare X and Y\`"). Delete an issue only when the user explicitly asked to delete it (closing is an update).`;
|
|
525
521
|
|
|
526
522
|
// The explore agent (docs/reference/specs/agent-explore.md): a long, read-only
|
|
527
523
|
// investigation — "run our CI locally and validate the claims", "how long does
|
|
@@ -544,7 +540,7 @@ THE DELIVERABLE IS A CLAIM TABLE. Turn the request into the claims it makes or a
|
|
|
544
540
|
|
|
545
541
|
TIME. Your budget is up to two hours — less when a boundary or the request's \`budget:\` directive clipped it, which the runtime-config block above says — and the wrap-up warning tells you when to stop starting new checks. A single command is capped at ${BASH_TIMEOUT_MAX_MS / 60_000} minutes (pass the bash tool's \`timeoutMs\`, up to ${BASH_TIMEOUT_MAX_MS} ms, for a long one). A job that needs longer — a full suite, a build, a pipeline run — is started detached and polled across tool calls: \`setsid -f sh -c '<command> > /tmp/job.log 2>&1; echo $? > /tmp/job.exit'\`, then \`tail -n 40 /tmp/job.log\` and \`cat /tmp/job.exit\` on later calls (a plain background job dies with the command that started it; a \`setsid -f\` job outlives it). Batch commands into few tool calls; never explore file by file.
|
|
546
542
|
|
|
547
|
-
READ-ONLY: NEVER open a pull request, and never commit or push — no branch, no \`gh pr create\`, no PR or issue write of any kind. You hold a read credential and your job is to find out, not to change. If the investigation shows a change is needed, say exactly what and where in your write-up and point the user at \`agent:
|
|
543
|
+
READ-ONLY: NEVER open a pull request, and never commit or push — no branch, no \`gh pr create\`, no PR or issue write of any kind. You hold a read credential and your job is to find out, not to change. If the investigation shows a change is needed, say exactly what and where in your write-up and point the user at \`agent:ship\` (it makes the change, opens the PR and loops review).
|
|
548
544
|
|
|
549
545
|
You cannot attach or post files: your whole answer is text. Never say a file is attached or below — name its path in the workspace and describe it (what it shows, its size) instead; a person who needs the file itself asks \`agent:coding\`, which can attach.
|
|
550
546
|
|
|
@@ -638,7 +634,7 @@ const WORK_PRESETS = {
|
|
|
638
634
|
machine: "none",
|
|
639
635
|
identity: "none",
|
|
640
636
|
maxTokens: 16000,
|
|
641
|
-
...loopBudget(
|
|
637
|
+
...loopBudget("general"),
|
|
642
638
|
},
|
|
643
639
|
coding: {
|
|
644
640
|
name: "coding",
|
|
@@ -648,7 +644,7 @@ const WORK_PRESETS = {
|
|
|
648
644
|
seededSystem: CODING_SYSTEM_SEEDED,
|
|
649
645
|
toolset: "full",
|
|
650
646
|
maxTokens: 64000,
|
|
651
|
-
...loopBudget(
|
|
647
|
+
...loopBudget("coding"),
|
|
652
648
|
// No built-in effort: the deployment decides (`defaults.efforts.coding`,
|
|
653
649
|
// `config set channel efforts.coding=…`, or `effort:` per request).
|
|
654
650
|
machine: "repo-resident",
|
|
@@ -669,7 +665,7 @@ const WORK_PRESETS = {
|
|
|
669
665
|
machine: "repo-resident",
|
|
670
666
|
identity: "read", // a read-scoped token and a read-only worktree: it cannot post or push from inside
|
|
671
667
|
maxTokens: 64000,
|
|
672
|
-
...loopBudget(
|
|
668
|
+
...loopBudget("review"), // a safety net — typical reviews land in ~5 minutes
|
|
673
669
|
effort: "medium", // fast turns; one big-context pass does the deep work
|
|
674
670
|
},
|
|
675
671
|
ship: {
|
|
@@ -698,7 +694,7 @@ const WORK_PRESETS = {
|
|
|
698
694
|
// reviewed pull request, never code landing on main.
|
|
699
695
|
maxTurns: 1,
|
|
700
696
|
maxTokens: 16000,
|
|
701
|
-
maxMinutes:
|
|
697
|
+
maxMinutes: ASKS.ship,
|
|
702
698
|
},
|
|
703
699
|
research: {
|
|
704
700
|
name: "research",
|
|
@@ -709,7 +705,7 @@ const WORK_PRESETS = {
|
|
|
709
705
|
machine: "none", // web I/O only; no workspace is provisioned
|
|
710
706
|
identity: "none",
|
|
711
707
|
maxTokens: 24000,
|
|
712
|
-
...loopBudget(
|
|
708
|
+
...loopBudget("research"),
|
|
713
709
|
effort: "medium",
|
|
714
710
|
},
|
|
715
711
|
explore: {
|
|
@@ -723,7 +719,7 @@ const WORK_PRESETS = {
|
|
|
723
719
|
machine: "repo-cold",
|
|
724
720
|
identity: "read", // a read-scoped token: it can clone and read, never push — whatever the caller holds
|
|
725
721
|
maxTokens: 64000,
|
|
726
|
-
...loopBudget(
|
|
722
|
+
...loopBudget("explore"),
|
|
727
723
|
// No built-in effort: the deployment decides, as for coding.
|
|
728
724
|
},
|
|
729
725
|
} satisfies Record<string, AgentDef>;
|
|
@@ -748,7 +744,7 @@ export const AGENTS: Record<string, AgentDef> = {
|
|
|
748
744
|
// spawn exactly those — so no child runs that the record did not name.
|
|
749
745
|
routable: false,
|
|
750
746
|
maxTokens: 32000,
|
|
751
|
-
...loopBudget(
|
|
747
|
+
...loopBudget("conductor"), // long enough to outlast a coding child; every child is capped by what remains of it
|
|
752
748
|
// No built-in effort: the deployment decides, as for coding.
|
|
753
749
|
},
|
|
754
750
|
};
|
|
@@ -73,10 +73,40 @@ export function budgetedAgent(agent: AgentDef, profile: RunProfile): AgentDef {
|
|
|
73
73
|
|
|
74
74
|
// ---- boundaries: a scope caps, never grants -----------------------------------
|
|
75
75
|
|
|
76
|
+
/** The blast-radius classes a scope may name as its `confirm`
|
|
77
|
+
* (docs/decisions/0044-a-routed-write-is-confirmed-in-proportion-to-its-blast-radius.md):
|
|
78
|
+
* the first class on the ladder the door hands back instead of running.
|
|
79
|
+
* `exec` is on the ladder for the comparison but not settable — a test or
|
|
80
|
+
* build never asks — and `never` is refused until the door's write misbind
|
|
81
|
+
* rate has been measured; the validator names both reasons. */
|
|
82
|
+
export type ConfirmClass = "write" | "destructive";
|
|
83
|
+
export const CONFIRM_CLASSES: readonly ConfirmClass[] = ["write", "destructive"];
|
|
84
|
+
|
|
85
|
+
/** The classes on the door's ladder — the command registry's `BlastRadius`,
|
|
86
|
+
* spelled here rather than imported: a type import still drags the registry's
|
|
87
|
+
* whole graph into every program that compiles this near-leaf module, the
|
|
88
|
+
* Workers included. The door indexes the ladder with the registry's type
|
|
89
|
+
* (`routedRunsAtOnce`), so a class added there without a rung here fails to
|
|
90
|
+
* compile at the one place the two vocabularies meet. */
|
|
91
|
+
export type ConfirmLadderClass = "read" | "exec" | "write" | "destructive";
|
|
92
|
+
|
|
93
|
+
/** The ladder the door compares on, `read < exec < write < destructive`: among
|
|
94
|
+
* the last three, from asking most to asking least — a `confirm` of `write`
|
|
95
|
+
* hands back every write and every destructive write, `destructive` only the
|
|
96
|
+
* destructive ones. A read is on the order so the comparison is total, but the
|
|
97
|
+
* door never asks for one. */
|
|
98
|
+
export const CONFIRM_ORDER: Record<ConfirmLadderClass, number> = { read: 0, exec: 1, write: 2, destructive: 3 };
|
|
99
|
+
|
|
100
|
+
/** The door's confirm class when no scope sets one: every routed write is
|
|
101
|
+
* handed back, a read or a test run at once — the door as it was before the
|
|
102
|
+
* axis existed. Attributed to `built-in`, a word that appears in no config. */
|
|
103
|
+
export const BUILT_IN_CONFIRM: ConfirmClass = "write";
|
|
104
|
+
|
|
76
105
|
/** A cap on the three axes that any scope may set (`defaults`, `channels.<id>`,
|
|
77
|
-
* `users.<id>`; docs/reference/specs/routing-and-config.md item 2)
|
|
78
|
-
* axis caps nothing. A boundary never
|
|
79
|
-
* and the policy table's one question
|
|
106
|
+
* `users.<id>`; docs/reference/specs/routing-and-config.md item 2), and the
|
|
107
|
+
* door's `confirm` beside them. An absent axis caps nothing. A boundary never
|
|
108
|
+
* grants: it is not a fourth grants axis, and the policy table's one question
|
|
109
|
+
* (who may run a preset) is unchanged. */
|
|
80
110
|
export interface Boundary {
|
|
81
111
|
/** The most a run may have, in minutes; at least 2 (the bash tool keeps a 60 s reserve). */
|
|
82
112
|
maxMinutes?: number;
|
|
@@ -84,6 +114,11 @@ export interface Boundary {
|
|
|
84
114
|
maxIdentity?: Identity;
|
|
85
115
|
/** The machine classes a run may execute on; a preset's class must be in every layer's set. */
|
|
86
116
|
machines?: MachineClass[];
|
|
117
|
+
/** The first blast-radius class a command the router bound is handed back
|
|
118
|
+
* at instead of run (record 0044). Not a run cap: it rides the boundary for
|
|
119
|
+
* its scopes and its intersection-toward-caution, is read by the door alone
|
|
120
|
+
* (`effectiveConfirm`), and never enters `intersectBoundaries` or a profile. */
|
|
121
|
+
confirm?: ConfirmClass;
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
/** One layer's boundary with the scope it came from, in resolution order:
|
|
@@ -143,6 +178,36 @@ export function intersectBoundaries(layers: readonly ScopedBoundary[]): Effectiv
|
|
|
143
178
|
return out.maxMinutes || out.maxIdentity || out.machines ? out : undefined;
|
|
144
179
|
}
|
|
145
180
|
|
|
181
|
+
/** Where the door's confirm class came from: a scope that set it, or the
|
|
182
|
+
* built-in default when none did. Local to the confirm axis — `BoundaryScope`
|
|
183
|
+
* itself is unchanged, since no run cap is ever attributed to `built-in`. */
|
|
184
|
+
export type ConfirmScope = BoundaryScope | "built-in";
|
|
185
|
+
|
|
186
|
+
/** The door's decision on the confirm axis: the class and the scope it names. */
|
|
187
|
+
export interface EffectiveConfirm {
|
|
188
|
+
value: ConfirmClass;
|
|
189
|
+
scope: ConfirmScope;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The confirm axis intersected over a request's path, apart from the run caps:
|
|
194
|
+
* the earliest class on `CONFIRM_ORDER` any layer named — the most cautious,
|
|
195
|
+
* since a scope's value is the most permissive answer it allows and the org's
|
|
196
|
+
* is therefore a floor no layer below it can loosen — attributed to the layer
|
|
197
|
+
* that set it (on a tie the first layer named keeps it, the least specific
|
|
198
|
+
* scope, as `intersectBoundaries` does). No layer set one: the built-in
|
|
199
|
+
* `write`, attributed to `built-in`. Pure over the same layers `intersectBoundaries` takes.
|
|
200
|
+
*/
|
|
201
|
+
export function effectiveConfirm(layers: readonly ScopedBoundary[]): EffectiveConfirm {
|
|
202
|
+
let out: EffectiveConfirm | undefined;
|
|
203
|
+
for (const { scope, boundary } of layers) {
|
|
204
|
+
if (boundary.confirm !== undefined && (!out || CONFIRM_ORDER[boundary.confirm] < CONFIRM_ORDER[out.value])) {
|
|
205
|
+
out = { value: boundary.confirm, scope };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return out ?? { value: BUILT_IN_CONFIRM, scope: "built-in" };
|
|
209
|
+
}
|
|
210
|
+
|
|
146
211
|
/**
|
|
147
212
|
* The boundaries on a child's path with its parent's remaining wall clock as
|
|
148
213
|
* one more layer (docs/reference/specs/routing-and-config.md item 20): the
|
|
@@ -82,6 +82,10 @@ export const POLICY: readonly Rule[] = [
|
|
|
82
82
|
{ action: "friction:write", resource: "command", when: [grant("friction:write")] },
|
|
83
83
|
|
|
84
84
|
// ── costs ────────────────────────────────────────────────────────────────
|
|
85
|
+
// `costs by` reads the snapshot's arithmetic: what every browser session
|
|
86
|
+
// holds (every group's read) and what a Slack user or a token is granted —
|
|
87
|
+
// never a chat baseline, since a by-user table names who spent what.
|
|
88
|
+
{ action: "costs:read", resource: "command", when: [grant("costs:read")] },
|
|
85
89
|
// `costs snapshot` reads both billing providers and replaces what every
|
|
86
90
|
// viewer of the costs page sees: the grant, never a baseline (the admins'
|
|
87
91
|
// `all` and a named `grants` entry hold it).
|
|
@@ -196,4 +196,14 @@ export interface ChannelDirectory {
|
|
|
196
196
|
/** Every channel the actor is in, platform-namespaced — what the resolver puts on
|
|
197
197
|
* `Actor.memberOf`; `unknown` when the adapter cannot say (fail-closed). */
|
|
198
198
|
channelsOf(actorId: string): Promise<ReadonlySet<string> | "unknown">;
|
|
199
|
+
/** Every channel the bot itself is in, with its visibility — the channels a person can
|
|
200
|
+
* pick a scope or an MCP tier for (settings-page.md item 7); `unknown` when the adapter
|
|
201
|
+
* cannot list them, and the surfaces fall back to the channels that already carry a scope. */
|
|
202
|
+
channels(): Promise<readonly ListedChannel[] | "unknown">;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** One channel the bot is in, as the directory lists it. */
|
|
206
|
+
export interface ListedChannel {
|
|
207
|
+
id: string;
|
|
208
|
+
visibility: ChannelVisibility;
|
|
199
209
|
}
|