@coreplane/switchboard 1.219.0 → 1.220.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.
@@ -71,6 +71,22 @@ RUN apt-get update \
71
71
  COPY --from=meat /go/bin/meat /usr/local/bin/meat
72
72
  RUN command -v meat >/dev/null && meat -h >/dev/null 2>&1
73
73
 
74
+ # pi (docs/reference/specs/harness-pi.md item 12): the harness for a preset
75
+ # without a workspace (general, research, conductor) runs pi as a child of
76
+ # the bot itself, since such a run has no execution container. The same layer
77
+ # as the execution images', on this image's Node (the same 24 tag), installed
78
+ # globally as root before the switch to the bot's user, so `switchboard` finds
79
+ # it on PATH and cannot alter it; at an EXACT pin held equal across the three
80
+ # images by src/deploy/imagePiHarness.test.ts (the pnpm lesson: a floating tag
81
+ # would move the harness's protocol with the build date). Proven by the layer,
82
+ # so the BUILD fails, not a run. Dark until a deployment sets
83
+ # `harness: { general: pi }`: nothing here starts pi on its own, and a run's
84
+ # pi on this host holds none of pi's own shell or file tools.
85
+ RUN npm install -g @earendil-works/pi-coding-agent@0.85.1 \
86
+ && npm cache clean --force \
87
+ && pi --version | grep -qx '0.85.1' \
88
+ && pi --help | grep -q -- '--mode <mode>'
89
+
74
90
  # Non-root user; agents run bash with this user's (container-scoped) permissions.
75
91
  RUN useradd -m -u 1001 switchboard
76
92
  WORKDIR /app
@@ -320,8 +320,10 @@ workspaceDir: ./workspaces
320
320
  # with the run's model-proxy bearer as its only key, every event bridged onto
321
321
  # the run's own record, our tools and tool rules as its extension. Every
322
322
  # preset declares `native` until the pi series moves it; a deployment moves a
323
- # preset early here. Only a preset with a workspace (coding, review, explore)
324
- # can run on pi. A preset the registry does not know, or a value that is
323
+ # preset early here. A preset with a workspace (coding, review, explore) runs
324
+ # pi in the run's container; a preset without one (general, research,
325
+ # conductor) runs it as a child of the bot, with none of pi's own shell or
326
+ # file tools. A preset the registry does not know, or a value that is
325
327
  # neither harness, fails the load by name.
326
328
  # harness:
327
329
  # coding: pi
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.219.0",
3
+ "version": "1.220.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "switchboard",
9
- "version": "1.219.0",
9
+ "version": "1.220.0",
10
10
  "license": "Apache-2.0",
11
11
  "workspaces": [
12
12
  "web",
@@ -16710,7 +16710,7 @@
16710
16710
  },
16711
16711
  "packages/switchboard": {
16712
16712
  "name": "@coreplane/switchboard",
16713
- "version": "1.219.0",
16713
+ "version": "1.220.0",
16714
16714
  "license": "Apache-2.0",
16715
16715
  "dependencies": {
16716
16716
  "@anthropic-ai/sdk": "^0.124.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.219.0",
3
+ "version": "1.220.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.219.0",
3
- "commit": "584d81012dc5119229ec5e4ea04a304e3eceb450",
4
- "builtAt": "2026-09-15T01:38:10.165Z"
2
+ "version": "1.220.0",
3
+ "commit": "cf86e5df3d3256037bf4925a9e13de0b61483df3",
4
+ "builtAt": "2026-09-15T02:39:37.781Z"
5
5
  }
@@ -121,8 +121,9 @@ export interface AgentDef {
121
121
  residentSystem?: string;
122
122
  /** Which loop drives the preset's runs (`HARNESSES`): the native loop
123
123
  * unless declared, and whatever a deployment's `harness.<preset>` says
124
- * over that. Only a preset with a workspace can run on pi — pi is a process
125
- * in the run's execution container. */
124
+ * over that. A preset with a workspace runs pi in the run's execution
125
+ * container; a preset without one (machine class `none`) runs it as a
126
+ * child of the bot, with none of pi's own tools. */
126
127
  harness?: Harness;
127
128
  }
128
129
 
@@ -602,7 +603,7 @@ const WORK_PRESETS = {
602
603
  explore: {
603
604
  name: "explore",
604
605
  description:
605
- "Long, read-only investigation of a repository in a cold sandbox: runs builds, suites and pipelines, searches the web, and reports a claim table with commands and numbers. Never opens a PR. Up to two hours.",
606
+ "Read-only investigation of a repository in a cold sandbox: runs builds, suites and pipelines, searches the web, and reports a claim table with commands and numbers. Never opens a PR; cannot attach or post files.",
606
607
  system: EXPLORE_SYSTEM,
607
608
  toolset: "explore",
608
609
  // Always a cold per-thread sandbox with the checkout, never the resident a
@@ -71,6 +71,11 @@ export type RunNoteKind =
71
71
  * executor's bounded wait (docs/reference/specs/execution.md item 14). Capacity, not a
72
72
  * dead sandbox: the run goes on and the model is told to retry or finish. */
73
73
  | "fleet_busy"
74
+ /** The sandbox restarted under the run and came back (docs/reference/specs/
75
+ * resident-repos.md item 65): the executor waited for the resident's wake
76
+ * and re-attached; the interrupted call was settled with a synthetic
77
+ * result (run-loop.md item 19) and the run goes on. */
78
+ | "sandbox_restarted"
74
79
  | "stop_requested"
75
80
  | "stopped"
76
81
  /** Setup spans the request's stream sink had to drop before this run was
@@ -154,6 +159,7 @@ export const RUN_NOTE_KINDS = [
154
159
  "turn_budget_exhausted",
155
160
  "sandbox_dead",
156
161
  "fleet_busy",
162
+ "sandbox_restarted",
157
163
  "stop_requested",
158
164
  "stopped",
159
165
  "spans_dropped",
@@ -562,6 +562,17 @@ export function analyzeRunFriction(events: readonly RunEvent[], opts: FrictionOp
562
562
  eventIndex: index,
563
563
  });
564
564
  return;
565
+ case "sandbox_restarted":
566
+ // The container rolled under the run and came back: the run went on,
567
+ // but the minutes spent waiting for the wake are friction the deploy
568
+ // window owns.
569
+ findings.push({
570
+ category: "infra_failure",
571
+ severity: "medium",
572
+ summary: `sandbox restarted: ${ev.summary}`,
573
+ eventIndex: index,
574
+ });
575
+ return;
565
576
  }
566
577
  });
567
578
  // A slow turn whose produced event is past the stream (a turn that ended the run).
@@ -158,6 +158,39 @@ export interface RunRecord {
158
158
  * on records written before the session log existed and on runs without
159
159
  * a conversation of their own. */
160
160
  session?: RunSession;
161
+ /** The pull request the run's post-step opened or edited (item 2): the last
162
+ * `pr_opened` event, folded at the assembly. Present only on a run that
163
+ * reached one. A follow-up in the thread continues on its head branch
164
+ * (docs/reference/specs/resident-repos.md item 29) — read off the record,
165
+ * never off the reply's text. */
166
+ pr?: RunPullRequest;
167
+ }
168
+
169
+ /** A pull request as the record names it (item 2): its number and its GitHub
170
+ * URL, both from the `pr_opened` event the coding post-step published. */
171
+ export interface RunPullRequest {
172
+ number: number;
173
+ url: string;
174
+ }
175
+
176
+ /** The pull request a run's events say it opened or edited — the last
177
+ * `pr_opened` wins, as an edit after an open names the same PR — or nothing. */
178
+ export function prOfEvents(events: readonly RunEvent[]): RunPullRequest | undefined {
179
+ let pr: RunPullRequest | undefined;
180
+ for (const e of events) if (e.type === "pr_opened") pr = { number: e.number, url: e.url };
181
+ return pr;
182
+ }
183
+
184
+ function isRunPullRequestShape(v: unknown): v is RunPullRequest {
185
+ if (typeof v !== "object" || v === null) return false;
186
+ const p = v as Record<string, unknown>;
187
+ return (
188
+ typeof p.number === "number" &&
189
+ Number.isInteger(p.number) &&
190
+ p.number > 0 &&
191
+ typeof p.url === "string" &&
192
+ p.url.length > 0
193
+ );
161
194
  }
162
195
 
163
196
  /** The three places a run's conversation can start (item 52): the thread's
@@ -574,6 +607,8 @@ export function isRunRecord(v: unknown): v is RunRecord {
574
607
  if (r.dispositions !== undefined && !isFindingDispositionsShape(r.dispositions)) return false;
575
608
  if (r.reviewPost !== undefined && !isReviewPostShape(r.reviewPost)) return false;
576
609
  if (r.profile !== undefined && !isRunProfileRecord(r.profile)) return false;
610
+ // The pull request the run reached (item 2): a positive integer and a URL, or absent.
611
+ if (r.pr !== undefined && !isRunPullRequestShape(r.pr)) return false;
577
612
  // A parent is named by a run id (item 46): the same shape as the record's own.
578
613
  if (r.parentRunId !== undefined && (typeof r.parentRunId !== "string" || !RUN_ID_PATTERN.test(r.parentRunId)))
579
614
  return false;