@coreplane/switchboard 1.222.0 → 1.223.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.
@@ -328,6 +328,20 @@ workspaceDir: ./workspaces
328
328
  # harness:
329
329
  # coding: pi
330
330
 
331
+ # What the harness writes into pi's per-run settings for every run on pi
332
+ # (docs/reference/specs/harness-pi.md item 4): pi's compaction thresholds, in
333
+ # tokens, under pi's own names. pi compacts when the context passes the model's
334
+ # window less `reserveTokens` and keeps about `keepRecentTokens` of the newest
335
+ # turns; unset, pi's defaults stand (16384 reserved, 20000 kept) and the file
336
+ # is exactly what it was before this block existed. A reserve near the window
337
+ # makes a short run compact — what a receipt or a test of the compaction path
338
+ # wants; production leaves it unset. Positive integers only; any other shape
339
+ # or key fails the load by name.
340
+ # pi:
341
+ # compaction:
342
+ # reserveTokens: 150000
343
+ # keepRecentTokens: 8000
344
+
331
345
  # The fan-out cap a spawning run meets (docs/reference/specs/agent-conductor.md).
332
346
  # `agent:conductor` starts child runs as the person who asked — each an
333
347
  # ordinary run in a thread of its own, under that person's permissions — and
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "switchboard",
3
- "version": "1.222.0",
3
+ "version": "1.223.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "switchboard",
9
- "version": "1.222.0",
9
+ "version": "1.223.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.222.0",
16713
+ "version": "1.223.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.222.0",
3
+ "version": "1.223.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.222.0",
3
- "commit": "1db005e243ee6d065d25b6d31abf87b1c0c5ba3e",
4
- "builtAt": "2026-09-15T15:12:17.375Z"
2
+ "version": "1.223.0",
3
+ "commit": "8078f3e3769389ce78b23344b892e18715aad568",
4
+ "builtAt": "2026-09-15T16:23:42.063Z"
5
5
  }
package/dist/cli.js CHANGED
@@ -2858,8 +2858,24 @@ function validateConfig(cfg) {
2858
2858
  if (cfg.routing !== void 0) validateRouting(cfg.routing, cfg.providers);
2859
2859
  if (cfg.artifacts !== void 0) validateArtifacts(cfg.artifacts);
2860
2860
  if (cfg.harness !== void 0) validateHarness(cfg.harness);
2861
+ if (cfg.pi !== void 0) validatePi(cfg.pi);
2861
2862
  validateDashboardConfig(cfg.dashboard);
2862
2863
  }
2864
+ function validatePi(pi) {
2865
+ if (typeof pi !== "object" || pi === null || Array.isArray(pi)) throw new Error("config.yaml: pi must be a mapping");
2866
+ for (const key of unknownKeys2(pi, PI_KEYS)) throw new Error(`config.yaml: pi.${key} is not a known key`);
2867
+ const { compaction } = pi;
2868
+ if (compaction === void 0) return;
2869
+ if (typeof compaction !== "object" || compaction === null || Array.isArray(compaction))
2870
+ throw new Error("config.yaml: pi.compaction must be a mapping");
2871
+ for (const key of unknownKeys2(compaction, PI_COMPACTION_KEYS))
2872
+ throw new Error(`config.yaml: pi.compaction.${key} is not a known key`);
2873
+ for (const key of Object.keys(PI_COMPACTION_KEYS)) {
2874
+ const value = compaction[key];
2875
+ if (value !== void 0 && (typeof value !== "number" || !Number.isInteger(value) || value <= 0))
2876
+ throw new Error(`config.yaml: pi.compaction.${key} must be a positive integer (tokens)`);
2877
+ }
2878
+ }
2863
2879
  function validateHarness(harness) {
2864
2880
  if (typeof harness !== "object" || harness === null || Array.isArray(harness))
2865
2881
  throw new Error("config.yaml: harness must be a mapping of preset to harness (native | pi)");
@@ -2977,7 +2993,7 @@ function validateRuntimeOverrides(ro) {
2977
2993
  }
2978
2994
  }
2979
2995
  }
2980
- var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, ROUTE_ANSWER_MODES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
2996
+ var MAX_INSTRUCTIONS_LENGTH, CONFIG_KEYS, SELF_IMPROVEMENT_KEYS, BOUNDARY_KEYS, MIN_BOUNDARY_MINUTES, PI_KEYS, PI_COMPACTION_KEYS, ROUTE_ANSWER_MODES, ROUTING_KEYS, SHIP_KEYS, SPAWN_KEYS;
2981
2997
  var init_validate = __esm({
2982
2998
  "../../src/config/validate.ts"() {
2983
2999
  "use strict";
@@ -3012,6 +3028,7 @@ var init_validate = __esm({
3012
3028
  spawn: true,
3013
3029
  routing: true,
3014
3030
  harness: true,
3031
+ pi: true,
3015
3032
  slack: true,
3016
3033
  runHistory: true,
3017
3034
  runtimeOverrides: true,
@@ -3026,6 +3043,8 @@ var init_validate = __esm({
3026
3043
  };
3027
3044
  BOUNDARY_KEYS = { maxMinutes: true, maxIdentity: true, machines: true };
3028
3045
  MIN_BOUNDARY_MINUTES = 2;
3046
+ PI_KEYS = { compaction: true };
3047
+ PI_COMPACTION_KEYS = { reserveTokens: true, keepRecentTokens: true };
3029
3048
  ROUTE_ANSWER_MODES = ["tool", "text"];
3030
3049
  ROUTING_KEYS = { auto: true, model: true, answer: true };
3031
3050
  SHIP_KEYS = { maxRounds: true, maxMinutes: true };
@@ -12186,6 +12205,8 @@ var init_costs = __esm({
12186
12205
  async fetchCostReport(range) {
12187
12206
  const rows = [];
12188
12207
  let closedThrough = null;
12208
+ const endExclusive = range.partialLastDay ? range.to : addDays2(range.to, 1);
12209
+ if (range.from >= endExclusive) return { rows, closedThrough };
12189
12210
  let page2 = null;
12190
12211
  for (let i = 0; ; i++) {
12191
12212
  if (i >= MAX_COST_PAGES)
@@ -12194,7 +12215,7 @@ var init_costs = __esm({
12194
12215
  );
12195
12216
  const url = new URL(ANTHROPIC_COST_REPORT);
12196
12217
  url.searchParams.set("starting_at", `${range.from}T00:00:00Z`);
12197
- url.searchParams.set("ending_at", `${addDays2(range.to, 1)}T00:00:00Z`);
12218
+ url.searchParams.set("ending_at", `${endExclusive}T00:00:00Z`);
12198
12219
  url.searchParams.set("bucket_width", "1d");
12199
12220
  url.searchParams.append("group_by[]", "workspace_id");
12200
12221
  url.searchParams.set("limit", "31");
@@ -31092,12 +31113,18 @@ function piSystemPrompt(spec) {
31092
31113
  ${harnessPromptNote(spec.relayTools, spec.identity)}
31093
31114
  `;
31094
31115
  }
31095
- function piSettingsJson() {
31096
- return JSON.stringify({ defaultProjectTrust: "never", checkForUpdates: false }, null, 2) + "\n";
31116
+ function piSettingsJson(compaction) {
31117
+ const settings = { defaultProjectTrust: "never", checkForUpdates: false };
31118
+ const thresholds = {
31119
+ ...compaction?.reserveTokens !== void 0 ? { reserveTokens: compaction.reserveTokens } : {},
31120
+ ...compaction?.keepRecentTokens !== void 0 ? { keepRecentTokens: compaction.keepRecentTokens } : {}
31121
+ };
31122
+ if (Object.keys(thresholds).length > 0) settings.compaction = thresholds;
31123
+ return JSON.stringify(settings, null, 2) + "\n";
31097
31124
  }
31098
31125
  function piLaunchFiles(spec) {
31099
31126
  return [
31100
- { path: `${spec.paths.agentDir}/settings.json`, content: piSettingsJson() },
31127
+ { path: `${spec.paths.agentDir}/settings.json`, content: piSettingsJson(spec.compaction) },
31101
31128
  { path: `${spec.paths.agentDir}/models.json`, content: piModelsJson(spec) },
31102
31129
  { path: `${spec.paths.agentDir}/SYSTEM.md`, content: piSystemPrompt(spec) },
31103
31130
  { path: spec.paths.extension, content: PI_EXTENSION_SOURCE }
@@ -32210,6 +32237,9 @@ function promptOf(seed) {
32210
32237
  }
32211
32238
  return { message: texts.join("\n\n"), ...images.length > 0 ? { images } : {} };
32212
32239
  }
32240
+ function settlementText(s) {
32241
+ return s.action === "synthetic" ? s.text : `The bot restarted while this ${s.toolUse.name} call was in flight; its result was lost \u2014 re-check its effects before re-running it.`;
32242
+ }
32213
32243
  function settlementResults(settlements) {
32214
32244
  if (settlements.length === 0) return void 0;
32215
32245
  return {
@@ -32217,11 +32247,14 @@ function settlementResults(settlements) {
32217
32247
  content: settlements.map((s) => ({
32218
32248
  type: "tool_result",
32219
32249
  toolUseId: s.toolUse.id,
32220
- content: s.action === "synthetic" ? s.text : `The bot restarted while this ${s.toolUse.name} call was in flight; its result was lost \u2014 re-check its effects before re-running it.`,
32250
+ content: settlementText(s),
32221
32251
  isError: true
32222
32252
  }))
32223
32253
  };
32224
32254
  }
32255
+ function settlementAnswer(s) {
32256
+ return { content: [{ type: "text", text: settlementText(s) }], isError: true };
32257
+ }
32225
32258
  async function runPiHarness(deps, run2) {
32226
32259
  const { container, clock: clock3 } = deps;
32227
32260
  const now = () => clock3();
@@ -32298,6 +32331,8 @@ async function runPiHarness(deps, run2) {
32298
32331
  }
32299
32332
  };
32300
32333
  const forget = deps.registry.register(live);
32334
+ const calls = deps.registry.calls(run2.runId);
32335
+ for (const s of run2.resume?.settlements ?? []) calls?.settle(s.toolUse.id, settlementAnswer(s));
32301
32336
  let pid;
32302
32337
  let transport;
32303
32338
  let facts2;
@@ -32342,9 +32377,10 @@ async function runPiHarness(deps, run2) {
32342
32377
  offset: recorded.logOffset
32343
32378
  });
32344
32379
  catchingUp = true;
32380
+ const inFlight = run2.resume?.settlements.length ?? 0;
32345
32381
  note(
32346
32382
  "resumed",
32347
- `resumed after a restart: pi still runs in the container (pid ${pid}); continuing its session with ${Math.round(remainingMs / 6e4)} min of budget left`
32383
+ `resumed after a restart: pi still runs in the container (pid ${pid}); continuing its session with ${Math.round(remainingMs / 6e4)} min of budget left` + (inFlight > 0 ? ` \u2014 ${inFlight} call(s) were in flight, each answered with a restart note if pi asks for it again` : "")
32348
32384
  );
32349
32385
  } else {
32350
32386
  paths = await container.makeRoot(run2.runId);
@@ -32359,7 +32395,8 @@ async function runPiHarness(deps, run2) {
32359
32395
  ...run2.effort ? { effort: run2.effort } : {},
32360
32396
  identity: run2.agent.identity,
32361
32397
  system: run2.system,
32362
- relayTools: run2.tools.map((t) => t.name)
32398
+ relayTools: run2.tools.map((t) => t.name),
32399
+ ...deps.compaction ? { compaction: deps.compaction } : {}
32363
32400
  };
32364
32401
  let session;
32365
32402
  if (run2.resume) {
@@ -32417,7 +32454,9 @@ async function runPiHarness(deps, run2) {
32417
32454
  }
32418
32455
  transport.send({ id: "retry", type: "set_auto_retry", enabled: false });
32419
32456
  transport.send({ id: "state", type: "get_state" });
32420
- if (reattached || run2.resume) transport.send({ id: "prompt", type: "prompt", message: CONTINUE_PROMPT });
32457
+ if (reattached)
32458
+ transport.send({ id: "prompt", type: "prompt", message: CONTINUE_PROMPT, streamingBehavior: "steer" });
32459
+ else if (run2.resume) transport.send({ id: "prompt", type: "prompt", message: CONTINUE_PROMPT });
32421
32460
  else transport.send({ id: "prompt", type: "prompt", ...promptOf(run2.messages) });
32422
32461
  let warned = false;
32423
32462
  let settled = false;
@@ -33571,6 +33610,9 @@ async function runLoop2(deps, ctx) {
33571
33610
  harnessUrl,
33572
33611
  registry: deps.harness.registry,
33573
33612
  ...deps.runBearers ? { bearers: deps.runBearers } : {},
33613
+ // The deployment's compaction thresholds ride into pi's settings for
33614
+ // every run on pi (harness-pi item 4); absent, pi's defaults stand.
33615
+ ...deps.config.config.pi?.compaction ? { compaction: deps.config.config.pi.compaction } : {},
33574
33616
  clock: clock3,
33575
33617
  sleep: (ms) => new Promise((r) => setTimeout(r, ms))
33576
33618
  },
@@ -43082,6 +43124,13 @@ var init_relay = __esm({
43082
43124
  }
43083
43125
  return answer;
43084
43126
  }
43127
+ /** The call's answer is known before it is asked (harness-pi item 8): the
43128
+ * record's settlement for a call in flight when the previous generation
43129
+ * died, whose extension asks again with the same id. Every ask reads it and
43130
+ * the tool never runs; a call already joined keeps the answer it has. */
43131
+ settle(callId, answer) {
43132
+ if (!this.calls.has(callId)) this.calls.set(callId, Promise.resolve(answer));
43133
+ }
43085
43134
  end() {
43086
43135
  this.ending.abort();
43087
43136
  this.calls.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coreplane/switchboard",
3
- "version": "1.222.0",
3
+ "version": "1.223.0",
4
4
  "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.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://openswitchboard.dev",