remote-codex 0.11.27 → 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
@@ -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,15 @@ 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
+ );
18384
+ if (threadPatch.status !== "running" && latestPersistedFailure) {
18385
+ threadPatch.status = "failed";
18386
+ threadPatch.lastError = latestPersistedFailure.error;
18387
+ }
18376
18388
  const nextThreadPatch = {
18377
18389
  ...threadPatch,
18378
18390
  ...threadPatch.status !== "running" ? { providerTurnId: null } : {}
@@ -18400,8 +18412,17 @@ var ThreadDetailAssembler = class {
18400
18412
  this.input.liveState,
18401
18413
  input.turnMetadataById
18402
18414
  );
18403
- const orderedVisibleTurns = applyLiveAgentMessageOrderingHints(
18415
+ const visibleTurnsWithPersistedFailures = appendPersistedFailureTurnsIfMissing(
18404
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,
18405
18426
  input.localThreadId,
18406
18427
  this.input.liveState
18407
18428
  );
@@ -18447,13 +18468,16 @@ var ThreadDetailAssembler = class {
18447
18468
  input.record,
18448
18469
  latestThreadTurnMetadata(input.turnMetadataById)
18449
18470
  );
18450
- const localTurns = localSession?.turns ?? [...persistedItemsByTurnId.keys()].map((turnId) => ({
18451
- id: turnId,
18452
- startedAt: null,
18453
- status: "completed",
18454
- error: null,
18455
- items: []
18456
- }));
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
+ });
18457
18481
  const turns = mergePersistedHistoryItemsIntoTurns(
18458
18482
  applyRecordedTurnItemOrders(
18459
18483
  localTurns,
@@ -18552,6 +18576,87 @@ function appendActiveLiveTurnIfMissing(turns, localThreadId, providerTurnId, liv
18552
18576
  }
18553
18577
  ];
18554
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
+ }
18555
18660
  function buildTurnDto(turn, metadata) {
18556
18661
  const tokenUsage = parseThreadTurnTokenUsageJson(metadata?.tokenUsageJson);
18557
18662
  const displayPrompt = metadata?.displayPrompt?.trim();
@@ -19005,10 +19110,20 @@ var ThreadManagementCoordinator = class {
19005
19110
  };
19006
19111
 
19007
19112
  // src/thread-prompt-turn-coordinator.ts
19113
+ import { randomUUID as randomUUID3 } from "crypto";
19008
19114
  function isAutoGeneratedTitle(title) {
19009
19115
  const normalized = title?.trim();
19010
19116
  return !normalized || normalized === "Untitled thread" || normalized === "Thread";
19011
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
+ }
19012
19127
  var ThreadPromptTurnCoordinator = class {
19013
19128
  constructor(db, liveState, providerRuntime, callbacks) {
19014
19129
  this.db = db;
@@ -19051,7 +19166,22 @@ var ThreadPromptTurnCoordinator = class {
19051
19166
  if (input.displayTurnId) {
19052
19167
  startTurnInput.displayTurnId = input.displayTurnId;
19053
19168
  }
19054
- 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
+ }
19055
19185
  const displayTurnId = input.displayTurnId ?? turn.providerTurnId;
19056
19186
  if (displayTurnId !== turn.providerTurnId) {
19057
19187
  this.liveState.setRuntimeDisplayTurnMapping(localThreadId, {
@@ -19104,6 +19234,80 @@ var ThreadPromptTurnCoordinator = class {
19104
19234
  const updated = getThreadRecordById(this.db, localThreadId);
19105
19235
  return this.callbacks.toThreadDto(updated, /* @__PURE__ */ new Set([record.providerSessionId]));
19106
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
+ }
19107
19311
  async steerOrStartPromptTurn(localThreadId, record, input) {
19108
19312
  const runtime = this.callbacks.runtimeForProvider(record.provider);
19109
19313
  let steerTurnId = record.providerTurnId;
@@ -19214,15 +19418,16 @@ function normalizeCollaborationMode(value) {
19214
19418
  return value === "plan" ? "plan" : "default";
19215
19419
  }
19216
19420
  function buildThreadPatch(remoteSession, model, reasoningEffort) {
19217
- 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;
19218
19423
  return {
19219
19424
  provider: remoteSession.provider,
19220
19425
  providerSessionId: remoteSession.providerSessionId,
19221
- status: remoteSession.status,
19426
+ status: latestStatus,
19222
19427
  summaryText: remoteSession.preview || null,
19223
19428
  model: model ?? null,
19224
19429
  reasoningEffort: normalizeReasoningEffort2(reasoningEffort),
19225
- lastError: failedTurn?.error?.message ?? null,
19430
+ lastError: latestTurn?.status === "failed" ? latestTurn.error?.message ?? "Turn failed." : null,
19226
19431
  updatedAt: remoteSession.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
19227
19432
  };
19228
19433
  }
@@ -21287,7 +21492,7 @@ var ThreadForkCoordinator = class {
21287
21492
  };
21288
21493
 
21289
21494
  // src/thread-attachment-coordinator.ts
21290
- import { randomUUID as randomUUID3 } from "crypto";
21495
+ import { randomUUID as randomUUID4 } from "crypto";
21291
21496
  import fs12 from "fs/promises";
21292
21497
  import path16 from "path";
21293
21498
  async function pathExists(absPath) {
@@ -21305,7 +21510,7 @@ function sanitizeAttachmentFileName(originalName) {
21305
21510
  const sanitizedStem = rawStem.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
21306
21511
  const stem = sanitizedStem || "attachment";
21307
21512
  const normalizedExtension = extension.slice(0, 16);
21308
- return `${stem}-${randomUUID3().slice(0, 8)}${normalizedExtension}`;
21513
+ return `${stem}-${randomUUID4().slice(0, 8)}${normalizedExtension}`;
21309
21514
  }
21310
21515
  function threadTempDirectoryPath2(workspacePath, localThreadId) {
21311
21516
  return path16.join(workspacePath, ".temp", "threads", localThreadId);
@@ -25010,7 +25215,7 @@ async function registerAuthRoutes(app2) {
25010
25215
  // src/provider-host-config-service.ts
25011
25216
  import fs20 from "fs/promises";
25012
25217
  import path23 from "path";
25013
- import { randomUUID as randomUUID4 } from "crypto";
25218
+ import { randomUUID as randomUUID5 } from "crypto";
25014
25219
  function providerError(message, statusCode = 404) {
25015
25220
  const error = new Error(message);
25016
25221
  error.statusCode = statusCode;
@@ -25158,7 +25363,7 @@ var ProviderHostConfigService = class {
25158
25363
  const providerHome = this.providerHome(provider2);
25159
25364
  const fileNames = this.archiveFileNames(provider2);
25160
25365
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
25161
- 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)}`;
25162
25367
  const archivePath = resolveArchivePath(providerHome, id);
25163
25368
  const files = Object.fromEntries(
25164
25369
  fileNames.map((name) => [