codex-to-im 1.0.44 → 1.0.46
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 +1118 -470
- package/dist/ui-server.mjs +48 -10
- package/docs/dev-plan.md +684 -0
- package/package.json +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -1223,14 +1223,19 @@ function buildPostContent(text2) {
|
|
|
1223
1223
|
function htmlToFeishuMarkdown(html) {
|
|
1224
1224
|
return html.replace(/<b>(.*?)<\/b>/gi, "**$1**").replace(/<strong>(.*?)<\/strong>/gi, "**$1**").replace(/<i>(.*?)<\/i>/gi, "*$1*").replace(/<em>(.*?)<\/em>/gi, "*$1*").replace(/<code>(.*?)<\/code>/gi, "`$1`").replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\n{3,}/g, "\n\n").trim();
|
|
1225
1225
|
}
|
|
1226
|
-
function
|
|
1226
|
+
function normalizeToolStatusForRender(status, options) {
|
|
1227
|
+
if (status !== "running" || !options.terminalStatus) return status;
|
|
1228
|
+
return options.terminalStatus === "completed" ? "complete" : "error";
|
|
1229
|
+
}
|
|
1230
|
+
function buildToolProgressMarkdown(tools, options = {}) {
|
|
1227
1231
|
if (tools.length === 0) return "";
|
|
1228
1232
|
const grouped = /* @__PURE__ */ new Map();
|
|
1229
1233
|
for (const tool of tools) {
|
|
1230
1234
|
const key = tool.name || "tool";
|
|
1231
1235
|
const bucket = grouped.get(key) || { running: 0, complete: 0, error: 0 };
|
|
1232
|
-
|
|
1233
|
-
|
|
1236
|
+
const status = normalizeToolStatusForRender(tool.status, options);
|
|
1237
|
+
if (status === "running") bucket.running += 1;
|
|
1238
|
+
else if (status === "error") bucket.error += 1;
|
|
1234
1239
|
else bucket.complete += 1;
|
|
1235
1240
|
grouped.set(key, bucket);
|
|
1236
1241
|
}
|
|
@@ -1249,11 +1254,25 @@ function buildToolProgressMarkdown(tools) {
|
|
|
1249
1254
|
});
|
|
1250
1255
|
return lines.join("\n");
|
|
1251
1256
|
}
|
|
1252
|
-
function
|
|
1257
|
+
function getTaskProgressPresentation(task, options) {
|
|
1258
|
+
if (!options.terminalStatus) {
|
|
1259
|
+
return task.status === "completed" ? { icon: "\u2705", label: "\u5DF2\u5B8C\u6210" } : task.status === "in_progress" ? { icon: "\u{1F504}", label: "\u6267\u884C\u4E2D" } : { icon: "\u23F3", label: "\u7B49\u5F85\u4E2D" };
|
|
1260
|
+
}
|
|
1261
|
+
if (task.status === "completed") {
|
|
1262
|
+
return { icon: "\u2705", label: "\u5DF2\u5B8C\u6210" };
|
|
1263
|
+
}
|
|
1264
|
+
if (options.terminalStatus === "completed") {
|
|
1265
|
+
return { icon: "\u2705", label: "\u5DF2\u7ED3\u675F" };
|
|
1266
|
+
}
|
|
1267
|
+
if (options.terminalStatus === "interrupted") {
|
|
1268
|
+
return task.status === "pending" ? { icon: "\u26A0\uFE0F", label: "\u672A\u6267\u884C" } : { icon: "\u26A0\uFE0F", label: "\u5DF2\u505C\u6B62" };
|
|
1269
|
+
}
|
|
1270
|
+
return task.status === "pending" ? { icon: "\u274C", label: "\u672A\u6267\u884C" } : { icon: "\u274C", label: "\u5DF2\u4E2D\u65AD" };
|
|
1271
|
+
}
|
|
1272
|
+
function buildTaskProgressMarkdown(tasks, options = {}) {
|
|
1253
1273
|
if (tasks.length === 0) return "";
|
|
1254
1274
|
return tasks.map((task) => {
|
|
1255
|
-
const icon
|
|
1256
|
-
const label = task.status === "completed" ? "\u5DF2\u5B8C\u6210" : task.status === "in_progress" ? "\u6267\u884C\u4E2D" : "\u7B49\u5F85\u4E2D";
|
|
1275
|
+
const { icon, label } = getTaskProgressPresentation(task, options);
|
|
1257
1276
|
return `${icon} ${task.text}\uFF08${label}\uFF09`;
|
|
1258
1277
|
}).join("\n");
|
|
1259
1278
|
}
|
|
@@ -1274,11 +1293,12 @@ function buildStreamingToolsContent(tools) {
|
|
|
1274
1293
|
function buildStreamingTaskContent(tasks) {
|
|
1275
1294
|
return buildTaskProgressMarkdown(tasks);
|
|
1276
1295
|
}
|
|
1277
|
-
function buildFinalCardJson(text2, tasks, tools, footer) {
|
|
1296
|
+
function buildFinalCardJson(text2, tasks, tools, footer, terminalStatus) {
|
|
1278
1297
|
const elements = [];
|
|
1279
1298
|
const content = preprocessFeishuMarkdown(text2);
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1299
|
+
const renderOptions = { terminalStatus };
|
|
1300
|
+
const taskMd = buildTaskProgressMarkdown(tasks, renderOptions);
|
|
1301
|
+
const toolMd = buildToolProgressMarkdown(tools, renderOptions);
|
|
1282
1302
|
if (content) {
|
|
1283
1303
|
elements.push({
|
|
1284
1304
|
tag: "markdown",
|
|
@@ -1384,9 +1404,53 @@ var MAX_FILE_SIZE = 20 * 1024 * 1024;
|
|
|
1384
1404
|
var TYPING_EMOJI = "Typing";
|
|
1385
1405
|
var CARD_THROTTLE_MS = 1e3;
|
|
1386
1406
|
var CARD_REQUEST_TIMEOUT_MS = 15e3;
|
|
1407
|
+
var CARD_FINALIZE_FLUSH_WAIT_EXTRA_MS = 1e3;
|
|
1408
|
+
var CARD_FULL_REFRESH_INTERVAL_MS = 5 * 6e4;
|
|
1387
1409
|
var INITIAL_STREAMING_STATUS = "\u5904\u7406\u4E2D";
|
|
1388
1410
|
var EMPTY_STREAMING_TASKS = "";
|
|
1389
1411
|
var EMPTY_STREAMING_TOOLS = "";
|
|
1412
|
+
function buildStreamingCardBody(content, tasksText, toolsText, statusText) {
|
|
1413
|
+
return {
|
|
1414
|
+
schema: "2.0",
|
|
1415
|
+
config: {
|
|
1416
|
+
streaming_mode: true,
|
|
1417
|
+
wide_screen_mode: true,
|
|
1418
|
+
summary: { content: "\u601D\u8003\u4E2D..." }
|
|
1419
|
+
},
|
|
1420
|
+
body: {
|
|
1421
|
+
elements: [
|
|
1422
|
+
{
|
|
1423
|
+
tag: "markdown",
|
|
1424
|
+
content,
|
|
1425
|
+
text_align: "left",
|
|
1426
|
+
text_size: "normal",
|
|
1427
|
+
element_id: "streaming_content"
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
tag: "markdown",
|
|
1431
|
+
content: tasksText,
|
|
1432
|
+
text_align: "left",
|
|
1433
|
+
text_size: "normal",
|
|
1434
|
+
element_id: "streaming_tasks"
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
tag: "markdown",
|
|
1438
|
+
content: toolsText,
|
|
1439
|
+
text_align: "left",
|
|
1440
|
+
text_size: "normal",
|
|
1441
|
+
element_id: "streaming_tools"
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
tag: "markdown",
|
|
1445
|
+
content: statusText,
|
|
1446
|
+
text_align: "left",
|
|
1447
|
+
text_size: "notation",
|
|
1448
|
+
element_id: "streaming_status"
|
|
1449
|
+
}
|
|
1450
|
+
]
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1390
1454
|
var MIME_BY_TYPE = {
|
|
1391
1455
|
image: "image/png",
|
|
1392
1456
|
file: "application/octet-stream",
|
|
@@ -1421,6 +1485,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1421
1485
|
/** Cached tenant token for upload APIs. */
|
|
1422
1486
|
tenantTokenCache = null;
|
|
1423
1487
|
cardRequestTimeoutMs = CARD_REQUEST_TIMEOUT_MS;
|
|
1488
|
+
cardFinalizeFlushWaitExtraMs = CARD_FINALIZE_FLUSH_WAIT_EXTRA_MS;
|
|
1489
|
+
cardFullRefreshIntervalMs = CARD_FULL_REFRESH_INTERVAL_MS;
|
|
1424
1490
|
constructor(instance) {
|
|
1425
1491
|
super();
|
|
1426
1492
|
this.channelType = instance?.id || "feishu";
|
|
@@ -1654,46 +1720,12 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1654
1720
|
return false;
|
|
1655
1721
|
}
|
|
1656
1722
|
try {
|
|
1657
|
-
const cardBody =
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
},
|
|
1664
|
-
body: {
|
|
1665
|
-
elements: [
|
|
1666
|
-
{
|
|
1667
|
-
tag: "markdown",
|
|
1668
|
-
content: "\u{1F4AD} Thinking...",
|
|
1669
|
-
text_align: "left",
|
|
1670
|
-
text_size: "normal",
|
|
1671
|
-
element_id: "streaming_content"
|
|
1672
|
-
},
|
|
1673
|
-
{
|
|
1674
|
-
tag: "markdown",
|
|
1675
|
-
content: EMPTY_STREAMING_TASKS,
|
|
1676
|
-
text_align: "left",
|
|
1677
|
-
text_size: "normal",
|
|
1678
|
-
element_id: "streaming_tasks"
|
|
1679
|
-
},
|
|
1680
|
-
{
|
|
1681
|
-
tag: "markdown",
|
|
1682
|
-
content: EMPTY_STREAMING_TOOLS,
|
|
1683
|
-
text_align: "left",
|
|
1684
|
-
text_size: "normal",
|
|
1685
|
-
element_id: "streaming_tools"
|
|
1686
|
-
},
|
|
1687
|
-
{
|
|
1688
|
-
tag: "markdown",
|
|
1689
|
-
content: INITIAL_STREAMING_STATUS,
|
|
1690
|
-
text_align: "left",
|
|
1691
|
-
text_size: "notation",
|
|
1692
|
-
element_id: "streaming_status"
|
|
1693
|
-
}
|
|
1694
|
-
]
|
|
1695
|
-
}
|
|
1696
|
-
};
|
|
1723
|
+
const cardBody = buildStreamingCardBody(
|
|
1724
|
+
"\u{1F4AD} Thinking...",
|
|
1725
|
+
EMPTY_STREAMING_TASKS,
|
|
1726
|
+
EMPTY_STREAMING_TOOLS,
|
|
1727
|
+
INITIAL_STREAMING_STATUS
|
|
1728
|
+
);
|
|
1697
1729
|
const createResp = await this.withFeishuRequestTimeout(cardKey, "card.create", () => cardkit.card.create({
|
|
1698
1730
|
data: { type: "card_json", data: JSON.stringify(cardBody) }
|
|
1699
1731
|
}));
|
|
@@ -1724,12 +1756,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1724
1756
|
console.warn("[feishu-adapter] Card message send returned no message_id");
|
|
1725
1757
|
return false;
|
|
1726
1758
|
}
|
|
1759
|
+
const now2 = Date.now();
|
|
1727
1760
|
this.activeCards.set(cardKey, {
|
|
1728
1761
|
chatId,
|
|
1729
1762
|
cardId,
|
|
1730
1763
|
messageId,
|
|
1731
1764
|
sequence: 0,
|
|
1732
|
-
startTime:
|
|
1765
|
+
startTime: now2,
|
|
1733
1766
|
taskItems: [],
|
|
1734
1767
|
toolCalls: [],
|
|
1735
1768
|
thinking: true,
|
|
@@ -1748,7 +1781,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1748
1781
|
lastSuccessfulFlushAt: null,
|
|
1749
1782
|
lastFlushErrorAt: null,
|
|
1750
1783
|
lastFlushError: null,
|
|
1751
|
-
consecutiveFlushFailures: 0
|
|
1784
|
+
consecutiveFlushFailures: 0,
|
|
1785
|
+
lastFullRefreshAttemptAt: now2,
|
|
1786
|
+
lastSuccessfulFullRefreshAt: null
|
|
1752
1787
|
});
|
|
1753
1788
|
console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
|
|
1754
1789
|
return true;
|
|
@@ -1837,6 +1872,17 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1837
1872
|
const toolsText = buildStreamingToolsContent(state.toolCalls) || EMPTY_STREAMING_TOOLS;
|
|
1838
1873
|
const statusText = state.pendingStatusText || INITIAL_STREAMING_STATUS;
|
|
1839
1874
|
const updates = [];
|
|
1875
|
+
if (this.shouldFullRefreshCard(state, Date.now())) {
|
|
1876
|
+
const refreshed = await this.flushFullCardRefresh(
|
|
1877
|
+
streamKey,
|
|
1878
|
+
state,
|
|
1879
|
+
content,
|
|
1880
|
+
tasksText,
|
|
1881
|
+
toolsText,
|
|
1882
|
+
statusText
|
|
1883
|
+
);
|
|
1884
|
+
if (refreshed) return;
|
|
1885
|
+
}
|
|
1840
1886
|
if (content !== state.renderedText) {
|
|
1841
1887
|
updates.push({
|
|
1842
1888
|
elementId: "streaming_content",
|
|
@@ -1903,24 +1949,33 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1903
1949
|
state.toolCalls = tools;
|
|
1904
1950
|
this.scheduleCardFlush(cardKey);
|
|
1905
1951
|
}
|
|
1906
|
-
async awaitCardFlushCompletion(streamKey) {
|
|
1952
|
+
async awaitCardFlushCompletion(streamKey, timeoutMs = this.getCardRequestTimeoutMs() + Math.max(0, this.cardFinalizeFlushWaitExtraMs)) {
|
|
1953
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
1907
1954
|
while (true) {
|
|
1908
1955
|
const state = this.activeCards.get(streamKey);
|
|
1909
|
-
if (!state) return;
|
|
1956
|
+
if (!state) return true;
|
|
1910
1957
|
const inFlight = state.flushInFlight;
|
|
1911
1958
|
if (inFlight) {
|
|
1959
|
+
const remainingMs = deadline - Date.now();
|
|
1960
|
+
if (remainingMs <= 0) return false;
|
|
1961
|
+
const timedOut = Symbol("flush-timeout");
|
|
1912
1962
|
try {
|
|
1913
|
-
await
|
|
1963
|
+
const result = await Promise.race([
|
|
1964
|
+
inFlight.then(() => null),
|
|
1965
|
+
new Promise((resolve2) => setTimeout(() => resolve2(timedOut), remainingMs))
|
|
1966
|
+
]);
|
|
1967
|
+
if (result === timedOut) return false;
|
|
1914
1968
|
} catch {
|
|
1915
1969
|
}
|
|
1916
1970
|
continue;
|
|
1917
1971
|
}
|
|
1972
|
+
if (Date.now() >= deadline) return false;
|
|
1918
1973
|
if (state.flushQueued) {
|
|
1919
1974
|
state.flushQueued = false;
|
|
1920
1975
|
this.enqueueCardFlush(streamKey);
|
|
1921
1976
|
continue;
|
|
1922
1977
|
}
|
|
1923
|
-
return;
|
|
1978
|
+
return true;
|
|
1924
1979
|
}
|
|
1925
1980
|
}
|
|
1926
1981
|
/**
|
|
@@ -1943,7 +1998,12 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1943
1998
|
clearTimeout(state.throttleTimer);
|
|
1944
1999
|
state.throttleTimer = null;
|
|
1945
2000
|
}
|
|
1946
|
-
await this.awaitCardFlushCompletion(cardKey);
|
|
2001
|
+
const flushed = await this.awaitCardFlushCompletion(cardKey);
|
|
2002
|
+
if (!flushed) {
|
|
2003
|
+
console.warn(`[feishu-adapter] Card finalize proceeding after flush wait timeout: streamKey=${cardKey}`);
|
|
2004
|
+
state.flushInFlight = null;
|
|
2005
|
+
state.flushQueued = false;
|
|
2006
|
+
}
|
|
1947
2007
|
try {
|
|
1948
2008
|
state.sequence++;
|
|
1949
2009
|
await this.withFeishuRequestTimeout(cardKey, "card.settings", () => cardkit.card.settings({
|
|
@@ -1972,7 +2032,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1972
2032
|
|
|
1973
2033
|
${trimmedResponse}`;
|
|
1974
2034
|
}
|
|
1975
|
-
const finalCardJson = buildFinalCardJson(finalText, state.taskItems, state.toolCalls, footer);
|
|
2035
|
+
const finalCardJson = buildFinalCardJson(finalText, state.taskItems, state.toolCalls, footer, status);
|
|
1976
2036
|
state.sequence++;
|
|
1977
2037
|
await this.withFeishuRequestTimeout(cardKey, "card.update", () => cardkit.card.update({
|
|
1978
2038
|
path: { card_id: state.cardId },
|
|
@@ -2026,6 +2086,44 @@ ${trimmedResponse}`;
|
|
|
2026
2086
|
consecutiveFailures: state.consecutiveFlushFailures
|
|
2027
2087
|
};
|
|
2028
2088
|
}
|
|
2089
|
+
shouldFullRefreshCard(state, now2) {
|
|
2090
|
+
const interval = Math.max(0, this.cardFullRefreshIntervalMs);
|
|
2091
|
+
if (interval <= 0) return false;
|
|
2092
|
+
if (!Number.isFinite(now2)) return false;
|
|
2093
|
+
return now2 - state.lastFullRefreshAttemptAt >= interval;
|
|
2094
|
+
}
|
|
2095
|
+
async flushFullCardRefresh(streamKey, state, content, tasksText, toolsText, statusText) {
|
|
2096
|
+
state.lastFullRefreshAttemptAt = Date.now();
|
|
2097
|
+
const cardkit = this.restClient?.cardkit?.v1;
|
|
2098
|
+
if (!cardkit?.card?.update) return false;
|
|
2099
|
+
try {
|
|
2100
|
+
state.sequence++;
|
|
2101
|
+
await this.withFeishuRequestTimeout(streamKey, "card.update:streaming_refresh", () => cardkit.card.update({
|
|
2102
|
+
path: { card_id: state.cardId },
|
|
2103
|
+
data: {
|
|
2104
|
+
card: {
|
|
2105
|
+
type: "card_json",
|
|
2106
|
+
data: JSON.stringify(buildStreamingCardBody(content, tasksText, toolsText, statusText))
|
|
2107
|
+
},
|
|
2108
|
+
sequence: state.sequence
|
|
2109
|
+
}
|
|
2110
|
+
}));
|
|
2111
|
+
state.renderedText = content;
|
|
2112
|
+
state.renderedTasksText = tasksText;
|
|
2113
|
+
state.renderedToolsText = toolsText;
|
|
2114
|
+
state.renderedStatusText = statusText;
|
|
2115
|
+
state.lastSuccessfulFullRefreshAt = Date.now();
|
|
2116
|
+
this.markCardFlushSuccess(state);
|
|
2117
|
+
return true;
|
|
2118
|
+
} catch (err) {
|
|
2119
|
+
this.markCardFlushFailure(state, err);
|
|
2120
|
+
console.warn(
|
|
2121
|
+
"[feishu-adapter] card.update streaming refresh failed:",
|
|
2122
|
+
err instanceof Error ? err.message : err
|
|
2123
|
+
);
|
|
2124
|
+
return false;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2029
2127
|
getCardRequestTimeoutMs() {
|
|
2030
2128
|
return Math.max(1, this.cardRequestTimeoutMs);
|
|
2031
2129
|
}
|
|
@@ -10473,6 +10571,18 @@ function readDesktopSessionEventStream(threadId) {
|
|
|
10473
10571
|
return readDesktopSessionEventStreamByFilePath(session.filePath);
|
|
10474
10572
|
}
|
|
10475
10573
|
|
|
10574
|
+
// src/lib/bridge/turns/turn-classifier.ts
|
|
10575
|
+
function normalizeThreadId(value) {
|
|
10576
|
+
const normalized = value?.trim();
|
|
10577
|
+
return normalized || void 0;
|
|
10578
|
+
}
|
|
10579
|
+
function getCodexThreadId(session, binding) {
|
|
10580
|
+
return normalizeThreadId(session?.codex_thread_id) || normalizeThreadId(binding?.sdkSessionId) || normalizeThreadId(session?.sdk_session_id);
|
|
10581
|
+
}
|
|
10582
|
+
function getExplicitDesktopThreadId(session) {
|
|
10583
|
+
return normalizeThreadId(session?.desktop_thread_id) || (session?.thread_origin === "desktop" ? normalizeThreadId(session.sdk_session_id) : void 0);
|
|
10584
|
+
}
|
|
10585
|
+
|
|
10476
10586
|
// src/session-bindings.ts
|
|
10477
10587
|
function asChannelProvider(value) {
|
|
10478
10588
|
return value === "feishu" || value === "weixin" ? value : void 0;
|
|
@@ -10518,15 +10628,31 @@ function assertBindingTargetAvailable(store, current, opts) {
|
|
|
10518
10628
|
function getSessionMode(store, session) {
|
|
10519
10629
|
return session.preferred_mode || store.getSetting("bridge_default_mode") || "code";
|
|
10520
10630
|
}
|
|
10631
|
+
function getBindingResumeThreadId(session) {
|
|
10632
|
+
return getCodexThreadId(session) || "";
|
|
10633
|
+
}
|
|
10634
|
+
function markSessionAsDesktopBacked(store, sessionId, desktopThreadId) {
|
|
10635
|
+
store.updateSdkSessionId(sessionId, desktopThreadId);
|
|
10636
|
+
store.updateSession(sessionId, {
|
|
10637
|
+
sdk_session_id: desktopThreadId,
|
|
10638
|
+
codex_thread_id: desktopThreadId,
|
|
10639
|
+
desktop_thread_id: desktopThreadId,
|
|
10640
|
+
thread_origin: "desktop"
|
|
10641
|
+
});
|
|
10642
|
+
}
|
|
10521
10643
|
function bindStoreToSession(store, channelType, chatId, sessionId, chatMeta) {
|
|
10522
10644
|
const session = store.getSession(sessionId);
|
|
10523
10645
|
if (!session) return null;
|
|
10524
10646
|
assertBindingTargetAvailable(
|
|
10525
10647
|
store,
|
|
10526
10648
|
{ channelType, chatId },
|
|
10527
|
-
{
|
|
10649
|
+
{
|
|
10650
|
+
sessionId: session.id,
|
|
10651
|
+
sdkSessionId: getCodexThreadId(session) || getExplicitDesktopThreadId(session)
|
|
10652
|
+
}
|
|
10528
10653
|
);
|
|
10529
10654
|
const meta = resolveChannelMeta(channelType);
|
|
10655
|
+
const sdkSessionId = getBindingResumeThreadId(session);
|
|
10530
10656
|
return store.upsertChannelBinding({
|
|
10531
10657
|
channelType,
|
|
10532
10658
|
channelProvider: meta.provider,
|
|
@@ -10535,7 +10661,7 @@ function bindStoreToSession(store, channelType, chatId, sessionId, chatMeta) {
|
|
|
10535
10661
|
chatUserId: chatMeta?.chatUserId,
|
|
10536
10662
|
chatDisplayName: chatMeta?.chatDisplayName,
|
|
10537
10663
|
codepilotSessionId: session.id,
|
|
10538
|
-
sdkSessionId
|
|
10664
|
+
sdkSessionId,
|
|
10539
10665
|
workingDirectory: session.working_directory,
|
|
10540
10666
|
model: session.model,
|
|
10541
10667
|
mode: getSessionMode(store, session)
|
|
@@ -10550,6 +10676,7 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
10550
10676
|
const meta = resolveChannelMeta(channelType);
|
|
10551
10677
|
const existing = store.findSessionBySdkSessionId(sdkSessionId);
|
|
10552
10678
|
if (existing) {
|
|
10679
|
+
markSessionAsDesktopBacked(store, existing.id, sdkSessionId);
|
|
10553
10680
|
return store.upsertChannelBinding({
|
|
10554
10681
|
channelType,
|
|
10555
10682
|
channelProvider: meta.provider,
|
|
@@ -10574,7 +10701,7 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
10574
10701
|
workingDirectory,
|
|
10575
10702
|
"code"
|
|
10576
10703
|
);
|
|
10577
|
-
store
|
|
10704
|
+
markSessionAsDesktopBacked(store, session.id, sdkSessionId);
|
|
10578
10705
|
return store.upsertChannelBinding({
|
|
10579
10706
|
channelType,
|
|
10580
10707
|
channelProvider: meta.provider,
|
|
@@ -10947,15 +11074,17 @@ async function deliver(adapter, message, opts) {
|
|
|
10947
11074
|
} catch {
|
|
10948
11075
|
}
|
|
10949
11076
|
}
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
11077
|
+
if (opts?.audit !== false) {
|
|
11078
|
+
try {
|
|
11079
|
+
store.insertAuditLog({
|
|
11080
|
+
channelType: adapter.channelType,
|
|
11081
|
+
chatId: message.address.chatId,
|
|
11082
|
+
direction: "outbound",
|
|
11083
|
+
messageId: lastMessageId || "",
|
|
11084
|
+
summary: message.text.slice(0, 200)
|
|
11085
|
+
});
|
|
11086
|
+
} catch {
|
|
11087
|
+
}
|
|
10959
11088
|
}
|
|
10960
11089
|
return { ok: true, messageId: lastMessageId };
|
|
10961
11090
|
}
|
|
@@ -11552,7 +11681,6 @@ function buildHealthCommandResponse(title, diagnosis, markdown = false) {
|
|
|
11552
11681
|
title,
|
|
11553
11682
|
[
|
|
11554
11683
|
["Session", diagnosis.sessionId],
|
|
11555
|
-
["\u68C0\u67E5\u65F6\u95F4", formatCommandTimestamp(diagnosis.checkedAt)],
|
|
11556
11684
|
["\u8FD0\u884C\u72B6\u6001", formatRuntimeStatus({ runtime_status: diagnosis.runtimeStatus, queued_count: 0 })],
|
|
11557
11685
|
["\u5065\u5EB7\u72B6\u6001", formatHealthStatusLabel(diagnosis.healthStatus)],
|
|
11558
11686
|
["\u5F53\u524D\u9636\u6BB5", currentStage],
|
|
@@ -11573,7 +11701,6 @@ function buildHealthListResponse(diagnoses, markdown = false) {
|
|
|
11573
11701
|
diagnoses.map((diagnosis) => ({
|
|
11574
11702
|
heading: diagnosis.sessionId,
|
|
11575
11703
|
details: [
|
|
11576
|
-
`\u68C0\u67E5\u65F6\u95F4\uFF1A${formatCommandTimestamp(diagnosis.checkedAt)}`,
|
|
11577
11704
|
`\u5065\u5EB7\u72B6\u6001\uFF1A${formatHealthStatusLabel(diagnosis.healthStatus)}`,
|
|
11578
11705
|
`\u5F53\u524D\u9636\u6BB5\uFF1A${diagnosis.activeToolName ? `\u5DE5\u5177 \xB7 ${diagnosis.activeToolName}` : diagnosis.lastProgressType || "-"}`,
|
|
11579
11706
|
`\u6700\u540E\u8FDB\u5C55\uFF1A${formatCommandTimestamp(diagnosis.lastProgressAt)}`,
|
|
@@ -11681,6 +11808,8 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
|
11681
11808
|
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
11682
11809
|
startedAt: safeTimestamp,
|
|
11683
11810
|
lastActivityAt: safeTimestamp,
|
|
11811
|
+
lastContentResponseAt: null,
|
|
11812
|
+
lastResponseAt: null,
|
|
11684
11813
|
lastStatusText: null,
|
|
11685
11814
|
lastStatusAt: 0,
|
|
11686
11815
|
statusNote: null,
|
|
@@ -11727,8 +11856,14 @@ function ensureMirrorTurnState(subscription, record) {
|
|
|
11727
11856
|
}
|
|
11728
11857
|
return subscription.pendingTurn;
|
|
11729
11858
|
}
|
|
11730
|
-
function
|
|
11731
|
-
turnState.
|
|
11859
|
+
function markMirrorActivity(turnState, timestamp) {
|
|
11860
|
+
turnState.lastActivityAt = timestamp || nowIso2();
|
|
11861
|
+
}
|
|
11862
|
+
function markMirrorContentResponse(turnState, timestamp) {
|
|
11863
|
+
const responseAt = timestamp || nowIso2();
|
|
11864
|
+
markMirrorActivity(turnState, responseAt);
|
|
11865
|
+
turnState.lastContentResponseAt = responseAt;
|
|
11866
|
+
turnState.lastResponseAt = responseAt;
|
|
11732
11867
|
}
|
|
11733
11868
|
function finalizeMirrorTurn(subscription, signature, timestamp, status, preferredText) {
|
|
11734
11869
|
const pendingTurn = subscription.pendingTurn;
|
|
@@ -11801,7 +11936,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11801
11936
|
if (text2) {
|
|
11802
11937
|
pendingTurn.lastAssistantText = text2;
|
|
11803
11938
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11804
|
-
|
|
11939
|
+
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11805
11940
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
11806
11941
|
}
|
|
11807
11942
|
} else if (record.role === "commentary") {
|
|
@@ -11809,7 +11944,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11809
11944
|
if (text2) {
|
|
11810
11945
|
pendingTurn.lastCommentaryText = text2;
|
|
11811
11946
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11812
|
-
|
|
11947
|
+
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11813
11948
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
11814
11949
|
}
|
|
11815
11950
|
}
|
|
@@ -11820,14 +11955,14 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11820
11955
|
const text2 = record.content.trim();
|
|
11821
11956
|
if (!text2) continue;
|
|
11822
11957
|
pendingTurn.statusNote = text2;
|
|
11823
|
-
|
|
11958
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11824
11959
|
hooks.onStatusProgress?.(subscription, pendingTurn);
|
|
11825
11960
|
continue;
|
|
11826
11961
|
}
|
|
11827
11962
|
if (record.type === "plan_update") {
|
|
11828
11963
|
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
11829
11964
|
pendingTurn.taskItems = record.tasks || [];
|
|
11830
|
-
|
|
11965
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11831
11966
|
hooks.onTaskProgress?.(subscription, pendingTurn);
|
|
11832
11967
|
continue;
|
|
11833
11968
|
}
|
|
@@ -11840,7 +11975,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11840
11975
|
name: toolName,
|
|
11841
11976
|
status: "running"
|
|
11842
11977
|
});
|
|
11843
|
-
|
|
11978
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11844
11979
|
hooks.onToolProgress?.(subscription, pendingTurn);
|
|
11845
11980
|
continue;
|
|
11846
11981
|
}
|
|
@@ -11853,7 +11988,7 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11853
11988
|
name: existing?.name || record.toolName || "tool",
|
|
11854
11989
|
status: record.isError ? "error" : "complete"
|
|
11855
11990
|
});
|
|
11856
|
-
|
|
11991
|
+
markMirrorActivity(pendingTurn, record.timestamp);
|
|
11857
11992
|
hooks.onToolProgress?.(subscription, pendingTurn);
|
|
11858
11993
|
continue;
|
|
11859
11994
|
}
|
|
@@ -12022,9 +12157,6 @@ function abortMirrorSuppression(store, sessionId, config2, suppressionId, nowMs
|
|
|
12022
12157
|
}
|
|
12023
12158
|
target.until = nowMs + config2.suppressionWindowMs;
|
|
12024
12159
|
}
|
|
12025
|
-
function isMirrorSuppressed(store, sessionId, nowMs = Date.now()) {
|
|
12026
|
-
return getMirrorSuppressionStates(store, sessionId, nowMs).length > 0;
|
|
12027
|
-
}
|
|
12028
12160
|
function filterSuppressedMirrorRecords(store, sessionId, records, config2, nowMs = Date.now()) {
|
|
12029
12161
|
const suppressions = getMirrorSuppressionStates(store, sessionId, nowMs);
|
|
12030
12162
|
const ignoredTurnIds = cleanupIgnoredMirrorTurns(store, sessionId, nowMs);
|
|
@@ -12661,7 +12793,7 @@ function supportsOutboundArtifacts(provider) {
|
|
|
12661
12793
|
}
|
|
12662
12794
|
|
|
12663
12795
|
// src/lib/bridge/feedback-delivery.ts
|
|
12664
|
-
async function deliverTextResponse(adapter, address, responseText, sessionId, replyToMessageId) {
|
|
12796
|
+
async function deliverTextResponse(adapter, address, responseText, sessionId, replyToMessageId, options) {
|
|
12665
12797
|
if (!responseText.trim()) return { ok: true };
|
|
12666
12798
|
const parseMode = getFeedbackParseMode(adapter.channelType);
|
|
12667
12799
|
const renderedText = renderFeedbackText(responseText, parseMode);
|
|
@@ -12671,14 +12803,14 @@ async function deliverTextResponse(adapter, address, responseText, sessionId, re
|
|
|
12671
12803
|
text: responseText,
|
|
12672
12804
|
parseMode: "Markdown",
|
|
12673
12805
|
replyToMessageId
|
|
12674
|
-
}, { sessionId });
|
|
12806
|
+
}, { sessionId, audit: options?.audit });
|
|
12675
12807
|
}
|
|
12676
12808
|
return deliver(adapter, {
|
|
12677
12809
|
address,
|
|
12678
12810
|
text: parseMode === "Markdown" ? responseText : renderedText,
|
|
12679
12811
|
parseMode,
|
|
12680
12812
|
replyToMessageId
|
|
12681
|
-
}, { sessionId });
|
|
12813
|
+
}, { sessionId, audit: options?.audit });
|
|
12682
12814
|
}
|
|
12683
12815
|
async function deliverBridgeNotice(adapter, address, text2, options) {
|
|
12684
12816
|
return deliverTextResponse(
|
|
@@ -12686,7 +12818,8 @@ async function deliverBridgeNotice(adapter, address, text2, options) {
|
|
|
12686
12818
|
address,
|
|
12687
12819
|
text2,
|
|
12688
12820
|
options?.sessionId,
|
|
12689
|
-
options?.replyToMessageId
|
|
12821
|
+
options?.replyToMessageId,
|
|
12822
|
+
{ audit: options?.audit }
|
|
12690
12823
|
);
|
|
12691
12824
|
}
|
|
12692
12825
|
async function deliverResponse(adapter, address, responseText, sessionId, replyToMessageId, attachments = []) {
|
|
@@ -12802,6 +12935,7 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
12802
12935
|
}
|
|
12803
12936
|
let response = "";
|
|
12804
12937
|
let responseParseMode = getFeedbackParseMode(adapter.channelType);
|
|
12938
|
+
let auditResponse = true;
|
|
12805
12939
|
const currentBinding = store.getChannelBinding(msg.address.channelType, msg.address.chatId);
|
|
12806
12940
|
switch (command) {
|
|
12807
12941
|
case "/start":
|
|
@@ -13134,6 +13268,7 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13134
13268
|
break;
|
|
13135
13269
|
}
|
|
13136
13270
|
if (!args) {
|
|
13271
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13137
13272
|
const currentModel = resolveDisplayedModel(
|
|
13138
13273
|
binding,
|
|
13139
13274
|
session,
|
|
@@ -13145,14 +13280,14 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13145
13280
|
[["\u6A21\u578B", formatDisplayedModel(currentModel)]],
|
|
13146
13281
|
[
|
|
13147
13282
|
getAvailableModelChoicesText(),
|
|
13148
|
-
|
|
13283
|
+
desktopThreadId ? "\u5F53\u524D\u662F\u5171\u4EAB\u684C\u9762\u7EBF\u7A0B\uFF0C\u53EA\u652F\u6301\u67E5\u770B\u6A21\u578B\uFF1B\u5982\u9700\u5207\u6362\uFF0C\u8BF7\u5148\u7528 `/new` \u65B0\u5EFA\u4E00\u4E2A IM \u4F1A\u8BDD\u7EBF\u7A0B\u3002" : "\u53D1\u9001 `/model gpt-5.4` \u53EF\u5207\u6362\uFF1B\u53D1\u9001 `/model default` \u53EF\u56DE\u9000\u5230\u9ED8\u8BA4\u6A21\u578B\u3002",
|
|
13149
13284
|
"\u6A21\u578B\u5207\u6362\u53EA\u5F71\u54CD\u540E\u7EED\u4ECE IM \u53D1\u8D77\u7684 Codex CLI \u8BF7\u6C42\u3002"
|
|
13150
13285
|
],
|
|
13151
13286
|
responseParseMode === "Markdown"
|
|
13152
13287
|
);
|
|
13153
13288
|
break;
|
|
13154
13289
|
}
|
|
13155
|
-
if (
|
|
13290
|
+
if (getExplicitDesktopThreadId(session)) {
|
|
13156
13291
|
response = "\u5F53\u524D\u662F\u5171\u4EAB\u684C\u9762\u7EBF\u7A0B\uFF0C\u4E0D\u652F\u6301\u76F4\u63A5\u5207\u6362\u6A21\u578B\u3002\u8BF7\u5148\u7528 `/new` \u65B0\u5EFA\u4E00\u4E2A\u7EBF\u7A0B\uFF0C\u518D\u6267\u884C `/model ...`\u3002";
|
|
13157
13292
|
break;
|
|
13158
13293
|
}
|
|
@@ -13203,9 +13338,32 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13203
13338
|
break;
|
|
13204
13339
|
}
|
|
13205
13340
|
case "/status": {
|
|
13206
|
-
|
|
13341
|
+
auditResponse = false;
|
|
13342
|
+
const binding = currentBinding;
|
|
13343
|
+
if (!binding) {
|
|
13344
|
+
response = buildCommandFields(
|
|
13345
|
+
"\u5F53\u524D\u4F1A\u8BDD",
|
|
13346
|
+
[],
|
|
13347
|
+
["\u5F53\u524D\u804A\u5929\u8FD8\u6CA1\u6709\u7ED1\u5B9A\u4F1A\u8BDD\u3002\u53EF\u5148\u53D1\u9001 `/t` \u67E5\u770B\u6700\u8FD1\u684C\u9762\u4F1A\u8BDD\uFF0C\u518D\u7528 `/t 1` \u63A5\u7BA1\uFF1B\u6216\u53D1\u9001 `/new proj1` / `/new \u7EDD\u5BF9\u8DEF\u5F84` \u521B\u5EFA\u9879\u76EE\u4F1A\u8BDD\u3002"],
|
|
13348
|
+
responseParseMode === "Markdown"
|
|
13349
|
+
);
|
|
13350
|
+
break;
|
|
13351
|
+
}
|
|
13207
13352
|
const session = store.getSession(binding.codepilotSessionId);
|
|
13208
|
-
|
|
13353
|
+
if (!session) {
|
|
13354
|
+
response = buildCommandFields(
|
|
13355
|
+
"\u5F53\u524D\u4F1A\u8BDD",
|
|
13356
|
+
[
|
|
13357
|
+
["Session", binding.codepilotSessionId],
|
|
13358
|
+
["\u76EE\u5F55", formatCommandPath(binding.workingDirectory)]
|
|
13359
|
+
],
|
|
13360
|
+
["\u5F53\u524D\u804A\u5929\u7ED1\u5B9A\u7684\u4F1A\u8BDD\u5DF2\u7ECF\u4E0D\u5B58\u5728\u3002\u53EF\u7528 `/t` \u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\uFF0C\u6216\u7528 `/new proj1` / `/new \u7EDD\u5BF9\u8DEF\u5F84` \u521B\u5EFA\u65B0\u4F1A\u8BDD\u3002"],
|
|
13361
|
+
responseParseMode === "Markdown"
|
|
13362
|
+
);
|
|
13363
|
+
break;
|
|
13364
|
+
}
|
|
13365
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13366
|
+
const threadTitle = getDesktopThreadTitle(desktopThreadId);
|
|
13209
13367
|
const sandboxMode = resolveEffectiveSandboxMode();
|
|
13210
13368
|
const reasoningEffort = resolveEffectiveReasoningEffort(session);
|
|
13211
13369
|
const currentModel = resolveDisplayedModel(
|
|
@@ -13229,21 +13387,25 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13229
13387
|
["\u601D\u8003\u7EA7\u522B", formatReasoningEffort(reasoningEffort)]
|
|
13230
13388
|
],
|
|
13231
13389
|
[
|
|
13232
|
-
|
|
13390
|
+
desktopThreadId ? "\u5F53\u524D\u804A\u5929\u5DF2\u7ED1\u5B9A\u5230\u4E00\u6761\u5171\u4EAB\u4F1A\u8BDD\uFF0C\u76F4\u63A5\u53D1\u9001\u6D88\u606F\u5373\u53EF\u7EE7\u7EED\u3002" : session?.session_type === "draft" ? "\u5F53\u524D\u804A\u5929\u6B63\u5728\u4F7F\u7528\u4E34\u65F6\u8349\u7A3F\u7EBF\u7A0B\uFF08\u7B49\u540C `/t 0`\uFF09\u3002\u53EF\u76F4\u63A5\u53D1\u9001\u6D88\u606F\uFF0C\u6216\u7528 `/t` / `/new proj1` / `/new \u7EDD\u5BF9\u8DEF\u5F84` \u5207\u6362\u5230\u6B63\u5F0F\u4F1A\u8BDD\u3002" : "\u5F53\u524D\u804A\u5929\u8FD8\u6CA1\u6709\u7ED1\u5B9A\u684C\u9762\u4F1A\u8BDD\u3002\u53EF\u5148\u53D1\u9001 `/t`\uFF0C\u518D\u7528 `/t 1` \u63A5\u7BA1\u3002"
|
|
13233
13391
|
],
|
|
13234
13392
|
responseParseMode === "Markdown"
|
|
13235
13393
|
);
|
|
13236
13394
|
break;
|
|
13237
13395
|
}
|
|
13238
13396
|
case "/health": {
|
|
13397
|
+
auditResponse = false;
|
|
13239
13398
|
if (args === "all") {
|
|
13240
13399
|
const diagnoses = await deps.diagnoseAllActiveSessions();
|
|
13241
13400
|
response = diagnoses.length > 0 ? buildHealthListResponse(diagnoses, responseParseMode === "Markdown") : "\u5F53\u524D\u6CA1\u6709\u68C0\u6D4B\u5230\u8FD0\u884C\u4E2D\u7684\u4F1A\u8BDD\u3002";
|
|
13242
13401
|
break;
|
|
13243
13402
|
}
|
|
13244
|
-
const binding = currentBinding || resolve(msg.address);
|
|
13245
13403
|
const explicitTargetSessionId = args.trim();
|
|
13246
|
-
const targetSessionId = explicitTargetSessionId ||
|
|
13404
|
+
const targetSessionId = explicitTargetSessionId || currentBinding?.codepilotSessionId;
|
|
13405
|
+
if (!targetSessionId) {
|
|
13406
|
+
response = "\u5F53\u524D\u804A\u5929\u8FD8\u6CA1\u6709\u7ED1\u5B9A\u4F1A\u8BDD\u3002\u5148\u53D1\u9001\u6D88\u606F\u521B\u5EFA\u4F1A\u8BDD\uFF0C\u6216\u5148\u7528 `/t 1` \u63A5\u7BA1\u684C\u9762\u4F1A\u8BDD\u3002";
|
|
13407
|
+
break;
|
|
13408
|
+
}
|
|
13247
13409
|
const diagnosis = await deps.diagnoseSessionHealth(targetSessionId);
|
|
13248
13410
|
if (!diagnosis) {
|
|
13249
13411
|
response = `\u6CA1\u6709\u627E\u5230\u4F1A\u8BDD ${targetSessionId}\u3002`;
|
|
@@ -13262,15 +13424,16 @@ async function handleBridgeCommand(adapter, msg, text2, deps) {
|
|
|
13262
13424
|
break;
|
|
13263
13425
|
}
|
|
13264
13426
|
const limit = getHistoryMessageLimit();
|
|
13265
|
-
const
|
|
13427
|
+
const session = store.getSession(currentBinding.codepilotSessionId);
|
|
13428
|
+
const desktopThreadId = getExplicitDesktopThreadId(session);
|
|
13429
|
+
const desktopMessages = desktopThreadId ? readDesktopSessionMessages(desktopThreadId, limit) : [];
|
|
13266
13430
|
const { messages: storedMessages } = store.getMessages(currentBinding.codepilotSessionId, { limit });
|
|
13267
13431
|
const messages = desktopMessages.length > 0 ? desktopMessages : storedMessages;
|
|
13268
13432
|
if (messages.length === 0) {
|
|
13269
13433
|
response = "\u5F53\u524D\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u5386\u53F2\u6D88\u606F\u3002";
|
|
13270
13434
|
break;
|
|
13271
13435
|
}
|
|
13272
|
-
const threadTitle = getDesktopThreadTitle(
|
|
13273
|
-
const session = store.getSession(currentBinding.codepilotSessionId);
|
|
13436
|
+
const threadTitle = getDesktopThreadTitle(desktopThreadId);
|
|
13274
13437
|
const header = buildCommandFields(
|
|
13275
13438
|
"\u6700\u8FD1\u5BF9\u8BDD\uFF08raw\uFF09",
|
|
13276
13439
|
[
|
|
@@ -13295,10 +13458,27 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
13295
13458
|
}
|
|
13296
13459
|
case "/stop": {
|
|
13297
13460
|
const binding = resolve(msg.address);
|
|
13461
|
+
const session = store.getSession(binding.codepilotSessionId);
|
|
13298
13462
|
const task = deps.getActiveTask(binding.codepilotSessionId);
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13463
|
+
const runningHealthStatuses = /* @__PURE__ */ new Set([
|
|
13464
|
+
"running_active",
|
|
13465
|
+
"waiting_tool",
|
|
13466
|
+
"slow_observed",
|
|
13467
|
+
"suspected_stall",
|
|
13468
|
+
"suspected_stream_ui_stall",
|
|
13469
|
+
"suspected_detached"
|
|
13470
|
+
]);
|
|
13471
|
+
const looksRunning = session?.runtime_status === "running" || session?.runtime_status === "queued" || runningHealthStatuses.has(session?.health_status || "");
|
|
13472
|
+
if (task || looksRunning) {
|
|
13473
|
+
const taskName = getSessionDisplayName(session, binding.workingDirectory);
|
|
13474
|
+
const detail = "\u7528\u6237\u6267\u884C /stop\uFF0C\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
13475
|
+
if (deps.forceStopSession) {
|
|
13476
|
+
await deps.forceStopSession(binding.codepilotSessionId, detail);
|
|
13477
|
+
} else if (task) {
|
|
13478
|
+
task.abortController.abort();
|
|
13479
|
+
}
|
|
13480
|
+
deps.recordInteractiveHealthEnd?.(binding.codepilotSessionId, "aborted", detail);
|
|
13481
|
+
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`;
|
|
13302
13482
|
} else {
|
|
13303
13483
|
response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
|
13304
13484
|
}
|
|
@@ -13402,7 +13582,8 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
13402
13582
|
}
|
|
13403
13583
|
if (response) {
|
|
13404
13584
|
await deliverBridgeNotice(adapter, msg.address, response, {
|
|
13405
|
-
replyToMessageId: msg.messageId
|
|
13585
|
+
replyToMessageId: msg.messageId,
|
|
13586
|
+
audit: auditResponse
|
|
13406
13587
|
});
|
|
13407
13588
|
}
|
|
13408
13589
|
}
|
|
@@ -13414,6 +13595,39 @@ import path13 from "node:path";
|
|
|
13414
13595
|
import fs8 from "fs";
|
|
13415
13596
|
import path12 from "path";
|
|
13416
13597
|
import crypto5 from "crypto";
|
|
13598
|
+
|
|
13599
|
+
// src/lib/bridge/turns/final-response-artifacts.ts
|
|
13600
|
+
function attachmentKey(attachment) {
|
|
13601
|
+
return [
|
|
13602
|
+
attachment.kind,
|
|
13603
|
+
attachment.path,
|
|
13604
|
+
attachment.caption || "",
|
|
13605
|
+
attachment.name || ""
|
|
13606
|
+
].join("\0");
|
|
13607
|
+
}
|
|
13608
|
+
function dedupeOutboundAttachments(attachments) {
|
|
13609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13610
|
+
const deduped = [];
|
|
13611
|
+
for (const attachment of attachments) {
|
|
13612
|
+
const key = attachmentKey(attachment);
|
|
13613
|
+
if (seen.has(key)) continue;
|
|
13614
|
+
seen.add(key);
|
|
13615
|
+
deduped.push(attachment);
|
|
13616
|
+
}
|
|
13617
|
+
return deduped;
|
|
13618
|
+
}
|
|
13619
|
+
function collectFinalResponseArtifacts(text2, attachments = []) {
|
|
13620
|
+
const parsed = parseOutboundArtifacts(text2 || "");
|
|
13621
|
+
return {
|
|
13622
|
+
text: parsed.cleanText,
|
|
13623
|
+
attachments: dedupeOutboundAttachments([
|
|
13624
|
+
...attachments,
|
|
13625
|
+
...parsed.attachments
|
|
13626
|
+
])
|
|
13627
|
+
};
|
|
13628
|
+
}
|
|
13629
|
+
|
|
13630
|
+
// src/lib/bridge/conversation-engine.ts
|
|
13417
13631
|
init_runtime_options();
|
|
13418
13632
|
|
|
13419
13633
|
// src/lib/bridge/sse-stream-decoder.ts
|
|
@@ -13792,8 +14006,8 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13792
14006
|
if (contentBlocks.length > 0) {
|
|
13793
14007
|
for (const block2 of contentBlocks) {
|
|
13794
14008
|
if (block2.type !== "text") continue;
|
|
13795
|
-
const parsed =
|
|
13796
|
-
block2.text = parsed.
|
|
14009
|
+
const parsed = collectFinalResponseArtifacts(block2.text);
|
|
14010
|
+
block2.text = parsed.text;
|
|
13797
14011
|
outboundAttachments.push(...parsed.attachments);
|
|
13798
14012
|
}
|
|
13799
14013
|
const hasToolBlocks = contentBlocks.some(
|
|
@@ -13807,7 +14021,7 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13807
14021
|
const responseText = contentBlocks.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
13808
14022
|
return {
|
|
13809
14023
|
responseText,
|
|
13810
|
-
outboundAttachments,
|
|
14024
|
+
outboundAttachments: dedupeOutboundAttachments(outboundAttachments),
|
|
13811
14025
|
tokenUsage,
|
|
13812
14026
|
hasError,
|
|
13813
14027
|
errorMessage,
|
|
@@ -13840,6 +14054,42 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
13840
14054
|
}
|
|
13841
14055
|
}
|
|
13842
14056
|
|
|
14057
|
+
// src/lib/bridge/turns/response-assembler.ts
|
|
14058
|
+
function assembleFinalResponse(source, input) {
|
|
14059
|
+
const parsed = collectFinalResponseArtifacts(input.text, input.attachments);
|
|
14060
|
+
return {
|
|
14061
|
+
text: parsed.text,
|
|
14062
|
+
attachments: parsed.attachments,
|
|
14063
|
+
hasError: input.hasError,
|
|
14064
|
+
errorMessage: input.errorMessage,
|
|
14065
|
+
source
|
|
14066
|
+
};
|
|
14067
|
+
}
|
|
14068
|
+
function assembleSdkFinalResponse(input) {
|
|
14069
|
+
return assembleFinalResponse("sdk_result", input);
|
|
14070
|
+
}
|
|
14071
|
+
function assembleDesktopFinalResponse(input) {
|
|
14072
|
+
return assembleFinalResponse("desktop_task_complete", input);
|
|
14073
|
+
}
|
|
14074
|
+
function hasFinalResponsePayload(response) {
|
|
14075
|
+
return Boolean(response.text || response.attachments.length > 0);
|
|
14076
|
+
}
|
|
14077
|
+
function mergeFinalResponses(primary, fallback) {
|
|
14078
|
+
return {
|
|
14079
|
+
text: primary.text || fallback.text,
|
|
14080
|
+
attachments: dedupeOutboundAttachments([
|
|
14081
|
+
...fallback.attachments,
|
|
14082
|
+
...primary.attachments
|
|
14083
|
+
]),
|
|
14084
|
+
hasError: primary.hasError ?? fallback.hasError,
|
|
14085
|
+
errorMessage: primary.errorMessage || fallback.errorMessage,
|
|
14086
|
+
source: primary.source
|
|
14087
|
+
};
|
|
14088
|
+
}
|
|
14089
|
+
function stripFinalOnlyBlocksForStreaming(text2) {
|
|
14090
|
+
return stripOutboundArtifactBlocksForStreaming(text2);
|
|
14091
|
+
}
|
|
14092
|
+
|
|
13843
14093
|
// src/lib/bridge/stream-feedback-controller.ts
|
|
13844
14094
|
function pushStreamFeedbackText(target, text2) {
|
|
13845
14095
|
if (typeof target.adapter.onStreamText !== "function") return;
|
|
@@ -13868,13 +14118,15 @@ function pushStreamFeedbackTasks(target, tasks) {
|
|
|
13868
14118
|
}
|
|
13869
14119
|
}
|
|
13870
14120
|
function pushStreamFeedbackStatus(target, text2) {
|
|
13871
|
-
if (typeof target.adapter.onStreamStatus !== "function") return;
|
|
14121
|
+
if (typeof target.adapter.onStreamStatus !== "function") return false;
|
|
13872
14122
|
target.ensureStarted?.();
|
|
13873
14123
|
const rendered = renderFeedbackTextForChannel(target.channelType, text2);
|
|
13874
|
-
if (!rendered) return;
|
|
14124
|
+
if (!rendered) return false;
|
|
13875
14125
|
try {
|
|
13876
14126
|
target.adapter.onStreamStatus(target.chatId, rendered, target.streamKey);
|
|
14127
|
+
return true;
|
|
13877
14128
|
} catch {
|
|
14129
|
+
return false;
|
|
13878
14130
|
}
|
|
13879
14131
|
}
|
|
13880
14132
|
async function finalizeStreamFeedback(target, status, text2) {
|
|
@@ -13887,6 +14139,118 @@ async function finalizeStreamFeedback(target, status, text2) {
|
|
|
13887
14139
|
}
|
|
13888
14140
|
}
|
|
13889
14141
|
|
|
14142
|
+
// src/lib/bridge/turns/delivery-pipeline.ts
|
|
14143
|
+
function normalizeUnknownSendResult(result) {
|
|
14144
|
+
if (result && typeof result === "object" && "ok" in result) {
|
|
14145
|
+
return result;
|
|
14146
|
+
}
|
|
14147
|
+
return { ok: true };
|
|
14148
|
+
}
|
|
14149
|
+
async function deliverFinalResponse(context, response, options = {}) {
|
|
14150
|
+
let lastResult = { ok: true };
|
|
14151
|
+
const deliverResponse2 = context.deliverResponse || deliverResponse;
|
|
14152
|
+
if (!options.skipText && response.text.trim()) {
|
|
14153
|
+
if (context.deliverText) {
|
|
14154
|
+
lastResult = await context.deliverText(response.text);
|
|
14155
|
+
} else {
|
|
14156
|
+
lastResult = normalizeUnknownSendResult(await deliverResponse2(
|
|
14157
|
+
context.adapter,
|
|
14158
|
+
context.address,
|
|
14159
|
+
response.text,
|
|
14160
|
+
context.sessionId,
|
|
14161
|
+
context.replyToMessageId,
|
|
14162
|
+
[]
|
|
14163
|
+
));
|
|
14164
|
+
}
|
|
14165
|
+
if (!lastResult.ok) return lastResult;
|
|
14166
|
+
}
|
|
14167
|
+
if (response.attachments.length > 0) {
|
|
14168
|
+
lastResult = normalizeUnknownSendResult(await deliverResponse2(
|
|
14169
|
+
context.adapter,
|
|
14170
|
+
context.address,
|
|
14171
|
+
"",
|
|
14172
|
+
context.sessionId,
|
|
14173
|
+
context.replyToMessageId,
|
|
14174
|
+
response.attachments
|
|
14175
|
+
));
|
|
14176
|
+
if (!lastResult.ok) return lastResult;
|
|
14177
|
+
}
|
|
14178
|
+
return lastResult;
|
|
14179
|
+
}
|
|
14180
|
+
async function finalizeStreamingUi(target, status, response) {
|
|
14181
|
+
return finalizeStreamFeedback(target, status, response.text);
|
|
14182
|
+
}
|
|
14183
|
+
|
|
14184
|
+
// src/lib/bridge/turns/stream-state.ts
|
|
14185
|
+
function createStreamState(startedAtMs) {
|
|
14186
|
+
const safeStartedAtMs = Number.isFinite(startedAtMs) ? startedAtMs : Date.now();
|
|
14187
|
+
return {
|
|
14188
|
+
startedAtMs: safeStartedAtMs,
|
|
14189
|
+
lastActivityAtMs: safeStartedAtMs,
|
|
14190
|
+
lastContentResponseAtMs: null,
|
|
14191
|
+
statusNote: null,
|
|
14192
|
+
lastStatusText: null,
|
|
14193
|
+
lastStatusAtMs: 0
|
|
14194
|
+
};
|
|
14195
|
+
}
|
|
14196
|
+
function recordStreamActivity(state, nowMs) {
|
|
14197
|
+
if (!Number.isFinite(nowMs)) return;
|
|
14198
|
+
state.lastActivityAtMs = Math.max(state.lastActivityAtMs, nowMs);
|
|
14199
|
+
}
|
|
14200
|
+
function recordStreamContentResponse(state, nowMs) {
|
|
14201
|
+
if (!Number.isFinite(nowMs)) return;
|
|
14202
|
+
recordStreamActivity(state, nowMs);
|
|
14203
|
+
state.lastContentResponseAtMs = nowMs;
|
|
14204
|
+
}
|
|
14205
|
+
function updateStreamStatusNote(state, note, nowMs) {
|
|
14206
|
+
state.statusNote = (note || "").trim() || null;
|
|
14207
|
+
if (state.statusNote) {
|
|
14208
|
+
recordStreamActivity(state, nowMs);
|
|
14209
|
+
}
|
|
14210
|
+
}
|
|
14211
|
+
function formatRuntimeDuration(ms) {
|
|
14212
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
14213
|
+
const seconds = totalSeconds % 60;
|
|
14214
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
14215
|
+
const minutes = totalMinutes % 60;
|
|
14216
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
14217
|
+
const parts = [];
|
|
14218
|
+
if (hours > 0) parts.push(`${hours}\u5C0F\u65F6`);
|
|
14219
|
+
if (minutes > 0) parts.push(`${minutes}\u5206`);
|
|
14220
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}\u79D2`);
|
|
14221
|
+
return parts.join("");
|
|
14222
|
+
}
|
|
14223
|
+
function formatStreamRuntimeStatus(elapsedMs, lastContentResponseAgeMs, statusNote) {
|
|
14224
|
+
const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
|
|
14225
|
+
if (typeof lastContentResponseAgeMs === "number" && lastContentResponseAgeMs >= 0) {
|
|
14226
|
+
parts.push(`\u4E0A\u6B21\u54CD\u5E94\u8DDD\u4ECA ${formatRuntimeDuration(lastContentResponseAgeMs)}`);
|
|
14227
|
+
}
|
|
14228
|
+
const runtimeText = parts.join("\uFF0C");
|
|
14229
|
+
const note = (statusNote || "").trim();
|
|
14230
|
+
return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
|
|
14231
|
+
${runtimeText}` : runtimeText;
|
|
14232
|
+
}
|
|
14233
|
+
function getStreamLastContentResponseAgeMs(state, nowMs, options = {}) {
|
|
14234
|
+
const fallbackToStart = options.fallbackToStart !== false;
|
|
14235
|
+
const base = state.lastContentResponseAtMs ?? (fallbackToStart ? state.startedAtMs : null);
|
|
14236
|
+
if (base == null || !Number.isFinite(base) || !Number.isFinite(nowMs)) return null;
|
|
14237
|
+
return Math.max(0, nowMs - base);
|
|
14238
|
+
}
|
|
14239
|
+
function shouldShowStreamLastContentResponseAge(state, nowMs, config2) {
|
|
14240
|
+
if (!Number.isFinite(nowMs)) return false;
|
|
14241
|
+
const elapsedMs = nowMs - state.startedAtMs;
|
|
14242
|
+
if (elapsedMs < Math.max(0, config2.idleStartMs)) return false;
|
|
14243
|
+
const ageMs = getStreamLastContentResponseAgeMs(state, nowMs);
|
|
14244
|
+
return ageMs != null && ageMs >= Math.max(1e3, config2.heartbeatMs);
|
|
14245
|
+
}
|
|
14246
|
+
function buildStreamRuntimeStatus(state, nowMs, options = {}) {
|
|
14247
|
+
return formatStreamRuntimeStatus(
|
|
14248
|
+
Math.max(0, nowMs - state.startedAtMs),
|
|
14249
|
+
options.includeLastContentResponseAge ? getStreamLastContentResponseAgeMs(state, nowMs) : null,
|
|
14250
|
+
state.statusNote
|
|
14251
|
+
);
|
|
14252
|
+
}
|
|
14253
|
+
|
|
13890
14254
|
// src/lib/bridge/interactive-message-runner.ts
|
|
13891
14255
|
function generateDraftId() {
|
|
13892
14256
|
return Math.floor(Math.random() * 2147483646) + 1;
|
|
@@ -13930,18 +14294,6 @@ function flushPreview(adapter, state, config2) {
|
|
|
13930
14294
|
}).catch(() => {
|
|
13931
14295
|
});
|
|
13932
14296
|
}
|
|
13933
|
-
function formatRuntimeDuration(ms) {
|
|
13934
|
-
const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
|
|
13935
|
-
const seconds = totalSeconds % 60;
|
|
13936
|
-
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
13937
|
-
const minutes = totalMinutes % 60;
|
|
13938
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
13939
|
-
const parts = [];
|
|
13940
|
-
if (hours > 0) parts.push(`${hours}\u5C0F\u65F6`);
|
|
13941
|
-
if (minutes > 0) parts.push(`${minutes}\u5206`);
|
|
13942
|
-
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}\u79D2`);
|
|
13943
|
-
return parts.join("");
|
|
13944
|
-
}
|
|
13945
14297
|
function pathBaseName(value) {
|
|
13946
14298
|
return value.includes("\\") ? path13.win32.basename(value) : path13.basename(value);
|
|
13947
14299
|
}
|
|
@@ -13963,18 +14315,10 @@ function buildStaleTaskCompletionNotice(address, binding) {
|
|
|
13963
14315
|
const taskName = formatTaskDisplayName(binding);
|
|
13964
14316
|
return `\u65E7\u4F1A\u8BDD\u300C${taskName}\u300D\u4EFB\u52A1\u5DF2\u7ED3\u675F\uFF0C\u4F46\u5F53\u524D\u804A\u5929\u5DF2\u5207\u6362\u5230\u5176\u4ED6\u4F1A\u8BDD\uFF0C\u56DE\u590D\u5DF2\u8DF3\u8FC7\u3002`;
|
|
13965
14317
|
}
|
|
13966
|
-
function formatInteractiveRuntimeStatus(elapsedMs, lastResponseAgeMs, statusNote) {
|
|
13967
|
-
const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
|
|
13968
|
-
if (typeof lastResponseAgeMs === "number" && lastResponseAgeMs >= 0) {
|
|
13969
|
-
parts.push(`\u4E0A\u6B21\u54CD\u5E94\u8DDD\u4ECA ${formatRuntimeDuration(lastResponseAgeMs)}`);
|
|
13970
|
-
}
|
|
13971
|
-
const runtimeText = parts.join("\uFF0C");
|
|
13972
|
-
const note = (statusNote || "").trim();
|
|
13973
|
-
return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
|
|
13974
|
-
${runtimeText}` : runtimeText;
|
|
13975
|
-
}
|
|
13976
14318
|
async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
13977
14319
|
const binding = resolve(msg.address);
|
|
14320
|
+
const initialSession = getBridgeContext().store.getSession(binding.codepilotSessionId);
|
|
14321
|
+
const desktopThreadId = getExplicitDesktopThreadId(initialSession);
|
|
13978
14322
|
const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
|
|
13979
14323
|
const nowMs = deps.nowMs ?? (() => Date.now());
|
|
13980
14324
|
const setIntervalFn = deps.setIntervalFn ?? ((callback, intervalMs) => setInterval(callback, intervalMs));
|
|
@@ -14000,11 +14344,14 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14000
14344
|
const taskAbort = new AbortController();
|
|
14001
14345
|
const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
14002
14346
|
const taskStartedAt = nowMs();
|
|
14347
|
+
const streamState = createStreamState(taskStartedAt);
|
|
14003
14348
|
let externalTerminalRequest = null;
|
|
14349
|
+
let desktopTerminalFinalExpected = false;
|
|
14004
14350
|
let resolveExternalTerminal = null;
|
|
14005
14351
|
const externalTerminalPromise = new Promise((resolve2) => {
|
|
14006
14352
|
resolveExternalTerminal = resolve2;
|
|
14007
14353
|
});
|
|
14354
|
+
let processResultSettled = false;
|
|
14008
14355
|
let resolveExternalTerminalCompletion = null;
|
|
14009
14356
|
let externalTerminalCompletionSettled = false;
|
|
14010
14357
|
const externalTerminalCompletion = new Promise((resolve2) => {
|
|
@@ -14028,6 +14375,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14028
14375
|
structuredStreamUiActive: false,
|
|
14029
14376
|
lastActivityAt: taskStartedAt,
|
|
14030
14377
|
lastResponseAt: null,
|
|
14378
|
+
lastContentResponseAt: null,
|
|
14031
14379
|
streamFinalized: false,
|
|
14032
14380
|
uiEnded: false,
|
|
14033
14381
|
mirrorSuppressionId: null,
|
|
@@ -14036,13 +14384,26 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14036
14384
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return false;
|
|
14037
14385
|
externalTerminalRequest = { outcome, detail, finalText };
|
|
14038
14386
|
resolveExternalTerminal?.(externalTerminalRequest);
|
|
14039
|
-
if (!taskAbort.signal.aborted) {
|
|
14387
|
+
if (!processResultSettled && !taskAbort.signal.aborted) {
|
|
14040
14388
|
taskAbort.abort();
|
|
14041
14389
|
}
|
|
14042
14390
|
return externalTerminalCompletion;
|
|
14043
14391
|
}
|
|
14044
14392
|
};
|
|
14045
14393
|
deps.registerInteractiveTask(taskState);
|
|
14394
|
+
deps.registerBridgeTurn?.({
|
|
14395
|
+
id: taskId,
|
|
14396
|
+
sessionId: binding.codepilotSessionId,
|
|
14397
|
+
kind: desktopThreadId ? "im_desktop_reuse" : "im_sdk",
|
|
14398
|
+
origin: "im",
|
|
14399
|
+
progressSource: "sdk_stream",
|
|
14400
|
+
finalSource: desktopThreadId ? "desktop_task_complete" : "sdk_result",
|
|
14401
|
+
codexThreadId: binding.sdkSessionId || initialSession?.codex_thread_id || initialSession?.sdk_session_id || void 0,
|
|
14402
|
+
desktopThreadId,
|
|
14403
|
+
requestMessageId: msg.messageId,
|
|
14404
|
+
streamKey,
|
|
14405
|
+
startedAt: taskStartedAt
|
|
14406
|
+
});
|
|
14046
14407
|
deps.recordInteractiveHealthStart(binding.codepilotSessionId);
|
|
14047
14408
|
let previewState = null;
|
|
14048
14409
|
const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
|
|
@@ -14062,7 +14423,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14062
14423
|
const ps = previewState;
|
|
14063
14424
|
const cfg = streamCfg;
|
|
14064
14425
|
if (ps.degraded) return;
|
|
14065
|
-
const sanitizedText =
|
|
14426
|
+
const sanitizedText = stripFinalOnlyBlocksForStreaming(fullText);
|
|
14066
14427
|
ps.pendingText = sanitizedText.length > cfg.maxChars ? sanitizedText.slice(0, cfg.maxChars) + "..." : sanitizedText;
|
|
14067
14428
|
const delta = ps.pendingText.length - ps.lastSentText.length;
|
|
14068
14429
|
const elapsed = Date.now() - ps.lastSentAt;
|
|
@@ -14104,7 +14465,6 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14104
14465
|
};
|
|
14105
14466
|
const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
|
|
14106
14467
|
const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
|
|
14107
|
-
let latestStatusNote = null;
|
|
14108
14468
|
let latestTasks = [];
|
|
14109
14469
|
const syncStructuredStreamUiState = () => {
|
|
14110
14470
|
if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
|
|
@@ -14123,12 +14483,22 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14123
14483
|
if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
|
|
14124
14484
|
pushStreamFeedbackStatus(
|
|
14125
14485
|
streamFeedbackTarget,
|
|
14126
|
-
|
|
14486
|
+
lastResponseAgeMs == null ? buildStreamRuntimeStatus(streamState, nowMs()) : formatStreamRuntimeStatus(nowMs() - taskStartedAt, lastResponseAgeMs, streamState.statusNote)
|
|
14127
14487
|
);
|
|
14128
14488
|
syncStructuredStreamUiSnapshot();
|
|
14129
14489
|
};
|
|
14130
|
-
const
|
|
14131
|
-
|
|
14490
|
+
const markActivity = () => {
|
|
14491
|
+
const now2 = nowMs();
|
|
14492
|
+
recordStreamActivity(streamState, now2);
|
|
14493
|
+
taskState.lastActivityAt = streamState.lastActivityAtMs;
|
|
14494
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14495
|
+
};
|
|
14496
|
+
const markContentResponse = () => {
|
|
14497
|
+
const now2 = nowMs();
|
|
14498
|
+
recordStreamContentResponse(streamState, now2);
|
|
14499
|
+
taskState.lastActivityAt = streamState.lastActivityAtMs;
|
|
14500
|
+
taskState.lastResponseAt = streamState.lastContentResponseAtMs;
|
|
14501
|
+
taskState.lastContentResponseAt = streamState.lastContentResponseAtMs;
|
|
14132
14502
|
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14133
14503
|
};
|
|
14134
14504
|
let streamStatusHeartbeat = null;
|
|
@@ -14167,10 +14537,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14167
14537
|
endPreviewOnce();
|
|
14168
14538
|
if (hasStreamingCards && !streamUiFinalizeAttempted) {
|
|
14169
14539
|
streamUiFinalizeAttempted = true;
|
|
14170
|
-
taskState.streamFinalized = await
|
|
14540
|
+
taskState.streamFinalized = await finalizeStreamingUi(
|
|
14171
14541
|
streamFeedbackTarget,
|
|
14172
14542
|
status,
|
|
14173
|
-
responseText
|
|
14543
|
+
assembleDesktopFinalResponse({ text: responseText })
|
|
14174
14544
|
);
|
|
14175
14545
|
}
|
|
14176
14546
|
return taskState.streamFinalized;
|
|
@@ -14184,12 +14554,12 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14184
14554
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14185
14555
|
pushStreamFeedbackText(
|
|
14186
14556
|
streamFeedbackTarget,
|
|
14187
|
-
|
|
14557
|
+
stripFinalOnlyBlocksForStreaming(fullText)
|
|
14188
14558
|
);
|
|
14189
14559
|
} : void 0;
|
|
14190
14560
|
const onToolEvent = (toolId, toolName, status) => {
|
|
14191
14561
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14192
|
-
|
|
14562
|
+
markActivity();
|
|
14193
14563
|
deps.recordInteractiveHealthTool(binding.codepilotSessionId, toolId, toolName, status);
|
|
14194
14564
|
if (toolName) {
|
|
14195
14565
|
toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
|
|
@@ -14205,7 +14575,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14205
14575
|
};
|
|
14206
14576
|
const onTaskEvent = (tasks) => {
|
|
14207
14577
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14208
|
-
|
|
14578
|
+
markActivity();
|
|
14209
14579
|
latestTasks = tasks;
|
|
14210
14580
|
if (hasStreamingCards) {
|
|
14211
14581
|
pushStreamFeedbackTasks(streamFeedbackTarget, latestTasks);
|
|
@@ -14215,17 +14585,15 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14215
14585
|
};
|
|
14216
14586
|
const onStatusNote = (note) => {
|
|
14217
14587
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14218
|
-
|
|
14219
|
-
if (
|
|
14220
|
-
markSuccessfulResponse();
|
|
14221
|
-
}
|
|
14588
|
+
updateStreamStatusNote(streamState, note, nowMs());
|
|
14589
|
+
if (streamState.statusNote) markActivity();
|
|
14222
14590
|
pushRunningStatus(null);
|
|
14223
14591
|
syncStructuredStreamUiSnapshot();
|
|
14224
14592
|
};
|
|
14225
14593
|
const onPartialText = (fullText) => {
|
|
14226
14594
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14227
14595
|
if (fullText.trim()) {
|
|
14228
|
-
|
|
14596
|
+
markContentResponse();
|
|
14229
14597
|
}
|
|
14230
14598
|
deps.recordInteractiveHealthProgress(binding.codepilotSessionId, "text");
|
|
14231
14599
|
previewOnPartialText?.(fullText);
|
|
@@ -14233,6 +14601,34 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14233
14601
|
pushRunningStatus(null);
|
|
14234
14602
|
syncStructuredStreamUiSnapshot();
|
|
14235
14603
|
};
|
|
14604
|
+
const waitForDesktopTerminalFinalization = async () => {
|
|
14605
|
+
if (externalTerminalRequest) return externalTerminalRequest;
|
|
14606
|
+
const timeoutMs = Math.max(0, deps.desktopTerminalFinalizationTimeoutMs ?? 0);
|
|
14607
|
+
if (!desktopThreadId || !desktopTerminalFinalExpected || timeoutMs <= 0) return null;
|
|
14608
|
+
if (taskAbort.signal.aborted) return null;
|
|
14609
|
+
return new Promise((resolve2) => {
|
|
14610
|
+
let settled = false;
|
|
14611
|
+
let timer = null;
|
|
14612
|
+
const finish = (terminal) => {
|
|
14613
|
+
if (settled) return;
|
|
14614
|
+
settled = true;
|
|
14615
|
+
if (timer) {
|
|
14616
|
+
clearTimeout(timer);
|
|
14617
|
+
timer = null;
|
|
14618
|
+
}
|
|
14619
|
+
taskAbort.signal.removeEventListener("abort", onAbort);
|
|
14620
|
+
resolve2(terminal);
|
|
14621
|
+
};
|
|
14622
|
+
const onAbort = () => finish(null);
|
|
14623
|
+
timer = setTimeout(() => finish(null), timeoutMs);
|
|
14624
|
+
taskAbort.signal.addEventListener("abort", onAbort, { once: true });
|
|
14625
|
+
externalTerminalPromise.then((terminal) => {
|
|
14626
|
+
finish(terminal);
|
|
14627
|
+
}, () => {
|
|
14628
|
+
finish(null);
|
|
14629
|
+
});
|
|
14630
|
+
});
|
|
14631
|
+
};
|
|
14236
14632
|
if (supportsStructuredStreamUi) {
|
|
14237
14633
|
pushRunningStatus(null);
|
|
14238
14634
|
streamStatusHeartbeat = setIntervalFn(() => {
|
|
@@ -14245,8 +14641,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14245
14641
|
return;
|
|
14246
14642
|
}
|
|
14247
14643
|
const elapsedMs = nowMs() - taskStartedAt;
|
|
14248
|
-
const
|
|
14249
|
-
|
|
14644
|
+
const showLastResponseAge = shouldShowStreamLastContentResponseAge(streamState, nowMs(), {
|
|
14645
|
+
idleStartMs: streamStatusIdleDetectionStartMs,
|
|
14646
|
+
heartbeatMs: streamStatusHeartbeatMs
|
|
14647
|
+
}) ? getStreamLastContentResponseAgeMs(streamState, nowMs()) : null;
|
|
14250
14648
|
pushRunningStatus(showLastResponseAge);
|
|
14251
14649
|
syncStructuredStreamUiSnapshot();
|
|
14252
14650
|
}, streamStatusHeartbeatMs);
|
|
@@ -14254,6 +14652,22 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14254
14652
|
let finalOutcome = "failed";
|
|
14255
14653
|
let finalOutcomeDetail;
|
|
14256
14654
|
let shouldRecordHealthEnd = true;
|
|
14655
|
+
let forceStopStarted = false;
|
|
14656
|
+
taskState.forceStop = async (detail = "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002") => {
|
|
14657
|
+
if (forceStopStarted) return true;
|
|
14658
|
+
forceStopStarted = true;
|
|
14659
|
+
finalOutcome = "aborted";
|
|
14660
|
+
finalOutcomeDetail = detail;
|
|
14661
|
+
taskAbort.abort();
|
|
14662
|
+
stopStructuredStreamStatusUpdates();
|
|
14663
|
+
endPreviewOnce();
|
|
14664
|
+
try {
|
|
14665
|
+
await finalizeStreamUiOnce("interrupted", detail);
|
|
14666
|
+
} catch {
|
|
14667
|
+
}
|
|
14668
|
+
endMessageUiOnce();
|
|
14669
|
+
return true;
|
|
14670
|
+
};
|
|
14257
14671
|
try {
|
|
14258
14672
|
const promptText = text2 || (attachments && attachments.length > 0 ? "Describe this image." : "");
|
|
14259
14673
|
const processPromise = processMessageImpl(
|
|
@@ -14275,7 +14689,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14275
14689
|
"permission_wait",
|
|
14276
14690
|
`\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${perm.toolName} \u7684\u6743\u9650\u786E\u8BA4\u3002`
|
|
14277
14691
|
);
|
|
14278
|
-
|
|
14692
|
+
markActivity();
|
|
14279
14693
|
pushRunningStatus(null);
|
|
14280
14694
|
syncStructuredStreamUiSnapshot();
|
|
14281
14695
|
},
|
|
@@ -14286,7 +14700,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14286
14700
|
onTaskEvent,
|
|
14287
14701
|
onStatusNote,
|
|
14288
14702
|
(preparedPrompt) => {
|
|
14289
|
-
if (
|
|
14703
|
+
if (desktopThreadId) {
|
|
14704
|
+
desktopTerminalFinalExpected = true;
|
|
14705
|
+
}
|
|
14706
|
+
if (desktopThreadId && !taskState.mirrorSuppressionId) {
|
|
14290
14707
|
taskState.mirrorSuppressionId = deps.beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
14291
14708
|
}
|
|
14292
14709
|
}
|
|
@@ -14308,73 +14725,87 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14308
14725
|
finalOutcomeDetail = raced.terminal.detail;
|
|
14309
14726
|
const streamEndStatus = raced.terminal.outcome === "completed" ? "completed" : raced.terminal.outcome === "aborted" ? "interrupted" : "error";
|
|
14310
14727
|
const staleTaskNotice2 = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14311
|
-
const
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14728
|
+
const terminalResponse2 = assembleDesktopFinalResponse({
|
|
14729
|
+
text: staleTaskNotice2 || raced.terminal.finalText || ""
|
|
14730
|
+
});
|
|
14731
|
+
const cardFinalized2 = await finalizeStreamUiOnce(streamEndStatus, terminalResponse2.text);
|
|
14732
|
+
if (hasFinalResponsePayload(terminalResponse2)) {
|
|
14733
|
+
await deliverFinalResponse({
|
|
14315
14734
|
adapter,
|
|
14316
|
-
msg.address,
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
);
|
|
14735
|
+
address: msg.address,
|
|
14736
|
+
sessionId: binding.codepilotSessionId,
|
|
14737
|
+
replyToMessageId: msg.messageId,
|
|
14738
|
+
deliverResponse: deps.deliverResponse
|
|
14739
|
+
}, terminalResponse2, { skipText: cardFinalized2 });
|
|
14322
14740
|
}
|
|
14323
14741
|
return;
|
|
14324
14742
|
}
|
|
14325
14743
|
const result = raced.result;
|
|
14744
|
+
processResultSettled = true;
|
|
14326
14745
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
14327
14746
|
shouldRecordHealthEnd = false;
|
|
14328
14747
|
return;
|
|
14329
14748
|
}
|
|
14749
|
+
const terminalAfterProcess = await waitForDesktopTerminalFinalization();
|
|
14750
|
+
const terminalResponse = terminalAfterProcess?.outcome === "completed" ? assembleDesktopFinalResponse({ text: terminalAfterProcess.finalText || "" }) : null;
|
|
14751
|
+
const sdkResponse = assembleSdkFinalResponse({
|
|
14752
|
+
text: result.responseText,
|
|
14753
|
+
attachments: result.outboundAttachments,
|
|
14754
|
+
hasError: result.hasError,
|
|
14755
|
+
errorMessage: result.errorMessage
|
|
14756
|
+
});
|
|
14757
|
+
const terminalHasFinalPayload = Boolean(
|
|
14758
|
+
terminalResponse && hasFinalResponsePayload(terminalResponse)
|
|
14759
|
+
);
|
|
14760
|
+
const effectiveResponse = terminalResponse && terminalHasFinalPayload ? mergeFinalResponses(terminalResponse, sdkResponse) : sdkResponse;
|
|
14330
14761
|
let cardFinalized = false;
|
|
14331
14762
|
const staleTaskNotice = buildStaleTaskCompletionNotice(msg.address, binding);
|
|
14763
|
+
const staleResponse = staleTaskNotice ? assembleDesktopFinalResponse({ text: staleTaskNotice }) : null;
|
|
14332
14764
|
if (hasStreamingCards) {
|
|
14333
|
-
const streamEndStatus = taskAbort.signal.aborted ? "interrupted" : result.hasError ? "error" : "completed";
|
|
14765
|
+
const streamEndStatus = terminalAfterProcess ? terminalAfterProcess.outcome === "completed" ? "completed" : terminalAfterProcess.outcome === "aborted" ? "interrupted" : "error" : taskAbort.signal.aborted ? "interrupted" : result.hasError ? "error" : "completed";
|
|
14334
14766
|
cardFinalized = await finalizeStreamUiOnce(
|
|
14335
14767
|
streamEndStatus,
|
|
14336
|
-
|
|
14768
|
+
staleResponse?.text || (streamEndStatus === "interrupted" ? "" : effectiveResponse.text)
|
|
14337
14769
|
);
|
|
14338
14770
|
}
|
|
14339
|
-
if (
|
|
14340
|
-
|
|
14341
|
-
await deps.deliverResponse(
|
|
14342
|
-
adapter,
|
|
14343
|
-
msg.address,
|
|
14344
|
-
staleTaskNotice,
|
|
14345
|
-
binding.codepilotSessionId,
|
|
14346
|
-
msg.messageId,
|
|
14347
|
-
[]
|
|
14348
|
-
);
|
|
14349
|
-
}
|
|
14350
|
-
} else if (result.responseText || result.outboundAttachments.length > 0) {
|
|
14351
|
-
const textToDeliver = cardFinalized ? "" : result.responseText;
|
|
14352
|
-
if (!cardFinalized || result.outboundAttachments.length > 0) {
|
|
14353
|
-
await deps.deliverResponse(
|
|
14354
|
-
adapter,
|
|
14355
|
-
msg.address,
|
|
14356
|
-
textToDeliver,
|
|
14357
|
-
binding.codepilotSessionId,
|
|
14358
|
-
msg.messageId,
|
|
14359
|
-
result.outboundAttachments
|
|
14360
|
-
);
|
|
14361
|
-
}
|
|
14362
|
-
} else if (result.hasError && !taskAbort.signal.aborted) {
|
|
14363
|
-
await deps.deliverResponse(
|
|
14771
|
+
if (staleResponse) {
|
|
14772
|
+
await deliverFinalResponse({
|
|
14364
14773
|
adapter,
|
|
14365
|
-
msg.address,
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14774
|
+
address: msg.address,
|
|
14775
|
+
sessionId: binding.codepilotSessionId,
|
|
14776
|
+
replyToMessageId: msg.messageId,
|
|
14777
|
+
deliverResponse: deps.deliverResponse
|
|
14778
|
+
}, staleResponse, { skipText: cardFinalized });
|
|
14779
|
+
} else if (hasFinalResponsePayload(effectiveResponse)) {
|
|
14780
|
+
await deliverFinalResponse({
|
|
14781
|
+
adapter,
|
|
14782
|
+
address: msg.address,
|
|
14783
|
+
sessionId: binding.codepilotSessionId,
|
|
14784
|
+
replyToMessageId: msg.messageId,
|
|
14785
|
+
deliverResponse: deps.deliverResponse
|
|
14786
|
+
}, effectiveResponse, { skipText: cardFinalized });
|
|
14787
|
+
} else if (result.hasError && !taskAbort.signal.aborted) {
|
|
14788
|
+
await deliverFinalResponse(
|
|
14789
|
+
{
|
|
14790
|
+
adapter,
|
|
14791
|
+
address: msg.address,
|
|
14792
|
+
sessionId: binding.codepilotSessionId,
|
|
14793
|
+
replyToMessageId: msg.messageId,
|
|
14794
|
+
deliverResponse: deps.deliverResponse
|
|
14795
|
+
},
|
|
14796
|
+
assembleSdkFinalResponse({
|
|
14797
|
+
text: `**Error:** ${result.errorMessage}`,
|
|
14798
|
+
hasError: true,
|
|
14799
|
+
errorMessage: result.errorMessage
|
|
14800
|
+
})
|
|
14370
14801
|
);
|
|
14371
14802
|
}
|
|
14372
14803
|
try {
|
|
14373
14804
|
deps.persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
|
|
14374
14805
|
} catch {
|
|
14375
14806
|
}
|
|
14376
|
-
finalOutcome = result.hasError ? "failed" : "completed";
|
|
14377
|
-
finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
|
|
14807
|
+
finalOutcome = terminalAfterProcess?.outcome || (result.hasError ? "failed" : "completed");
|
|
14808
|
+
finalOutcomeDetail = terminalAfterProcess?.detail || (result.hasError ? result.errorMessage?.trim() || void 0 : void 0);
|
|
14378
14809
|
} finally {
|
|
14379
14810
|
await finalizeStreamUiOnce(
|
|
14380
14811
|
taskAbort.signal.aborted ? "interrupted" : finalOutcome === "completed" ? "completed" : "error",
|
|
@@ -14391,11 +14822,12 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14391
14822
|
if (shouldRecordHealthEnd) {
|
|
14392
14823
|
if (taskAbort.signal.aborted && !externalTerminalRequest) {
|
|
14393
14824
|
finalOutcome = "aborted";
|
|
14394
|
-
finalOutcomeDetail = "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
14825
|
+
finalOutcomeDetail = finalOutcomeDetail || "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
14395
14826
|
}
|
|
14396
14827
|
deps.recordInteractiveHealthEnd(binding.codepilotSessionId, finalOutcome, finalOutcomeDetail);
|
|
14397
14828
|
}
|
|
14398
14829
|
deps.releaseInteractiveTask(binding.codepilotSessionId, taskId);
|
|
14830
|
+
deps.releaseBridgeTurn?.(binding.codepilotSessionId, taskId);
|
|
14399
14831
|
endMessageUiOnce();
|
|
14400
14832
|
settleExternalTerminalCompletion(taskState.streamFinalized || !hasStreamingCards);
|
|
14401
14833
|
}
|
|
@@ -14410,13 +14842,14 @@ var TERMINAL_SESSION_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
|
14410
14842
|
function isTerminalSessionHealthStatus(status) {
|
|
14411
14843
|
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
14412
14844
|
}
|
|
14413
|
-
function terminalOutcomeFromHealthStatus(status) {
|
|
14414
|
-
if (status === "completed") return "completed";
|
|
14415
|
-
if (status === "failed") return "failed";
|
|
14416
|
-
if (status === "aborted") return "aborted";
|
|
14417
|
-
return null;
|
|
14418
|
-
}
|
|
14419
14845
|
function createInteractiveRuntime(getState2, deps) {
|
|
14846
|
+
const sessionLockVersions = /* @__PURE__ */ new Map();
|
|
14847
|
+
function getSessionLockVersion(sessionId) {
|
|
14848
|
+
return sessionLockVersions.get(sessionId) || 0;
|
|
14849
|
+
}
|
|
14850
|
+
function invalidateSessionLockQueue(sessionId) {
|
|
14851
|
+
sessionLockVersions.set(sessionId, getSessionLockVersion(sessionId) + 1);
|
|
14852
|
+
}
|
|
14420
14853
|
function getQueuedCount(sessionId) {
|
|
14421
14854
|
return getState2().queuedCounts.get(sessionId) || 0;
|
|
14422
14855
|
}
|
|
@@ -14463,18 +14896,28 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14463
14896
|
if (!task?.finalizeFromExternalTerminal) return false;
|
|
14464
14897
|
return task.finalizeFromExternalTerminal(outcome, detail, finalText);
|
|
14465
14898
|
}
|
|
14899
|
+
async function forceStopSession(sessionId, detail) {
|
|
14900
|
+
const state = getState2();
|
|
14901
|
+
const task = state.activeTasks.get(sessionId);
|
|
14902
|
+
let handled = false;
|
|
14903
|
+
if (task?.forceStop) {
|
|
14904
|
+
handled = await task.forceStop(detail);
|
|
14905
|
+
} else if (task) {
|
|
14906
|
+
task.abortController.abort();
|
|
14907
|
+
handled = true;
|
|
14908
|
+
}
|
|
14909
|
+
state.activeTasks.delete(sessionId);
|
|
14910
|
+
state.queuedCounts.delete(sessionId);
|
|
14911
|
+
state.sessionLocks.delete(sessionId);
|
|
14912
|
+
invalidateSessionLockQueue(sessionId);
|
|
14913
|
+
syncSessionRuntimeState(sessionId);
|
|
14914
|
+
return handled;
|
|
14915
|
+
}
|
|
14466
14916
|
async function reconcileTerminalSessionRuntimeState() {
|
|
14467
14917
|
const store = deps.getStore();
|
|
14468
14918
|
for (const session of store.listSessions()) {
|
|
14469
14919
|
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
14470
|
-
|
|
14471
|
-
if (activeTask) {
|
|
14472
|
-
const outcome = terminalOutcomeFromHealthStatus(session.health_status);
|
|
14473
|
-
if (outcome) {
|
|
14474
|
-
await finalizeTerminalActiveTask(session.id, outcome, session.health_reason || void 0);
|
|
14475
|
-
}
|
|
14476
|
-
continue;
|
|
14477
|
-
}
|
|
14920
|
+
if (getState2().activeTasks.has(session.id)) continue;
|
|
14478
14921
|
const queuedCount = getQueuedCount(session.id);
|
|
14479
14922
|
const persistedQueuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
|
|
14480
14923
|
if (queuedCount > 0) continue;
|
|
@@ -14521,6 +14964,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14521
14964
|
const state = getState2();
|
|
14522
14965
|
const prev = state.sessionLocks.get(sessionId) || Promise.resolve();
|
|
14523
14966
|
const queued = state.sessionLocks.has(sessionId);
|
|
14967
|
+
const lockVersion = getSessionLockVersion(sessionId);
|
|
14524
14968
|
if (queued) {
|
|
14525
14969
|
incrementQueuedCount(sessionId);
|
|
14526
14970
|
}
|
|
@@ -14528,6 +14972,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14528
14972
|
if (queued) {
|
|
14529
14973
|
decrementQueuedCount(sessionId);
|
|
14530
14974
|
}
|
|
14975
|
+
if (getSessionLockVersion(sessionId) !== lockVersion) return;
|
|
14531
14976
|
await fn();
|
|
14532
14977
|
};
|
|
14533
14978
|
const current = prev.then(wrapped, wrapped);
|
|
@@ -14549,6 +14994,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14549
14994
|
releaseInteractiveTask,
|
|
14550
14995
|
syncSessionRuntimeState,
|
|
14551
14996
|
finalizeTerminalActiveTask,
|
|
14997
|
+
forceStopSession,
|
|
14552
14998
|
reconcileTerminalSessionRuntimeState,
|
|
14553
14999
|
resetPersistedInteractiveRuntimeState,
|
|
14554
15000
|
processWithSessionLock
|
|
@@ -14887,7 +15333,7 @@ function buildMirrorSubscriptionRegistryPlan(bindings, activeChannelTypes, exist
|
|
|
14887
15333
|
if (binding.active === false) return false;
|
|
14888
15334
|
if (!activeChannels.has(binding.channelType)) return false;
|
|
14889
15335
|
const session = getSession(binding.codepilotSessionId);
|
|
14890
|
-
return Boolean(
|
|
15336
|
+
return Boolean(session?.desktop_thread_id || (session?.thread_origin === "desktop" ? session.sdk_session_id : null));
|
|
14891
15337
|
});
|
|
14892
15338
|
const desiredIds = new Set(upsertBindings.map((binding) => binding.id));
|
|
14893
15339
|
const removeBindingIds = Array.from(existingBindingIds).filter((bindingId) => !desiredIds.has(bindingId));
|
|
@@ -14981,11 +15427,17 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
14981
15427
|
function clearDanglingMirrorThread(subscription, reason) {
|
|
14982
15428
|
const { store } = getBridgeContext();
|
|
14983
15429
|
const session = store.getSession(subscription.sessionId);
|
|
14984
|
-
const currentThreadId = session
|
|
15430
|
+
const currentThreadId = getExplicitDesktopThreadId(session) || subscription.threadId;
|
|
14985
15431
|
console.warn(
|
|
14986
15432
|
`[bridge-manager] Clearing dangling desktop thread ${currentThreadId} for session ${subscription.sessionId}: ${reason}`
|
|
14987
15433
|
);
|
|
14988
15434
|
store.updateSdkSessionId(subscription.sessionId, "");
|
|
15435
|
+
store.updateSession(subscription.sessionId, {
|
|
15436
|
+
sdk_session_id: "",
|
|
15437
|
+
codex_thread_id: void 0,
|
|
15438
|
+
desktop_thread_id: void 0,
|
|
15439
|
+
thread_origin: void 0
|
|
15440
|
+
});
|
|
14989
15441
|
removeMirrorSubscription(subscription.bindingId);
|
|
14990
15442
|
}
|
|
14991
15443
|
function upsertMirrorSubscription(binding) {
|
|
@@ -14996,7 +15448,7 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
14996
15448
|
removeMirrorSubscription(binding.id);
|
|
14997
15449
|
return;
|
|
14998
15450
|
}
|
|
14999
|
-
const threadId =
|
|
15451
|
+
const threadId = getExplicitDesktopThreadId(session) || "";
|
|
15000
15452
|
if (!threadId) {
|
|
15001
15453
|
removeMirrorSubscription(binding.id);
|
|
15002
15454
|
return;
|
|
@@ -15109,11 +15561,13 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
15109
15561
|
`[bridge-manager] Unhandled desktop mirror event for thread ${subscription.threadId}: ${kind}`
|
|
15110
15562
|
);
|
|
15111
15563
|
}
|
|
15112
|
-
|
|
15113
|
-
|
|
15564
|
+
const routeResult = deliverableRecords.length > 0 && deps.routeDesktopRecords ? await deps.routeDesktopRecords(subscription.sessionId, subscription.threadId, deliverableRecords) : { claimed: [], unclaimed: deliverableRecords, terminalClaimed: false };
|
|
15565
|
+
const mirrorRecords = routeResult.unclaimed;
|
|
15566
|
+
if (mirrorRecords.length > 0) {
|
|
15567
|
+
deps.observeSessionHealthRecords(subscription.sessionId, subscription.threadId, mirrorRecords);
|
|
15114
15568
|
}
|
|
15115
|
-
const blocked = getState2().activeTasks.has(subscription.sessionId)
|
|
15116
|
-
const deliveryPlan = buildMirrorDeliveryPlan(subscription,
|
|
15569
|
+
const blocked = getState2().activeTasks.has(subscription.sessionId);
|
|
15570
|
+
const deliveryPlan = buildMirrorDeliveryPlan(subscription, mirrorRecords, {
|
|
15117
15571
|
blocked,
|
|
15118
15572
|
filterSuppressedRecords: deps.filterSuppressedMirrorRecords,
|
|
15119
15573
|
flushTimedOutTurn: (currentSubscription) => deps.flushTimedOutMirrorTurn(currentSubscription),
|
|
@@ -15210,6 +15664,241 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
15210
15664
|
};
|
|
15211
15665
|
}
|
|
15212
15666
|
|
|
15667
|
+
// src/lib/bridge/mirror-feedback-controller.ts
|
|
15668
|
+
function createMirrorStreamFeedbackTarget(subscription, turnState, adapter, startMirrorStreaming) {
|
|
15669
|
+
return {
|
|
15670
|
+
adapter,
|
|
15671
|
+
channelType: subscription.channelType,
|
|
15672
|
+
chatId: subscription.chatId,
|
|
15673
|
+
streamKey: turnState.streamKey,
|
|
15674
|
+
ensureStarted: () => {
|
|
15675
|
+
startMirrorStreaming(subscription, turnState);
|
|
15676
|
+
}
|
|
15677
|
+
};
|
|
15678
|
+
}
|
|
15679
|
+
function createMirrorFeedbackController(deps) {
|
|
15680
|
+
function getMirrorStreamingAdapter(subscription) {
|
|
15681
|
+
const adapter = deps.getAdapter(subscription.channelType);
|
|
15682
|
+
if (!adapter || !adapter.isRunning()) return null;
|
|
15683
|
+
if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
|
|
15684
|
+
if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
|
|
15685
|
+
return null;
|
|
15686
|
+
}
|
|
15687
|
+
return adapter;
|
|
15688
|
+
}
|
|
15689
|
+
function getMirrorStreamingText(subscription, turnState) {
|
|
15690
|
+
const title = deps.getThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
15691
|
+
const markdown = getFeedbackParseMode(subscription.channelType) === "Markdown";
|
|
15692
|
+
const rendered = formatMirrorMessage(
|
|
15693
|
+
title,
|
|
15694
|
+
turnState.userText,
|
|
15695
|
+
stripOutboundArtifactBlocksForStreaming(turnState.streamedText),
|
|
15696
|
+
markdown,
|
|
15697
|
+
true
|
|
15698
|
+
);
|
|
15699
|
+
return rendered || buildMirrorTitle(title, markdown);
|
|
15700
|
+
}
|
|
15701
|
+
function startMirrorStreaming(subscription, turnState) {
|
|
15702
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15703
|
+
if (!adapter || turnState.streamStarted) return;
|
|
15704
|
+
try {
|
|
15705
|
+
adapter.onMirrorStreamStart?.(subscription.chatId, turnState.streamKey);
|
|
15706
|
+
if (!adapter.onMirrorStreamStart) {
|
|
15707
|
+
adapter.onStreamText?.(subscription.chatId, "", turnState.streamKey);
|
|
15708
|
+
}
|
|
15709
|
+
turnState.streamStarted = true;
|
|
15710
|
+
} catch {
|
|
15711
|
+
}
|
|
15712
|
+
}
|
|
15713
|
+
function createStreamTarget(subscription, turnState, adapter) {
|
|
15714
|
+
return createMirrorStreamFeedbackTarget(subscription, turnState, adapter, startMirrorStreaming);
|
|
15715
|
+
}
|
|
15716
|
+
function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
15717
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15718
|
+
if (!adapter || typeof adapter.onStreamStatus !== "function") return;
|
|
15719
|
+
if (!(adapter.supportsStructuredStreamingUi?.(subscription.chatId) ?? true)) return;
|
|
15720
|
+
const startedAtMs = Date.parse(turnState.startedAt);
|
|
15721
|
+
if (!Number.isFinite(startedAtMs)) return;
|
|
15722
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
15723
|
+
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 0);
|
|
15724
|
+
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
15725
|
+
return;
|
|
15726
|
+
}
|
|
15727
|
+
const statusText = formatStreamRuntimeStatus(
|
|
15728
|
+
Math.max(0, nowMs - startedAtMs),
|
|
15729
|
+
options.lastResponseAgeMs,
|
|
15730
|
+
turnState.statusNote
|
|
15731
|
+
);
|
|
15732
|
+
if (turnState.lastStatusText === statusText) return;
|
|
15733
|
+
const pushed = pushStreamFeedbackStatus(
|
|
15734
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15735
|
+
statusText
|
|
15736
|
+
);
|
|
15737
|
+
if (!pushed) return;
|
|
15738
|
+
turnState.lastStatusText = statusText;
|
|
15739
|
+
turnState.lastStatusAt = nowMs;
|
|
15740
|
+
}
|
|
15741
|
+
function refreshMirrorStreamingStatus2(subscription, nowMs = Date.now(), config2) {
|
|
15742
|
+
const pendingTurn = subscription.pendingTurn;
|
|
15743
|
+
if (!pendingTurn?.streamStarted) return;
|
|
15744
|
+
const startedAtMs = Date.parse(pendingTurn.startedAt);
|
|
15745
|
+
if (!Number.isFinite(startedAtMs)) return;
|
|
15746
|
+
const lastContentResponseAtMs = pendingTurn.lastContentResponseAt ? Date.parse(pendingTurn.lastContentResponseAt) : pendingTurn.lastResponseAt ? Date.parse(pendingTurn.lastResponseAt) : null;
|
|
15747
|
+
const streamState = {
|
|
15748
|
+
startedAtMs,
|
|
15749
|
+
lastContentResponseAtMs: Number.isFinite(lastContentResponseAtMs) ? lastContentResponseAtMs : null
|
|
15750
|
+
};
|
|
15751
|
+
if (!shouldShowStreamLastContentResponseAge(streamState, nowMs, config2)) return;
|
|
15752
|
+
pushMirrorStreamingStatus(subscription, pendingTurn, {
|
|
15753
|
+
nowMs,
|
|
15754
|
+
lastResponseAgeMs: getStreamLastContentResponseAgeMs(streamState, nowMs),
|
|
15755
|
+
minIntervalMs: config2.heartbeatMs
|
|
15756
|
+
});
|
|
15757
|
+
}
|
|
15758
|
+
function updateMirrorStreaming(subscription, turnState) {
|
|
15759
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15760
|
+
if (!adapter) return;
|
|
15761
|
+
pushStreamFeedbackText(
|
|
15762
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15763
|
+
getMirrorStreamingText(subscription, turnState)
|
|
15764
|
+
);
|
|
15765
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15766
|
+
}
|
|
15767
|
+
function updateMirrorToolProgress(subscription, turnState) {
|
|
15768
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15769
|
+
if (!adapter) return;
|
|
15770
|
+
pushStreamFeedbackTools(
|
|
15771
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15772
|
+
Array.from(turnState.toolCalls.values())
|
|
15773
|
+
);
|
|
15774
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15775
|
+
}
|
|
15776
|
+
function updateMirrorTaskProgress(subscription, turnState) {
|
|
15777
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15778
|
+
if (!adapter) return;
|
|
15779
|
+
pushStreamFeedbackTasks(
|
|
15780
|
+
createStreamTarget(subscription, turnState, adapter),
|
|
15781
|
+
turnState.taskItems
|
|
15782
|
+
);
|
|
15783
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15784
|
+
}
|
|
15785
|
+
function updateMirrorStatusProgress(subscription, turnState) {
|
|
15786
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15787
|
+
if (!adapter) return;
|
|
15788
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
15789
|
+
}
|
|
15790
|
+
function stopMirrorStreaming2(subscription, status = "interrupted") {
|
|
15791
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
15792
|
+
const pendingTurn = subscription.pendingTurn;
|
|
15793
|
+
if (!adapter || !pendingTurn?.streamStarted) return;
|
|
15794
|
+
void finalizeStreamFeedback(
|
|
15795
|
+
createStreamTarget(subscription, pendingTurn, adapter),
|
|
15796
|
+
status,
|
|
15797
|
+
getMirrorStreamingText(subscription, pendingTurn)
|
|
15798
|
+
);
|
|
15799
|
+
}
|
|
15800
|
+
async function deliverMirrorTurn(subscription, turn) {
|
|
15801
|
+
const adapter = deps.getAdapter(subscription.channelType);
|
|
15802
|
+
if (!adapter || !adapter.isRunning()) return;
|
|
15803
|
+
const title = deps.getThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
15804
|
+
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
15805
|
+
const markdown = responseParseMode === "Markdown";
|
|
15806
|
+
const rawFinalResponse = assembleDesktopFinalResponse({ text: turn.text });
|
|
15807
|
+
const attachments = rawFinalResponse.attachments;
|
|
15808
|
+
const cleanTurnText = rawFinalResponse.text;
|
|
15809
|
+
const renderedTextBase = formatMirrorMessage(title, turn.userText, cleanTurnText, markdown);
|
|
15810
|
+
const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, cleanTurnText, markdown, true);
|
|
15811
|
+
const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
|
|
15812
|
+
const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
|
|
15813
|
+
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
15814
|
+
const streamText = renderFeedbackText(
|
|
15815
|
+
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
15816
|
+
responseParseMode
|
|
15817
|
+
);
|
|
15818
|
+
const address = {
|
|
15819
|
+
channelType: subscription.channelType,
|
|
15820
|
+
chatId: subscription.chatId
|
|
15821
|
+
};
|
|
15822
|
+
if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
|
|
15823
|
+
try {
|
|
15824
|
+
const finalized = await adapter.onStreamEnd(
|
|
15825
|
+
subscription.chatId,
|
|
15826
|
+
turn.status,
|
|
15827
|
+
streamText,
|
|
15828
|
+
turn.streamKey
|
|
15829
|
+
);
|
|
15830
|
+
if (finalized) {
|
|
15831
|
+
if (attachments.length > 0) {
|
|
15832
|
+
const attachmentResult = await deliverFinalResponse(
|
|
15833
|
+
{
|
|
15834
|
+
adapter,
|
|
15835
|
+
address,
|
|
15836
|
+
sessionId: subscription.sessionId,
|
|
15837
|
+
deliverResponse: deps.deliverResponse
|
|
15838
|
+
},
|
|
15839
|
+
assembleDesktopFinalResponse({ attachments }),
|
|
15840
|
+
{ skipText: true }
|
|
15841
|
+
);
|
|
15842
|
+
if (!attachmentResult.ok) {
|
|
15843
|
+
throw new Error(attachmentResult.error || "mirror attachment delivery failed");
|
|
15844
|
+
}
|
|
15845
|
+
}
|
|
15846
|
+
subscription.lastDeliveredAt = turn.timestamp || deps.nowIso();
|
|
15847
|
+
return;
|
|
15848
|
+
}
|
|
15849
|
+
} catch (error) {
|
|
15850
|
+
console.warn("[bridge-manager] Mirror stream finalize failed:", error instanceof Error ? error.message : error);
|
|
15851
|
+
}
|
|
15852
|
+
}
|
|
15853
|
+
const finalResponse = assembleDesktopFinalResponse({
|
|
15854
|
+
text: text2,
|
|
15855
|
+
attachments
|
|
15856
|
+
});
|
|
15857
|
+
if (!finalResponse.text && finalResponse.attachments.length === 0) return;
|
|
15858
|
+
const response = await deliverFinalResponse({
|
|
15859
|
+
adapter,
|
|
15860
|
+
address,
|
|
15861
|
+
sessionId: subscription.sessionId,
|
|
15862
|
+
deliverResponse: deps.deliverResponse,
|
|
15863
|
+
deliverText: async (messageText) => deliver(adapter, {
|
|
15864
|
+
address,
|
|
15865
|
+
text: messageText,
|
|
15866
|
+
parseMode: responseParseMode
|
|
15867
|
+
}, {
|
|
15868
|
+
sessionId: subscription.sessionId,
|
|
15869
|
+
dedupKey: `mirror:${subscription.bindingId}:${turn.signature}`
|
|
15870
|
+
})
|
|
15871
|
+
}, finalResponse);
|
|
15872
|
+
if (!response.ok) {
|
|
15873
|
+
throw new Error(response.error || "mirror delivery failed");
|
|
15874
|
+
}
|
|
15875
|
+
subscription.lastDeliveredAt = turn.timestamp || deps.nowIso();
|
|
15876
|
+
}
|
|
15877
|
+
async function deliverMirrorTurns2(subscription, turns) {
|
|
15878
|
+
let deliveredCount = 0;
|
|
15879
|
+
for (const turn of turns.slice(0, deps.eventBatchLimit)) {
|
|
15880
|
+
try {
|
|
15881
|
+
await deliverMirrorTurn(subscription, turn);
|
|
15882
|
+
deliveredCount += 1;
|
|
15883
|
+
} catch (error) {
|
|
15884
|
+
return { deliveredCount, error };
|
|
15885
|
+
}
|
|
15886
|
+
}
|
|
15887
|
+
return { deliveredCount };
|
|
15888
|
+
}
|
|
15889
|
+
return {
|
|
15890
|
+
hooks: {
|
|
15891
|
+
onStreamText: updateMirrorStreaming,
|
|
15892
|
+
onStatusProgress: updateMirrorStatusProgress,
|
|
15893
|
+
onTaskProgress: updateMirrorTaskProgress,
|
|
15894
|
+
onToolProgress: updateMirrorToolProgress
|
|
15895
|
+
},
|
|
15896
|
+
refreshMirrorStreamingStatus: refreshMirrorStreamingStatus2,
|
|
15897
|
+
stopMirrorStreaming: stopMirrorStreaming2,
|
|
15898
|
+
deliverMirrorTurns: deliverMirrorTurns2
|
|
15899
|
+
};
|
|
15900
|
+
}
|
|
15901
|
+
|
|
15213
15902
|
// src/lib/bridge/session-health-process.ts
|
|
15214
15903
|
import { execFile } from "node:child_process";
|
|
15215
15904
|
import { promisify } from "node:util";
|
|
@@ -15302,7 +15991,6 @@ async function probeCodexThreadProcess(threadId) {
|
|
|
15302
15991
|
var HEALTH_RECENT_PROGRESS_MS = 10 * 60 * 1e3;
|
|
15303
15992
|
var HEALTH_SLOW_OBSERVED_MS = 30 * 60 * 1e3;
|
|
15304
15993
|
var HEALTH_PROGRESS_PERSIST_THROTTLE_MS = 15 * 1e3;
|
|
15305
|
-
var HEALTH_PROCESS_PROBE_CACHE_MS = 30 * 1e3;
|
|
15306
15994
|
var HEALTH_STREAM_UI_STALL_MS = 60 * 1e3;
|
|
15307
15995
|
var RUNNING_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
15308
15996
|
"running_active",
|
|
@@ -15435,14 +16123,34 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15435
16123
|
const lastStreamUiError = trimOrNull(session.last_stream_ui_error);
|
|
15436
16124
|
const streamUiConsecutiveFailures = typeof session.stream_ui_consecutive_failures === "number" && Number.isFinite(session.stream_ui_consecutive_failures) && session.stream_ui_consecutive_failures > 0 ? session.stream_ui_consecutive_failures : 0;
|
|
15437
16125
|
const sdkSessionId = trimOrNull(session.sdk_session_id);
|
|
15438
|
-
const checkedAt = trimOrNull(session.last_health_check_at);
|
|
15439
16126
|
const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
|
|
15440
16127
|
const previousStatus = session.health_status || "idle";
|
|
16128
|
+
if (!isRunningRuntimeStatus(runtimeStatus) && isRunningHealthStatus(previousStatus)) {
|
|
16129
|
+
return {
|
|
16130
|
+
sessionId: session.id,
|
|
16131
|
+
checkedAt: null,
|
|
16132
|
+
runtimeStatus,
|
|
16133
|
+
healthStatus: "idle",
|
|
16134
|
+
healthReason: "\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002",
|
|
16135
|
+
lastProgressAt,
|
|
16136
|
+
lastProgressType,
|
|
16137
|
+
activeToolName,
|
|
16138
|
+
activeToolStartedAt,
|
|
16139
|
+
lastToolFinishedAt,
|
|
16140
|
+
lastStreamUiAttemptAt,
|
|
16141
|
+
lastStreamUiUpdateAt,
|
|
16142
|
+
streamUiFlushStartedAt,
|
|
16143
|
+
lastStreamUiErrorAt,
|
|
16144
|
+
lastStreamUiError,
|
|
16145
|
+
streamUiConsecutiveFailures,
|
|
16146
|
+
sdkSessionId
|
|
16147
|
+
};
|
|
16148
|
+
}
|
|
15441
16149
|
if (!lastProgressMs) {
|
|
15442
16150
|
const fallbackStatus = isRunningRuntimeStatus(runtimeStatus) ? "running_active" : previousStatus;
|
|
15443
16151
|
return {
|
|
15444
16152
|
sessionId: session.id,
|
|
15445
|
-
checkedAt,
|
|
16153
|
+
checkedAt: null,
|
|
15446
16154
|
runtimeStatus,
|
|
15447
16155
|
healthStatus: fallbackStatus,
|
|
15448
16156
|
healthReason: session.health_reason?.trim() || (fallbackStatus === "idle" ? "\u5F53\u524D\u6CA1\u6709\u8BB0\u5F55\u5230\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002" : "\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\uFF0C\u4F46\u8FD8\u6CA1\u6709\u8BB0\u5F55\u5230\u8BE6\u7EC6\u8FDB\u5C55\u3002"),
|
|
@@ -15470,17 +16178,17 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15470
16178
|
);
|
|
15471
16179
|
} else if (idleMs <= HEALTH_RECENT_PROGRESS_MS) {
|
|
15472
16180
|
healthStatus = activeToolName ? "waiting_tool" : "running_active";
|
|
15473
|
-
healthReason = activeToolName ? `\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${activeToolName}\u3002` : "\
|
|
16181
|
+
healthReason = activeToolName ? `\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${activeToolName}\u3002` : "\u8FD1\u671F\u4ECD\u6709\u65B0\u8FDB\u5C55\u3002";
|
|
15474
16182
|
} else if (idleMs <= HEALTH_SLOW_OBSERVED_MS) {
|
|
15475
16183
|
healthStatus = activeToolName ? "waiting_tool" : "slow_observed";
|
|
15476
|
-
healthReason = activeToolName ? `\u5DE5\u5177 ${activeToolName} \u5DF2\u8FD0\u884C\u8F83\u4E45\uFF0C\u4F46\u4ECD\u5728\u89C2\u5BDF\u7A97\u53E3\u5185\u3002` : "\
|
|
16184
|
+
healthReason = activeToolName ? `\u5DE5\u5177 ${activeToolName} \u5DF2\u8FD0\u884C\u8F83\u4E45\uFF0C\u4F46\u4ECD\u5728\u89C2\u5BDF\u7A97\u53E3\u5185\u3002` : "\u8FD1\u671F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8FDB\u5C55\uFF0C\u5148\u6807\u8BB0\u4E3A\u5F85\u89C2\u5BDF\u3002";
|
|
15477
16185
|
} else {
|
|
15478
16186
|
healthStatus = "suspected_stall";
|
|
15479
16187
|
healthReason = activeToolName ? `\u5DE5\u5177 ${activeToolName} \u5DF2\u957F\u65F6\u95F4\u6CA1\u6709\u65B0\u8FDB\u5C55\uFF0C\u7591\u4F3C\u5361\u4F4F\u3002` : "\u5DF2\u7ECF\u8D85\u8FC7 30 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8FDB\u5C55\uFF0C\u7591\u4F3C\u5361\u4F4F\u3002";
|
|
15480
16188
|
}
|
|
15481
16189
|
return {
|
|
15482
16190
|
sessionId: session.id,
|
|
15483
|
-
checkedAt,
|
|
16191
|
+
checkedAt: null,
|
|
15484
16192
|
runtimeStatus,
|
|
15485
16193
|
healthStatus,
|
|
15486
16194
|
healthReason,
|
|
@@ -15540,9 +16248,6 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
15540
16248
|
return diagnosis;
|
|
15541
16249
|
}
|
|
15542
16250
|
const lastProgressMs = parseIsoMs(diagnosis.lastProgressAt || void 0);
|
|
15543
|
-
if (!lastProgressMs || nowMs - lastProgressMs > HEALTH_RECENT_PROGRESS_MS) {
|
|
15544
|
-
return diagnosis;
|
|
15545
|
-
}
|
|
15546
16251
|
const lastStreamUiUpdateMs = parseIsoMs(diagnosis.lastStreamUiUpdateAt || void 0);
|
|
15547
16252
|
const lastStreamUiAttemptMs = parseIsoMs(diagnosis.lastStreamUiAttemptAt || void 0);
|
|
15548
16253
|
const streamUiFlushStartedMs = parseIsoMs(diagnosis.streamUiFlushStartedAt || void 0);
|
|
@@ -15561,7 +16266,21 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
15561
16266
|
healthReason: details.join(" ")
|
|
15562
16267
|
};
|
|
15563
16268
|
}
|
|
15564
|
-
if (
|
|
16269
|
+
if (lastStreamUiAttemptMs && (!lastStreamUiUpdateMs || lastStreamUiAttemptMs - lastStreamUiUpdateMs >= HEALTH_STREAM_UI_STALL_MS)) {
|
|
16270
|
+
const details = ["\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u6709\u5237\u65B0\u5C1D\u8BD5\u672A\u6210\u529F\u8DDF\u8FDB\uFF0C\u7591\u4F3C\u505C\u66F4\u3002"];
|
|
16271
|
+
if (diagnosis.streamUiConsecutiveFailures > 0) {
|
|
16272
|
+
details.push(`\u6700\u8FD1\u8FDE\u7EED\u5931\u8D25 ${diagnosis.streamUiConsecutiveFailures} \u6B21\u3002`);
|
|
16273
|
+
}
|
|
16274
|
+
if (lastStreamUiErrorText) {
|
|
16275
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
16276
|
+
}
|
|
16277
|
+
return {
|
|
16278
|
+
...diagnosis,
|
|
16279
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
16280
|
+
healthReason: details.join(" ")
|
|
16281
|
+
};
|
|
16282
|
+
}
|
|
16283
|
+
if (lastProgressMs && lastStreamUiUpdateMs && lastProgressMs - lastStreamUiUpdateMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
15565
16284
|
const details = ["\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u5DF2\u957F\u65F6\u95F4\u6CA1\u6709\u8DDF\u4E0A\u6700\u65B0\u6267\u884C\u8FDB\u5C55\uFF0C\u7591\u4F3C\u505C\u66F4\u3002"];
|
|
15566
16285
|
if (lastStreamUiErrorText) {
|
|
15567
16286
|
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
@@ -15572,7 +16291,7 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
15572
16291
|
healthReason: details.join(" ")
|
|
15573
16292
|
};
|
|
15574
16293
|
}
|
|
15575
|
-
if (!lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16294
|
+
if (lastProgressMs && !lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
15576
16295
|
const details = ["\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u53EA\u6709\u53D1\u9001\u5C1D\u8BD5\u3001\u6CA1\u6709\u6210\u529F\u5237\u65B0\u8BB0\u5F55\uFF0C\u7591\u4F3C\u505C\u66F4\u3002"];
|
|
15577
16296
|
if (lastStreamUiErrorText) {
|
|
15578
16297
|
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
@@ -15583,13 +16302,30 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
15583
16302
|
healthReason: details.join(" ")
|
|
15584
16303
|
};
|
|
15585
16304
|
}
|
|
16305
|
+
const lastStreamUiActivityMs = Math.max(
|
|
16306
|
+
lastStreamUiAttemptMs || 0,
|
|
16307
|
+
lastStreamUiUpdateMs || 0,
|
|
16308
|
+
streamUiFlushStartedMs || 0
|
|
16309
|
+
);
|
|
16310
|
+
if (lastStreamUiActivityMs > 0 && nowMs - lastStreamUiActivityMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16311
|
+
return {
|
|
16312
|
+
...diagnosis,
|
|
16313
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
16314
|
+
healthReason: "\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u72B6\u6001\u5237\u65B0\u5DF2\u505C\u6B62\uFF0C\u7591\u4F3C\u505C\u66F4\u3002"
|
|
16315
|
+
};
|
|
16316
|
+
}
|
|
15586
16317
|
return diagnosis;
|
|
15587
16318
|
}
|
|
15588
16319
|
|
|
15589
16320
|
// src/lib/bridge/session-health-runtime.ts
|
|
15590
16321
|
function createSessionHealthRuntime(deps) {
|
|
15591
16322
|
const lastProgressPersistAt = /* @__PURE__ */ new Map();
|
|
15592
|
-
|
|
16323
|
+
function isTerminalHealthStatus(status) {
|
|
16324
|
+
return status === "completed" || status === "failed" || status === "aborted";
|
|
16325
|
+
}
|
|
16326
|
+
function shouldIgnoreNonStartProgress(session) {
|
|
16327
|
+
return isTerminalHealthStatus(session.health_status);
|
|
16328
|
+
}
|
|
15593
16329
|
function summarizePlanUpdate(tasks) {
|
|
15594
16330
|
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
15595
16331
|
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
@@ -15657,6 +16393,8 @@ function createSessionHealthRuntime(deps) {
|
|
|
15657
16393
|
}
|
|
15658
16394
|
function recordInteractiveProgress(sessionId, type, detail) {
|
|
15659
16395
|
const nowIso4 = deps.nowIso();
|
|
16396
|
+
const session = deps.getStore().getSession(sessionId);
|
|
16397
|
+
if (!session || shouldIgnoreNonStartProgress(session)) return;
|
|
15660
16398
|
maybePersistProgress(sessionId, {
|
|
15661
16399
|
health_status: type === "permission_wait" ? "waiting_tool" : "running_active",
|
|
15662
16400
|
health_reason: buildProgressReason(type, detail),
|
|
@@ -15669,6 +16407,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
15669
16407
|
const store = deps.getStore();
|
|
15670
16408
|
const session = store.getSession(sessionId);
|
|
15671
16409
|
if (!session) return;
|
|
16410
|
+
if (shouldIgnoreNonStartProgress(session)) return;
|
|
15672
16411
|
const activeTools = new Map(
|
|
15673
16412
|
parseActiveToolsJson(session.active_tools_json).map((tool) => [tool.id, tool])
|
|
15674
16413
|
);
|
|
@@ -15725,11 +16464,9 @@ function createSessionHealthRuntime(deps) {
|
|
|
15725
16464
|
last_stream_ui_update_at: void 0,
|
|
15726
16465
|
last_stream_ui_error_at: void 0,
|
|
15727
16466
|
last_stream_ui_error: void 0,
|
|
15728
|
-
stream_ui_consecutive_failures: void 0
|
|
15729
|
-
last_health_check_at: nowIso4
|
|
16467
|
+
stream_ui_consecutive_failures: void 0
|
|
15730
16468
|
}, { force: true });
|
|
15731
16469
|
lastProgressPersistAt.set(sessionId, Date.now());
|
|
15732
|
-
processProbeCache.delete(sessionId);
|
|
15733
16470
|
}
|
|
15734
16471
|
function toIso(value) {
|
|
15735
16472
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
@@ -15824,17 +16561,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
15824
16561
|
async function loadProcessProbe(session) {
|
|
15825
16562
|
const threadId = session.sdk_session_id?.trim();
|
|
15826
16563
|
if (!threadId || !deps.probeThreadProcess) return null;
|
|
15827
|
-
|
|
15828
|
-
const nowMs = Date.now();
|
|
15829
|
-
if (cached && nowMs - cached.checkedAtMs < HEALTH_PROCESS_PROBE_CACHE_MS) {
|
|
15830
|
-
return cached.result;
|
|
15831
|
-
}
|
|
15832
|
-
const result = await deps.probeThreadProcess(threadId);
|
|
15833
|
-
processProbeCache.set(session.id, {
|
|
15834
|
-
checkedAtMs: nowMs,
|
|
15835
|
-
result
|
|
15836
|
-
});
|
|
15837
|
-
return result;
|
|
16564
|
+
return deps.probeThreadProcess(threadId);
|
|
15838
16565
|
}
|
|
15839
16566
|
async function diagnoseSessionHealth(sessionId) {
|
|
15840
16567
|
const store = deps.getStore();
|
|
@@ -15842,21 +16569,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
15842
16569
|
if (!session) return null;
|
|
15843
16570
|
const base = computeBaseDiagnosis(session, Date.now());
|
|
15844
16571
|
const processProbe = await loadProcessProbe(session);
|
|
15845
|
-
|
|
15846
|
-
const diagnosis = applyStreamUiDiagnosis(
|
|
16572
|
+
return applyStreamUiDiagnosis(
|
|
15847
16573
|
applyProcessProbeDiagnosis(base, processProbe),
|
|
15848
16574
|
Date.now()
|
|
15849
16575
|
);
|
|
15850
|
-
const checkedDiagnosis = {
|
|
15851
|
-
...diagnosis,
|
|
15852
|
-
checkedAt
|
|
15853
|
-
};
|
|
15854
|
-
updateSessionHealth(sessionId, {
|
|
15855
|
-
health_status: checkedDiagnosis.healthStatus,
|
|
15856
|
-
health_reason: checkedDiagnosis.healthReason,
|
|
15857
|
-
last_health_check_at: checkedAt
|
|
15858
|
-
}, { touch: false });
|
|
15859
|
-
return checkedDiagnosis;
|
|
15860
16576
|
}
|
|
15861
16577
|
async function diagnoseAllActiveSessions() {
|
|
15862
16578
|
const store = deps.getStore();
|
|
@@ -15877,6 +16593,100 @@ function createSessionHealthRuntime(deps) {
|
|
|
15877
16593
|
};
|
|
15878
16594
|
}
|
|
15879
16595
|
|
|
16596
|
+
// src/lib/bridge/turns/desktop-terminal-router.ts
|
|
16597
|
+
function isTerminalRecord(record) {
|
|
16598
|
+
return record.type === "task_complete" || record.type === "task_aborted";
|
|
16599
|
+
}
|
|
16600
|
+
function toTerminalRecord(sessionId, desktopThreadId, record) {
|
|
16601
|
+
return {
|
|
16602
|
+
sessionId,
|
|
16603
|
+
desktopThreadId,
|
|
16604
|
+
turnId: record.turnId,
|
|
16605
|
+
text: record.content,
|
|
16606
|
+
outcome: record.type === "task_aborted" ? "aborted" : "completed",
|
|
16607
|
+
timestamp: record.timestamp
|
|
16608
|
+
};
|
|
16609
|
+
}
|
|
16610
|
+
async function routeDesktopRecords(sessionId, desktopThreadId, records, coordinator) {
|
|
16611
|
+
let terminalRecord = null;
|
|
16612
|
+
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
16613
|
+
if (!isTerminalRecord(records[index])) continue;
|
|
16614
|
+
terminalRecord = records[index];
|
|
16615
|
+
break;
|
|
16616
|
+
}
|
|
16617
|
+
if (!terminalRecord) {
|
|
16618
|
+
return {
|
|
16619
|
+
claimed: [],
|
|
16620
|
+
unclaimed: records,
|
|
16621
|
+
terminalClaimed: false
|
|
16622
|
+
};
|
|
16623
|
+
}
|
|
16624
|
+
const claim = await coordinator.claimDesktopTerminal(
|
|
16625
|
+
toTerminalRecord(sessionId, desktopThreadId, terminalRecord)
|
|
16626
|
+
);
|
|
16627
|
+
if (!claim.claimed) {
|
|
16628
|
+
return {
|
|
16629
|
+
claimed: [],
|
|
16630
|
+
unclaimed: records,
|
|
16631
|
+
terminalClaimed: false
|
|
16632
|
+
};
|
|
16633
|
+
}
|
|
16634
|
+
const claimedTurnId = terminalRecord.turnId;
|
|
16635
|
+
const claimed = claimedTurnId ? records.filter((record) => record.turnId === claimedTurnId) : [terminalRecord];
|
|
16636
|
+
const claimedSet = new Set(claimed.map((record) => record.signature));
|
|
16637
|
+
return {
|
|
16638
|
+
claimed,
|
|
16639
|
+
unclaimed: records.filter((record) => !claimedSet.has(record.signature)),
|
|
16640
|
+
terminalClaimed: true
|
|
16641
|
+
};
|
|
16642
|
+
}
|
|
16643
|
+
|
|
16644
|
+
// src/lib/bridge/turns/turn-coordinator.ts
|
|
16645
|
+
function createTurnCoordinator(deps = {}) {
|
|
16646
|
+
const activeTurnsBySession = /* @__PURE__ */ new Map();
|
|
16647
|
+
function registerInteractiveTurn(turn) {
|
|
16648
|
+
activeTurnsBySession.set(turn.sessionId, turn);
|
|
16649
|
+
}
|
|
16650
|
+
function getActiveTurn(sessionId) {
|
|
16651
|
+
return activeTurnsBySession.get(sessionId);
|
|
16652
|
+
}
|
|
16653
|
+
async function claimDesktopTerminal(terminal) {
|
|
16654
|
+
const turn = activeTurnsBySession.get(terminal.sessionId);
|
|
16655
|
+
if (!turn || turn.kind !== "im_desktop_reuse") {
|
|
16656
|
+
return { claimed: false };
|
|
16657
|
+
}
|
|
16658
|
+
if (turn.desktopThreadId && turn.desktopThreadId !== terminal.desktopThreadId) {
|
|
16659
|
+
return { claimed: false };
|
|
16660
|
+
}
|
|
16661
|
+
const finalized = await deps.finalizeTerminalTurn?.(turn, terminal);
|
|
16662
|
+
return finalized ? { claimed: true, turn } : { claimed: false, turn };
|
|
16663
|
+
}
|
|
16664
|
+
function releaseTurn(turnId) {
|
|
16665
|
+
for (const [sessionId, turn] of activeTurnsBySession) {
|
|
16666
|
+
if (turn.id !== turnId) continue;
|
|
16667
|
+
activeTurnsBySession.delete(sessionId);
|
|
16668
|
+
return;
|
|
16669
|
+
}
|
|
16670
|
+
}
|
|
16671
|
+
function releaseSessionTurn(sessionId, turnId) {
|
|
16672
|
+
const turn = activeTurnsBySession.get(sessionId);
|
|
16673
|
+
if (!turn) return;
|
|
16674
|
+
if (turnId && turn.id !== turnId) return;
|
|
16675
|
+
activeTurnsBySession.delete(sessionId);
|
|
16676
|
+
}
|
|
16677
|
+
function clear() {
|
|
16678
|
+
activeTurnsBySession.clear();
|
|
16679
|
+
}
|
|
16680
|
+
return {
|
|
16681
|
+
registerInteractiveTurn,
|
|
16682
|
+
getActiveTurn,
|
|
16683
|
+
claimDesktopTerminal,
|
|
16684
|
+
releaseTurn,
|
|
16685
|
+
releaseSessionTurn,
|
|
16686
|
+
clear
|
|
16687
|
+
};
|
|
16688
|
+
}
|
|
16689
|
+
|
|
15880
16690
|
// src/lib/bridge/bridge-manager.ts
|
|
15881
16691
|
var GLOBAL_KEY = "__bridge_manager__";
|
|
15882
16692
|
var DANGLING_MIRROR_THREAD_RETRY_LIMIT = 3;
|
|
@@ -15887,9 +16697,10 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
|
|
|
15887
16697
|
var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
15888
16698
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
15889
16699
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
16700
|
+
var DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS = 3e4;
|
|
15890
16701
|
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
15891
16702
|
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
15892
|
-
var MIRROR_TURN_BUFFER_TIMEOUT_MS =
|
|
16703
|
+
var MIRROR_TURN_BUFFER_TIMEOUT_MS = 10 * 6e4;
|
|
15893
16704
|
function describeUnknownError(error) {
|
|
15894
16705
|
if (error instanceof Error) {
|
|
15895
16706
|
return error.stack || `${error.name}: ${error.message}`;
|
|
@@ -15974,6 +16785,23 @@ var INTERACTIVE_RUNTIME = createInteractiveRuntime(getState, {
|
|
|
15974
16785
|
getStore: () => getBridgeContext().store,
|
|
15975
16786
|
nowIso: nowIso3
|
|
15976
16787
|
});
|
|
16788
|
+
function formatDesktopTerminalDetail(terminal) {
|
|
16789
|
+
if (terminal.outcome === "aborted") {
|
|
16790
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
16791
|
+
}
|
|
16792
|
+
if (terminal.outcome === "failed") {
|
|
16793
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5F53\u524D\u4EFB\u52A1\u6267\u884C\u5931\u8D25\u3002";
|
|
16794
|
+
}
|
|
16795
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u5B8C\u6210\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
16796
|
+
}
|
|
16797
|
+
var TURN_COORDINATOR = createTurnCoordinator({
|
|
16798
|
+
finalizeTerminalTurn: (turn, terminal) => INTERACTIVE_RUNTIME.finalizeTerminalActiveTask(
|
|
16799
|
+
turn.sessionId,
|
|
16800
|
+
terminal.outcome,
|
|
16801
|
+
formatDesktopTerminalDetail(terminal),
|
|
16802
|
+
terminal.text
|
|
16803
|
+
)
|
|
16804
|
+
});
|
|
15977
16805
|
var SESSION_HEALTH_RUNTIME = createSessionHealthRuntime({
|
|
15978
16806
|
getStore: () => getBridgeContext().store,
|
|
15979
16807
|
nowIso: nowIso3,
|
|
@@ -16010,9 +16838,6 @@ function settleMirrorSuppression2(sessionId, suppressionId, durationMs = MIRROR_
|
|
|
16010
16838
|
durationMs
|
|
16011
16839
|
);
|
|
16012
16840
|
}
|
|
16013
|
-
function isMirrorSuppressed2(sessionId) {
|
|
16014
|
-
return isMirrorSuppressed(getMirrorSuppressionStore(), sessionId);
|
|
16015
|
-
}
|
|
16016
16841
|
function filterSuppressedMirrorRecords2(sessionId, records) {
|
|
16017
16842
|
return filterSuppressedMirrorRecords(
|
|
16018
16843
|
getMirrorSuppressionStore(),
|
|
@@ -16046,28 +16871,6 @@ function syncMirrorSessionStateSafe(sessionId, context) {
|
|
|
16046
16871
|
);
|
|
16047
16872
|
}
|
|
16048
16873
|
}
|
|
16049
|
-
function getMirrorStreamingAdapter(subscription) {
|
|
16050
|
-
const state = getState();
|
|
16051
|
-
const adapter = state.adapters.get(subscription.channelType);
|
|
16052
|
-
if (!adapter || !adapter.isRunning()) return null;
|
|
16053
|
-
if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
|
|
16054
|
-
if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
|
|
16055
|
-
return null;
|
|
16056
|
-
}
|
|
16057
|
-
return adapter;
|
|
16058
|
-
}
|
|
16059
|
-
function getMirrorStreamingText(subscription, turnState) {
|
|
16060
|
-
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
16061
|
-
const markdown = getFeedbackParseMode(subscription.channelType) === "Markdown";
|
|
16062
|
-
const rendered = formatMirrorMessage(
|
|
16063
|
-
title,
|
|
16064
|
-
turnState.userText,
|
|
16065
|
-
turnState.streamedText,
|
|
16066
|
-
markdown,
|
|
16067
|
-
true
|
|
16068
|
-
);
|
|
16069
|
-
return rendered || buildMirrorTitle(title, markdown);
|
|
16070
|
-
}
|
|
16071
16874
|
function getMirrorStructuredStreamStatusConfig() {
|
|
16072
16875
|
const { store } = getBridgeContext();
|
|
16073
16876
|
const idleStartSeconds = parseInt(store.getSetting("bridge_stream_status_idle_start_seconds") || "", 10);
|
|
@@ -16083,216 +16886,43 @@ function getMirrorStructuredStreamStatusConfig() {
|
|
|
16083
16886
|
)
|
|
16084
16887
|
};
|
|
16085
16888
|
}
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
}
|
|
16094
|
-
turnState.streamStarted = true;
|
|
16095
|
-
} catch {
|
|
16096
|
-
}
|
|
16097
|
-
}
|
|
16098
|
-
function createMirrorStreamFeedbackTarget(subscription, turnState, adapter) {
|
|
16099
|
-
return {
|
|
16100
|
-
adapter,
|
|
16101
|
-
channelType: subscription.channelType,
|
|
16102
|
-
chatId: subscription.chatId,
|
|
16103
|
-
streamKey: turnState.streamKey,
|
|
16104
|
-
ensureStarted: () => {
|
|
16105
|
-
startMirrorStreaming(subscription, turnState);
|
|
16106
|
-
}
|
|
16107
|
-
};
|
|
16108
|
-
}
|
|
16109
|
-
function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
16110
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16111
|
-
if (!adapter || typeof adapter.onStreamStatus !== "function") return;
|
|
16112
|
-
if (!(adapter.supportsStructuredStreamingUi?.(subscription.chatId) ?? true)) return;
|
|
16113
|
-
const startedAtMs = Date.parse(turnState.startedAt);
|
|
16114
|
-
if (!Number.isFinite(startedAtMs)) return;
|
|
16115
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
16116
|
-
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 0);
|
|
16117
|
-
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
16118
|
-
return;
|
|
16119
|
-
}
|
|
16120
|
-
const statusText = formatInteractiveRuntimeStatus(
|
|
16121
|
-
Math.max(0, nowMs - startedAtMs),
|
|
16122
|
-
options.lastResponseAgeMs,
|
|
16123
|
-
turnState.statusNote
|
|
16124
|
-
);
|
|
16125
|
-
if (turnState.lastStatusText === statusText) return;
|
|
16126
|
-
pushStreamFeedbackStatus(
|
|
16127
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16128
|
-
statusText
|
|
16129
|
-
);
|
|
16130
|
-
turnState.lastStatusText = statusText;
|
|
16131
|
-
turnState.lastStatusAt = nowMs;
|
|
16132
|
-
}
|
|
16889
|
+
var MIRROR_FEEDBACK = createMirrorFeedbackController({
|
|
16890
|
+
getAdapter: (channelType) => getState().adapters.get(channelType) || null,
|
|
16891
|
+
getThreadTitle: (threadId) => getDesktopThreadTitle(threadId),
|
|
16892
|
+
nowIso: nowIso3,
|
|
16893
|
+
eventBatchLimit: MIRROR_EVENT_BATCH_LIMIT,
|
|
16894
|
+
deliverResponse
|
|
16895
|
+
});
|
|
16133
16896
|
function refreshMirrorStreamingStatus(subscription, nowMs = Date.now(), config2 = getMirrorStructuredStreamStatusConfig()) {
|
|
16134
|
-
|
|
16135
|
-
if (!pendingTurn?.streamStarted) return;
|
|
16136
|
-
const startedAtMs = Date.parse(pendingTurn.startedAt);
|
|
16137
|
-
if (!Number.isFinite(startedAtMs)) return;
|
|
16138
|
-
const elapsedMs = nowMs - startedAtMs;
|
|
16139
|
-
if (elapsedMs < config2.idleStartMs) return;
|
|
16140
|
-
const lastResponseAtMs = pendingTurn.lastResponseAt ? Date.parse(pendingTurn.lastResponseAt) : NaN;
|
|
16141
|
-
const lastResponseAgeMs = Number.isFinite(lastResponseAtMs) ? nowMs - lastResponseAtMs : null;
|
|
16142
|
-
if (lastResponseAgeMs != null && lastResponseAgeMs < config2.heartbeatMs) return;
|
|
16143
|
-
pushMirrorStreamingStatus(subscription, pendingTurn, {
|
|
16144
|
-
nowMs,
|
|
16145
|
-
lastResponseAgeMs,
|
|
16146
|
-
minIntervalMs: config2.heartbeatMs
|
|
16147
|
-
});
|
|
16897
|
+
MIRROR_FEEDBACK.refreshMirrorStreamingStatus(subscription, nowMs, config2);
|
|
16148
16898
|
}
|
|
16149
16899
|
function refreshActiveMirrorStreamingStatuses(nowMs = Date.now()) {
|
|
16150
16900
|
for (const subscription of getState().mirrorSubscriptions.values()) {
|
|
16151
16901
|
refreshMirrorStreamingStatus(subscription, nowMs);
|
|
16152
16902
|
}
|
|
16153
16903
|
}
|
|
16154
|
-
function updateMirrorStreaming(subscription, turnState) {
|
|
16155
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16156
|
-
if (!adapter) return;
|
|
16157
|
-
pushStreamFeedbackText(
|
|
16158
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16159
|
-
getMirrorStreamingText(subscription, turnState)
|
|
16160
|
-
);
|
|
16161
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16162
|
-
}
|
|
16163
|
-
function updateMirrorToolProgress(subscription, turnState) {
|
|
16164
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16165
|
-
if (!adapter) return;
|
|
16166
|
-
pushStreamFeedbackTools(
|
|
16167
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16168
|
-
Array.from(turnState.toolCalls.values())
|
|
16169
|
-
);
|
|
16170
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16171
|
-
}
|
|
16172
|
-
function updateMirrorTaskProgress(subscription, turnState) {
|
|
16173
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16174
|
-
if (!adapter) return;
|
|
16175
|
-
pushStreamFeedbackTasks(
|
|
16176
|
-
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
16177
|
-
turnState.taskItems
|
|
16178
|
-
);
|
|
16179
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16180
|
-
}
|
|
16181
|
-
function updateMirrorStatusProgress(subscription, turnState) {
|
|
16182
|
-
const adapter = getMirrorStreamingAdapter(subscription);
|
|
16183
|
-
if (!adapter) return;
|
|
16184
|
-
pushMirrorStreamingStatus(subscription, turnState);
|
|
16185
|
-
}
|
|
16186
16904
|
function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
16187
|
-
|
|
16188
|
-
const pendingTurn = subscription.pendingTurn;
|
|
16189
|
-
if (!adapter || !pendingTurn?.streamStarted) return;
|
|
16190
|
-
void finalizeStreamFeedback(
|
|
16191
|
-
createMirrorStreamFeedbackTarget(subscription, pendingTurn, adapter),
|
|
16192
|
-
status,
|
|
16193
|
-
getMirrorStreamingText(subscription, pendingTurn)
|
|
16194
|
-
);
|
|
16195
|
-
}
|
|
16196
|
-
async function deliverMirrorTurn(subscription, turn) {
|
|
16197
|
-
const state = getState();
|
|
16198
|
-
const adapter = state.adapters.get(subscription.channelType);
|
|
16199
|
-
if (!adapter || !adapter.isRunning()) return;
|
|
16200
|
-
const title = getDesktopThreadTitle(subscription.threadId)?.trim() || "\u684C\u9762\u7EBF\u7A0B";
|
|
16201
|
-
const responseParseMode = getFeedbackParseMode(subscription.channelType);
|
|
16202
|
-
const markdown = responseParseMode === "Markdown";
|
|
16203
|
-
const renderedTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown);
|
|
16204
|
-
const renderedStreamTextBase = formatMirrorMessage(title, turn.userText, turn.text, markdown, true);
|
|
16205
|
-
const renderedText = turn.timedOut ? appendMirrorTimeoutNotice(renderedTextBase || buildMirrorTitle(title, markdown), markdown) : renderedTextBase;
|
|
16206
|
-
const renderedStreamText = turn.timedOut ? appendMirrorTimeoutNotice(renderedStreamTextBase || buildMirrorTitle(title, markdown), markdown) : renderedStreamTextBase;
|
|
16207
|
-
const text2 = renderedText ? renderFeedbackText(renderedText, responseParseMode) : "";
|
|
16208
|
-
const streamText = renderFeedbackText(
|
|
16209
|
-
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
16210
|
-
responseParseMode
|
|
16211
|
-
);
|
|
16212
|
-
if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
|
|
16213
|
-
try {
|
|
16214
|
-
const finalized = await adapter.onStreamEnd(
|
|
16215
|
-
subscription.chatId,
|
|
16216
|
-
turn.status,
|
|
16217
|
-
streamText,
|
|
16218
|
-
turn.streamKey
|
|
16219
|
-
);
|
|
16220
|
-
if (finalized) {
|
|
16221
|
-
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
16222
|
-
return;
|
|
16223
|
-
}
|
|
16224
|
-
} catch (error) {
|
|
16225
|
-
console.warn("[bridge-manager] Mirror stream finalize failed:", error instanceof Error ? error.message : error);
|
|
16226
|
-
}
|
|
16227
|
-
}
|
|
16228
|
-
if (!text2) return;
|
|
16229
|
-
const response = await deliver(adapter, {
|
|
16230
|
-
address: {
|
|
16231
|
-
channelType: subscription.channelType,
|
|
16232
|
-
chatId: subscription.chatId
|
|
16233
|
-
},
|
|
16234
|
-
text: text2,
|
|
16235
|
-
parseMode: responseParseMode
|
|
16236
|
-
}, {
|
|
16237
|
-
sessionId: subscription.sessionId,
|
|
16238
|
-
dedupKey: `mirror:${subscription.bindingId}:${turn.signature}`
|
|
16239
|
-
});
|
|
16240
|
-
if (!response.ok) {
|
|
16241
|
-
throw new Error(response.error || "mirror delivery failed");
|
|
16242
|
-
}
|
|
16243
|
-
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
16905
|
+
MIRROR_FEEDBACK.stopMirrorStreaming(subscription, status);
|
|
16244
16906
|
}
|
|
16245
16907
|
async function deliverMirrorTurns(subscription, turns) {
|
|
16246
|
-
|
|
16247
|
-
for (const turn of turns.slice(0, MIRROR_EVENT_BATCH_LIMIT)) {
|
|
16248
|
-
try {
|
|
16249
|
-
await deliverMirrorTurn(subscription, turn);
|
|
16250
|
-
deliveredCount += 1;
|
|
16251
|
-
} catch (error) {
|
|
16252
|
-
return { deliveredCount, error };
|
|
16253
|
-
}
|
|
16254
|
-
}
|
|
16255
|
-
return { deliveredCount };
|
|
16908
|
+
return MIRROR_FEEDBACK.deliverMirrorTurns(subscription, turns);
|
|
16256
16909
|
}
|
|
16257
|
-
var MIRROR_TURN_HOOKS =
|
|
16258
|
-
onStreamText: updateMirrorStreaming,
|
|
16259
|
-
onStatusProgress: updateMirrorStatusProgress,
|
|
16260
|
-
onTaskProgress: updateMirrorTaskProgress,
|
|
16261
|
-
onToolProgress: updateMirrorToolProgress
|
|
16262
|
-
};
|
|
16910
|
+
var MIRROR_TURN_HOOKS = MIRROR_FEEDBACK.hooks;
|
|
16263
16911
|
function consumeMirrorRecords2(subscription, records) {
|
|
16264
16912
|
return consumeMirrorRecords(subscription, records, MIRROR_TURN_HOOKS);
|
|
16265
16913
|
}
|
|
16266
16914
|
function flushTimedOutMirrorTurn2(subscription, nowMs = Date.now()) {
|
|
16915
|
+
if (subscription.pendingTurn?.streamStarted) {
|
|
16916
|
+
return null;
|
|
16917
|
+
}
|
|
16267
16918
|
return flushTimedOutMirrorTurn(subscription, MIRROR_TURN_BUFFER_TIMEOUT_MS, nowMs);
|
|
16268
16919
|
}
|
|
16269
16920
|
function hasPendingMirrorWork2(subscription) {
|
|
16270
16921
|
return hasPendingMirrorWork(subscription);
|
|
16271
16922
|
}
|
|
16272
16923
|
function consumeBufferedMirrorTurns2(subscription, nowMs = Date.now()) {
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
function finalizeInteractiveTaskFromMirrorRecords(sessionId, records) {
|
|
16276
|
-
let terminalRecord = null;
|
|
16277
|
-
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
16278
|
-
const record = records[index];
|
|
16279
|
-
if (record.type === "task_complete" || record.type === "task_aborted") {
|
|
16280
|
-
terminalRecord = record;
|
|
16281
|
-
break;
|
|
16282
|
-
}
|
|
16283
|
-
}
|
|
16284
|
-
if (!terminalRecord) return Promise.resolve(false);
|
|
16285
|
-
const outcome = terminalRecord.type === "task_complete" ? "completed" : "aborted";
|
|
16286
|
-
const detail = terminalRecord.type === "task_complete" ? "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u5B8C\u6210\u5F53\u524D\u4EFB\u52A1\u3002" : "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
16287
|
-
return INTERACTIVE_RUNTIME.finalizeTerminalActiveTask(
|
|
16288
|
-
sessionId,
|
|
16289
|
-
outcome,
|
|
16290
|
-
detail,
|
|
16291
|
-
terminalRecord.content
|
|
16292
|
-
).catch((error) => {
|
|
16293
|
-
console.error("[bridge-manager] Failed to finalize terminal interactive task:", describeUnknownError(error));
|
|
16294
|
-
return false;
|
|
16295
|
-
});
|
|
16924
|
+
const timeoutMs = subscription.pendingTurn?.streamStarted ? Number.POSITIVE_INFINITY : MIRROR_TURN_BUFFER_TIMEOUT_MS;
|
|
16925
|
+
return consumeBufferedMirrorTurns(subscription, timeoutMs, nowMs, MIRROR_TURN_HOOKS);
|
|
16296
16926
|
}
|
|
16297
16927
|
var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
16298
16928
|
watchDebounceMs: MIRROR_WATCH_DEBOUNCE_MS,
|
|
@@ -16304,12 +16934,16 @@ var MIRROR_RUNTIME = createMirrorRuntime(getState, {
|
|
|
16304
16934
|
describeUnknownError,
|
|
16305
16935
|
getDesktopSessionByThreadIdSafe,
|
|
16306
16936
|
syncMirrorSessionStateSafe,
|
|
16307
|
-
isMirrorSuppressed: isMirrorSuppressed2,
|
|
16308
16937
|
filterSuppressedMirrorRecords: filterSuppressedMirrorRecords2,
|
|
16309
16938
|
observeSessionHealthRecords: (sessionId, threadId, records) => {
|
|
16310
16939
|
SESSION_HEALTH_RUNTIME.observeDesktopMirrorRecords(sessionId, threadId, records);
|
|
16311
|
-
void finalizeInteractiveTaskFromMirrorRecords(sessionId, records);
|
|
16312
16940
|
},
|
|
16941
|
+
routeDesktopRecords: (sessionId, threadId, records) => routeDesktopRecords(
|
|
16942
|
+
sessionId,
|
|
16943
|
+
threadId,
|
|
16944
|
+
records,
|
|
16945
|
+
TURN_COORDINATOR
|
|
16946
|
+
),
|
|
16313
16947
|
consumeMirrorRecords: consumeMirrorRecords2,
|
|
16314
16948
|
flushTimedOutMirrorTurn: (subscription) => flushTimedOutMirrorTurn2(subscription),
|
|
16315
16949
|
hasPendingMirrorWork: hasPendingMirrorWork2,
|
|
@@ -16557,6 +17191,7 @@ async function handleMessage(adapter, msg) {
|
|
|
16557
17191
|
try {
|
|
16558
17192
|
await runInteractiveMessage(adapter, msg, text2, hasAttachments ? msg.attachments : void 0, {
|
|
16559
17193
|
registerInteractiveTask: (task) => INTERACTIVE_RUNTIME.registerInteractiveTask(task),
|
|
17194
|
+
registerBridgeTurn: (turn) => TURN_COORDINATOR.registerInteractiveTurn(turn),
|
|
16560
17195
|
resetMirrorSessionForInteractiveRun,
|
|
16561
17196
|
isCurrentInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.isCurrentInteractiveTask(sessionId, taskId),
|
|
16562
17197
|
touchInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.touchInteractiveTask(sessionId, taskId),
|
|
@@ -16573,8 +17208,10 @@ async function handleMessage(adapter, msg) {
|
|
|
16573
17208
|
abortMirrorSuppression: abortMirrorSuppression2,
|
|
16574
17209
|
settleMirrorSuppression: settleMirrorSuppression2,
|
|
16575
17210
|
releaseInteractiveTask: (sessionId, taskId) => INTERACTIVE_RUNTIME.releaseInteractiveTask(sessionId, taskId),
|
|
17211
|
+
releaseBridgeTurn: (sessionId, taskId) => TURN_COORDINATOR.releaseSessionTurn(sessionId, taskId),
|
|
16576
17212
|
deliverResponse,
|
|
16577
|
-
persistSdkSessionUpdate
|
|
17213
|
+
persistSdkSessionUpdate,
|
|
17214
|
+
desktopTerminalFinalizationTimeoutMs: DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS
|
|
16578
17215
|
});
|
|
16579
17216
|
} finally {
|
|
16580
17217
|
ack();
|
|
@@ -16583,6 +17220,8 @@ async function handleMessage(adapter, msg) {
|
|
|
16583
17220
|
async function handleCommand(adapter, msg, text2) {
|
|
16584
17221
|
await handleBridgeCommand(adapter, msg, text2, {
|
|
16585
17222
|
getActiveTask: (sessionId) => INTERACTIVE_RUNTIME.getActiveTask(sessionId),
|
|
17223
|
+
forceStopSession: (sessionId, detail) => INTERACTIVE_RUNTIME.forceStopSession(sessionId, detail),
|
|
17224
|
+
recordInteractiveHealthEnd: (sessionId, outcome, detail) => SESSION_HEALTH_RUNTIME.recordInteractiveEnd(sessionId, outcome, detail),
|
|
16586
17225
|
diagnoseSessionHealth: (sessionId) => SESSION_HEALTH_RUNTIME.diagnoseSessionHealth(sessionId),
|
|
16587
17226
|
diagnoseAllActiveSessions: () => SESSION_HEALTH_RUNTIME.diagnoseAllActiveSessions()
|
|
16588
17227
|
});
|
|
@@ -16886,7 +17525,7 @@ var JsonFileStore = class {
|
|
|
16886
17525
|
findSessionBySdkSessionId(sdkSessionId) {
|
|
16887
17526
|
this.reloadSessions();
|
|
16888
17527
|
for (const session of this.sessions.values()) {
|
|
16889
|
-
if (session.sdk_session_id === sdkSessionId) {
|
|
17528
|
+
if (session.sdk_session_id === sdkSessionId || session.codex_thread_id === sdkSessionId || session.desktop_thread_id === sdkSessionId) {
|
|
16890
17529
|
return session;
|
|
16891
17530
|
}
|
|
16892
17531
|
}
|
|
@@ -17020,6 +17659,15 @@ var JsonFileStore = class {
|
|
|
17020
17659
|
const s = this.sessions.get(sessionId);
|
|
17021
17660
|
if (s) {
|
|
17022
17661
|
s.sdk_session_id = sdkSessionId;
|
|
17662
|
+
if (sdkSessionId) {
|
|
17663
|
+
s.codex_thread_id = sdkSessionId;
|
|
17664
|
+
s.thread_origin = s.thread_origin || "bridge";
|
|
17665
|
+
} else {
|
|
17666
|
+
delete s.codex_thread_id;
|
|
17667
|
+
if (s.thread_origin !== "desktop") {
|
|
17668
|
+
delete s.thread_origin;
|
|
17669
|
+
}
|
|
17670
|
+
}
|
|
17023
17671
|
s.updated_at = now();
|
|
17024
17672
|
this.persistSessions();
|
|
17025
17673
|
}
|