remote-codex 0.11.27 → 0.11.29

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
@@ -697,7 +697,9 @@ var RelayStore = class _RelayStore {
697
697
  device_id TEXT NOT NULL REFERENCES relay_devices(id) ON DELETE CASCADE,
698
698
  device_name TEXT,
699
699
  thread_id TEXT NOT NULL,
700
+ thread_title TEXT,
700
701
  workspace_id TEXT,
702
+ workspace_label TEXT,
701
703
  label TEXT,
702
704
  thread_access TEXT NOT NULL DEFAULT 'control',
703
705
  workspace_access TEXT NOT NULL DEFAULT 'none',
@@ -747,7 +749,9 @@ var RelayStore = class _RelayStore {
747
749
  `);
748
750
  this.ensureColumn("relay_users", "last_seen_at", "TEXT");
749
751
  this.ensureColumn("relay_devices", "token", "TEXT");
752
+ this.ensureColumn("relay_shares", "thread_title", "TEXT");
750
753
  this.ensureColumn("relay_shares", "workspace_id", "TEXT");
754
+ this.ensureColumn("relay_shares", "workspace_label", "TEXT");
751
755
  this.ensureColumn("relay_shares", "thread_access", "TEXT NOT NULL DEFAULT 'control'");
752
756
  this.ensureColumn("relay_shares", "workspace_access", "TEXT NOT NULL DEFAULT 'none'");
753
757
  this.ensureColumn("relay_shares", "expires_at", "TEXT");
@@ -934,9 +938,9 @@ var RelayStore = class _RelayStore {
934
938
  `
935
939
  INSERT INTO relay_shares (
936
940
  id, owner_user_id, owner_username, target_user_id, target_username,
937
- device_id, device_name, thread_id, workspace_id, label,
941
+ device_id, device_name, thread_id, thread_title, workspace_id, workspace_label, label,
938
942
  thread_access, workspace_access, created_at, revoked_at, expires_at
939
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
943
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
940
944
  `
941
945
  ).run(
942
946
  share.id,
@@ -947,7 +951,9 @@ var RelayStore = class _RelayStore {
947
951
  share.deviceId,
948
952
  share.deviceName,
949
953
  share.threadId,
954
+ share.threadTitle,
950
955
  share.workspaceId,
956
+ share.workspaceLabel,
951
957
  share.label,
952
958
  share.threadAccess,
953
959
  share.workspaceAccess,
@@ -956,6 +962,26 @@ var RelayStore = class _RelayStore {
956
962
  share.expiresAt
957
963
  );
958
964
  }
965
+ updateShareMetadata(shareId, input) {
966
+ const threadTitle = normalizeOptionalMetadata(input.threadTitle);
967
+ const workspaceLabel = normalizeOptionalMetadata(input.workspaceLabel);
968
+ if (threadTitle === void 0 && workspaceLabel === void 0) {
969
+ return this.rowToShare(
970
+ this.sqlite.prepare("SELECT * FROM relay_shares WHERE id = ?").get(shareId)
971
+ );
972
+ }
973
+ this.sqlite.prepare(
974
+ `
975
+ UPDATE relay_shares
976
+ SET thread_title = COALESCE(?, thread_title),
977
+ workspace_label = COALESCE(?, workspace_label)
978
+ WHERE id = ?
979
+ `
980
+ ).run(threadTitle ?? null, workspaceLabel ?? null, shareId);
981
+ return this.rowToShare(
982
+ this.sqlite.prepare("SELECT * FROM relay_shares WHERE id = ?").get(shareId)
983
+ );
984
+ }
959
985
  getShareAccessEvents(shareId) {
960
986
  return this.sqlite.prepare(
961
987
  `
@@ -1129,9 +1155,9 @@ var RelayStore = class _RelayStore {
1129
1155
  deviceId: row.device_id,
1130
1156
  deviceName: row.device_name ?? "Remote Codex device",
1131
1157
  threadId: row.thread_id,
1132
- threadTitle: null,
1158
+ threadTitle: row.thread_title ?? null,
1133
1159
  workspaceId: row.workspace_id ?? null,
1134
- workspaceLabel: null,
1160
+ workspaceLabel: row.workspace_label ?? null,
1135
1161
  label: row.label,
1136
1162
  threadAccess: normalizeThreadAccess(row.thread_access),
1137
1163
  workspaceAccess: normalizeWorkspaceAccess(row.workspace_access),
@@ -1186,6 +1212,12 @@ function normalizeExpiresAt(value) {
1186
1212
  const timestamp = Date.parse(value);
1187
1213
  return Number.isNaN(timestamp) ? null : new Date(timestamp).toISOString();
1188
1214
  }
1215
+ function normalizeOptionalMetadata(value) {
1216
+ if (value === void 0) {
1217
+ return void 0;
1218
+ }
1219
+ return value?.trim() || void 0;
1220
+ }
1189
1221
  function normalizeConversationWindowDays(value) {
1190
1222
  if (!Number.isFinite(value ?? NaN)) {
1191
1223
  return 7;
@@ -1461,7 +1493,7 @@ function buildRelayServer(config2, options = {}) {
1461
1493
  if (!user) {
1462
1494
  return;
1463
1495
  }
1464
- return enrichPortalSummary(store.portalSummary(user.id, connectionStatus(state)), state);
1496
+ return enrichPortalSummary(store.portalSummary(user.id, connectionStatus(state)), state, store);
1465
1497
  });
1466
1498
  app2.get("/relay/access", async (request, reply) => {
1467
1499
  const user = requireRelayUser(request, reply, store);
@@ -2233,13 +2265,16 @@ async function fetchRelayThreads(supervisor, deviceId) {
2233
2265
  }
2234
2266
  return threads.slice(0, 80);
2235
2267
  }
2236
- async function enrichPortalSummary(portal, state) {
2268
+ async function enrichPortalSummary(portal, state, store) {
2237
2269
  const threadCache = /* @__PURE__ */ new Map();
2238
2270
  const workspaceCache = /* @__PURE__ */ new Map();
2239
2271
  const enrichShare = async (share) => {
2240
2272
  const supervisor = state.supervisors.get(share.deviceId);
2241
2273
  if (!supervisor || supervisor.socket.readyState !== WEBSOCKET_OPEN) {
2242
- return share;
2274
+ return {
2275
+ ...share,
2276
+ threadTitle: stableShareThreadTitle(share)
2277
+ };
2243
2278
  }
2244
2279
  const threadCacheKey = `${share.deviceId}:${share.threadId}`;
2245
2280
  let threadTitlePromise = threadCache.get(threadCacheKey);
@@ -2266,10 +2301,16 @@ async function enrichPortalSummary(portal, state) {
2266
2301
  threadTitlePromise,
2267
2302
  workspaceLabelPromise
2268
2303
  ]);
2304
+ if (threadTitle || workspaceLabel) {
2305
+ store.updateShareMetadata(share.id, {
2306
+ threadTitle,
2307
+ workspaceLabel
2308
+ });
2309
+ }
2269
2310
  return {
2270
2311
  ...share,
2271
- threadTitle,
2272
- workspaceLabel
2312
+ threadTitle: threadTitle ?? stableShareThreadTitle(share),
2313
+ workspaceLabel: workspaceLabel ?? share.workspaceLabel
2273
2314
  };
2274
2315
  };
2275
2316
  const [sharedWithMe, sharedByMe] = await Promise.all([
@@ -2295,6 +2336,14 @@ async function fetchRelayWorkspaceLabel(supervisor, deviceId, workspaceId) {
2295
2336
  const payload = await forwardSupervisorJson(supervisor, deviceId, `/api/workspaces/${encodeURIComponent(workspaceId)}`);
2296
2337
  return stringField(payload, "label");
2297
2338
  }
2339
+ function stableShareThreadTitle(share) {
2340
+ const threadTitle = share.threadTitle?.trim();
2341
+ if (!threadTitle) {
2342
+ return null;
2343
+ }
2344
+ const label = share.label?.trim();
2345
+ return label && threadTitle === label ? null : threadTitle;
2346
+ }
2298
2347
  async function forwardSupervisorJson(supervisor, deviceId, targetPath, options = {}) {
2299
2348
  try {
2300
2349
  const response = await supervisor.requestBroker.forward(
@@ -18362,6 +18362,9 @@ var ThreadDetailAssembler = class {
18362
18362
  input.record.model,
18363
18363
  input.record.reasoningEffort
18364
18364
  );
18365
+ const persistedItemsByTurnIdForPatch = this.input.callbacks.listPersistedHistoryItemsByTurnId(
18366
+ input.localThreadId
18367
+ );
18365
18368
  const activeDisplayTurnId = this.input.liveState.displayTurnIdForRuntimeTurn(
18366
18369
  input.localThreadId,
18367
18370
  input.record.providerTurnId
@@ -18373,6 +18376,16 @@ var ThreadDetailAssembler = class {
18373
18376
  if (input.record.providerTurnId && threadPatch.status === "idle" && activeLiveItems && activeLiveItems.items.length > 0) {
18374
18377
  threadPatch.status = "running";
18375
18378
  }
18379
+ const latestPersistedFailure = latestPersistedFailureAfter(
18380
+ persistedItemsByTurnIdForPatch,
18381
+ input.turnMetadataById,
18382
+ newestRemoteTurnStartedAt(remoteSession.turns),
18383
+ new Set(remoteSession.turns.map((turn) => turn.providerTurnId))
18384
+ );
18385
+ if (threadPatch.status !== "running" && latestPersistedFailure) {
18386
+ threadPatch.status = "failed";
18387
+ threadPatch.lastError = latestPersistedFailure.error;
18388
+ }
18376
18389
  const nextThreadPatch = {
18377
18390
  ...threadPatch,
18378
18391
  ...threadPatch.status !== "running" ? { providerTurnId: null } : {}
@@ -18400,8 +18413,17 @@ var ThreadDetailAssembler = class {
18400
18413
  this.input.liveState,
18401
18414
  input.turnMetadataById
18402
18415
  );
18403
- const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
18416
+ const visibleTurnsWithPersistedFailures = appendPersistedFailureTurnsIfMissing(
18404
18417
  visibleTurnsWithActiveLiveTurn,
18418
+ persistedItemsByTurnId,
18419
+ input.turnMetadataById,
18420
+ {
18421
+ includeAllMissing: shouldCacheFullDetail,
18422
+ includeLatestMissing: options.beforeTurnId === void 0
18423
+ }
18424
+ );
18425
+ const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
18426
+ visibleTurnsWithPersistedFailures,
18405
18427
  input.localThreadId,
18406
18428
  this.input.liveState
18407
18429
  );
@@ -18447,13 +18469,16 @@ var ThreadDetailAssembler = class {
18447
18469
  input.record,
18448
18470
  latestThreadTurnMetadata(input.turnMetadataById)
18449
18471
  );
18450
- const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.keys()].map((turnId) => ({
18451
- id: turnId,
18452
- startedAt: null,
18453
- status: "completed",
18454
- error: null,
18455
- items: []
18456
- }));
18472
+ const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.entries()].map(([turnId, items]) => {
18473
+ const error = persistedTurnError(items);
18474
+ return {
18475
+ id: turnId,
18476
+ startedAt: input.turnMetadataById.get(turnId)?.createdAt ?? null,
18477
+ status: error ? "failed" : "completed",
18478
+ error,
18479
+ items: []
18480
+ };
18481
+ });
18457
18482
  const turns = mergePersistedHistoryItemsIntoTurns(
18458
18483
  applyRecordedTurnItemOrders(
18459
18484
  localTurns,
@@ -18552,6 +18577,93 @@ function appendActiveLiveTurnIfMissing(turns, localThreadId, providerTurnId, liv
18552
18577
  }
18553
18578
  ];
18554
18579
  }
18580
+ function appendPersistedFailureTurnsIfMissing(turns, persistedItemsByTurnId, metadataById, options) {
18581
+ if (persistedItemsByTurnId.size === 0) {
18582
+ return turns;
18583
+ }
18584
+ const existingTurnIds = new Set(turns.map((turn) => turn.id));
18585
+ const newestVisibleStartedAt = newestStartedAt(turns);
18586
+ const missingFailureTurns = [];
18587
+ for (const [turnId, items] of persistedItemsByTurnId.entries()) {
18588
+ if (existingTurnIds.has(turnId)) {
18589
+ continue;
18590
+ }
18591
+ const error = persistedTurnError(items);
18592
+ if (!error) {
18593
+ continue;
18594
+ }
18595
+ const startedAt = metadataById.get(turnId)?.createdAt ?? earliestItemCreatedAt(items);
18596
+ const shouldInclude = options.includeAllMissing || options.includeLatestMissing && (!newestVisibleStartedAt || !startedAt || startedAt >= newestVisibleStartedAt);
18597
+ if (!shouldInclude) {
18598
+ continue;
18599
+ }
18600
+ missingFailureTurns.push({
18601
+ id: turnId,
18602
+ startedAt,
18603
+ status: "failed",
18604
+ error,
18605
+ items: []
18606
+ });
18607
+ }
18608
+ if (missingFailureTurns.length === 0) {
18609
+ return turns;
18610
+ }
18611
+ return sortTurnsByStartedAt([...turns, ...missingFailureTurns]);
18612
+ }
18613
+ function persistedTurnError(items) {
18614
+ const failedItem = [...items].reverse().find(
18615
+ (item) => item.status === "failed" || item.status === "error"
18616
+ );
18617
+ return failedItem?.text?.trim() || null;
18618
+ }
18619
+ function earliestItemCreatedAt(items) {
18620
+ return items.map((item) => item.createdAt).filter((value) => Boolean(value)).sort()[0] ?? null;
18621
+ }
18622
+ function newestStartedAt(turns) {
18623
+ return turns.map((turn) => turn.startedAt).filter((value) => Boolean(value)).sort().at(-1) ?? null;
18624
+ }
18625
+ function newestRemoteTurnStartedAt(turns) {
18626
+ return turns.map((turn) => turn.startedAt).filter((value) => Boolean(value)).sort().at(-1) ?? null;
18627
+ }
18628
+ function latestPersistedFailureAfter(persistedItemsByTurnId, metadataById, timestamp, remoteTurnIds) {
18629
+ let latest = null;
18630
+ for (const [turnId, items] of persistedItemsByTurnId.entries()) {
18631
+ if (persistedItemsBelongToRemoteTurn(turnId, items, remoteTurnIds)) {
18632
+ continue;
18633
+ }
18634
+ const error = persistedTurnError(items);
18635
+ if (!error) {
18636
+ continue;
18637
+ }
18638
+ const startedAt = metadataById.get(turnId)?.createdAt ?? earliestItemCreatedAt(items);
18639
+ if (timestamp && startedAt && startedAt < timestamp) {
18640
+ continue;
18641
+ }
18642
+ if (!latest || !latest.startedAt && startedAt || latest.startedAt && startedAt && startedAt > latest.startedAt) {
18643
+ latest = { error, startedAt };
18644
+ }
18645
+ }
18646
+ return latest;
18647
+ }
18648
+ function persistedItemsBelongToRemoteTurn(turnId, items, remoteTurnIds) {
18649
+ return remoteTurnIds.has(turnId) || items.some((item) => item.sourceTurnId && remoteTurnIds.has(item.sourceTurnId));
18650
+ }
18651
+ function sortTurnsByStartedAt(turns) {
18652
+ return turns.map((turn, index) => ({ turn, index })).sort((left, right) => {
18653
+ const leftStartedAt = left.turn.startedAt;
18654
+ const rightStartedAt = right.turn.startedAt;
18655
+ if (!leftStartedAt && !rightStartedAt) {
18656
+ return left.index - right.index;
18657
+ }
18658
+ if (!leftStartedAt) {
18659
+ return 1;
18660
+ }
18661
+ if (!rightStartedAt) {
18662
+ return -1;
18663
+ }
18664
+ return leftStartedAt.localeCompare(rightStartedAt) || left.index - right.index;
18665
+ }).map(({ turn }) => turn);
18666
+ }
18555
18667
  function buildTurnDto(turn, metadata) {
18556
18668
  const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
18557
18669
  const displayPrompt = metadata?.displayPrompt?.trim();
@@ -19005,10 +19117,20 @@ var ThreadManagementCoordinator = class {
19005
19117
  };
19006
19118
 
19007
19119
  // src/thread-prompt-turn-coordinator.ts
19120
+ import { randomUUID as randomUUID3 } from "crypto";
19008
19121
  function isAutoGeneratedTitle(title) {
19009
19122
  const normalized = title?.trim();
19010
19123
  return !normalized || normalized === "Untitled thread" || normalized === "Thread";
19011
19124
  }
19125
+ function errorMessage3(error) {
19126
+ return error instanceof Error ? error.message : String(error);
19127
+ }
19128
+ function promptStartFailureMessage(error, runtime, fallbackProvider) {
19129
+ const providerLabel = runtime.displayName || fallbackProvider || "Agent backend";
19130
+ const rawMessage = errorMessage3(error).trim() || "Unknown error.";
19131
+ const codeLabel = error instanceof AgentRuntimeError ? ` (${error.code})` : "";
19132
+ return `${providerLabel} failed to start this turn${codeLabel}: ${rawMessage}`;
19133
+ }
19012
19134
  var ThreadPromptTurnCoordinator = class {
19013
19135
  constructor(db, liveState, providerRuntime, callbacks) {
19014
19136
  this.db = db;
@@ -19051,7 +19173,22 @@ var ThreadPromptTurnCoordinator = class {
19051
19173
  if (input.displayTurnId) {
19052
19174
  startTurnInput.displayTurnId = input.displayTurnId;
19053
19175
  }
19054
- const turn = await runtime.startTurn(startTurnInput);
19176
+ let turn;
19177
+ try {
19178
+ turn = await runtime.startTurn(startTurnInput);
19179
+ } catch (error) {
19180
+ const failureMessage = promptStartFailureMessage(error, runtime, record.provider);
19181
+ this.persistPromptStartFailure(localThreadId, record, input, {
19182
+ displayPrompt,
19183
+ message: failureMessage,
19184
+ modelRecords,
19185
+ pricingSnapshot
19186
+ });
19187
+ throw new HttpError(503, {
19188
+ code: "service_unavailable",
19189
+ message: failureMessage
19190
+ });
19191
+ }
19055
19192
  const displayTurnId = input.displayTurnId ?? turn.providerTurnId;
19056
19193
  if (displayTurnId !== turn.providerTurnId) {
19057
19194
  this.liveState.setRuntimeDisplayTurnMapping(localThreadId, {
@@ -19104,6 +19241,80 @@ var ThreadPromptTurnCoordinator = class {
19104
19241
  const updated = getThreadRecordById(this.db, localThreadId);
19105
19242
  return this.callbacks.toThreadDto(updated, /* @__PURE__ */ new Set([record.providerSessionId]));
19106
19243
  }
19244
+ persistPromptStartFailure(localThreadId, record, input, failure) {
19245
+ const now = (/* @__PURE__ */ new Date()).toISOString();
19246
+ const displayTurnId = input.displayTurnId ?? `local-failed-${randomUUID3()}`;
19247
+ const items = input.hidden ? [
19248
+ {
19249
+ id: `${displayTurnId}:error`,
19250
+ kind: "agentMessage",
19251
+ text: failure.message,
19252
+ status: "failed",
19253
+ createdAt: now,
19254
+ transcriptOrder: 0,
19255
+ sourceTurnId: displayTurnId
19256
+ }
19257
+ ] : [
19258
+ {
19259
+ id: `${displayTurnId}:user`,
19260
+ kind: "userMessage",
19261
+ text: failure.displayPrompt,
19262
+ createdAt: now,
19263
+ transcriptOrder: 0
19264
+ },
19265
+ {
19266
+ id: `${displayTurnId}:error`,
19267
+ kind: "agentMessage",
19268
+ text: failure.message,
19269
+ status: "failed",
19270
+ createdAt: now,
19271
+ transcriptOrder: 1,
19272
+ sourceTurnId: displayTurnId
19273
+ }
19274
+ ];
19275
+ upsertThreadTurnMetadata(this.db, {
19276
+ threadId: localThreadId,
19277
+ turnId: displayTurnId,
19278
+ model: input.effectiveModel,
19279
+ reasoningEffort: input.normalizedReasoning,
19280
+ reasoningEffortAvailable: this.providerRuntime.reasoningEffortAvailableForModel(
19281
+ failure.modelRecords,
19282
+ input.effectiveModel
19283
+ ),
19284
+ pricingModelKey: failure.pricingSnapshot?.pricingModelKey ?? null,
19285
+ pricingTierKey: failure.pricingSnapshot?.pricingTierKey ?? null,
19286
+ displayPrompt: input.hidden ? null : failure.displayPrompt
19287
+ });
19288
+ for (const item of items) {
19289
+ upsertThreadHistoryItemRecord(this.db, {
19290
+ threadId: localThreadId,
19291
+ turnId: displayTurnId,
19292
+ itemId: item.id,
19293
+ itemJson: JSON.stringify(item)
19294
+ });
19295
+ }
19296
+ const patch = {
19297
+ providerTurnId: displayTurnId,
19298
+ status: "failed",
19299
+ lastError: failure.message,
19300
+ lastTurnStartedAt: now,
19301
+ lastTurnCompletedAt: now,
19302
+ model: input.effectiveModel,
19303
+ reasoningEffort: input.normalizedReasoning,
19304
+ collaborationMode: input.collaborationMode,
19305
+ sandboxMode: input.sandboxMode
19306
+ };
19307
+ if (!input.hidden) {
19308
+ patch.summaryText = failure.displayPrompt;
19309
+ if (isAutoGeneratedTitle(record.title)) {
19310
+ patch.title = truncateAutoThreadTitle(failure.displayPrompt);
19311
+ }
19312
+ }
19313
+ updateThreadRecord(this.db, localThreadId, patch);
19314
+ this.liveState.setLivePlan(localThreadId, null);
19315
+ this.liveState.setLiveItems(localThreadId, null);
19316
+ this.callbacks.invalidateThreadDetailCache(localThreadId);
19317
+ }
19107
19318
  async steerOrStartPromptTurn(localThreadId, record, input) {
19108
19319
  const runtime = this.callbacks.runtimeForProvider(record.provider);
19109
19320
  let steerTurnId = record.providerTurnId;
@@ -19214,15 +19425,16 @@ function normalizeCollaborationMode(value) {
19214
19425
  return value === "plan" ? "plan" : "default";
19215
19426
  }
19216
19427
  function buildThreadPatch(remoteSession, model, reasoningEffort) {
19217
- const failedTurn = "turns" in remoteSession ? remoteSession.turns.find((turn) => turn.status === "failed") : null;
19428
+ const latestTurn = "turns" in remoteSession ? remoteSession.turns.at(-1) ?? null : null;
19429
+ const latestStatus = latestTurn?.status === "failed" ? "failed" : latestTurn?.status === "interrupted" ? "interrupted" : remoteSession.status;
19218
19430
  return {
19219
19431
  provider: remoteSession.provider,
19220
19432
  providerSessionId: remoteSession.providerSessionId,
19221
- status: remoteSession.status,
19433
+ status: latestStatus,
19222
19434
  summaryText: remoteSession.preview || null,
19223
19435
  model: model ?? null,
19224
19436
  reasoningEffort: normalizeReasoningEffort2(reasoningEffort),
19225
- lastError: failedTurn?.error?.message ?? null,
19437
+ lastError: latestTurn?.status === "failed" ? latestTurn.error?.message ?? "Turn failed." : null,
19226
19438
  updatedAt: remoteSession.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
19227
19439
  };
19228
19440
  }
@@ -21287,7 +21499,7 @@ var ThreadForkCoordinator = class {
21287
21499
  };
21288
21500
 
21289
21501
  // src/thread-attachment-coordinator.ts
21290
- import { randomUUID as randomUUID3 } from "crypto";
21502
+ import { randomUUID as randomUUID4 } from "crypto";
21291
21503
  import fs12 from "fs/promises";
21292
21504
  import path16 from "path";
21293
21505
  async function pathExists(absPath) {
@@ -21305,7 +21517,7 @@ function sanitizeAttachmentFileName(originalName) {
21305
21517
  const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
21306
21518
  const stem = sanitizedStem || "attachment";
21307
21519
  const normalizedExtension = extension.slice(0, 16);
21308
- return `${stem}-${randomUUID3().slice(0, 8)}${normalizedExtension}`;
21520
+ return `${stem}-${randomUUID4().slice(0, 8)}${normalizedExtension}`;
21309
21521
  }
21310
21522
  function threadTempDirectoryPath2(workspacePath, localThreadId) {
21311
21523
  return path16.join(workspacePath, ".temp", "threads", localThreadId);
@@ -25010,7 +25222,7 @@ async function registerAuthRoutes(app2) {
25010
25222
  // src/provider-host-config-service.ts
25011
25223
  import fs20 from "fs/promises";
25012
25224
  import path23 from "path";
25013
- import { randomUUID as randomUUID4 } from "crypto";
25225
+ import { randomUUID as randomUUID5 } from "crypto";
25014
25226
  function providerError(message, statusCode = 404) {
25015
25227
  const error = new Error(message);
25016
25228
  error.statusCode = statusCode;
@@ -25158,7 +25370,7 @@ var ProviderHostConfigService = class {
25158
25370
  const providerHome = this.providerHome(provider2);
25159
25371
  const fileNames = this.archiveFileNames(provider2);
25160
25372
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
25161
- const id = `${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID4().slice(0, 8)}`;
25373
+ const id = `${createdAt.replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID5().slice(0, 8)}`;
25162
25374
  const archivePath = resolveArchivePath(providerHome, id);
25163
25375
  const files = Object.fromEntries(
25164
25376
  fileNames.map((name) => [