codex-to-im 1.0.45 → 1.0.47
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 +539 -81
- package/docs/dev-plan.md +34 -3
- package/package.json +2 -2
package/dist/daemon.mjs
CHANGED
|
@@ -571,7 +571,8 @@ var init_codex_provider = __esm({
|
|
|
571
571
|
usage: usage ? {
|
|
572
572
|
input_tokens: usage.input_tokens ?? 0,
|
|
573
573
|
output_tokens: usage.output_tokens ?? 0,
|
|
574
|
-
cache_read_input_tokens: usage.cached_input_tokens ?? 0
|
|
574
|
+
cache_read_input_tokens: usage.cached_input_tokens ?? 0,
|
|
575
|
+
reasoning_output_tokens: usage.reasoning_output_tokens ?? 0
|
|
575
576
|
} : void 0,
|
|
576
577
|
...threadId ? { session_id: threadId } : {}
|
|
577
578
|
}));
|
|
@@ -1223,14 +1224,19 @@ function buildPostContent(text2) {
|
|
|
1223
1224
|
function htmlToFeishuMarkdown(html) {
|
|
1224
1225
|
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
1226
|
}
|
|
1226
|
-
function
|
|
1227
|
+
function normalizeToolStatusForRender(status, options) {
|
|
1228
|
+
if (status !== "running" || !options.terminalStatus) return status;
|
|
1229
|
+
return options.terminalStatus === "completed" ? "complete" : "error";
|
|
1230
|
+
}
|
|
1231
|
+
function buildToolProgressMarkdown(tools, options = {}) {
|
|
1227
1232
|
if (tools.length === 0) return "";
|
|
1228
1233
|
const grouped = /* @__PURE__ */ new Map();
|
|
1229
1234
|
for (const tool of tools) {
|
|
1230
1235
|
const key = tool.name || "tool";
|
|
1231
1236
|
const bucket = grouped.get(key) || { running: 0, complete: 0, error: 0 };
|
|
1232
|
-
|
|
1233
|
-
|
|
1237
|
+
const status = normalizeToolStatusForRender(tool.status, options);
|
|
1238
|
+
if (status === "running") bucket.running += 1;
|
|
1239
|
+
else if (status === "error") bucket.error += 1;
|
|
1234
1240
|
else bucket.complete += 1;
|
|
1235
1241
|
grouped.set(key, bucket);
|
|
1236
1242
|
}
|
|
@@ -1249,11 +1255,25 @@ function buildToolProgressMarkdown(tools) {
|
|
|
1249
1255
|
});
|
|
1250
1256
|
return lines.join("\n");
|
|
1251
1257
|
}
|
|
1252
|
-
function
|
|
1258
|
+
function getTaskProgressPresentation(task, options) {
|
|
1259
|
+
if (!options.terminalStatus) {
|
|
1260
|
+
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" };
|
|
1261
|
+
}
|
|
1262
|
+
if (task.status === "completed") {
|
|
1263
|
+
return { icon: "\u2705", label: "\u5DF2\u5B8C\u6210" };
|
|
1264
|
+
}
|
|
1265
|
+
if (options.terminalStatus === "completed") {
|
|
1266
|
+
return { icon: "\u2705", label: "\u5DF2\u7ED3\u675F" };
|
|
1267
|
+
}
|
|
1268
|
+
if (options.terminalStatus === "interrupted") {
|
|
1269
|
+
return task.status === "pending" ? { icon: "\u26A0\uFE0F", label: "\u672A\u6267\u884C" } : { icon: "\u26A0\uFE0F", label: "\u5DF2\u505C\u6B62" };
|
|
1270
|
+
}
|
|
1271
|
+
return task.status === "pending" ? { icon: "\u274C", label: "\u672A\u6267\u884C" } : { icon: "\u274C", label: "\u5DF2\u4E2D\u65AD" };
|
|
1272
|
+
}
|
|
1273
|
+
function buildTaskProgressMarkdown(tasks, options = {}) {
|
|
1253
1274
|
if (tasks.length === 0) return "";
|
|
1254
1275
|
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";
|
|
1276
|
+
const { icon, label } = getTaskProgressPresentation(task, options);
|
|
1257
1277
|
return `${icon} ${task.text}\uFF08${label}\uFF09`;
|
|
1258
1278
|
}).join("\n");
|
|
1259
1279
|
}
|
|
@@ -1274,11 +1294,12 @@ function buildStreamingToolsContent(tools) {
|
|
|
1274
1294
|
function buildStreamingTaskContent(tasks) {
|
|
1275
1295
|
return buildTaskProgressMarkdown(tasks);
|
|
1276
1296
|
}
|
|
1277
|
-
function buildFinalCardJson(text2, tasks, tools, footer) {
|
|
1297
|
+
function buildFinalCardJson(text2, tasks, tools, footer, terminalStatus) {
|
|
1278
1298
|
const elements = [];
|
|
1279
1299
|
const content = preprocessFeishuMarkdown(text2);
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1300
|
+
const renderOptions = { terminalStatus };
|
|
1301
|
+
const taskMd = buildTaskProgressMarkdown(tasks, renderOptions);
|
|
1302
|
+
const toolMd = buildToolProgressMarkdown(tools, renderOptions);
|
|
1282
1303
|
if (content) {
|
|
1283
1304
|
elements.push({
|
|
1284
1305
|
tag: "markdown",
|
|
@@ -1384,9 +1405,53 @@ var MAX_FILE_SIZE = 20 * 1024 * 1024;
|
|
|
1384
1405
|
var TYPING_EMOJI = "Typing";
|
|
1385
1406
|
var CARD_THROTTLE_MS = 1e3;
|
|
1386
1407
|
var CARD_REQUEST_TIMEOUT_MS = 15e3;
|
|
1408
|
+
var CARD_FINALIZE_FLUSH_WAIT_EXTRA_MS = 1e3;
|
|
1409
|
+
var CARD_FULL_REFRESH_INTERVAL_MS = 5 * 6e4;
|
|
1387
1410
|
var INITIAL_STREAMING_STATUS = "\u5904\u7406\u4E2D";
|
|
1388
1411
|
var EMPTY_STREAMING_TASKS = "";
|
|
1389
1412
|
var EMPTY_STREAMING_TOOLS = "";
|
|
1413
|
+
function buildStreamingCardBody(content, tasksText, toolsText, statusText) {
|
|
1414
|
+
return {
|
|
1415
|
+
schema: "2.0",
|
|
1416
|
+
config: {
|
|
1417
|
+
streaming_mode: true,
|
|
1418
|
+
wide_screen_mode: true,
|
|
1419
|
+
summary: { content: "\u601D\u8003\u4E2D..." }
|
|
1420
|
+
},
|
|
1421
|
+
body: {
|
|
1422
|
+
elements: [
|
|
1423
|
+
{
|
|
1424
|
+
tag: "markdown",
|
|
1425
|
+
content,
|
|
1426
|
+
text_align: "left",
|
|
1427
|
+
text_size: "normal",
|
|
1428
|
+
element_id: "streaming_content"
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
tag: "markdown",
|
|
1432
|
+
content: tasksText,
|
|
1433
|
+
text_align: "left",
|
|
1434
|
+
text_size: "normal",
|
|
1435
|
+
element_id: "streaming_tasks"
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
tag: "markdown",
|
|
1439
|
+
content: toolsText,
|
|
1440
|
+
text_align: "left",
|
|
1441
|
+
text_size: "normal",
|
|
1442
|
+
element_id: "streaming_tools"
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
tag: "markdown",
|
|
1446
|
+
content: statusText,
|
|
1447
|
+
text_align: "left",
|
|
1448
|
+
text_size: "notation",
|
|
1449
|
+
element_id: "streaming_status"
|
|
1450
|
+
}
|
|
1451
|
+
]
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1390
1455
|
var MIME_BY_TYPE = {
|
|
1391
1456
|
image: "image/png",
|
|
1392
1457
|
file: "application/octet-stream",
|
|
@@ -1421,6 +1486,8 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1421
1486
|
/** Cached tenant token for upload APIs. */
|
|
1422
1487
|
tenantTokenCache = null;
|
|
1423
1488
|
cardRequestTimeoutMs = CARD_REQUEST_TIMEOUT_MS;
|
|
1489
|
+
cardFinalizeFlushWaitExtraMs = CARD_FINALIZE_FLUSH_WAIT_EXTRA_MS;
|
|
1490
|
+
cardFullRefreshIntervalMs = CARD_FULL_REFRESH_INTERVAL_MS;
|
|
1424
1491
|
constructor(instance) {
|
|
1425
1492
|
super();
|
|
1426
1493
|
this.channelType = instance?.id || "feishu";
|
|
@@ -1654,46 +1721,12 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1654
1721
|
return false;
|
|
1655
1722
|
}
|
|
1656
1723
|
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
|
-
};
|
|
1724
|
+
const cardBody = buildStreamingCardBody(
|
|
1725
|
+
"\u{1F4AD} Thinking...",
|
|
1726
|
+
EMPTY_STREAMING_TASKS,
|
|
1727
|
+
EMPTY_STREAMING_TOOLS,
|
|
1728
|
+
INITIAL_STREAMING_STATUS
|
|
1729
|
+
);
|
|
1697
1730
|
const createResp = await this.withFeishuRequestTimeout(cardKey, "card.create", () => cardkit.card.create({
|
|
1698
1731
|
data: { type: "card_json", data: JSON.stringify(cardBody) }
|
|
1699
1732
|
}));
|
|
@@ -1724,12 +1757,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1724
1757
|
console.warn("[feishu-adapter] Card message send returned no message_id");
|
|
1725
1758
|
return false;
|
|
1726
1759
|
}
|
|
1760
|
+
const now2 = Date.now();
|
|
1727
1761
|
this.activeCards.set(cardKey, {
|
|
1728
1762
|
chatId,
|
|
1729
1763
|
cardId,
|
|
1730
1764
|
messageId,
|
|
1731
1765
|
sequence: 0,
|
|
1732
|
-
startTime:
|
|
1766
|
+
startTime: now2,
|
|
1733
1767
|
taskItems: [],
|
|
1734
1768
|
toolCalls: [],
|
|
1735
1769
|
thinking: true,
|
|
@@ -1748,7 +1782,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1748
1782
|
lastSuccessfulFlushAt: null,
|
|
1749
1783
|
lastFlushErrorAt: null,
|
|
1750
1784
|
lastFlushError: null,
|
|
1751
|
-
consecutiveFlushFailures: 0
|
|
1785
|
+
consecutiveFlushFailures: 0,
|
|
1786
|
+
lastFullRefreshAttemptAt: now2,
|
|
1787
|
+
lastSuccessfulFullRefreshAt: null
|
|
1752
1788
|
});
|
|
1753
1789
|
console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
|
|
1754
1790
|
return true;
|
|
@@ -1837,6 +1873,17 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1837
1873
|
const toolsText = buildStreamingToolsContent(state.toolCalls) || EMPTY_STREAMING_TOOLS;
|
|
1838
1874
|
const statusText = state.pendingStatusText || INITIAL_STREAMING_STATUS;
|
|
1839
1875
|
const updates = [];
|
|
1876
|
+
if (this.shouldFullRefreshCard(state, Date.now())) {
|
|
1877
|
+
const refreshed = await this.flushFullCardRefresh(
|
|
1878
|
+
streamKey,
|
|
1879
|
+
state,
|
|
1880
|
+
content,
|
|
1881
|
+
tasksText,
|
|
1882
|
+
toolsText,
|
|
1883
|
+
statusText
|
|
1884
|
+
);
|
|
1885
|
+
if (refreshed) return;
|
|
1886
|
+
}
|
|
1840
1887
|
if (content !== state.renderedText) {
|
|
1841
1888
|
updates.push({
|
|
1842
1889
|
elementId: "streaming_content",
|
|
@@ -1903,24 +1950,33 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1903
1950
|
state.toolCalls = tools;
|
|
1904
1951
|
this.scheduleCardFlush(cardKey);
|
|
1905
1952
|
}
|
|
1906
|
-
async awaitCardFlushCompletion(streamKey) {
|
|
1953
|
+
async awaitCardFlushCompletion(streamKey, timeoutMs = this.getCardRequestTimeoutMs() + Math.max(0, this.cardFinalizeFlushWaitExtraMs)) {
|
|
1954
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
1907
1955
|
while (true) {
|
|
1908
1956
|
const state = this.activeCards.get(streamKey);
|
|
1909
|
-
if (!state) return;
|
|
1957
|
+
if (!state) return true;
|
|
1910
1958
|
const inFlight = state.flushInFlight;
|
|
1911
1959
|
if (inFlight) {
|
|
1960
|
+
const remainingMs = deadline - Date.now();
|
|
1961
|
+
if (remainingMs <= 0) return false;
|
|
1962
|
+
const timedOut = Symbol("flush-timeout");
|
|
1912
1963
|
try {
|
|
1913
|
-
await
|
|
1964
|
+
const result = await Promise.race([
|
|
1965
|
+
inFlight.then(() => null),
|
|
1966
|
+
new Promise((resolve2) => setTimeout(() => resolve2(timedOut), remainingMs))
|
|
1967
|
+
]);
|
|
1968
|
+
if (result === timedOut) return false;
|
|
1914
1969
|
} catch {
|
|
1915
1970
|
}
|
|
1916
1971
|
continue;
|
|
1917
1972
|
}
|
|
1973
|
+
if (Date.now() >= deadline) return false;
|
|
1918
1974
|
if (state.flushQueued) {
|
|
1919
1975
|
state.flushQueued = false;
|
|
1920
1976
|
this.enqueueCardFlush(streamKey);
|
|
1921
1977
|
continue;
|
|
1922
1978
|
}
|
|
1923
|
-
return;
|
|
1979
|
+
return true;
|
|
1924
1980
|
}
|
|
1925
1981
|
}
|
|
1926
1982
|
/**
|
|
@@ -1943,7 +1999,12 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1943
1999
|
clearTimeout(state.throttleTimer);
|
|
1944
2000
|
state.throttleTimer = null;
|
|
1945
2001
|
}
|
|
1946
|
-
await this.awaitCardFlushCompletion(cardKey);
|
|
2002
|
+
const flushed = await this.awaitCardFlushCompletion(cardKey);
|
|
2003
|
+
if (!flushed) {
|
|
2004
|
+
console.warn(`[feishu-adapter] Card finalize proceeding after flush wait timeout: streamKey=${cardKey}`);
|
|
2005
|
+
state.flushInFlight = null;
|
|
2006
|
+
state.flushQueued = false;
|
|
2007
|
+
}
|
|
1947
2008
|
try {
|
|
1948
2009
|
state.sequence++;
|
|
1949
2010
|
await this.withFeishuRequestTimeout(cardKey, "card.settings", () => cardkit.card.settings({
|
|
@@ -1972,7 +2033,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
1972
2033
|
|
|
1973
2034
|
${trimmedResponse}`;
|
|
1974
2035
|
}
|
|
1975
|
-
const finalCardJson = buildFinalCardJson(finalText, state.taskItems, state.toolCalls, footer);
|
|
2036
|
+
const finalCardJson = buildFinalCardJson(finalText, state.taskItems, state.toolCalls, footer, status);
|
|
1976
2037
|
state.sequence++;
|
|
1977
2038
|
await this.withFeishuRequestTimeout(cardKey, "card.update", () => cardkit.card.update({
|
|
1978
2039
|
path: { card_id: state.cardId },
|
|
@@ -2026,6 +2087,44 @@ ${trimmedResponse}`;
|
|
|
2026
2087
|
consecutiveFailures: state.consecutiveFlushFailures
|
|
2027
2088
|
};
|
|
2028
2089
|
}
|
|
2090
|
+
shouldFullRefreshCard(state, now2) {
|
|
2091
|
+
const interval = Math.max(0, this.cardFullRefreshIntervalMs);
|
|
2092
|
+
if (interval <= 0) return false;
|
|
2093
|
+
if (!Number.isFinite(now2)) return false;
|
|
2094
|
+
return now2 - state.lastFullRefreshAttemptAt >= interval;
|
|
2095
|
+
}
|
|
2096
|
+
async flushFullCardRefresh(streamKey, state, content, tasksText, toolsText, statusText) {
|
|
2097
|
+
state.lastFullRefreshAttemptAt = Date.now();
|
|
2098
|
+
const cardkit = this.restClient?.cardkit?.v1;
|
|
2099
|
+
if (!cardkit?.card?.update) return false;
|
|
2100
|
+
try {
|
|
2101
|
+
state.sequence++;
|
|
2102
|
+
await this.withFeishuRequestTimeout(streamKey, "card.update:streaming_refresh", () => cardkit.card.update({
|
|
2103
|
+
path: { card_id: state.cardId },
|
|
2104
|
+
data: {
|
|
2105
|
+
card: {
|
|
2106
|
+
type: "card_json",
|
|
2107
|
+
data: JSON.stringify(buildStreamingCardBody(content, tasksText, toolsText, statusText))
|
|
2108
|
+
},
|
|
2109
|
+
sequence: state.sequence
|
|
2110
|
+
}
|
|
2111
|
+
}));
|
|
2112
|
+
state.renderedText = content;
|
|
2113
|
+
state.renderedTasksText = tasksText;
|
|
2114
|
+
state.renderedToolsText = toolsText;
|
|
2115
|
+
state.renderedStatusText = statusText;
|
|
2116
|
+
state.lastSuccessfulFullRefreshAt = Date.now();
|
|
2117
|
+
this.markCardFlushSuccess(state);
|
|
2118
|
+
return true;
|
|
2119
|
+
} catch (err) {
|
|
2120
|
+
this.markCardFlushFailure(state, err);
|
|
2121
|
+
console.warn(
|
|
2122
|
+
"[feishu-adapter] card.update streaming refresh failed:",
|
|
2123
|
+
err instanceof Error ? err.message : err
|
|
2124
|
+
);
|
|
2125
|
+
return false;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2029
2128
|
getCardRequestTimeoutMs() {
|
|
2030
2129
|
return Math.max(1, this.cardRequestTimeoutMs);
|
|
2031
2130
|
}
|
|
@@ -9973,6 +10072,13 @@ function parseUpdatePlanTasks(argumentsJson) {
|
|
|
9973
10072
|
if (!parsed || typeof parsed !== "object") return [];
|
|
9974
10073
|
return parseTaskProgressItems(parsed.plan ?? parsed.tasks);
|
|
9975
10074
|
}
|
|
10075
|
+
function extractReasoningSummary(payload) {
|
|
10076
|
+
for (const value of [payload.summary, payload.content, payload.text, payload.message]) {
|
|
10077
|
+
const text2 = extractNormalizedStructuredText(value);
|
|
10078
|
+
if (text2) return text2;
|
|
10079
|
+
}
|
|
10080
|
+
return "";
|
|
10081
|
+
}
|
|
9976
10082
|
function extractToolOutputText(value) {
|
|
9977
10083
|
if (typeof value !== "string") return extractNormalizedFreeText(value);
|
|
9978
10084
|
const trimmed = value.trim();
|
|
@@ -9986,6 +10092,39 @@ function extractToolOutputText(value) {
|
|
|
9986
10092
|
}
|
|
9987
10093
|
return extractNormalizedFreeText(value);
|
|
9988
10094
|
}
|
|
10095
|
+
function summarizePatchChanges(value) {
|
|
10096
|
+
if (!value || typeof value !== "object") return "";
|
|
10097
|
+
return Object.entries(value).map(([filePath, detail]) => {
|
|
10098
|
+
const kind = detail && typeof detail === "object" ? extractNormalizedFreeText(detail.type ?? detail.kind) : "";
|
|
10099
|
+
return kind ? `${kind}: ${filePath}` : filePath;
|
|
10100
|
+
}).filter(Boolean).join("\n");
|
|
10101
|
+
}
|
|
10102
|
+
function summarizeToolSearchOutput(value) {
|
|
10103
|
+
if (!Array.isArray(value)) return "";
|
|
10104
|
+
let count = 0;
|
|
10105
|
+
const names = [];
|
|
10106
|
+
for (const entry of value) {
|
|
10107
|
+
if (!entry || typeof entry !== "object") continue;
|
|
10108
|
+
const namespaceName = extractNormalizedFreeText(entry.name);
|
|
10109
|
+
if (namespaceName) names.push(namespaceName);
|
|
10110
|
+
const tools = entry.tools;
|
|
10111
|
+
if (Array.isArray(tools)) count += tools.length;
|
|
10112
|
+
}
|
|
10113
|
+
const prefix = count > 0 ? `Found ${count} tools` : "";
|
|
10114
|
+
const suffix = names.length > 0 ? names.slice(0, 5).join(", ") : "";
|
|
10115
|
+
return [prefix, suffix].filter(Boolean).join(": ");
|
|
10116
|
+
}
|
|
10117
|
+
function getDynamicToolCallId(payload) {
|
|
10118
|
+
return extractNormalizedFreeText(payload.call_id ?? payload.callId);
|
|
10119
|
+
}
|
|
10120
|
+
function formatDesktopToolName(namespaceValue, nameValue) {
|
|
10121
|
+
const name = extractNormalizedFreeText(nameValue);
|
|
10122
|
+
if (!name) return "";
|
|
10123
|
+
const namespace = extractNormalizedFreeText(namespaceValue);
|
|
10124
|
+
if (!namespace) return name;
|
|
10125
|
+
if (name.startsWith(namespace)) return name;
|
|
10126
|
+
return namespace.endsWith("__") || namespace.endsWith("/") || namespace.endsWith(".") ? `${namespace}${name}` : `${namespace}__${name}`;
|
|
10127
|
+
}
|
|
9989
10128
|
function createDesktopEventSignature(rawLine) {
|
|
9990
10129
|
return crypto4.createHash("sha1").update(rawLine).digest("hex");
|
|
9991
10130
|
}
|
|
@@ -10017,7 +10156,28 @@ function isSessionMessageLine(line) {
|
|
|
10017
10156
|
function isTurnContextLine(line) {
|
|
10018
10157
|
return line.type === "turn_context";
|
|
10019
10158
|
}
|
|
10159
|
+
var IGNORED_EVENT_MSG_TYPES = /* @__PURE__ */ new Set([
|
|
10160
|
+
"context_compacted",
|
|
10161
|
+
"thread_name_updated",
|
|
10162
|
+
"thread_rolled_back",
|
|
10163
|
+
"token_count"
|
|
10164
|
+
]);
|
|
10165
|
+
var IGNORED_RESPONSE_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
10166
|
+
"web_search_call"
|
|
10167
|
+
]);
|
|
10168
|
+
function isIgnoredMirrorLineKind(line) {
|
|
10169
|
+
if (isSessionEventLine(line)) {
|
|
10170
|
+
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
10171
|
+
return IGNORED_EVENT_MSG_TYPES.has(payloadType);
|
|
10172
|
+
}
|
|
10173
|
+
if (isSessionMessageLine(line)) {
|
|
10174
|
+
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
10175
|
+
return IGNORED_RESPONSE_ITEM_TYPES.has(payloadType);
|
|
10176
|
+
}
|
|
10177
|
+
return false;
|
|
10178
|
+
}
|
|
10020
10179
|
function describeUnhandledMirrorLineKind(line) {
|
|
10180
|
+
if (isIgnoredMirrorLineKind(line)) return null;
|
|
10021
10181
|
if (isSessionEventLine(line)) {
|
|
10022
10182
|
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
10023
10183
|
return `event_msg:${payloadType || "<unknown>"}`;
|
|
@@ -10094,6 +10254,20 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
|
|
|
10094
10254
|
});
|
|
10095
10255
|
return;
|
|
10096
10256
|
}
|
|
10257
|
+
if (isSessionEventLine(parsed) && parsed.payload?.type === "agent_message") {
|
|
10258
|
+
const text2 = extractNormalizedStructuredText(parsed.payload.message);
|
|
10259
|
+
if (!text2) return;
|
|
10260
|
+
const role = parsed.payload.phase === "commentary" ? "commentary" : "assistant";
|
|
10261
|
+
const lastEvent = events[events.length - 1];
|
|
10262
|
+
if (lastEvent?.role === role && lastEvent.content === text2) return;
|
|
10263
|
+
events.push({
|
|
10264
|
+
signature: createDesktopEventSignature(rawLine),
|
|
10265
|
+
role,
|
|
10266
|
+
content: text2,
|
|
10267
|
+
timestamp: parsed.timestamp || ""
|
|
10268
|
+
});
|
|
10269
|
+
return;
|
|
10270
|
+
}
|
|
10097
10271
|
if (isSessionEventLine(parsed) && parsed.payload?.type === "task_complete") {
|
|
10098
10272
|
const text2 = extractNormalizedStructuredText(parsed.payload.last_agent_message);
|
|
10099
10273
|
if (!text2) return;
|
|
@@ -10112,10 +10286,14 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
|
|
|
10112
10286
|
if (isSessionMessageLine(parsed) && parsed.payload?.type === "message" && parsed.payload.role === "assistant") {
|
|
10113
10287
|
const text2 = extractDesktopMessageText(parsed);
|
|
10114
10288
|
if (!text2) return;
|
|
10289
|
+
const role = parsed.payload.phase === "commentary" ? "commentary" : "assistant";
|
|
10290
|
+
const content = parsed.payload.phase === "commentary" ? text2.replace(/^\[commentary\]\n/, "") : text2;
|
|
10291
|
+
const lastEvent = events[events.length - 1];
|
|
10292
|
+
if (lastEvent?.role === role && lastEvent.content === content) return;
|
|
10115
10293
|
events.push({
|
|
10116
10294
|
signature: createDesktopEventSignature(rawLine),
|
|
10117
|
-
role
|
|
10118
|
-
content
|
|
10295
|
+
role,
|
|
10296
|
+
content,
|
|
10119
10297
|
timestamp: parsed.timestamp || ""
|
|
10120
10298
|
});
|
|
10121
10299
|
}
|
|
@@ -10152,6 +10330,22 @@ function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
|
|
|
10152
10330
|
});
|
|
10153
10331
|
return true;
|
|
10154
10332
|
}
|
|
10333
|
+
if (isIgnoredMirrorLineKind(parsed)) {
|
|
10334
|
+
return true;
|
|
10335
|
+
}
|
|
10336
|
+
if (parsed.payload?.type === "agent_message") {
|
|
10337
|
+
const text2 = extractNormalizedStructuredText(parsed.payload.message);
|
|
10338
|
+
if (!text2) return true;
|
|
10339
|
+
records.push({
|
|
10340
|
+
signature,
|
|
10341
|
+
type: "message",
|
|
10342
|
+
role: parsed.payload.phase === "commentary" ? "commentary" : "assistant",
|
|
10343
|
+
content: text2,
|
|
10344
|
+
timestamp,
|
|
10345
|
+
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
10346
|
+
});
|
|
10347
|
+
return true;
|
|
10348
|
+
}
|
|
10155
10349
|
if (parsed.payload?.type === "agent_reasoning") {
|
|
10156
10350
|
const text2 = extractNormalizedStructuredText(parsed.payload.text);
|
|
10157
10351
|
if (!text2) return true;
|
|
@@ -10194,6 +10388,68 @@ function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
|
|
|
10194
10388
|
});
|
|
10195
10389
|
return true;
|
|
10196
10390
|
}
|
|
10391
|
+
if (parsed.payload?.type === "exec_command_end") {
|
|
10392
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
10393
|
+
const exitCode = typeof parsed.payload.exit_code === "number" ? parsed.payload.exit_code : null;
|
|
10394
|
+
const status = extractNormalizedFreeText(parsed.payload.status).toLowerCase();
|
|
10395
|
+
records.push({
|
|
10396
|
+
signature,
|
|
10397
|
+
type: "tool_finished",
|
|
10398
|
+
content: extractToolOutputText(
|
|
10399
|
+
parsed.payload.aggregated_output ?? parsed.payload.formatted_output ?? parsed.payload.stdout ?? parsed.payload.stderr ?? parsed.payload.command
|
|
10400
|
+
),
|
|
10401
|
+
timestamp,
|
|
10402
|
+
...parsed.payload.turn_id || activeTurnId ? { turnId: parsed.payload.turn_id || activeTurnId || void 0 } : {},
|
|
10403
|
+
toolId,
|
|
10404
|
+
toolName: "Bash",
|
|
10405
|
+
isError: status === "failed" || exitCode != null && exitCode !== 0
|
|
10406
|
+
});
|
|
10407
|
+
return true;
|
|
10408
|
+
}
|
|
10409
|
+
if (parsed.payload?.type === "patch_apply_end") {
|
|
10410
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
10411
|
+
const status = extractNormalizedFreeText(parsed.payload.status).toLowerCase();
|
|
10412
|
+
records.push({
|
|
10413
|
+
signature,
|
|
10414
|
+
type: "tool_finished",
|
|
10415
|
+
content: summarizePatchChanges(parsed.payload.changes) || extractToolOutputText(parsed.payload.stdout ?? parsed.payload.stderr),
|
|
10416
|
+
timestamp,
|
|
10417
|
+
...parsed.payload.turn_id || activeTurnId ? { turnId: parsed.payload.turn_id || activeTurnId || void 0 } : {},
|
|
10418
|
+
toolId,
|
|
10419
|
+
toolName: "apply_patch",
|
|
10420
|
+
isError: parsed.payload.success === false || status === "failed"
|
|
10421
|
+
});
|
|
10422
|
+
return true;
|
|
10423
|
+
}
|
|
10424
|
+
if (parsed.payload?.type === "dynamic_tool_call_request") {
|
|
10425
|
+
const toolId = getDynamicToolCallId(parsed.payload) || signature;
|
|
10426
|
+
const toolName = extractNormalizedFreeText(parsed.payload.tool) || "tool";
|
|
10427
|
+
records.push({
|
|
10428
|
+
signature,
|
|
10429
|
+
type: "tool_started",
|
|
10430
|
+
content: "",
|
|
10431
|
+
timestamp,
|
|
10432
|
+
...parsed.payload.turnId || activeTurnId ? { turnId: parsed.payload.turnId || activeTurnId || void 0 } : {},
|
|
10433
|
+
toolId,
|
|
10434
|
+
toolName
|
|
10435
|
+
});
|
|
10436
|
+
return true;
|
|
10437
|
+
}
|
|
10438
|
+
if (parsed.payload?.type === "dynamic_tool_call_response") {
|
|
10439
|
+
const toolId = getDynamicToolCallId(parsed.payload) || signature;
|
|
10440
|
+
const toolName = extractNormalizedFreeText(parsed.payload.tool) || "tool";
|
|
10441
|
+
records.push({
|
|
10442
|
+
signature,
|
|
10443
|
+
type: "tool_finished",
|
|
10444
|
+
content: extractToolOutputText(parsed.payload.content_items ?? parsed.payload.error),
|
|
10445
|
+
timestamp,
|
|
10446
|
+
...parsed.payload.turn_id || activeTurnId ? { turnId: parsed.payload.turn_id || activeTurnId || void 0 } : {},
|
|
10447
|
+
toolId,
|
|
10448
|
+
toolName,
|
|
10449
|
+
isError: parsed.payload.success === false
|
|
10450
|
+
});
|
|
10451
|
+
return true;
|
|
10452
|
+
}
|
|
10197
10453
|
if (parsed.payload?.type === "user_message") {
|
|
10198
10454
|
const text2 = extractNormalizedStructuredText(parsed.payload.message);
|
|
10199
10455
|
if (!text2) return true;
|
|
@@ -10223,6 +10479,21 @@ function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
|
|
|
10223
10479
|
function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds) {
|
|
10224
10480
|
const signature = createDesktopEventSignature(rawLine);
|
|
10225
10481
|
const timestamp = parsed.timestamp || "";
|
|
10482
|
+
if (isIgnoredMirrorLineKind(parsed)) {
|
|
10483
|
+
return true;
|
|
10484
|
+
}
|
|
10485
|
+
if (parsed.payload?.type === "reasoning") {
|
|
10486
|
+
const text2 = extractReasoningSummary(parsed.payload);
|
|
10487
|
+
if (!text2) return true;
|
|
10488
|
+
records.push({
|
|
10489
|
+
signature,
|
|
10490
|
+
type: "reasoning",
|
|
10491
|
+
content: text2,
|
|
10492
|
+
timestamp,
|
|
10493
|
+
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
10494
|
+
});
|
|
10495
|
+
return true;
|
|
10496
|
+
}
|
|
10226
10497
|
if (parsed.payload?.type === "message" && parsed.payload.role === "assistant") {
|
|
10227
10498
|
const text2 = extractDesktopMessageText(parsed);
|
|
10228
10499
|
if (!text2) return true;
|
|
@@ -10236,8 +10507,36 @@ function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId,
|
|
|
10236
10507
|
});
|
|
10237
10508
|
return true;
|
|
10238
10509
|
}
|
|
10510
|
+
if (parsed.payload?.type === "tool_search_call") {
|
|
10511
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
10512
|
+
records.push({
|
|
10513
|
+
signature,
|
|
10514
|
+
type: "tool_started",
|
|
10515
|
+
content: "",
|
|
10516
|
+
timestamp,
|
|
10517
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
10518
|
+
toolId,
|
|
10519
|
+
toolName: "tool_search"
|
|
10520
|
+
});
|
|
10521
|
+
return true;
|
|
10522
|
+
}
|
|
10523
|
+
if (parsed.payload?.type === "tool_search_output") {
|
|
10524
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
10525
|
+
const status = extractNormalizedFreeText(parsed.payload.status).toLowerCase();
|
|
10526
|
+
records.push({
|
|
10527
|
+
signature,
|
|
10528
|
+
type: "tool_finished",
|
|
10529
|
+
content: summarizeToolSearchOutput(parsed.payload.tools),
|
|
10530
|
+
timestamp,
|
|
10531
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
10532
|
+
toolId,
|
|
10533
|
+
toolName: "tool_search",
|
|
10534
|
+
isError: status === "failed"
|
|
10535
|
+
});
|
|
10536
|
+
return true;
|
|
10537
|
+
}
|
|
10239
10538
|
if (parsed.payload?.type === "function_call") {
|
|
10240
|
-
const toolName =
|
|
10539
|
+
const toolName = formatDesktopToolName(parsed.payload.namespace, parsed.payload.name);
|
|
10241
10540
|
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
10242
10541
|
if (!toolName) return true;
|
|
10243
10542
|
if (toolName === "update_plan") {
|
|
@@ -10265,7 +10564,7 @@ function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId,
|
|
|
10265
10564
|
return true;
|
|
10266
10565
|
}
|
|
10267
10566
|
if (parsed.payload?.type === "custom_tool_call") {
|
|
10268
|
-
const toolName =
|
|
10567
|
+
const toolName = formatDesktopToolName(parsed.payload.namespace, parsed.payload.name);
|
|
10269
10568
|
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
10270
10569
|
if (!toolName) return true;
|
|
10271
10570
|
if (toolName === "update_plan") {
|
|
@@ -11703,6 +12002,7 @@ ${notice}` : notice;
|
|
|
11703
12002
|
function nowIso2() {
|
|
11704
12003
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
11705
12004
|
}
|
|
12005
|
+
var MIRROR_DUPLICATE_TEXT_WINDOW_MS = 2e3;
|
|
11706
12006
|
function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
11707
12007
|
const safeTimestamp = timestamp || nowIso2();
|
|
11708
12008
|
return {
|
|
@@ -11767,6 +12067,14 @@ function markMirrorContentResponse(turnState, timestamp) {
|
|
|
11767
12067
|
turnState.lastContentResponseAt = responseAt;
|
|
11768
12068
|
turnState.lastResponseAt = responseAt;
|
|
11769
12069
|
}
|
|
12070
|
+
function isNearDuplicateMirrorText(previousText, nextText, previousTimestamp, nextTimestamp) {
|
|
12071
|
+
if (previousText !== nextText) return false;
|
|
12072
|
+
if (!previousTimestamp || !nextTimestamp) return true;
|
|
12073
|
+
const previousMs = Date.parse(previousTimestamp);
|
|
12074
|
+
const nextMs = Date.parse(nextTimestamp);
|
|
12075
|
+
if (!Number.isFinite(previousMs) || !Number.isFinite(nextMs)) return true;
|
|
12076
|
+
return Math.abs(nextMs - previousMs) <= MIRROR_DUPLICATE_TEXT_WINDOW_MS;
|
|
12077
|
+
}
|
|
11770
12078
|
function finalizeMirrorTurn(subscription, signature, timestamp, status, preferredText) {
|
|
11771
12079
|
const pendingTurn = subscription.pendingTurn;
|
|
11772
12080
|
subscription.pendingTurn = null;
|
|
@@ -11836,7 +12144,14 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11836
12144
|
if (record.role === "assistant") {
|
|
11837
12145
|
const text2 = record.content.trim();
|
|
11838
12146
|
if (text2) {
|
|
12147
|
+
if (isNearDuplicateMirrorText(
|
|
12148
|
+
pendingTurn.lastAssistantText,
|
|
12149
|
+
text2,
|
|
12150
|
+
pendingTurn.lastAssistantTextAt ?? null,
|
|
12151
|
+
record.timestamp
|
|
12152
|
+
)) continue;
|
|
11839
12153
|
pendingTurn.lastAssistantText = text2;
|
|
12154
|
+
pendingTurn.lastAssistantTextAt = record.timestamp || nowIso2();
|
|
11840
12155
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11841
12156
|
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11842
12157
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
@@ -11844,7 +12159,14 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
11844
12159
|
} else if (record.role === "commentary") {
|
|
11845
12160
|
const text2 = record.content.trim();
|
|
11846
12161
|
if (text2) {
|
|
12162
|
+
if (isNearDuplicateMirrorText(
|
|
12163
|
+
pendingTurn.lastCommentaryText,
|
|
12164
|
+
text2,
|
|
12165
|
+
pendingTurn.lastCommentaryTextAt ?? null,
|
|
12166
|
+
record.timestamp
|
|
12167
|
+
)) continue;
|
|
11847
12168
|
pendingTurn.lastCommentaryText = text2;
|
|
12169
|
+
pendingTurn.lastCommentaryTextAt = record.timestamp || nowIso2();
|
|
11848
12170
|
appendMirrorStreamText(pendingTurn, text2);
|
|
11849
12171
|
markMirrorContentResponse(pendingTurn, record.timestamp);
|
|
11850
12172
|
hooks.onStreamText?.(subscription, pendingTurn);
|
|
@@ -13360,10 +13682,27 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
13360
13682
|
}
|
|
13361
13683
|
case "/stop": {
|
|
13362
13684
|
const binding = resolve(msg.address);
|
|
13685
|
+
const session = store.getSession(binding.codepilotSessionId);
|
|
13363
13686
|
const task = deps.getActiveTask(binding.codepilotSessionId);
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13687
|
+
const runningHealthStatuses = /* @__PURE__ */ new Set([
|
|
13688
|
+
"running_active",
|
|
13689
|
+
"waiting_tool",
|
|
13690
|
+
"slow_observed",
|
|
13691
|
+
"suspected_stall",
|
|
13692
|
+
"suspected_stream_ui_stall",
|
|
13693
|
+
"suspected_detached"
|
|
13694
|
+
]);
|
|
13695
|
+
const looksRunning = session?.runtime_status === "running" || session?.runtime_status === "queued" || runningHealthStatuses.has(session?.health_status || "");
|
|
13696
|
+
if (task || looksRunning) {
|
|
13697
|
+
const taskName = getSessionDisplayName(session, binding.workingDirectory);
|
|
13698
|
+
const detail = "\u7528\u6237\u6267\u884C /stop\uFF0C\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002";
|
|
13699
|
+
if (deps.forceStopSession) {
|
|
13700
|
+
await deps.forceStopSession(binding.codepilotSessionId, detail);
|
|
13701
|
+
} else if (task) {
|
|
13702
|
+
task.abortController.abort();
|
|
13703
|
+
}
|
|
13704
|
+
deps.recordInteractiveHealthEnd?.(binding.codepilotSessionId, "aborted", detail);
|
|
13705
|
+
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`;
|
|
13367
13706
|
} else {
|
|
13368
13707
|
response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
|
13369
13708
|
}
|
|
@@ -14128,6 +14467,10 @@ function shouldShowStreamLastContentResponseAge(state, nowMs, config2) {
|
|
|
14128
14467
|
const ageMs = getStreamLastContentResponseAgeMs(state, nowMs);
|
|
14129
14468
|
return ageMs != null && ageMs >= Math.max(1e3, config2.heartbeatMs);
|
|
14130
14469
|
}
|
|
14470
|
+
function getVisibleStreamLastContentResponseAgeMs(state, nowMs, config2) {
|
|
14471
|
+
if (!shouldShowStreamLastContentResponseAge(state, nowMs, config2)) return null;
|
|
14472
|
+
return getStreamLastContentResponseAgeMs(state, nowMs);
|
|
14473
|
+
}
|
|
14131
14474
|
function buildStreamRuntimeStatus(state, nowMs, options = {}) {
|
|
14132
14475
|
return formatStreamRuntimeStatus(
|
|
14133
14476
|
Math.max(0, nowMs - state.startedAtMs),
|
|
@@ -14364,11 +14707,20 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14364
14707
|
if (!snapshot) return;
|
|
14365
14708
|
deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, snapshot);
|
|
14366
14709
|
};
|
|
14710
|
+
const getVisibleLastResponseAgeMs = () => getVisibleStreamLastContentResponseAgeMs(
|
|
14711
|
+
streamState,
|
|
14712
|
+
nowMs(),
|
|
14713
|
+
{
|
|
14714
|
+
idleStartMs: streamStatusIdleDetectionStartMs,
|
|
14715
|
+
heartbeatMs: streamStatusHeartbeatMs
|
|
14716
|
+
}
|
|
14717
|
+
);
|
|
14367
14718
|
const pushRunningStatus = (lastResponseAgeMs) => {
|
|
14368
14719
|
if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
|
|
14720
|
+
const effectiveLastResponseAgeMs = lastResponseAgeMs === void 0 ? getVisibleLastResponseAgeMs() : lastResponseAgeMs;
|
|
14369
14721
|
pushStreamFeedbackStatus(
|
|
14370
14722
|
streamFeedbackTarget,
|
|
14371
|
-
|
|
14723
|
+
effectiveLastResponseAgeMs == null ? buildStreamRuntimeStatus(streamState, nowMs()) : formatStreamRuntimeStatus(nowMs() - taskStartedAt, effectiveLastResponseAgeMs, streamState.statusNote)
|
|
14372
14724
|
);
|
|
14373
14725
|
syncStructuredStreamUiSnapshot();
|
|
14374
14726
|
};
|
|
@@ -14455,7 +14807,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14455
14807
|
if (hasStreamingCards) {
|
|
14456
14808
|
pushStreamFeedbackTools(streamFeedbackTarget, Array.from(toolCallTracker.values()));
|
|
14457
14809
|
}
|
|
14458
|
-
pushRunningStatus(
|
|
14810
|
+
pushRunningStatus();
|
|
14459
14811
|
syncStructuredStreamUiSnapshot();
|
|
14460
14812
|
};
|
|
14461
14813
|
const onTaskEvent = (tasks) => {
|
|
@@ -14465,14 +14817,14 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14465
14817
|
if (hasStreamingCards) {
|
|
14466
14818
|
pushStreamFeedbackTasks(streamFeedbackTarget, latestTasks);
|
|
14467
14819
|
}
|
|
14468
|
-
pushRunningStatus(
|
|
14820
|
+
pushRunningStatus();
|
|
14469
14821
|
syncStructuredStreamUiSnapshot();
|
|
14470
14822
|
};
|
|
14471
14823
|
const onStatusNote = (note) => {
|
|
14472
14824
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
14473
14825
|
updateStreamStatusNote(streamState, note, nowMs());
|
|
14474
14826
|
if (streamState.statusNote) markActivity();
|
|
14475
|
-
pushRunningStatus(
|
|
14827
|
+
pushRunningStatus();
|
|
14476
14828
|
syncStructuredStreamUiSnapshot();
|
|
14477
14829
|
};
|
|
14478
14830
|
const onPartialText = (fullText) => {
|
|
@@ -14483,7 +14835,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14483
14835
|
deps.recordInteractiveHealthProgress(binding.codepilotSessionId, "text");
|
|
14484
14836
|
previewOnPartialText?.(fullText);
|
|
14485
14837
|
onStreamCardText?.(fullText);
|
|
14486
|
-
pushRunningStatus(
|
|
14838
|
+
pushRunningStatus();
|
|
14487
14839
|
syncStructuredStreamUiSnapshot();
|
|
14488
14840
|
};
|
|
14489
14841
|
const waitForDesktopTerminalFinalization = async () => {
|
|
@@ -14537,6 +14889,22 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14537
14889
|
let finalOutcome = "failed";
|
|
14538
14890
|
let finalOutcomeDetail;
|
|
14539
14891
|
let shouldRecordHealthEnd = true;
|
|
14892
|
+
let forceStopStarted = false;
|
|
14893
|
+
taskState.forceStop = async (detail = "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002") => {
|
|
14894
|
+
if (forceStopStarted) return true;
|
|
14895
|
+
forceStopStarted = true;
|
|
14896
|
+
finalOutcome = "aborted";
|
|
14897
|
+
finalOutcomeDetail = detail;
|
|
14898
|
+
taskAbort.abort();
|
|
14899
|
+
stopStructuredStreamStatusUpdates();
|
|
14900
|
+
endPreviewOnce();
|
|
14901
|
+
try {
|
|
14902
|
+
await finalizeStreamUiOnce("interrupted", detail);
|
|
14903
|
+
} catch {
|
|
14904
|
+
}
|
|
14905
|
+
endMessageUiOnce();
|
|
14906
|
+
return true;
|
|
14907
|
+
};
|
|
14540
14908
|
try {
|
|
14541
14909
|
const promptText = text2 || (attachments && attachments.length > 0 ? "Describe this image." : "");
|
|
14542
14910
|
const processPromise = processMessageImpl(
|
|
@@ -14559,7 +14927,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14559
14927
|
`\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${perm.toolName} \u7684\u6743\u9650\u786E\u8BA4\u3002`
|
|
14560
14928
|
);
|
|
14561
14929
|
markActivity();
|
|
14562
|
-
pushRunningStatus(
|
|
14930
|
+
pushRunningStatus();
|
|
14563
14931
|
syncStructuredStreamUiSnapshot();
|
|
14564
14932
|
},
|
|
14565
14933
|
taskAbort.signal,
|
|
@@ -14691,7 +15059,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14691
15059
|
if (shouldRecordHealthEnd) {
|
|
14692
15060
|
if (taskAbort.signal.aborted && !externalTerminalRequest) {
|
|
14693
15061
|
finalOutcome = "aborted";
|
|
14694
|
-
finalOutcomeDetail = "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
15062
|
+
finalOutcomeDetail = finalOutcomeDetail || "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
14695
15063
|
}
|
|
14696
15064
|
deps.recordInteractiveHealthEnd(binding.codepilotSessionId, finalOutcome, finalOutcomeDetail);
|
|
14697
15065
|
}
|
|
@@ -14712,6 +15080,13 @@ function isTerminalSessionHealthStatus(status) {
|
|
|
14712
15080
|
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
14713
15081
|
}
|
|
14714
15082
|
function createInteractiveRuntime(getState2, deps) {
|
|
15083
|
+
const sessionLockVersions = /* @__PURE__ */ new Map();
|
|
15084
|
+
function getSessionLockVersion(sessionId) {
|
|
15085
|
+
return sessionLockVersions.get(sessionId) || 0;
|
|
15086
|
+
}
|
|
15087
|
+
function invalidateSessionLockQueue(sessionId) {
|
|
15088
|
+
sessionLockVersions.set(sessionId, getSessionLockVersion(sessionId) + 1);
|
|
15089
|
+
}
|
|
14715
15090
|
function getQueuedCount(sessionId) {
|
|
14716
15091
|
return getState2().queuedCounts.get(sessionId) || 0;
|
|
14717
15092
|
}
|
|
@@ -14758,6 +15133,23 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14758
15133
|
if (!task?.finalizeFromExternalTerminal) return false;
|
|
14759
15134
|
return task.finalizeFromExternalTerminal(outcome, detail, finalText);
|
|
14760
15135
|
}
|
|
15136
|
+
async function forceStopSession(sessionId, detail) {
|
|
15137
|
+
const state = getState2();
|
|
15138
|
+
const task = state.activeTasks.get(sessionId);
|
|
15139
|
+
let handled = false;
|
|
15140
|
+
if (task?.forceStop) {
|
|
15141
|
+
handled = await task.forceStop(detail);
|
|
15142
|
+
} else if (task) {
|
|
15143
|
+
task.abortController.abort();
|
|
15144
|
+
handled = true;
|
|
15145
|
+
}
|
|
15146
|
+
state.activeTasks.delete(sessionId);
|
|
15147
|
+
state.queuedCounts.delete(sessionId);
|
|
15148
|
+
state.sessionLocks.delete(sessionId);
|
|
15149
|
+
invalidateSessionLockQueue(sessionId);
|
|
15150
|
+
syncSessionRuntimeState(sessionId);
|
|
15151
|
+
return handled;
|
|
15152
|
+
}
|
|
14761
15153
|
async function reconcileTerminalSessionRuntimeState() {
|
|
14762
15154
|
const store = deps.getStore();
|
|
14763
15155
|
for (const session of store.listSessions()) {
|
|
@@ -14809,6 +15201,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14809
15201
|
const state = getState2();
|
|
14810
15202
|
const prev = state.sessionLocks.get(sessionId) || Promise.resolve();
|
|
14811
15203
|
const queued = state.sessionLocks.has(sessionId);
|
|
15204
|
+
const lockVersion = getSessionLockVersion(sessionId);
|
|
14812
15205
|
if (queued) {
|
|
14813
15206
|
incrementQueuedCount(sessionId);
|
|
14814
15207
|
}
|
|
@@ -14816,6 +15209,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14816
15209
|
if (queued) {
|
|
14817
15210
|
decrementQueuedCount(sessionId);
|
|
14818
15211
|
}
|
|
15212
|
+
if (getSessionLockVersion(sessionId) !== lockVersion) return;
|
|
14819
15213
|
await fn();
|
|
14820
15214
|
};
|
|
14821
15215
|
const current = prev.then(wrapped, wrapped);
|
|
@@ -14837,6 +15231,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14837
15231
|
releaseInteractiveTask,
|
|
14838
15232
|
syncSessionRuntimeState,
|
|
14839
15233
|
finalizeTerminalActiveTask,
|
|
15234
|
+
forceStopSession,
|
|
14840
15235
|
reconcileTerminalSessionRuntimeState,
|
|
14841
15236
|
resetPersistedInteractiveRuntimeState,
|
|
14842
15237
|
processWithSessionLock
|
|
@@ -15566,9 +15961,16 @@ function createMirrorFeedbackController(deps) {
|
|
|
15566
15961
|
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
15567
15962
|
return;
|
|
15568
15963
|
}
|
|
15964
|
+
const lastContentResponseAtMs = turnState.lastContentResponseAt ? Date.parse(turnState.lastContentResponseAt) : turnState.lastResponseAt ? Date.parse(turnState.lastResponseAt) : null;
|
|
15965
|
+
const streamState = {
|
|
15966
|
+
startedAtMs,
|
|
15967
|
+
lastContentResponseAtMs: Number.isFinite(lastContentResponseAtMs) ? lastContentResponseAtMs : null
|
|
15968
|
+
};
|
|
15969
|
+
const statusConfig = deps.getStructuredStreamStatusConfig?.();
|
|
15970
|
+
const effectiveLastResponseAgeMs = Object.prototype.hasOwnProperty.call(options, "lastResponseAgeMs") ? options.lastResponseAgeMs : statusConfig ? getVisibleStreamLastContentResponseAgeMs(streamState, nowMs, statusConfig) : null;
|
|
15569
15971
|
const statusText = formatStreamRuntimeStatus(
|
|
15570
15972
|
Math.max(0, nowMs - startedAtMs),
|
|
15571
|
-
|
|
15973
|
+
effectiveLastResponseAgeMs,
|
|
15572
15974
|
turnState.statusNote
|
|
15573
15975
|
);
|
|
15574
15976
|
if (turnState.lastStatusText === statusText) return;
|
|
@@ -15967,6 +16369,27 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15967
16369
|
const sdkSessionId = trimOrNull(session.sdk_session_id);
|
|
15968
16370
|
const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
|
|
15969
16371
|
const previousStatus = session.health_status || "idle";
|
|
16372
|
+
if (!isRunningRuntimeStatus(runtimeStatus) && isRunningHealthStatus(previousStatus)) {
|
|
16373
|
+
return {
|
|
16374
|
+
sessionId: session.id,
|
|
16375
|
+
checkedAt: null,
|
|
16376
|
+
runtimeStatus,
|
|
16377
|
+
healthStatus: "idle",
|
|
16378
|
+
healthReason: "\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002",
|
|
16379
|
+
lastProgressAt,
|
|
16380
|
+
lastProgressType,
|
|
16381
|
+
activeToolName,
|
|
16382
|
+
activeToolStartedAt,
|
|
16383
|
+
lastToolFinishedAt,
|
|
16384
|
+
lastStreamUiAttemptAt,
|
|
16385
|
+
lastStreamUiUpdateAt,
|
|
16386
|
+
streamUiFlushStartedAt,
|
|
16387
|
+
lastStreamUiErrorAt,
|
|
16388
|
+
lastStreamUiError,
|
|
16389
|
+
streamUiConsecutiveFailures,
|
|
16390
|
+
sdkSessionId
|
|
16391
|
+
};
|
|
16392
|
+
}
|
|
15970
16393
|
if (!lastProgressMs) {
|
|
15971
16394
|
const fallbackStatus = isRunningRuntimeStatus(runtimeStatus) ? "running_active" : previousStatus;
|
|
15972
16395
|
return {
|
|
@@ -15999,10 +16422,10 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15999
16422
|
);
|
|
16000
16423
|
} else if (idleMs <= HEALTH_RECENT_PROGRESS_MS) {
|
|
16001
16424
|
healthStatus = activeToolName ? "waiting_tool" : "running_active";
|
|
16002
|
-
healthReason = activeToolName ? `\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${activeToolName}\u3002` : "\
|
|
16425
|
+
healthReason = activeToolName ? `\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${activeToolName}\u3002` : "\u8FD1\u671F\u4ECD\u6709\u65B0\u8FDB\u5C55\u3002";
|
|
16003
16426
|
} else if (idleMs <= HEALTH_SLOW_OBSERVED_MS) {
|
|
16004
16427
|
healthStatus = activeToolName ? "waiting_tool" : "slow_observed";
|
|
16005
|
-
healthReason = activeToolName ? `\u5DE5\u5177 ${activeToolName} \u5DF2\u8FD0\u884C\u8F83\u4E45\uFF0C\u4F46\u4ECD\u5728\u89C2\u5BDF\u7A97\u53E3\u5185\u3002` : "\
|
|
16428
|
+
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";
|
|
16006
16429
|
} else {
|
|
16007
16430
|
healthStatus = "suspected_stall";
|
|
16008
16431
|
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";
|
|
@@ -16069,9 +16492,6 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16069
16492
|
return diagnosis;
|
|
16070
16493
|
}
|
|
16071
16494
|
const lastProgressMs = parseIsoMs(diagnosis.lastProgressAt || void 0);
|
|
16072
|
-
if (!lastProgressMs || nowMs - lastProgressMs > HEALTH_RECENT_PROGRESS_MS) {
|
|
16073
|
-
return diagnosis;
|
|
16074
|
-
}
|
|
16075
16495
|
const lastStreamUiUpdateMs = parseIsoMs(diagnosis.lastStreamUiUpdateAt || void 0);
|
|
16076
16496
|
const lastStreamUiAttemptMs = parseIsoMs(diagnosis.lastStreamUiAttemptAt || void 0);
|
|
16077
16497
|
const streamUiFlushStartedMs = parseIsoMs(diagnosis.streamUiFlushStartedAt || void 0);
|
|
@@ -16090,7 +16510,21 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16090
16510
|
healthReason: details.join(" ")
|
|
16091
16511
|
};
|
|
16092
16512
|
}
|
|
16093
|
-
if (
|
|
16513
|
+
if (lastStreamUiAttemptMs && (!lastStreamUiUpdateMs || lastStreamUiAttemptMs - lastStreamUiUpdateMs >= HEALTH_STREAM_UI_STALL_MS)) {
|
|
16514
|
+
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"];
|
|
16515
|
+
if (diagnosis.streamUiConsecutiveFailures > 0) {
|
|
16516
|
+
details.push(`\u6700\u8FD1\u8FDE\u7EED\u5931\u8D25 ${diagnosis.streamUiConsecutiveFailures} \u6B21\u3002`);
|
|
16517
|
+
}
|
|
16518
|
+
if (lastStreamUiErrorText) {
|
|
16519
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
16520
|
+
}
|
|
16521
|
+
return {
|
|
16522
|
+
...diagnosis,
|
|
16523
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
16524
|
+
healthReason: details.join(" ")
|
|
16525
|
+
};
|
|
16526
|
+
}
|
|
16527
|
+
if (lastProgressMs && lastStreamUiUpdateMs && lastProgressMs - lastStreamUiUpdateMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16094
16528
|
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"];
|
|
16095
16529
|
if (lastStreamUiErrorText) {
|
|
16096
16530
|
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
@@ -16101,7 +16535,7 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16101
16535
|
healthReason: details.join(" ")
|
|
16102
16536
|
};
|
|
16103
16537
|
}
|
|
16104
|
-
if (!lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16538
|
+
if (lastProgressMs && !lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16105
16539
|
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"];
|
|
16106
16540
|
if (lastStreamUiErrorText) {
|
|
16107
16541
|
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
@@ -16112,12 +16546,30 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16112
16546
|
healthReason: details.join(" ")
|
|
16113
16547
|
};
|
|
16114
16548
|
}
|
|
16549
|
+
const lastStreamUiActivityMs = Math.max(
|
|
16550
|
+
lastStreamUiAttemptMs || 0,
|
|
16551
|
+
lastStreamUiUpdateMs || 0,
|
|
16552
|
+
streamUiFlushStartedMs || 0
|
|
16553
|
+
);
|
|
16554
|
+
if (lastStreamUiActivityMs > 0 && nowMs - lastStreamUiActivityMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16555
|
+
return {
|
|
16556
|
+
...diagnosis,
|
|
16557
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
16558
|
+
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"
|
|
16559
|
+
};
|
|
16560
|
+
}
|
|
16115
16561
|
return diagnosis;
|
|
16116
16562
|
}
|
|
16117
16563
|
|
|
16118
16564
|
// src/lib/bridge/session-health-runtime.ts
|
|
16119
16565
|
function createSessionHealthRuntime(deps) {
|
|
16120
16566
|
const lastProgressPersistAt = /* @__PURE__ */ new Map();
|
|
16567
|
+
function isTerminalHealthStatus(status) {
|
|
16568
|
+
return status === "completed" || status === "failed" || status === "aborted";
|
|
16569
|
+
}
|
|
16570
|
+
function shouldIgnoreNonStartProgress(session) {
|
|
16571
|
+
return isTerminalHealthStatus(session.health_status);
|
|
16572
|
+
}
|
|
16121
16573
|
function summarizePlanUpdate(tasks) {
|
|
16122
16574
|
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
16123
16575
|
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
@@ -16185,6 +16637,8 @@ function createSessionHealthRuntime(deps) {
|
|
|
16185
16637
|
}
|
|
16186
16638
|
function recordInteractiveProgress(sessionId, type, detail) {
|
|
16187
16639
|
const nowIso4 = deps.nowIso();
|
|
16640
|
+
const session = deps.getStore().getSession(sessionId);
|
|
16641
|
+
if (!session || shouldIgnoreNonStartProgress(session)) return;
|
|
16188
16642
|
maybePersistProgress(sessionId, {
|
|
16189
16643
|
health_status: type === "permission_wait" ? "waiting_tool" : "running_active",
|
|
16190
16644
|
health_reason: buildProgressReason(type, detail),
|
|
@@ -16197,6 +16651,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
16197
16651
|
const store = deps.getStore();
|
|
16198
16652
|
const session = store.getSession(sessionId);
|
|
16199
16653
|
if (!session) return;
|
|
16654
|
+
if (shouldIgnoreNonStartProgress(session)) return;
|
|
16200
16655
|
const activeTools = new Map(
|
|
16201
16656
|
parseActiveToolsJson(session.active_tools_json).map((tool) => [tool.id, tool])
|
|
16202
16657
|
);
|
|
@@ -16489,7 +16944,7 @@ var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
|
16489
16944
|
var DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS = 3e4;
|
|
16490
16945
|
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
16491
16946
|
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
16492
|
-
var MIRROR_TURN_BUFFER_TIMEOUT_MS =
|
|
16947
|
+
var MIRROR_TURN_BUFFER_TIMEOUT_MS = 10 * 6e4;
|
|
16493
16948
|
function describeUnknownError(error) {
|
|
16494
16949
|
if (error instanceof Error) {
|
|
16495
16950
|
return error.stack || `${error.name}: ${error.message}`;
|
|
@@ -16678,6 +17133,7 @@ function getMirrorStructuredStreamStatusConfig() {
|
|
|
16678
17133
|
var MIRROR_FEEDBACK = createMirrorFeedbackController({
|
|
16679
17134
|
getAdapter: (channelType) => getState().adapters.get(channelType) || null,
|
|
16680
17135
|
getThreadTitle: (threadId) => getDesktopThreadTitle(threadId),
|
|
17136
|
+
getStructuredStreamStatusConfig: getMirrorStructuredStreamStatusConfig,
|
|
16681
17137
|
nowIso: nowIso3,
|
|
16682
17138
|
eventBatchLimit: MIRROR_EVENT_BATCH_LIMIT,
|
|
16683
17139
|
deliverResponse
|
|
@@ -17009,6 +17465,8 @@ async function handleMessage(adapter, msg) {
|
|
|
17009
17465
|
async function handleCommand(adapter, msg, text2) {
|
|
17010
17466
|
await handleBridgeCommand(adapter, msg, text2, {
|
|
17011
17467
|
getActiveTask: (sessionId) => INTERACTIVE_RUNTIME.getActiveTask(sessionId),
|
|
17468
|
+
forceStopSession: (sessionId, detail) => INTERACTIVE_RUNTIME.forceStopSession(sessionId, detail),
|
|
17469
|
+
recordInteractiveHealthEnd: (sessionId, outcome, detail) => SESSION_HEALTH_RUNTIME.recordInteractiveEnd(sessionId, outcome, detail),
|
|
17012
17470
|
diagnoseSessionHealth: (sessionId) => SESSION_HEALTH_RUNTIME.diagnoseSessionHealth(sessionId),
|
|
17013
17471
|
diagnoseAllActiveSessions: () => SESSION_HEALTH_RUNTIME.diagnoseAllActiveSessions()
|
|
17014
17472
|
});
|
package/docs/dev-plan.md
CHANGED
|
@@ -18,10 +18,15 @@
|
|
|
18
18
|
|
|
19
19
|
## 当前进度
|
|
20
20
|
|
|
21
|
-
更新时间:2026-04-
|
|
21
|
+
更新时间:2026-04-28
|
|
22
22
|
|
|
23
23
|
已完成:
|
|
24
24
|
|
|
25
|
+
- 追加恢复任务已完成代码实现:`/stop` 改为线程级强制恢复,不重启 bridge。
|
|
26
|
+
- 追加恢复任务已完成代码实现:强制停止会释放 active task、queued count、session lock,并让旧队列任务失效。
|
|
27
|
+
- 追加恢复任务已完成代码实现:飞书卡片 finalize 不再无限等待卡住的流式 flush。
|
|
28
|
+
- 追加恢复任务已完成代码实现:终态 session 不会再被迟到的正文/工具进展覆盖回 running。
|
|
29
|
+
- 追加恢复任务已完成代码实现:健康诊断对 `runtime_status=idle` 但 `health_status=running_*` 的陈旧状态按 idle 展示,仍保持只读。
|
|
25
30
|
- 阶段 1 已完成:新增 turn 类型与 turn 分类器。
|
|
26
31
|
- 阶段 2 已完成主路径:新增 `TurnCoordinator` 与 Desktop terminal router,并接入 mirror runtime。
|
|
27
32
|
- 阶段 3 已完成主路径:新增 `ResponseAssembler` 与 `DeliveryPipeline`,并接入 interactive final 和 mirror final。
|
|
@@ -63,10 +68,36 @@
|
|
|
63
68
|
|
|
64
69
|
下一步:
|
|
65
70
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
71
|
+
- 运行 `npm run typecheck`、`npm test`、必要时 `npm run build`。
|
|
72
|
+
- 验证通过后进入上线前审查、提交发布准备。
|
|
68
73
|
- 后续任何清理仍必须保持 health/status 查询只读,不能把诊断命令当作运行态修复入口。
|
|
69
74
|
|
|
75
|
+
## 追加开发任务:线程级停止与卡住恢复
|
|
76
|
+
|
|
77
|
+
状态:已完成(2026-04-28)
|
|
78
|
+
|
|
79
|
+
任务清单:
|
|
80
|
+
|
|
81
|
+
- `/stop` 不重启 bridge,只针对当前绑定 session 执行强制停止。
|
|
82
|
+
- `/stop` 对内存中没有 active task、但持久化 health 仍显示 running/stall 的 session 也能恢复。
|
|
83
|
+
- 强制停止需要释放 `activeTasks`、`queuedCounts`、`sessionLocks`,并让停止前排队的旧 work 不再继续执行。
|
|
84
|
+
- 强制停止需要写入 `health_status=aborted`,避免出现 `runtime_status=idle` 但 health 仍是 running/stall。
|
|
85
|
+
- 飞书流式卡片收尾前等待 in-flight flush,但等待必须有上限;超时后继续 finalize,避免 IM 卡片永久不结束。
|
|
86
|
+
- 健康检查和线程状态查询继续保持只读,不承担任何修复动作。
|
|
87
|
+
|
|
88
|
+
测试清单:
|
|
89
|
+
|
|
90
|
+
- `session-health-runtime.test.ts`:终态 session 不被迟到 progress/tool 覆盖;陈旧 idle+running health 诊断为 idle 且不落盘。
|
|
91
|
+
- `interactive-runtime.test.ts`:`forceStopSession` 清理运行态;旧排队 work 在 force stop 后不再执行。
|
|
92
|
+
- `command-dispatch.test.ts`:`/stop` 能恢复没有 active task 的疑似卡住 session。
|
|
93
|
+
- `feishu-adapter.test.ts`:卡片 finalize 不会被卡住的 flush 永久阻塞。
|
|
94
|
+
|
|
95
|
+
验证结果:
|
|
96
|
+
|
|
97
|
+
- `npm run typecheck` 通过。
|
|
98
|
+
- `npm test -- --test-name-pattern="session-health-runtime|interactive-runtime|command-dispatch|feishu-adapter|bridge-manager stop handling"` 实际执行全量 359 个测试,全部通过。
|
|
99
|
+
- `npm run build` 通过。
|
|
100
|
+
|
|
70
101
|
## 目标
|
|
71
102
|
|
|
72
103
|
- 明确建模三类 turn:`im_sdk`、`im_desktop_reuse`、`desktop_mirror`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-to-im",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.47",
|
|
4
4
|
"description": "Installable Codex-to-IM bridge with local setup UI and background service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/zhangle1987/codex-to-im#readme",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
44
|
-
"@openai/codex-sdk": "^0.
|
|
44
|
+
"@openai/codex-sdk": "^0.130.0",
|
|
45
45
|
"markdown-it": "^14.1.1",
|
|
46
46
|
"qrcode": "^1.5.4",
|
|
47
47
|
"ws": "^8.18.0"
|