remote-codex 0.11.26 → 0.11.28

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/README.md CHANGED
@@ -103,6 +103,18 @@ remote-codex relay-supervisor
103
103
  `45679` is used by default in copied setup commands to avoid common local
104
104
  `8787` port conflicts. You can change it if needed.
105
105
 
106
+ By default, `remote-codex relay-supervisor` starts itself inside a detached
107
+ `tmux` session so closing the terminal does not take the device offline. Manage
108
+ it with:
109
+
110
+ ```bash
111
+ remote-codex relay-supervisor status
112
+ remote-codex relay-supervisor stop
113
+ ```
114
+
115
+ If `tmux` is not installed it runs in the foreground. Use
116
+ `remote-codex relay-supervisor run` for explicit foreground/debug mode.
117
+
106
118
  ### Local Mode
107
119
 
108
120
  ```bash
@@ -247,6 +259,17 @@ remote-codex relay-supervisor
247
259
 
248
260
  复制命令默认使用 `45679`,用于避开本机常见的 `8787` 端口冲突;需要时可以手动换。
249
261
 
262
+ 默认情况下,`remote-codex relay-supervisor` 会尝试启动到 detached `tmux`
263
+ session 里,这样关闭终端窗口不会让设备下线。可以用下面命令管理:
264
+
265
+ ```bash
266
+ remote-codex relay-supervisor status
267
+ remote-codex relay-supervisor stop
268
+ ```
269
+
270
+ 如果设备没有安装 `tmux`,它会自动退回前台运行。需要显式前台调试时使用
271
+ `remote-codex relay-supervisor run`。
272
+
250
273
  ### 本地模式
251
274
 
252
275
  ```bash
@@ -10862,6 +10862,15 @@ var HIDDEN_ASK_USER_QUESTION_CONTINUATION_PREFIX = "The user answered the clarif
10862
10862
  var SUPPRESSED_ASSISTANT_TEXTS = /* @__PURE__ */ new Set([
10863
10863
  "No response requested."
10864
10864
  ]);
10865
+ var CLAUDE_LIMIT_ERROR_PATTERNS = [
10866
+ /\byou(?:'|’)ve hit your session limit\b/i,
10867
+ /\byou have hit your session limit\b/i,
10868
+ /\b(?:hit|reached|exceeded) (?:the )?(?:session|usage|rate) limit\b/i,
10869
+ /\b(?:session|usage|rate) limit (?:hit|reached|exceeded)\b/i,
10870
+ /\bquota exceeded\b/i,
10871
+ /\bcredit balance (?:is )?(?:too low|insufficient|exhausted)\b/i,
10872
+ /\binsufficient credits?\b/i
10873
+ ];
10865
10874
  function normalizedToolName(toolName) {
10866
10875
  return toolName.replace(/[\s_-]+/g, "").toLowerCase();
10867
10876
  }
@@ -11043,6 +11052,26 @@ function isHiddenContinuationMessage(message) {
11043
11052
  function shouldSuppressAssistantText(text2) {
11044
11053
  return SUPPRESSED_ASSISTANT_TEXTS.has(text2.trim());
11045
11054
  }
11055
+ function claudeLimitErrorMessage(text2) {
11056
+ const normalized = text2?.trim();
11057
+ if (!normalized) {
11058
+ return null;
11059
+ }
11060
+ return CLAUDE_LIMIT_ERROR_PATTERNS.some((pattern) => pattern.test(normalized)) ? normalized : null;
11061
+ }
11062
+ function limitErrorFromHistoryItems(items) {
11063
+ for (let index = items.length - 1; index >= 0; index -= 1) {
11064
+ const item = items[index];
11065
+ if (item?.kind !== "agentMessage") {
11066
+ continue;
11067
+ }
11068
+ const error = claudeLimitErrorMessage(item.text);
11069
+ if (error) {
11070
+ return error;
11071
+ }
11072
+ }
11073
+ return null;
11074
+ }
11046
11075
  function userMessageToHistoryItem(id, message) {
11047
11076
  return {
11048
11077
  id,
@@ -11182,6 +11211,28 @@ function toolResultBlocks(message) {
11182
11211
  };
11183
11212
  }).filter((block) => Boolean(block));
11184
11213
  }
11214
+ function xmlTagText(input, tagName) {
11215
+ const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i").exec(input);
11216
+ return match?.[1]?.trim() || null;
11217
+ }
11218
+ function decodeBasicXmlEntities(input) {
11219
+ return input.replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
11220
+ }
11221
+ function taskNotificationToolResult(message) {
11222
+ const text2 = messageContentText(message).trim();
11223
+ if (!text2.startsWith("<task-notification>") || !text2.includes("</task-notification>")) {
11224
+ return null;
11225
+ }
11226
+ const toolUseId = xmlTagText(text2, "tool-use-id");
11227
+ if (!toolUseId) {
11228
+ return null;
11229
+ }
11230
+ const result = xmlTagText(text2, "result") ?? xmlTagText(text2, "summary") ?? text2;
11231
+ return {
11232
+ toolUseId,
11233
+ result: decodeBasicXmlEntities(result)
11234
+ };
11235
+ }
11185
11236
  function suppressedClaudeToolUseIds(message) {
11186
11237
  const ids = /* @__PURE__ */ new Set();
11187
11238
  for (const block of contentBlocks(message)) {
@@ -11892,6 +11943,13 @@ function queryResultError(message) {
11892
11943
  }
11893
11944
  return message.errors?.join("\n") || message.stop_reason || "Claude turn failed.";
11894
11945
  }
11946
+ function statusForHistoricalItems(items) {
11947
+ const limitError = limitErrorFromHistoryItems(items);
11948
+ return {
11949
+ status: limitError ? "failed" : "completed",
11950
+ error: limitError
11951
+ };
11952
+ }
11895
11953
  function assistantMessagePayload(message) {
11896
11954
  return message.type === "assistant" ? message.message : null;
11897
11955
  }
@@ -12699,7 +12757,8 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
12699
12757
  this.deleteActiveTurn(state);
12700
12758
  this.emitUsage(state);
12701
12759
  const completedAt = (/* @__PURE__ */ new Date()).toISOString();
12702
- const status = state.interrupted ? "interrupted" : terminalStatus ?? "completed";
12760
+ const limitError = limitErrorFromHistoryItems(orderedItems(state));
12761
+ const status = state.interrupted ? "interrupted" : limitError ? "failed" : terminalStatus ?? "completed";
12703
12762
  this.emitRuntimeEvent({
12704
12763
  type: "turn.completed",
12705
12764
  provider: "claude",
@@ -12708,7 +12767,7 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
12708
12767
  providerTurnId: state.providerTurnId,
12709
12768
  startedAt: state.startedAt,
12710
12769
  status,
12711
- error: terminalError,
12770
+ error: limitError ?? terminalError,
12712
12771
  items: finalizeTurnItems(state, status, completedAt),
12713
12772
  rawTurn: rawMessages
12714
12773
  })
@@ -12801,6 +12860,18 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
12801
12860
  return;
12802
12861
  }
12803
12862
  if (message.type === "user") {
12863
+ const taskNotification = taskNotificationToolResult(message.message);
12864
+ if (taskNotification && !state.suppressedToolUseIds.has(taskNotification.toolUseId)) {
12865
+ const item = resultForToolUse({
12866
+ toolUseId: taskNotification.toolUseId,
12867
+ result: message.tool_use_result ?? taskNotification.result,
12868
+ previous: state.items.get(taskNotification.toolUseId) ?? null
12869
+ });
12870
+ const nextItem = withHistoryItemCreatedAt(item, messageCreatedAt);
12871
+ addOrUpdateItem(state, nextItem);
12872
+ this.emitItem(state, nextItem, "item.completed");
12873
+ return;
12874
+ }
12804
12875
  const rawToolResults = toolResultBlocks(message.message);
12805
12876
  const toolResults = rawToolResults.filter(
12806
12877
  (toolResult) => !state.suppressedToolUseIds.has(toolResult.toolUseId)
@@ -13108,6 +13179,24 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
13108
13179
  };
13109
13180
  for (const message of messages) {
13110
13181
  if (message.type === "user") {
13182
+ const taskNotification = taskNotificationToolResult(message.message);
13183
+ if (taskNotification) {
13184
+ if (suppressedToolUseIds.has(taskNotification.toolUseId)) {
13185
+ continue;
13186
+ }
13187
+ const previous = current?.itemsById.get(taskNotification.toolUseId) ?? null;
13188
+ upsertCurrentItem(
13189
+ withHistoryItemCreatedAt(
13190
+ resultForToolUse({
13191
+ toolUseId: taskNotification.toolUseId,
13192
+ result: message.tool_use_result ?? taskNotification.result,
13193
+ previous
13194
+ }),
13195
+ sessionMessageTimestamp(message) ?? current?.startedAt
13196
+ )
13197
+ );
13198
+ continue;
13199
+ }
13111
13200
  const rawToolResults = toolResultBlocks(message.message);
13112
13201
  const toolResults = rawToolResults.filter(
13113
13202
  (toolResult) => !suppressedToolUseIds.has(toolResult.toolUseId)
@@ -13143,10 +13232,12 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
13143
13232
  }
13144
13233
  skippingHiddenInit = false;
13145
13234
  if (current && current.items.length > 0) {
13235
+ const outcome = statusForHistoricalItems(current.items);
13146
13236
  turns.push(buildAgentTurn({
13147
13237
  providerTurnId: current.providerTurnId,
13148
13238
  startedAt: current.startedAt,
13149
- status: "completed",
13239
+ status: outcome.status,
13240
+ error: outcome.error,
13150
13241
  items: current.items
13151
13242
  }));
13152
13243
  }
@@ -13201,10 +13292,12 @@ var ClaudeRuntimeAdapter = class extends EventEmitter5 {
13201
13292
  }
13202
13293
  }
13203
13294
  if (current && current.items.length > 0) {
13295
+ const outcome = statusForHistoricalItems(current.items);
13204
13296
  turns.push(buildAgentTurn({
13205
13297
  providerTurnId: current.providerTurnId,
13206
13298
  startedAt: current.startedAt,
13207
- status: "completed",
13299
+ status: outcome.status,
13300
+ error: outcome.error,
13208
13301
  items: current.items
13209
13302
  }));
13210
13303
  }
@@ -17797,8 +17890,10 @@ var ThreadRuntimeEventProjector = class {
17797
17890
  }
17798
17891
  const turnId = liveState.displayTurnIdForRuntimeTurn(record.id, event.providerTurnId) ?? event.providerTurnId;
17799
17892
  updateThreadRecord(db, record.id, {
17893
+ providerTurnId: null,
17800
17894
  status: "failed",
17801
- lastError: event.error
17895
+ lastError: event.error,
17896
+ lastTurnCompletedAt: (/* @__PURE__ */ new Date()).toISOString()
17802
17897
  });
17803
17898
  liveState.setLivePlan(record.id, null);
17804
17899
  liveState.setLiveItems(record.id, null);
@@ -18267,6 +18362,9 @@ var ThreadDetailAssembler = class {
18267
18362
  input.record.model,
18268
18363
  input.record.reasoningEffort
18269
18364
  );
18365
+ const persistedItemsByTurnIdForPatch = this.input.callbacks.listPersistedHistoryItemsByTurnId(
18366
+ input.localThreadId
18367
+ );
18270
18368
  const activeDisplayTurnId = this.input.liveState.displayTurnIdForRuntimeTurn(
18271
18369
  input.localThreadId,
18272
18370
  input.record.providerTurnId
@@ -18278,7 +18376,20 @@ var ThreadDetailAssembler = class {
18278
18376
  if (input.record.providerTurnId && threadPatch.status === "idle" && activeLiveItems && activeLiveItems.items.length > 0) {
18279
18377
  threadPatch.status = "running";
18280
18378
  }
18281
- this.input.callbacks.updateThreadRecord(input.record.id, threadPatch);
18379
+ const latestPersistedFailure = latestPersistedFailureAfter(
18380
+ persistedItemsByTurnIdForPatch,
18381
+ input.turnMetadataById,
18382
+ newestRemoteTurnStartedAt(remoteSession.turns)
18383
+ );
18384
+ if (threadPatch.status !== "running" && latestPersistedFailure) {
18385
+ threadPatch.status = "failed";
18386
+ threadPatch.lastError = latestPersistedFailure.error;
18387
+ }
18388
+ const nextThreadPatch = {
18389
+ ...threadPatch,
18390
+ ...threadPatch.status !== "running" ? { providerTurnId: null } : {}
18391
+ };
18392
+ this.input.callbacks.updateThreadRecord(input.record.id, nextThreadPatch);
18282
18393
  const updated = this.input.callbacks.getUpdatedThreadRecord(input.record.id);
18283
18394
  this.input.callbacks.syncAfterRemoteSession(updated.id, remoteSession);
18284
18395
  const deferredDetails = /* @__PURE__ */ new Map();
@@ -18301,8 +18412,17 @@ var ThreadDetailAssembler = class {
18301
18412
  this.input.liveState,
18302
18413
  input.turnMetadataById
18303
18414
  );
18304
- const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
18415
+ const visibleTurnsWithPersistedFailures = appendPersistedFailureTurnsIfMissing(
18305
18416
  visibleTurnsWithActiveLiveTurn,
18417
+ persistedItemsByTurnId,
18418
+ input.turnMetadataById,
18419
+ {
18420
+ includeAllMissing: shouldCacheFullDetail,
18421
+ includeLatestMissing: options.beforeTurnId === void 0
18422
+ }
18423
+ );
18424
+ const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
18425
+ visibleTurnsWithPersistedFailures,
18306
18426
  input.localThreadId,
18307
18427
  this.input.liveState
18308
18428
  );
@@ -18348,13 +18468,16 @@ var ThreadDetailAssembler = class {
18348
18468
  input.record,
18349
18469
  latestThreadTurnMetadata(input.turnMetadataById)
18350
18470
  );
18351
- const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.keys()].map((turnId) => ({
18352
- id: turnId,
18353
- startedAt: null,
18354
- status: "completed",
18355
- error: null,
18356
- items: []
18357
- }));
18471
+ const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.entries()].map(([turnId, items]) => {
18472
+ const error = persistedTurnError(items);
18473
+ return {
18474
+ id: turnId,
18475
+ startedAt: input.turnMetadataById.get(turnId)?.createdAt ?? null,
18476
+ status: error ? "failed" : "completed",
18477
+ error,
18478
+ items: []
18479
+ };
18480
+ });
18358
18481
  const turns = mergePersistedHistoryItemsIntoTurns(
18359
18482
  applyRecordedTurnItemOrders(
18360
18483
  localTurns,
@@ -18453,6 +18576,87 @@ function appendActiveLiveTurnIfMissing(turns, localThreadId, providerTurnId, liv
18453
18576
  }
18454
18577
  ];
18455
18578
  }
18579
+ function appendPersistedFailureTurnsIfMissing(turns, persistedItemsByTurnId, metadataById, options) {
18580
+ if (persistedItemsByTurnId.size === 0) {
18581
+ return turns;
18582
+ }
18583
+ const existingTurnIds = new Set(turns.map((turn) => turn.id));
18584
+ const newestVisibleStartedAt = newestStartedAt(turns);
18585
+ const missingFailureTurns = [];
18586
+ for (const [turnId, items] of persistedItemsByTurnId.entries()) {
18587
+ if (existingTurnIds.has(turnId)) {
18588
+ continue;
18589
+ }
18590
+ const error = persistedTurnError(items);
18591
+ if (!error) {
18592
+ continue;
18593
+ }
18594
+ const startedAt = metadataById.get(turnId)?.createdAt ?? earliestItemCreatedAt(items);
18595
+ const shouldInclude = options.includeAllMissing || options.includeLatestMissing && (!newestVisibleStartedAt || !startedAt || startedAt >= newestVisibleStartedAt);
18596
+ if (!shouldInclude) {
18597
+ continue;
18598
+ }
18599
+ missingFailureTurns.push({
18600
+ id: turnId,
18601
+ startedAt,
18602
+ status: "failed",
18603
+ error,
18604
+ items: []
18605
+ });
18606
+ }
18607
+ if (missingFailureTurns.length === 0) {
18608
+ return turns;
18609
+ }
18610
+ return sortTurnsByStartedAt([...turns, ...missingFailureTurns]);
18611
+ }
18612
+ function persistedTurnError(items) {
18613
+ const failedItem = [...items].reverse().find(
18614
+ (item) => item.status === "failed" || item.status === "error"
18615
+ );
18616
+ return failedItem?.text?.trim() || null;
18617
+ }
18618
+ function earliestItemCreatedAt(items) {
18619
+ return items.map((item) => item.createdAt).filter((value) => Boolean(value)).sort()[0] ?? null;
18620
+ }
18621
+ function newestStartedAt(turns) {
18622
+ return turns.map((turn) => turn.startedAt).filter((value) => Boolean(value)).sort().at(-1) ?? null;
18623
+ }
18624
+ function newestRemoteTurnStartedAt(turns) {
18625
+ return turns.map((turn) => turn.startedAt).filter((value) => Boolean(value)).sort().at(-1) ?? null;
18626
+ }
18627
+ function latestPersistedFailureAfter(persistedItemsByTurnId, metadataById, timestamp) {
18628
+ let latest = null;
18629
+ for (const [turnId, items] of persistedItemsByTurnId.entries()) {
18630
+ const error = persistedTurnError(items);
18631
+ if (!error) {
18632
+ continue;
18633
+ }
18634
+ const startedAt = metadataById.get(turnId)?.createdAt ?? earliestItemCreatedAt(items);
18635
+ if (timestamp && startedAt && startedAt < timestamp) {
18636
+ continue;
18637
+ }
18638
+ if (!latest || !latest.startedAt && startedAt || latest.startedAt && startedAt && startedAt > latest.startedAt) {
18639
+ latest = { error, startedAt };
18640
+ }
18641
+ }
18642
+ return latest;
18643
+ }
18644
+ function sortTurnsByStartedAt(turns) {
18645
+ return turns.map((turn, index) => ({ turn, index })).sort((left, right) => {
18646
+ const leftStartedAt = left.turn.startedAt;
18647
+ const rightStartedAt = right.turn.startedAt;
18648
+ if (!leftStartedAt && !rightStartedAt) {
18649
+ return left.index - right.index;
18650
+ }
18651
+ if (!leftStartedAt) {
18652
+ return 1;
18653
+ }
18654
+ if (!rightStartedAt) {
18655
+ return -1;
18656
+ }
18657
+ return leftStartedAt.localeCompare(rightStartedAt) || left.index - right.index;
18658
+ }).map(({ turn }) => turn);
18659
+ }
18456
18660
  function buildTurnDto(turn, metadata) {
18457
18661
  const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
18458
18662
  const displayPrompt = metadata?.displayPrompt?.trim();
@@ -18906,10 +19110,20 @@ var ThreadManagementCoordinator = class {
18906
19110
  };
18907
19111
 
18908
19112
  // src/thread-prompt-turn-coordinator.ts
19113
+ import { randomUUID as randomUUID3 } from "crypto";
18909
19114
  function isAutoGeneratedTitle(title) {
18910
19115
  const normalized = title?.trim();
18911
19116
  return !normalized || normalized === "Untitled thread" || normalized === "Thread";
18912
19117
  }
19118
+ function errorMessage3(error) {
19119
+ return error instanceof Error ? error.message : String(error);
19120
+ }
19121
+ function promptStartFailureMessage(error, runtime, fallbackProvider) {
19122
+ const providerLabel = runtime.displayName || fallbackProvider || "Agent backend";
19123
+ const rawMessage = errorMessage3(error).trim() || "Unknown error.";
19124
+ const codeLabel = error instanceof AgentRuntimeError ? ` (${error.code})` : "";
19125
+ return `${providerLabel} failed to start this turn${codeLabel}: ${rawMessage}`;
19126
+ }
18913
19127
  var ThreadPromptTurnCoordinator = class {
18914
19128
  constructor(db, liveState, providerRuntime, callbacks) {
18915
19129
  this.db = db;
@@ -18952,7 +19166,22 @@ var ThreadPromptTurnCoordinator = class {
18952
19166
  if (input.displayTurnId) {
18953
19167
  startTurnInput.displayTurnId = input.displayTurnId;
18954
19168
  }
18955
- const turn = await runtime.startTurn(startTurnInput);
19169
+ let turn;
19170
+ try {
19171
+ turn = await runtime.startTurn(startTurnInput);
19172
+ } catch (error) {
19173
+ const failureMessage = promptStartFailureMessage(error, runtime, record.provider);
19174
+ this.persistPromptStartFailure(localThreadId, record, input, {
19175
+ displayPrompt,
19176
+ message: failureMessage,
19177
+ modelRecords,
19178
+ pricingSnapshot
19179
+ });
19180
+ throw new HttpError(503, {
19181
+ code: "service_unavailable",
19182
+ message: failureMessage
19183
+ });
19184
+ }
18956
19185
  const displayTurnId = input.displayTurnId ?? turn.providerTurnId;
18957
19186
  if (displayTurnId !== turn.providerTurnId) {
18958
19187
  this.liveState.setRuntimeDisplayTurnMapping(localThreadId, {
@@ -19005,6 +19234,80 @@ var ThreadPromptTurnCoordinator = class {
19005
19234
  const updated = getThreadRecordById(this.db, localThreadId);
19006
19235
  return this.callbacks.toThreadDto(updated, /* @__PURE__ */ new Set([record.providerSessionId]));
19007
19236
  }
19237
+ persistPromptStartFailure(localThreadId, record, input, failure) {
19238
+ const now = (/* @__PURE__ */ new Date()).toISOString();
19239
+ const displayTurnId = input.displayTurnId ?? `local-failed-${randomUUID3()}`;
19240
+ const items = input.hidden ? [
19241
+ {
19242
+ id: `${displayTurnId}:error`,
19243
+ kind: "agentMessage",
19244
+ text: failure.message,
19245
+ status: "failed",
19246
+ createdAt: now,
19247
+ transcriptOrder: 0,
19248
+ sourceTurnId: displayTurnId
19249
+ }
19250
+ ] : [
19251
+ {
19252
+ id: `${displayTurnId}:user`,
19253
+ kind: "userMessage",
19254
+ text: failure.displayPrompt,
19255
+ createdAt: now,
19256
+ transcriptOrder: 0
19257
+ },
19258
+ {
19259
+ id: `${displayTurnId}:error`,
19260
+ kind: "agentMessage",
19261
+ text: failure.message,
19262
+ status: "failed",
19263
+ createdAt: now,
19264
+ transcriptOrder: 1,
19265
+ sourceTurnId: displayTurnId
19266
+ }
19267
+ ];
19268
+ upsertThreadTurnMetadata(this.db, {
19269
+ threadId: localThreadId,
19270
+ turnId: displayTurnId,
19271
+ model: input.effectiveModel,
19272
+ reasoningEffort: input.normalizedReasoning,
19273
+ reasoningEffortAvailable: this.providerRuntime.reasoningEffortAvailableForModel(
19274
+ failure.modelRecords,
19275
+ input.effectiveModel
19276
+ ),
19277
+ pricingModelKey: failure.pricingSnapshot?.pricingModelKey ?? null,
19278
+ pricingTierKey: failure.pricingSnapshot?.pricingTierKey ?? null,
19279
+ displayPrompt: input.hidden ? null : failure.displayPrompt
19280
+ });
19281
+ for (const item of items) {
19282
+ upsertThreadHistoryItemRecord(this.db, {
19283
+ threadId: localThreadId,
19284
+ turnId: displayTurnId,
19285
+ itemId: item.id,
19286
+ itemJson: JSON.stringify(item)
19287
+ });
19288
+ }
19289
+ const patch = {
19290
+ providerTurnId: displayTurnId,
19291
+ status: "failed",
19292
+ lastError: failure.message,
19293
+ lastTurnStartedAt: now,
19294
+ lastTurnCompletedAt: now,
19295
+ model: input.effectiveModel,
19296
+ reasoningEffort: input.normalizedReasoning,
19297
+ collaborationMode: input.collaborationMode,
19298
+ sandboxMode: input.sandboxMode
19299
+ };
19300
+ if (!input.hidden) {
19301
+ patch.summaryText = failure.displayPrompt;
19302
+ if (isAutoGeneratedTitle(record.title)) {
19303
+ patch.title = truncateAutoThreadTitle(failure.displayPrompt);
19304
+ }
19305
+ }
19306
+ updateThreadRecord(this.db, localThreadId, patch);
19307
+ this.liveState.setLivePlan(localThreadId, null);
19308
+ this.liveState.setLiveItems(localThreadId, null);
19309
+ this.callbacks.invalidateThreadDetailCache(localThreadId);
19310
+ }
19008
19311
  async steerOrStartPromptTurn(localThreadId, record, input) {
19009
19312
  const runtime = this.callbacks.runtimeForProvider(record.provider);
19010
19313
  let steerTurnId = record.providerTurnId;
@@ -19115,19 +19418,21 @@ function normalizeCollaborationMode(value) {
19115
19418
  return value === "plan" ? "plan" : "default";
19116
19419
  }
19117
19420
  function buildThreadPatch(remoteSession, model, reasoningEffort) {
19118
- const failedTurn = "turns" in remoteSession ? remoteSession.turns.find((turn) => turn.status === "failed") : null;
19421
+ const latestTurn = "turns" in remoteSession ? remoteSession.turns.at(-1) ?? null : null;
19422
+ const latestStatus = latestTurn?.status === "failed" ? "failed" : latestTurn?.status === "interrupted" ? "interrupted" : remoteSession.status;
19119
19423
  return {
19120
19424
  provider: remoteSession.provider,
19121
19425
  providerSessionId: remoteSession.providerSessionId,
19122
- status: remoteSession.status,
19426
+ status: latestStatus,
19123
19427
  summaryText: remoteSession.preview || null,
19124
19428
  model: model ?? null,
19125
19429
  reasoningEffort: normalizeReasoningEffort2(reasoningEffort),
19126
- lastError: failedTurn?.error?.message ?? null,
19430
+ lastError: latestTurn?.status === "failed" ? latestTurn.error?.message ?? "Turn failed." : null,
19127
19431
  updatedAt: remoteSession.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
19128
19432
  };
19129
19433
  }
19130
19434
  function toThreadDto(record, loadedIds, callbacks) {
19435
+ const status = record.status ?? "idle";
19131
19436
  return {
19132
19437
  id: record.id,
19133
19438
  workspaceId: record.workspaceId,
@@ -19141,10 +19446,10 @@ function toThreadDto(record, loadedIds, callbacks) {
19141
19446
  collaborationMode: normalizeCollaborationMode(record.collaborationMode),
19142
19447
  approvalMode: record.approvalMode ?? "yolo",
19143
19448
  sandboxMode: normalizeSandboxMode(record.sandboxMode) ?? defaultSandboxModeForApprovalMode(record.approvalMode ?? "yolo"),
19144
- status: record.status ?? "idle",
19449
+ status,
19145
19450
  summaryText: record.summaryText ?? null,
19146
19451
  lastError: record.lastError ?? null,
19147
- activeTurnId: record.providerTurnId ?? null,
19452
+ activeTurnId: status === "running" ? record.providerTurnId ?? null : null,
19148
19453
  isLoaded: record.isConnected !== false && (record.providerSessionId ? loadedIds.has(record.providerSessionId) : false),
19149
19454
  isPinned: record.isPinned,
19150
19455
  createdAt: record.createdAt,
@@ -21187,7 +21492,7 @@ var ThreadForkCoordinator = class {
21187
21492
  };
21188
21493
 
21189
21494
  // src/thread-attachment-coordinator.ts
21190
- import { randomUUID as randomUUID3 } from "crypto";
21495
+ import { randomUUID as randomUUID4 } from "crypto";
21191
21496
  import fs12 from "fs/promises";
21192
21497
  import path16 from "path";
21193
21498
  async function pathExists(absPath) {
@@ -21205,7 +21510,7 @@ function sanitizeAttachmentFileName(originalName) {
21205
21510
  const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
21206
21511
  const stem = sanitizedStem || "attachment";
21207
21512
  const normalizedExtension = extension.slice(0, 16);
21208
- return `${stem}-${randomUUID3().slice(0, 8)}${normalizedExtension}`;
21513
+ return `${stem}-${randomUUID4().slice(0, 8)}${normalizedExtension}`;
21209
21514
  }
21210
21515
  function threadTempDirectoryPath2(workspacePath, localThreadId) {
21211
21516
  return path16.join(workspacePath, ".temp", "threads", localThreadId);
@@ -24910,7 +25215,7 @@ async function registerAuthRoutes(app2) {
24910
25215
  // src/provider-host-config-service.ts
24911
25216
  import fs20 from "fs/promises";
24912
25217
  import path23 from "path";
24913
- import { randomUUID as randomUUID4 } from "crypto";
25218
+ import { randomUUID as randomUUID5 } from "crypto";
24914
25219
  function providerError(message, statusCode = 404) {
24915
25220
  const error = new Error(message);
24916
25221
  error.statusCode = statusCode;
@@ -25058,7 +25363,7 @@ var ProviderHostConfigService = class {
25058
25363
  const providerHome = this.providerHome(provider2);
25059
25364
  const fileNames = this.archiveFileNames(provider2);
25060
25365
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
25061
- const id = `${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID4().slice(0, 8)}`;
25366
+ const id = `${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID5().slice(0, 8)}`;
25062
25367
  const archivePath = resolveArchivePath(providerHome, id);
25063
25368
  const files = Object.fromEntries(
25064
25369
  fileNames.map((name) => [