codex-to-im 1.0.39 → 1.0.41
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 +1020 -138
- package/package.json +2 -2
- package/scripts/patch-codex-sdk-windows-hide.js +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -3959,6 +3959,55 @@ function shouldRetryFreshThread(message) {
|
|
|
3959
3959
|
const lower = message.toLowerCase();
|
|
3960
3960
|
return lower.includes("resuming session with different model") || lower.includes("no such session") || lower.includes("resume") && lower.includes("session");
|
|
3961
3961
|
}
|
|
3962
|
+
function normalizeCodexErrorMessage(message) {
|
|
3963
|
+
const trimmed = (message || "").trim();
|
|
3964
|
+
if (!trimmed) return "Codex \u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
3965
|
+
const lower = trimmed.toLowerCase();
|
|
3966
|
+
if (lower.includes("timeout waiting for child process to exit") || lower.includes("reconnecting...")) {
|
|
3967
|
+
return "Codex \u4F1A\u8BDD\u6062\u590D\u5931\u8D25\uFF0C\u4E0A\u4E00\u8F6E\u6267\u884C\u8FDB\u7A0B\u672A\u6B63\u5E38\u9000\u51FA\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5982\u679C\u8FDE\u7EED\u5931\u8D25\uFF0C\u8BF7\u65B0\u5F00\u7EBF\u7A0B\u6216\u5207\u6362\u5230 `/t 0`\u3002";
|
|
3968
|
+
}
|
|
3969
|
+
return trimmed;
|
|
3970
|
+
}
|
|
3971
|
+
function normalizeTaskText(value) {
|
|
3972
|
+
return typeof value === "string" ? value.trim() : "";
|
|
3973
|
+
}
|
|
3974
|
+
function mapTodoListItems(items) {
|
|
3975
|
+
if (!Array.isArray(items)) return [];
|
|
3976
|
+
const normalized = items.map((item) => ({
|
|
3977
|
+
text: normalizeTaskText(item?.text),
|
|
3978
|
+
completed: item?.completed === true
|
|
3979
|
+
})).filter((item) => item.text);
|
|
3980
|
+
let firstIncompleteSeen = false;
|
|
3981
|
+
return normalized.map((item) => {
|
|
3982
|
+
if (item.completed) {
|
|
3983
|
+
return { text: item.text, status: "completed" };
|
|
3984
|
+
}
|
|
3985
|
+
if (!firstIncompleteSeen) {
|
|
3986
|
+
firstIncompleteSeen = true;
|
|
3987
|
+
return { text: item.text, status: "in_progress" };
|
|
3988
|
+
}
|
|
3989
|
+
return { text: item.text, status: "pending" };
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
function extractMcpContentText(value) {
|
|
3993
|
+
if (!Array.isArray(value)) return "";
|
|
3994
|
+
return value.map((block2) => {
|
|
3995
|
+
if (!block2 || typeof block2 !== "object") return "";
|
|
3996
|
+
const record = block2;
|
|
3997
|
+
if (typeof record.text === "string") return record.text.trim();
|
|
3998
|
+
if (typeof record.content === "string") return record.content.trim();
|
|
3999
|
+
return "";
|
|
4000
|
+
}).filter(Boolean).join("\n\n").trim();
|
|
4001
|
+
}
|
|
4002
|
+
function stringifyUnknown(value) {
|
|
4003
|
+
if (typeof value === "string") return value;
|
|
4004
|
+
if (value == null) return "";
|
|
4005
|
+
try {
|
|
4006
|
+
return JSON.stringify(value);
|
|
4007
|
+
} catch {
|
|
4008
|
+
return String(value);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
3962
4011
|
var MIME_EXT, CodexProvider;
|
|
3963
4012
|
var init_codex_provider = __esm({
|
|
3964
4013
|
"src/codex-provider.ts"() {
|
|
@@ -4047,6 +4096,7 @@ var init_codex_provider = __esm({
|
|
|
4047
4096
|
input = params.prompt;
|
|
4048
4097
|
}
|
|
4049
4098
|
let retryFresh = false;
|
|
4099
|
+
const emittedToolStarts = /* @__PURE__ */ new Set();
|
|
4050
4100
|
while (true) {
|
|
4051
4101
|
let thread;
|
|
4052
4102
|
if (savedThreadId) {
|
|
@@ -4059,6 +4109,7 @@ var init_codex_provider = __esm({
|
|
|
4059
4109
|
thread = codex.startThread(threadOptions);
|
|
4060
4110
|
}
|
|
4061
4111
|
let sawAnyEvent = false;
|
|
4112
|
+
let sawTerminalEvent = false;
|
|
4062
4113
|
try {
|
|
4063
4114
|
const { events } = await thread.runStreamed(input, {
|
|
4064
4115
|
signal: params.abortController?.signal
|
|
@@ -4077,9 +4128,19 @@ var init_codex_provider = __esm({
|
|
|
4077
4128
|
}));
|
|
4078
4129
|
break;
|
|
4079
4130
|
}
|
|
4131
|
+
case "turn.started":
|
|
4132
|
+
break;
|
|
4133
|
+
case "item.started":
|
|
4134
|
+
case "item.updated":
|
|
4080
4135
|
case "item.completed": {
|
|
4081
4136
|
const item = event.item;
|
|
4082
|
-
self.
|
|
4137
|
+
self.handleItemEvent(
|
|
4138
|
+
controller,
|
|
4139
|
+
item,
|
|
4140
|
+
event.type === "item.started" ? "started" : event.type === "item.updated" ? "updated" : "completed",
|
|
4141
|
+
params.sessionId,
|
|
4142
|
+
emittedToolStarts
|
|
4143
|
+
);
|
|
4083
4144
|
break;
|
|
4084
4145
|
}
|
|
4085
4146
|
case "turn.completed": {
|
|
@@ -4093,19 +4154,33 @@ var init_codex_provider = __esm({
|
|
|
4093
4154
|
} : void 0,
|
|
4094
4155
|
...threadId ? { session_id: threadId } : {}
|
|
4095
4156
|
}));
|
|
4157
|
+
sawTerminalEvent = true;
|
|
4096
4158
|
break;
|
|
4097
4159
|
}
|
|
4098
4160
|
case "turn.failed": {
|
|
4099
|
-
const error = event.message;
|
|
4100
|
-
controller.enqueue(sseEvent("error", error || "Turn failed"));
|
|
4161
|
+
const error = event.error?.message;
|
|
4162
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Turn failed")));
|
|
4163
|
+
sawTerminalEvent = true;
|
|
4101
4164
|
break;
|
|
4102
4165
|
}
|
|
4103
4166
|
case "error": {
|
|
4104
4167
|
const error = event.message;
|
|
4105
|
-
controller.enqueue(sseEvent("error", error || "Thread error"));
|
|
4168
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Thread error")));
|
|
4169
|
+
sawTerminalEvent = true;
|
|
4170
|
+
break;
|
|
4171
|
+
}
|
|
4172
|
+
default: {
|
|
4173
|
+
const exhaustiveEvent = event;
|
|
4174
|
+
console.warn(
|
|
4175
|
+
"[codex-provider] Unhandled thread event:",
|
|
4176
|
+
stringifyUnknown(exhaustiveEvent)
|
|
4177
|
+
);
|
|
4106
4178
|
break;
|
|
4107
4179
|
}
|
|
4108
4180
|
}
|
|
4181
|
+
if (sawTerminalEvent) {
|
|
4182
|
+
break;
|
|
4183
|
+
}
|
|
4109
4184
|
}
|
|
4110
4185
|
break;
|
|
4111
4186
|
} catch (err) {
|
|
@@ -4124,7 +4199,7 @@ var init_codex_provider = __esm({
|
|
|
4124
4199
|
const message = err instanceof Error ? err.message : String(err);
|
|
4125
4200
|
console.error("[codex-provider] Error:", err instanceof Error ? err.stack || err.message : err);
|
|
4126
4201
|
try {
|
|
4127
|
-
controller.enqueue(sseEvent("error", message));
|
|
4202
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(message)));
|
|
4128
4203
|
controller.close();
|
|
4129
4204
|
} catch {
|
|
4130
4205
|
}
|
|
@@ -4141,12 +4216,22 @@ var init_codex_provider = __esm({
|
|
|
4141
4216
|
});
|
|
4142
4217
|
}
|
|
4143
4218
|
/**
|
|
4144
|
-
* Map a
|
|
4219
|
+
* Map a Codex item event to SSE events.
|
|
4145
4220
|
*/
|
|
4146
|
-
|
|
4221
|
+
handleItemEvent(controller, item, phase, sessionId, emittedToolStarts) {
|
|
4147
4222
|
const itemType = item.type;
|
|
4223
|
+
const ensureToolUse = (toolId, name, input) => {
|
|
4224
|
+
if (emittedToolStarts.has(toolId)) return;
|
|
4225
|
+
emittedToolStarts.add(toolId);
|
|
4226
|
+
controller.enqueue(sseEvent("tool_use", {
|
|
4227
|
+
id: toolId,
|
|
4228
|
+
name,
|
|
4229
|
+
input
|
|
4230
|
+
}));
|
|
4231
|
+
};
|
|
4148
4232
|
switch (itemType) {
|
|
4149
4233
|
case "agent_message": {
|
|
4234
|
+
if (phase !== "completed") break;
|
|
4150
4235
|
const text2 = item.text || "";
|
|
4151
4236
|
if (text2) {
|
|
4152
4237
|
controller.enqueue(sseEvent("text", text2));
|
|
@@ -4158,12 +4243,11 @@ var init_codex_provider = __esm({
|
|
|
4158
4243
|
const command = item.command || "";
|
|
4159
4244
|
const output = item.aggregated_output || "";
|
|
4160
4245
|
const exitCode = item.exit_code;
|
|
4246
|
+
const status = item.status;
|
|
4161
4247
|
const isError = exitCode != null && exitCode !== 0;
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
input: { command }
|
|
4166
|
-
}));
|
|
4248
|
+
const terminal = phase === "completed" || status === "completed" || status === "failed";
|
|
4249
|
+
ensureToolUse(toolId, "Bash", { command });
|
|
4250
|
+
if (!terminal) break;
|
|
4167
4251
|
const resultContent = output || (isError ? `Exit code: ${exitCode}` : "Done");
|
|
4168
4252
|
controller.enqueue(sseEvent("tool_result", {
|
|
4169
4253
|
tool_use_id: toolId,
|
|
@@ -4173,14 +4257,11 @@ var init_codex_provider = __esm({
|
|
|
4173
4257
|
break;
|
|
4174
4258
|
}
|
|
4175
4259
|
case "file_change": {
|
|
4260
|
+
if (phase !== "completed") break;
|
|
4176
4261
|
const toolId = item.id || `tool-${Date.now()}`;
|
|
4177
4262
|
const changes = item.changes || [];
|
|
4178
4263
|
const summary = changes.map((c) => `${c.kind}: ${c.path}`).join("\n");
|
|
4179
|
-
|
|
4180
|
-
id: toolId,
|
|
4181
|
-
name: "Edit",
|
|
4182
|
-
input: { files: changes }
|
|
4183
|
-
}));
|
|
4264
|
+
ensureToolUse(toolId, "Edit", { files: changes });
|
|
4184
4265
|
controller.enqueue(sseEvent("tool_result", {
|
|
4185
4266
|
tool_use_id: toolId,
|
|
4186
4267
|
content: summary || "File changes applied",
|
|
@@ -4195,13 +4276,11 @@ var init_codex_provider = __esm({
|
|
|
4195
4276
|
const args = item.arguments;
|
|
4196
4277
|
const result = item.result;
|
|
4197
4278
|
const error = item.error;
|
|
4198
|
-
const
|
|
4199
|
-
const
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
input: args
|
|
4204
|
-
}));
|
|
4279
|
+
const status = item.status;
|
|
4280
|
+
const terminal = phase === "completed" || status === "completed" || status === "failed";
|
|
4281
|
+
const resultText = extractMcpContentText(result?.content) || stringifyUnknown(result?.structured_content) || stringifyUnknown(result?.content);
|
|
4282
|
+
ensureToolUse(toolId, `mcp__${server}__${tool}`, args);
|
|
4283
|
+
if (!terminal) break;
|
|
4205
4284
|
controller.enqueue(sseEvent("tool_result", {
|
|
4206
4285
|
tool_use_id: toolId,
|
|
4207
4286
|
content: error?.message || resultText || "Done",
|
|
@@ -4209,6 +4288,18 @@ var init_codex_provider = __esm({
|
|
|
4209
4288
|
}));
|
|
4210
4289
|
break;
|
|
4211
4290
|
}
|
|
4291
|
+
case "web_search": {
|
|
4292
|
+
const toolId = item.id || `tool-${Date.now()}`;
|
|
4293
|
+
const query2 = item.query || "";
|
|
4294
|
+
ensureToolUse(toolId, "Web Search", { query: query2 });
|
|
4295
|
+
if (phase !== "completed") break;
|
|
4296
|
+
controller.enqueue(sseEvent("tool_result", {
|
|
4297
|
+
tool_use_id: toolId,
|
|
4298
|
+
content: query2 || "Search completed",
|
|
4299
|
+
is_error: false
|
|
4300
|
+
}));
|
|
4301
|
+
break;
|
|
4302
|
+
}
|
|
4212
4303
|
case "reasoning": {
|
|
4213
4304
|
const text2 = item.text || "";
|
|
4214
4305
|
if (text2) {
|
|
@@ -4216,8 +4307,33 @@ var init_codex_provider = __esm({
|
|
|
4216
4307
|
}
|
|
4217
4308
|
break;
|
|
4218
4309
|
}
|
|
4310
|
+
case "todo_list": {
|
|
4311
|
+
const tasks = mapTodoListItems(item.items);
|
|
4312
|
+
controller.enqueue(sseEvent("task_update", {
|
|
4313
|
+
session_id: sessionId,
|
|
4314
|
+
sdk_session_id: this.threadIds.get(sessionId) || void 0,
|
|
4315
|
+
tasks,
|
|
4316
|
+
todos: tasks
|
|
4317
|
+
}));
|
|
4318
|
+
break;
|
|
4319
|
+
}
|
|
4320
|
+
case "error": {
|
|
4321
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(item.message || "Codex error")));
|
|
4322
|
+
break;
|
|
4323
|
+
}
|
|
4324
|
+
default: {
|
|
4325
|
+
const exhaustiveItem = item;
|
|
4326
|
+
console.warn(
|
|
4327
|
+
"[codex-provider] Unhandled thread item:",
|
|
4328
|
+
stringifyUnknown(exhaustiveItem)
|
|
4329
|
+
);
|
|
4330
|
+
break;
|
|
4331
|
+
}
|
|
4219
4332
|
}
|
|
4220
4333
|
}
|
|
4334
|
+
handleCompletedItem(controller, item) {
|
|
4335
|
+
this.handleItemEvent(controller, item, "completed", "test-session", /* @__PURE__ */ new Set());
|
|
4336
|
+
}
|
|
4221
4337
|
};
|
|
4222
4338
|
}
|
|
4223
4339
|
});
|
|
@@ -5508,6 +5624,14 @@ function buildToolProgressMarkdown(tools) {
|
|
|
5508
5624
|
});
|
|
5509
5625
|
return lines.join("\n");
|
|
5510
5626
|
}
|
|
5627
|
+
function buildTaskProgressMarkdown(tasks) {
|
|
5628
|
+
if (tasks.length === 0) return "";
|
|
5629
|
+
return tasks.map((task) => {
|
|
5630
|
+
const icon = task.status === "completed" ? "\u2705" : task.status === "in_progress" ? "\u{1F504}" : "\u23F3";
|
|
5631
|
+
const label = task.status === "completed" ? "\u5DF2\u5B8C\u6210" : task.status === "in_progress" ? "\u6267\u884C\u4E2D" : "\u7B49\u5F85\u4E2D";
|
|
5632
|
+
return `${icon} ${task.text}\uFF08${label}\uFF09`;
|
|
5633
|
+
}).join("\n");
|
|
5634
|
+
}
|
|
5511
5635
|
function formatElapsed(ms) {
|
|
5512
5636
|
if (ms < 1e3) return `${ms}ms`;
|
|
5513
5637
|
const sec = ms / 1e3;
|
|
@@ -5522,9 +5646,13 @@ function buildStreamingTextContent(text2) {
|
|
|
5522
5646
|
function buildStreamingToolsContent(tools) {
|
|
5523
5647
|
return buildToolProgressMarkdown(tools);
|
|
5524
5648
|
}
|
|
5525
|
-
function
|
|
5649
|
+
function buildStreamingTaskContent(tasks) {
|
|
5650
|
+
return buildTaskProgressMarkdown(tasks);
|
|
5651
|
+
}
|
|
5652
|
+
function buildFinalCardJson(text2, tasks, tools, footer) {
|
|
5526
5653
|
const elements = [];
|
|
5527
5654
|
const content = preprocessFeishuMarkdown(text2);
|
|
5655
|
+
const taskMd = buildTaskProgressMarkdown(tasks);
|
|
5528
5656
|
const toolMd = buildToolProgressMarkdown(tools);
|
|
5529
5657
|
if (content) {
|
|
5530
5658
|
elements.push({
|
|
@@ -5534,6 +5662,17 @@ function buildFinalCardJson(text2, tools, footer) {
|
|
|
5534
5662
|
text_size: "normal"
|
|
5535
5663
|
});
|
|
5536
5664
|
}
|
|
5665
|
+
if (taskMd) {
|
|
5666
|
+
if (elements.length > 0) {
|
|
5667
|
+
elements.push({ tag: "hr" });
|
|
5668
|
+
}
|
|
5669
|
+
elements.push({
|
|
5670
|
+
tag: "markdown",
|
|
5671
|
+
content: taskMd,
|
|
5672
|
+
text_align: "left",
|
|
5673
|
+
text_size: "normal"
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
5537
5676
|
if (toolMd) {
|
|
5538
5677
|
if (elements.length > 0) {
|
|
5539
5678
|
elements.push({ tag: "hr" });
|
|
@@ -5619,7 +5758,9 @@ var DEDUP_MAX = 1e3;
|
|
|
5619
5758
|
var MAX_FILE_SIZE = 20 * 1024 * 1024;
|
|
5620
5759
|
var TYPING_EMOJI = "Typing";
|
|
5621
5760
|
var CARD_THROTTLE_MS = 200;
|
|
5761
|
+
var CARD_REQUEST_TIMEOUT_MS = 15e3;
|
|
5622
5762
|
var INITIAL_STREAMING_STATUS = "\u5904\u7406\u4E2D";
|
|
5763
|
+
var EMPTY_STREAMING_TASKS = "";
|
|
5623
5764
|
var EMPTY_STREAMING_TOOLS = "";
|
|
5624
5765
|
var MIME_BY_TYPE = {
|
|
5625
5766
|
image: "image/png",
|
|
@@ -5650,6 +5791,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5650
5791
|
cardCreatePromises = /* @__PURE__ */ new Map();
|
|
5651
5792
|
/** Cached tenant token for upload APIs. */
|
|
5652
5793
|
tenantTokenCache = null;
|
|
5794
|
+
cardRequestTimeoutMs = CARD_REQUEST_TIMEOUT_MS;
|
|
5653
5795
|
constructor(instance) {
|
|
5654
5796
|
super();
|
|
5655
5797
|
this.channelType = instance?.id || "feishu";
|
|
@@ -5877,6 +6019,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5877
6019
|
text_size: "normal",
|
|
5878
6020
|
element_id: "streaming_content"
|
|
5879
6021
|
},
|
|
6022
|
+
{
|
|
6023
|
+
tag: "markdown",
|
|
6024
|
+
content: EMPTY_STREAMING_TASKS,
|
|
6025
|
+
text_align: "left",
|
|
6026
|
+
text_size: "normal",
|
|
6027
|
+
element_id: "streaming_tasks"
|
|
6028
|
+
},
|
|
5880
6029
|
{
|
|
5881
6030
|
tag: "markdown",
|
|
5882
6031
|
content: EMPTY_STREAMING_TOOLS,
|
|
@@ -5894,9 +6043,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5894
6043
|
]
|
|
5895
6044
|
}
|
|
5896
6045
|
};
|
|
5897
|
-
const createResp = await cardkit.card.create({
|
|
6046
|
+
const createResp = await this.withFeishuRequestTimeout(cardKey, "card.create", () => cardkit.card.create({
|
|
5898
6047
|
data: { type: "card_json", data: JSON.stringify(cardBody) }
|
|
5899
|
-
});
|
|
6048
|
+
}));
|
|
5900
6049
|
const cardId = createResp?.data?.card_id;
|
|
5901
6050
|
if (!cardId) {
|
|
5902
6051
|
console.warn("[feishu-adapter] Card create returned no card_id");
|
|
@@ -5905,19 +6054,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5905
6054
|
const cardContent = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
5906
6055
|
let msgResp;
|
|
5907
6056
|
if (replyToMessageId) {
|
|
5908
|
-
msgResp = await this.restClient.im.message.reply({
|
|
6057
|
+
msgResp = await this.withFeishuRequestTimeout(cardKey, "im.message.reply:interactive", () => this.restClient.im.message.reply({
|
|
5909
6058
|
path: { message_id: replyToMessageId },
|
|
5910
6059
|
data: { content: cardContent, msg_type: "interactive" }
|
|
5911
|
-
});
|
|
6060
|
+
}));
|
|
5912
6061
|
} else {
|
|
5913
|
-
msgResp = await this.restClient.im.message.create({
|
|
6062
|
+
msgResp = await this.withFeishuRequestTimeout(cardKey, "im.message.create:interactive", () => this.restClient.im.message.create({
|
|
5914
6063
|
params: { receive_id_type: "chat_id" },
|
|
5915
6064
|
data: {
|
|
5916
6065
|
receive_id: chatId,
|
|
5917
6066
|
msg_type: "interactive",
|
|
5918
6067
|
content: cardContent
|
|
5919
6068
|
}
|
|
5920
|
-
});
|
|
6069
|
+
}));
|
|
5921
6070
|
}
|
|
5922
6071
|
const messageId = msgResp?.data?.message_id;
|
|
5923
6072
|
if (!messageId) {
|
|
@@ -5930,17 +6079,25 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5930
6079
|
messageId,
|
|
5931
6080
|
sequence: 0,
|
|
5932
6081
|
startTime: Date.now(),
|
|
6082
|
+
taskItems: [],
|
|
5933
6083
|
toolCalls: [],
|
|
5934
6084
|
thinking: true,
|
|
5935
6085
|
pendingText: null,
|
|
6086
|
+
pendingTasksText: EMPTY_STREAMING_TASKS,
|
|
5936
6087
|
pendingStatusText: INITIAL_STREAMING_STATUS,
|
|
5937
6088
|
renderedText: "\u{1F4AD} Thinking...",
|
|
6089
|
+
renderedTasksText: EMPTY_STREAMING_TASKS,
|
|
5938
6090
|
renderedToolsText: EMPTY_STREAMING_TOOLS,
|
|
5939
6091
|
renderedStatusText: INITIAL_STREAMING_STATUS,
|
|
5940
6092
|
lastUpdateAt: 0,
|
|
5941
6093
|
throttleTimer: null,
|
|
5942
6094
|
flushInFlight: null,
|
|
5943
|
-
flushQueued: false
|
|
6095
|
+
flushQueued: false,
|
|
6096
|
+
lastFlushStartedAt: null,
|
|
6097
|
+
lastSuccessfulFlushAt: null,
|
|
6098
|
+
lastFlushErrorAt: null,
|
|
6099
|
+
lastFlushError: null,
|
|
6100
|
+
consecutiveFlushFailures: 0
|
|
5944
6101
|
});
|
|
5945
6102
|
console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
|
|
5946
6103
|
return true;
|
|
@@ -5969,6 +6126,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5969
6126
|
state.pendingStatusText = statusText || INITIAL_STREAMING_STATUS;
|
|
5970
6127
|
this.scheduleCardFlush(cardKey);
|
|
5971
6128
|
}
|
|
6129
|
+
updateTaskProgress(chatId, tasks, streamKey) {
|
|
6130
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
6131
|
+
const state = this.activeCards.get(cardKey);
|
|
6132
|
+
if (!state) return;
|
|
6133
|
+
state.taskItems = tasks;
|
|
6134
|
+
state.pendingTasksText = buildStreamingTaskContent(tasks) || EMPTY_STREAMING_TASKS;
|
|
6135
|
+
this.scheduleCardFlush(cardKey);
|
|
6136
|
+
}
|
|
5972
6137
|
enqueueCardFlush(streamKey) {
|
|
5973
6138
|
const state = this.activeCards.get(streamKey);
|
|
5974
6139
|
if (!state) return;
|
|
@@ -5976,6 +6141,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5976
6141
|
state.flushQueued = true;
|
|
5977
6142
|
return;
|
|
5978
6143
|
}
|
|
6144
|
+
state.lastFlushStartedAt = Date.now();
|
|
5979
6145
|
state.flushInFlight = this.flushCardUpdate(streamKey).catch((err) => {
|
|
5980
6146
|
console.warn("[feishu-adapter] cardElement.content failed:", err instanceof Error ? err.message : err);
|
|
5981
6147
|
}).finally(() => {
|
|
@@ -6016,6 +6182,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6016
6182
|
const cardkit = this.restClient.cardkit?.v1;
|
|
6017
6183
|
if (!cardkit?.cardElement?.content) return;
|
|
6018
6184
|
const content = buildStreamingTextContent(state.pendingText || "");
|
|
6185
|
+
const tasksText = state.pendingTasksText || EMPTY_STREAMING_TASKS;
|
|
6019
6186
|
const toolsText = buildStreamingToolsContent(state.toolCalls) || EMPTY_STREAMING_TOOLS;
|
|
6020
6187
|
const statusText = state.pendingStatusText || INITIAL_STREAMING_STATUS;
|
|
6021
6188
|
const updates = [];
|
|
@@ -6028,6 +6195,15 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6028
6195
|
}
|
|
6029
6196
|
});
|
|
6030
6197
|
}
|
|
6198
|
+
if (tasksText !== state.renderedTasksText) {
|
|
6199
|
+
updates.push({
|
|
6200
|
+
elementId: "streaming_tasks",
|
|
6201
|
+
content: tasksText,
|
|
6202
|
+
onSuccess: () => {
|
|
6203
|
+
state.renderedTasksText = tasksText;
|
|
6204
|
+
}
|
|
6205
|
+
});
|
|
6206
|
+
}
|
|
6031
6207
|
if (toolsText !== state.renderedToolsText) {
|
|
6032
6208
|
updates.push({
|
|
6033
6209
|
elementId: "streaming_tools",
|
|
@@ -6051,13 +6227,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6051
6227
|
for (const update of updates) {
|
|
6052
6228
|
state.sequence++;
|
|
6053
6229
|
try {
|
|
6054
|
-
await cardkit.cardElement.content({
|
|
6230
|
+
await this.withFeishuRequestTimeout(streamKey, `cardElement.content:${update.elementId}`, () => cardkit.cardElement.content({
|
|
6055
6231
|
path: { card_id: cardId, element_id: update.elementId },
|
|
6056
6232
|
data: { content: update.content, sequence: state.sequence }
|
|
6057
|
-
});
|
|
6233
|
+
}));
|
|
6058
6234
|
update.onSuccess();
|
|
6059
|
-
|
|
6235
|
+
this.markCardFlushSuccess(state);
|
|
6060
6236
|
} catch (err) {
|
|
6237
|
+
this.markCardFlushFailure(state, err);
|
|
6061
6238
|
console.warn(
|
|
6062
6239
|
`[feishu-adapter] cardElement.content failed for ${update.elementId}:`,
|
|
6063
6240
|
err instanceof Error ? err.message : err
|
|
@@ -6118,13 +6295,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6118
6295
|
await this.awaitCardFlushCompletion(cardKey);
|
|
6119
6296
|
try {
|
|
6120
6297
|
state.sequence++;
|
|
6121
|
-
await cardkit.card.settings({
|
|
6298
|
+
await this.withFeishuRequestTimeout(cardKey, "card.settings", () => cardkit.card.settings({
|
|
6122
6299
|
path: { card_id: state.cardId },
|
|
6123
6300
|
data: {
|
|
6124
6301
|
settings: JSON.stringify({ streaming_mode: false }),
|
|
6125
6302
|
sequence: state.sequence
|
|
6126
6303
|
}
|
|
6127
|
-
});
|
|
6304
|
+
}));
|
|
6128
6305
|
const statusLabels = {
|
|
6129
6306
|
completed: "\u2705 Completed",
|
|
6130
6307
|
interrupted: "\u26A0\uFE0F Interrupted",
|
|
@@ -6144,15 +6321,15 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6144
6321
|
|
|
6145
6322
|
${trimmedResponse}`;
|
|
6146
6323
|
}
|
|
6147
|
-
const finalCardJson = buildFinalCardJson(finalText, state.toolCalls, footer);
|
|
6324
|
+
const finalCardJson = buildFinalCardJson(finalText, state.taskItems, state.toolCalls, footer);
|
|
6148
6325
|
state.sequence++;
|
|
6149
|
-
await cardkit.card.update({
|
|
6326
|
+
await this.withFeishuRequestTimeout(cardKey, "card.update", () => cardkit.card.update({
|
|
6150
6327
|
path: { card_id: state.cardId },
|
|
6151
6328
|
data: {
|
|
6152
6329
|
card: { type: "card_json", data: finalCardJson },
|
|
6153
6330
|
sequence: state.sequence
|
|
6154
6331
|
}
|
|
6155
|
-
});
|
|
6332
|
+
}));
|
|
6156
6333
|
console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
|
|
6157
6334
|
return true;
|
|
6158
6335
|
} catch (err) {
|
|
@@ -6184,6 +6361,76 @@ ${trimmedResponse}`;
|
|
|
6184
6361
|
hasActiveStreamingUi(chatId, streamKey) {
|
|
6185
6362
|
return this.hasActiveCard(chatId, streamKey);
|
|
6186
6363
|
}
|
|
6364
|
+
getStructuredStreamingUiSnapshot(chatId, streamKey) {
|
|
6365
|
+
const state = this.activeCards.get(this.resolveStreamKey(chatId, streamKey));
|
|
6366
|
+
if (!state) return null;
|
|
6367
|
+
return {
|
|
6368
|
+
active: true,
|
|
6369
|
+
lastAttemptAt: state.lastFlushStartedAt,
|
|
6370
|
+
lastUpdateAt: state.lastSuccessfulFlushAt ?? (state.lastUpdateAt > 0 ? state.lastUpdateAt : null),
|
|
6371
|
+
lastErrorAt: state.lastFlushErrorAt,
|
|
6372
|
+
lastError: state.lastFlushError,
|
|
6373
|
+
flushInFlight: Boolean(state.flushInFlight),
|
|
6374
|
+
flushInFlightSince: state.flushInFlight ? state.lastFlushStartedAt : null,
|
|
6375
|
+
consecutiveFailures: state.consecutiveFlushFailures
|
|
6376
|
+
};
|
|
6377
|
+
}
|
|
6378
|
+
getCardRequestTimeoutMs() {
|
|
6379
|
+
return Math.max(1, this.cardRequestTimeoutMs);
|
|
6380
|
+
}
|
|
6381
|
+
logRequestOperation(phase, scope, target, startedAt, detail) {
|
|
6382
|
+
const durationMs = Math.max(0, Date.now() - startedAt);
|
|
6383
|
+
const suffix = detail ? `, detail=${detail}` : "";
|
|
6384
|
+
const line = `[feishu-adapter] Request ${phase}: scope=${scope}, target=${target}, duration=${durationMs}ms${suffix}`;
|
|
6385
|
+
if (phase === "start" || phase === "success") {
|
|
6386
|
+
console.log(line);
|
|
6387
|
+
return;
|
|
6388
|
+
}
|
|
6389
|
+
console.warn(line);
|
|
6390
|
+
}
|
|
6391
|
+
async withFeishuRequestTimeout(scope, target, operation) {
|
|
6392
|
+
const startedAt = Date.now();
|
|
6393
|
+
const timeoutMs = this.getCardRequestTimeoutMs();
|
|
6394
|
+
this.logRequestOperation("start", scope, target, startedAt);
|
|
6395
|
+
let timeoutHandle = null;
|
|
6396
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
6397
|
+
timeoutHandle = setTimeout(() => {
|
|
6398
|
+
reject(new Error(`timeout after ${timeoutMs}ms`));
|
|
6399
|
+
}, timeoutMs);
|
|
6400
|
+
});
|
|
6401
|
+
const operationPromise = operation();
|
|
6402
|
+
operationPromise.catch(() => {
|
|
6403
|
+
});
|
|
6404
|
+
try {
|
|
6405
|
+
const result = await Promise.race([operationPromise, timeoutPromise]);
|
|
6406
|
+
this.logRequestOperation("success", scope, target, startedAt);
|
|
6407
|
+
return result;
|
|
6408
|
+
} catch (error) {
|
|
6409
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6410
|
+
this.logRequestOperation(
|
|
6411
|
+
detail.startsWith("timeout after ") ? "timeout" : "error",
|
|
6412
|
+
scope,
|
|
6413
|
+
target,
|
|
6414
|
+
startedAt,
|
|
6415
|
+
detail
|
|
6416
|
+
);
|
|
6417
|
+
throw error;
|
|
6418
|
+
} finally {
|
|
6419
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
6420
|
+
}
|
|
6421
|
+
}
|
|
6422
|
+
markCardFlushFailure(state, error) {
|
|
6423
|
+
state.lastFlushErrorAt = Date.now();
|
|
6424
|
+
state.lastFlushError = error instanceof Error ? error.message : String(error);
|
|
6425
|
+
state.consecutiveFlushFailures += 1;
|
|
6426
|
+
}
|
|
6427
|
+
markCardFlushSuccess(state) {
|
|
6428
|
+
const now2 = Date.now();
|
|
6429
|
+
state.lastUpdateAt = now2;
|
|
6430
|
+
state.lastSuccessfulFlushAt = now2;
|
|
6431
|
+
state.lastFlushError = null;
|
|
6432
|
+
state.consecutiveFlushFailures = 0;
|
|
6433
|
+
}
|
|
6187
6434
|
// ── Streaming adapter interface ────────────────────────────────
|
|
6188
6435
|
/**
|
|
6189
6436
|
* Called by bridge-manager on each text SSE event.
|
|
@@ -6211,8 +6458,30 @@ ${trimmedResponse}`;
|
|
|
6211
6458
|
}
|
|
6212
6459
|
onToolEvent(chatId, tools, streamKey) {
|
|
6213
6460
|
if (!this.isStreamingEnabled()) return;
|
|
6461
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
6462
|
+
if (!this.activeCards.has(cardKey)) {
|
|
6463
|
+
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
6464
|
+
this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
|
|
6465
|
+
if (ok) this.updateToolProgress(chatId, tools, cardKey);
|
|
6466
|
+
}).catch(() => {
|
|
6467
|
+
});
|
|
6468
|
+
return;
|
|
6469
|
+
}
|
|
6214
6470
|
this.updateToolProgress(chatId, tools, streamKey);
|
|
6215
6471
|
}
|
|
6472
|
+
onTaskEvent(chatId, tasks, streamKey) {
|
|
6473
|
+
if (!this.isStreamingEnabled()) return;
|
|
6474
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
6475
|
+
if (!this.activeCards.has(cardKey)) {
|
|
6476
|
+
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
6477
|
+
this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
|
|
6478
|
+
if (ok) this.updateTaskProgress(chatId, tasks, cardKey);
|
|
6479
|
+
}).catch(() => {
|
|
6480
|
+
});
|
|
6481
|
+
return;
|
|
6482
|
+
}
|
|
6483
|
+
this.updateTaskProgress(chatId, tasks, streamKey);
|
|
6484
|
+
}
|
|
6216
6485
|
onStreamStatus(chatId, statusText, streamKey) {
|
|
6217
6486
|
if (!this.isStreamingEnabled()) return;
|
|
6218
6487
|
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
@@ -6362,17 +6631,17 @@ ${trimmedResponse}`;
|
|
|
6362
6631
|
}
|
|
6363
6632
|
async sendStructuredMessage(chatId, msgType, content, replyToMessageId) {
|
|
6364
6633
|
try {
|
|
6365
|
-
const res = replyToMessageId ? await this.restClient.im.message.reply({
|
|
6634
|
+
const res = replyToMessageId ? await this.withFeishuRequestTimeout(chatId, `im.message.reply:${msgType}`, () => this.restClient.im.message.reply({
|
|
6366
6635
|
path: { message_id: replyToMessageId },
|
|
6367
6636
|
data: { msg_type: msgType, content }
|
|
6368
|
-
}) : await this.restClient.im.message.create({
|
|
6637
|
+
})) : await this.withFeishuRequestTimeout(chatId, `im.message.create:${msgType}`, () => this.restClient.im.message.create({
|
|
6369
6638
|
params: { receive_id_type: "chat_id" },
|
|
6370
6639
|
data: {
|
|
6371
6640
|
receive_id: chatId,
|
|
6372
6641
|
msg_type: msgType,
|
|
6373
6642
|
content
|
|
6374
6643
|
}
|
|
6375
|
-
});
|
|
6644
|
+
}));
|
|
6376
6645
|
if (res?.data?.message_id) {
|
|
6377
6646
|
return { ok: true, messageId: res.data.message_id };
|
|
6378
6647
|
}
|
|
@@ -6388,14 +6657,14 @@ ${trimmedResponse}`;
|
|
|
6388
6657
|
async sendAsCard(chatId, text2) {
|
|
6389
6658
|
const cardContent = buildCardContent(text2);
|
|
6390
6659
|
try {
|
|
6391
|
-
const res = await this.restClient.im.message.create({
|
|
6660
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:interactive-card", () => this.restClient.im.message.create({
|
|
6392
6661
|
params: { receive_id_type: "chat_id" },
|
|
6393
6662
|
data: {
|
|
6394
6663
|
receive_id: chatId,
|
|
6395
6664
|
msg_type: "interactive",
|
|
6396
6665
|
content: cardContent
|
|
6397
6666
|
}
|
|
6398
|
-
});
|
|
6667
|
+
}));
|
|
6399
6668
|
if (res?.data?.message_id) {
|
|
6400
6669
|
return { ok: true, messageId: res.data.message_id };
|
|
6401
6670
|
}
|
|
@@ -6412,14 +6681,14 @@ ${trimmedResponse}`;
|
|
|
6412
6681
|
async sendAsPost(chatId, text2) {
|
|
6413
6682
|
const postContent = buildPostContent(text2);
|
|
6414
6683
|
try {
|
|
6415
|
-
const res = await this.restClient.im.message.create({
|
|
6684
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:post", () => this.restClient.im.message.create({
|
|
6416
6685
|
params: { receive_id_type: "chat_id" },
|
|
6417
6686
|
data: {
|
|
6418
6687
|
receive_id: chatId,
|
|
6419
6688
|
msg_type: "post",
|
|
6420
6689
|
content: postContent
|
|
6421
6690
|
}
|
|
6422
|
-
});
|
|
6691
|
+
}));
|
|
6423
6692
|
if (res?.data?.message_id) {
|
|
6424
6693
|
return { ok: true, messageId: res.data.message_id };
|
|
6425
6694
|
}
|
|
@@ -6431,14 +6700,14 @@ ${trimmedResponse}`;
|
|
|
6431
6700
|
}
|
|
6432
6701
|
async sendAsPlainText(chatId, text2) {
|
|
6433
6702
|
try {
|
|
6434
|
-
const res = await this.restClient.im.message.create({
|
|
6703
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:text", () => this.restClient.im.message.create({
|
|
6435
6704
|
params: { receive_id_type: "chat_id" },
|
|
6436
6705
|
data: {
|
|
6437
6706
|
receive_id: chatId,
|
|
6438
6707
|
msg_type: "text",
|
|
6439
6708
|
content: JSON.stringify({ text: text2 })
|
|
6440
6709
|
}
|
|
6441
|
-
});
|
|
6710
|
+
}));
|
|
6442
6711
|
if (res?.data?.message_id) {
|
|
6443
6712
|
return { ok: true, messageId: res.data.message_id };
|
|
6444
6713
|
}
|
|
@@ -6463,14 +6732,14 @@ ${trimmedResponse}`;
|
|
|
6463
6732
|
if (permId) {
|
|
6464
6733
|
const cardJson2 = buildPermissionButtonCard(mdText, permId, chatId);
|
|
6465
6734
|
try {
|
|
6466
|
-
const res = await this.restClient.im.message.create({
|
|
6735
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-button-card", () => this.restClient.im.message.create({
|
|
6467
6736
|
params: { receive_id_type: "chat_id" },
|
|
6468
6737
|
data: {
|
|
6469
6738
|
receive_id: chatId,
|
|
6470
6739
|
msg_type: "interactive",
|
|
6471
6740
|
content: cardJson2
|
|
6472
6741
|
}
|
|
6473
|
-
});
|
|
6742
|
+
}));
|
|
6474
6743
|
if (res?.data?.message_id) {
|
|
6475
6744
|
return { ok: true, messageId: res.data.message_id };
|
|
6476
6745
|
}
|
|
@@ -6514,14 +6783,14 @@ ${trimmedResponse}`;
|
|
|
6514
6783
|
}
|
|
6515
6784
|
});
|
|
6516
6785
|
try {
|
|
6517
|
-
const res = await this.restClient.im.message.create({
|
|
6786
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-fallback-card", () => this.restClient.im.message.create({
|
|
6518
6787
|
params: { receive_id_type: "chat_id" },
|
|
6519
6788
|
data: {
|
|
6520
6789
|
receive_id: chatId,
|
|
6521
6790
|
msg_type: "interactive",
|
|
6522
6791
|
content: cardJson
|
|
6523
6792
|
}
|
|
6524
|
-
});
|
|
6793
|
+
}));
|
|
6525
6794
|
if (res?.data?.message_id) {
|
|
6526
6795
|
return { ok: true, messageId: res.data.message_id };
|
|
6527
6796
|
}
|
|
@@ -6538,14 +6807,14 @@ ${trimmedResponse}`;
|
|
|
6538
6807
|
...permCommands
|
|
6539
6808
|
].join("\n");
|
|
6540
6809
|
try {
|
|
6541
|
-
const res = await this.restClient.im.message.create({
|
|
6810
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-fallback-text", () => this.restClient.im.message.create({
|
|
6542
6811
|
params: { receive_id_type: "chat_id" },
|
|
6543
6812
|
data: {
|
|
6544
6813
|
receive_id: chatId,
|
|
6545
6814
|
msg_type: "text",
|
|
6546
6815
|
content: JSON.stringify({ text: plainText })
|
|
6547
6816
|
}
|
|
6548
|
-
});
|
|
6817
|
+
}));
|
|
6549
6818
|
if (res?.data?.message_id) {
|
|
6550
6819
|
return { ok: true, messageId: res.data.message_id };
|
|
6551
6820
|
}
|
|
@@ -15021,6 +15290,54 @@ function extractNormalizedStructuredText(value) {
|
|
|
15021
15290
|
collectStructuredTextParts(value, parts);
|
|
15022
15291
|
return parts.length > 0 ? normalizeStructuredText(parts.join("\n\n")) : "";
|
|
15023
15292
|
}
|
|
15293
|
+
function parseJsonSafely(value) {
|
|
15294
|
+
if (!value) return null;
|
|
15295
|
+
try {
|
|
15296
|
+
return JSON.parse(value);
|
|
15297
|
+
} catch {
|
|
15298
|
+
return null;
|
|
15299
|
+
}
|
|
15300
|
+
}
|
|
15301
|
+
function normalizeTaskStatus(value) {
|
|
15302
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
15303
|
+
if (normalized === "in_progress" || normalized === "running" || normalized === "active") {
|
|
15304
|
+
return "in_progress";
|
|
15305
|
+
}
|
|
15306
|
+
if (normalized === "completed" || normalized === "complete" || normalized === "done") {
|
|
15307
|
+
return "completed";
|
|
15308
|
+
}
|
|
15309
|
+
return "pending";
|
|
15310
|
+
}
|
|
15311
|
+
function parseTaskProgressItems(value) {
|
|
15312
|
+
if (!Array.isArray(value)) return [];
|
|
15313
|
+
return value.map((item) => {
|
|
15314
|
+
const record = item;
|
|
15315
|
+
const text2 = extractNormalizedStructuredText(record.text ?? record.step);
|
|
15316
|
+
if (!text2) return null;
|
|
15317
|
+
return {
|
|
15318
|
+
text: text2,
|
|
15319
|
+
status: normalizeTaskStatus(record.status)
|
|
15320
|
+
};
|
|
15321
|
+
}).filter((item) => Boolean(item));
|
|
15322
|
+
}
|
|
15323
|
+
function parseUpdatePlanTasks(argumentsJson) {
|
|
15324
|
+
const parsed = parseJsonSafely(argumentsJson);
|
|
15325
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
15326
|
+
return parseTaskProgressItems(parsed.plan ?? parsed.tasks);
|
|
15327
|
+
}
|
|
15328
|
+
function extractToolOutputText(value) {
|
|
15329
|
+
if (typeof value !== "string") return extractNormalizedFreeText(value);
|
|
15330
|
+
const trimmed = value.trim();
|
|
15331
|
+
if (!trimmed) return "";
|
|
15332
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
15333
|
+
const parsed = parseJsonSafely(trimmed);
|
|
15334
|
+
if (parsed && typeof parsed === "object") {
|
|
15335
|
+
const extracted = extractNormalizedFreeText(parsed.output ?? parsed);
|
|
15336
|
+
if (extracted) return extracted;
|
|
15337
|
+
}
|
|
15338
|
+
}
|
|
15339
|
+
return extractNormalizedFreeText(value);
|
|
15340
|
+
}
|
|
15024
15341
|
function createDesktopEventSignature(rawLine) {
|
|
15025
15342
|
return crypto7.createHash("sha1").update(rawLine).digest("hex");
|
|
15026
15343
|
}
|
|
@@ -15052,6 +15369,17 @@ function isSessionMessageLine(line) {
|
|
|
15052
15369
|
function isTurnContextLine(line) {
|
|
15053
15370
|
return line.type === "turn_context";
|
|
15054
15371
|
}
|
|
15372
|
+
function describeUnhandledMirrorLineKind(line) {
|
|
15373
|
+
if (isSessionEventLine(line)) {
|
|
15374
|
+
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
15375
|
+
return `event_msg:${payloadType || "<unknown>"}`;
|
|
15376
|
+
}
|
|
15377
|
+
if (isSessionMessageLine(line)) {
|
|
15378
|
+
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
15379
|
+
return `response_item:${payloadType || "<unknown>"}`;
|
|
15380
|
+
}
|
|
15381
|
+
return null;
|
|
15382
|
+
}
|
|
15055
15383
|
function listDesktopSessions(limit) {
|
|
15056
15384
|
const root = getCodexSessionsRoot();
|
|
15057
15385
|
if (!fs4.existsSync(root)) return [];
|
|
@@ -15144,81 +15472,213 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
|
|
|
15144
15472
|
});
|
|
15145
15473
|
}
|
|
15146
15474
|
}
|
|
15147
|
-
function pushDesktopMirrorRecord(records, parsed, rawLine, activeTurnId) {
|
|
15148
|
-
if (isSessionEventLine(parsed)
|
|
15475
|
+
function pushDesktopMirrorRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds) {
|
|
15476
|
+
if (isSessionEventLine(parsed)) {
|
|
15477
|
+
return pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId);
|
|
15478
|
+
}
|
|
15479
|
+
if (isSessionMessageLine(parsed)) {
|
|
15480
|
+
return pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds);
|
|
15481
|
+
}
|
|
15482
|
+
return false;
|
|
15483
|
+
}
|
|
15484
|
+
function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
|
|
15485
|
+
const signature = createDesktopEventSignature(rawLine);
|
|
15486
|
+
const timestamp = parsed.timestamp || "";
|
|
15487
|
+
if (parsed.payload?.type === "task_started") {
|
|
15149
15488
|
records.push({
|
|
15150
|
-
signature
|
|
15489
|
+
signature,
|
|
15151
15490
|
type: "task_started",
|
|
15152
15491
|
content: "",
|
|
15153
|
-
timestamp
|
|
15492
|
+
timestamp,
|
|
15154
15493
|
turnId: parsed.payload.turn_id || ""
|
|
15155
15494
|
});
|
|
15156
|
-
return;
|
|
15495
|
+
return true;
|
|
15157
15496
|
}
|
|
15158
|
-
if (
|
|
15497
|
+
if (parsed.payload?.type === "turn_aborted") {
|
|
15498
|
+
records.push({
|
|
15499
|
+
signature,
|
|
15500
|
+
type: "task_aborted",
|
|
15501
|
+
content: extractNormalizedStructuredText(parsed.payload.reason),
|
|
15502
|
+
timestamp,
|
|
15503
|
+
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15504
|
+
});
|
|
15505
|
+
return true;
|
|
15506
|
+
}
|
|
15507
|
+
if (parsed.payload?.type === "agent_reasoning") {
|
|
15508
|
+
const text2 = extractNormalizedStructuredText(parsed.payload.text);
|
|
15509
|
+
if (!text2) return true;
|
|
15510
|
+
records.push({
|
|
15511
|
+
signature,
|
|
15512
|
+
type: "reasoning",
|
|
15513
|
+
content: text2,
|
|
15514
|
+
timestamp,
|
|
15515
|
+
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15516
|
+
});
|
|
15517
|
+
return true;
|
|
15518
|
+
}
|
|
15519
|
+
if (parsed.payload?.type === "web_search_end") {
|
|
15520
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15521
|
+
records.push({
|
|
15522
|
+
signature,
|
|
15523
|
+
type: "tool_finished",
|
|
15524
|
+
content: extractNormalizedStructuredText(parsed.payload.query),
|
|
15525
|
+
timestamp,
|
|
15526
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15527
|
+
toolId,
|
|
15528
|
+
toolName: "Web Search"
|
|
15529
|
+
});
|
|
15530
|
+
return true;
|
|
15531
|
+
}
|
|
15532
|
+
if (parsed.payload?.type === "mcp_tool_call_end") {
|
|
15533
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15534
|
+
const server = extractNormalizedFreeText(parsed.payload.invocation?.server);
|
|
15535
|
+
const tool = extractNormalizedFreeText(parsed.payload.invocation?.tool);
|
|
15536
|
+
const toolName = server && tool ? `mcp__${server}__${tool}` : "mcp_tool_call";
|
|
15537
|
+
records.push({
|
|
15538
|
+
signature,
|
|
15539
|
+
type: "tool_finished",
|
|
15540
|
+
content: "",
|
|
15541
|
+
timestamp,
|
|
15542
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15543
|
+
toolId,
|
|
15544
|
+
toolName,
|
|
15545
|
+
isError: false
|
|
15546
|
+
});
|
|
15547
|
+
return true;
|
|
15548
|
+
}
|
|
15549
|
+
if (parsed.payload?.type === "user_message") {
|
|
15159
15550
|
const text2 = extractNormalizedStructuredText(parsed.payload.message);
|
|
15160
|
-
if (!text2) return;
|
|
15551
|
+
if (!text2) return true;
|
|
15161
15552
|
records.push({
|
|
15162
|
-
signature
|
|
15553
|
+
signature,
|
|
15163
15554
|
type: "message",
|
|
15164
15555
|
role: "user",
|
|
15165
15556
|
content: text2,
|
|
15166
|
-
timestamp
|
|
15557
|
+
timestamp,
|
|
15167
15558
|
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15168
15559
|
});
|
|
15169
|
-
return;
|
|
15560
|
+
return true;
|
|
15170
15561
|
}
|
|
15171
|
-
if (
|
|
15562
|
+
if (parsed.payload?.type === "task_complete") {
|
|
15172
15563
|
records.push({
|
|
15173
|
-
signature
|
|
15564
|
+
signature,
|
|
15174
15565
|
type: "task_complete",
|
|
15175
15566
|
role: "assistant",
|
|
15176
15567
|
content: extractNormalizedStructuredText(parsed.payload.last_agent_message),
|
|
15177
|
-
timestamp
|
|
15568
|
+
timestamp,
|
|
15178
15569
|
turnId: parsed.payload.turn_id || ""
|
|
15179
15570
|
});
|
|
15180
|
-
return;
|
|
15571
|
+
return true;
|
|
15181
15572
|
}
|
|
15182
|
-
|
|
15573
|
+
return false;
|
|
15574
|
+
}
|
|
15575
|
+
function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds) {
|
|
15576
|
+
const signature = createDesktopEventSignature(rawLine);
|
|
15577
|
+
const timestamp = parsed.timestamp || "";
|
|
15578
|
+
if (parsed.payload?.type === "message" && parsed.payload.role === "assistant") {
|
|
15183
15579
|
const text2 = extractDesktopMessageText(parsed);
|
|
15184
|
-
if (!text2) return;
|
|
15580
|
+
if (!text2) return true;
|
|
15185
15581
|
records.push({
|
|
15186
|
-
signature
|
|
15582
|
+
signature,
|
|
15187
15583
|
type: "message",
|
|
15188
15584
|
role: parsed.payload.phase === "commentary" ? "commentary" : "assistant",
|
|
15189
15585
|
content: parsed.payload.phase === "commentary" ? text2.replace(/^\[commentary\]\n/, "") : text2,
|
|
15190
|
-
timestamp
|
|
15586
|
+
timestamp,
|
|
15191
15587
|
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15192
15588
|
});
|
|
15193
|
-
return;
|
|
15589
|
+
return true;
|
|
15194
15590
|
}
|
|
15195
|
-
if (
|
|
15591
|
+
if (parsed.payload?.type === "function_call") {
|
|
15196
15592
|
const toolName = extractNormalizedFreeText(parsed.payload.name);
|
|
15197
|
-
const toolId = extractNormalizedFreeText(parsed.payload.call_id) ||
|
|
15198
|
-
if (!toolName) return;
|
|
15593
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15594
|
+
if (!toolName) return true;
|
|
15595
|
+
if (toolName === "update_plan") {
|
|
15596
|
+
const tasks = parseUpdatePlanTasks(parsed.payload.arguments);
|
|
15597
|
+
activeSpecialCallIds.add(toolId);
|
|
15598
|
+
records.push({
|
|
15599
|
+
signature,
|
|
15600
|
+
type: "plan_update",
|
|
15601
|
+
content: "",
|
|
15602
|
+
timestamp,
|
|
15603
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15604
|
+
tasks
|
|
15605
|
+
});
|
|
15606
|
+
return true;
|
|
15607
|
+
}
|
|
15199
15608
|
records.push({
|
|
15200
|
-
signature
|
|
15609
|
+
signature,
|
|
15201
15610
|
type: "tool_started",
|
|
15202
15611
|
content: "",
|
|
15203
|
-
timestamp
|
|
15612
|
+
timestamp,
|
|
15204
15613
|
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15205
15614
|
toolId,
|
|
15206
15615
|
toolName
|
|
15207
15616
|
});
|
|
15208
|
-
return;
|
|
15617
|
+
return true;
|
|
15618
|
+
}
|
|
15619
|
+
if (parsed.payload?.type === "custom_tool_call") {
|
|
15620
|
+
const toolName = extractNormalizedFreeText(parsed.payload.name);
|
|
15621
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15622
|
+
if (!toolName) return true;
|
|
15623
|
+
if (toolName === "update_plan") {
|
|
15624
|
+
const tasks = parseUpdatePlanTasks(typeof parsed.payload.input === "string" ? parsed.payload.input : void 0);
|
|
15625
|
+
activeSpecialCallIds.add(toolId);
|
|
15626
|
+
records.push({
|
|
15627
|
+
signature,
|
|
15628
|
+
type: "plan_update",
|
|
15629
|
+
content: "",
|
|
15630
|
+
timestamp,
|
|
15631
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15632
|
+
tasks
|
|
15633
|
+
});
|
|
15634
|
+
return true;
|
|
15635
|
+
}
|
|
15636
|
+
records.push({
|
|
15637
|
+
signature,
|
|
15638
|
+
type: "tool_started",
|
|
15639
|
+
content: "",
|
|
15640
|
+
timestamp,
|
|
15641
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15642
|
+
toolId,
|
|
15643
|
+
toolName
|
|
15644
|
+
});
|
|
15645
|
+
return true;
|
|
15209
15646
|
}
|
|
15210
|
-
if (
|
|
15211
|
-
const toolId = extractNormalizedFreeText(parsed.payload.call_id) ||
|
|
15647
|
+
if (parsed.payload?.type === "function_call_output") {
|
|
15648
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15649
|
+
if (activeSpecialCallIds.has(toolId)) {
|
|
15650
|
+
activeSpecialCallIds.delete(toolId);
|
|
15651
|
+
return true;
|
|
15652
|
+
}
|
|
15212
15653
|
records.push({
|
|
15213
|
-
signature
|
|
15654
|
+
signature,
|
|
15655
|
+
type: "tool_finished",
|
|
15656
|
+
content: extractToolOutputText(parsed.payload.output),
|
|
15657
|
+
timestamp,
|
|
15658
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15659
|
+
toolId,
|
|
15660
|
+
isError: parsed.payload.is_error === true
|
|
15661
|
+
});
|
|
15662
|
+
return true;
|
|
15663
|
+
}
|
|
15664
|
+
if (parsed.payload?.type === "custom_tool_call_output") {
|
|
15665
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15666
|
+
if (activeSpecialCallIds.has(toolId)) {
|
|
15667
|
+
activeSpecialCallIds.delete(toolId);
|
|
15668
|
+
return true;
|
|
15669
|
+
}
|
|
15670
|
+
records.push({
|
|
15671
|
+
signature,
|
|
15214
15672
|
type: "tool_finished",
|
|
15215
|
-
content:
|
|
15216
|
-
timestamp
|
|
15673
|
+
content: extractToolOutputText(parsed.payload.output),
|
|
15674
|
+
timestamp,
|
|
15217
15675
|
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15218
15676
|
toolId,
|
|
15219
15677
|
isError: parsed.payload.is_error === true
|
|
15220
15678
|
});
|
|
15679
|
+
return true;
|
|
15221
15680
|
}
|
|
15681
|
+
return false;
|
|
15222
15682
|
}
|
|
15223
15683
|
function parseDesktopSessionEventText(content, leadingText = "", flushTrailingText = false) {
|
|
15224
15684
|
const combined = `${leadingText}${content}`;
|
|
@@ -15254,14 +15714,16 @@ function parseDesktopSessionEventText(content, leadingText = "", flushTrailingTe
|
|
|
15254
15714
|
trailingText
|
|
15255
15715
|
};
|
|
15256
15716
|
}
|
|
15257
|
-
function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false, initialTurnId = null) {
|
|
15717
|
+
function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false, initialTurnId = null, initialSpecialCallIds = []) {
|
|
15258
15718
|
const combined = `${leadingText}${content}`;
|
|
15259
15719
|
if (!combined) {
|
|
15260
15720
|
return {
|
|
15261
15721
|
records: [],
|
|
15262
15722
|
nextOffset: 0,
|
|
15263
15723
|
trailingText: "",
|
|
15264
|
-
nextTurnId: initialTurnId
|
|
15724
|
+
nextTurnId: initialTurnId,
|
|
15725
|
+
nextSpecialCallIds: [],
|
|
15726
|
+
unknownKinds: []
|
|
15265
15727
|
};
|
|
15266
15728
|
}
|
|
15267
15729
|
const hasTrailingNewline = combined.endsWith("\n") || combined.endsWith("\r");
|
|
@@ -15273,6 +15735,8 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
|
|
|
15273
15735
|
}
|
|
15274
15736
|
const records = [];
|
|
15275
15737
|
let activeTurnId = initialTurnId;
|
|
15738
|
+
const activeSpecialCallIds = new Set(initialSpecialCallIds);
|
|
15739
|
+
const unknownKinds = /* @__PURE__ */ new Set();
|
|
15276
15740
|
for (const line of rawLines) {
|
|
15277
15741
|
const trimmed = line.trim();
|
|
15278
15742
|
if (!trimmed) continue;
|
|
@@ -15290,20 +15754,27 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
|
|
|
15290
15754
|
const eventPayload = parsed.payload;
|
|
15291
15755
|
activeTurnId = eventPayload?.turn_id || activeTurnId;
|
|
15292
15756
|
}
|
|
15293
|
-
pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId);
|
|
15294
|
-
if (
|
|
15757
|
+
const handled = pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId, activeSpecialCallIds);
|
|
15758
|
+
if (!handled) {
|
|
15759
|
+
const unknownKind = describeUnhandledMirrorLineKind(parsed);
|
|
15760
|
+
if (unknownKind) unknownKinds.add(unknownKind);
|
|
15761
|
+
}
|
|
15762
|
+
if (isSessionEventLine(parsed) && (parsed.payload?.type === "task_complete" || parsed.payload?.type === "turn_aborted")) {
|
|
15295
15763
|
const eventPayload = parsed.payload;
|
|
15296
15764
|
const completedTurnId = eventPayload?.turn_id || activeTurnId;
|
|
15297
15765
|
if (!completedTurnId || completedTurnId === activeTurnId) {
|
|
15298
15766
|
activeTurnId = null;
|
|
15299
15767
|
}
|
|
15768
|
+
activeSpecialCallIds.clear();
|
|
15300
15769
|
}
|
|
15301
15770
|
}
|
|
15302
15771
|
return {
|
|
15303
15772
|
records,
|
|
15304
15773
|
nextOffset: 0,
|
|
15305
15774
|
trailingText,
|
|
15306
|
-
nextTurnId: activeTurnId
|
|
15775
|
+
nextTurnId: activeTurnId,
|
|
15776
|
+
nextSpecialCallIds: Array.from(activeSpecialCallIds),
|
|
15777
|
+
unknownKinds: Array.from(unknownKinds)
|
|
15307
15778
|
};
|
|
15308
15779
|
}
|
|
15309
15780
|
function readDesktopSessionMessages(threadId, limit = 8) {
|
|
@@ -15324,7 +15795,7 @@ function readDesktopSessionEventStreamByFilePath(filePath) {
|
|
|
15324
15795
|
}
|
|
15325
15796
|
return parseDesktopSessionEventText(content, "", true).events;
|
|
15326
15797
|
}
|
|
15327
|
-
function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null) {
|
|
15798
|
+
function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null, currentSpecialCallIds = []) {
|
|
15328
15799
|
let content = "";
|
|
15329
15800
|
try {
|
|
15330
15801
|
content = readFileUtf8Range(filePath, startOffset, endOffset);
|
|
@@ -15333,15 +15804,19 @@ function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, en
|
|
|
15333
15804
|
records: [],
|
|
15334
15805
|
nextOffset: startOffset,
|
|
15335
15806
|
trailingText,
|
|
15336
|
-
nextTurnId: currentTurnId
|
|
15807
|
+
nextTurnId: currentTurnId,
|
|
15808
|
+
nextSpecialCallIds: Array.from(currentSpecialCallIds),
|
|
15809
|
+
unknownKinds: []
|
|
15337
15810
|
};
|
|
15338
15811
|
}
|
|
15339
|
-
const parsed = parseDesktopMirrorRecordText(content, trailingText, false, currentTurnId);
|
|
15812
|
+
const parsed = parseDesktopMirrorRecordText(content, trailingText, false, currentTurnId, currentSpecialCallIds);
|
|
15340
15813
|
return {
|
|
15341
15814
|
records: parsed.records,
|
|
15342
15815
|
nextOffset: Math.max(startOffset, endOffset),
|
|
15343
15816
|
trailingText: parsed.trailingText,
|
|
15344
|
-
nextTurnId: parsed.nextTurnId
|
|
15817
|
+
nextTurnId: parsed.nextTurnId,
|
|
15818
|
+
nextSpecialCallIds: parsed.nextSpecialCallIds,
|
|
15819
|
+
unknownKinds: parsed.unknownKinds
|
|
15345
15820
|
};
|
|
15346
15821
|
}
|
|
15347
15822
|
function readDesktopSessionEventStream(threadId) {
|
|
@@ -16446,6 +16921,8 @@ function formatHealthStatusLabel(healthStatus) {
|
|
|
16446
16921
|
return "\u957F\u65F6\u8FD0\u884C\uFF0C\u5F85\u89C2\u5BDF";
|
|
16447
16922
|
case "suspected_stall":
|
|
16448
16923
|
return "\u7591\u4F3C\u5361\u4F4F";
|
|
16924
|
+
case "suspected_stream_ui_stall":
|
|
16925
|
+
return "\u6D41\u5F0F UI \u7591\u4F3C\u5361\u4F4F";
|
|
16449
16926
|
case "suspected_detached":
|
|
16450
16927
|
return "\u7591\u4F3C\u8131\u6302";
|
|
16451
16928
|
case "completed":
|
|
@@ -16500,8 +16977,10 @@ function buildHealthCommandResponse(title, diagnosis, markdown = false) {
|
|
|
16500
16977
|
["\u5065\u5EB7\u72B6\u6001", formatHealthStatusLabel(diagnosis.healthStatus)],
|
|
16501
16978
|
["\u5F53\u524D\u9636\u6BB5", currentStage],
|
|
16502
16979
|
["\u6700\u540E\u8FDB\u5C55", formatCommandTimestamp(diagnosis.lastProgressAt)],
|
|
16980
|
+
["\u6D41\u5F0F UI \u5237\u65B0", formatCommandTimestamp(diagnosis.lastStreamUiUpdateAt)],
|
|
16503
16981
|
["\u5DE5\u5177\u5F00\u59CB", formatCommandTimestamp(diagnosis.activeToolStartedAt)],
|
|
16504
16982
|
["\u6700\u8FD1\u5DE5\u5177\u5B8C\u6210", formatCommandTimestamp(diagnosis.lastToolFinishedAt)],
|
|
16983
|
+
["\u6D41\u5F0F UI \u9519\u8BEF", diagnosis.lastStreamUiErrorAt ? `${formatCommandTimestamp(diagnosis.lastStreamUiErrorAt)}${diagnosis.lastStreamUiError ? ` \xB7 ${diagnosis.lastStreamUiError}` : ""}` : "-"],
|
|
16505
16984
|
["\u672C\u5730\u8FDB\u7A0B", formatHealthProcessProbe(diagnosis)]
|
|
16506
16985
|
],
|
|
16507
16986
|
[diagnosis.healthReason],
|
|
@@ -16621,11 +17100,15 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
|
16621
17100
|
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
16622
17101
|
startedAt: safeTimestamp,
|
|
16623
17102
|
lastActivityAt: safeTimestamp,
|
|
17103
|
+
lastStatusText: null,
|
|
17104
|
+
lastStatusAt: 0,
|
|
17105
|
+
statusNote: null,
|
|
16624
17106
|
userText: null,
|
|
16625
17107
|
lastAssistantText: null,
|
|
16626
17108
|
lastCommentaryText: null,
|
|
16627
17109
|
streamedText: "",
|
|
16628
17110
|
streamStarted: false,
|
|
17111
|
+
taskItems: [],
|
|
16629
17112
|
toolCalls: /* @__PURE__ */ new Map()
|
|
16630
17113
|
};
|
|
16631
17114
|
}
|
|
@@ -16673,7 +17156,7 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
|
|
|
16673
17156
|
pendingTurn.lastCommentaryText
|
|
16674
17157
|
].map((value) => (value || "").trim()).find(Boolean) || "";
|
|
16675
17158
|
const userText = pendingTurn.userText?.trim() || null;
|
|
16676
|
-
if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
|
|
17159
|
+
if (!text2 && !userText && pendingTurn.toolCalls.size === 0 && pendingTurn.taskItems.length === 0) return null;
|
|
16677
17160
|
return {
|
|
16678
17161
|
streamKey: pendingTurn.streamKey,
|
|
16679
17162
|
userText,
|
|
@@ -16712,6 +17195,12 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
16712
17195
|
if (completed) finalized.push(completed);
|
|
16713
17196
|
continue;
|
|
16714
17197
|
}
|
|
17198
|
+
if (record.type === "task_aborted") {
|
|
17199
|
+
ensureMirrorTurnState(subscription, record);
|
|
17200
|
+
const interrupted = finalizeMirrorTurn(subscription, record.signature, record.timestamp, "interrupted");
|
|
17201
|
+
if (interrupted) finalized.push(interrupted);
|
|
17202
|
+
continue;
|
|
17203
|
+
}
|
|
16715
17204
|
if (record.type === "message" && record.role === "user") {
|
|
16716
17205
|
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
16717
17206
|
const text2 = record.content.trim();
|
|
@@ -16740,6 +17229,20 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
16740
17229
|
}
|
|
16741
17230
|
continue;
|
|
16742
17231
|
}
|
|
17232
|
+
if (record.type === "reasoning") {
|
|
17233
|
+
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
17234
|
+
const text2 = record.content.trim();
|
|
17235
|
+
if (!text2) continue;
|
|
17236
|
+
pendingTurn.statusNote = text2;
|
|
17237
|
+
hooks.onStatusProgress?.(subscription, pendingTurn);
|
|
17238
|
+
continue;
|
|
17239
|
+
}
|
|
17240
|
+
if (record.type === "plan_update") {
|
|
17241
|
+
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
17242
|
+
pendingTurn.taskItems = record.tasks || [];
|
|
17243
|
+
hooks.onTaskProgress?.(subscription, pendingTurn);
|
|
17244
|
+
continue;
|
|
17245
|
+
}
|
|
16743
17246
|
if (record.type === "tool_started") {
|
|
16744
17247
|
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
16745
17248
|
const toolId = record.toolId || record.signature;
|
|
@@ -16782,8 +17285,30 @@ function flushTimedOutMirrorTurn(subscription, idleTimeoutMs, nowMs = Date.now()
|
|
|
16782
17285
|
"interrupted"
|
|
16783
17286
|
);
|
|
16784
17287
|
}
|
|
17288
|
+
function enqueuePendingMirrorDeliveries(subscription, turns) {
|
|
17289
|
+
if (turns.length === 0) return;
|
|
17290
|
+
const existingSignatures = new Set(subscription.pendingDeliveries.map((turn) => turn.signature));
|
|
17291
|
+
for (const turn of turns) {
|
|
17292
|
+
if (existingSignatures.has(turn.signature)) continue;
|
|
17293
|
+
subscription.pendingDeliveries.push(turn);
|
|
17294
|
+
existingSignatures.add(turn.signature);
|
|
17295
|
+
}
|
|
17296
|
+
}
|
|
17297
|
+
function removePendingMirrorDeliveries(subscription, turns) {
|
|
17298
|
+
if (turns.length === 0 || subscription.pendingDeliveries.length === 0) return;
|
|
17299
|
+
const deliveredSignatures = new Set(turns.map((turn) => turn.signature));
|
|
17300
|
+
subscription.pendingDeliveries = subscription.pendingDeliveries.filter(
|
|
17301
|
+
(turn) => !deliveredSignatures.has(turn.signature)
|
|
17302
|
+
);
|
|
17303
|
+
}
|
|
17304
|
+
function selectPendingMirrorDeliveries(subscription, blocked) {
|
|
17305
|
+
if (!blocked) {
|
|
17306
|
+
return subscription.pendingDeliveries.slice();
|
|
17307
|
+
}
|
|
17308
|
+
return subscription.pendingDeliveries.filter((turn) => turn.timedOut);
|
|
17309
|
+
}
|
|
16785
17310
|
function hasPendingMirrorWork(subscription) {
|
|
16786
|
-
return subscription.bufferedRecords.length > 0 || subscription.pendingTurn !== null;
|
|
17311
|
+
return subscription.bufferedRecords.length > 0 || subscription.pendingTurn !== null || subscription.pendingDeliveries.length > 0;
|
|
16787
17312
|
}
|
|
16788
17313
|
function consumeBufferedMirrorTurns(subscription, idleTimeoutMs, nowMs = Date.now(), hooks = {}) {
|
|
16789
17314
|
const bufferedRecords = subscription.bufferedRecords;
|
|
@@ -18604,7 +19129,7 @@ function buildConversationPromptText(text2, files = []) {
|
|
|
18604
19129
|
|
|
18605
19130
|
${attachmentSupplement}` : attachmentSupplement;
|
|
18606
19131
|
}
|
|
18607
|
-
async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent, onPromptPrepared) {
|
|
19132
|
+
async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent, onTaskEvent, onStatusNote, onPromptPrepared) {
|
|
18608
19133
|
const { store, llm } = getBridgeContext();
|
|
18609
19134
|
const sessionId = binding.codepilotSessionId;
|
|
18610
19135
|
const lockId = crypto8.randomBytes(8).toString("hex");
|
|
@@ -18723,14 +19248,22 @@ async function processMessage(binding, text2, onPermissionRequest, abortSignal,
|
|
|
18723
19248
|
}
|
|
18724
19249
|
}
|
|
18725
19250
|
});
|
|
18726
|
-
return await consumeStream(
|
|
19251
|
+
return await consumeStream(
|
|
19252
|
+
stream,
|
|
19253
|
+
sessionId,
|
|
19254
|
+
onPermissionRequest,
|
|
19255
|
+
onPartialText,
|
|
19256
|
+
onToolEvent,
|
|
19257
|
+
onTaskEvent,
|
|
19258
|
+
onStatusNote
|
|
19259
|
+
);
|
|
18727
19260
|
} finally {
|
|
18728
19261
|
clearInterval(renewalInterval);
|
|
18729
19262
|
store.releaseSessionLock(sessionId, lockId);
|
|
18730
19263
|
store.setSessionRuntimeStatus(sessionId, "idle");
|
|
18731
19264
|
}
|
|
18732
19265
|
}
|
|
18733
|
-
async function consumeStream(stream, sessionId, onPermissionRequest, onPartialText, onToolEvent) {
|
|
19266
|
+
async function consumeStream(stream, sessionId, onPermissionRequest, onPartialText, onToolEvent, onTaskEvent, onStatusNote) {
|
|
18734
19267
|
const { store } = getBridgeContext();
|
|
18735
19268
|
const contentBlocks = [];
|
|
18736
19269
|
let currentText = "";
|
|
@@ -18839,6 +19372,12 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
18839
19372
|
if (statusData.model) {
|
|
18840
19373
|
store.updateSessionModel(sessionId, statusData.model);
|
|
18841
19374
|
}
|
|
19375
|
+
if (typeof statusData.reasoning === "string" && onStatusNote) {
|
|
19376
|
+
try {
|
|
19377
|
+
onStatusNote(statusData.reasoning);
|
|
19378
|
+
} catch {
|
|
19379
|
+
}
|
|
19380
|
+
}
|
|
18842
19381
|
} catch {
|
|
18843
19382
|
}
|
|
18844
19383
|
break;
|
|
@@ -18846,8 +19385,15 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
18846
19385
|
case "task_update": {
|
|
18847
19386
|
try {
|
|
18848
19387
|
const taskData = JSON.parse(event.data);
|
|
18849
|
-
|
|
18850
|
-
|
|
19388
|
+
const tasks = Array.isArray(taskData.tasks) ? taskData.tasks : Array.isArray(taskData.todos) ? taskData.todos : null;
|
|
19389
|
+
if (tasks) {
|
|
19390
|
+
store.syncSdkTasks(sessionId, tasks);
|
|
19391
|
+
if (onTaskEvent) {
|
|
19392
|
+
try {
|
|
19393
|
+
onTaskEvent(tasks);
|
|
19394
|
+
} catch {
|
|
19395
|
+
}
|
|
19396
|
+
}
|
|
18851
19397
|
}
|
|
18852
19398
|
} catch {
|
|
18853
19399
|
}
|
|
@@ -18945,6 +19491,14 @@ function pushStreamFeedbackTools(target, tools) {
|
|
|
18945
19491
|
} catch {
|
|
18946
19492
|
}
|
|
18947
19493
|
}
|
|
19494
|
+
function pushStreamFeedbackTasks(target, tasks) {
|
|
19495
|
+
if (typeof target.adapter.onTaskEvent !== "function") return;
|
|
19496
|
+
target.ensureStarted?.();
|
|
19497
|
+
try {
|
|
19498
|
+
target.adapter.onTaskEvent(target.chatId, tasks, target.streamKey);
|
|
19499
|
+
} catch {
|
|
19500
|
+
}
|
|
19501
|
+
}
|
|
18948
19502
|
function pushStreamFeedbackStatus(target, text2) {
|
|
18949
19503
|
if (typeof target.adapter.onStreamStatus !== "function") return;
|
|
18950
19504
|
target.ensureStarted?.();
|
|
@@ -19023,12 +19577,15 @@ function formatRuntimeDuration(ms) {
|
|
|
19023
19577
|
if (seconds === 0) return `${hours}h ${minutes}m`;
|
|
19024
19578
|
return `${hours}h ${minutes}m ${seconds}s`;
|
|
19025
19579
|
}
|
|
19026
|
-
function formatInteractiveRuntimeStatus(elapsedMs, silentMs) {
|
|
19580
|
+
function formatInteractiveRuntimeStatus(elapsedMs, silentMs, statusNote) {
|
|
19027
19581
|
const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
|
|
19028
19582
|
if (typeof silentMs === "number" && silentMs >= 0) {
|
|
19029
19583
|
parts.push(`\u6700\u8FD1 ${formatRuntimeDuration(silentMs)} \u65E0\u65B0\u8F93\u51FA`);
|
|
19030
19584
|
}
|
|
19031
|
-
|
|
19585
|
+
const runtimeText = parts.join("\uFF0C");
|
|
19586
|
+
const note = (statusNote || "").trim();
|
|
19587
|
+
return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
|
|
19588
|
+
${runtimeText}` : runtimeText;
|
|
19032
19589
|
}
|
|
19033
19590
|
async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
19034
19591
|
const binding = resolve(msg.address);
|
|
@@ -19123,23 +19680,35 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19123
19680
|
adapter,
|
|
19124
19681
|
channelType: adapter.channelType,
|
|
19125
19682
|
chatId: msg.address.chatId,
|
|
19126
|
-
streamKey
|
|
19683
|
+
streamKey,
|
|
19684
|
+
ensureStarted: () => {
|
|
19685
|
+
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
19686
|
+
}
|
|
19127
19687
|
};
|
|
19128
19688
|
const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
|
|
19129
19689
|
const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
|
|
19690
|
+
let latestStatusNote = null;
|
|
19691
|
+
let latestTasks = [];
|
|
19130
19692
|
const syncStructuredStreamUiState = () => {
|
|
19131
19693
|
if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
|
|
19132
19694
|
if (adapter.hasActiveStreamingUi?.(msg.address.chatId, streamKey)) {
|
|
19133
19695
|
taskState.structuredStreamUiActive = true;
|
|
19134
19696
|
}
|
|
19135
19697
|
};
|
|
19698
|
+
const syncStructuredStreamUiSnapshot = () => {
|
|
19699
|
+
if (!supportsStructuredStreamUi) return;
|
|
19700
|
+
syncStructuredStreamUiState();
|
|
19701
|
+
const snapshot = adapter.getStructuredStreamingUiSnapshot?.(msg.address.chatId, streamKey);
|
|
19702
|
+
if (!snapshot) return;
|
|
19703
|
+
deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, snapshot);
|
|
19704
|
+
};
|
|
19136
19705
|
const pushRunningStatus = (silentMs) => {
|
|
19137
19706
|
if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
|
|
19138
19707
|
pushStreamFeedbackStatus(
|
|
19139
19708
|
streamFeedbackTarget,
|
|
19140
|
-
formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs)
|
|
19709
|
+
formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs, latestStatusNote)
|
|
19141
19710
|
);
|
|
19142
|
-
|
|
19711
|
+
syncStructuredStreamUiSnapshot();
|
|
19143
19712
|
};
|
|
19144
19713
|
let streamStatusHeartbeat = null;
|
|
19145
19714
|
let streamStatusUpdatesClosed = false;
|
|
@@ -19173,7 +19742,24 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19173
19742
|
pushStreamFeedbackTools(streamFeedbackTarget, Array.from(toolCallTracker.values()));
|
|
19174
19743
|
}
|
|
19175
19744
|
pushRunningStatus(null);
|
|
19176
|
-
|
|
19745
|
+
syncStructuredStreamUiSnapshot();
|
|
19746
|
+
};
|
|
19747
|
+
const onTaskEvent = (tasks) => {
|
|
19748
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
19749
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19750
|
+
latestTasks = tasks;
|
|
19751
|
+
if (hasStreamingCards) {
|
|
19752
|
+
pushStreamFeedbackTasks(streamFeedbackTarget, latestTasks);
|
|
19753
|
+
}
|
|
19754
|
+
pushRunningStatus(null);
|
|
19755
|
+
syncStructuredStreamUiSnapshot();
|
|
19756
|
+
};
|
|
19757
|
+
const onStatusNote = (note) => {
|
|
19758
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
19759
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19760
|
+
latestStatusNote = (note || "").trim() || null;
|
|
19761
|
+
pushRunningStatus(null);
|
|
19762
|
+
syncStructuredStreamUiSnapshot();
|
|
19177
19763
|
};
|
|
19178
19764
|
const onPartialText = (fullText) => {
|
|
19179
19765
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
@@ -19182,7 +19768,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19182
19768
|
previewOnPartialText?.(fullText);
|
|
19183
19769
|
onStreamCardText?.(fullText);
|
|
19184
19770
|
pushRunningStatus(null);
|
|
19185
|
-
|
|
19771
|
+
syncStructuredStreamUiSnapshot();
|
|
19186
19772
|
};
|
|
19187
19773
|
if (supportsStructuredStreamUi) {
|
|
19188
19774
|
pushRunningStatus(null);
|
|
@@ -19196,11 +19782,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19196
19782
|
return;
|
|
19197
19783
|
}
|
|
19198
19784
|
const elapsedMs = nowMs() - taskStartedAt;
|
|
19199
|
-
if (elapsedMs < streamStatusIdleDetectionStartMs) return;
|
|
19200
19785
|
const silentMs = nowMs() - taskState.lastActivityAt;
|
|
19201
|
-
|
|
19202
|
-
pushRunningStatus(
|
|
19203
|
-
|
|
19786
|
+
const showSilentDuration = elapsedMs >= streamStatusIdleDetectionStartMs && silentMs >= streamStatusHeartbeatMs ? silentMs : null;
|
|
19787
|
+
pushRunningStatus(showSilentDuration);
|
|
19788
|
+
syncStructuredStreamUiSnapshot();
|
|
19204
19789
|
}, streamStatusHeartbeatMs);
|
|
19205
19790
|
}
|
|
19206
19791
|
let finalOutcome = "failed";
|
|
@@ -19227,11 +19812,16 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19227
19812
|
"permission_wait",
|
|
19228
19813
|
`\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${perm.toolName} \u7684\u6743\u9650\u786E\u8BA4\u3002`
|
|
19229
19814
|
);
|
|
19815
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19816
|
+
pushRunningStatus(null);
|
|
19817
|
+
syncStructuredStreamUiSnapshot();
|
|
19230
19818
|
},
|
|
19231
19819
|
taskAbort.signal,
|
|
19232
19820
|
attachments && attachments.length > 0 ? attachments : void 0,
|
|
19233
19821
|
onPartialText,
|
|
19234
19822
|
onToolEvent,
|
|
19823
|
+
onTaskEvent,
|
|
19824
|
+
onStatusNote,
|
|
19235
19825
|
(preparedPrompt) => {
|
|
19236
19826
|
if (!taskState.mirrorSuppressionId) {
|
|
19237
19827
|
taskState.mirrorSuppressionId = deps.beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
@@ -19254,14 +19844,16 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19254
19844
|
}
|
|
19255
19845
|
if (result.responseText || result.outboundAttachments.length > 0) {
|
|
19256
19846
|
const textToDeliver = cardFinalized ? "" : result.responseText;
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19847
|
+
if (!cardFinalized || result.outboundAttachments.length > 0) {
|
|
19848
|
+
await deps.deliverResponse(
|
|
19849
|
+
adapter,
|
|
19850
|
+
msg.address,
|
|
19851
|
+
textToDeliver,
|
|
19852
|
+
binding.codepilotSessionId,
|
|
19853
|
+
msg.messageId,
|
|
19854
|
+
result.outboundAttachments
|
|
19855
|
+
);
|
|
19856
|
+
}
|
|
19265
19857
|
} else if (result.hasError) {
|
|
19266
19858
|
await deps.deliverResponse(
|
|
19267
19859
|
adapter,
|
|
@@ -19280,6 +19872,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19280
19872
|
finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
|
|
19281
19873
|
} finally {
|
|
19282
19874
|
stopStructuredStreamStatusUpdates();
|
|
19875
|
+
deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, { active: false });
|
|
19283
19876
|
if (previewState) {
|
|
19284
19877
|
if (previewState.throttleTimer) {
|
|
19285
19878
|
clearTimeout(previewState.throttleTimer);
|
|
@@ -19318,6 +19911,14 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19318
19911
|
}
|
|
19319
19912
|
|
|
19320
19913
|
// src/lib/bridge/interactive-runtime.ts
|
|
19914
|
+
var TERMINAL_SESSION_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
19915
|
+
"completed",
|
|
19916
|
+
"failed",
|
|
19917
|
+
"aborted"
|
|
19918
|
+
]);
|
|
19919
|
+
function isTerminalSessionHealthStatus(status) {
|
|
19920
|
+
return Boolean(status && TERMINAL_SESSION_HEALTH_STATUSES.has(status));
|
|
19921
|
+
}
|
|
19321
19922
|
function buildInteractiveIdleReminderNotice() {
|
|
19322
19923
|
return [
|
|
19323
19924
|
"\u63D0\u9192\uFF1A\u8FD9\u8F6E\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u7ECF\u8D85\u8FC7 10 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8F93\u51FA\u3002",
|
|
@@ -19391,6 +19992,24 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
19391
19992
|
await remindIdleInteractiveTask(task);
|
|
19392
19993
|
}
|
|
19393
19994
|
}
|
|
19995
|
+
function reconcileTerminalSessionRuntimeState() {
|
|
19996
|
+
const store = deps.getStore();
|
|
19997
|
+
for (const session of store.listSessions()) {
|
|
19998
|
+
const queuedCount = getQueuedCount(session.id);
|
|
19999
|
+
const persistedQueuedCount = session.queued_count && session.queued_count > 0 ? session.queued_count : 0;
|
|
20000
|
+
const hasActiveTask = getState2().activeTasks.has(session.id);
|
|
20001
|
+
if (hasActiveTask || queuedCount > 0) continue;
|
|
20002
|
+
if (!isTerminalSessionHealthStatus(session.health_status)) continue;
|
|
20003
|
+
if (persistedQueuedCount === 0 && session.runtime_status !== "running" && session.runtime_status !== "queued") {
|
|
20004
|
+
continue;
|
|
20005
|
+
}
|
|
20006
|
+
store.updateSession(session.id, {
|
|
20007
|
+
queued_count: 0,
|
|
20008
|
+
runtime_status: "idle",
|
|
20009
|
+
last_runtime_update_at: deps.nowIso()
|
|
20010
|
+
});
|
|
20011
|
+
}
|
|
20012
|
+
}
|
|
19394
20013
|
function resetPersistedInteractiveRuntimeState() {
|
|
19395
20014
|
const store = deps.getStore();
|
|
19396
20015
|
for (const session of store.listSessions()) {
|
|
@@ -19452,6 +20071,7 @@ function createInteractiveRuntime(getState2, options, deps) {
|
|
|
19452
20071
|
releaseInteractiveTask,
|
|
19453
20072
|
syncSessionRuntimeState,
|
|
19454
20073
|
reconcileIdleInteractiveTasks,
|
|
20074
|
+
reconcileTerminalSessionRuntimeState,
|
|
19455
20075
|
resetPersistedInteractiveRuntimeState,
|
|
19456
20076
|
processWithSessionLock
|
|
19457
20077
|
};
|
|
@@ -19468,6 +20088,7 @@ function resetMirrorReadState(subscription) {
|
|
|
19468
20088
|
subscription.fileIdentity = null;
|
|
19469
20089
|
subscription.trailingText = "";
|
|
19470
20090
|
subscription.activeMirrorTurnId = null;
|
|
20091
|
+
subscription.activeSpecialCallIds.clear();
|
|
19471
20092
|
subscription.bufferedRecords = [];
|
|
19472
20093
|
}
|
|
19473
20094
|
function createMirrorSubscription(input) {
|
|
@@ -19491,8 +20112,11 @@ function createMirrorSubscription(input) {
|
|
|
19491
20112
|
fileIdentity: null,
|
|
19492
20113
|
trailingText: "",
|
|
19493
20114
|
activeMirrorTurnId: null,
|
|
20115
|
+
activeSpecialCallIds: /* @__PURE__ */ new Set(),
|
|
19494
20116
|
bufferedRecords: [],
|
|
19495
20117
|
pendingTurn: null,
|
|
20118
|
+
pendingDeliveries: [],
|
|
20119
|
+
unknownMirrorKindsSeen: /* @__PURE__ */ new Set(),
|
|
19496
20120
|
missingThreadPolls: 0,
|
|
19497
20121
|
consecutiveFailures: 0,
|
|
19498
20122
|
suspendedUntil: null
|
|
@@ -19503,6 +20127,8 @@ function resetMirrorSubscriptionForThreadChange(subscription, lastDeliveredAt) {
|
|
|
19503
20127
|
subscription.lastDeliveredAt = lastDeliveredAt;
|
|
19504
20128
|
subscription.dirty = true;
|
|
19505
20129
|
subscription.pendingTurn = null;
|
|
20130
|
+
subscription.pendingDeliveries = [];
|
|
20131
|
+
subscription.unknownMirrorKindsSeen.clear();
|
|
19506
20132
|
subscription.missingThreadPolls = 0;
|
|
19507
20133
|
subscription.consecutiveFailures = 0;
|
|
19508
20134
|
subscription.suspendedUntil = null;
|
|
@@ -19699,6 +20325,7 @@ function isMirrorSnapshotUnchanged(subscription, snapshot) {
|
|
|
19699
20325
|
}
|
|
19700
20326
|
function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
19701
20327
|
let deliverableRecords = [];
|
|
20328
|
+
let unknownKinds = [];
|
|
19702
20329
|
const requiresFullRecover = !subscription.cursor.initialized || subscription.fileOffset === 0 || subscription.fileIdentity !== null && subscription.fileIdentity !== snapshot.identity || subscription.fileSize !== null && snapshot.size < subscription.fileOffset || subscription.fileSize !== null && snapshot.size === subscription.fileOffset && subscription.fileMtimeMs !== null && snapshot.mtimeMs !== subscription.fileMtimeMs;
|
|
19703
20330
|
if (requiresFullRecover) {
|
|
19704
20331
|
const previousCursor = subscription.cursor;
|
|
@@ -19707,7 +20334,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
19707
20334
|
0,
|
|
19708
20335
|
snapshot.size,
|
|
19709
20336
|
"",
|
|
19710
|
-
null
|
|
20337
|
+
null,
|
|
20338
|
+
[]
|
|
19711
20339
|
);
|
|
19712
20340
|
const delta = reconcileDesktopMirrorCursor(subscription.cursor, fullDelta.records);
|
|
19713
20341
|
subscription.cursor = delta.nextCursor;
|
|
@@ -19715,6 +20343,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
19715
20343
|
subscription.trailingText = "";
|
|
19716
20344
|
subscription.fileOffset = snapshot.size;
|
|
19717
20345
|
subscription.activeMirrorTurnId = fullDelta.nextTurnId;
|
|
20346
|
+
subscription.activeSpecialCallIds = new Set(fullDelta.nextSpecialCallIds);
|
|
20347
|
+
unknownKinds = fullDelta.unknownKinds;
|
|
19718
20348
|
} else if (snapshot.size > subscription.fileOffset || subscription.trailingText) {
|
|
19719
20349
|
const previousCursor = subscription.cursor;
|
|
19720
20350
|
const delta = readDesktopSessionMirrorRecordDeltaByFilePath(
|
|
@@ -19722,19 +20352,25 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
19722
20352
|
subscription.fileOffset,
|
|
19723
20353
|
snapshot.size,
|
|
19724
20354
|
subscription.trailingText,
|
|
19725
|
-
subscription.activeMirrorTurnId
|
|
20355
|
+
subscription.activeMirrorTurnId,
|
|
20356
|
+
subscription.activeSpecialCallIds
|
|
19726
20357
|
);
|
|
19727
20358
|
deliverableRecords = filterDuplicateAssistantEvents(previousCursor, delta.records);
|
|
19728
20359
|
subscription.cursor = advanceDesktopMirrorCursor(subscription.cursor, delta.records);
|
|
19729
20360
|
subscription.trailingText = delta.trailingText;
|
|
19730
20361
|
subscription.fileOffset = delta.nextOffset;
|
|
19731
20362
|
subscription.activeMirrorTurnId = delta.nextTurnId;
|
|
20363
|
+
subscription.activeSpecialCallIds = new Set(delta.nextSpecialCallIds);
|
|
20364
|
+
unknownKinds = delta.unknownKinds;
|
|
19732
20365
|
}
|
|
19733
20366
|
subscription.fileSize = snapshot.size;
|
|
19734
20367
|
subscription.fileMtimeMs = snapshot.mtimeMs;
|
|
19735
20368
|
subscription.fileIdentity = snapshot.identity;
|
|
19736
20369
|
subscription.dirty = false;
|
|
19737
|
-
return
|
|
20370
|
+
return {
|
|
20371
|
+
records: deliverableRecords,
|
|
20372
|
+
unknownKinds
|
|
20373
|
+
};
|
|
19738
20374
|
}
|
|
19739
20375
|
|
|
19740
20376
|
// src/lib/bridge/mirror-delivery-plan.ts
|
|
@@ -19749,7 +20385,7 @@ function buildMirrorDeliveryPlan(subscription, deliverableRecords, options) {
|
|
|
19749
20385
|
if (options.blocked) {
|
|
19750
20386
|
return {
|
|
19751
20387
|
syncReason: "mirror reconcile active task",
|
|
19752
|
-
|
|
20388
|
+
finalizedTurns: timedOutTurn ? [timedOutTurn] : []
|
|
19753
20389
|
};
|
|
19754
20390
|
}
|
|
19755
20391
|
const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
|
|
@@ -19757,12 +20393,12 @@ function buildMirrorDeliveryPlan(subscription, deliverableRecords, options) {
|
|
|
19757
20393
|
if (finalizedTurns.length === 0) {
|
|
19758
20394
|
return {
|
|
19759
20395
|
syncReason: "mirror reconcile no finalized turns",
|
|
19760
|
-
|
|
20396
|
+
finalizedTurns: []
|
|
19761
20397
|
};
|
|
19762
20398
|
}
|
|
19763
20399
|
return {
|
|
19764
20400
|
syncReason: "mirror reconcile delivered turns",
|
|
19765
|
-
|
|
20401
|
+
finalizedTurns
|
|
19766
20402
|
};
|
|
19767
20403
|
}
|
|
19768
20404
|
|
|
@@ -19986,21 +20622,36 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
19986
20622
|
deps.syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile unchanged snapshot");
|
|
19987
20623
|
return "processed";
|
|
19988
20624
|
}
|
|
19989
|
-
const
|
|
20625
|
+
const readResult = readMirrorDeliverableRecords(subscription, snapshot);
|
|
20626
|
+
const deliverableRecords = readResult.records;
|
|
20627
|
+
for (const kind of readResult.unknownKinds) {
|
|
20628
|
+
if (subscription.unknownMirrorKindsSeen.has(kind)) continue;
|
|
20629
|
+
subscription.unknownMirrorKindsSeen.add(kind);
|
|
20630
|
+
console.warn(
|
|
20631
|
+
`[bridge-manager] Unhandled desktop mirror event for thread ${subscription.threadId}: ${kind}`
|
|
20632
|
+
);
|
|
20633
|
+
}
|
|
19990
20634
|
if (deliverableRecords.length > 0) {
|
|
19991
20635
|
deps.observeSessionHealthRecords(subscription.sessionId, subscription.threadId, deliverableRecords);
|
|
19992
20636
|
}
|
|
20637
|
+
const blocked = getState2().activeTasks.has(subscription.sessionId) || deps.isMirrorSuppressed(subscription.sessionId);
|
|
19993
20638
|
const deliveryPlan = buildMirrorDeliveryPlan(subscription, deliverableRecords, {
|
|
19994
|
-
blocked
|
|
20639
|
+
blocked,
|
|
19995
20640
|
filterSuppressedRecords: deps.filterSuppressedMirrorRecords,
|
|
19996
20641
|
flushTimedOutTurn: (currentSubscription) => deps.flushTimedOutMirrorTurn(currentSubscription),
|
|
19997
20642
|
consumeBufferedTurns: (currentSubscription) => deps.consumeBufferedMirrorTurns(currentSubscription)
|
|
19998
20643
|
});
|
|
19999
|
-
if (deliveryPlan.
|
|
20000
|
-
|
|
20001
|
-
|
|
20002
|
-
|
|
20003
|
-
|
|
20644
|
+
if (deliveryPlan.finalizedTurns.length > 0) {
|
|
20645
|
+
enqueuePendingMirrorDeliveries(subscription, deliveryPlan.finalizedTurns);
|
|
20646
|
+
}
|
|
20647
|
+
const turnsToAttempt = selectPendingMirrorDeliveries(subscription, blocked);
|
|
20648
|
+
if (turnsToAttempt.length > 0) {
|
|
20649
|
+
const deliveryResult = await deps.deliverMirrorTurns(subscription, turnsToAttempt);
|
|
20650
|
+
if (deliveryResult.deliveredCount > 0) {
|
|
20651
|
+
removePendingMirrorDeliveries(subscription, turnsToAttempt.slice(0, deliveryResult.deliveredCount));
|
|
20652
|
+
}
|
|
20653
|
+
if (deliveryResult.error) {
|
|
20654
|
+
const error = deliveryResult.error;
|
|
20004
20655
|
console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
|
|
20005
20656
|
}
|
|
20006
20657
|
}
|
|
@@ -20174,11 +20825,13 @@ var HEALTH_RECENT_PROGRESS_MS = 10 * 60 * 1e3;
|
|
|
20174
20825
|
var HEALTH_SLOW_OBSERVED_MS = 30 * 60 * 1e3;
|
|
20175
20826
|
var HEALTH_PROGRESS_PERSIST_THROTTLE_MS = 15 * 1e3;
|
|
20176
20827
|
var HEALTH_PROCESS_PROBE_CACHE_MS = 30 * 1e3;
|
|
20828
|
+
var HEALTH_STREAM_UI_STALL_MS = 60 * 1e3;
|
|
20177
20829
|
var RUNNING_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
20178
20830
|
"running_active",
|
|
20179
20831
|
"waiting_tool",
|
|
20180
20832
|
"slow_observed",
|
|
20181
20833
|
"suspected_stall",
|
|
20834
|
+
"suspected_stream_ui_stall",
|
|
20182
20835
|
"suspected_detached"
|
|
20183
20836
|
]);
|
|
20184
20837
|
function parseIsoMs(value) {
|
|
@@ -20246,6 +20899,10 @@ function buildProgressReason(type, detail) {
|
|
|
20246
20899
|
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u684C\u9762\u4F1A\u8BDD\u6D88\u606F\u3002";
|
|
20247
20900
|
case "commentary":
|
|
20248
20901
|
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u6267\u884C\u8FDB\u5C55\u8BF4\u660E\u3002";
|
|
20902
|
+
case "reasoning":
|
|
20903
|
+
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u601D\u8003/\u72B6\u6001\u8BF4\u660E\u3002";
|
|
20904
|
+
case "plan_update":
|
|
20905
|
+
return "\u6700\u8FD1\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
20249
20906
|
case "text":
|
|
20250
20907
|
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u6B63\u6587\u8F93\u51FA\u3002";
|
|
20251
20908
|
case "permission_wait":
|
|
@@ -20293,6 +20950,12 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
20293
20950
|
const activeToolName = summarizeActiveTools(activeTools) || trimOrNull(session.active_tool_name);
|
|
20294
20951
|
const activeToolStartedAt = getActiveToolStartedAt(activeTools) || trimOrNull(session.active_tool_started_at);
|
|
20295
20952
|
const lastToolFinishedAt = trimOrNull(session.last_tool_finished_at);
|
|
20953
|
+
const lastStreamUiAttemptAt = trimOrNull(session.last_stream_ui_attempt_at);
|
|
20954
|
+
const lastStreamUiUpdateAt = trimOrNull(session.last_stream_ui_update_at);
|
|
20955
|
+
const streamUiFlushStartedAt = trimOrNull(session.stream_ui_flush_started_at);
|
|
20956
|
+
const lastStreamUiErrorAt = trimOrNull(session.last_stream_ui_error_at);
|
|
20957
|
+
const lastStreamUiError = trimOrNull(session.last_stream_ui_error);
|
|
20958
|
+
const streamUiConsecutiveFailures = typeof session.stream_ui_consecutive_failures === "number" && Number.isFinite(session.stream_ui_consecutive_failures) && session.stream_ui_consecutive_failures > 0 ? session.stream_ui_consecutive_failures : 0;
|
|
20296
20959
|
const sdkSessionId = trimOrNull(session.sdk_session_id);
|
|
20297
20960
|
const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
|
|
20298
20961
|
const previousStatus = session.health_status || "idle";
|
|
@@ -20308,6 +20971,12 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
20308
20971
|
activeToolName,
|
|
20309
20972
|
activeToolStartedAt,
|
|
20310
20973
|
lastToolFinishedAt,
|
|
20974
|
+
lastStreamUiAttemptAt,
|
|
20975
|
+
lastStreamUiUpdateAt,
|
|
20976
|
+
streamUiFlushStartedAt,
|
|
20977
|
+
lastStreamUiErrorAt,
|
|
20978
|
+
lastStreamUiError,
|
|
20979
|
+
streamUiConsecutiveFailures,
|
|
20311
20980
|
sdkSessionId
|
|
20312
20981
|
};
|
|
20313
20982
|
}
|
|
@@ -20339,6 +21008,12 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
20339
21008
|
activeToolName,
|
|
20340
21009
|
activeToolStartedAt,
|
|
20341
21010
|
lastToolFinishedAt,
|
|
21011
|
+
lastStreamUiAttemptAt,
|
|
21012
|
+
lastStreamUiUpdateAt,
|
|
21013
|
+
streamUiFlushStartedAt,
|
|
21014
|
+
lastStreamUiErrorAt,
|
|
21015
|
+
lastStreamUiError,
|
|
21016
|
+
streamUiConsecutiveFailures,
|
|
20342
21017
|
sdkSessionId
|
|
20343
21018
|
};
|
|
20344
21019
|
}
|
|
@@ -20379,11 +21054,75 @@ function applyProcessProbeDiagnosis(diagnosis, processProbe) {
|
|
|
20379
21054
|
processProbe
|
|
20380
21055
|
};
|
|
20381
21056
|
}
|
|
21057
|
+
function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
21058
|
+
if (!isRunningRuntimeStatus(diagnosis.runtimeStatus)) {
|
|
21059
|
+
return diagnosis;
|
|
21060
|
+
}
|
|
21061
|
+
const lastProgressMs = parseIsoMs(diagnosis.lastProgressAt || void 0);
|
|
21062
|
+
if (!lastProgressMs || nowMs - lastProgressMs > HEALTH_RECENT_PROGRESS_MS) {
|
|
21063
|
+
return diagnosis;
|
|
21064
|
+
}
|
|
21065
|
+
const lastStreamUiUpdateMs = parseIsoMs(diagnosis.lastStreamUiUpdateAt || void 0);
|
|
21066
|
+
const lastStreamUiAttemptMs = parseIsoMs(diagnosis.lastStreamUiAttemptAt || void 0);
|
|
21067
|
+
const streamUiFlushStartedMs = parseIsoMs(diagnosis.streamUiFlushStartedAt || void 0);
|
|
21068
|
+
const lastStreamUiErrorText = diagnosis.lastStreamUiError?.trim();
|
|
21069
|
+
if (streamUiFlushStartedMs && nowMs - streamUiFlushStartedMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
21070
|
+
const details = ["\u4EFB\u52A1\u4ECD\u5728\u7EE7\u7EED\uFF0C\u4F46\u6D41\u5F0F UI \u5237\u65B0\u8BF7\u6C42\u5DF2\u957F\u65F6\u95F4\u672A\u5B8C\u6210\uFF0C\u7591\u4F3C\u5361\u4F4F\u3002"];
|
|
21071
|
+
if (diagnosis.streamUiConsecutiveFailures > 0) {
|
|
21072
|
+
details.push(`\u6700\u8FD1\u8FDE\u7EED\u5931\u8D25 ${diagnosis.streamUiConsecutiveFailures} \u6B21\u3002`);
|
|
21073
|
+
}
|
|
21074
|
+
if (lastStreamUiErrorText) {
|
|
21075
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
21076
|
+
}
|
|
21077
|
+
return {
|
|
21078
|
+
...diagnosis,
|
|
21079
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
21080
|
+
healthReason: details.join(" ")
|
|
21081
|
+
};
|
|
21082
|
+
}
|
|
21083
|
+
if (lastStreamUiUpdateMs && lastProgressMs - lastStreamUiUpdateMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
21084
|
+
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"];
|
|
21085
|
+
if (lastStreamUiErrorText) {
|
|
21086
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
21087
|
+
}
|
|
21088
|
+
return {
|
|
21089
|
+
...diagnosis,
|
|
21090
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
21091
|
+
healthReason: details.join(" ")
|
|
21092
|
+
};
|
|
21093
|
+
}
|
|
21094
|
+
if (!lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
21095
|
+
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"];
|
|
21096
|
+
if (lastStreamUiErrorText) {
|
|
21097
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
21098
|
+
}
|
|
21099
|
+
return {
|
|
21100
|
+
...diagnosis,
|
|
21101
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
21102
|
+
healthReason: details.join(" ")
|
|
21103
|
+
};
|
|
21104
|
+
}
|
|
21105
|
+
return diagnosis;
|
|
21106
|
+
}
|
|
20382
21107
|
|
|
20383
21108
|
// src/lib/bridge/session-health-runtime.ts
|
|
20384
21109
|
function createSessionHealthRuntime(deps) {
|
|
20385
21110
|
const lastProgressPersistAt = /* @__PURE__ */ new Map();
|
|
20386
21111
|
const processProbeCache = /* @__PURE__ */ new Map();
|
|
21112
|
+
function summarizePlanUpdate(tasks) {
|
|
21113
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
21114
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
21115
|
+
}
|
|
21116
|
+
let inProgress = 0;
|
|
21117
|
+
let pending = 0;
|
|
21118
|
+
let completed = 0;
|
|
21119
|
+
for (const task of tasks) {
|
|
21120
|
+
if (task?.status === "completed") completed += 1;
|
|
21121
|
+
else if (task?.status === "in_progress") inProgress += 1;
|
|
21122
|
+
else pending += 1;
|
|
21123
|
+
}
|
|
21124
|
+
return `\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\uFF08\u6267\u884C\u4E2D ${inProgress} \u9879\uFF0C\u7B49\u5F85\u4E2D ${pending} \u9879\uFF0C\u5DF2\u5B8C\u6210 ${completed} \u9879\uFF09\u3002`;
|
|
21125
|
+
}
|
|
20387
21126
|
function updateSessionHealth(sessionId, updates, options) {
|
|
20388
21127
|
const store = deps.getStore();
|
|
20389
21128
|
const session = store.getSession(sessionId);
|
|
@@ -20426,7 +21165,13 @@ function createSessionHealthRuntime(deps) {
|
|
|
20426
21165
|
active_tools_json: void 0,
|
|
20427
21166
|
active_tool_name: void 0,
|
|
20428
21167
|
active_tool_started_at: void 0,
|
|
20429
|
-
last_tool_finished_at: void 0
|
|
21168
|
+
last_tool_finished_at: void 0,
|
|
21169
|
+
last_stream_ui_attempt_at: void 0,
|
|
21170
|
+
last_stream_ui_update_at: void 0,
|
|
21171
|
+
stream_ui_flush_started_at: void 0,
|
|
21172
|
+
last_stream_ui_error_at: void 0,
|
|
21173
|
+
last_stream_ui_error: void 0,
|
|
21174
|
+
stream_ui_consecutive_failures: void 0
|
|
20430
21175
|
});
|
|
20431
21176
|
}
|
|
20432
21177
|
function recordInteractiveProgress(sessionId, type, detail) {
|
|
@@ -20494,11 +21239,29 @@ function createSessionHealthRuntime(deps) {
|
|
|
20494
21239
|
active_tools_json: void 0,
|
|
20495
21240
|
active_tool_name: void 0,
|
|
20496
21241
|
active_tool_started_at: void 0,
|
|
21242
|
+
stream_ui_flush_started_at: void 0,
|
|
20497
21243
|
last_health_check_at: nowIso4
|
|
20498
21244
|
}, { force: true });
|
|
20499
21245
|
lastProgressPersistAt.set(sessionId, Date.now());
|
|
20500
21246
|
processProbeCache.delete(sessionId);
|
|
20501
21247
|
}
|
|
21248
|
+
function toIso(value) {
|
|
21249
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
21250
|
+
return new Date(value).toISOString();
|
|
21251
|
+
}
|
|
21252
|
+
function recordStructuredStreamUi(sessionId, snapshot) {
|
|
21253
|
+
const updates = {
|
|
21254
|
+
stream_ui_flush_started_at: snapshot.active && snapshot.flushInFlight ? toIso(snapshot.flushInFlightSince ?? snapshot.lastAttemptAt) : void 0
|
|
21255
|
+
};
|
|
21256
|
+
if (snapshot.active) {
|
|
21257
|
+
updates.last_stream_ui_attempt_at = toIso(snapshot.lastAttemptAt);
|
|
21258
|
+
updates.last_stream_ui_update_at = toIso(snapshot.lastUpdateAt);
|
|
21259
|
+
updates.last_stream_ui_error_at = toIso(snapshot.lastErrorAt);
|
|
21260
|
+
updates.last_stream_ui_error = snapshot.lastError?.trim() || void 0;
|
|
21261
|
+
updates.stream_ui_consecutive_failures = snapshot.consecutiveFailures && snapshot.consecutiveFailures > 0 ? snapshot.consecutiveFailures : void 0;
|
|
21262
|
+
}
|
|
21263
|
+
updateSessionHealth(sessionId, updates);
|
|
21264
|
+
}
|
|
20502
21265
|
function observeDesktopMirrorRecords(sessionId, _threadId, records) {
|
|
20503
21266
|
for (const record of records) {
|
|
20504
21267
|
if (record.type === "task_started") {
|
|
@@ -20509,6 +21272,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
20509
21272
|
recordInteractiveEnd(sessionId, "completed", "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u5B8C\u6210\u5F53\u524D\u4EFB\u52A1\u3002");
|
|
20510
21273
|
continue;
|
|
20511
21274
|
}
|
|
21275
|
+
if (record.type === "task_aborted") {
|
|
21276
|
+
recordInteractiveEnd(sessionId, "aborted", "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002");
|
|
21277
|
+
continue;
|
|
21278
|
+
}
|
|
20512
21279
|
if (record.type === "tool_started") {
|
|
20513
21280
|
recordToolState(sessionId, record.toolId || record.signature, record.toolName || "tool", "running");
|
|
20514
21281
|
continue;
|
|
@@ -20522,6 +21289,22 @@ function createSessionHealthRuntime(deps) {
|
|
|
20522
21289
|
);
|
|
20523
21290
|
continue;
|
|
20524
21291
|
}
|
|
21292
|
+
if (record.type === "reasoning") {
|
|
21293
|
+
recordInteractiveProgress(
|
|
21294
|
+
sessionId,
|
|
21295
|
+
"reasoning",
|
|
21296
|
+
"\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u65B0\u7684\u601D\u8003/\u72B6\u6001\u8BF4\u660E\u3002"
|
|
21297
|
+
);
|
|
21298
|
+
continue;
|
|
21299
|
+
}
|
|
21300
|
+
if (record.type === "plan_update") {
|
|
21301
|
+
recordInteractiveProgress(
|
|
21302
|
+
sessionId,
|
|
21303
|
+
"plan_update",
|
|
21304
|
+
summarizePlanUpdate(record.tasks)
|
|
21305
|
+
);
|
|
21306
|
+
continue;
|
|
21307
|
+
}
|
|
20525
21308
|
if (record.type === "message") {
|
|
20526
21309
|
recordInteractiveProgress(
|
|
20527
21310
|
sessionId,
|
|
@@ -20536,7 +21319,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
20536
21319
|
const store = deps.getStore();
|
|
20537
21320
|
for (const session of store.listSessions()) {
|
|
20538
21321
|
if (!shouldTrackSession(session)) continue;
|
|
20539
|
-
const diagnosis =
|
|
21322
|
+
const diagnosis = applyStreamUiDiagnosis(
|
|
21323
|
+
{ ...computeBaseDiagnosis(session, nowMs), processProbe: null },
|
|
21324
|
+
nowMs
|
|
21325
|
+
);
|
|
20540
21326
|
updateSessionHealth(session.id, {
|
|
20541
21327
|
health_status: diagnosis.healthStatus,
|
|
20542
21328
|
health_reason: diagnosis.healthReason
|
|
@@ -20564,7 +21350,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
20564
21350
|
if (!session) return null;
|
|
20565
21351
|
const base = computeBaseDiagnosis(session, Date.now());
|
|
20566
21352
|
const processProbe = await loadProcessProbe(session);
|
|
20567
|
-
const diagnosis =
|
|
21353
|
+
const diagnosis = applyStreamUiDiagnosis(
|
|
21354
|
+
applyProcessProbeDiagnosis(base, processProbe),
|
|
21355
|
+
Date.now()
|
|
21356
|
+
);
|
|
20568
21357
|
updateSessionHealth(sessionId, {
|
|
20569
21358
|
health_status: diagnosis.healthStatus,
|
|
20570
21359
|
health_reason: diagnosis.healthReason,
|
|
@@ -20582,6 +21371,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
20582
21371
|
recordInteractiveStart,
|
|
20583
21372
|
recordInteractiveProgress,
|
|
20584
21373
|
recordToolState,
|
|
21374
|
+
recordStructuredStreamUi,
|
|
20585
21375
|
recordInteractiveEnd,
|
|
20586
21376
|
observeDesktopMirrorRecords,
|
|
20587
21377
|
reconcileSessionHealth,
|
|
@@ -20601,6 +21391,8 @@ var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
|
20601
21391
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
20602
21392
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
20603
21393
|
var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
|
|
21394
|
+
var MIRROR_STREAM_STATUS_IDLE_START_MS = 18e4;
|
|
21395
|
+
var MIRROR_STREAM_STATUS_HEARTBEAT_MS = 1e4;
|
|
20604
21396
|
var MIRROR_IDLE_TIMEOUT_MS = 6e5;
|
|
20605
21397
|
function describeUnknownError(error) {
|
|
20606
21398
|
if (error instanceof Error) {
|
|
@@ -20782,6 +21574,21 @@ function getMirrorStreamingText(subscription, turnState) {
|
|
|
20782
21574
|
);
|
|
20783
21575
|
return rendered || buildMirrorTitle(title, markdown);
|
|
20784
21576
|
}
|
|
21577
|
+
function getMirrorStructuredStreamStatusConfig() {
|
|
21578
|
+
const { store } = getBridgeContext();
|
|
21579
|
+
const idleStartSeconds = parseInt(store.getSetting("bridge_stream_status_idle_start_seconds") || "", 10);
|
|
21580
|
+
const heartbeatSeconds = parseInt(store.getSetting("bridge_stream_status_check_interval_seconds") || "", 10);
|
|
21581
|
+
return {
|
|
21582
|
+
idleStartMs: Math.max(
|
|
21583
|
+
0,
|
|
21584
|
+
(Number.isFinite(idleStartSeconds) && idleStartSeconds > 0 ? idleStartSeconds : MIRROR_STREAM_STATUS_IDLE_START_MS / 1e3) * 1e3
|
|
21585
|
+
),
|
|
21586
|
+
heartbeatMs: Math.max(
|
|
21587
|
+
1e3,
|
|
21588
|
+
(Number.isFinite(heartbeatSeconds) && heartbeatSeconds > 0 ? heartbeatSeconds : MIRROR_STREAM_STATUS_HEARTBEAT_MS / 1e3) * 1e3
|
|
21589
|
+
)
|
|
21590
|
+
};
|
|
21591
|
+
}
|
|
20785
21592
|
function startMirrorStreaming(subscription, turnState) {
|
|
20786
21593
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
20787
21594
|
if (!adapter || turnState.streamStarted) return;
|
|
@@ -20805,6 +21612,51 @@ function createMirrorStreamFeedbackTarget(subscription, turnState, adapter) {
|
|
|
20805
21612
|
}
|
|
20806
21613
|
};
|
|
20807
21614
|
}
|
|
21615
|
+
function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
21616
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
21617
|
+
if (!adapter || typeof adapter.onStreamStatus !== "function") return;
|
|
21618
|
+
if (!(adapter.supportsStructuredStreamingUi?.(subscription.chatId) ?? true)) return;
|
|
21619
|
+
const startedAtMs = Date.parse(turnState.startedAt);
|
|
21620
|
+
if (!Number.isFinite(startedAtMs)) return;
|
|
21621
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
21622
|
+
const minIntervalMs = Math.max(0, options.minIntervalMs ?? 0);
|
|
21623
|
+
if (minIntervalMs > 0 && turnState.lastStatusAt > 0 && nowMs - turnState.lastStatusAt < minIntervalMs) {
|
|
21624
|
+
return;
|
|
21625
|
+
}
|
|
21626
|
+
const statusText = formatInteractiveRuntimeStatus(
|
|
21627
|
+
Math.max(0, nowMs - startedAtMs),
|
|
21628
|
+
options.silentMs,
|
|
21629
|
+
turnState.statusNote
|
|
21630
|
+
);
|
|
21631
|
+
if (turnState.lastStatusText === statusText) return;
|
|
21632
|
+
pushStreamFeedbackStatus(
|
|
21633
|
+
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
21634
|
+
statusText
|
|
21635
|
+
);
|
|
21636
|
+
turnState.lastStatusText = statusText;
|
|
21637
|
+
turnState.lastStatusAt = nowMs;
|
|
21638
|
+
}
|
|
21639
|
+
function refreshMirrorStreamingStatus(subscription, nowMs = Date.now(), config2 = getMirrorStructuredStreamStatusConfig()) {
|
|
21640
|
+
const pendingTurn = subscription.pendingTurn;
|
|
21641
|
+
if (!pendingTurn?.streamStarted) return;
|
|
21642
|
+
const startedAtMs = Date.parse(pendingTurn.startedAt);
|
|
21643
|
+
const lastActivityMs = Date.parse(pendingTurn.lastActivityAt);
|
|
21644
|
+
if (!Number.isFinite(startedAtMs) || !Number.isFinite(lastActivityMs)) return;
|
|
21645
|
+
const elapsedMs = nowMs - startedAtMs;
|
|
21646
|
+
if (elapsedMs < config2.idleStartMs) return;
|
|
21647
|
+
const silentMs = nowMs - lastActivityMs;
|
|
21648
|
+
if (silentMs < config2.heartbeatMs) return;
|
|
21649
|
+
pushMirrorStreamingStatus(subscription, pendingTurn, {
|
|
21650
|
+
nowMs,
|
|
21651
|
+
silentMs,
|
|
21652
|
+
minIntervalMs: config2.heartbeatMs
|
|
21653
|
+
});
|
|
21654
|
+
}
|
|
21655
|
+
function refreshActiveMirrorStreamingStatuses(nowMs = Date.now()) {
|
|
21656
|
+
for (const subscription of getState().mirrorSubscriptions.values()) {
|
|
21657
|
+
refreshMirrorStreamingStatus(subscription, nowMs);
|
|
21658
|
+
}
|
|
21659
|
+
}
|
|
20808
21660
|
function updateMirrorStreaming(subscription, turnState) {
|
|
20809
21661
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
20810
21662
|
if (!adapter) return;
|
|
@@ -20812,6 +21664,7 @@ function updateMirrorStreaming(subscription, turnState) {
|
|
|
20812
21664
|
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
20813
21665
|
getMirrorStreamingText(subscription, turnState)
|
|
20814
21666
|
);
|
|
21667
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
20815
21668
|
}
|
|
20816
21669
|
function updateMirrorToolProgress(subscription, turnState) {
|
|
20817
21670
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
@@ -20820,6 +21673,21 @@ function updateMirrorToolProgress(subscription, turnState) {
|
|
|
20820
21673
|
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
20821
21674
|
Array.from(turnState.toolCalls.values())
|
|
20822
21675
|
);
|
|
21676
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
21677
|
+
}
|
|
21678
|
+
function updateMirrorTaskProgress(subscription, turnState) {
|
|
21679
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
21680
|
+
if (!adapter) return;
|
|
21681
|
+
pushStreamFeedbackTasks(
|
|
21682
|
+
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
21683
|
+
turnState.taskItems
|
|
21684
|
+
);
|
|
21685
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
21686
|
+
}
|
|
21687
|
+
function updateMirrorStatusProgress(subscription, turnState) {
|
|
21688
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
21689
|
+
if (!adapter) return;
|
|
21690
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
20823
21691
|
}
|
|
20824
21692
|
function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
20825
21693
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
@@ -20881,12 +21749,21 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
20881
21749
|
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
20882
21750
|
}
|
|
20883
21751
|
async function deliverMirrorTurns(subscription, turns) {
|
|
20884
|
-
|
|
20885
|
-
|
|
21752
|
+
let deliveredCount = 0;
|
|
21753
|
+
for (const turn of turns.slice(0, MIRROR_EVENT_BATCH_LIMIT)) {
|
|
21754
|
+
try {
|
|
21755
|
+
await deliverMirrorTurn(subscription, turn);
|
|
21756
|
+
deliveredCount += 1;
|
|
21757
|
+
} catch (error) {
|
|
21758
|
+
return { deliveredCount, error };
|
|
21759
|
+
}
|
|
20886
21760
|
}
|
|
21761
|
+
return { deliveredCount };
|
|
20887
21762
|
}
|
|
20888
21763
|
var MIRROR_TURN_HOOKS = {
|
|
20889
21764
|
onStreamText: updateMirrorStreaming,
|
|
21765
|
+
onStatusProgress: updateMirrorStatusProgress,
|
|
21766
|
+
onTaskProgress: updateMirrorTaskProgress,
|
|
20890
21767
|
onToolProgress: updateMirrorToolProgress
|
|
20891
21768
|
};
|
|
20892
21769
|
function consumeMirrorRecords2(subscription, records) {
|
|
@@ -20928,6 +21805,7 @@ function resetMirrorSessionForInteractiveRun(sessionId) {
|
|
|
20928
21805
|
}
|
|
20929
21806
|
async function reconcileMirrorSubscriptions() {
|
|
20930
21807
|
await MIRROR_RUNTIME.reconcileMirrorSubscriptions();
|
|
21808
|
+
refreshActiveMirrorStreamingStatuses();
|
|
20931
21809
|
}
|
|
20932
21810
|
function clearMirrorSubscriptions() {
|
|
20933
21811
|
MIRROR_RUNTIME.clearMirrorSubscriptions();
|
|
@@ -20978,6 +21856,7 @@ async function start() {
|
|
|
20978
21856
|
});
|
|
20979
21857
|
try {
|
|
20980
21858
|
SESSION_HEALTH_RUNTIME.reconcileSessionHealth();
|
|
21859
|
+
INTERACTIVE_RUNTIME.reconcileTerminalSessionRuntimeState();
|
|
20981
21860
|
} catch (err) {
|
|
20982
21861
|
console.error("[bridge-manager] Session health reconcile failed:", describeUnknownError(err));
|
|
20983
21862
|
}
|
|
@@ -21171,6 +22050,9 @@ async function handleMessage(adapter, msg) {
|
|
|
21171
22050
|
recordInteractiveHealthTool: (sessionId, toolId, toolName, status) => {
|
|
21172
22051
|
SESSION_HEALTH_RUNTIME.recordToolState(sessionId, toolId, toolName, status);
|
|
21173
22052
|
},
|
|
22053
|
+
recordInteractiveStreamUiSnapshot: (sessionId, snapshot) => {
|
|
22054
|
+
SESSION_HEALTH_RUNTIME.recordStructuredStreamUi(sessionId, snapshot);
|
|
22055
|
+
},
|
|
21174
22056
|
recordInteractiveHealthEnd: (sessionId, outcome, detail) => SESSION_HEALTH_RUNTIME.recordInteractiveEnd(sessionId, outcome, detail),
|
|
21175
22057
|
beginMirrorSuppression: beginMirrorSuppression2,
|
|
21176
22058
|
abortMirrorSuppression: abortMirrorSuppression2,
|