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 +23 -0
- package/apps/relay-server/dist/index.js +58 -9
- package/apps/supervisor-api/dist/index.js +228 -16
- package/apps/supervisor-web/dist/assets/index-FOSXdyuj.css +1 -0
- package/apps/supervisor-web/dist/assets/index-fi_pAm4J.js +6 -0
- package/apps/supervisor-web/dist/assets/{thread-ui-CpukI6ql.js → thread-ui-DXUs5Aqt.js} +26 -28
- package/apps/supervisor-web/dist/index.html +3 -3
- package/bin/remote-codex.mjs +216 -12
- package/package.json +1 -1
- package/apps/supervisor-web/dist/assets/index-BVYGmxwf.css +0 -1
- package/apps/supervisor-web/dist/assets/index-pMPBEcL2.js +0 -5
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
|
|
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
|
|
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.
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
19433
|
+
status: latestStatus,
|
|
19222
19434
|
summaryText: remoteSession.preview || null,
|
|
19223
19435
|
model: model ?? null,
|
|
19224
19436
|
reasoningEffort: normalizeReasoningEffort2(reasoningEffort),
|
|
19225
|
-
lastError:
|
|
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
|
|
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}-${
|
|
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
|
|
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)}-${
|
|
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) => [
|