@coreplane/switchboard 1.258.0 → 1.260.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/assets/config/config.example.yaml +17 -25
  2. package/dist/assets/deploy/cloudflare-memory/worker.ts +178 -27
  3. package/dist/assets/deploy/cloudflare-resident/worker.ts +74 -52
  4. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +6 -3
  5. package/dist/assets/package-lock.json +3 -3
  6. package/dist/assets/package.json +1 -1
  7. package/dist/assets/source.json +3 -3
  8. package/dist/assets/src/agents/registry.ts +3 -5
  9. package/dist/assets/src/core/authz/policy.ts +5 -0
  10. package/dist/assets/src/core/budgets.ts +7 -0
  11. package/dist/assets/src/core/coordinator/contract.ts +93 -5
  12. package/dist/assets/src/core/coordinator/driver.ts +386 -124
  13. package/dist/assets/src/core/memory/types.ts +5 -0
  14. package/dist/assets/src/core/pipelineStanding.ts +11 -4
  15. package/dist/assets/src/core/plane/decide.ts +53 -12
  16. package/dist/assets/src/core/provider.ts +8 -3
  17. package/dist/assets/src/core/runEvents.ts +14 -15
  18. package/dist/assets/src/core/runRecord.ts +36 -3
  19. package/dist/assets/src/core/ship/coordinator.ts +58 -18
  20. package/dist/assets/src/core/ship/renewal.ts +21 -8
  21. package/dist/assets/src/core/types.ts +12 -1
  22. package/dist/assets/web/dist/.vite/manifest.json +67 -67
  23. package/dist/assets/web/dist/assets/{DeliveryPage-DadntjSp.js → DeliveryPage-DvMrWUg7.js} +1 -1
  24. package/dist/assets/web/dist/assets/HomePage-CcGEJ4w0.js +1 -0
  25. package/dist/assets/web/dist/assets/{PendingTurnRow-DBPMLVsm.js → PendingTurnRow-BzGVxDYs.js} +1 -1
  26. package/dist/assets/web/dist/assets/{PlanePage-DLusMGQ0.js → PlanePage-Arj9cyd5.js} +1 -1
  27. package/dist/assets/web/dist/assets/{ResidentDetailPage-CPnHnrxy.js → ResidentDetailPage-CBPPe4Sj.js} +1 -1
  28. package/dist/assets/web/dist/assets/{ResidentsIndexPage-C9hASwHL.js → ResidentsIndexPage-CEpXgGo7.js} +1 -1
  29. package/dist/assets/web/dist/assets/RunFoldRow-BJq7tnLr.js +1 -0
  30. package/dist/assets/web/dist/assets/{RunRoutePage-o1xzkTGZ.js → RunRoutePage-DWiny7LN.js} +4 -4
  31. package/dist/assets/web/dist/assets/RunsIndexPage-Cxufw5sV.js +1 -0
  32. package/dist/assets/web/dist/assets/{ScheduledPage-BIdfC0V3.js → ScheduledPage-SBcuDdVg.js} +1 -1
  33. package/dist/assets/web/dist/assets/{SettingsPage-CnqWAjl3.js → SettingsPage-DrD_vWdp.js} +1 -1
  34. package/dist/assets/web/dist/assets/{SilentTurn-DGNg0sIZ.js → SilentTurn-Bjl7EPEN.js} +1 -1
  35. package/dist/assets/web/dist/assets/{StatusDot-D_D_iJS8.js → StatusDot-BqteQTav.js} +1 -1
  36. package/dist/assets/web/dist/assets/{Tooltip-DVFmM7on.js → Tooltip-KRqJXEPN.js} +1 -1
  37. package/dist/assets/web/dist/assets/{UnitRoutePage-7fAXFoB_.js → UnitRoutePage-Cv28FV4I.js} +1 -1
  38. package/dist/assets/web/dist/assets/{budgets-BRLm6oID.js → budgets-DQXVllsm.js} +1 -1
  39. package/dist/assets/web/dist/assets/{dist-C1VX-_mm.js → dist-CTASUSno.js} +1 -1
  40. package/dist/assets/web/dist/assets/{indexRow-BO_GIdF6.js → indexRow-B1YBy_IL.js} +1 -1
  41. package/dist/assets/web/dist/assets/{main-Cl7xHG34.js → main-eTAe9-hk.js} +2 -2
  42. package/dist/assets/web/dist/assets/{sseReplay-BjlKmQsY.js → sseReplay-BcsNbn2j.js} +1 -1
  43. package/dist/cli.js +8324 -7937
  44. package/package.json +1 -1
  45. package/dist/assets/web/dist/assets/HomePage-BEEpTnVT.js +0 -1
  46. package/dist/assets/web/dist/assets/RunFoldRow-OPe7Z0Cz.js +0 -1
  47. package/dist/assets/web/dist/assets/RunsIndexPage-0YAClYQh.js +0 -1
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.258.0",
3
+ "version": "1.260.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "switchboard",
9
- "version": "1.258.0",
9
+ "version": "1.260.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.258.0",
20448
+ "version": "1.260.0",
20449
20449
  "license": "Apache-2.0",
20450
20450
  "dependencies": {
20451
20451
  "@earendil-works/pi-ai": "0.85.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.258.0",
3
+ "version": "1.260.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",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.258.0",
3
- "commit": "05d8b84a387c7b7c15ed8dd2dc30f08a5982a981",
4
- "builtAt": "2026-09-20T17:05:48.184Z"
2
+ "version": "1.260.0",
3
+ "commit": "33c6cd103ce9d8a49be5249f98306fe46160f81c",
4
+ "builtAt": "2026-09-21T15:19:23.063Z"
5
5
  }
@@ -47,11 +47,9 @@ export function machineNeedsRepo(machine: MachineClass): boolean {
47
47
  export const IDENTITIES = ["none", "read", "write"] as const;
48
48
  export type Identity = (typeof IDENTITIES)[number];
49
49
 
50
- /** The model tiers a preset may run on (the one-door plan's tiers rule): the
51
- * `fast` tier is the router's own model (`routing.model`), everything else is
52
- * `strong`. Each preset declares its allowed set below (`AgentDef.tiers`);
53
- * a parent choosing a child's model at spawn is held to the child preset's
54
- * set, and escalation is a new run — a run's tier is fixed at dispatch. */
50
+ /** The model tiers a preset may run on. The retired classifier leaves no
51
+ * configured fast-tier ref, so current run models resolve strong; declarations
52
+ * remain explicit for model-card tiering and future configured catalogues. */
55
53
  export const MODEL_TIERS = ["fast", "strong"] as const;
56
54
  export type ModelTier = (typeof MODEL_TIERS)[number];
57
55
 
@@ -118,6 +118,11 @@ export const POLICY: readonly Rule[] = [
118
118
  // `pulls rebase` force-pushes the pipeline's branches and can start a paid
119
119
  // fix round (record 0071): a grant, never a chat baseline.
120
120
  { action: "pulls:write", resource: "command", when: [grant("pulls:write")] },
121
+ // `pulls merge|enqueue` (record 0070, criterion 4): a person's merge through
122
+ // the door — the grant, never a baseline, so a grant-less person has a
123
+ // thread that reads everything and merges nothing. The release pull
124
+ // request's handoff and the approval fences are the handler's own refusals.
125
+ { action: "merge:write", resource: "command", when: [grant("merge:write")] },
121
126
 
122
127
  // ── repos ────────────────────────────────────────────────────────────────
123
128
  { action: "repo:read", resource: "command", when: [grant("repo:read")] },
@@ -236,6 +236,13 @@ export const HOSTED_DEADLINE_MARGIN_MINUTES = 60;
236
236
  * and the pause before a busy spawn is asked again. */
237
237
  export const SHIP_WAIT = { marginMinutes: 5, chunkMinutes: 5, mergeChunkMinutes: 5, busyRetryMinutes: 2 } as const;
238
238
 
239
+ /** How long a restarting close gives its replacement dispatch to claim the
240
+ * same run before a read with no successor treats the close as final
241
+ * (run-history item 47a). One whole coordinator wait chunk lets an ordinary
242
+ * replacement claim without shortening the parent's first wait; a successor
243
+ * found after this deadline still wins because read-record searches first. */
244
+ export const RESTART_CLAIM_GRACE_MS = minutesToMs(SHIP_WAIT.chunkMinutes);
245
+
239
246
  /** The plane's table (docs/decisions/0064): how long a finished run stays on
240
247
  * it, a reservation's window, and the default re-ask cadence — how often a
241
248
  * silent resident something waits on is probed (`plane.reaskMinutes`
@@ -111,12 +111,24 @@ export interface ThreadEventAttachment {
111
111
  mediaType: string;
112
112
  data: string;
113
113
  name?: string;
114
+ /** The accepted channel file's by-reference source, when one exists, so the
115
+ * coding child can stage it without moving the inline bytes through a
116
+ * second request. */
117
+ staged?: {
118
+ name: string;
119
+ size: number;
120
+ type: string;
121
+ url: string;
122
+ messageId: string;
123
+ workspaceIndex?: number;
124
+ };
114
125
  }
115
126
 
116
127
  export interface ThreadEvent {
117
128
  /** Assigned by the store's append, in arrival order, per unit. */
118
129
  seq: number;
119
- /** The channel's message id, when the platform gave one. */
130
+ /** The channel's message id, or another producer's stable event id. An
131
+ * append that repeats it on one unit returns the original row. */
120
132
  id?: string;
121
133
  /** The sender (platform-namespaced) and the display name the channel knew. */
122
134
  sender: string;
@@ -174,11 +186,24 @@ export function capThreadEvent<T extends Omit<ThreadEvent, "seq"> & { seq?: numb
174
186
  }
175
187
  }
176
188
 
189
+ const isThreadEventStagedFile = (v: unknown): v is NonNullable<ThreadEventAttachment["staged"]> =>
190
+ isObject(v) &&
191
+ typeof v.name === "string" &&
192
+ typeof v.size === "number" &&
193
+ Number.isInteger(v.size) &&
194
+ v.size > 0 &&
195
+ typeof v.type === "string" &&
196
+ typeof v.url === "string" &&
197
+ typeof v.messageId === "string" &&
198
+ (v.workspaceIndex === undefined ||
199
+ (typeof v.workspaceIndex === "number" && Number.isInteger(v.workspaceIndex) && v.workspaceIndex >= 0));
200
+
177
201
  const isThreadEventAttachment = (v: unknown): v is ThreadEventAttachment =>
178
202
  isObject(v) &&
179
203
  typeof v.mediaType === "string" &&
180
204
  typeof v.data === "string" &&
181
- (v.name === undefined || typeof v.name === "string");
205
+ (v.name === undefined || typeof v.name === "string") &&
206
+ (v.staged === undefined || isThreadEventStagedFile(v.staged));
182
207
 
183
208
  const isThreadEventMode = (v: unknown): v is ThreadEventMode => v === "steer" || v === "wake" || v === "interrupt";
184
209
 
@@ -399,6 +424,26 @@ export interface UnitIdle {
399
424
  wakes: number;
400
425
  }
401
426
 
427
+ /** The durable answer to one indexed idle wait. The wait id is the key on the
428
+ * unit row, so a reclaimed runner receives byte-for-byte the decision the
429
+ * first caller stored instead of spending a renewal or consuming an event
430
+ * twice. */
431
+ export type UnitWakeAnswer =
432
+ | {
433
+ kind: "segment";
434
+ index: number;
435
+ from?: string;
436
+ runId?: string;
437
+ spendUsd: number | null;
438
+ handoff?: Handoff;
439
+ texts: string[];
440
+ senders: string[];
441
+ leaseMs?: number;
442
+ }
443
+ | { kind: "answered"; reply: string }
444
+ | { kind: "stopped" }
445
+ | { kind: "expired" };
446
+
402
447
  /** What the severity gate caught on a round: the level in force and the gated findings as `id (severity)`. */
403
448
  export interface RoundGate {
404
449
  level: AddressSeverity;
@@ -456,14 +501,20 @@ export interface CoordinatorUnit {
456
501
  * (null once any run's cost is unknown), `handoff` that child's lists, and
457
502
  * `wakes` how many wakes this idle has answered — zero at the write. */
458
503
  idle?: UnitIdle;
504
+ /** Answers to indexed idle waits, keyed by the wait step's durable identity. */
505
+ wakes?: Record<string, UnitWakeAnswer>;
459
506
  /** The round boundaries the coordinator reported, oldest first (the `ship_round`
460
507
  * vocabulary). `gate` rides an approve the machine's severity check caught
461
508
  * carrying a finding at or above the level in force ([agent-ship](../../../docs/reference/specs/agent-ship.md)
462
509
  * item 9) — a mismatch to be seen, since the child's parser holds an approve
463
510
  * to the same level. */
464
511
  rounds: Array<{ index: number; agent: string; outcome: string; at: number; gate?: RoundGate }>;
465
- /** How the unit ended: the ending's kind and the thread's report, when it has. */
466
- ending?: { kind: string; report: string; at: number };
512
+ /** How the unit ended: the ending's kind and the thread's report, when it
513
+ * has. `cause` names the machine's reason behind a driver-posted kind;
514
+ * `step` and `round` locate that reason without parsing the report. For a
515
+ * `step_threw` failure the driver records all available fields before it
516
+ * rethrows (issue 2100); unit-start has no round yet. */
517
+ ending?: { kind: string; report: string; at: number; cause?: string; step?: string; round?: number };
467
518
  startedAt?: number;
468
519
  }
469
520
 
@@ -548,6 +599,27 @@ const isSegment = (v: unknown): boolean =>
548
599
  (v.runId === undefined || isText(v.runId)) &&
549
600
  typeof v.at === "number";
550
601
 
602
+ export const isUnitWakeAnswer = (v: unknown): v is UnitWakeAnswer => {
603
+ if (!isObject(v)) return false;
604
+ if (v.kind === "answered") return typeof v.reply === "string";
605
+ if (v.kind === "stopped" || v.kind === "expired") return true;
606
+ return (
607
+ v.kind === "segment" &&
608
+ typeof v.index === "number" &&
609
+ Number.isInteger(v.index) &&
610
+ v.index >= 1 &&
611
+ (v.from === undefined || isText(v.from)) &&
612
+ (v.runId === undefined || isText(v.runId)) &&
613
+ (v.spendUsd === null || isFinite(v.spendUsd)) &&
614
+ (v.handoff === undefined || isHandoffShape(v.handoff)) &&
615
+ Array.isArray(v.texts) &&
616
+ v.texts.every((text) => typeof text === "string") &&
617
+ Array.isArray(v.senders) &&
618
+ v.senders.every((sender) => isText(sender)) &&
619
+ (v.leaseMs === undefined || (isFinite(v.leaseMs) && v.leaseMs > 0))
620
+ );
621
+ };
622
+
551
623
  export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
552
624
  if (!isObject(v)) return false;
553
625
  const r = v;
@@ -562,6 +634,13 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
562
634
  if (r.lastPush !== undefined && !isText(r.lastPush)) return false;
563
635
  if (r.segments !== undefined && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
564
636
  if (r.idle !== undefined && !isUnitIdle(r.idle)) return false;
637
+ if (
638
+ r.wakes !== undefined &&
639
+ (!isObject(r.wakes) ||
640
+ Array.isArray(r.wakes) ||
641
+ !Object.entries(r.wakes).every(([waitId, answer]) => STEP_NAME_PATTERN.test(waitId) && isUnitWakeAnswer(answer)))
642
+ )
643
+ return false;
565
644
  if (
566
645
  !Array.isArray(r.rounds) ||
567
646
  r.rounds.length > MAX_ROUNDS ||
@@ -578,7 +657,16 @@ export function isCoordinatorUnit(v: unknown): v is CoordinatorUnit {
578
657
  return false;
579
658
  if (
580
659
  r.ending !== undefined &&
581
- !(isObject(r.ending) && isText(r.ending.kind) && isText(r.ending.report, MAX_REPORT) && isFinite(r.ending.at))
660
+ !(
661
+ isObject(r.ending) &&
662
+ isText(r.ending.kind) &&
663
+ isText(r.ending.report, MAX_REPORT) &&
664
+ isFinite(r.ending.at) &&
665
+ (r.ending.cause === undefined || isText(r.ending.cause, 64)) &&
666
+ (r.ending.step === undefined || (typeof r.ending.step === "string" && STEP_NAME_PATTERN.test(r.ending.step))) &&
667
+ (r.ending.round === undefined ||
668
+ (typeof r.ending.round === "number" && Number.isInteger(r.ending.round) && r.ending.round >= 0))
669
+ )
582
670
  )
583
671
  return false;
584
672
  if (r.startedAt !== undefined && !isFinite(r.startedAt)) return false;