codex-to-im 1.0.64 → 1.0.66
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/dist/daemon.mjs +759 -126
- package/dist/ui-server.mjs +1 -1
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -1280,6 +1280,16 @@ function normalizeCodexErrorMessage(message) {
|
|
|
1280
1280
|
}
|
|
1281
1281
|
return trimmed;
|
|
1282
1282
|
}
|
|
1283
|
+
function isCodexProcessRecoveryErrorMessage(message) {
|
|
1284
|
+
const lower = (message || "").trim().toLowerCase();
|
|
1285
|
+
return lower.includes("timeout waiting for child process to exit") || lower.includes("reconnecting...");
|
|
1286
|
+
}
|
|
1287
|
+
function enqueueCodexError(controller, message) {
|
|
1288
|
+
if (isCodexProcessRecoveryErrorMessage(message)) {
|
|
1289
|
+
controller.enqueue(sseEvent("status", { error_code: "desktop_transport_lost" }));
|
|
1290
|
+
}
|
|
1291
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(message)));
|
|
1292
|
+
}
|
|
1283
1293
|
function getTerminalDrainTimeoutMs() {
|
|
1284
1294
|
const configured = parseInt(process.env.CTI_CODEX_TERMINAL_DRAIN_TIMEOUT_MS || "", 10);
|
|
1285
1295
|
if (Number.isFinite(configured) && configured >= 10) {
|
|
@@ -1452,6 +1462,10 @@ var init_codex_provider = __esm({
|
|
|
1452
1462
|
clearCachedThreadId(sessionId) {
|
|
1453
1463
|
this.threadIds.delete(sessionId);
|
|
1454
1464
|
}
|
|
1465
|
+
recycleSdkClient(sessionId) {
|
|
1466
|
+
this.clearCachedThreadId(sessionId);
|
|
1467
|
+
this.codex = null;
|
|
1468
|
+
}
|
|
1455
1469
|
/**
|
|
1456
1470
|
* Lazily load the Codex SDK. Throws a clear error if the installation is incomplete.
|
|
1457
1471
|
*/
|
|
@@ -1641,15 +1655,25 @@ var init_codex_provider = __esm({
|
|
|
1641
1655
|
}
|
|
1642
1656
|
case "turn.failed": {
|
|
1643
1657
|
const error = event.error?.message;
|
|
1644
|
-
|
|
1645
|
-
|
|
1658
|
+
const message = error || "Turn failed";
|
|
1659
|
+
if (isCodexProcessRecoveryErrorMessage(message)) {
|
|
1660
|
+
self.recycleSdkClient(params.sessionId);
|
|
1661
|
+
} else {
|
|
1662
|
+
self.clearCachedThreadId(params.sessionId);
|
|
1663
|
+
}
|
|
1664
|
+
enqueueCodexError(controller, message);
|
|
1646
1665
|
sawTerminalEvent = true;
|
|
1647
1666
|
break;
|
|
1648
1667
|
}
|
|
1649
1668
|
case "error": {
|
|
1650
1669
|
const error = event.message;
|
|
1651
|
-
|
|
1652
|
-
|
|
1670
|
+
const message = error || "Thread error";
|
|
1671
|
+
if (isCodexProcessRecoveryErrorMessage(message)) {
|
|
1672
|
+
self.recycleSdkClient(params.sessionId);
|
|
1673
|
+
} else {
|
|
1674
|
+
self.clearCachedThreadId(params.sessionId);
|
|
1675
|
+
}
|
|
1676
|
+
enqueueCodexError(controller, message);
|
|
1653
1677
|
sawTerminalEvent = true;
|
|
1654
1678
|
break;
|
|
1655
1679
|
}
|
|
@@ -1681,6 +1705,12 @@ var init_codex_provider = __esm({
|
|
|
1681
1705
|
console.warn("[codex-provider] Suppressed Codex SDK Windows process cleanup parse noise:", message);
|
|
1682
1706
|
break;
|
|
1683
1707
|
}
|
|
1708
|
+
if (isCodexProcessRecoveryErrorMessage(message)) {
|
|
1709
|
+
if (!runAbortController.signal.aborted) {
|
|
1710
|
+
runAbortController.abort();
|
|
1711
|
+
}
|
|
1712
|
+
self.recycleSdkClient(params.sessionId);
|
|
1713
|
+
}
|
|
1684
1714
|
if (savedThreadId && !retryFresh && !sawAnyEvent && shouldRetryFreshThread(message)) {
|
|
1685
1715
|
console.warn("[codex-provider] Resume failed, retrying with a fresh thread:", message);
|
|
1686
1716
|
self.clearCachedThreadId(params.sessionId);
|
|
@@ -1699,9 +1729,13 @@ var init_codex_provider = __esm({
|
|
|
1699
1729
|
} catch (err) {
|
|
1700
1730
|
const message = err instanceof Error ? err.message : String(err);
|
|
1701
1731
|
console.error("[codex-provider] Error:", err instanceof Error ? err.stack || err.message : err);
|
|
1702
|
-
|
|
1732
|
+
if (isCodexProcessRecoveryErrorMessage(message)) {
|
|
1733
|
+
self.recycleSdkClient(params.sessionId);
|
|
1734
|
+
} else {
|
|
1735
|
+
self.clearCachedThreadId(params.sessionId);
|
|
1736
|
+
}
|
|
1703
1737
|
try {
|
|
1704
|
-
controller
|
|
1738
|
+
enqueueCodexError(controller, message);
|
|
1705
1739
|
controller.close();
|
|
1706
1740
|
} catch {
|
|
1707
1741
|
}
|
|
@@ -11867,7 +11901,7 @@ function parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds, vi
|
|
|
11867
11901
|
}
|
|
11868
11902
|
const lastEventAt = visibleThread?.updatedAtMs ? new Date(visibleThread.updatedAtMs).toISOString() : stat.mtime.toISOString();
|
|
11869
11903
|
const firstSeenAt = parsed.payload.timestamp || parsed.timestamp || stat.birthtime.toISOString();
|
|
11870
|
-
const title =
|
|
11904
|
+
const title = threadIndexEntries.get(threadId)?.title || visibleThread?.title || buildFallbackTitle(threadId, filePath, cwd);
|
|
11871
11905
|
return {
|
|
11872
11906
|
threadId,
|
|
11873
11907
|
filePath,
|
|
@@ -12047,30 +12081,53 @@ function formatDesktopToolName(namespaceValue, nameValue) {
|
|
|
12047
12081
|
function createDesktopEventSignature(rawLine) {
|
|
12048
12082
|
return crypto7.createHash("sha1").update(rawLine).digest("hex");
|
|
12049
12083
|
}
|
|
12050
|
-
function readCompleteUtf8LineRange(filePath, startOffset, endOffset) {
|
|
12084
|
+
function readCompleteUtf8LineRange(filePath, startOffset, endOffset, maxBytes) {
|
|
12051
12085
|
const safeStart = Math.max(0, startOffset);
|
|
12052
12086
|
const safeEnd = Math.max(safeStart, endOffset);
|
|
12053
|
-
const
|
|
12054
|
-
if (
|
|
12087
|
+
const availableBytes = safeEnd - safeStart;
|
|
12088
|
+
if (availableBytes <= 0) return { content: "", nextOffset: safeStart };
|
|
12089
|
+
const boundedBytes = typeof maxBytes === "number" && Number.isFinite(maxBytes) ? Math.max(1, Math.min(availableBytes, Math.floor(maxBytes))) : availableBytes;
|
|
12055
12090
|
const fd = fs5.openSync(filePath, "r");
|
|
12056
12091
|
try {
|
|
12057
|
-
const
|
|
12058
|
-
let
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
12067
|
-
|
|
12092
|
+
const chunks = [];
|
|
12093
|
+
let totalBytes = 0;
|
|
12094
|
+
let position = safeStart;
|
|
12095
|
+
let firstChunk = true;
|
|
12096
|
+
while (position < safeEnd) {
|
|
12097
|
+
const requestedBytes = Math.min(boundedBytes, safeEnd - position);
|
|
12098
|
+
const buffer = Buffer.allocUnsafe(requestedBytes);
|
|
12099
|
+
let totalRead = 0;
|
|
12100
|
+
while (totalRead < requestedBytes) {
|
|
12101
|
+
const bytesRead = fs5.readSync(
|
|
12102
|
+
fd,
|
|
12103
|
+
buffer,
|
|
12104
|
+
totalRead,
|
|
12105
|
+
requestedBytes - totalRead,
|
|
12106
|
+
position + totalRead
|
|
12107
|
+
);
|
|
12108
|
+
if (bytesRead <= 0) break;
|
|
12109
|
+
totalRead += bytesRead;
|
|
12110
|
+
}
|
|
12111
|
+
if (totalRead <= 0) break;
|
|
12112
|
+
const readBuffer = buffer.subarray(0, totalRead);
|
|
12113
|
+
const newlineIndex = firstChunk ? readBuffer.lastIndexOf(10) : readBuffer.indexOf(10);
|
|
12114
|
+
if (newlineIndex >= 0) {
|
|
12115
|
+
const completeChunk = readBuffer.subarray(0, newlineIndex + 1);
|
|
12116
|
+
chunks.push(completeChunk);
|
|
12117
|
+
totalBytes += completeChunk.length;
|
|
12118
|
+
const completeBuffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, totalBytes);
|
|
12119
|
+
return {
|
|
12120
|
+
content: completeBuffer.toString("utf-8"),
|
|
12121
|
+
nextOffset: safeStart + totalBytes
|
|
12122
|
+
};
|
|
12123
|
+
}
|
|
12124
|
+
chunks.push(readBuffer);
|
|
12125
|
+
totalBytes += totalRead;
|
|
12126
|
+
position += totalRead;
|
|
12127
|
+
firstChunk = false;
|
|
12128
|
+
if (totalRead < requestedBytes) break;
|
|
12068
12129
|
}
|
|
12069
|
-
|
|
12070
|
-
return {
|
|
12071
|
-
content: readBuffer.subarray(0, consumedBytes).toString("utf-8"),
|
|
12072
|
-
nextOffset: safeStart + consumedBytes
|
|
12073
|
-
};
|
|
12130
|
+
return { content: "", nextOffset: safeStart };
|
|
12074
12131
|
} finally {
|
|
12075
12132
|
fs5.closeSync(fd);
|
|
12076
12133
|
}
|
|
@@ -12831,10 +12888,10 @@ function readDesktopSessionMessages(threadId, limit = 8) {
|
|
|
12831
12888
|
if (!session) return [];
|
|
12832
12889
|
return readDesktopSessionMessagesByFilePath(session.filePath, limit);
|
|
12833
12890
|
}
|
|
12834
|
-
function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null, currentSpecialCallIds = []) {
|
|
12891
|
+
function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null, currentSpecialCallIds = [], options = {}) {
|
|
12835
12892
|
let range;
|
|
12836
12893
|
try {
|
|
12837
|
-
range = readCompleteUtf8LineRange(filePath, startOffset, endOffset);
|
|
12894
|
+
range = readCompleteUtf8LineRange(filePath, startOffset, endOffset, options.maxBytes);
|
|
12838
12895
|
} catch {
|
|
12839
12896
|
return {
|
|
12840
12897
|
records: [],
|
|
@@ -14478,6 +14535,18 @@ function abortMirrorSuppression(store, sessionId, config2, suppressionId, nowMs
|
|
|
14478
14535
|
}
|
|
14479
14536
|
target.until = nowMs + config2.suppressionWindowMs;
|
|
14480
14537
|
}
|
|
14538
|
+
function handoffMirrorSuppression(store, sessionId, suppressionId) {
|
|
14539
|
+
clearMirrorSuppression(store, sessionId, suppressionId);
|
|
14540
|
+
}
|
|
14541
|
+
function ignoreMirrorTurn(store, sessionId, config2, turnId, nowMs = Date.now()) {
|
|
14542
|
+
markIgnoredMirrorTurn(
|
|
14543
|
+
store,
|
|
14544
|
+
sessionId,
|
|
14545
|
+
turnId,
|
|
14546
|
+
config2.promptMatchGraceMs,
|
|
14547
|
+
nowMs
|
|
14548
|
+
);
|
|
14549
|
+
}
|
|
14481
14550
|
function filterSuppressedMirrorRecords(store, sessionId, records, config2, nowMs = Date.now(), onTurnAssociated) {
|
|
14482
14551
|
const suppressions = getMirrorSuppressionStates(store, sessionId, nowMs);
|
|
14483
14552
|
const ignoredTurnIds = cleanupIgnoredMirrorTurns(store, sessionId, nowMs);
|
|
@@ -14489,7 +14558,7 @@ function filterSuppressedMirrorRecords(store, sessionId, records, config2, nowMs
|
|
|
14489
14558
|
while (true) {
|
|
14490
14559
|
const ignoredTurnIds2 = cleanupIgnoredMirrorTurns(store, sessionId, nowMs);
|
|
14491
14560
|
if (record.turnId && ignoredTurnIds2.has(record.turnId)) {
|
|
14492
|
-
if (record.type === "task_complete") {
|
|
14561
|
+
if (record.type === "task_complete" || record.type === "task_aborted") {
|
|
14493
14562
|
clearIgnoredMirrorTurn(store, sessionId, record.turnId, nowMs);
|
|
14494
14563
|
}
|
|
14495
14564
|
handled = true;
|
|
@@ -15951,16 +16020,35 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
15951
16020
|
"suspected_detached"
|
|
15952
16021
|
]);
|
|
15953
16022
|
const looksRunning = session?.runtime_status === "running" || session?.runtime_status === "queued" || runningHealthStatuses.has(session?.health_status || "");
|
|
15954
|
-
|
|
15955
|
-
|
|
15956
|
-
|
|
15957
|
-
|
|
15958
|
-
|
|
15959
|
-
|
|
16023
|
+
const taskName = getSessionDisplayName(session, binding.workingDirectory);
|
|
16024
|
+
const stopDetail = "\u7528\u6237\u6267\u884C /stop\uFF0C\u8BF7\u6C42\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
16025
|
+
if (deps.forceStopSession) {
|
|
16026
|
+
const result = await deps.forceStopSession(binding.codepilotSessionId, stopDetail);
|
|
16027
|
+
if (result.status === "stopped") {
|
|
16028
|
+
deps.recordInteractiveHealthEnd?.(binding.codepilotSessionId, "aborted", stopDetail);
|
|
16029
|
+
response = `\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\u4EFB\u52A1\u5DF2\u505C\u6B62\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u6D88\u606F\u6062\u590D\u8BE5\u7EBF\u7A0B\u3002`;
|
|
16030
|
+
} else if (result.status === "stop_requested") {
|
|
16031
|
+
deps.recordInteractiveHealthEnd?.(binding.codepilotSessionId, "aborted", stopDetail);
|
|
16032
|
+
response = `\u5DF2\u5411\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\u53D1\u9001\u505C\u6B62\u8BF7\u6C42\u3002\u53EF\u53D1\u9001 \`//\` \u786E\u8BA4\u6700\u7EC8\u72B6\u6001\u3002`;
|
|
16033
|
+
} else if (result.status === "not_running") {
|
|
16034
|
+
response = result.detail || "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
|
16035
|
+
} else if (result.status === "unavailable") {
|
|
16036
|
+
response = `\u65E0\u6CD5\u4ECE IM \u5B89\u5168\u505C\u6B62\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\uFF1A${result.detail}
|
|
16037
|
+
|
|
16038
|
+
\u672A\u7EC8\u6B62 Codex Desktop App \u6216\u5176\u4ED6\u65E0\u6CD5\u786E\u8BA4\u5F52\u5C5E\u7684\u8FDB\u7A0B\u3002`;
|
|
16039
|
+
} else {
|
|
16040
|
+
response = `\u505C\u6B62\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\u5931\u8D25\uFF1A${result.detail}
|
|
16041
|
+
|
|
16042
|
+
\u53EF\u7A0D\u540E\u91CD\u8BD5 \`/stop\`\uFF0C\u6216\u53D1\u9001 \`//\` \u67E5\u770B\u4EFB\u52A1\u662F\u5426\u4ECD\u5728\u8FD0\u884C\u3002`;
|
|
16043
|
+
}
|
|
16044
|
+
} else if (task || looksRunning) {
|
|
16045
|
+
if (task) {
|
|
15960
16046
|
task.abortController.abort();
|
|
16047
|
+
deps.recordInteractiveHealthEnd?.(binding.codepilotSessionId, "aborted", stopDetail);
|
|
16048
|
+
response = `\u5DF2\u5411\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\u53D1\u9001\u505C\u6B62\u8BF7\u6C42\u3002\u53EF\u53D1\u9001 \`//\` \u786E\u8BA4\u6700\u7EC8\u72B6\u6001\u3002`;
|
|
16049
|
+
} else {
|
|
16050
|
+
response = "\u5F53\u524D\u4EFB\u52A1\u770B\u8D77\u6765\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5F53\u524D\u8FD0\u884C\u65F6\u4E0D\u652F\u6301\u4ECE IM \u5B89\u5168\u505C\u6B62\u3002";
|
|
15961
16051
|
}
|
|
15962
|
-
deps.recordInteractiveHealthEnd?.(binding.codepilotSessionId, "aborted", detail);
|
|
15963
|
-
response = `\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\u4EFB\u52A1\u5DF2\u505C\u6B62\uFF0C\u53EF\u7EE7\u7EED\u53D1\u9001\u6D88\u606F\u6062\u590D\u8BE5\u7EBF\u7A0B\u3002`;
|
|
15964
16052
|
} else {
|
|
15965
16053
|
response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
|
15966
16054
|
}
|
|
@@ -16458,6 +16546,7 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
16458
16546
|
let hasError = false;
|
|
16459
16547
|
let errorMessage = "";
|
|
16460
16548
|
let errorCode;
|
|
16549
|
+
let providerErrorCode;
|
|
16461
16550
|
const seenToolResultIds = /* @__PURE__ */ new Set();
|
|
16462
16551
|
const permissionRequests = [];
|
|
16463
16552
|
let capturedSdkSessionId = null;
|
|
@@ -16590,6 +16679,9 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
16590
16679
|
} catch {
|
|
16591
16680
|
}
|
|
16592
16681
|
}
|
|
16682
|
+
if (statusData.error_code === "desktop_transport_lost") {
|
|
16683
|
+
providerErrorCode = "desktop_transport_lost";
|
|
16684
|
+
}
|
|
16593
16685
|
} catch {
|
|
16594
16686
|
}
|
|
16595
16687
|
break;
|
|
@@ -16616,6 +16708,8 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
16616
16708
|
errorMessage = event.data || "Unknown error";
|
|
16617
16709
|
if (isSessionBusyErrorMessage(errorMessage)) {
|
|
16618
16710
|
errorCode = "session_busy";
|
|
16711
|
+
} else if (providerErrorCode) {
|
|
16712
|
+
errorCode = providerErrorCode;
|
|
16619
16713
|
}
|
|
16620
16714
|
break;
|
|
16621
16715
|
case "result": {
|
|
@@ -16640,7 +16734,7 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
16640
16734
|
tokenUsage,
|
|
16641
16735
|
hasError,
|
|
16642
16736
|
errorMessage,
|
|
16643
|
-
errorCode,
|
|
16737
|
+
errorCode: errorCode || providerErrorCode,
|
|
16644
16738
|
permissionRequests,
|
|
16645
16739
|
sdkSessionId: capturedSdkSessionId
|
|
16646
16740
|
};
|
|
@@ -16654,7 +16748,7 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
16654
16748
|
tokenUsage,
|
|
16655
16749
|
hasError: true,
|
|
16656
16750
|
errorMessage: fallbackErrorMessage,
|
|
16657
|
-
errorCode: isSessionBusyErrorMessage(fallbackErrorMessage) ? "session_busy" :
|
|
16751
|
+
errorCode: isSessionBusyErrorMessage(fallbackErrorMessage) ? "session_busy" : providerErrorCode,
|
|
16658
16752
|
permissionRequests,
|
|
16659
16753
|
sdkSessionId: capturedSdkSessionId
|
|
16660
16754
|
};
|
|
@@ -16977,6 +17071,12 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
16977
17071
|
const streamState = createStreamState(taskStartedAt);
|
|
16978
17072
|
let externalTerminalRequest = null;
|
|
16979
17073
|
let desktopTerminalFinalExpected = false;
|
|
17074
|
+
let desktopTurnAccepted = false;
|
|
17075
|
+
let desktopTurnId = null;
|
|
17076
|
+
let desktopHandoffPreparation = null;
|
|
17077
|
+
let desktopHandoffStopReady = false;
|
|
17078
|
+
let desktopHandoffTaskActivated = false;
|
|
17079
|
+
let retainDesktopHandoffTask = false;
|
|
16980
17080
|
let resolveExternalTerminal = null;
|
|
16981
17081
|
const externalTerminalPromise = new Promise((resolve2) => {
|
|
16982
17082
|
resolveExternalTerminal = resolve2;
|
|
@@ -17032,7 +17132,25 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
17032
17132
|
desktopThreadId,
|
|
17033
17133
|
requestMessageId: msg.messageId,
|
|
17034
17134
|
streamKey,
|
|
17035
|
-
startedAt: taskStartedAt
|
|
17135
|
+
startedAt: taskStartedAt,
|
|
17136
|
+
onDesktopTurnAssociated: (turnId) => {
|
|
17137
|
+
desktopTurnAccepted = true;
|
|
17138
|
+
desktopTurnId = turnId;
|
|
17139
|
+
if (desktopThreadId && deps.prepareDesktopHandoffTask && !desktopHandoffPreparation) {
|
|
17140
|
+
desktopHandoffPreparation = deps.prepareDesktopHandoffTask({
|
|
17141
|
+
sessionId: binding.codepilotSessionId,
|
|
17142
|
+
taskId,
|
|
17143
|
+
threadId: desktopThreadId,
|
|
17144
|
+
turnId
|
|
17145
|
+
}).catch((error) => {
|
|
17146
|
+
console.warn(
|
|
17147
|
+
`[interactive-message-runner] Failed to prepare Desktop handoff stop control for ${desktopThreadId}:`,
|
|
17148
|
+
error instanceof Error ? error.message : error
|
|
17149
|
+
);
|
|
17150
|
+
return false;
|
|
17151
|
+
});
|
|
17152
|
+
}
|
|
17153
|
+
}
|
|
17036
17154
|
});
|
|
17037
17155
|
deps.recordInteractiveHealthStart(binding.codepilotSessionId);
|
|
17038
17156
|
let previewState = null;
|
|
@@ -17409,7 +17527,30 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
17409
17527
|
shouldRecordHealthEnd = false;
|
|
17410
17528
|
return;
|
|
17411
17529
|
}
|
|
17530
|
+
const desktopTransportLost = Boolean(
|
|
17531
|
+
desktopThreadId && desktopTurnAccepted && result.hasError && result.errorCode === "desktop_transport_lost"
|
|
17532
|
+
);
|
|
17533
|
+
if (desktopTransportLost && desktopHandoffPreparation && deps.activateDesktopHandoffTask) {
|
|
17534
|
+
desktopHandoffStopReady = await desktopHandoffPreparation;
|
|
17535
|
+
if (deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
17536
|
+
desktopHandoffTaskActivated = deps.activateDesktopHandoffTask(
|
|
17537
|
+
binding.codepilotSessionId,
|
|
17538
|
+
taskId
|
|
17539
|
+
);
|
|
17540
|
+
}
|
|
17541
|
+
}
|
|
17542
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
17543
|
+
shouldRecordHealthEnd = false;
|
|
17544
|
+
return;
|
|
17545
|
+
}
|
|
17412
17546
|
const terminalAfterProcess = await waitForDesktopTerminalFinalization();
|
|
17547
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
17548
|
+
shouldRecordHealthEnd = false;
|
|
17549
|
+
return;
|
|
17550
|
+
}
|
|
17551
|
+
if (taskAbort.signal.aborted && !externalTerminalRequest) {
|
|
17552
|
+
return;
|
|
17553
|
+
}
|
|
17413
17554
|
const terminalResponse = terminalAfterProcess?.outcome === "completed" ? assembleDesktopFinalResponse({ text: terminalAfterProcess.finalText || "" }) : null;
|
|
17414
17555
|
const sdkResponse = assembleSdkFinalResponse({
|
|
17415
17556
|
text: result.responseText,
|
|
@@ -17417,6 +17558,33 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
17417
17558
|
hasError: result.hasError,
|
|
17418
17559
|
errorMessage: result.errorMessage
|
|
17419
17560
|
});
|
|
17561
|
+
const shouldHandoffToMirror = Boolean(
|
|
17562
|
+
desktopThreadId && desktopTurnAccepted && !terminalAfterProcess && result.hasError && result.errorCode === "desktop_transport_lost" && deps.handoffMirrorSuppression
|
|
17563
|
+
);
|
|
17564
|
+
if (shouldHandoffToMirror) {
|
|
17565
|
+
const canStopFromIm = desktopHandoffTaskActivated && desktopHandoffStopReady;
|
|
17566
|
+
retainDesktopHandoffTask = Boolean(desktopHandoffTaskActivated && desktopTurnId);
|
|
17567
|
+
const handoffText = [
|
|
17568
|
+
"Codex Desktop \u5DF2\u63A5\u6536\u672C\u6B21\u8BF7\u6C42\uFF0C\u4F46 SDK \u8FDE\u63A5\u5728\u6267\u884C\u4E2D\u65AD\u5F00\u3002",
|
|
17569
|
+
"\u5DF2\u81EA\u52A8\u5207\u6362\u4E3A\u684C\u9762\u955C\u50CF\u63A5\u7BA1\uFF1B\u540E\u7EED\u56DE\u590D\u548C\u9644\u4EF6\u4F1A\u7EE7\u7EED\u56DE\u4F20\uFF0C\u8BF7\u4E0D\u8981\u91CD\u590D\u53D1\u9001\u540C\u4E00\u8BF7\u6C42\u3002",
|
|
17570
|
+
canStopFromIm ? "\u53EF\u53D1\u9001 `//` \u67E5\u770B\u5F53\u524D\u72B6\u6001\uFF1B\u82E5\u8981\u653E\u5F03\u672C\u6B21\u684C\u9762\u4EFB\u52A1\uFF0C\u8BF7\u53D1\u9001 `/stop`\u3002`/t 0` \u53EA\u5207\u6362\u4F1A\u8BDD\uFF0C\u4E0D\u4F1A\u505C\u6B62\u539F\u4EFB\u52A1\u3002" : "\u53EF\u53D1\u9001 `//` \u67E5\u770B\u5F53\u524D\u72B6\u6001\uFF1B\u4E5F\u53EF\u53D1\u9001 `/stop` \u5C1D\u8BD5\u5B89\u5168\u505C\u6B62\u3002\u82E5\u65E0\u6CD5\u9A8C\u8BC1 bridge \u81EA\u6709\u8FDB\u7A0B\uFF0C\u7CFB\u7EDF\u4E0D\u4F1A\u8BEF\u505C Desktop App\u3002"
|
|
17571
|
+
].join("\n\n");
|
|
17572
|
+
if (taskState.mirrorSuppressionId && deps.handoffMirrorSuppression) {
|
|
17573
|
+
deps.handoffMirrorSuppression(
|
|
17574
|
+
binding.codepilotSessionId,
|
|
17575
|
+
taskState.mirrorSuppressionId
|
|
17576
|
+
);
|
|
17577
|
+
taskState.mirrorSuppressionId = null;
|
|
17578
|
+
}
|
|
17579
|
+
const handoffResponse = assembleSdkFinalResponse({
|
|
17580
|
+
text: handoffText,
|
|
17581
|
+
attachments: result.outboundAttachments
|
|
17582
|
+
});
|
|
17583
|
+
const cardFinalized2 = await finalizeStreamUiOnce("interrupted", handoffResponse.text);
|
|
17584
|
+
await deliverFinalPayload(handoffResponse, { skipText: cardFinalized2 });
|
|
17585
|
+
shouldRecordHealthEnd = false;
|
|
17586
|
+
return;
|
|
17587
|
+
}
|
|
17420
17588
|
const terminalHasFinalPayload = Boolean(
|
|
17421
17589
|
terminalResponse && hasFinalResponsePayload(terminalResponse)
|
|
17422
17590
|
);
|
|
@@ -17472,6 +17640,9 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
17472
17640
|
}
|
|
17473
17641
|
deps.recordInteractiveHealthEnd(binding.codepilotSessionId, finalOutcome, finalOutcomeDetail);
|
|
17474
17642
|
}
|
|
17643
|
+
if (!retainDesktopHandoffTask) {
|
|
17644
|
+
deps.releaseDesktopHandoffTask?.(binding.codepilotSessionId, taskId);
|
|
17645
|
+
}
|
|
17475
17646
|
deps.releaseInteractiveTask(binding.codepilotSessionId, taskId);
|
|
17476
17647
|
deps.releaseBridgeTurn?.(binding.codepilotSessionId, taskId);
|
|
17477
17648
|
endMessageUiOnce();
|
|
@@ -17545,7 +17716,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
17545
17716
|
async function forceStopSession(sessionId, detail) {
|
|
17546
17717
|
const state = getState2();
|
|
17547
17718
|
const task = state.activeTasks.get(sessionId);
|
|
17548
|
-
let handled =
|
|
17719
|
+
let handled = getQueuedCount(sessionId) > 0;
|
|
17549
17720
|
if (task?.forceStop) {
|
|
17550
17721
|
handled = await task.forceStop(detail);
|
|
17551
17722
|
} else if (task) {
|
|
@@ -17659,6 +17830,7 @@ function resetMirrorReadState(subscription) {
|
|
|
17659
17830
|
subscription.trailingText = "";
|
|
17660
17831
|
subscription.activeMirrorTurnId = null;
|
|
17661
17832
|
subscription.activeSpecialCallIds.clear();
|
|
17833
|
+
subscription.recoveryState = null;
|
|
17662
17834
|
subscription.bufferedRecords = [];
|
|
17663
17835
|
}
|
|
17664
17836
|
function createMirrorSubscription(input) {
|
|
@@ -17683,6 +17855,7 @@ function createMirrorSubscription(input) {
|
|
|
17683
17855
|
trailingText: "",
|
|
17684
17856
|
activeMirrorTurnId: null,
|
|
17685
17857
|
activeSpecialCallIds: /* @__PURE__ */ new Set(),
|
|
17858
|
+
recoveryState: null,
|
|
17686
17859
|
bufferedRecords: [],
|
|
17687
17860
|
pendingTurn: null,
|
|
17688
17861
|
pendingDeliveries: [],
|
|
@@ -17794,73 +17967,9 @@ function filterDuplicateAssistantEvents(cursor, records) {
|
|
|
17794
17967
|
}
|
|
17795
17968
|
return startIndex === 0 ? records : records.slice(startIndex);
|
|
17796
17969
|
}
|
|
17797
|
-
function findLastEventIndex(records, signature) {
|
|
17798
|
-
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
17799
|
-
if (records[index]?.signature === signature) {
|
|
17800
|
-
return index;
|
|
17801
|
-
}
|
|
17802
|
-
}
|
|
17803
|
-
return -1;
|
|
17804
|
-
}
|
|
17805
|
-
function collectEventsAfterTimestamp(records, timestamp) {
|
|
17806
|
-
if (!timestamp) return [];
|
|
17807
|
-
return records.filter((event) => Boolean(event.timestamp) && event.timestamp > timestamp);
|
|
17808
|
-
}
|
|
17809
|
-
function reconcileDesktopMirrorCursor(cursor, records) {
|
|
17810
|
-
const nextCursor = makeCursor(records);
|
|
17811
|
-
if (!cursor?.initialized) {
|
|
17812
|
-
return {
|
|
17813
|
-
nextCursor,
|
|
17814
|
-
deliverableRecords: [],
|
|
17815
|
-
reset: false
|
|
17816
|
-
};
|
|
17817
|
-
}
|
|
17818
|
-
if (records.length === 0) {
|
|
17819
|
-
return {
|
|
17820
|
-
nextCursor,
|
|
17821
|
-
deliverableRecords: [],
|
|
17822
|
-
reset: cursor.lastEventCount > 0
|
|
17823
|
-
};
|
|
17824
|
-
}
|
|
17825
|
-
if (cursor.lastEventSignature) {
|
|
17826
|
-
const lastSeenIndex = findLastEventIndex(records, cursor.lastEventSignature);
|
|
17827
|
-
if (lastSeenIndex === -1) {
|
|
17828
|
-
const recoveredEvents = collectEventsAfterTimestamp(records, cursor.lastEventTimestamp);
|
|
17829
|
-
return {
|
|
17830
|
-
nextCursor,
|
|
17831
|
-
deliverableRecords: recoveredEvents,
|
|
17832
|
-
reset: true
|
|
17833
|
-
};
|
|
17834
|
-
}
|
|
17835
|
-
return {
|
|
17836
|
-
nextCursor,
|
|
17837
|
-
deliverableRecords: records.slice(lastSeenIndex + 1),
|
|
17838
|
-
reset: false
|
|
17839
|
-
};
|
|
17840
|
-
}
|
|
17841
|
-
if (cursor.lastEventCount === 0) {
|
|
17842
|
-
return {
|
|
17843
|
-
nextCursor,
|
|
17844
|
-
deliverableRecords: cursor.lastEventTimestamp ? collectEventsAfterTimestamp(records, cursor.lastEventTimestamp) : records,
|
|
17845
|
-
reset: false
|
|
17846
|
-
};
|
|
17847
|
-
}
|
|
17848
|
-
if (records.length < cursor.lastEventCount) {
|
|
17849
|
-
const recoveredEvents = collectEventsAfterTimestamp(records, cursor.lastEventTimestamp);
|
|
17850
|
-
return {
|
|
17851
|
-
nextCursor,
|
|
17852
|
-
deliverableRecords: recoveredEvents,
|
|
17853
|
-
reset: true
|
|
17854
|
-
};
|
|
17855
|
-
}
|
|
17856
|
-
return {
|
|
17857
|
-
nextCursor,
|
|
17858
|
-
deliverableRecords: records.slice(cursor.lastEventCount),
|
|
17859
|
-
reset: false
|
|
17860
|
-
};
|
|
17861
|
-
}
|
|
17862
17970
|
|
|
17863
17971
|
// src/lib/bridge/mirror-reconcile-core.ts
|
|
17972
|
+
var DEFAULT_MAX_MIRROR_READ_BYTES = 64 * 1024 * 1024;
|
|
17864
17973
|
function statMirrorFile(filePath) {
|
|
17865
17974
|
try {
|
|
17866
17975
|
const stat = fs10.statSync(filePath);
|
|
@@ -17912,12 +18021,70 @@ function findLastCompleteLineOffset(filePath, fileSize) {
|
|
|
17912
18021
|
fs10.closeSync(fd);
|
|
17913
18022
|
}
|
|
17914
18023
|
}
|
|
17915
|
-
function
|
|
18024
|
+
function beginMirrorRecovery(subscription) {
|
|
18025
|
+
subscription.recoveryState = {
|
|
18026
|
+
baselineCursor: { ...subscription.cursor },
|
|
18027
|
+
scannedCursor: { initialized: true, lastEventCount: 0 },
|
|
18028
|
+
boundaryFound: false,
|
|
18029
|
+
dedupePending: true,
|
|
18030
|
+
scannedRecordCount: 0
|
|
18031
|
+
};
|
|
18032
|
+
subscription.trailingText = "";
|
|
18033
|
+
subscription.fileOffset = 0;
|
|
18034
|
+
subscription.activeMirrorTurnId = null;
|
|
18035
|
+
subscription.activeSpecialCallIds.clear();
|
|
18036
|
+
}
|
|
18037
|
+
function findCursorTimestampBoundary(cursor, records) {
|
|
18038
|
+
if (!cursor.lastEventTimestamp) return -1;
|
|
18039
|
+
return records.findIndex((record) => Boolean(record.timestamp) && record.timestamp > cursor.lastEventTimestamp);
|
|
18040
|
+
}
|
|
18041
|
+
function selectRecoveredRecords(subscription, records) {
|
|
18042
|
+
const recovery = subscription.recoveryState;
|
|
18043
|
+
if (!recovery || records.length === 0) return [];
|
|
18044
|
+
if (recovery.boundaryFound) return records;
|
|
18045
|
+
const baseline = recovery.baselineCursor;
|
|
18046
|
+
if (baseline.lastEventSignature) {
|
|
18047
|
+
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
18048
|
+
if (records[index]?.signature !== baseline.lastEventSignature) continue;
|
|
18049
|
+
recovery.boundaryFound = true;
|
|
18050
|
+
return records.slice(index + 1);
|
|
18051
|
+
}
|
|
18052
|
+
}
|
|
18053
|
+
const timestampBoundary = findCursorTimestampBoundary(baseline, records);
|
|
18054
|
+
if (timestampBoundary >= 0) {
|
|
18055
|
+
recovery.boundaryFound = true;
|
|
18056
|
+
return records.slice(timestampBoundary);
|
|
18057
|
+
}
|
|
18058
|
+
if (baseline.lastEventSignature || baseline.lastEventTimestamp) return [];
|
|
18059
|
+
if (baseline.lastEventCount > recovery.scannedRecordCount) {
|
|
18060
|
+
const boundary = baseline.lastEventCount - recovery.scannedRecordCount;
|
|
18061
|
+
if (boundary >= records.length) return [];
|
|
18062
|
+
recovery.boundaryFound = true;
|
|
18063
|
+
return records.slice(boundary);
|
|
18064
|
+
}
|
|
18065
|
+
recovery.boundaryFound = true;
|
|
18066
|
+
return records;
|
|
18067
|
+
}
|
|
18068
|
+
function filterFirstRecoveredAssistantDuplicates(subscription, records) {
|
|
18069
|
+
const recovery = subscription.recoveryState;
|
|
18070
|
+
if (!recovery?.dedupePending || records.length === 0) return records;
|
|
18071
|
+
const filtered = filterDuplicateAssistantEvents(recovery.baselineCursor, records);
|
|
18072
|
+
if (filtered.length > 0) {
|
|
18073
|
+
recovery.dedupePending = false;
|
|
18074
|
+
}
|
|
18075
|
+
return filtered;
|
|
18076
|
+
}
|
|
18077
|
+
function readMirrorDeliverableRecords(subscription, snapshot, options = {}) {
|
|
17916
18078
|
let deliverableRecords = [];
|
|
17917
18079
|
let unknownKinds = [];
|
|
17918
|
-
const
|
|
18080
|
+
const previousOffset = subscription.fileOffset;
|
|
18081
|
+
const configuredMaxReadBytes = options.maxReadBytes ?? DEFAULT_MAX_MIRROR_READ_BYTES;
|
|
18082
|
+
const maxReadBytes = Number.isFinite(configuredMaxReadBytes) && configuredMaxReadBytes > 0 ? Math.max(1, Math.floor(configuredMaxReadBytes)) : DEFAULT_MAX_MIRROR_READ_BYTES;
|
|
18083
|
+
const sourceChanged = subscription.fileIdentity !== null && subscription.fileIdentity !== snapshot.identity || subscription.fileSize !== null && snapshot.size < subscription.fileOffset || subscription.fileSize !== null && snapshot.size === subscription.fileOffset && subscription.fileMtimeMs !== null && snapshot.mtimeMs !== subscription.fileMtimeMs;
|
|
18084
|
+
const requiresFullRecover = Boolean(subscription.recoveryState) || !subscription.cursor.initialized || subscription.fileOffset === 0 || sourceChanged;
|
|
17919
18085
|
if (requiresFullRecover && !subscription.cursor.initialized && !subscription.lastDeliveredAt) {
|
|
17920
18086
|
subscription.cursor = { initialized: true, lastEventCount: 0 };
|
|
18087
|
+
subscription.recoveryState = null;
|
|
17921
18088
|
subscription.trailingText = "";
|
|
17922
18089
|
subscription.fileOffset = findLastCompleteLineOffset(subscription.filePath, snapshot.size);
|
|
17923
18090
|
subscription.activeMirrorTurnId = null;
|
|
@@ -17926,26 +18093,37 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
17926
18093
|
subscription.fileMtimeMs = snapshot.mtimeMs;
|
|
17927
18094
|
subscription.fileIdentity = snapshot.identity;
|
|
17928
18095
|
subscription.dirty = false;
|
|
17929
|
-
return { records: [], unknownKinds: [] };
|
|
18096
|
+
return { records: [], unknownKinds: [], hasMoreData: false };
|
|
17930
18097
|
}
|
|
17931
18098
|
if (requiresFullRecover) {
|
|
17932
|
-
|
|
17933
|
-
|
|
18099
|
+
if (!subscription.recoveryState || sourceChanged) {
|
|
18100
|
+
beginMirrorRecovery(subscription);
|
|
18101
|
+
}
|
|
18102
|
+
const recovery = subscription.recoveryState;
|
|
18103
|
+
const delta = readDesktopSessionMirrorRecordDeltaByFilePath(
|
|
17934
18104
|
subscription.filePath,
|
|
17935
|
-
|
|
18105
|
+
subscription.fileOffset,
|
|
17936
18106
|
snapshot.size,
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
18107
|
+
subscription.trailingText,
|
|
18108
|
+
subscription.activeMirrorTurnId,
|
|
18109
|
+
subscription.activeSpecialCallIds,
|
|
18110
|
+
{ maxBytes: maxReadBytes }
|
|
18111
|
+
);
|
|
18112
|
+
deliverableRecords = filterFirstRecoveredAssistantDuplicates(
|
|
18113
|
+
subscription,
|
|
18114
|
+
selectRecoveredRecords(subscription, delta.records)
|
|
17940
18115
|
);
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
subscription.
|
|
17945
|
-
subscription.
|
|
17946
|
-
subscription.
|
|
17947
|
-
|
|
17948
|
-
|
|
18116
|
+
recovery.scannedCursor = advanceDesktopMirrorCursor(recovery.scannedCursor, delta.records);
|
|
18117
|
+
recovery.scannedRecordCount += delta.records.length;
|
|
18118
|
+
subscription.trailingText = delta.trailingText;
|
|
18119
|
+
subscription.fileOffset = delta.nextOffset;
|
|
18120
|
+
subscription.activeMirrorTurnId = delta.nextTurnId;
|
|
18121
|
+
subscription.activeSpecialCallIds = new Set(delta.nextSpecialCallIds);
|
|
18122
|
+
unknownKinds = delta.unknownKinds;
|
|
18123
|
+
if (subscription.fileOffset >= snapshot.size && !subscription.trailingText) {
|
|
18124
|
+
subscription.cursor = recovery.scannedCursor;
|
|
18125
|
+
subscription.recoveryState = null;
|
|
18126
|
+
}
|
|
17949
18127
|
} else if (snapshot.size > subscription.fileOffset || subscription.trailingText) {
|
|
17950
18128
|
const previousCursor = subscription.cursor;
|
|
17951
18129
|
const delta = readDesktopSessionMirrorRecordDeltaByFilePath(
|
|
@@ -17954,7 +18132,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
17954
18132
|
snapshot.size,
|
|
17955
18133
|
subscription.trailingText,
|
|
17956
18134
|
subscription.activeMirrorTurnId,
|
|
17957
|
-
subscription.activeSpecialCallIds
|
|
18135
|
+
subscription.activeSpecialCallIds,
|
|
18136
|
+
{ maxBytes: maxReadBytes }
|
|
17958
18137
|
);
|
|
17959
18138
|
deliverableRecords = filterDuplicateAssistantEvents(previousCursor, delta.records);
|
|
17960
18139
|
subscription.cursor = advanceDesktopMirrorCursor(subscription.cursor, delta.records);
|
|
@@ -17970,7 +18149,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
17970
18149
|
subscription.dirty = false;
|
|
17971
18150
|
return {
|
|
17972
18151
|
records: deliverableRecords,
|
|
17973
|
-
unknownKinds
|
|
18152
|
+
unknownKinds,
|
|
18153
|
+
hasMoreData: subscription.fileOffset > previousOffset && subscription.fileOffset < snapshot.size
|
|
17974
18154
|
};
|
|
17975
18155
|
}
|
|
17976
18156
|
|
|
@@ -18298,6 +18478,9 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
18298
18478
|
return "processed";
|
|
18299
18479
|
}
|
|
18300
18480
|
const readResult = readMirrorDeliverableRecords(subscription, snapshot);
|
|
18481
|
+
if (readResult.hasMoreData) {
|
|
18482
|
+
scheduleMirrorWake();
|
|
18483
|
+
}
|
|
18301
18484
|
const deliverableRecords = readResult.records;
|
|
18302
18485
|
for (const kind of readResult.unknownKinds) {
|
|
18303
18486
|
if (subscription.unknownMirrorKindsSeen.has(kind)) continue;
|
|
@@ -18423,8 +18606,41 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
18423
18606
|
}
|
|
18424
18607
|
}
|
|
18425
18608
|
}
|
|
18609
|
+
function interruptMirrorTurn(sessionId, threadId, turnId) {
|
|
18610
|
+
const normalizedTurnId = turnId.trim();
|
|
18611
|
+
if (!normalizedTurnId) return false;
|
|
18612
|
+
const streamKey = buildMirrorStreamKey(sessionId, normalizedTurnId, "");
|
|
18613
|
+
let interrupted = false;
|
|
18614
|
+
for (const subscription of getState2().mirrorSubscriptions.values()) {
|
|
18615
|
+
if (subscription.sessionId !== sessionId || subscription.threadId !== threadId) continue;
|
|
18616
|
+
let subscriptionInterrupted = false;
|
|
18617
|
+
const bufferedCount = subscription.bufferedRecords.length;
|
|
18618
|
+
const deliveryCount = subscription.pendingDeliveries.length;
|
|
18619
|
+
subscription.bufferedRecords = subscription.bufferedRecords.filter(
|
|
18620
|
+
(record) => record.turnId !== normalizedTurnId
|
|
18621
|
+
);
|
|
18622
|
+
subscription.pendingDeliveries = subscription.pendingDeliveries.filter(
|
|
18623
|
+
(turn) => turn.streamKey !== streamKey
|
|
18624
|
+
);
|
|
18625
|
+
if (subscription.pendingTurn?.turnId === normalizedTurnId) {
|
|
18626
|
+
deps.stopMirrorStreaming(subscription, "interrupted");
|
|
18627
|
+
subscription.pendingTurn = null;
|
|
18628
|
+
subscriptionInterrupted = true;
|
|
18629
|
+
}
|
|
18630
|
+
if (subscription.bufferedRecords.length !== bufferedCount || subscription.pendingDeliveries.length !== deliveryCount) {
|
|
18631
|
+
subscriptionInterrupted = true;
|
|
18632
|
+
}
|
|
18633
|
+
if (subscriptionInterrupted) {
|
|
18634
|
+
interrupted = true;
|
|
18635
|
+
orphanProbeAt.delete(subscription.bindingId);
|
|
18636
|
+
deps.syncMirrorSessionStateSafe(sessionId, "mirror turn interrupted from IM");
|
|
18637
|
+
}
|
|
18638
|
+
}
|
|
18639
|
+
return interrupted;
|
|
18640
|
+
}
|
|
18426
18641
|
return {
|
|
18427
18642
|
resetMirrorSessionForInteractiveRun: resetMirrorSessionForInteractiveRun2,
|
|
18643
|
+
interruptMirrorTurn,
|
|
18428
18644
|
reconcileMirrorSubscriptions: reconcileMirrorSubscriptions2,
|
|
18429
18645
|
clearMirrorSubscriptions: clearMirrorSubscriptions2
|
|
18430
18646
|
};
|
|
@@ -19453,7 +19669,11 @@ function createTurnCoordinator(deps = {}) {
|
|
|
19453
19669
|
const normalizedTurnId = turnId.trim();
|
|
19454
19670
|
if (!turn || turn.kind !== "im_desktop_reuse" || !normalizedTurnId) return false;
|
|
19455
19671
|
if (turn.codexTurnId && turn.codexTurnId !== normalizedTurnId) return false;
|
|
19672
|
+
const changed = turn.codexTurnId !== normalizedTurnId;
|
|
19456
19673
|
turn.codexTurnId = normalizedTurnId;
|
|
19674
|
+
if (changed) {
|
|
19675
|
+
turn.onDesktopTurnAssociated?.(normalizedTurnId);
|
|
19676
|
+
}
|
|
19457
19677
|
return true;
|
|
19458
19678
|
}
|
|
19459
19679
|
async function claimDesktopTerminal(terminal) {
|
|
@@ -19497,6 +19717,354 @@ function createTurnCoordinator(deps = {}) {
|
|
|
19497
19717
|
};
|
|
19498
19718
|
}
|
|
19499
19719
|
|
|
19720
|
+
// src/lib/bridge/managed-codex-process.ts
|
|
19721
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
19722
|
+
import { promisify as promisify2 } from "node:util";
|
|
19723
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
19724
|
+
var PROCESS_QUERY_TIMEOUT_MS = 5e3;
|
|
19725
|
+
var PROCESS_STOP_TIMEOUT_MS = 1e4;
|
|
19726
|
+
var PROCESS_EXIT_WAIT_MS = 5e3;
|
|
19727
|
+
function escapePowerShellSingleQuoted2(value) {
|
|
19728
|
+
return value.replace(/'/g, "''");
|
|
19729
|
+
}
|
|
19730
|
+
function escapeRegex(value) {
|
|
19731
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19732
|
+
}
|
|
19733
|
+
function normalizeSnapshot(raw) {
|
|
19734
|
+
if (!raw || typeof raw !== "object") return null;
|
|
19735
|
+
const record = raw;
|
|
19736
|
+
const pid = Number(record.ProcessId);
|
|
19737
|
+
const parentPid = Number(record.ParentProcessId);
|
|
19738
|
+
const createdAt = typeof record.CreationDate === "string" ? record.CreationDate.trim() : "";
|
|
19739
|
+
const name = typeof record.Name === "string" ? record.Name.trim() : "";
|
|
19740
|
+
const commandLine = typeof record.CommandLine === "string" ? record.CommandLine.trim() : "";
|
|
19741
|
+
if (!Number.isInteger(pid) || pid <= 0 || !Number.isInteger(parentPid) || parentPid <= 0) {
|
|
19742
|
+
return null;
|
|
19743
|
+
}
|
|
19744
|
+
if (!createdAt || !name || !commandLine) return null;
|
|
19745
|
+
return { pid, parentPid, createdAt, name, commandLine };
|
|
19746
|
+
}
|
|
19747
|
+
function parseSnapshots(stdout) {
|
|
19748
|
+
const trimmed = stdout.trim();
|
|
19749
|
+
if (!trimmed) return [];
|
|
19750
|
+
const parsed = JSON.parse(trimmed);
|
|
19751
|
+
const records = Array.isArray(parsed) ? parsed : [parsed];
|
|
19752
|
+
return records.map((record) => normalizeSnapshot(record)).filter((record) => Boolean(record));
|
|
19753
|
+
}
|
|
19754
|
+
function isManagedCodexExecProcess(processSnapshot, expected) {
|
|
19755
|
+
if (processSnapshot.name.toLowerCase() !== "codex.exe") return false;
|
|
19756
|
+
if (processSnapshot.parentPid !== expected.parentPid) return false;
|
|
19757
|
+
const commandLine = processSnapshot.commandLine;
|
|
19758
|
+
if (!/(?:^|\s)exec(?=\s|$)/i.test(commandLine)) return false;
|
|
19759
|
+
const threadId = escapeRegex(expected.threadId.trim());
|
|
19760
|
+
if (!threadId) return false;
|
|
19761
|
+
const resumePattern = new RegExp(
|
|
19762
|
+
`(?:^|\\s)resume\\s+(?:"${threadId}"|${threadId})(?=\\s|$)`,
|
|
19763
|
+
"i"
|
|
19764
|
+
);
|
|
19765
|
+
return resumePattern.test(commandLine);
|
|
19766
|
+
}
|
|
19767
|
+
async function captureManagedCodexExecProcess(threadId, ownerPid = process.pid) {
|
|
19768
|
+
const normalizedThreadId = threadId.trim();
|
|
19769
|
+
if (process.platform !== "win32" || !normalizedThreadId || !Number.isInteger(ownerPid) || ownerPid <= 0) {
|
|
19770
|
+
return null;
|
|
19771
|
+
}
|
|
19772
|
+
const escapedThreadId = escapePowerShellSingleQuoted2(normalizedThreadId);
|
|
19773
|
+
const script = [
|
|
19774
|
+
`$threadId = '${escapedThreadId}'`,
|
|
19775
|
+
"$escaped = [regex]::Escape($threadId)",
|
|
19776
|
+
`$ownerPid = ${ownerPid}`,
|
|
19777
|
+
"$procs = Get-CimInstance Win32_Process | Where-Object {",
|
|
19778
|
+
" $_.Name -ieq 'codex.exe' -and [int]$_.ParentProcessId -eq $ownerPid -and $_.CommandLine -match $escaped",
|
|
19779
|
+
"} | Select-Object ProcessId, ParentProcessId, CreationDate, Name, CommandLine",
|
|
19780
|
+
"if ($null -eq $procs) { '' } else { $procs | ConvertTo-Json -Compress }"
|
|
19781
|
+
].join("; ");
|
|
19782
|
+
try {
|
|
19783
|
+
const { stdout } = await execFileAsync2(
|
|
19784
|
+
"powershell.exe",
|
|
19785
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
19786
|
+
{
|
|
19787
|
+
windowsHide: true,
|
|
19788
|
+
timeout: PROCESS_QUERY_TIMEOUT_MS,
|
|
19789
|
+
maxBuffer: 512 * 1024,
|
|
19790
|
+
encoding: "utf8"
|
|
19791
|
+
}
|
|
19792
|
+
);
|
|
19793
|
+
const snapshot = parseSnapshots(stdout).find((candidate) => isManagedCodexExecProcess(candidate, {
|
|
19794
|
+
threadId: normalizedThreadId,
|
|
19795
|
+
parentPid: ownerPid
|
|
19796
|
+
}));
|
|
19797
|
+
return snapshot ? {
|
|
19798
|
+
threadId: normalizedThreadId,
|
|
19799
|
+
pid: snapshot.pid,
|
|
19800
|
+
parentPid: snapshot.parentPid,
|
|
19801
|
+
createdAt: snapshot.createdAt
|
|
19802
|
+
} : null;
|
|
19803
|
+
} catch {
|
|
19804
|
+
return null;
|
|
19805
|
+
}
|
|
19806
|
+
}
|
|
19807
|
+
async function inspectManagedCodexProcess(pid) {
|
|
19808
|
+
if (process.platform !== "win32") {
|
|
19809
|
+
return { status: "unsupported", error: "Managed Desktop task stopping is currently supported only on Windows." };
|
|
19810
|
+
}
|
|
19811
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
19812
|
+
return { status: "error", error: "Invalid Codex process id." };
|
|
19813
|
+
}
|
|
19814
|
+
const script = [
|
|
19815
|
+
`$proc = Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}" | Select-Object -First 1 ProcessId, ParentProcessId, CreationDate, Name, CommandLine`,
|
|
19816
|
+
"if ($null -eq $proc) { '' } else { $proc | ConvertTo-Json -Compress }"
|
|
19817
|
+
].join("; ");
|
|
19818
|
+
try {
|
|
19819
|
+
const { stdout } = await execFileAsync2(
|
|
19820
|
+
"powershell.exe",
|
|
19821
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
19822
|
+
{
|
|
19823
|
+
windowsHide: true,
|
|
19824
|
+
timeout: PROCESS_QUERY_TIMEOUT_MS,
|
|
19825
|
+
maxBuffer: 512 * 1024,
|
|
19826
|
+
encoding: "utf8"
|
|
19827
|
+
}
|
|
19828
|
+
);
|
|
19829
|
+
const snapshot = parseSnapshots(stdout)[0];
|
|
19830
|
+
return snapshot ? { status: "found", process: snapshot } : { status: "not_found" };
|
|
19831
|
+
} catch (error) {
|
|
19832
|
+
return { status: "error", error: error instanceof Error ? error.message : String(error) };
|
|
19833
|
+
}
|
|
19834
|
+
}
|
|
19835
|
+
async function terminateWindowsProcessTree(pid) {
|
|
19836
|
+
await execFileAsync2(
|
|
19837
|
+
"taskkill.exe",
|
|
19838
|
+
["/PID", String(pid), "/T", "/F"],
|
|
19839
|
+
{
|
|
19840
|
+
windowsHide: true,
|
|
19841
|
+
timeout: PROCESS_STOP_TIMEOUT_MS,
|
|
19842
|
+
maxBuffer: 512 * 1024
|
|
19843
|
+
}
|
|
19844
|
+
);
|
|
19845
|
+
}
|
|
19846
|
+
function sleep(delayMs) {
|
|
19847
|
+
return new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
19848
|
+
}
|
|
19849
|
+
async function stopManagedCodexExecProcess(identity, deps = {}) {
|
|
19850
|
+
const inspectProcess = deps.inspectProcess ?? inspectManagedCodexProcess;
|
|
19851
|
+
const terminateProcessTree = deps.terminateProcessTree ?? terminateWindowsProcessTree;
|
|
19852
|
+
const sleepFn = deps.sleep ?? sleep;
|
|
19853
|
+
const nowMs = deps.nowMs ?? (() => Date.now());
|
|
19854
|
+
const initial = await inspectProcess(identity.pid);
|
|
19855
|
+
if (initial.status === "not_found") return { status: "not_running" };
|
|
19856
|
+
if (initial.status === "unsupported") return initial;
|
|
19857
|
+
if (initial.status === "error") return { status: "failed", error: initial.error };
|
|
19858
|
+
if (initial.process.createdAt !== identity.createdAt || !isManagedCodexExecProcess(initial.process, identity)) {
|
|
19859
|
+
return {
|
|
19860
|
+
status: "unsafe",
|
|
19861
|
+
error: "The recorded process identity no longer matches the bridge-owned Codex exec process."
|
|
19862
|
+
};
|
|
19863
|
+
}
|
|
19864
|
+
try {
|
|
19865
|
+
await terminateProcessTree(identity.pid);
|
|
19866
|
+
} catch (error) {
|
|
19867
|
+
const afterFailure = await inspectProcess(identity.pid);
|
|
19868
|
+
if (afterFailure.status === "not_found") return { status: "stopped" };
|
|
19869
|
+
return { status: "failed", error: error instanceof Error ? error.message : String(error) };
|
|
19870
|
+
}
|
|
19871
|
+
const deadline = nowMs() + PROCESS_EXIT_WAIT_MS;
|
|
19872
|
+
while (nowMs() < deadline) {
|
|
19873
|
+
const inspection = await inspectProcess(identity.pid);
|
|
19874
|
+
if (inspection.status === "not_found") return { status: "stopped" };
|
|
19875
|
+
if (inspection.status === "found") {
|
|
19876
|
+
if (inspection.process.createdAt !== identity.createdAt || !isManagedCodexExecProcess(inspection.process, identity)) {
|
|
19877
|
+
return { status: "stopped" };
|
|
19878
|
+
}
|
|
19879
|
+
} else if (inspection.status === "unsupported") {
|
|
19880
|
+
return inspection;
|
|
19881
|
+
} else {
|
|
19882
|
+
return { status: "failed", error: inspection.error };
|
|
19883
|
+
}
|
|
19884
|
+
await sleepFn(100);
|
|
19885
|
+
}
|
|
19886
|
+
return { status: "failed", error: "Codex process did not exit before the stop timeout." };
|
|
19887
|
+
}
|
|
19888
|
+
|
|
19889
|
+
// src/lib/bridge/desktop-handoff-control.ts
|
|
19890
|
+
function clearHandoffFields() {
|
|
19891
|
+
return {
|
|
19892
|
+
desktop_handoff_task_id: void 0,
|
|
19893
|
+
desktop_handoff_thread_id: void 0,
|
|
19894
|
+
desktop_handoff_turn_id: void 0,
|
|
19895
|
+
desktop_handoff_owner_pid: void 0,
|
|
19896
|
+
desktop_handoff_process_pid: void 0,
|
|
19897
|
+
desktop_handoff_process_parent_pid: void 0,
|
|
19898
|
+
desktop_handoff_process_created_at: void 0,
|
|
19899
|
+
desktop_handoff_registered_at: void 0,
|
|
19900
|
+
desktop_handoff_active: void 0
|
|
19901
|
+
};
|
|
19902
|
+
}
|
|
19903
|
+
function readTask(session) {
|
|
19904
|
+
const sessionId = session?.id || "";
|
|
19905
|
+
const taskId = session?.desktop_handoff_task_id?.trim() || "";
|
|
19906
|
+
const threadId = session?.desktop_handoff_thread_id?.trim() || "";
|
|
19907
|
+
const turnId = session?.desktop_handoff_turn_id?.trim() || "";
|
|
19908
|
+
const registeredAt = session?.desktop_handoff_registered_at?.trim() || "";
|
|
19909
|
+
const ownerPid = Number(session?.desktop_handoff_owner_pid);
|
|
19910
|
+
if (!sessionId || !taskId || !threadId || !turnId || !registeredAt) return null;
|
|
19911
|
+
if (!Number.isInteger(ownerPid) || ownerPid <= 0) return null;
|
|
19912
|
+
const pid = Number(session?.desktop_handoff_process_pid);
|
|
19913
|
+
const parentPid = Number(session?.desktop_handoff_process_parent_pid);
|
|
19914
|
+
const createdAt = session?.desktop_handoff_process_created_at?.trim() || "";
|
|
19915
|
+
const hasProcessIdentity = Number.isInteger(pid) && pid > 0 && Number.isInteger(parentPid) && parentPid > 0 && Boolean(createdAt);
|
|
19916
|
+
return {
|
|
19917
|
+
sessionId,
|
|
19918
|
+
taskId,
|
|
19919
|
+
threadId,
|
|
19920
|
+
turnId,
|
|
19921
|
+
ownerPid,
|
|
19922
|
+
registeredAt,
|
|
19923
|
+
active: session?.desktop_handoff_active === true,
|
|
19924
|
+
processIdentity: hasProcessIdentity ? {
|
|
19925
|
+
threadId,
|
|
19926
|
+
pid,
|
|
19927
|
+
parentPid,
|
|
19928
|
+
createdAt
|
|
19929
|
+
} : null
|
|
19930
|
+
};
|
|
19931
|
+
}
|
|
19932
|
+
function isTerminalRecord2(record) {
|
|
19933
|
+
return record.type === "task_complete" || record.type === "task_aborted";
|
|
19934
|
+
}
|
|
19935
|
+
function createDesktopHandoffControl(deps) {
|
|
19936
|
+
const captureProcess = deps.captureProcess ?? captureManagedCodexExecProcess;
|
|
19937
|
+
const stopProcess = deps.stopProcess ?? stopManagedCodexExecProcess;
|
|
19938
|
+
const nowIso4 = deps.nowIso ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
19939
|
+
const ownerPid = deps.ownerPid ?? process.pid;
|
|
19940
|
+
function releaseTask(sessionId, taskId) {
|
|
19941
|
+
const store = deps.getStore();
|
|
19942
|
+
const current = readTask(store.getSession(sessionId));
|
|
19943
|
+
if (!current) return;
|
|
19944
|
+
if (taskId && current.taskId !== taskId) return;
|
|
19945
|
+
store.updateSession(sessionId, clearHandoffFields(), { touch: false });
|
|
19946
|
+
}
|
|
19947
|
+
async function prepareTask(task) {
|
|
19948
|
+
const store = deps.getStore();
|
|
19949
|
+
const session = store.getSession(task.sessionId);
|
|
19950
|
+
if (!session) return false;
|
|
19951
|
+
const registeredAt = nowIso4();
|
|
19952
|
+
store.updateSession(task.sessionId, {
|
|
19953
|
+
...clearHandoffFields(),
|
|
19954
|
+
desktop_handoff_task_id: task.taskId,
|
|
19955
|
+
desktop_handoff_thread_id: task.threadId,
|
|
19956
|
+
desktop_handoff_turn_id: task.turnId,
|
|
19957
|
+
desktop_handoff_owner_pid: ownerPid,
|
|
19958
|
+
desktop_handoff_registered_at: registeredAt,
|
|
19959
|
+
desktop_handoff_active: false
|
|
19960
|
+
}, { touch: false });
|
|
19961
|
+
let identity = null;
|
|
19962
|
+
try {
|
|
19963
|
+
identity = await captureProcess(task.threadId, ownerPid);
|
|
19964
|
+
} catch (error) {
|
|
19965
|
+
console.warn(
|
|
19966
|
+
`[desktop-handoff] Failed to capture Codex exec process for ${task.threadId}:`,
|
|
19967
|
+
error instanceof Error ? error.message : error
|
|
19968
|
+
);
|
|
19969
|
+
}
|
|
19970
|
+
if (!identity) return false;
|
|
19971
|
+
const current = readTask(store.getSession(task.sessionId));
|
|
19972
|
+
if (!current || current.taskId !== task.taskId || current.turnId !== task.turnId) return false;
|
|
19973
|
+
store.updateSession(task.sessionId, {
|
|
19974
|
+
desktop_handoff_process_pid: identity.pid,
|
|
19975
|
+
desktop_handoff_process_parent_pid: identity.parentPid,
|
|
19976
|
+
desktop_handoff_process_created_at: identity.createdAt
|
|
19977
|
+
}, { touch: false });
|
|
19978
|
+
return true;
|
|
19979
|
+
}
|
|
19980
|
+
function hasTask(sessionId) {
|
|
19981
|
+
return readTask(deps.getStore().getSession(sessionId))?.active === true;
|
|
19982
|
+
}
|
|
19983
|
+
function activateTask(sessionId, taskId) {
|
|
19984
|
+
const store = deps.getStore();
|
|
19985
|
+
const current = readTask(store.getSession(sessionId));
|
|
19986
|
+
if (!current || current.taskId !== taskId) return false;
|
|
19987
|
+
store.updateSession(sessionId, { desktop_handoff_active: true }, { touch: false });
|
|
19988
|
+
return true;
|
|
19989
|
+
}
|
|
19990
|
+
async function stopTask(sessionId) {
|
|
19991
|
+
const store = deps.getStore();
|
|
19992
|
+
let task = readTask(store.getSession(sessionId));
|
|
19993
|
+
if (!task || !task.active) return { status: "not_running" };
|
|
19994
|
+
if (!task.processIdentity) {
|
|
19995
|
+
let identity = null;
|
|
19996
|
+
try {
|
|
19997
|
+
identity = await captureProcess(task.threadId, task.ownerPid);
|
|
19998
|
+
} catch (error) {
|
|
19999
|
+
return {
|
|
20000
|
+
status: "failed",
|
|
20001
|
+
task,
|
|
20002
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20003
|
+
};
|
|
20004
|
+
}
|
|
20005
|
+
if (!identity) {
|
|
20006
|
+
return {
|
|
20007
|
+
status: "unavailable",
|
|
20008
|
+
task,
|
|
20009
|
+
error: "\u672A\u627E\u5230\u53EF\u9A8C\u8BC1\u4E3A bridge \u81EA\u6709\u7684 Codex exec \u8FDB\u7A0B\u3002"
|
|
20010
|
+
};
|
|
20011
|
+
}
|
|
20012
|
+
store.updateSession(sessionId, {
|
|
20013
|
+
desktop_handoff_process_pid: identity.pid,
|
|
20014
|
+
desktop_handoff_process_parent_pid: identity.parentPid,
|
|
20015
|
+
desktop_handoff_process_created_at: identity.createdAt
|
|
20016
|
+
}, { touch: false });
|
|
20017
|
+
task = { ...task, processIdentity: identity };
|
|
20018
|
+
}
|
|
20019
|
+
const processIdentity = task.processIdentity;
|
|
20020
|
+
if (!processIdentity) {
|
|
20021
|
+
return { status: "unavailable", task, error: "\u672A\u80FD\u4FDD\u5B58\u53EF\u9A8C\u8BC1\u7684 Codex exec \u8FDB\u7A0B\u8EAB\u4EFD\u3002" };
|
|
20022
|
+
}
|
|
20023
|
+
let stopped;
|
|
20024
|
+
try {
|
|
20025
|
+
stopped = await stopProcess(processIdentity);
|
|
20026
|
+
} catch (error) {
|
|
20027
|
+
return {
|
|
20028
|
+
status: "failed",
|
|
20029
|
+
task,
|
|
20030
|
+
error: error instanceof Error ? error.message : String(error)
|
|
20031
|
+
};
|
|
20032
|
+
}
|
|
20033
|
+
if (stopped.status === "stopped") {
|
|
20034
|
+
releaseTask(sessionId, task.taskId);
|
|
20035
|
+
return { status: "stopped", task };
|
|
20036
|
+
}
|
|
20037
|
+
if (stopped.status === "not_running") {
|
|
20038
|
+
releaseTask(sessionId, task.taskId);
|
|
20039
|
+
return { status: "not_running", task };
|
|
20040
|
+
}
|
|
20041
|
+
if (stopped.status === "unsafe") {
|
|
20042
|
+
releaseTask(sessionId, task.taskId);
|
|
20043
|
+
return { status: "unavailable", task, error: stopped.error };
|
|
20044
|
+
}
|
|
20045
|
+
if (stopped.status === "unsupported") {
|
|
20046
|
+
return { status: "unavailable", task, error: stopped.error };
|
|
20047
|
+
}
|
|
20048
|
+
return { status: "failed", task, error: stopped.error };
|
|
20049
|
+
}
|
|
20050
|
+
function observeRecords(sessionId, threadId, records) {
|
|
20051
|
+
if (records.length === 0) return;
|
|
20052
|
+
const task = readTask(deps.getStore().getSession(sessionId));
|
|
20053
|
+
if (!task || task.threadId !== threadId) return;
|
|
20054
|
+
if (records.some((record) => record.turnId === task.turnId && isTerminalRecord2(record))) {
|
|
20055
|
+
releaseTask(sessionId, task.taskId);
|
|
20056
|
+
}
|
|
20057
|
+
}
|
|
20058
|
+
return {
|
|
20059
|
+
prepareTask,
|
|
20060
|
+
activateTask,
|
|
20061
|
+
releaseTask,
|
|
20062
|
+
hasTask,
|
|
20063
|
+
stopTask,
|
|
20064
|
+
observeRecords
|
|
20065
|
+
};
|
|
20066
|
+
}
|
|
20067
|
+
|
|
19500
20068
|
// src/lib/bridge/bridge-manager.ts
|
|
19501
20069
|
var GLOBAL_KEY = "__bridge_manager__";
|
|
19502
20070
|
var DANGLING_MIRROR_THREAD_RETRY_LIMIT = 3;
|
|
@@ -19596,6 +20164,10 @@ var INTERACTIVE_RUNTIME = createInteractiveRuntime(getState, {
|
|
|
19596
20164
|
getStore: () => getBridgeContext().store,
|
|
19597
20165
|
nowIso: nowIso3
|
|
19598
20166
|
});
|
|
20167
|
+
var DESKTOP_HANDOFF_CONTROL = createDesktopHandoffControl({
|
|
20168
|
+
getStore: () => getBridgeContext().store,
|
|
20169
|
+
nowIso: nowIso3
|
|
20170
|
+
});
|
|
19599
20171
|
function formatDesktopTerminalDetail(terminal) {
|
|
19600
20172
|
if (terminal.outcome === "aborted") {
|
|
19601
20173
|
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
@@ -19679,6 +20251,17 @@ function abortMirrorSuppression2(sessionId, suppressionId) {
|
|
|
19679
20251
|
suppressionId
|
|
19680
20252
|
);
|
|
19681
20253
|
}
|
|
20254
|
+
function handoffMirrorSuppression2(sessionId, suppressionId) {
|
|
20255
|
+
handoffMirrorSuppression(getMirrorSuppressionStore(), sessionId, suppressionId);
|
|
20256
|
+
}
|
|
20257
|
+
function ignoreMirrorTurn2(sessionId, turnId) {
|
|
20258
|
+
ignoreMirrorTurn(
|
|
20259
|
+
getMirrorSuppressionStore(),
|
|
20260
|
+
sessionId,
|
|
20261
|
+
MIRROR_SUPPRESSION_CONFIG,
|
|
20262
|
+
turnId
|
|
20263
|
+
);
|
|
20264
|
+
}
|
|
19682
20265
|
function settleMirrorSuppression2(sessionId, suppressionId, durationMs = MIRROR_SUPPRESSION_WINDOW_MS) {
|
|
19683
20266
|
settleMirrorSuppression(
|
|
19684
20267
|
getMirrorSuppressionStore(),
|
|
@@ -19803,6 +20386,7 @@ var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
|
19803
20386
|
syncMirrorSessionStateSafe,
|
|
19804
20387
|
filterSuppressedMirrorRecords: filterSuppressedMirrorRecords2,
|
|
19805
20388
|
observeSessionHealthRecords: (sessionId, threadId, records) => {
|
|
20389
|
+
DESKTOP_HANDOFF_CONTROL.observeRecords(sessionId, threadId, records);
|
|
19806
20390
|
SESSION_HEALTH_RUNTIME.observeDesktopMirrorRecords(sessionId, threadId, records);
|
|
19807
20391
|
},
|
|
19808
20392
|
recordOrphanedMirrorTurn: (sessionId, detail) => {
|
|
@@ -19835,6 +20419,51 @@ async function reconcileMirrorSubscriptions() {
|
|
|
19835
20419
|
function clearMirrorSubscriptions() {
|
|
19836
20420
|
MIRROR_RUNTIME.clearMirrorSubscriptions();
|
|
19837
20421
|
}
|
|
20422
|
+
async function forceStopBridgeSession(sessionId, detail) {
|
|
20423
|
+
const activeTask = INTERACTIVE_RUNTIME.getActiveTask(sessionId);
|
|
20424
|
+
const hadQueuedWork = INTERACTIVE_RUNTIME.getQueuedCount(sessionId) > 0;
|
|
20425
|
+
const handoffResult = DESKTOP_HANDOFF_CONTROL.hasTask(sessionId) ? await DESKTOP_HANDOFF_CONTROL.stopTask(sessionId) : null;
|
|
20426
|
+
let interactiveHandled = false;
|
|
20427
|
+
if (activeTask || hadQueuedWork) {
|
|
20428
|
+
interactiveHandled = await INTERACTIVE_RUNTIME.forceStopSession(sessionId, detail);
|
|
20429
|
+
if (activeTask && !handoffResult) {
|
|
20430
|
+
DESKTOP_HANDOFF_CONTROL.releaseTask(sessionId, activeTask.id);
|
|
20431
|
+
}
|
|
20432
|
+
}
|
|
20433
|
+
if (handoffResult) {
|
|
20434
|
+
if (handoffResult.status === "stopped") {
|
|
20435
|
+
ignoreMirrorTurn2(sessionId, handoffResult.task.turnId);
|
|
20436
|
+
MIRROR_RUNTIME.interruptMirrorTurn(
|
|
20437
|
+
sessionId,
|
|
20438
|
+
handoffResult.task.threadId,
|
|
20439
|
+
handoffResult.task.turnId
|
|
20440
|
+
);
|
|
20441
|
+
return { status: "stopped" };
|
|
20442
|
+
}
|
|
20443
|
+
if (handoffResult.status === "unavailable") {
|
|
20444
|
+
return {
|
|
20445
|
+
status: "unavailable",
|
|
20446
|
+
detail: `${interactiveHandled ? "\u5F53\u524D IM \u8BF7\u6C42\u5DF2\u505C\u6B62\uFF0C\u4F46\u6B64\u524D\u7684 Desktop \u4EFB\u52A1\u4ECD\u65E0\u6CD5\u786E\u8BA4\u3002 " : ""}${handoffResult.error}`
|
|
20447
|
+
};
|
|
20448
|
+
}
|
|
20449
|
+
if (handoffResult.status === "failed") {
|
|
20450
|
+
return {
|
|
20451
|
+
status: "failed",
|
|
20452
|
+
detail: `${interactiveHandled ? "\u5F53\u524D IM \u8BF7\u6C42\u5DF2\u505C\u6B62\uFF1B\u6B64\u524D\u7684 Desktop \u4EFB\u52A1\u505C\u6B62\u5931\u8D25\u3002 " : ""}${handoffResult.error}`
|
|
20453
|
+
};
|
|
20454
|
+
}
|
|
20455
|
+
if (!interactiveHandled) {
|
|
20456
|
+
return {
|
|
20457
|
+
status: "not_running",
|
|
20458
|
+
detail: "\u8BB0\u5F55\u4E2D\u7684 Desktop \u4EFB\u52A1\u8FDB\u7A0B\u5DF2\u7ECF\u7ED3\u675F\uFF1B\u5982\u6709\u5C1A\u672A\u6295\u9012\u7684\u7ED3\u679C\uFF0Cmirror \u4F1A\u7EE7\u7EED\u5B8C\u6210\u6536\u5C3E\u3002"
|
|
20459
|
+
};
|
|
20460
|
+
}
|
|
20461
|
+
}
|
|
20462
|
+
if (interactiveHandled) {
|
|
20463
|
+
return activeTask ? { status: "stop_requested" } : { status: "stopped" };
|
|
20464
|
+
}
|
|
20465
|
+
return { status: "not_running" };
|
|
20466
|
+
}
|
|
19838
20467
|
var ADAPTER_RUNTIME = createAdapterRuntime(getState, {
|
|
19839
20468
|
notifyAdapterSetChanged: (channelTypes) => {
|
|
19840
20469
|
const { lifecycle } = getBridgeContext();
|
|
@@ -20095,7 +20724,11 @@ async function handleMessage(adapter, msg) {
|
|
|
20095
20724
|
recordInteractiveHealthEnd: (sessionId, outcome, detail) => SESSION_HEALTH_RUNTIME.recordInteractiveEnd(sessionId, outcome, detail),
|
|
20096
20725
|
beginMirrorSuppression: beginMirrorSuppression2,
|
|
20097
20726
|
abortMirrorSuppression: abortMirrorSuppression2,
|
|
20727
|
+
handoffMirrorSuppression: handoffMirrorSuppression2,
|
|
20098
20728
|
settleMirrorSuppression: settleMirrorSuppression2,
|
|
20729
|
+
prepareDesktopHandoffTask: (task) => DESKTOP_HANDOFF_CONTROL.prepareTask(task),
|
|
20730
|
+
activateDesktopHandoffTask: (sessionId, taskId) => DESKTOP_HANDOFF_CONTROL.activateTask(sessionId, taskId),
|
|
20731
|
+
releaseDesktopHandoffTask: (sessionId, taskId) => DESKTOP_HANDOFF_CONTROL.releaseTask(sessionId, taskId),
|
|
20099
20732
|
releaseInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.releaseInteractiveTask(sessionId, taskId),
|
|
20100
20733
|
releaseBridgeTurn: (sessionId, taskId) => TURN_COORDINATOR.releaseSessionTurn(sessionId, taskId),
|
|
20101
20734
|
deliverResponse,
|
|
@@ -20107,7 +20740,7 @@ async function handleMessage(adapter, msg) {
|
|
|
20107
20740
|
async function handleCommand(adapter, msg, text2) {
|
|
20108
20741
|
await handleBridgeCommand(adapter, msg, text2, {
|
|
20109
20742
|
getActiveTask: (sessionId) => INTERACTIVE_RUNTIME.getActiveTask(sessionId),
|
|
20110
|
-
forceStopSession:
|
|
20743
|
+
forceStopSession: forceStopBridgeSession,
|
|
20111
20744
|
recordInteractiveHealthEnd: (sessionId, outcome, detail) => SESSION_HEALTH_RUNTIME.recordInteractiveEnd(sessionId, outcome, detail),
|
|
20112
20745
|
diagnoseSessionHealth: (sessionId) => SESSION_HEALTH_RUNTIME.diagnoseSessionHealth(sessionId),
|
|
20113
20746
|
diagnoseAllActiveSessions: () => SESSION_HEALTH_RUNTIME.diagnoseAllActiveSessions()
|
package/dist/ui-server.mjs
CHANGED
|
@@ -5548,7 +5548,7 @@ function parseDesktopSession(filePath, threadIndexEntries, archivedThreadIds, vi
|
|
|
5548
5548
|
}
|
|
5549
5549
|
const lastEventAt = visibleThread?.updatedAtMs ? new Date(visibleThread.updatedAtMs).toISOString() : stat.mtime.toISOString();
|
|
5550
5550
|
const firstSeenAt = parsed.payload.timestamp || parsed.timestamp || stat.birthtime.toISOString();
|
|
5551
|
-
const title =
|
|
5551
|
+
const title = threadIndexEntries.get(threadId)?.title || visibleThread?.title || buildFallbackTitle(threadId, filePath, cwd);
|
|
5552
5552
|
return {
|
|
5553
5553
|
threadId,
|
|
5554
5554
|
filePath,
|
package/package.json
CHANGED