codex-to-im 1.0.45 → 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 +282 -69
- package/docs/dev-plan.md +34 -3
- 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
|
}
|
|
@@ -13360,10 +13458,27 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
13360
13458
|
}
|
|
13361
13459
|
case "/stop": {
|
|
13362
13460
|
const binding = resolve(msg.address);
|
|
13461
|
+
const session = store.getSession(binding.codepilotSessionId);
|
|
13363
13462
|
const task = deps.getActiveTask(binding.codepilotSessionId);
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
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`;
|
|
13367
13482
|
} else {
|
|
13368
13483
|
response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
|
13369
13484
|
}
|
|
@@ -14537,6 +14652,22 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14537
14652
|
let finalOutcome = "failed";
|
|
14538
14653
|
let finalOutcomeDetail;
|
|
14539
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
|
+
};
|
|
14540
14671
|
try {
|
|
14541
14672
|
const promptText = text2 || (attachments && attachments.length > 0 ? "Describe this image." : "");
|
|
14542
14673
|
const processPromise = processMessageImpl(
|
|
@@ -14691,7 +14822,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
14691
14822
|
if (shouldRecordHealthEnd) {
|
|
14692
14823
|
if (taskAbort.signal.aborted && !externalTerminalRequest) {
|
|
14693
14824
|
finalOutcome = "aborted";
|
|
14694
|
-
finalOutcomeDetail = "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
14825
|
+
finalOutcomeDetail = finalOutcomeDetail || "\u4EFB\u52A1\u5DF2\u6536\u5230\u505C\u6B62\u8BF7\u6C42\u3002";
|
|
14695
14826
|
}
|
|
14696
14827
|
deps.recordInteractiveHealthEnd(binding.codepilotSessionId, finalOutcome, finalOutcomeDetail);
|
|
14697
14828
|
}
|
|
@@ -14712,6 +14843,13 @@ function isTerminalSessionHealthStatus(status) {
|
|
|
14712
14843
|
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
14713
14844
|
}
|
|
14714
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
|
+
}
|
|
14715
14853
|
function getQueuedCount(sessionId) {
|
|
14716
14854
|
return getState2().queuedCounts.get(sessionId) || 0;
|
|
14717
14855
|
}
|
|
@@ -14758,6 +14896,23 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14758
14896
|
if (!task?.finalizeFromExternalTerminal) return false;
|
|
14759
14897
|
return task.finalizeFromExternalTerminal(outcome, detail, finalText);
|
|
14760
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
|
+
}
|
|
14761
14916
|
async function reconcileTerminalSessionRuntimeState() {
|
|
14762
14917
|
const store = deps.getStore();
|
|
14763
14918
|
for (const session of store.listSessions()) {
|
|
@@ -14809,6 +14964,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14809
14964
|
const state = getState2();
|
|
14810
14965
|
const prev = state.sessionLocks.get(sessionId) || Promise.resolve();
|
|
14811
14966
|
const queued = state.sessionLocks.has(sessionId);
|
|
14967
|
+
const lockVersion = getSessionLockVersion(sessionId);
|
|
14812
14968
|
if (queued) {
|
|
14813
14969
|
incrementQueuedCount(sessionId);
|
|
14814
14970
|
}
|
|
@@ -14816,6 +14972,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14816
14972
|
if (queued) {
|
|
14817
14973
|
decrementQueuedCount(sessionId);
|
|
14818
14974
|
}
|
|
14975
|
+
if (getSessionLockVersion(sessionId) !== lockVersion) return;
|
|
14819
14976
|
await fn();
|
|
14820
14977
|
};
|
|
14821
14978
|
const current = prev.then(wrapped, wrapped);
|
|
@@ -14837,6 +14994,7 @@ function createInteractiveRuntime(getState2, deps) {
|
|
|
14837
14994
|
releaseInteractiveTask,
|
|
14838
14995
|
syncSessionRuntimeState,
|
|
14839
14996
|
finalizeTerminalActiveTask,
|
|
14997
|
+
forceStopSession,
|
|
14840
14998
|
reconcileTerminalSessionRuntimeState,
|
|
14841
14999
|
resetPersistedInteractiveRuntimeState,
|
|
14842
15000
|
processWithSessionLock
|
|
@@ -15967,6 +16125,27 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15967
16125
|
const sdkSessionId = trimOrNull(session.sdk_session_id);
|
|
15968
16126
|
const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
|
|
15969
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
|
+
}
|
|
15970
16149
|
if (!lastProgressMs) {
|
|
15971
16150
|
const fallbackStatus = isRunningRuntimeStatus(runtimeStatus) ? "running_active" : previousStatus;
|
|
15972
16151
|
return {
|
|
@@ -15999,10 +16178,10 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
15999
16178
|
);
|
|
16000
16179
|
} else if (idleMs <= HEALTH_RECENT_PROGRESS_MS) {
|
|
16001
16180
|
healthStatus = activeToolName ? "waiting_tool" : "running_active";
|
|
16002
|
-
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";
|
|
16003
16182
|
} else if (idleMs <= HEALTH_SLOW_OBSERVED_MS) {
|
|
16004
16183
|
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` : "\
|
|
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";
|
|
16006
16185
|
} else {
|
|
16007
16186
|
healthStatus = "suspected_stall";
|
|
16008
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";
|
|
@@ -16069,9 +16248,6 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16069
16248
|
return diagnosis;
|
|
16070
16249
|
}
|
|
16071
16250
|
const lastProgressMs = parseIsoMs(diagnosis.lastProgressAt || void 0);
|
|
16072
|
-
if (!lastProgressMs || nowMs - lastProgressMs > HEALTH_RECENT_PROGRESS_MS) {
|
|
16073
|
-
return diagnosis;
|
|
16074
|
-
}
|
|
16075
16251
|
const lastStreamUiUpdateMs = parseIsoMs(diagnosis.lastStreamUiUpdateAt || void 0);
|
|
16076
16252
|
const lastStreamUiAttemptMs = parseIsoMs(diagnosis.lastStreamUiAttemptAt || void 0);
|
|
16077
16253
|
const streamUiFlushStartedMs = parseIsoMs(diagnosis.streamUiFlushStartedAt || void 0);
|
|
@@ -16090,7 +16266,21 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16090
16266
|
healthReason: details.join(" ")
|
|
16091
16267
|
};
|
|
16092
16268
|
}
|
|
16093
|
-
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) {
|
|
16094
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"];
|
|
16095
16285
|
if (lastStreamUiErrorText) {
|
|
16096
16286
|
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
@@ -16101,7 +16291,7 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16101
16291
|
healthReason: details.join(" ")
|
|
16102
16292
|
};
|
|
16103
16293
|
}
|
|
16104
|
-
if (!lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16294
|
+
if (lastProgressMs && !lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
16105
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"];
|
|
16106
16296
|
if (lastStreamUiErrorText) {
|
|
16107
16297
|
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
@@ -16112,12 +16302,30 @@ function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
|
16112
16302
|
healthReason: details.join(" ")
|
|
16113
16303
|
};
|
|
16114
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
|
+
}
|
|
16115
16317
|
return diagnosis;
|
|
16116
16318
|
}
|
|
16117
16319
|
|
|
16118
16320
|
// src/lib/bridge/session-health-runtime.ts
|
|
16119
16321
|
function createSessionHealthRuntime(deps) {
|
|
16120
16322
|
const lastProgressPersistAt = /* @__PURE__ */ new Map();
|
|
16323
|
+
function isTerminalHealthStatus(status) {
|
|
16324
|
+
return status === "completed" || status === "failed" || status === "aborted";
|
|
16325
|
+
}
|
|
16326
|
+
function shouldIgnoreNonStartProgress(session) {
|
|
16327
|
+
return isTerminalHealthStatus(session.health_status);
|
|
16328
|
+
}
|
|
16121
16329
|
function summarizePlanUpdate(tasks) {
|
|
16122
16330
|
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
16123
16331
|
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
@@ -16185,6 +16393,8 @@ function createSessionHealthRuntime(deps) {
|
|
|
16185
16393
|
}
|
|
16186
16394
|
function recordInteractiveProgress(sessionId, type, detail) {
|
|
16187
16395
|
const nowIso4 = deps.nowIso();
|
|
16396
|
+
const session = deps.getStore().getSession(sessionId);
|
|
16397
|
+
if (!session || shouldIgnoreNonStartProgress(session)) return;
|
|
16188
16398
|
maybePersistProgress(sessionId, {
|
|
16189
16399
|
health_status: type === "permission_wait" ? "waiting_tool" : "running_active",
|
|
16190
16400
|
health_reason: buildProgressReason(type, detail),
|
|
@@ -16197,6 +16407,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
16197
16407
|
const store = deps.getStore();
|
|
16198
16408
|
const session = store.getSession(sessionId);
|
|
16199
16409
|
if (!session) return;
|
|
16410
|
+
if (shouldIgnoreNonStartProgress(session)) return;
|
|
16200
16411
|
const activeTools = new Map(
|
|
16201
16412
|
parseActiveToolsJson(session.active_tools_json).map((tool) => [tool.id, tool])
|
|
16202
16413
|
);
|
|
@@ -16489,7 +16700,7 @@ var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
|
16489
16700
|
var DESKTOP_TERMINAL_FINALIZATION_TIMEOUT_MS = 3e4;
|
|
16490
16701
|
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
16491
16702
|
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
16492
|
-
var MIRROR_TURN_BUFFER_TIMEOUT_MS =
|
|
16703
|
+
var MIRROR_TURN_BUFFER_TIMEOUT_MS = 10 * 6e4;
|
|
16493
16704
|
function describeUnknownError(error) {
|
|
16494
16705
|
if (error instanceof Error) {
|
|
16495
16706
|
return error.stack || `${error.name}: ${error.message}`;
|
|
@@ -17009,6 +17220,8 @@ async function handleMessage(adapter, msg) {
|
|
|
17009
17220
|
async function handleCommand(adapter, msg, text2) {
|
|
17010
17221
|
await handleBridgeCommand(adapter, msg, text2, {
|
|
17011
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),
|
|
17012
17225
|
diagnoseSessionHealth: (sessionId) => SESSION_HEALTH_RUNTIME.diagnoseSessionHealth(sessionId),
|
|
17013
17226
|
diagnoseAllActiveSessions: () => SESSION_HEALTH_RUNTIME.diagnoseAllActiveSessions()
|
|
17014
17227
|
});
|
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