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 +23 -0
- package/apps/supervisor-api/dist/index.js +329 -24
- package/apps/supervisor-web/dist/assets/{index-CIcJHgFF.js → index-Dpf8YNxk.js} +1 -1
- package/apps/supervisor-web/dist/assets/index-IJffwc2e.css +1 -0
- package/apps/supervisor-web/dist/assets/thread-ui-BnLdOiXc.js +3679 -0
- package/apps/supervisor-web/dist/index.html +3 -3
- package/bin/remote-codex.mjs +216 -12
- package/package.json +1 -1
- package/packages/claude/src/historyItems.ts +71 -0
- package/packages/claude/src/runtimeAdapter.test.ts +259 -0
- package/packages/claude/src/runtimeAdapter.ts +52 -3
- package/apps/supervisor-web/dist/assets/index-BVYGmxwf.css +0 -1
- package/apps/supervisor-web/dist/assets/thread-ui-CaDgVQIY.js +0 -3679
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(/"/g, '"').replace(/'/g, "'").replace(/>/g, ">").replace(/</g, "<").replace(/&/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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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.
|
|
18352
|
-
|
|
18353
|
-
|
|
18354
|
-
|
|
18355
|
-
|
|
18356
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
19426
|
+
status: latestStatus,
|
|
19123
19427
|
summaryText: remoteSession.preview || null,
|
|
19124
19428
|
model: model ?? null,
|
|
19125
19429
|
reasoningEffort: normalizeReasoningEffort2(reasoningEffort),
|
|
19126
|
-
lastError:
|
|
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
|
|
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
|
|
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}-${
|
|
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
|
|
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)}-${
|
|
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) => [
|