orkestrator 2.22.3 → 2.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orkestrator",
3
- "version": "2.22.3",
3
+ "version": "2.23.0",
4
4
  "description": "Run the Orkestrator backend service without the desktop application",
5
5
  "homepage": "https://orkestrator.dev",
6
6
  "repository": {
@@ -39,8 +39,8 @@
39
39
  "sharp": "0.35.5"
40
40
  },
41
41
  "devDependencies": {
42
- "@orkestrator/protocol": "2.22.3",
43
- "@orkestrator/toolchain": "2.22.3"
42
+ "@orkestrator/protocol": "2.23.0",
43
+ "@orkestrator/toolchain": "2.23.0"
44
44
  },
45
45
  "engines": {
46
46
  "bun": ">=1.4.3"
@@ -2377,6 +2377,7 @@ var DEBUG_EVENTS = new Set([
2377
2377
  "[session-manager] Prompt completed",
2378
2378
  "[session-manager] Query completed successfully",
2379
2379
  "[session-manager] Query result",
2380
+ "[session-manager] Query stopped by the user",
2380
2381
  "[session-manager] Question not found for requestId:",
2381
2382
  "[session-manager] Re-prompting after approved-plan ExitPlanMode failure",
2382
2383
  "[session-manager] Re-prompting with plan rejection feedback",
@@ -2535,6 +2536,19 @@ function claudeCommandChangesDir() {
2535
2536
  }
2536
2537
 
2537
2538
  // ../../bridges/claude-bridge/src/services/session-preferences.ts
2539
+ function parseTurnInFlightMark(value) {
2540
+ if (!value || typeof value !== "object" || Array.isArray(value))
2541
+ return;
2542
+ const record = value;
2543
+ if (typeof record.bridgeInstanceId !== "string" || record.bridgeInstanceId.length === 0 || record.bridgeInstanceId.length > 64 || typeof record.pid !== "number" || !Number.isSafeInteger(record.pid) || record.pid <= 0 || typeof record.startedAt !== "number" || !Number.isFinite(record.startedAt)) {
2544
+ return;
2545
+ }
2546
+ return {
2547
+ bridgeInstanceId: record.bridgeInstanceId,
2548
+ pid: record.pid,
2549
+ startedAt: record.startedAt
2550
+ };
2551
+ }
2538
2552
  var MAX_SHELL_TASK_DETAILS = 256;
2539
2553
  function parseShellTaskDetails(value) {
2540
2554
  if (!value || typeof value !== "object")
@@ -2782,6 +2796,9 @@ function parsePreferences(raw) {
2782
2796
  if (Array.isArray(record.shellTaskDetails)) {
2783
2797
  preferences.shellTaskDetails = record.shellTaskDetails.slice(-MAX_SHELL_TASK_DETAILS).map(parseShellTaskDetails).filter((entry) => entry !== undefined);
2784
2798
  }
2799
+ const turnInFlight = parseTurnInFlightMark(record.turnInFlight);
2800
+ if (turnInFlight)
2801
+ preferences.turnInFlight = turnInFlight;
2785
2802
  return preferences;
2786
2803
  }
2787
2804
  function parseSteerJournalEntry(value) {
@@ -6620,6 +6637,64 @@ function normalizePersistedSessionMessages(persisted) {
6620
6637
  return { messages, taskRegistry, backgroundTasks };
6621
6638
  }
6622
6639
 
6640
+ // ../../bridges/claude-bridge/src/services/session-manager-turn-interruption.ts
6641
+ import { randomUUID } from "node:crypto";
6642
+ var CLAUDE_RESTARTED_MID_TURN_MESSAGE = "Claude restarted before this turn finished, so its work stopped partway. Send a message to continue.";
6643
+ var BRIDGE_INSTANCE_ID = randomUUID();
6644
+ function claudeProcessKilledMessage(signal) {
6645
+ return `Claude Code was stopped (${signal}) before this turn finished, so its work stopped partway. Send a message to continue.`;
6646
+ }
6647
+ function killedClaudeProcessSignal(error) {
6648
+ if (!error || typeof error !== "object")
6649
+ return;
6650
+ const record = error;
6651
+ if (record.errorClass !== "process_killed_by_signal")
6652
+ return;
6653
+ return typeof record.signal === "string" && /^SIG[A-Z0-9]{1,8}$/.test(record.signal) ? record.signal : "a signal";
6654
+ }
6655
+ function turnInFlight(session) {
6656
+ return session.status === "running" || (session.retainedContinuationRequestIds?.size ?? 0) > 0 || session.status === "error" && session.interruption === "restart";
6657
+ }
6658
+ function syncTurnInFlightMark(session, options = {}) {
6659
+ if (session.deleting)
6660
+ return;
6661
+ writeTurnInFlightMark(session, options.stopped !== true && turnInFlight(session), options.started);
6662
+ }
6663
+ function clearTurnInFlightMarkForClose(session) {
6664
+ writeTurnInFlightMark(session, false);
6665
+ }
6666
+ function writeTurnInFlightMark(session, wanted, claim = false) {
6667
+ if (wanted === (session.turnInFlightMarked === true) && !(wanted && claim && !session.turnInFlightMarkOwned))
6668
+ return;
6669
+ const sdkSessionId = session.sdkSessionId ?? sdkSessionIdFromBridgeId(session.id);
6670
+ if (!sdkSessionId)
6671
+ return;
6672
+ session.turnInFlightMarked = wanted;
6673
+ session.turnInFlightMarkOwned = wanted;
6674
+ const update = {
6675
+ turnInFlight: wanted ? { bridgeInstanceId: BRIDGE_INSTANCE_ID, pid: process.pid, startedAt: Date.now() } : undefined
6676
+ };
6677
+ updateSessionPreferences(sdkSessionId, update).catch((error) => {
6678
+ debugLog("[session-manager] Failed to record the turn-in-flight mark:", error instanceof Error ? error.message : String(error));
6679
+ });
6680
+ }
6681
+ function restoredTurnInterruption(preferences) {
6682
+ const mark = preferences?.turnInFlight;
6683
+ if (!mark)
6684
+ return { status: "idle" };
6685
+ if (mark.bridgeInstanceId === BRIDGE_INSTANCE_ID) {
6686
+ return { status: "idle", turnInFlightMarked: true, turnInFlightMarkOwned: true };
6687
+ }
6688
+ if (mark.pid !== process.pid && processIsAlive(mark.pid))
6689
+ return { status: "idle" };
6690
+ return {
6691
+ status: "error",
6692
+ error: CLAUDE_RESTARTED_MID_TURN_MESSAGE,
6693
+ interruption: "restart",
6694
+ turnInFlightMarked: true
6695
+ };
6696
+ }
6697
+
6623
6698
  // ../../bridges/claude-bridge/src/services/session-manager-lifecycle.ts
6624
6699
  async function getSessionActivity(sessionId) {
6625
6700
  const session = sessions.get(sessionId);
@@ -6879,6 +6954,7 @@ function abortSession(sessionId) {
6879
6954
  session.activity = undefined;
6880
6955
  session.thinkingTokens = undefined;
6881
6956
  releaseQueryControl(session);
6957
+ syncTurnInFlightMark(session, { stopped: true });
6882
6958
  cleanupPendingInteractions(sessionId);
6883
6959
  appendInterruptedNotice(session, sessionId);
6884
6960
  eventEmitter.emit({
@@ -7539,7 +7615,7 @@ async function readJournal(path, maxEntries) {
7539
7615
 
7540
7616
  // ../protocol/src/node/workspace-change-probe.ts
7541
7617
  import { execFile as execFile3, spawn as spawn2 } from "node:child_process";
7542
- import { randomUUID } from "node:crypto";
7618
+ import { randomUUID as randomUUID2 } from "node:crypto";
7543
7619
  import { rmSync } from "node:fs";
7544
7620
  import { copyFile, lstat, mkdtemp, rm as rm3 } from "node:fs/promises";
7545
7621
  import { tmpdir } from "node:os";
@@ -7842,7 +7918,7 @@ class WorkspaceChangeProbe {
7842
7918
  return;
7843
7919
  const { root, indexPath } = repo.location;
7844
7920
  const started = this.now();
7845
- const tempIndex = join4(this.tempDir, `orkestrator-probe-${randomUUID()}.index`);
7921
+ const tempIndex = join4(this.tempDir, `orkestrator-probe-${randomUUID2()}.index`);
7846
7922
  const objectDirectory = await mkdtemp(join4(this.tempDir, "orkestrator-probe-objects-"));
7847
7923
  temporaryObjectDirectories.add(objectDirectory);
7848
7924
  let retained = false;
@@ -8185,7 +8261,7 @@ async function reconcilePersistedSessions() {
8185
8261
  id,
8186
8262
  title: info.customTitle || info.summary || `Session ${info.sessionId.slice(-6)}`,
8187
8263
  messages: [],
8188
- status: "idle",
8264
+ ...restoredTurnInterruption(storedPreferences),
8189
8265
  createdAt: new Date(info.createdAt ?? info.lastModified),
8190
8266
  lastActivity: new Date(info.lastModified),
8191
8267
  sdkSessionId: info.sessionId,
@@ -8227,7 +8303,7 @@ async function materializePersistedSessionState(sessionId, sdkId) {
8227
8303
  id: sessionId,
8228
8304
  title: info.customTitle || info.summary || `Session ${sdkId.slice(-6)}`,
8229
8305
  messages: [],
8230
- status: "idle",
8306
+ ...restoredTurnInterruption(preferences),
8231
8307
  createdAt: new Date(info.createdAt ?? info.lastModified),
8232
8308
  lastActivity: new Date(info.lastModified),
8233
8309
  sdkSessionId: sdkId,
@@ -9876,6 +9952,7 @@ function releaseCompletedTurnToBackgroundTasks(turn, backgroundTasksRunning) {
9876
9952
  if (turn.dispatchRequestId) {
9877
9953
  recordPromptDispatch(sessionId, turn.dispatchRequestId, "already-processed");
9878
9954
  }
9955
+ syncTurnInFlightMark(session);
9879
9956
  eventEmitter.emit({
9880
9957
  type: "session.idle",
9881
9958
  sessionId,
@@ -9896,6 +9973,7 @@ function stopWaitingForContinuation(turn) {
9896
9973
  session.retainedContinuationRequestIds = undefined;
9897
9974
  }
9898
9975
  }
9976
+ syncTurnInFlightMark(session);
9899
9977
  }
9900
9978
  function waitForContinuationAfterNotification(turn) {
9901
9979
  const { session } = turn;
@@ -9905,6 +9983,7 @@ function waitForContinuationAfterNotification(turn) {
9905
9983
  if (turn.dispatchRequestId) {
9906
9984
  (session.retainedContinuationRequestIds ??= new Set).add(turn.dispatchRequestId);
9907
9985
  }
9986
+ syncTurnInFlightMark(session);
9908
9987
  if (turn.runtime?.idleTimer)
9909
9988
  armClaudeRuntimeIdleDetach(turn.runtime);
9910
9989
  }
@@ -9996,6 +10075,7 @@ function captureTurnStartup(session) {
9996
10075
  turnStartedAt: session.turnStartedAt,
9997
10076
  abortController: session.abortController,
9998
10077
  error: session.error,
10078
+ interruption: session.interruption,
9999
10079
  lastActivity: session.lastActivity,
10000
10080
  persistedMessagesLoaded: session.persistedMessagesLoaded,
10001
10081
  structuredOutput: session.structuredOutput,
@@ -10164,6 +10244,7 @@ async function applyPromptPlanMode(admission, planMode) {
10164
10244
  session.status = beforeStartup.status;
10165
10245
  session.turnStartedAt = beforeStartup.turnStartedAt;
10166
10246
  session.error = beforeStartup.error;
10247
+ session.interruption = beforeStartup.interruption;
10167
10248
  session.lastActivity = beforeStartup.lastActivity;
10168
10249
  session.persistedMessagesLoaded = beforeStartup.persistedMessagesLoaded;
10169
10250
  if (needsTranscriptHydration)
@@ -10183,6 +10264,7 @@ async function applyPromptPlanMode(admission, planMode) {
10183
10264
  function beginPromptActivity(admission) {
10184
10265
  const { session, sessionId } = admission;
10185
10266
  session.error = undefined;
10267
+ session.interruption = undefined;
10186
10268
  session.lastActivity = new Date;
10187
10269
  if (session.promptSuggestion !== undefined) {
10188
10270
  session.promptSuggestion = undefined;
@@ -10468,7 +10550,7 @@ async function getMcpRuntimeConfig(cwd, env = process.env, connection, sources =
10468
10550
  }
10469
10551
 
10470
10552
  // ../../bridges/claude-bridge/src/services/session-manager-commands.ts
10471
- import { randomUUID as randomUUID2 } from "node:crypto";
10553
+ import { randomUUID as randomUUID3 } from "node:crypto";
10472
10554
 
10473
10555
  // ../protocol/src/agent-command-catalogue.ts
10474
10556
  var NATIVE_AGENT_COMMAND_CATALOGUE_VERSION = 1;
@@ -11355,9 +11437,11 @@ async function configureClaudeSession(session, input) {
11355
11437
  }
11356
11438
  }
11357
11439
  async function gracefulInterruptClaudeSession(sessionId) {
11358
- const control = sessions.get(sessionId)?.queryControl;
11359
- if (!control?.interrupt)
11440
+ const session = sessions.get(sessionId);
11441
+ const control = session?.queryControl;
11442
+ if (!session || !control?.interrupt)
11360
11443
  return { interrupted: false, stillQueued: [] };
11444
+ session.interruptRequestedGeneration = session.latestTurnGeneration;
11361
11445
  try {
11362
11446
  const receipt = await control.interrupt();
11363
11447
  return { interrupted: true, stillQueued: receipt?.still_queued ?? [] };
@@ -11368,7 +11452,7 @@ async function gracefulInterruptClaudeSession(sessionId) {
11368
11452
  }
11369
11453
 
11370
11454
  // ../../bridges/claude-bridge/src/services/session-manager-commands.ts
11371
- var CATALOGUE_GENERATION = randomUUID2();
11455
+ var CATALOGUE_GENERATION = randomUUID3();
11372
11456
  var COMMAND_PROBE_TIMEOUT_MS = 30000;
11373
11457
  var MAX_LOCAL_COMMAND_OUTPUT_BYTES = 64 * 1024;
11374
11458
  var MAX_MESSAGE_BYTES = 512;
@@ -11980,7 +12064,7 @@ function createStaleMcpServerHook(mcp) {
11980
12064
  hookSpecificOutput: {
11981
12065
  hookEventName: "PreToolUse",
11982
12066
  permissionDecision: "deny",
11983
- permissionDecisionReason: `The MCP server "${server}" was removed from this session's configuration, so ${input.tool_name} is not available. Claude is still running background work in this session; the server unloads when that work finishes or the session is stopped.`
12067
+ permissionDecisionReason: `The MCP server "${server}" was removed from this session's configuration, so ${input.tool_name} is not available. This Claude process loaded it at launch and cannot unload it; it is gone once the process closes after an idle period, or when the session is stopped.`
11984
12068
  }
11985
12069
  };
11986
12070
  };
@@ -13600,6 +13684,10 @@ function claudeStartupFailureMessage(reason) {
13600
13684
  return "Anthropic refused your organization's required Claude settings for this sign-in. Sign in to Claude again; if that does not help, ask an administrator to allow Claude Code for this account.";
13601
13685
  }
13602
13686
  }
13687
+ var STOPPED_TERMINAL_REASONS = new Set([
13688
+ "aborted_streaming",
13689
+ "aborted_tools"
13690
+ ]);
13603
13691
  async function handleResultMessage(turn, message) {
13604
13692
  const { session, sessionId, stream } = turn;
13605
13693
  const resultMsg = message;
@@ -13642,6 +13730,9 @@ async function handleResultMessage(turn, message) {
13642
13730
  turn.diagnostics?.terminalSettled("resolved");
13643
13731
  turn.receivedResult = true;
13644
13732
  turn.receivedPromptResult = true;
13733
+ const interruptRequested = session.interruptRequestedGeneration === turn.turnGeneration;
13734
+ if (interruptRequested)
13735
+ session.interruptRequestedGeneration = undefined;
13645
13736
  debugLog("[session-manager] Query result", {
13646
13737
  sessionId,
13647
13738
  subtype: resultMsg.subtype,
@@ -13716,6 +13807,9 @@ async function handleResultMessage(turn, message) {
13716
13807
  }
13717
13808
  debugLog("[session-manager] Query completed successfully", { sessionId });
13718
13809
  finishTurnInputIfSettled(turn);
13810
+ } else if (!turn.options?.outputSchema && STOPPED_TERMINAL_REASONS.has(resultMsg.terminal_reason ?? "") && interruptRequested) {
13811
+ debugLog("[session-manager] Query stopped by the user", { sessionId });
13812
+ finishTurnInputIfSettled(turn);
13719
13813
  } else {
13720
13814
  console.error("[session-manager] Query error:", resultMsg.subtype, {
13721
13815
  sessionId,
@@ -14552,9 +14646,24 @@ function endClaudeRuntime(runtime, failure) {
14552
14646
  disarmClaudeRuntimeIdleDetach(runtime);
14553
14647
  runtime.input.close();
14554
14648
  const { session, control } = runtime;
14649
+ const killedBy = session.claudeRuntime === runtime && !runtime.abortController.signal.aborted ? killedClaudeProcessSignal(failure?.error) : undefined;
14555
14650
  if (session.claudeRuntime === runtime)
14556
14651
  session.claudeRuntime = undefined;
14557
14652
  const orphanedForeground = !runtime.waitingTurns.has(runtime.latestTurn) && session.queryControl === control && session.status === "running";
14653
+ const interruptedOutsidePrompt = killedBy && (orphanedForeground || session.abortController === undefined) && (session.status === "running" || (session.retainedContinuationRequestIds?.size ?? 0) > 0 || claudeRuntimeOwesWork(runtime));
14654
+ if (interruptedOutsidePrompt) {
14655
+ session.status = "error";
14656
+ session.error = claudeProcessKilledMessage(killedBy);
14657
+ session.interruption = "restart";
14658
+ session.turnStartedAt = undefined;
14659
+ session.abortController = undefined;
14660
+ session.completionBlockedByBackgroundTasks = false;
14661
+ eventEmitter.emit({
14662
+ type: "session.error",
14663
+ sessionId: session.id,
14664
+ data: { error: session.error }
14665
+ });
14666
+ }
14558
14667
  removeBackgroundTaskCandidatesOwnedBy(session, control);
14559
14668
  forgetSettlingBackgroundTasksOwnedBy(session, control);
14560
14669
  const settled = settleTasksOwnedByClosedControl(session, control, "The Claude session that owned this task ended before it reported a result");
@@ -14571,8 +14680,9 @@ function endClaudeRuntime(runtime, failure) {
14571
14680
  for (const turn of turns) {
14572
14681
  turn.runtimeEnded(turn === failedTurn && failure ? { error: failure.error } : undefined);
14573
14682
  }
14574
- if (orphanedForeground)
14683
+ if (orphanedForeground && !interruptedOutsidePrompt)
14575
14684
  releaseOrphanedForeground(runtime, failure?.error);
14685
+ syncTurnInFlightMark(session);
14576
14686
  }
14577
14687
  function releaseOrphanedForeground(runtime, error) {
14578
14688
  const { session } = runtime;
@@ -14697,6 +14807,7 @@ function markQueryStarted(turn, testHooks) {
14697
14807
  }, { once: true });
14698
14808
  session.finishTurnInputIfSettled = turn.finishTurnInputForThisTurn;
14699
14809
  turn.queryStarted = true;
14810
+ syncTurnInFlightMark(session, { started: true });
14700
14811
  testHooks?.onQueryStarted?.();
14701
14812
  readLiveCommandInventory(session, control);
14702
14813
  reportUltracodeRuntime(session, control);
@@ -14842,6 +14953,7 @@ function settleFailedTurn(turn, error) {
14842
14953
  session.turnStartedAt = turn.beforeStartup.turnStartedAt;
14843
14954
  session.abortController = turn.beforeStartup.abortController;
14844
14955
  session.error = turn.beforeStartup.error;
14956
+ session.interruption = turn.beforeStartup.interruption;
14845
14957
  session.inProgressUsage = turn.beforeStartup.inProgressUsage;
14846
14958
  session.inProgressUsageGeneration = turn.beforeStartup.inProgressUsageGeneration;
14847
14959
  const index = session.messages.indexOf(userMessage);
@@ -14859,12 +14971,17 @@ function settleFailedTurn(turn, error) {
14859
14971
  errorClass: diagnosticErrorClass(error)
14860
14972
  });
14861
14973
  if (session.abortController === abortController) {
14974
+ const killedBy = killedClaudeProcessSignal(error);
14975
+ const failure = killedBy ? claudeProcessKilledMessage(killedBy) : error instanceof Error ? error.message : String(error);
14862
14976
  if (options?.outputSchema && structuredRequestId && !session.structuredOutput) {
14863
- recordStructuredOutput(session, structuredOutputFailure("claude", "provider_error", error instanceof Error ? error.message : String(error), { requestId: structuredRequestId }));
14977
+ recordStructuredOutput(session, structuredOutputFailure("claude", "provider_error", failure, {
14978
+ requestId: structuredRequestId
14979
+ }));
14864
14980
  }
14865
14981
  session.status = "error";
14866
14982
  session.turnStartedAt = undefined;
14867
- session.error = error instanceof Error ? error.message : String(error);
14983
+ session.error = failure;
14984
+ session.interruption = killedBy ? "restart" : undefined;
14868
14985
  session.abortController = undefined;
14869
14986
  session.completionBlockedByBackgroundTasks = false;
14870
14987
  cleanupPendingInteractions(sessionId);
@@ -14904,6 +15021,9 @@ function finalizeTurn(turn) {
14904
15021
  diagnostics
14905
15022
  } = turn;
14906
15023
  turn.queryEnded = true;
15024
+ if (session.interruptRequestedGeneration === turnGeneration) {
15025
+ session.interruptRequestedGeneration = undefined;
15026
+ }
14907
15027
  diagnostics?.close();
14908
15028
  flushDebugLogs();
14909
15029
  abortController.signal.removeEventListener("abort", turn.observeAbort);
@@ -14952,6 +15072,8 @@ function finalizeTurn(turn) {
14952
15072
  }
14953
15073
  reconcileIdleClaudeRuntime(session);
14954
15074
  stream.clearFlushTimer();
15075
+ if (sessions.get(sessionId) === session)
15076
+ syncTurnInFlightMark(session);
14955
15077
  }
14956
15078
 
14957
15079
  // ../../bridges/claude-bridge/src/services/session-manager-prompt.ts
@@ -15032,6 +15154,7 @@ function closeSessionRetainingHistory(sessionId, options = {}) {
15032
15154
  throw new SessionCloseNotConfirmedError("A racing prompt dispatch did not settle");
15033
15155
  }
15034
15156
  forgetPromptDispatchesForSession(sessionId);
15157
+ clearTurnInFlightMarkForClose(session);
15035
15158
  if (sessions.get(sessionId) === session)
15036
15159
  sessions.delete(sessionId);
15037
15160
  return "closed";
@@ -15094,7 +15217,7 @@ config.get("/models", async (c) => {
15094
15217
  var config_default = config;
15095
15218
 
15096
15219
  // ../../bridges/claude-bridge/src/routes/session.ts
15097
- import { randomUUID as randomUUID3 } from "node:crypto";
15220
+ import { randomUUID as randomUUID4 } from "node:crypto";
15098
15221
 
15099
15222
  // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/middleware/body-limit/index.js
15100
15223
  var ERROR_MESSAGE = "Payload Too Large";
@@ -16040,7 +16163,7 @@ function registerSessionTranscriptRoutes(app, generation) {
16040
16163
 
16041
16164
  // ../../bridges/claude-bridge/src/routes/session.ts
16042
16165
  var session = new Hono2;
16043
- var TRANSCRIPT_GENERATION = randomUUID3();
16166
+ var TRANSCRIPT_GENERATION = randomUUID4();
16044
16167
  var MAX_IMAGE_ATTACHMENT_BYTES2 = 8 * 1024 * 1024;
16045
16168
  var MAX_QUESTION_ANSWER_REQUEST_BYTES = AGENT_INTERACTION_LIMITS.maxSerializedPayloadBytes + 1024;
16046
16169
  var MAX_CLAUDE_TRANSCRIPT_RESPONSE_BYTES = 16 * 1024 * 1024;
@@ -16182,6 +16305,7 @@ session.get("/:id", async (c) => {
16182
16305
  createdAt: sessionData.createdAt.toISOString(),
16183
16306
  lastActivity: sessionData.lastActivity.toISOString(),
16184
16307
  error: sessionData.error,
16308
+ ...sessionData.status === "error" && sessionData.interruption ? { interruption: sessionData.interruption } : {},
16185
16309
  structuredOutputRequestId: sessionData.structuredOutputRequestId,
16186
16310
  structuredOutput: sessionData.structuredOutput,
16187
16311
  contextUsage: sessionData.inProgressUsage ?? sessionData.usage,