@coreplane/switchboard 1.222.0 → 1.224.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/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 }
@@ -31654,7 +31681,7 @@ var init_bridge2 = __esm({
31654
31681
  return this.answerText ?? this.heldAnswer;
31655
31682
  }
31656
31683
  observe(event) {
31657
- const out = { replies: [], settled: false, turnEnded: false };
31684
+ const out = { replies: [], settled: false };
31658
31685
  const disposition = PI_EVENT_DISPOSITION[event.type];
31659
31686
  if (disposition === void 0) {
31660
31687
  this.note("harness_error", `pi emitted an event kind this build does not know: ${redactAndCap(event.type, 80)}`);
@@ -31671,9 +31698,6 @@ var init_bridge2 = __esm({
31671
31698
  case "agent_settled":
31672
31699
  out.settled = true;
31673
31700
  break;
31674
- case "turn_end":
31675
- out.turnEnded = true;
31676
- break;
31677
31701
  case "message_start":
31678
31702
  if (isRecord3(event.message) && event.message.role === "assistant") this.assistantStartedAt = this.deps.clock();
31679
31703
  break;
@@ -31866,6 +31890,7 @@ var init_bridge2 = __esm({
31866
31890
  });
31867
31891
 
31868
31892
  // ../../src/core/harness/pi/mirror.ts
31893
+ import { isDeepStrictEqual } from "node:util";
31869
31894
  function partsOf2(content) {
31870
31895
  if (typeof content === "string") return [{ type: "text", text: content }];
31871
31896
  if (!Array.isArray(content)) return [];
@@ -32015,11 +32040,14 @@ var init_mirror = __esm({
32015
32040
  constructor(deps) {
32016
32041
  this.deps = deps;
32017
32042
  this.idx = deps.seedLength;
32043
+ this.mirroredTail = deps.mirroredTail;
32018
32044
  }
32019
32045
  deps;
32020
32046
  idx;
32021
32047
  pendingUser = [];
32022
- seedEchoed = false;
32048
+ /** pi is about to echo the seed's prompt as a user message (see `expectSeedEcho`). */
32049
+ seedEchoPending = false;
32050
+ mirroredTail;
32023
32051
  iteration = 0;
32024
32052
  /** The highest ledger inbox seq folded in so far (run-history item 40). */
32025
32053
  inboxConsumedSeq = 0;
@@ -32027,19 +32055,43 @@ var init_mirror = __esm({
32027
32055
  get wired() {
32028
32056
  return this.deps.onStep !== void 0;
32029
32057
  }
32058
+ /** pi answered the seed's prompt: the first user message without results
32059
+ * that follows is its echo — the seed, on the ledger already — and is not
32060
+ * written twice. A continue prompt (harness-pi item 8) expects none: its
32061
+ * echo is a turn the model was told, folded in like a steer's text. */
32062
+ expectSeedEcho() {
32063
+ this.seedEchoPending = true;
32064
+ }
32065
+ /** The row about to be written, against the transcript's tail — compared
32066
+ * once, with the first row the mirror meets: equal means the ledger holds
32067
+ * it already (read again, harness-pi item 8), together with the results
32068
+ * pending before it, which were its step's user turn. */
32069
+ alreadyHeld(row) {
32070
+ const tail = this.mirroredTail;
32071
+ if (tail === void 0) return false;
32072
+ this.mirroredTail = void 0;
32073
+ if (!isDeepStrictEqual(row, tail)) return false;
32074
+ this.pendingUser = [];
32075
+ return true;
32076
+ }
32077
+ /** Feeds one finished message; answers whether the ledger now holds
32078
+ * everything up to it — a step written, or an assistant turn found already
32079
+ * there — so the harness can move the row's offset past it. A result or a
32080
+ * steer's text waits in memory for the next step, and the answer is no. */
32030
32081
  async onMessage(message3, turn) {
32031
- if (!this.deps.onStep) return;
32082
+ if (!this.deps.onStep) return false;
32032
32083
  const chat = chatMessageOf(message3);
32033
- if (!chat) return;
32034
- if (chat.content.length === 0) return;
32084
+ if (!chat) return false;
32085
+ if (chat.content.length === 0) return false;
32035
32086
  if (chat.role === "user") {
32036
- if (!this.seedEchoed && !chat.content.some((p) => p.type === "tool_result")) {
32037
- this.seedEchoed = true;
32038
- return;
32087
+ if (this.seedEchoPending && !chat.content.some((p) => p.type === "tool_result")) {
32088
+ this.seedEchoPending = false;
32089
+ return false;
32039
32090
  }
32040
32091
  this.pendingUser.push(...chat.content);
32041
- return;
32092
+ return false;
32042
32093
  }
32094
+ if (this.alreadyHeld({ turn: chat })) return true;
32043
32095
  const turns = [];
32044
32096
  if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
32045
32097
  this.pendingUser = [];
@@ -32056,13 +32108,16 @@ var init_mirror = __esm({
32056
32108
  };
32057
32109
  this.idx += turns.length;
32058
32110
  await this.deps.onStep(report2);
32111
+ return true;
32059
32112
  }
32060
32113
  /** pi compacted (session-log item 6): the results pending since the last
32061
32114
  * assistant turn go as the user turn they would have been, the entry as the
32062
32115
  * row after them — its own step with nothing in flight, so the log holds
32063
- * the summary where pi wrote it and the index moves past it. */
32116
+ * the summary where pi wrote it and the index moves past it. Answers as
32117
+ * `onMessage` does: the ledger holds everything up to the entry. */
32064
32118
  async onCompaction(entry, turn) {
32065
- if (!this.deps.onStep) return;
32119
+ if (!this.deps.onStep) return false;
32120
+ if (this.alreadyHeld({ compaction: entry })) return true;
32066
32121
  const turns = [];
32067
32122
  if (this.pendingUser.length > 0) turns.push({ role: "user", content: this.pendingUser });
32068
32123
  this.pendingUser = [];
@@ -32078,6 +32133,7 @@ var init_mirror = __esm({
32078
32133
  };
32079
32134
  this.idx += turns.length + 1;
32080
32135
  await this.deps.onStep(report2);
32136
+ return true;
32081
32137
  }
32082
32138
  };
32083
32139
  }
@@ -32094,11 +32150,16 @@ var init_transport = __esm({
32094
32150
  constructor(deps) {
32095
32151
  this.deps = deps;
32096
32152
  this.offset = deps.offset ?? 0;
32153
+ this.consumedOffset = this.offset;
32097
32154
  this.lines = this.read();
32098
32155
  }
32099
32156
  deps;
32100
- /** The byte the next read starts at — the re-attach fact. */
32157
+ /** The byte the next read starts at: the read position, moved by whole chunks. */
32101
32158
  offset;
32159
+ /** The boundary after the record last handed out, moved by records: where a
32160
+ * reader that has seen everything so far continues from — the harness's
32161
+ * re-attach fact, taken when nothing handed out is left unwritten. */
32162
+ consumedOffset;
32102
32163
  /** pi was found dead while the harness still listened. */
32103
32164
  exited = false;
32104
32165
  closed = false;
@@ -32151,11 +32212,15 @@ var init_transport = __esm({
32151
32212
  /** Complete lines out of the buffered bytes; the partial tail waits for more. */
32152
32213
  *split(chunk) {
32153
32214
  this.buffer = Buffer.concat([this.buffer, Buffer.from(chunk)]);
32215
+ const base = this.offset - this.buffer.length;
32154
32216
  let start3 = 0;
32155
32217
  for (let i = this.buffer.indexOf(NEWLINE, start3); i >= 0; i = this.buffer.indexOf(NEWLINE, start3)) {
32156
32218
  let line3 = this.buffer.subarray(start3, i).toString("utf8");
32157
32219
  if (line3.endsWith("\r")) line3 = line3.slice(0, -1);
32158
- if (line3.length > 0) yield line3;
32220
+ if (line3.length > 0) {
32221
+ this.consumedOffset = base + i + 1;
32222
+ yield line3;
32223
+ }
32159
32224
  start3 = i + 1;
32160
32225
  }
32161
32226
  this.buffer = this.buffer.subarray(start3);
@@ -32165,6 +32230,7 @@ var init_transport = __esm({
32165
32230
  });
32166
32231
 
32167
32232
  // ../../src/core/harness/pi/harness.ts
32233
+ import { randomUUID as randomUUID2 } from "node:crypto";
32168
32234
  function piHarnessFactsOf(value) {
32169
32235
  if (typeof value !== "object" || value === null) return void 0;
32170
32236
  const v = value;
@@ -32210,6 +32276,9 @@ function promptOf(seed) {
32210
32276
  }
32211
32277
  return { message: texts.join("\n\n"), ...images.length > 0 ? { images } : {} };
32212
32278
  }
32279
+ function settlementText(s) {
32280
+ 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.`;
32281
+ }
32213
32282
  function settlementResults(settlements) {
32214
32283
  if (settlements.length === 0) return void 0;
32215
32284
  return {
@@ -32217,11 +32286,21 @@ function settlementResults(settlements) {
32217
32286
  content: settlements.map((s) => ({
32218
32287
  type: "tool_result",
32219
32288
  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.`,
32289
+ content: settlementText(s),
32221
32290
  isError: true
32222
32291
  }))
32223
32292
  };
32224
32293
  }
32294
+ function ledgerTailOf(resume) {
32295
+ if (!resume) return void 0;
32296
+ const closing = resume.compactions?.filter((c) => c.before === resume.messages.length).at(-1);
32297
+ if (closing) return { compaction: closing.entry };
32298
+ const last = resume.messages.at(-1);
32299
+ return last?.role === "assistant" ? { turn: last } : void 0;
32300
+ }
32301
+ function settlementAnswer(s) {
32302
+ return { content: [{ type: "text", text: settlementText(s) }], isError: true };
32303
+ }
32225
32304
  async function runPiHarness(deps, run2) {
32226
32305
  const { container, clock: clock3 } = deps;
32227
32306
  const now = () => clock3();
@@ -32263,10 +32342,12 @@ async function runPiHarness(deps, run2) {
32263
32342
  };
32264
32343
  }
32265
32344
  bridge.turns = run2.resume?.turn ?? 0;
32345
+ const tail = ledgerTailOf(run2.resume);
32266
32346
  const mirror = new PiMirror({
32267
32347
  onStep: run2.onStep,
32268
- seedLength: run2.resume?.messages.length ?? run2.messages.length,
32269
- remainingMs: () => deadline - now()
32348
+ seedLength: run2.resume ? run2.resume.messages.length + (run2.resume.compactions?.length ?? 0) : run2.messages.length,
32349
+ remainingMs: () => deadline - now(),
32350
+ ...tail ? { mirroredTail: tail } : {}
32270
32351
  });
32271
32352
  mirror.inboxConsumedSeq = run2.resume?.inboxConsumedSeq ?? 0;
32272
32353
  let writeUp;
@@ -32298,15 +32379,22 @@ async function runPiHarness(deps, run2) {
32298
32379
  }
32299
32380
  };
32300
32381
  const forget = deps.registry.register(live);
32382
+ const calls = deps.registry.calls(run2.runId);
32383
+ for (const s of run2.resume?.settlements ?? []) calls?.settle(s.toolUse.id, settlementAnswer(s));
32301
32384
  let pid;
32302
32385
  let transport;
32303
32386
  let facts2;
32387
+ let mirrored = 0;
32304
32388
  const save = () => {
32305
32389
  if (facts2) {
32306
- if (transport) facts2 = { ...facts2, logOffset: transport.offset };
32390
+ facts2 = { ...facts2, logOffset: mirrored };
32307
32391
  run2.saveFacts?.(facts2);
32308
32392
  }
32309
32393
  };
32394
+ const held = () => {
32395
+ if (transport) mirrored = transport.consumedOffset;
32396
+ save();
32397
+ };
32310
32398
  let catchingUp = false;
32311
32399
  try {
32312
32400
  const recorded = run2.resume?.facts;
@@ -32333,18 +32421,20 @@ async function runPiHarness(deps, run2) {
32333
32421
  if (reattached && recorded && paths !== void 0) {
32334
32422
  pid = recorded.pid;
32335
32423
  facts2 = { ...recorded, ...recorded.container === void 0 && here !== void 0 ? { container: here } : {} };
32424
+ mirrored = recorded.logOffset;
32336
32425
  transport = new PiRpcTransport({
32337
32426
  container,
32338
32427
  paths,
32339
32428
  pid,
32340
32429
  pollMs: deps.pollMs ?? 750,
32341
32430
  sleep: deps.sleep,
32342
- offset: recorded.logOffset
32431
+ offset: mirrored
32343
32432
  });
32344
32433
  catchingUp = true;
32434
+ const inFlight = run2.resume?.settlements.length ?? 0;
32345
32435
  note(
32346
32436
  "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`
32437
+ `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
32438
  );
32349
32439
  } else {
32350
32440
  paths = await container.makeRoot(run2.runId);
@@ -32359,7 +32449,8 @@ async function runPiHarness(deps, run2) {
32359
32449
  ...run2.effort ? { effort: run2.effort } : {},
32360
32450
  identity: run2.agent.identity,
32361
32451
  system: run2.system,
32362
- relayTools: run2.tools.map((t) => t.name)
32452
+ relayTools: run2.tools.map((t) => t.name),
32453
+ ...deps.compaction ? { compaction: deps.compaction } : {}
32363
32454
  };
32364
32455
  let session;
32365
32456
  if (run2.resume) {
@@ -32415,10 +32506,15 @@ async function runPiHarness(deps, run2) {
32415
32506
  save();
32416
32507
  transport = new PiRpcTransport({ container, paths, pid, pollMs: deps.pollMs ?? 750, sleep: deps.sleep });
32417
32508
  }
32418
- transport.send({ id: "retry", type: "set_auto_retry", enabled: false });
32419
- transport.send({ id: "state", type: "get_state" });
32420
- if (reattached || run2.resume) transport.send({ id: "prompt", type: "prompt", message: CONTINUE_PROMPT });
32421
- else transport.send({ id: "prompt", type: "prompt", ...promptOf(run2.messages) });
32509
+ const stamp = `${now()}-${randomUUID2().slice(0, 8)}`;
32510
+ const ids = { retry: `retry:${stamp}`, state: `state:${stamp}`, prompt: `prompt:${stamp}` };
32511
+ const continuing = reattached || run2.resume !== void 0;
32512
+ transport.send({ id: ids.retry, type: "set_auto_retry", enabled: false });
32513
+ transport.send({ id: ids.state, type: "get_state" });
32514
+ if (reattached)
32515
+ transport.send({ id: ids.prompt, type: "prompt", message: CONTINUE_PROMPT, streamingBehavior: "steer" });
32516
+ else if (run2.resume) transport.send({ id: ids.prompt, type: "prompt", message: CONTINUE_PROMPT });
32517
+ else transport.send({ id: ids.prompt, type: "prompt", ...promptOf(run2.messages) });
32422
32518
  let warned = false;
32423
32519
  let settled = false;
32424
32520
  let stopMode;
@@ -32536,19 +32632,20 @@ ${stagedLine}` : prompt,
32536
32632
  }
32537
32633
  if (obs.response) {
32538
32634
  const r = obs.response;
32539
- if (r.id === "state" && r.success === true && typeof r.data?.sessionFile === "string") {
32635
+ if (r.id === ids.state && r.success === true && typeof r.data?.sessionFile === "string") {
32540
32636
  const data = r.data;
32541
32637
  facts2 = { ...facts2 ?? { pid, logOffset: 0, root: paths.dir }, sessionFile: String(data.sessionFile) };
32542
32638
  save();
32543
32639
  }
32544
- if (r.id === "prompt") {
32640
+ if (r.id === ids.prompt) {
32545
32641
  catchingUp = false;
32546
32642
  if (r.success === false) throw new PromptRefused(String(r.error ?? "no reason"));
32643
+ if (!continuing) mirror.expectSeedEcho();
32547
32644
  }
32548
32645
  }
32549
- if (obs.message) await mirror.onMessage(obs.message, bridge.turns);
32646
+ if (obs.message && await mirror.onMessage(obs.message, bridge.turns)) held();
32550
32647
  if (obs.compaction) {
32551
- await mirror.onCompaction(obs.compaction, bridge.turns);
32648
+ if (await mirror.onCompaction(obs.compaction, bridge.turns)) held();
32552
32649
  if (!writeUp && !hardStopped && !catchingUp) {
32553
32650
  let notepad;
32554
32651
  let unavailable = false;
@@ -32569,7 +32666,6 @@ ${stagedLine}` : prompt,
32569
32666
  note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
32570
32667
  else providerError = obs.providerError;
32571
32668
  }
32572
- if (obs.turnEnded) save();
32573
32669
  if (obs.settled && !catchingUp) {
32574
32670
  settled = true;
32575
32671
  break;
@@ -32583,8 +32679,8 @@ ${stagedLine}` : prompt,
32583
32679
  }
32584
32680
  if (bypass) throw bypass;
32585
32681
  if (!settled) {
32586
- const tail = await container.tail(paths.errLog, 2e3);
32587
- throw new Error(`pi exited before the run settled${tail.trim() ? `: ${redactAndCap(tail.trim(), 400)}` : ""}`);
32682
+ const tail2 = await container.tail(paths.errLog, 2e3);
32683
+ throw new Error(`pi exited before the run settled${tail2.trim() ? `: ${redactAndCap(tail2.trim(), 400)}` : ""}`);
32588
32684
  }
32589
32685
  if (providerError !== void 0) throw new Error(`the model call failed: ${providerError}`);
32590
32686
  const text = bridge.answer() ?? "";
@@ -33571,6 +33667,9 @@ async function runLoop2(deps, ctx) {
33571
33667
  harnessUrl,
33572
33668
  registry: deps.harness.registry,
33573
33669
  ...deps.runBearers ? { bearers: deps.runBearers } : {},
33670
+ // The deployment's compaction thresholds ride into pi's settings for
33671
+ // every run on pi (harness-pi item 4); absent, pi's defaults stand.
33672
+ ...deps.config.config.pi?.compaction ? { compaction: deps.config.config.pi.compaction } : {},
33574
33673
  clock: clock3,
33575
33674
  sleep: (ms) => new Promise((r) => setTimeout(r, ms))
33576
33675
  },
@@ -36745,8 +36844,8 @@ var init_types5 = __esm({
36745
36844
  });
36746
36845
 
36747
36846
  // ../../src/core/runLedger/writeThrough.ts
36748
- import { randomUUID as randomUUID2 } from "node:crypto";
36749
- function mintGeneration(now = Date.now, random = randomUUID2) {
36847
+ import { randomUUID as randomUUID3 } from "node:crypto";
36848
+ function mintGeneration(now = Date.now, random = randomUUID3) {
36750
36849
  const stamp = new Date(now()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
36751
36850
  const gen = `${stamp}-${random().replace(/-/g, "").slice(0, 8)}`;
36752
36851
  if (!GEN_PATTERN.test(gen)) throw new Error(`minted generation ${gen} does not match GEN_PATTERN`);
@@ -43082,6 +43181,13 @@ var init_relay = __esm({
43082
43181
  }
43083
43182
  return answer;
43084
43183
  }
43184
+ /** The call's answer is known before it is asked (harness-pi item 8): the
43185
+ * record's settlement for a call in flight when the previous generation
43186
+ * died, whose extension asks again with the same id. Every ask reads it and
43187
+ * the tool never runs; a call already joined keeps the answer it has. */
43188
+ settle(callId, answer) {
43189
+ if (!this.calls.has(callId)) this.calls.set(callId, Promise.resolve(answer));
43190
+ }
43085
43191
  end() {
43086
43192
  this.ending.abort();
43087
43193
  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.224.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",