codex-to-im 1.0.40 → 1.0.42
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/cli.mjs +106 -57
- package/dist/daemon.mjs +1051 -161
- package/dist/ui-server.mjs +181 -130
- package/package.json +2 -2
- package/scripts/patch-codex-sdk-windows-hide.js +1 -1
package/dist/daemon.mjs
CHANGED
|
@@ -3935,9 +3935,9 @@ var codex_provider_exports = {};
|
|
|
3935
3935
|
__export(codex_provider_exports, {
|
|
3936
3936
|
CodexProvider: () => CodexProvider
|
|
3937
3937
|
});
|
|
3938
|
-
import
|
|
3938
|
+
import fs15 from "node:fs";
|
|
3939
3939
|
import os4 from "node:os";
|
|
3940
|
-
import
|
|
3940
|
+
import path16 from "node:path";
|
|
3941
3941
|
function toApprovalPolicy(permissionMode) {
|
|
3942
3942
|
switch (permissionMode) {
|
|
3943
3943
|
case "never":
|
|
@@ -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"() {
|
|
@@ -3979,6 +4028,9 @@ var init_codex_provider = __esm({
|
|
|
3979
4028
|
threadIds = /* @__PURE__ */ new Map();
|
|
3980
4029
|
constructor(_pendingPerms) {
|
|
3981
4030
|
}
|
|
4031
|
+
clearCachedThreadId(sessionId) {
|
|
4032
|
+
this.threadIds.delete(sessionId);
|
|
4033
|
+
}
|
|
3982
4034
|
/**
|
|
3983
4035
|
* Lazily load the Codex SDK. Throws a clear error if the installation is incomplete.
|
|
3984
4036
|
*/
|
|
@@ -4032,13 +4084,13 @@ var init_codex_provider = __esm({
|
|
|
4032
4084
|
{ type: "text", text: params.prompt }
|
|
4033
4085
|
];
|
|
4034
4086
|
for (const file of imageFiles) {
|
|
4035
|
-
if (file.filePath &&
|
|
4087
|
+
if (file.filePath && fs15.existsSync(file.filePath)) {
|
|
4036
4088
|
parts.push({ type: "local_image", path: file.filePath });
|
|
4037
4089
|
continue;
|
|
4038
4090
|
}
|
|
4039
4091
|
const ext = MIME_EXT[file.type] || ".png";
|
|
4040
|
-
const tmpPath =
|
|
4041
|
-
|
|
4092
|
+
const tmpPath = path16.join(os4.tmpdir(), `cti-img-${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`);
|
|
4093
|
+
fs15.writeFileSync(tmpPath, Buffer.from(file.data, "base64"));
|
|
4042
4094
|
tempFiles.push(tmpPath);
|
|
4043
4095
|
parts.push({ type: "local_image", path: tmpPath });
|
|
4044
4096
|
}
|
|
@@ -4047,6 +4099,7 @@ var init_codex_provider = __esm({
|
|
|
4047
4099
|
input = params.prompt;
|
|
4048
4100
|
}
|
|
4049
4101
|
let retryFresh = false;
|
|
4102
|
+
const emittedToolStarts = /* @__PURE__ */ new Set();
|
|
4050
4103
|
while (true) {
|
|
4051
4104
|
let thread;
|
|
4052
4105
|
if (savedThreadId) {
|
|
@@ -4078,9 +4131,19 @@ var init_codex_provider = __esm({
|
|
|
4078
4131
|
}));
|
|
4079
4132
|
break;
|
|
4080
4133
|
}
|
|
4134
|
+
case "turn.started":
|
|
4135
|
+
break;
|
|
4136
|
+
case "item.started":
|
|
4137
|
+
case "item.updated":
|
|
4081
4138
|
case "item.completed": {
|
|
4082
4139
|
const item = event.item;
|
|
4083
|
-
self.
|
|
4140
|
+
self.handleItemEvent(
|
|
4141
|
+
controller,
|
|
4142
|
+
item,
|
|
4143
|
+
event.type === "item.started" ? "started" : event.type === "item.updated" ? "updated" : "completed",
|
|
4144
|
+
params.sessionId,
|
|
4145
|
+
emittedToolStarts
|
|
4146
|
+
);
|
|
4084
4147
|
break;
|
|
4085
4148
|
}
|
|
4086
4149
|
case "turn.completed": {
|
|
@@ -4098,17 +4161,27 @@ var init_codex_provider = __esm({
|
|
|
4098
4161
|
break;
|
|
4099
4162
|
}
|
|
4100
4163
|
case "turn.failed": {
|
|
4101
|
-
const error = event.message;
|
|
4102
|
-
|
|
4164
|
+
const error = event.error?.message;
|
|
4165
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4166
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Turn failed")));
|
|
4103
4167
|
sawTerminalEvent = true;
|
|
4104
4168
|
break;
|
|
4105
4169
|
}
|
|
4106
4170
|
case "error": {
|
|
4107
4171
|
const error = event.message;
|
|
4108
|
-
|
|
4172
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4173
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(error || "Thread error")));
|
|
4109
4174
|
sawTerminalEvent = true;
|
|
4110
4175
|
break;
|
|
4111
4176
|
}
|
|
4177
|
+
default: {
|
|
4178
|
+
const exhaustiveEvent = event;
|
|
4179
|
+
console.warn(
|
|
4180
|
+
"[codex-provider] Unhandled thread event:",
|
|
4181
|
+
stringifyUnknown(exhaustiveEvent)
|
|
4182
|
+
);
|
|
4183
|
+
break;
|
|
4184
|
+
}
|
|
4112
4185
|
}
|
|
4113
4186
|
if (sawTerminalEvent) {
|
|
4114
4187
|
break;
|
|
@@ -4119,10 +4192,12 @@ var init_codex_provider = __esm({
|
|
|
4119
4192
|
const message = err instanceof Error ? err.message : String(err);
|
|
4120
4193
|
if (savedThreadId && !retryFresh && !sawAnyEvent && shouldRetryFreshThread(message)) {
|
|
4121
4194
|
console.warn("[codex-provider] Resume failed, retrying with a fresh thread:", message);
|
|
4195
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4122
4196
|
savedThreadId = void 0;
|
|
4123
4197
|
retryFresh = true;
|
|
4124
4198
|
continue;
|
|
4125
4199
|
}
|
|
4200
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4126
4201
|
throw err;
|
|
4127
4202
|
}
|
|
4128
4203
|
}
|
|
@@ -4130,15 +4205,16 @@ var init_codex_provider = __esm({
|
|
|
4130
4205
|
} catch (err) {
|
|
4131
4206
|
const message = err instanceof Error ? err.message : String(err);
|
|
4132
4207
|
console.error("[codex-provider] Error:", err instanceof Error ? err.stack || err.message : err);
|
|
4208
|
+
self.clearCachedThreadId(params.sessionId);
|
|
4133
4209
|
try {
|
|
4134
|
-
controller.enqueue(sseEvent("error", message));
|
|
4210
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(message)));
|
|
4135
4211
|
controller.close();
|
|
4136
4212
|
} catch {
|
|
4137
4213
|
}
|
|
4138
4214
|
} finally {
|
|
4139
4215
|
for (const tmp of tempFiles) {
|
|
4140
4216
|
try {
|
|
4141
|
-
|
|
4217
|
+
fs15.unlinkSync(tmp);
|
|
4142
4218
|
} catch {
|
|
4143
4219
|
}
|
|
4144
4220
|
}
|
|
@@ -4148,12 +4224,22 @@ var init_codex_provider = __esm({
|
|
|
4148
4224
|
});
|
|
4149
4225
|
}
|
|
4150
4226
|
/**
|
|
4151
|
-
* Map a
|
|
4227
|
+
* Map a Codex item event to SSE events.
|
|
4152
4228
|
*/
|
|
4153
|
-
|
|
4229
|
+
handleItemEvent(controller, item, phase, sessionId, emittedToolStarts) {
|
|
4154
4230
|
const itemType = item.type;
|
|
4231
|
+
const ensureToolUse = (toolId, name, input) => {
|
|
4232
|
+
if (emittedToolStarts.has(toolId)) return;
|
|
4233
|
+
emittedToolStarts.add(toolId);
|
|
4234
|
+
controller.enqueue(sseEvent("tool_use", {
|
|
4235
|
+
id: toolId,
|
|
4236
|
+
name,
|
|
4237
|
+
input
|
|
4238
|
+
}));
|
|
4239
|
+
};
|
|
4155
4240
|
switch (itemType) {
|
|
4156
4241
|
case "agent_message": {
|
|
4242
|
+
if (phase !== "completed") break;
|
|
4157
4243
|
const text2 = item.text || "";
|
|
4158
4244
|
if (text2) {
|
|
4159
4245
|
controller.enqueue(sseEvent("text", text2));
|
|
@@ -4165,12 +4251,11 @@ var init_codex_provider = __esm({
|
|
|
4165
4251
|
const command = item.command || "";
|
|
4166
4252
|
const output = item.aggregated_output || "";
|
|
4167
4253
|
const exitCode = item.exit_code;
|
|
4254
|
+
const status = item.status;
|
|
4168
4255
|
const isError = exitCode != null && exitCode !== 0;
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
input: { command }
|
|
4173
|
-
}));
|
|
4256
|
+
const terminal = phase === "completed" || status === "completed" || status === "failed";
|
|
4257
|
+
ensureToolUse(toolId, "Bash", { command });
|
|
4258
|
+
if (!terminal) break;
|
|
4174
4259
|
const resultContent = output || (isError ? `Exit code: ${exitCode}` : "Done");
|
|
4175
4260
|
controller.enqueue(sseEvent("tool_result", {
|
|
4176
4261
|
tool_use_id: toolId,
|
|
@@ -4180,14 +4265,11 @@ var init_codex_provider = __esm({
|
|
|
4180
4265
|
break;
|
|
4181
4266
|
}
|
|
4182
4267
|
case "file_change": {
|
|
4268
|
+
if (phase !== "completed") break;
|
|
4183
4269
|
const toolId = item.id || `tool-${Date.now()}`;
|
|
4184
4270
|
const changes = item.changes || [];
|
|
4185
4271
|
const summary = changes.map((c) => `${c.kind}: ${c.path}`).join("\n");
|
|
4186
|
-
|
|
4187
|
-
id: toolId,
|
|
4188
|
-
name: "Edit",
|
|
4189
|
-
input: { files: changes }
|
|
4190
|
-
}));
|
|
4272
|
+
ensureToolUse(toolId, "Edit", { files: changes });
|
|
4191
4273
|
controller.enqueue(sseEvent("tool_result", {
|
|
4192
4274
|
tool_use_id: toolId,
|
|
4193
4275
|
content: summary || "File changes applied",
|
|
@@ -4202,13 +4284,11 @@ var init_codex_provider = __esm({
|
|
|
4202
4284
|
const args = item.arguments;
|
|
4203
4285
|
const result = item.result;
|
|
4204
4286
|
const error = item.error;
|
|
4205
|
-
const
|
|
4206
|
-
const
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
input: args
|
|
4211
|
-
}));
|
|
4287
|
+
const status = item.status;
|
|
4288
|
+
const terminal = phase === "completed" || status === "completed" || status === "failed";
|
|
4289
|
+
const resultText = extractMcpContentText(result?.content) || stringifyUnknown(result?.structured_content) || stringifyUnknown(result?.content);
|
|
4290
|
+
ensureToolUse(toolId, `mcp__${server}__${tool}`, args);
|
|
4291
|
+
if (!terminal) break;
|
|
4212
4292
|
controller.enqueue(sseEvent("tool_result", {
|
|
4213
4293
|
tool_use_id: toolId,
|
|
4214
4294
|
content: error?.message || resultText || "Done",
|
|
@@ -4216,6 +4296,18 @@ var init_codex_provider = __esm({
|
|
|
4216
4296
|
}));
|
|
4217
4297
|
break;
|
|
4218
4298
|
}
|
|
4299
|
+
case "web_search": {
|
|
4300
|
+
const toolId = item.id || `tool-${Date.now()}`;
|
|
4301
|
+
const query2 = item.query || "";
|
|
4302
|
+
ensureToolUse(toolId, "Web Search", { query: query2 });
|
|
4303
|
+
if (phase !== "completed") break;
|
|
4304
|
+
controller.enqueue(sseEvent("tool_result", {
|
|
4305
|
+
tool_use_id: toolId,
|
|
4306
|
+
content: query2 || "Search completed",
|
|
4307
|
+
is_error: false
|
|
4308
|
+
}));
|
|
4309
|
+
break;
|
|
4310
|
+
}
|
|
4219
4311
|
case "reasoning": {
|
|
4220
4312
|
const text2 = item.text || "";
|
|
4221
4313
|
if (text2) {
|
|
@@ -4223,15 +4315,41 @@ var init_codex_provider = __esm({
|
|
|
4223
4315
|
}
|
|
4224
4316
|
break;
|
|
4225
4317
|
}
|
|
4318
|
+
case "todo_list": {
|
|
4319
|
+
const tasks = mapTodoListItems(item.items);
|
|
4320
|
+
controller.enqueue(sseEvent("task_update", {
|
|
4321
|
+
session_id: sessionId,
|
|
4322
|
+
sdk_session_id: this.threadIds.get(sessionId) || void 0,
|
|
4323
|
+
tasks,
|
|
4324
|
+
todos: tasks
|
|
4325
|
+
}));
|
|
4326
|
+
break;
|
|
4327
|
+
}
|
|
4328
|
+
case "error": {
|
|
4329
|
+
this.clearCachedThreadId(sessionId);
|
|
4330
|
+
controller.enqueue(sseEvent("error", normalizeCodexErrorMessage(item.message || "Codex error")));
|
|
4331
|
+
break;
|
|
4332
|
+
}
|
|
4333
|
+
default: {
|
|
4334
|
+
const exhaustiveItem = item;
|
|
4335
|
+
console.warn(
|
|
4336
|
+
"[codex-provider] Unhandled thread item:",
|
|
4337
|
+
stringifyUnknown(exhaustiveItem)
|
|
4338
|
+
);
|
|
4339
|
+
break;
|
|
4340
|
+
}
|
|
4226
4341
|
}
|
|
4227
4342
|
}
|
|
4343
|
+
handleCompletedItem(controller, item) {
|
|
4344
|
+
this.handleItemEvent(controller, item, "completed", "test-session", /* @__PURE__ */ new Set());
|
|
4345
|
+
}
|
|
4228
4346
|
};
|
|
4229
4347
|
}
|
|
4230
4348
|
});
|
|
4231
4349
|
|
|
4232
4350
|
// src/main.ts
|
|
4233
|
-
import
|
|
4234
|
-
import
|
|
4351
|
+
import fs16 from "node:fs";
|
|
4352
|
+
import path17 from "node:path";
|
|
4235
4353
|
import crypto10 from "node:crypto";
|
|
4236
4354
|
|
|
4237
4355
|
// src/lib/bridge/context.ts
|
|
@@ -5515,6 +5633,14 @@ function buildToolProgressMarkdown(tools) {
|
|
|
5515
5633
|
});
|
|
5516
5634
|
return lines.join("\n");
|
|
5517
5635
|
}
|
|
5636
|
+
function buildTaskProgressMarkdown(tasks) {
|
|
5637
|
+
if (tasks.length === 0) return "";
|
|
5638
|
+
return tasks.map((task) => {
|
|
5639
|
+
const icon = task.status === "completed" ? "\u2705" : task.status === "in_progress" ? "\u{1F504}" : "\u23F3";
|
|
5640
|
+
const label = task.status === "completed" ? "\u5DF2\u5B8C\u6210" : task.status === "in_progress" ? "\u6267\u884C\u4E2D" : "\u7B49\u5F85\u4E2D";
|
|
5641
|
+
return `${icon} ${task.text}\uFF08${label}\uFF09`;
|
|
5642
|
+
}).join("\n");
|
|
5643
|
+
}
|
|
5518
5644
|
function formatElapsed(ms) {
|
|
5519
5645
|
if (ms < 1e3) return `${ms}ms`;
|
|
5520
5646
|
const sec = ms / 1e3;
|
|
@@ -5529,9 +5655,13 @@ function buildStreamingTextContent(text2) {
|
|
|
5529
5655
|
function buildStreamingToolsContent(tools) {
|
|
5530
5656
|
return buildToolProgressMarkdown(tools);
|
|
5531
5657
|
}
|
|
5532
|
-
function
|
|
5658
|
+
function buildStreamingTaskContent(tasks) {
|
|
5659
|
+
return buildTaskProgressMarkdown(tasks);
|
|
5660
|
+
}
|
|
5661
|
+
function buildFinalCardJson(text2, tasks, tools, footer) {
|
|
5533
5662
|
const elements = [];
|
|
5534
5663
|
const content = preprocessFeishuMarkdown(text2);
|
|
5664
|
+
const taskMd = buildTaskProgressMarkdown(tasks);
|
|
5535
5665
|
const toolMd = buildToolProgressMarkdown(tools);
|
|
5536
5666
|
if (content) {
|
|
5537
5667
|
elements.push({
|
|
@@ -5541,6 +5671,17 @@ function buildFinalCardJson(text2, tools, footer) {
|
|
|
5541
5671
|
text_size: "normal"
|
|
5542
5672
|
});
|
|
5543
5673
|
}
|
|
5674
|
+
if (taskMd) {
|
|
5675
|
+
if (elements.length > 0) {
|
|
5676
|
+
elements.push({ tag: "hr" });
|
|
5677
|
+
}
|
|
5678
|
+
elements.push({
|
|
5679
|
+
tag: "markdown",
|
|
5680
|
+
content: taskMd,
|
|
5681
|
+
text_align: "left",
|
|
5682
|
+
text_size: "normal"
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5544
5685
|
if (toolMd) {
|
|
5545
5686
|
if (elements.length > 0) {
|
|
5546
5687
|
elements.push({ tag: "hr" });
|
|
@@ -5626,7 +5767,9 @@ var DEDUP_MAX = 1e3;
|
|
|
5626
5767
|
var MAX_FILE_SIZE = 20 * 1024 * 1024;
|
|
5627
5768
|
var TYPING_EMOJI = "Typing";
|
|
5628
5769
|
var CARD_THROTTLE_MS = 200;
|
|
5770
|
+
var CARD_REQUEST_TIMEOUT_MS = 15e3;
|
|
5629
5771
|
var INITIAL_STREAMING_STATUS = "\u5904\u7406\u4E2D";
|
|
5772
|
+
var EMPTY_STREAMING_TASKS = "";
|
|
5630
5773
|
var EMPTY_STREAMING_TOOLS = "";
|
|
5631
5774
|
var MIME_BY_TYPE = {
|
|
5632
5775
|
image: "image/png",
|
|
@@ -5657,6 +5800,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5657
5800
|
cardCreatePromises = /* @__PURE__ */ new Map();
|
|
5658
5801
|
/** Cached tenant token for upload APIs. */
|
|
5659
5802
|
tenantTokenCache = null;
|
|
5803
|
+
cardRequestTimeoutMs = CARD_REQUEST_TIMEOUT_MS;
|
|
5660
5804
|
constructor(instance) {
|
|
5661
5805
|
super();
|
|
5662
5806
|
this.channelType = instance?.id || "feishu";
|
|
@@ -5884,6 +6028,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5884
6028
|
text_size: "normal",
|
|
5885
6029
|
element_id: "streaming_content"
|
|
5886
6030
|
},
|
|
6031
|
+
{
|
|
6032
|
+
tag: "markdown",
|
|
6033
|
+
content: EMPTY_STREAMING_TASKS,
|
|
6034
|
+
text_align: "left",
|
|
6035
|
+
text_size: "normal",
|
|
6036
|
+
element_id: "streaming_tasks"
|
|
6037
|
+
},
|
|
5887
6038
|
{
|
|
5888
6039
|
tag: "markdown",
|
|
5889
6040
|
content: EMPTY_STREAMING_TOOLS,
|
|
@@ -5901,9 +6052,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5901
6052
|
]
|
|
5902
6053
|
}
|
|
5903
6054
|
};
|
|
5904
|
-
const createResp = await cardkit.card.create({
|
|
6055
|
+
const createResp = await this.withFeishuRequestTimeout(cardKey, "card.create", () => cardkit.card.create({
|
|
5905
6056
|
data: { type: "card_json", data: JSON.stringify(cardBody) }
|
|
5906
|
-
});
|
|
6057
|
+
}));
|
|
5907
6058
|
const cardId = createResp?.data?.card_id;
|
|
5908
6059
|
if (!cardId) {
|
|
5909
6060
|
console.warn("[feishu-adapter] Card create returned no card_id");
|
|
@@ -5912,19 +6063,19 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5912
6063
|
const cardContent = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
5913
6064
|
let msgResp;
|
|
5914
6065
|
if (replyToMessageId) {
|
|
5915
|
-
msgResp = await this.restClient.im.message.reply({
|
|
6066
|
+
msgResp = await this.withFeishuRequestTimeout(cardKey, "im.message.reply:interactive", () => this.restClient.im.message.reply({
|
|
5916
6067
|
path: { message_id: replyToMessageId },
|
|
5917
6068
|
data: { content: cardContent, msg_type: "interactive" }
|
|
5918
|
-
});
|
|
6069
|
+
}));
|
|
5919
6070
|
} else {
|
|
5920
|
-
msgResp = await this.restClient.im.message.create({
|
|
6071
|
+
msgResp = await this.withFeishuRequestTimeout(cardKey, "im.message.create:interactive", () => this.restClient.im.message.create({
|
|
5921
6072
|
params: { receive_id_type: "chat_id" },
|
|
5922
6073
|
data: {
|
|
5923
6074
|
receive_id: chatId,
|
|
5924
6075
|
msg_type: "interactive",
|
|
5925
6076
|
content: cardContent
|
|
5926
6077
|
}
|
|
5927
|
-
});
|
|
6078
|
+
}));
|
|
5928
6079
|
}
|
|
5929
6080
|
const messageId = msgResp?.data?.message_id;
|
|
5930
6081
|
if (!messageId) {
|
|
@@ -5937,17 +6088,25 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5937
6088
|
messageId,
|
|
5938
6089
|
sequence: 0,
|
|
5939
6090
|
startTime: Date.now(),
|
|
6091
|
+
taskItems: [],
|
|
5940
6092
|
toolCalls: [],
|
|
5941
6093
|
thinking: true,
|
|
5942
6094
|
pendingText: null,
|
|
6095
|
+
pendingTasksText: EMPTY_STREAMING_TASKS,
|
|
5943
6096
|
pendingStatusText: INITIAL_STREAMING_STATUS,
|
|
5944
6097
|
renderedText: "\u{1F4AD} Thinking...",
|
|
6098
|
+
renderedTasksText: EMPTY_STREAMING_TASKS,
|
|
5945
6099
|
renderedToolsText: EMPTY_STREAMING_TOOLS,
|
|
5946
6100
|
renderedStatusText: INITIAL_STREAMING_STATUS,
|
|
5947
6101
|
lastUpdateAt: 0,
|
|
5948
6102
|
throttleTimer: null,
|
|
5949
6103
|
flushInFlight: null,
|
|
5950
|
-
flushQueued: false
|
|
6104
|
+
flushQueued: false,
|
|
6105
|
+
lastFlushStartedAt: null,
|
|
6106
|
+
lastSuccessfulFlushAt: null,
|
|
6107
|
+
lastFlushErrorAt: null,
|
|
6108
|
+
lastFlushError: null,
|
|
6109
|
+
consecutiveFlushFailures: 0
|
|
5951
6110
|
});
|
|
5952
6111
|
console.log(`[feishu-adapter] Streaming card created: streamKey=${cardKey}, cardId=${cardId}, msgId=${messageId}`);
|
|
5953
6112
|
return true;
|
|
@@ -5976,6 +6135,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5976
6135
|
state.pendingStatusText = statusText || INITIAL_STREAMING_STATUS;
|
|
5977
6136
|
this.scheduleCardFlush(cardKey);
|
|
5978
6137
|
}
|
|
6138
|
+
updateTaskProgress(chatId, tasks, streamKey) {
|
|
6139
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
6140
|
+
const state = this.activeCards.get(cardKey);
|
|
6141
|
+
if (!state) return;
|
|
6142
|
+
state.taskItems = tasks;
|
|
6143
|
+
state.pendingTasksText = buildStreamingTaskContent(tasks) || EMPTY_STREAMING_TASKS;
|
|
6144
|
+
this.scheduleCardFlush(cardKey);
|
|
6145
|
+
}
|
|
5979
6146
|
enqueueCardFlush(streamKey) {
|
|
5980
6147
|
const state = this.activeCards.get(streamKey);
|
|
5981
6148
|
if (!state) return;
|
|
@@ -5983,6 +6150,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5983
6150
|
state.flushQueued = true;
|
|
5984
6151
|
return;
|
|
5985
6152
|
}
|
|
6153
|
+
state.lastFlushStartedAt = Date.now();
|
|
5986
6154
|
state.flushInFlight = this.flushCardUpdate(streamKey).catch((err) => {
|
|
5987
6155
|
console.warn("[feishu-adapter] cardElement.content failed:", err instanceof Error ? err.message : err);
|
|
5988
6156
|
}).finally(() => {
|
|
@@ -6023,6 +6191,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6023
6191
|
const cardkit = this.restClient.cardkit?.v1;
|
|
6024
6192
|
if (!cardkit?.cardElement?.content) return;
|
|
6025
6193
|
const content = buildStreamingTextContent(state.pendingText || "");
|
|
6194
|
+
const tasksText = state.pendingTasksText || EMPTY_STREAMING_TASKS;
|
|
6026
6195
|
const toolsText = buildStreamingToolsContent(state.toolCalls) || EMPTY_STREAMING_TOOLS;
|
|
6027
6196
|
const statusText = state.pendingStatusText || INITIAL_STREAMING_STATUS;
|
|
6028
6197
|
const updates = [];
|
|
@@ -6035,6 +6204,15 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6035
6204
|
}
|
|
6036
6205
|
});
|
|
6037
6206
|
}
|
|
6207
|
+
if (tasksText !== state.renderedTasksText) {
|
|
6208
|
+
updates.push({
|
|
6209
|
+
elementId: "streaming_tasks",
|
|
6210
|
+
content: tasksText,
|
|
6211
|
+
onSuccess: () => {
|
|
6212
|
+
state.renderedTasksText = tasksText;
|
|
6213
|
+
}
|
|
6214
|
+
});
|
|
6215
|
+
}
|
|
6038
6216
|
if (toolsText !== state.renderedToolsText) {
|
|
6039
6217
|
updates.push({
|
|
6040
6218
|
elementId: "streaming_tools",
|
|
@@ -6058,13 +6236,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6058
6236
|
for (const update of updates) {
|
|
6059
6237
|
state.sequence++;
|
|
6060
6238
|
try {
|
|
6061
|
-
await cardkit.cardElement.content({
|
|
6239
|
+
await this.withFeishuRequestTimeout(streamKey, `cardElement.content:${update.elementId}`, () => cardkit.cardElement.content({
|
|
6062
6240
|
path: { card_id: cardId, element_id: update.elementId },
|
|
6063
6241
|
data: { content: update.content, sequence: state.sequence }
|
|
6064
|
-
});
|
|
6242
|
+
}));
|
|
6065
6243
|
update.onSuccess();
|
|
6066
|
-
|
|
6244
|
+
this.markCardFlushSuccess(state);
|
|
6067
6245
|
} catch (err) {
|
|
6246
|
+
this.markCardFlushFailure(state, err);
|
|
6068
6247
|
console.warn(
|
|
6069
6248
|
`[feishu-adapter] cardElement.content failed for ${update.elementId}:`,
|
|
6070
6249
|
err instanceof Error ? err.message : err
|
|
@@ -6125,13 +6304,13 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6125
6304
|
await this.awaitCardFlushCompletion(cardKey);
|
|
6126
6305
|
try {
|
|
6127
6306
|
state.sequence++;
|
|
6128
|
-
await cardkit.card.settings({
|
|
6307
|
+
await this.withFeishuRequestTimeout(cardKey, "card.settings", () => cardkit.card.settings({
|
|
6129
6308
|
path: { card_id: state.cardId },
|
|
6130
6309
|
data: {
|
|
6131
6310
|
settings: JSON.stringify({ streaming_mode: false }),
|
|
6132
6311
|
sequence: state.sequence
|
|
6133
6312
|
}
|
|
6134
|
-
});
|
|
6313
|
+
}));
|
|
6135
6314
|
const statusLabels = {
|
|
6136
6315
|
completed: "\u2705 Completed",
|
|
6137
6316
|
interrupted: "\u26A0\uFE0F Interrupted",
|
|
@@ -6151,15 +6330,15 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6151
6330
|
|
|
6152
6331
|
${trimmedResponse}`;
|
|
6153
6332
|
}
|
|
6154
|
-
const finalCardJson = buildFinalCardJson(finalText, state.toolCalls, footer);
|
|
6333
|
+
const finalCardJson = buildFinalCardJson(finalText, state.taskItems, state.toolCalls, footer);
|
|
6155
6334
|
state.sequence++;
|
|
6156
|
-
await cardkit.card.update({
|
|
6335
|
+
await this.withFeishuRequestTimeout(cardKey, "card.update", () => cardkit.card.update({
|
|
6157
6336
|
path: { card_id: state.cardId },
|
|
6158
6337
|
data: {
|
|
6159
6338
|
card: { type: "card_json", data: finalCardJson },
|
|
6160
6339
|
sequence: state.sequence
|
|
6161
6340
|
}
|
|
6162
|
-
});
|
|
6341
|
+
}));
|
|
6163
6342
|
console.log(`[feishu-adapter] Card finalized: streamKey=${cardKey}, cardId=${state.cardId}, status=${status}, elapsed=${formatElapsed(elapsedMs)}`);
|
|
6164
6343
|
return true;
|
|
6165
6344
|
} catch (err) {
|
|
@@ -6191,6 +6370,76 @@ ${trimmedResponse}`;
|
|
|
6191
6370
|
hasActiveStreamingUi(chatId, streamKey) {
|
|
6192
6371
|
return this.hasActiveCard(chatId, streamKey);
|
|
6193
6372
|
}
|
|
6373
|
+
getStructuredStreamingUiSnapshot(chatId, streamKey) {
|
|
6374
|
+
const state = this.activeCards.get(this.resolveStreamKey(chatId, streamKey));
|
|
6375
|
+
if (!state) return null;
|
|
6376
|
+
return {
|
|
6377
|
+
active: true,
|
|
6378
|
+
lastAttemptAt: state.lastFlushStartedAt,
|
|
6379
|
+
lastUpdateAt: state.lastSuccessfulFlushAt ?? (state.lastUpdateAt > 0 ? state.lastUpdateAt : null),
|
|
6380
|
+
lastErrorAt: state.lastFlushErrorAt,
|
|
6381
|
+
lastError: state.lastFlushError,
|
|
6382
|
+
flushInFlight: Boolean(state.flushInFlight),
|
|
6383
|
+
flushInFlightSince: state.flushInFlight ? state.lastFlushStartedAt : null,
|
|
6384
|
+
consecutiveFailures: state.consecutiveFlushFailures
|
|
6385
|
+
};
|
|
6386
|
+
}
|
|
6387
|
+
getCardRequestTimeoutMs() {
|
|
6388
|
+
return Math.max(1, this.cardRequestTimeoutMs);
|
|
6389
|
+
}
|
|
6390
|
+
logRequestOperation(phase, scope, target, startedAt, detail) {
|
|
6391
|
+
const durationMs = Math.max(0, Date.now() - startedAt);
|
|
6392
|
+
const suffix = detail ? `, detail=${detail}` : "";
|
|
6393
|
+
const line = `[feishu-adapter] Request ${phase}: scope=${scope}, target=${target}, duration=${durationMs}ms${suffix}`;
|
|
6394
|
+
if (phase === "start" || phase === "success") {
|
|
6395
|
+
console.log(line);
|
|
6396
|
+
return;
|
|
6397
|
+
}
|
|
6398
|
+
console.warn(line);
|
|
6399
|
+
}
|
|
6400
|
+
async withFeishuRequestTimeout(scope, target, operation) {
|
|
6401
|
+
const startedAt = Date.now();
|
|
6402
|
+
const timeoutMs = this.getCardRequestTimeoutMs();
|
|
6403
|
+
this.logRequestOperation("start", scope, target, startedAt);
|
|
6404
|
+
let timeoutHandle = null;
|
|
6405
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
6406
|
+
timeoutHandle = setTimeout(() => {
|
|
6407
|
+
reject(new Error(`timeout after ${timeoutMs}ms`));
|
|
6408
|
+
}, timeoutMs);
|
|
6409
|
+
});
|
|
6410
|
+
const operationPromise = operation();
|
|
6411
|
+
operationPromise.catch(() => {
|
|
6412
|
+
});
|
|
6413
|
+
try {
|
|
6414
|
+
const result = await Promise.race([operationPromise, timeoutPromise]);
|
|
6415
|
+
this.logRequestOperation("success", scope, target, startedAt);
|
|
6416
|
+
return result;
|
|
6417
|
+
} catch (error) {
|
|
6418
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
6419
|
+
this.logRequestOperation(
|
|
6420
|
+
detail.startsWith("timeout after ") ? "timeout" : "error",
|
|
6421
|
+
scope,
|
|
6422
|
+
target,
|
|
6423
|
+
startedAt,
|
|
6424
|
+
detail
|
|
6425
|
+
);
|
|
6426
|
+
throw error;
|
|
6427
|
+
} finally {
|
|
6428
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
6429
|
+
}
|
|
6430
|
+
}
|
|
6431
|
+
markCardFlushFailure(state, error) {
|
|
6432
|
+
state.lastFlushErrorAt = Date.now();
|
|
6433
|
+
state.lastFlushError = error instanceof Error ? error.message : String(error);
|
|
6434
|
+
state.consecutiveFlushFailures += 1;
|
|
6435
|
+
}
|
|
6436
|
+
markCardFlushSuccess(state) {
|
|
6437
|
+
const now2 = Date.now();
|
|
6438
|
+
state.lastUpdateAt = now2;
|
|
6439
|
+
state.lastSuccessfulFlushAt = now2;
|
|
6440
|
+
state.lastFlushError = null;
|
|
6441
|
+
state.consecutiveFlushFailures = 0;
|
|
6442
|
+
}
|
|
6194
6443
|
// ── Streaming adapter interface ────────────────────────────────
|
|
6195
6444
|
/**
|
|
6196
6445
|
* Called by bridge-manager on each text SSE event.
|
|
@@ -6218,8 +6467,30 @@ ${trimmedResponse}`;
|
|
|
6218
6467
|
}
|
|
6219
6468
|
onToolEvent(chatId, tools, streamKey) {
|
|
6220
6469
|
if (!this.isStreamingEnabled()) return;
|
|
6470
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
6471
|
+
if (!this.activeCards.has(cardKey)) {
|
|
6472
|
+
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
6473
|
+
this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
|
|
6474
|
+
if (ok) this.updateToolProgress(chatId, tools, cardKey);
|
|
6475
|
+
}).catch(() => {
|
|
6476
|
+
});
|
|
6477
|
+
return;
|
|
6478
|
+
}
|
|
6221
6479
|
this.updateToolProgress(chatId, tools, streamKey);
|
|
6222
6480
|
}
|
|
6481
|
+
onTaskEvent(chatId, tasks, streamKey) {
|
|
6482
|
+
if (!this.isStreamingEnabled()) return;
|
|
6483
|
+
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
6484
|
+
if (!this.activeCards.has(cardKey)) {
|
|
6485
|
+
const messageId = this.lastIncomingMessageId.get(chatId);
|
|
6486
|
+
this.createStreamingCard(chatId, messageId, cardKey).then((ok) => {
|
|
6487
|
+
if (ok) this.updateTaskProgress(chatId, tasks, cardKey);
|
|
6488
|
+
}).catch(() => {
|
|
6489
|
+
});
|
|
6490
|
+
return;
|
|
6491
|
+
}
|
|
6492
|
+
this.updateTaskProgress(chatId, tasks, streamKey);
|
|
6493
|
+
}
|
|
6223
6494
|
onStreamStatus(chatId, statusText, streamKey) {
|
|
6224
6495
|
if (!this.isStreamingEnabled()) return;
|
|
6225
6496
|
const cardKey = this.resolveStreamKey(chatId, streamKey);
|
|
@@ -6369,17 +6640,17 @@ ${trimmedResponse}`;
|
|
|
6369
6640
|
}
|
|
6370
6641
|
async sendStructuredMessage(chatId, msgType, content, replyToMessageId) {
|
|
6371
6642
|
try {
|
|
6372
|
-
const res = replyToMessageId ? await this.restClient.im.message.reply({
|
|
6643
|
+
const res = replyToMessageId ? await this.withFeishuRequestTimeout(chatId, `im.message.reply:${msgType}`, () => this.restClient.im.message.reply({
|
|
6373
6644
|
path: { message_id: replyToMessageId },
|
|
6374
6645
|
data: { msg_type: msgType, content }
|
|
6375
|
-
}) : await this.restClient.im.message.create({
|
|
6646
|
+
})) : await this.withFeishuRequestTimeout(chatId, `im.message.create:${msgType}`, () => this.restClient.im.message.create({
|
|
6376
6647
|
params: { receive_id_type: "chat_id" },
|
|
6377
6648
|
data: {
|
|
6378
6649
|
receive_id: chatId,
|
|
6379
6650
|
msg_type: msgType,
|
|
6380
6651
|
content
|
|
6381
6652
|
}
|
|
6382
|
-
});
|
|
6653
|
+
}));
|
|
6383
6654
|
if (res?.data?.message_id) {
|
|
6384
6655
|
return { ok: true, messageId: res.data.message_id };
|
|
6385
6656
|
}
|
|
@@ -6395,14 +6666,14 @@ ${trimmedResponse}`;
|
|
|
6395
6666
|
async sendAsCard(chatId, text2) {
|
|
6396
6667
|
const cardContent = buildCardContent(text2);
|
|
6397
6668
|
try {
|
|
6398
|
-
const res = await this.restClient.im.message.create({
|
|
6669
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:interactive-card", () => this.restClient.im.message.create({
|
|
6399
6670
|
params: { receive_id_type: "chat_id" },
|
|
6400
6671
|
data: {
|
|
6401
6672
|
receive_id: chatId,
|
|
6402
6673
|
msg_type: "interactive",
|
|
6403
6674
|
content: cardContent
|
|
6404
6675
|
}
|
|
6405
|
-
});
|
|
6676
|
+
}));
|
|
6406
6677
|
if (res?.data?.message_id) {
|
|
6407
6678
|
return { ok: true, messageId: res.data.message_id };
|
|
6408
6679
|
}
|
|
@@ -6419,14 +6690,14 @@ ${trimmedResponse}`;
|
|
|
6419
6690
|
async sendAsPost(chatId, text2) {
|
|
6420
6691
|
const postContent = buildPostContent(text2);
|
|
6421
6692
|
try {
|
|
6422
|
-
const res = await this.restClient.im.message.create({
|
|
6693
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:post", () => this.restClient.im.message.create({
|
|
6423
6694
|
params: { receive_id_type: "chat_id" },
|
|
6424
6695
|
data: {
|
|
6425
6696
|
receive_id: chatId,
|
|
6426
6697
|
msg_type: "post",
|
|
6427
6698
|
content: postContent
|
|
6428
6699
|
}
|
|
6429
|
-
});
|
|
6700
|
+
}));
|
|
6430
6701
|
if (res?.data?.message_id) {
|
|
6431
6702
|
return { ok: true, messageId: res.data.message_id };
|
|
6432
6703
|
}
|
|
@@ -6438,14 +6709,14 @@ ${trimmedResponse}`;
|
|
|
6438
6709
|
}
|
|
6439
6710
|
async sendAsPlainText(chatId, text2) {
|
|
6440
6711
|
try {
|
|
6441
|
-
const res = await this.restClient.im.message.create({
|
|
6712
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:text", () => this.restClient.im.message.create({
|
|
6442
6713
|
params: { receive_id_type: "chat_id" },
|
|
6443
6714
|
data: {
|
|
6444
6715
|
receive_id: chatId,
|
|
6445
6716
|
msg_type: "text",
|
|
6446
6717
|
content: JSON.stringify({ text: text2 })
|
|
6447
6718
|
}
|
|
6448
|
-
});
|
|
6719
|
+
}));
|
|
6449
6720
|
if (res?.data?.message_id) {
|
|
6450
6721
|
return { ok: true, messageId: res.data.message_id };
|
|
6451
6722
|
}
|
|
@@ -6470,14 +6741,14 @@ ${trimmedResponse}`;
|
|
|
6470
6741
|
if (permId) {
|
|
6471
6742
|
const cardJson2 = buildPermissionButtonCard(mdText, permId, chatId);
|
|
6472
6743
|
try {
|
|
6473
|
-
const res = await this.restClient.im.message.create({
|
|
6744
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-button-card", () => this.restClient.im.message.create({
|
|
6474
6745
|
params: { receive_id_type: "chat_id" },
|
|
6475
6746
|
data: {
|
|
6476
6747
|
receive_id: chatId,
|
|
6477
6748
|
msg_type: "interactive",
|
|
6478
6749
|
content: cardJson2
|
|
6479
6750
|
}
|
|
6480
|
-
});
|
|
6751
|
+
}));
|
|
6481
6752
|
if (res?.data?.message_id) {
|
|
6482
6753
|
return { ok: true, messageId: res.data.message_id };
|
|
6483
6754
|
}
|
|
@@ -6521,14 +6792,14 @@ ${trimmedResponse}`;
|
|
|
6521
6792
|
}
|
|
6522
6793
|
});
|
|
6523
6794
|
try {
|
|
6524
|
-
const res = await this.restClient.im.message.create({
|
|
6795
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-fallback-card", () => this.restClient.im.message.create({
|
|
6525
6796
|
params: { receive_id_type: "chat_id" },
|
|
6526
6797
|
data: {
|
|
6527
6798
|
receive_id: chatId,
|
|
6528
6799
|
msg_type: "interactive",
|
|
6529
6800
|
content: cardJson
|
|
6530
6801
|
}
|
|
6531
|
-
});
|
|
6802
|
+
}));
|
|
6532
6803
|
if (res?.data?.message_id) {
|
|
6533
6804
|
return { ok: true, messageId: res.data.message_id };
|
|
6534
6805
|
}
|
|
@@ -6545,14 +6816,14 @@ ${trimmedResponse}`;
|
|
|
6545
6816
|
...permCommands
|
|
6546
6817
|
].join("\n");
|
|
6547
6818
|
try {
|
|
6548
|
-
const res = await this.restClient.im.message.create({
|
|
6819
|
+
const res = await this.withFeishuRequestTimeout(chatId, "im.message.create:permission-fallback-text", () => this.restClient.im.message.create({
|
|
6549
6820
|
params: { receive_id_type: "chat_id" },
|
|
6550
6821
|
data: {
|
|
6551
6822
|
receive_id: chatId,
|
|
6552
6823
|
msg_type: "text",
|
|
6553
6824
|
content: JSON.stringify({ text: plainText })
|
|
6554
6825
|
}
|
|
6555
|
-
});
|
|
6826
|
+
}));
|
|
6556
6827
|
if (res?.data?.message_id) {
|
|
6557
6828
|
return { ok: true, messageId: res.data.message_id };
|
|
6558
6829
|
}
|
|
@@ -6898,20 +7169,20 @@ ${trimmedResponse}`;
|
|
|
6898
7169
|
buffer = Buffer.concat(chunks);
|
|
6899
7170
|
} catch (streamErr) {
|
|
6900
7171
|
console.warn("[feishu-adapter] Stream read failed, falling back to writeFile:", streamErr instanceof Error ? streamErr.message : streamErr);
|
|
6901
|
-
const
|
|
7172
|
+
const fs17 = await import("fs");
|
|
6902
7173
|
const os5 = await import("os");
|
|
6903
|
-
const
|
|
6904
|
-
const tmpPath =
|
|
7174
|
+
const path18 = await import("path");
|
|
7175
|
+
const tmpPath = path18.join(os5.tmpdir(), `feishu-dl-${crypto2.randomUUID()}`);
|
|
6905
7176
|
try {
|
|
6906
7177
|
await res.writeFile(tmpPath);
|
|
6907
|
-
buffer =
|
|
7178
|
+
buffer = fs17.readFileSync(tmpPath);
|
|
6908
7179
|
if (buffer.length > MAX_FILE_SIZE) {
|
|
6909
7180
|
console.warn(`[feishu-adapter] Resource too large (>${MAX_FILE_SIZE} bytes), key: ${fileKey}`);
|
|
6910
7181
|
return null;
|
|
6911
7182
|
}
|
|
6912
7183
|
} finally {
|
|
6913
7184
|
try {
|
|
6914
|
-
|
|
7185
|
+
fs17.unlinkSync(tmpPath);
|
|
6915
7186
|
} catch {
|
|
6916
7187
|
}
|
|
6917
7188
|
}
|
|
@@ -15028,6 +15299,54 @@ function extractNormalizedStructuredText(value) {
|
|
|
15028
15299
|
collectStructuredTextParts(value, parts);
|
|
15029
15300
|
return parts.length > 0 ? normalizeStructuredText(parts.join("\n\n")) : "";
|
|
15030
15301
|
}
|
|
15302
|
+
function parseJsonSafely(value) {
|
|
15303
|
+
if (!value) return null;
|
|
15304
|
+
try {
|
|
15305
|
+
return JSON.parse(value);
|
|
15306
|
+
} catch {
|
|
15307
|
+
return null;
|
|
15308
|
+
}
|
|
15309
|
+
}
|
|
15310
|
+
function normalizeTaskStatus(value) {
|
|
15311
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
15312
|
+
if (normalized === "in_progress" || normalized === "running" || normalized === "active") {
|
|
15313
|
+
return "in_progress";
|
|
15314
|
+
}
|
|
15315
|
+
if (normalized === "completed" || normalized === "complete" || normalized === "done") {
|
|
15316
|
+
return "completed";
|
|
15317
|
+
}
|
|
15318
|
+
return "pending";
|
|
15319
|
+
}
|
|
15320
|
+
function parseTaskProgressItems(value) {
|
|
15321
|
+
if (!Array.isArray(value)) return [];
|
|
15322
|
+
return value.map((item) => {
|
|
15323
|
+
const record = item;
|
|
15324
|
+
const text2 = extractNormalizedStructuredText(record.text ?? record.step);
|
|
15325
|
+
if (!text2) return null;
|
|
15326
|
+
return {
|
|
15327
|
+
text: text2,
|
|
15328
|
+
status: normalizeTaskStatus(record.status)
|
|
15329
|
+
};
|
|
15330
|
+
}).filter((item) => Boolean(item));
|
|
15331
|
+
}
|
|
15332
|
+
function parseUpdatePlanTasks(argumentsJson) {
|
|
15333
|
+
const parsed = parseJsonSafely(argumentsJson);
|
|
15334
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
15335
|
+
return parseTaskProgressItems(parsed.plan ?? parsed.tasks);
|
|
15336
|
+
}
|
|
15337
|
+
function extractToolOutputText(value) {
|
|
15338
|
+
if (typeof value !== "string") return extractNormalizedFreeText(value);
|
|
15339
|
+
const trimmed = value.trim();
|
|
15340
|
+
if (!trimmed) return "";
|
|
15341
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
15342
|
+
const parsed = parseJsonSafely(trimmed);
|
|
15343
|
+
if (parsed && typeof parsed === "object") {
|
|
15344
|
+
const extracted = extractNormalizedFreeText(parsed.output ?? parsed);
|
|
15345
|
+
if (extracted) return extracted;
|
|
15346
|
+
}
|
|
15347
|
+
}
|
|
15348
|
+
return extractNormalizedFreeText(value);
|
|
15349
|
+
}
|
|
15031
15350
|
function createDesktopEventSignature(rawLine) {
|
|
15032
15351
|
return crypto7.createHash("sha1").update(rawLine).digest("hex");
|
|
15033
15352
|
}
|
|
@@ -15059,6 +15378,17 @@ function isSessionMessageLine(line) {
|
|
|
15059
15378
|
function isTurnContextLine(line) {
|
|
15060
15379
|
return line.type === "turn_context";
|
|
15061
15380
|
}
|
|
15381
|
+
function describeUnhandledMirrorLineKind(line) {
|
|
15382
|
+
if (isSessionEventLine(line)) {
|
|
15383
|
+
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
15384
|
+
return `event_msg:${payloadType || "<unknown>"}`;
|
|
15385
|
+
}
|
|
15386
|
+
if (isSessionMessageLine(line)) {
|
|
15387
|
+
const payloadType = typeof line.payload?.type === "string" ? line.payload.type.trim() : "";
|
|
15388
|
+
return `response_item:${payloadType || "<unknown>"}`;
|
|
15389
|
+
}
|
|
15390
|
+
return null;
|
|
15391
|
+
}
|
|
15062
15392
|
function listDesktopSessions(limit) {
|
|
15063
15393
|
const root = getCodexSessionsRoot();
|
|
15064
15394
|
if (!fs4.existsSync(root)) return [];
|
|
@@ -15151,81 +15481,213 @@ function pushDesktopSessionEvent(events, parsed, rawLine) {
|
|
|
15151
15481
|
});
|
|
15152
15482
|
}
|
|
15153
15483
|
}
|
|
15154
|
-
function pushDesktopMirrorRecord(records, parsed, rawLine, activeTurnId) {
|
|
15155
|
-
if (isSessionEventLine(parsed)
|
|
15484
|
+
function pushDesktopMirrorRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds) {
|
|
15485
|
+
if (isSessionEventLine(parsed)) {
|
|
15486
|
+
return pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId);
|
|
15487
|
+
}
|
|
15488
|
+
if (isSessionMessageLine(parsed)) {
|
|
15489
|
+
return pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds);
|
|
15490
|
+
}
|
|
15491
|
+
return false;
|
|
15492
|
+
}
|
|
15493
|
+
function pushDesktopMirrorEventRecord(records, parsed, rawLine, activeTurnId) {
|
|
15494
|
+
const signature = createDesktopEventSignature(rawLine);
|
|
15495
|
+
const timestamp = parsed.timestamp || "";
|
|
15496
|
+
if (parsed.payload?.type === "task_started") {
|
|
15156
15497
|
records.push({
|
|
15157
|
-
signature
|
|
15498
|
+
signature,
|
|
15158
15499
|
type: "task_started",
|
|
15159
15500
|
content: "",
|
|
15160
|
-
timestamp
|
|
15501
|
+
timestamp,
|
|
15161
15502
|
turnId: parsed.payload.turn_id || ""
|
|
15162
15503
|
});
|
|
15163
|
-
return;
|
|
15504
|
+
return true;
|
|
15164
15505
|
}
|
|
15165
|
-
if (
|
|
15506
|
+
if (parsed.payload?.type === "turn_aborted") {
|
|
15507
|
+
records.push({
|
|
15508
|
+
signature,
|
|
15509
|
+
type: "task_aborted",
|
|
15510
|
+
content: extractNormalizedStructuredText(parsed.payload.reason),
|
|
15511
|
+
timestamp,
|
|
15512
|
+
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15513
|
+
});
|
|
15514
|
+
return true;
|
|
15515
|
+
}
|
|
15516
|
+
if (parsed.payload?.type === "agent_reasoning") {
|
|
15517
|
+
const text2 = extractNormalizedStructuredText(parsed.payload.text);
|
|
15518
|
+
if (!text2) return true;
|
|
15519
|
+
records.push({
|
|
15520
|
+
signature,
|
|
15521
|
+
type: "reasoning",
|
|
15522
|
+
content: text2,
|
|
15523
|
+
timestamp,
|
|
15524
|
+
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15525
|
+
});
|
|
15526
|
+
return true;
|
|
15527
|
+
}
|
|
15528
|
+
if (parsed.payload?.type === "web_search_end") {
|
|
15529
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15530
|
+
records.push({
|
|
15531
|
+
signature,
|
|
15532
|
+
type: "tool_finished",
|
|
15533
|
+
content: extractNormalizedStructuredText(parsed.payload.query),
|
|
15534
|
+
timestamp,
|
|
15535
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15536
|
+
toolId,
|
|
15537
|
+
toolName: "Web Search"
|
|
15538
|
+
});
|
|
15539
|
+
return true;
|
|
15540
|
+
}
|
|
15541
|
+
if (parsed.payload?.type === "mcp_tool_call_end") {
|
|
15542
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15543
|
+
const server = extractNormalizedFreeText(parsed.payload.invocation?.server);
|
|
15544
|
+
const tool = extractNormalizedFreeText(parsed.payload.invocation?.tool);
|
|
15545
|
+
const toolName = server && tool ? `mcp__${server}__${tool}` : "mcp_tool_call";
|
|
15546
|
+
records.push({
|
|
15547
|
+
signature,
|
|
15548
|
+
type: "tool_finished",
|
|
15549
|
+
content: "",
|
|
15550
|
+
timestamp,
|
|
15551
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15552
|
+
toolId,
|
|
15553
|
+
toolName,
|
|
15554
|
+
isError: false
|
|
15555
|
+
});
|
|
15556
|
+
return true;
|
|
15557
|
+
}
|
|
15558
|
+
if (parsed.payload?.type === "user_message") {
|
|
15166
15559
|
const text2 = extractNormalizedStructuredText(parsed.payload.message);
|
|
15167
|
-
if (!text2) return;
|
|
15560
|
+
if (!text2) return true;
|
|
15168
15561
|
records.push({
|
|
15169
|
-
signature
|
|
15562
|
+
signature,
|
|
15170
15563
|
type: "message",
|
|
15171
15564
|
role: "user",
|
|
15172
15565
|
content: text2,
|
|
15173
|
-
timestamp
|
|
15566
|
+
timestamp,
|
|
15174
15567
|
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15175
15568
|
});
|
|
15176
|
-
return;
|
|
15569
|
+
return true;
|
|
15177
15570
|
}
|
|
15178
|
-
if (
|
|
15571
|
+
if (parsed.payload?.type === "task_complete") {
|
|
15179
15572
|
records.push({
|
|
15180
|
-
signature
|
|
15573
|
+
signature,
|
|
15181
15574
|
type: "task_complete",
|
|
15182
15575
|
role: "assistant",
|
|
15183
15576
|
content: extractNormalizedStructuredText(parsed.payload.last_agent_message),
|
|
15184
|
-
timestamp
|
|
15577
|
+
timestamp,
|
|
15185
15578
|
turnId: parsed.payload.turn_id || ""
|
|
15186
15579
|
});
|
|
15187
|
-
return;
|
|
15580
|
+
return true;
|
|
15188
15581
|
}
|
|
15189
|
-
|
|
15582
|
+
return false;
|
|
15583
|
+
}
|
|
15584
|
+
function pushDesktopMirrorResponseRecord(records, parsed, rawLine, activeTurnId, activeSpecialCallIds) {
|
|
15585
|
+
const signature = createDesktopEventSignature(rawLine);
|
|
15586
|
+
const timestamp = parsed.timestamp || "";
|
|
15587
|
+
if (parsed.payload?.type === "message" && parsed.payload.role === "assistant") {
|
|
15190
15588
|
const text2 = extractDesktopMessageText(parsed);
|
|
15191
|
-
if (!text2) return;
|
|
15589
|
+
if (!text2) return true;
|
|
15192
15590
|
records.push({
|
|
15193
|
-
signature
|
|
15591
|
+
signature,
|
|
15194
15592
|
type: "message",
|
|
15195
15593
|
role: parsed.payload.phase === "commentary" ? "commentary" : "assistant",
|
|
15196
15594
|
content: parsed.payload.phase === "commentary" ? text2.replace(/^\[commentary\]\n/, "") : text2,
|
|
15197
|
-
timestamp
|
|
15595
|
+
timestamp,
|
|
15198
15596
|
...activeTurnId ? { turnId: activeTurnId } : {}
|
|
15199
15597
|
});
|
|
15200
|
-
return;
|
|
15598
|
+
return true;
|
|
15201
15599
|
}
|
|
15202
|
-
if (
|
|
15600
|
+
if (parsed.payload?.type === "function_call") {
|
|
15203
15601
|
const toolName = extractNormalizedFreeText(parsed.payload.name);
|
|
15204
|
-
const toolId = extractNormalizedFreeText(parsed.payload.call_id) ||
|
|
15205
|
-
if (!toolName) return;
|
|
15602
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15603
|
+
if (!toolName) return true;
|
|
15604
|
+
if (toolName === "update_plan") {
|
|
15605
|
+
const tasks = parseUpdatePlanTasks(parsed.payload.arguments);
|
|
15606
|
+
activeSpecialCallIds.add(toolId);
|
|
15607
|
+
records.push({
|
|
15608
|
+
signature,
|
|
15609
|
+
type: "plan_update",
|
|
15610
|
+
content: "",
|
|
15611
|
+
timestamp,
|
|
15612
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15613
|
+
tasks
|
|
15614
|
+
});
|
|
15615
|
+
return true;
|
|
15616
|
+
}
|
|
15206
15617
|
records.push({
|
|
15207
|
-
signature
|
|
15618
|
+
signature,
|
|
15208
15619
|
type: "tool_started",
|
|
15209
15620
|
content: "",
|
|
15210
|
-
timestamp
|
|
15621
|
+
timestamp,
|
|
15211
15622
|
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15212
15623
|
toolId,
|
|
15213
15624
|
toolName
|
|
15214
15625
|
});
|
|
15215
|
-
return;
|
|
15626
|
+
return true;
|
|
15216
15627
|
}
|
|
15217
|
-
if (
|
|
15218
|
-
const
|
|
15628
|
+
if (parsed.payload?.type === "custom_tool_call") {
|
|
15629
|
+
const toolName = extractNormalizedFreeText(parsed.payload.name);
|
|
15630
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15631
|
+
if (!toolName) return true;
|
|
15632
|
+
if (toolName === "update_plan") {
|
|
15633
|
+
const tasks = parseUpdatePlanTasks(typeof parsed.payload.input === "string" ? parsed.payload.input : void 0);
|
|
15634
|
+
activeSpecialCallIds.add(toolId);
|
|
15635
|
+
records.push({
|
|
15636
|
+
signature,
|
|
15637
|
+
type: "plan_update",
|
|
15638
|
+
content: "",
|
|
15639
|
+
timestamp,
|
|
15640
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15641
|
+
tasks
|
|
15642
|
+
});
|
|
15643
|
+
return true;
|
|
15644
|
+
}
|
|
15219
15645
|
records.push({
|
|
15220
|
-
signature
|
|
15646
|
+
signature,
|
|
15647
|
+
type: "tool_started",
|
|
15648
|
+
content: "",
|
|
15649
|
+
timestamp,
|
|
15650
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15651
|
+
toolId,
|
|
15652
|
+
toolName
|
|
15653
|
+
});
|
|
15654
|
+
return true;
|
|
15655
|
+
}
|
|
15656
|
+
if (parsed.payload?.type === "function_call_output") {
|
|
15657
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15658
|
+
if (activeSpecialCallIds.has(toolId)) {
|
|
15659
|
+
activeSpecialCallIds.delete(toolId);
|
|
15660
|
+
return true;
|
|
15661
|
+
}
|
|
15662
|
+
records.push({
|
|
15663
|
+
signature,
|
|
15221
15664
|
type: "tool_finished",
|
|
15222
|
-
content:
|
|
15223
|
-
timestamp
|
|
15665
|
+
content: extractToolOutputText(parsed.payload.output),
|
|
15666
|
+
timestamp,
|
|
15224
15667
|
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15225
15668
|
toolId,
|
|
15226
15669
|
isError: parsed.payload.is_error === true
|
|
15227
15670
|
});
|
|
15671
|
+
return true;
|
|
15228
15672
|
}
|
|
15673
|
+
if (parsed.payload?.type === "custom_tool_call_output") {
|
|
15674
|
+
const toolId = extractNormalizedFreeText(parsed.payload.call_id) || signature;
|
|
15675
|
+
if (activeSpecialCallIds.has(toolId)) {
|
|
15676
|
+
activeSpecialCallIds.delete(toolId);
|
|
15677
|
+
return true;
|
|
15678
|
+
}
|
|
15679
|
+
records.push({
|
|
15680
|
+
signature,
|
|
15681
|
+
type: "tool_finished",
|
|
15682
|
+
content: extractToolOutputText(parsed.payload.output),
|
|
15683
|
+
timestamp,
|
|
15684
|
+
...activeTurnId ? { turnId: activeTurnId } : {},
|
|
15685
|
+
toolId,
|
|
15686
|
+
isError: parsed.payload.is_error === true
|
|
15687
|
+
});
|
|
15688
|
+
return true;
|
|
15689
|
+
}
|
|
15690
|
+
return false;
|
|
15229
15691
|
}
|
|
15230
15692
|
function parseDesktopSessionEventText(content, leadingText = "", flushTrailingText = false) {
|
|
15231
15693
|
const combined = `${leadingText}${content}`;
|
|
@@ -15261,14 +15723,16 @@ function parseDesktopSessionEventText(content, leadingText = "", flushTrailingTe
|
|
|
15261
15723
|
trailingText
|
|
15262
15724
|
};
|
|
15263
15725
|
}
|
|
15264
|
-
function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false, initialTurnId = null) {
|
|
15726
|
+
function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingText = false, initialTurnId = null, initialSpecialCallIds = []) {
|
|
15265
15727
|
const combined = `${leadingText}${content}`;
|
|
15266
15728
|
if (!combined) {
|
|
15267
15729
|
return {
|
|
15268
15730
|
records: [],
|
|
15269
15731
|
nextOffset: 0,
|
|
15270
15732
|
trailingText: "",
|
|
15271
|
-
nextTurnId: initialTurnId
|
|
15733
|
+
nextTurnId: initialTurnId,
|
|
15734
|
+
nextSpecialCallIds: [],
|
|
15735
|
+
unknownKinds: []
|
|
15272
15736
|
};
|
|
15273
15737
|
}
|
|
15274
15738
|
const hasTrailingNewline = combined.endsWith("\n") || combined.endsWith("\r");
|
|
@@ -15280,6 +15744,8 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
|
|
|
15280
15744
|
}
|
|
15281
15745
|
const records = [];
|
|
15282
15746
|
let activeTurnId = initialTurnId;
|
|
15747
|
+
const activeSpecialCallIds = new Set(initialSpecialCallIds);
|
|
15748
|
+
const unknownKinds = /* @__PURE__ */ new Set();
|
|
15283
15749
|
for (const line of rawLines) {
|
|
15284
15750
|
const trimmed = line.trim();
|
|
15285
15751
|
if (!trimmed) continue;
|
|
@@ -15297,20 +15763,27 @@ function parseDesktopMirrorRecordText(content, leadingText = "", flushTrailingTe
|
|
|
15297
15763
|
const eventPayload = parsed.payload;
|
|
15298
15764
|
activeTurnId = eventPayload?.turn_id || activeTurnId;
|
|
15299
15765
|
}
|
|
15300
|
-
pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId);
|
|
15301
|
-
if (
|
|
15766
|
+
const handled = pushDesktopMirrorRecord(records, parsed, trimmed, activeTurnId, activeSpecialCallIds);
|
|
15767
|
+
if (!handled) {
|
|
15768
|
+
const unknownKind = describeUnhandledMirrorLineKind(parsed);
|
|
15769
|
+
if (unknownKind) unknownKinds.add(unknownKind);
|
|
15770
|
+
}
|
|
15771
|
+
if (isSessionEventLine(parsed) && (parsed.payload?.type === "task_complete" || parsed.payload?.type === "turn_aborted")) {
|
|
15302
15772
|
const eventPayload = parsed.payload;
|
|
15303
15773
|
const completedTurnId = eventPayload?.turn_id || activeTurnId;
|
|
15304
15774
|
if (!completedTurnId || completedTurnId === activeTurnId) {
|
|
15305
15775
|
activeTurnId = null;
|
|
15306
15776
|
}
|
|
15777
|
+
activeSpecialCallIds.clear();
|
|
15307
15778
|
}
|
|
15308
15779
|
}
|
|
15309
15780
|
return {
|
|
15310
15781
|
records,
|
|
15311
15782
|
nextOffset: 0,
|
|
15312
15783
|
trailingText,
|
|
15313
|
-
nextTurnId: activeTurnId
|
|
15784
|
+
nextTurnId: activeTurnId,
|
|
15785
|
+
nextSpecialCallIds: Array.from(activeSpecialCallIds),
|
|
15786
|
+
unknownKinds: Array.from(unknownKinds)
|
|
15314
15787
|
};
|
|
15315
15788
|
}
|
|
15316
15789
|
function readDesktopSessionMessages(threadId, limit = 8) {
|
|
@@ -15331,7 +15804,7 @@ function readDesktopSessionEventStreamByFilePath(filePath) {
|
|
|
15331
15804
|
}
|
|
15332
15805
|
return parseDesktopSessionEventText(content, "", true).events;
|
|
15333
15806
|
}
|
|
15334
|
-
function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null) {
|
|
15807
|
+
function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, endOffset, trailingText = "", currentTurnId = null, currentSpecialCallIds = []) {
|
|
15335
15808
|
let content = "";
|
|
15336
15809
|
try {
|
|
15337
15810
|
content = readFileUtf8Range(filePath, startOffset, endOffset);
|
|
@@ -15340,15 +15813,19 @@ function readDesktopSessionMirrorRecordDeltaByFilePath(filePath, startOffset, en
|
|
|
15340
15813
|
records: [],
|
|
15341
15814
|
nextOffset: startOffset,
|
|
15342
15815
|
trailingText,
|
|
15343
|
-
nextTurnId: currentTurnId
|
|
15816
|
+
nextTurnId: currentTurnId,
|
|
15817
|
+
nextSpecialCallIds: Array.from(currentSpecialCallIds),
|
|
15818
|
+
unknownKinds: []
|
|
15344
15819
|
};
|
|
15345
15820
|
}
|
|
15346
|
-
const parsed = parseDesktopMirrorRecordText(content, trailingText, false, currentTurnId);
|
|
15821
|
+
const parsed = parseDesktopMirrorRecordText(content, trailingText, false, currentTurnId, currentSpecialCallIds);
|
|
15347
15822
|
return {
|
|
15348
15823
|
records: parsed.records,
|
|
15349
15824
|
nextOffset: Math.max(startOffset, endOffset),
|
|
15350
15825
|
trailingText: parsed.trailingText,
|
|
15351
|
-
nextTurnId: parsed.nextTurnId
|
|
15826
|
+
nextTurnId: parsed.nextTurnId,
|
|
15827
|
+
nextSpecialCallIds: parsed.nextSpecialCallIds,
|
|
15828
|
+
unknownKinds: parsed.unknownKinds
|
|
15352
15829
|
};
|
|
15353
15830
|
}
|
|
15354
15831
|
function readDesktopSessionEventStream(threadId) {
|
|
@@ -16453,6 +16930,8 @@ function formatHealthStatusLabel(healthStatus) {
|
|
|
16453
16930
|
return "\u957F\u65F6\u8FD0\u884C\uFF0C\u5F85\u89C2\u5BDF";
|
|
16454
16931
|
case "suspected_stall":
|
|
16455
16932
|
return "\u7591\u4F3C\u5361\u4F4F";
|
|
16933
|
+
case "suspected_stream_ui_stall":
|
|
16934
|
+
return "\u6D41\u5F0F UI \u7591\u4F3C\u5361\u4F4F";
|
|
16456
16935
|
case "suspected_detached":
|
|
16457
16936
|
return "\u7591\u4F3C\u8131\u6302";
|
|
16458
16937
|
case "completed":
|
|
@@ -16507,8 +16986,10 @@ function buildHealthCommandResponse(title, diagnosis, markdown = false) {
|
|
|
16507
16986
|
["\u5065\u5EB7\u72B6\u6001", formatHealthStatusLabel(diagnosis.healthStatus)],
|
|
16508
16987
|
["\u5F53\u524D\u9636\u6BB5", currentStage],
|
|
16509
16988
|
["\u6700\u540E\u8FDB\u5C55", formatCommandTimestamp(diagnosis.lastProgressAt)],
|
|
16989
|
+
["\u6D41\u5F0F UI \u5237\u65B0", formatCommandTimestamp(diagnosis.lastStreamUiUpdateAt)],
|
|
16510
16990
|
["\u5DE5\u5177\u5F00\u59CB", formatCommandTimestamp(diagnosis.activeToolStartedAt)],
|
|
16511
16991
|
["\u6700\u8FD1\u5DE5\u5177\u5B8C\u6210", formatCommandTimestamp(diagnosis.lastToolFinishedAt)],
|
|
16992
|
+
["\u6D41\u5F0F UI \u9519\u8BEF", diagnosis.lastStreamUiErrorAt ? `${formatCommandTimestamp(diagnosis.lastStreamUiErrorAt)}${diagnosis.lastStreamUiError ? ` \xB7 ${diagnosis.lastStreamUiError}` : ""}` : "-"],
|
|
16512
16993
|
["\u672C\u5730\u8FDB\u7A0B", formatHealthProcessProbe(diagnosis)]
|
|
16513
16994
|
],
|
|
16514
16995
|
[diagnosis.healthReason],
|
|
@@ -16630,11 +17111,13 @@ function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
|
16630
17111
|
lastActivityAt: safeTimestamp,
|
|
16631
17112
|
lastStatusText: null,
|
|
16632
17113
|
lastStatusAt: 0,
|
|
17114
|
+
statusNote: null,
|
|
16633
17115
|
userText: null,
|
|
16634
17116
|
lastAssistantText: null,
|
|
16635
17117
|
lastCommentaryText: null,
|
|
16636
17118
|
streamedText: "",
|
|
16637
17119
|
streamStarted: false,
|
|
17120
|
+
taskItems: [],
|
|
16638
17121
|
toolCalls: /* @__PURE__ */ new Map()
|
|
16639
17122
|
};
|
|
16640
17123
|
}
|
|
@@ -16682,7 +17165,7 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
|
|
|
16682
17165
|
pendingTurn.lastCommentaryText
|
|
16683
17166
|
].map((value) => (value || "").trim()).find(Boolean) || "";
|
|
16684
17167
|
const userText = pendingTurn.userText?.trim() || null;
|
|
16685
|
-
if (!text2 && !userText && pendingTurn.toolCalls.size === 0) return null;
|
|
17168
|
+
if (!text2 && !userText && pendingTurn.toolCalls.size === 0 && pendingTurn.taskItems.length === 0) return null;
|
|
16686
17169
|
return {
|
|
16687
17170
|
streamKey: pendingTurn.streamKey,
|
|
16688
17171
|
userText,
|
|
@@ -16721,6 +17204,12 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
16721
17204
|
if (completed) finalized.push(completed);
|
|
16722
17205
|
continue;
|
|
16723
17206
|
}
|
|
17207
|
+
if (record.type === "task_aborted") {
|
|
17208
|
+
ensureMirrorTurnState(subscription, record);
|
|
17209
|
+
const interrupted = finalizeMirrorTurn(subscription, record.signature, record.timestamp, "interrupted");
|
|
17210
|
+
if (interrupted) finalized.push(interrupted);
|
|
17211
|
+
continue;
|
|
17212
|
+
}
|
|
16724
17213
|
if (record.type === "message" && record.role === "user") {
|
|
16725
17214
|
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
16726
17215
|
const text2 = record.content.trim();
|
|
@@ -16749,6 +17238,20 @@ function consumeMirrorRecords(subscription, records, hooks = {}) {
|
|
|
16749
17238
|
}
|
|
16750
17239
|
continue;
|
|
16751
17240
|
}
|
|
17241
|
+
if (record.type === "reasoning") {
|
|
17242
|
+
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
17243
|
+
const text2 = record.content.trim();
|
|
17244
|
+
if (!text2) continue;
|
|
17245
|
+
pendingTurn.statusNote = text2;
|
|
17246
|
+
hooks.onStatusProgress?.(subscription, pendingTurn);
|
|
17247
|
+
continue;
|
|
17248
|
+
}
|
|
17249
|
+
if (record.type === "plan_update") {
|
|
17250
|
+
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
17251
|
+
pendingTurn.taskItems = record.tasks || [];
|
|
17252
|
+
hooks.onTaskProgress?.(subscription, pendingTurn);
|
|
17253
|
+
continue;
|
|
17254
|
+
}
|
|
16752
17255
|
if (record.type === "tool_started") {
|
|
16753
17256
|
const pendingTurn = ensureMirrorTurnState(subscription, record);
|
|
16754
17257
|
const toolId = record.toolId || record.signature;
|
|
@@ -16791,8 +17294,30 @@ function flushTimedOutMirrorTurn(subscription, idleTimeoutMs, nowMs = Date.now()
|
|
|
16791
17294
|
"interrupted"
|
|
16792
17295
|
);
|
|
16793
17296
|
}
|
|
17297
|
+
function enqueuePendingMirrorDeliveries(subscription, turns) {
|
|
17298
|
+
if (turns.length === 0) return;
|
|
17299
|
+
const existingSignatures = new Set(subscription.pendingDeliveries.map((turn) => turn.signature));
|
|
17300
|
+
for (const turn of turns) {
|
|
17301
|
+
if (existingSignatures.has(turn.signature)) continue;
|
|
17302
|
+
subscription.pendingDeliveries.push(turn);
|
|
17303
|
+
existingSignatures.add(turn.signature);
|
|
17304
|
+
}
|
|
17305
|
+
}
|
|
17306
|
+
function removePendingMirrorDeliveries(subscription, turns) {
|
|
17307
|
+
if (turns.length === 0 || subscription.pendingDeliveries.length === 0) return;
|
|
17308
|
+
const deliveredSignatures = new Set(turns.map((turn) => turn.signature));
|
|
17309
|
+
subscription.pendingDeliveries = subscription.pendingDeliveries.filter(
|
|
17310
|
+
(turn) => !deliveredSignatures.has(turn.signature)
|
|
17311
|
+
);
|
|
17312
|
+
}
|
|
17313
|
+
function selectPendingMirrorDeliveries(subscription, blocked) {
|
|
17314
|
+
if (!blocked) {
|
|
17315
|
+
return subscription.pendingDeliveries.slice();
|
|
17316
|
+
}
|
|
17317
|
+
return subscription.pendingDeliveries.filter((turn) => turn.timedOut);
|
|
17318
|
+
}
|
|
16794
17319
|
function hasPendingMirrorWork(subscription) {
|
|
16795
|
-
return subscription.bufferedRecords.length > 0 || subscription.pendingTurn !== null;
|
|
17320
|
+
return subscription.bufferedRecords.length > 0 || subscription.pendingTurn !== null || subscription.pendingDeliveries.length > 0;
|
|
16796
17321
|
}
|
|
16797
17322
|
function consumeBufferedMirrorTurns(subscription, idleTimeoutMs, nowMs = Date.now(), hooks = {}) {
|
|
16798
17323
|
const bufferedRecords = subscription.bufferedRecords;
|
|
@@ -18613,7 +19138,7 @@ function buildConversationPromptText(text2, files = []) {
|
|
|
18613
19138
|
|
|
18614
19139
|
${attachmentSupplement}` : attachmentSupplement;
|
|
18615
19140
|
}
|
|
18616
|
-
async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent, onPromptPrepared) {
|
|
19141
|
+
async function processMessage(binding, text2, onPermissionRequest, abortSignal, files, onPartialText, onToolEvent, onTaskEvent, onStatusNote, onPromptPrepared) {
|
|
18617
19142
|
const { store, llm } = getBridgeContext();
|
|
18618
19143
|
const sessionId = binding.codepilotSessionId;
|
|
18619
19144
|
const lockId = crypto8.randomBytes(8).toString("hex");
|
|
@@ -18732,14 +19257,22 @@ async function processMessage(binding, text2, onPermissionRequest, abortSignal,
|
|
|
18732
19257
|
}
|
|
18733
19258
|
}
|
|
18734
19259
|
});
|
|
18735
|
-
return await consumeStream(
|
|
19260
|
+
return await consumeStream(
|
|
19261
|
+
stream,
|
|
19262
|
+
sessionId,
|
|
19263
|
+
onPermissionRequest,
|
|
19264
|
+
onPartialText,
|
|
19265
|
+
onToolEvent,
|
|
19266
|
+
onTaskEvent,
|
|
19267
|
+
onStatusNote
|
|
19268
|
+
);
|
|
18736
19269
|
} finally {
|
|
18737
19270
|
clearInterval(renewalInterval);
|
|
18738
19271
|
store.releaseSessionLock(sessionId, lockId);
|
|
18739
19272
|
store.setSessionRuntimeStatus(sessionId, "idle");
|
|
18740
19273
|
}
|
|
18741
19274
|
}
|
|
18742
|
-
async function consumeStream(stream, sessionId, onPermissionRequest, onPartialText, onToolEvent) {
|
|
19275
|
+
async function consumeStream(stream, sessionId, onPermissionRequest, onPartialText, onToolEvent, onTaskEvent, onStatusNote) {
|
|
18743
19276
|
const { store } = getBridgeContext();
|
|
18744
19277
|
const contentBlocks = [];
|
|
18745
19278
|
let currentText = "";
|
|
@@ -18848,6 +19381,12 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
18848
19381
|
if (statusData.model) {
|
|
18849
19382
|
store.updateSessionModel(sessionId, statusData.model);
|
|
18850
19383
|
}
|
|
19384
|
+
if (typeof statusData.reasoning === "string" && onStatusNote) {
|
|
19385
|
+
try {
|
|
19386
|
+
onStatusNote(statusData.reasoning);
|
|
19387
|
+
} catch {
|
|
19388
|
+
}
|
|
19389
|
+
}
|
|
18851
19390
|
} catch {
|
|
18852
19391
|
}
|
|
18853
19392
|
break;
|
|
@@ -18855,8 +19394,15 @@ async function consumeStream(stream, sessionId, onPermissionRequest, onPartialTe
|
|
|
18855
19394
|
case "task_update": {
|
|
18856
19395
|
try {
|
|
18857
19396
|
const taskData = JSON.parse(event.data);
|
|
18858
|
-
|
|
18859
|
-
|
|
19397
|
+
const tasks = Array.isArray(taskData.tasks) ? taskData.tasks : Array.isArray(taskData.todos) ? taskData.todos : null;
|
|
19398
|
+
if (tasks) {
|
|
19399
|
+
store.syncSdkTasks(sessionId, tasks);
|
|
19400
|
+
if (onTaskEvent) {
|
|
19401
|
+
try {
|
|
19402
|
+
onTaskEvent(tasks);
|
|
19403
|
+
} catch {
|
|
19404
|
+
}
|
|
19405
|
+
}
|
|
18860
19406
|
}
|
|
18861
19407
|
} catch {
|
|
18862
19408
|
}
|
|
@@ -18954,6 +19500,14 @@ function pushStreamFeedbackTools(target, tools) {
|
|
|
18954
19500
|
} catch {
|
|
18955
19501
|
}
|
|
18956
19502
|
}
|
|
19503
|
+
function pushStreamFeedbackTasks(target, tasks) {
|
|
19504
|
+
if (typeof target.adapter.onTaskEvent !== "function") return;
|
|
19505
|
+
target.ensureStarted?.();
|
|
19506
|
+
try {
|
|
19507
|
+
target.adapter.onTaskEvent(target.chatId, tasks, target.streamKey);
|
|
19508
|
+
} catch {
|
|
19509
|
+
}
|
|
19510
|
+
}
|
|
18957
19511
|
function pushStreamFeedbackStatus(target, text2) {
|
|
18958
19512
|
if (typeof target.adapter.onStreamStatus !== "function") return;
|
|
18959
19513
|
target.ensureStarted?.();
|
|
@@ -19032,12 +19586,15 @@ function formatRuntimeDuration(ms) {
|
|
|
19032
19586
|
if (seconds === 0) return `${hours}h ${minutes}m`;
|
|
19033
19587
|
return `${hours}h ${minutes}m ${seconds}s`;
|
|
19034
19588
|
}
|
|
19035
|
-
function formatInteractiveRuntimeStatus(elapsedMs, silentMs) {
|
|
19589
|
+
function formatInteractiveRuntimeStatus(elapsedMs, silentMs, statusNote) {
|
|
19036
19590
|
const parts = [elapsedMs < 1e3 ? "\u5904\u7406\u4E2D" : `\u5DF2\u8FD0\u884C ${formatRuntimeDuration(elapsedMs)}`];
|
|
19037
19591
|
if (typeof silentMs === "number" && silentMs >= 0) {
|
|
19038
19592
|
parts.push(`\u6700\u8FD1 ${formatRuntimeDuration(silentMs)} \u65E0\u65B0\u8F93\u51FA`);
|
|
19039
19593
|
}
|
|
19040
|
-
|
|
19594
|
+
const runtimeText = parts.join("\uFF0C");
|
|
19595
|
+
const note = (statusNote || "").trim();
|
|
19596
|
+
return note ? `\u5F53\u524D\u6B65\u9AA4\uFF1A${note}
|
|
19597
|
+
${runtimeText}` : runtimeText;
|
|
19041
19598
|
}
|
|
19042
19599
|
async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
19043
19600
|
const binding = resolve(msg.address);
|
|
@@ -19132,23 +19689,35 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19132
19689
|
adapter,
|
|
19133
19690
|
channelType: adapter.channelType,
|
|
19134
19691
|
chatId: msg.address.chatId,
|
|
19135
|
-
streamKey
|
|
19692
|
+
streamKey,
|
|
19693
|
+
ensureStarted: () => {
|
|
19694
|
+
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
19695
|
+
}
|
|
19136
19696
|
};
|
|
19137
19697
|
const supportsPersistentStreamStatus = hasStreamingCards && adapter.provider === "feishu" && typeof adapter.onStreamStatus === "function";
|
|
19138
19698
|
const supportsStructuredStreamUi = supportsPersistentStreamStatus && (adapter.supportsStructuredStreamingUi?.(msg.address.chatId) ?? true);
|
|
19699
|
+
let latestStatusNote = null;
|
|
19700
|
+
let latestTasks = [];
|
|
19139
19701
|
const syncStructuredStreamUiState = () => {
|
|
19140
19702
|
if (!supportsStructuredStreamUi || taskState.structuredStreamUiActive) return;
|
|
19141
19703
|
if (adapter.hasActiveStreamingUi?.(msg.address.chatId, streamKey)) {
|
|
19142
19704
|
taskState.structuredStreamUiActive = true;
|
|
19143
19705
|
}
|
|
19144
19706
|
};
|
|
19707
|
+
const syncStructuredStreamUiSnapshot = () => {
|
|
19708
|
+
if (!supportsStructuredStreamUi) return;
|
|
19709
|
+
syncStructuredStreamUiState();
|
|
19710
|
+
const snapshot = adapter.getStructuredStreamingUiSnapshot?.(msg.address.chatId, streamKey);
|
|
19711
|
+
if (!snapshot) return;
|
|
19712
|
+
deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, snapshot);
|
|
19713
|
+
};
|
|
19145
19714
|
const pushRunningStatus = (silentMs) => {
|
|
19146
19715
|
if (!supportsStructuredStreamUi || streamStatusUpdatesClosed) return;
|
|
19147
19716
|
pushStreamFeedbackStatus(
|
|
19148
19717
|
streamFeedbackTarget,
|
|
19149
|
-
formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs)
|
|
19718
|
+
formatInteractiveRuntimeStatus(nowMs() - taskStartedAt, silentMs, latestStatusNote)
|
|
19150
19719
|
);
|
|
19151
|
-
|
|
19720
|
+
syncStructuredStreamUiSnapshot();
|
|
19152
19721
|
};
|
|
19153
19722
|
let streamStatusHeartbeat = null;
|
|
19154
19723
|
let streamStatusUpdatesClosed = false;
|
|
@@ -19182,7 +19751,24 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19182
19751
|
pushStreamFeedbackTools(streamFeedbackTarget, Array.from(toolCallTracker.values()));
|
|
19183
19752
|
}
|
|
19184
19753
|
pushRunningStatus(null);
|
|
19185
|
-
|
|
19754
|
+
syncStructuredStreamUiSnapshot();
|
|
19755
|
+
};
|
|
19756
|
+
const onTaskEvent = (tasks) => {
|
|
19757
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
19758
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19759
|
+
latestTasks = tasks;
|
|
19760
|
+
if (hasStreamingCards) {
|
|
19761
|
+
pushStreamFeedbackTasks(streamFeedbackTarget, latestTasks);
|
|
19762
|
+
}
|
|
19763
|
+
pushRunningStatus(null);
|
|
19764
|
+
syncStructuredStreamUiSnapshot();
|
|
19765
|
+
};
|
|
19766
|
+
const onStatusNote = (note) => {
|
|
19767
|
+
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
19768
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19769
|
+
latestStatusNote = (note || "").trim() || null;
|
|
19770
|
+
pushRunningStatus(null);
|
|
19771
|
+
syncStructuredStreamUiSnapshot();
|
|
19186
19772
|
};
|
|
19187
19773
|
const onPartialText = (fullText) => {
|
|
19188
19774
|
if (!deps.isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
@@ -19191,7 +19777,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19191
19777
|
previewOnPartialText?.(fullText);
|
|
19192
19778
|
onStreamCardText?.(fullText);
|
|
19193
19779
|
pushRunningStatus(null);
|
|
19194
|
-
|
|
19780
|
+
syncStructuredStreamUiSnapshot();
|
|
19195
19781
|
};
|
|
19196
19782
|
if (supportsStructuredStreamUi) {
|
|
19197
19783
|
pushRunningStatus(null);
|
|
@@ -19205,11 +19791,10 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19205
19791
|
return;
|
|
19206
19792
|
}
|
|
19207
19793
|
const elapsedMs = nowMs() - taskStartedAt;
|
|
19208
|
-
if (elapsedMs < streamStatusIdleDetectionStartMs) return;
|
|
19209
19794
|
const silentMs = nowMs() - taskState.lastActivityAt;
|
|
19210
|
-
|
|
19211
|
-
pushRunningStatus(
|
|
19212
|
-
|
|
19795
|
+
const showSilentDuration = elapsedMs >= streamStatusIdleDetectionStartMs && silentMs >= streamStatusHeartbeatMs ? silentMs : null;
|
|
19796
|
+
pushRunningStatus(showSilentDuration);
|
|
19797
|
+
syncStructuredStreamUiSnapshot();
|
|
19213
19798
|
}, streamStatusHeartbeatMs);
|
|
19214
19799
|
}
|
|
19215
19800
|
let finalOutcome = "failed";
|
|
@@ -19236,11 +19821,16 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19236
19821
|
"permission_wait",
|
|
19237
19822
|
`\u5F53\u524D\u6B63\u5728\u7B49\u5F85\u5DE5\u5177 ${perm.toolName} \u7684\u6743\u9650\u786E\u8BA4\u3002`
|
|
19238
19823
|
);
|
|
19824
|
+
deps.touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19825
|
+
pushRunningStatus(null);
|
|
19826
|
+
syncStructuredStreamUiSnapshot();
|
|
19239
19827
|
},
|
|
19240
19828
|
taskAbort.signal,
|
|
19241
19829
|
attachments && attachments.length > 0 ? attachments : void 0,
|
|
19242
19830
|
onPartialText,
|
|
19243
19831
|
onToolEvent,
|
|
19832
|
+
onTaskEvent,
|
|
19833
|
+
onStatusNote,
|
|
19244
19834
|
(preparedPrompt) => {
|
|
19245
19835
|
if (!taskState.mirrorSuppressionId) {
|
|
19246
19836
|
taskState.mirrorSuppressionId = deps.beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
@@ -19263,14 +19853,16 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19263
19853
|
}
|
|
19264
19854
|
if (result.responseText || result.outboundAttachments.length > 0) {
|
|
19265
19855
|
const textToDeliver = cardFinalized ? "" : result.responseText;
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19273
|
-
|
|
19856
|
+
if (!cardFinalized || result.outboundAttachments.length > 0) {
|
|
19857
|
+
await deps.deliverResponse(
|
|
19858
|
+
adapter,
|
|
19859
|
+
msg.address,
|
|
19860
|
+
textToDeliver,
|
|
19861
|
+
binding.codepilotSessionId,
|
|
19862
|
+
msg.messageId,
|
|
19863
|
+
result.outboundAttachments
|
|
19864
|
+
);
|
|
19865
|
+
}
|
|
19274
19866
|
} else if (result.hasError) {
|
|
19275
19867
|
await deps.deliverResponse(
|
|
19276
19868
|
adapter,
|
|
@@ -19289,6 +19881,7 @@ async function runInteractiveMessage(adapter, msg, text2, attachments, deps) {
|
|
|
19289
19881
|
finalOutcomeDetail = result.hasError ? result.errorMessage?.trim() || void 0 : void 0;
|
|
19290
19882
|
} finally {
|
|
19291
19883
|
stopStructuredStreamStatusUpdates();
|
|
19884
|
+
deps.recordInteractiveStreamUiSnapshot?.(binding.codepilotSessionId, { active: false });
|
|
19292
19885
|
if (previewState) {
|
|
19293
19886
|
if (previewState.throttleTimer) {
|
|
19294
19887
|
clearTimeout(previewState.throttleTimer);
|
|
@@ -19504,6 +20097,7 @@ function resetMirrorReadState(subscription) {
|
|
|
19504
20097
|
subscription.fileIdentity = null;
|
|
19505
20098
|
subscription.trailingText = "";
|
|
19506
20099
|
subscription.activeMirrorTurnId = null;
|
|
20100
|
+
subscription.activeSpecialCallIds.clear();
|
|
19507
20101
|
subscription.bufferedRecords = [];
|
|
19508
20102
|
}
|
|
19509
20103
|
function createMirrorSubscription(input) {
|
|
@@ -19527,8 +20121,11 @@ function createMirrorSubscription(input) {
|
|
|
19527
20121
|
fileIdentity: null,
|
|
19528
20122
|
trailingText: "",
|
|
19529
20123
|
activeMirrorTurnId: null,
|
|
20124
|
+
activeSpecialCallIds: /* @__PURE__ */ new Set(),
|
|
19530
20125
|
bufferedRecords: [],
|
|
19531
20126
|
pendingTurn: null,
|
|
20127
|
+
pendingDeliveries: [],
|
|
20128
|
+
unknownMirrorKindsSeen: /* @__PURE__ */ new Set(),
|
|
19532
20129
|
missingThreadPolls: 0,
|
|
19533
20130
|
consecutiveFailures: 0,
|
|
19534
20131
|
suspendedUntil: null
|
|
@@ -19539,6 +20136,8 @@ function resetMirrorSubscriptionForThreadChange(subscription, lastDeliveredAt) {
|
|
|
19539
20136
|
subscription.lastDeliveredAt = lastDeliveredAt;
|
|
19540
20137
|
subscription.dirty = true;
|
|
19541
20138
|
subscription.pendingTurn = null;
|
|
20139
|
+
subscription.pendingDeliveries = [];
|
|
20140
|
+
subscription.unknownMirrorKindsSeen.clear();
|
|
19542
20141
|
subscription.missingThreadPolls = 0;
|
|
19543
20142
|
subscription.consecutiveFailures = 0;
|
|
19544
20143
|
subscription.suspendedUntil = null;
|
|
@@ -19735,6 +20334,7 @@ function isMirrorSnapshotUnchanged(subscription, snapshot) {
|
|
|
19735
20334
|
}
|
|
19736
20335
|
function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
19737
20336
|
let deliverableRecords = [];
|
|
20337
|
+
let unknownKinds = [];
|
|
19738
20338
|
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;
|
|
19739
20339
|
if (requiresFullRecover) {
|
|
19740
20340
|
const previousCursor = subscription.cursor;
|
|
@@ -19743,7 +20343,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
19743
20343
|
0,
|
|
19744
20344
|
snapshot.size,
|
|
19745
20345
|
"",
|
|
19746
|
-
null
|
|
20346
|
+
null,
|
|
20347
|
+
[]
|
|
19747
20348
|
);
|
|
19748
20349
|
const delta = reconcileDesktopMirrorCursor(subscription.cursor, fullDelta.records);
|
|
19749
20350
|
subscription.cursor = delta.nextCursor;
|
|
@@ -19751,6 +20352,8 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
19751
20352
|
subscription.trailingText = "";
|
|
19752
20353
|
subscription.fileOffset = snapshot.size;
|
|
19753
20354
|
subscription.activeMirrorTurnId = fullDelta.nextTurnId;
|
|
20355
|
+
subscription.activeSpecialCallIds = new Set(fullDelta.nextSpecialCallIds);
|
|
20356
|
+
unknownKinds = fullDelta.unknownKinds;
|
|
19754
20357
|
} else if (snapshot.size > subscription.fileOffset || subscription.trailingText) {
|
|
19755
20358
|
const previousCursor = subscription.cursor;
|
|
19756
20359
|
const delta = readDesktopSessionMirrorRecordDeltaByFilePath(
|
|
@@ -19758,19 +20361,25 @@ function readMirrorDeliverableRecords(subscription, snapshot) {
|
|
|
19758
20361
|
subscription.fileOffset,
|
|
19759
20362
|
snapshot.size,
|
|
19760
20363
|
subscription.trailingText,
|
|
19761
|
-
subscription.activeMirrorTurnId
|
|
20364
|
+
subscription.activeMirrorTurnId,
|
|
20365
|
+
subscription.activeSpecialCallIds
|
|
19762
20366
|
);
|
|
19763
20367
|
deliverableRecords = filterDuplicateAssistantEvents(previousCursor, delta.records);
|
|
19764
20368
|
subscription.cursor = advanceDesktopMirrorCursor(subscription.cursor, delta.records);
|
|
19765
20369
|
subscription.trailingText = delta.trailingText;
|
|
19766
20370
|
subscription.fileOffset = delta.nextOffset;
|
|
19767
20371
|
subscription.activeMirrorTurnId = delta.nextTurnId;
|
|
20372
|
+
subscription.activeSpecialCallIds = new Set(delta.nextSpecialCallIds);
|
|
20373
|
+
unknownKinds = delta.unknownKinds;
|
|
19768
20374
|
}
|
|
19769
20375
|
subscription.fileSize = snapshot.size;
|
|
19770
20376
|
subscription.fileMtimeMs = snapshot.mtimeMs;
|
|
19771
20377
|
subscription.fileIdentity = snapshot.identity;
|
|
19772
20378
|
subscription.dirty = false;
|
|
19773
|
-
return
|
|
20379
|
+
return {
|
|
20380
|
+
records: deliverableRecords,
|
|
20381
|
+
unknownKinds
|
|
20382
|
+
};
|
|
19774
20383
|
}
|
|
19775
20384
|
|
|
19776
20385
|
// src/lib/bridge/mirror-delivery-plan.ts
|
|
@@ -19785,7 +20394,7 @@ function buildMirrorDeliveryPlan(subscription, deliverableRecords, options) {
|
|
|
19785
20394
|
if (options.blocked) {
|
|
19786
20395
|
return {
|
|
19787
20396
|
syncReason: "mirror reconcile active task",
|
|
19788
|
-
|
|
20397
|
+
finalizedTurns: timedOutTurn ? [timedOutTurn] : []
|
|
19789
20398
|
};
|
|
19790
20399
|
}
|
|
19791
20400
|
const finalizedTurns = timedOutTurn ? [timedOutTurn] : [];
|
|
@@ -19793,12 +20402,12 @@ function buildMirrorDeliveryPlan(subscription, deliverableRecords, options) {
|
|
|
19793
20402
|
if (finalizedTurns.length === 0) {
|
|
19794
20403
|
return {
|
|
19795
20404
|
syncReason: "mirror reconcile no finalized turns",
|
|
19796
|
-
|
|
20405
|
+
finalizedTurns: []
|
|
19797
20406
|
};
|
|
19798
20407
|
}
|
|
19799
20408
|
return {
|
|
19800
20409
|
syncReason: "mirror reconcile delivered turns",
|
|
19801
|
-
|
|
20410
|
+
finalizedTurns
|
|
19802
20411
|
};
|
|
19803
20412
|
}
|
|
19804
20413
|
|
|
@@ -20022,21 +20631,36 @@ function createMirrorRuntime(getState2, options, deps) {
|
|
|
20022
20631
|
deps.syncMirrorSessionStateSafe(subscription.sessionId, "mirror reconcile unchanged snapshot");
|
|
20023
20632
|
return "processed";
|
|
20024
20633
|
}
|
|
20025
|
-
const
|
|
20634
|
+
const readResult = readMirrorDeliverableRecords(subscription, snapshot);
|
|
20635
|
+
const deliverableRecords = readResult.records;
|
|
20636
|
+
for (const kind of readResult.unknownKinds) {
|
|
20637
|
+
if (subscription.unknownMirrorKindsSeen.has(kind)) continue;
|
|
20638
|
+
subscription.unknownMirrorKindsSeen.add(kind);
|
|
20639
|
+
console.warn(
|
|
20640
|
+
`[bridge-manager] Unhandled desktop mirror event for thread ${subscription.threadId}: ${kind}`
|
|
20641
|
+
);
|
|
20642
|
+
}
|
|
20026
20643
|
if (deliverableRecords.length > 0) {
|
|
20027
20644
|
deps.observeSessionHealthRecords(subscription.sessionId, subscription.threadId, deliverableRecords);
|
|
20028
20645
|
}
|
|
20646
|
+
const blocked = getState2().activeTasks.has(subscription.sessionId) || deps.isMirrorSuppressed(subscription.sessionId);
|
|
20029
20647
|
const deliveryPlan = buildMirrorDeliveryPlan(subscription, deliverableRecords, {
|
|
20030
|
-
blocked
|
|
20648
|
+
blocked,
|
|
20031
20649
|
filterSuppressedRecords: deps.filterSuppressedMirrorRecords,
|
|
20032
20650
|
flushTimedOutTurn: (currentSubscription) => deps.flushTimedOutMirrorTurn(currentSubscription),
|
|
20033
20651
|
consumeBufferedTurns: (currentSubscription) => deps.consumeBufferedMirrorTurns(currentSubscription)
|
|
20034
20652
|
});
|
|
20035
|
-
if (deliveryPlan.
|
|
20036
|
-
|
|
20037
|
-
|
|
20038
|
-
|
|
20039
|
-
|
|
20653
|
+
if (deliveryPlan.finalizedTurns.length > 0) {
|
|
20654
|
+
enqueuePendingMirrorDeliveries(subscription, deliveryPlan.finalizedTurns);
|
|
20655
|
+
}
|
|
20656
|
+
const turnsToAttempt = selectPendingMirrorDeliveries(subscription, blocked);
|
|
20657
|
+
if (turnsToAttempt.length > 0) {
|
|
20658
|
+
const deliveryResult = await deps.deliverMirrorTurns(subscription, turnsToAttempt);
|
|
20659
|
+
if (deliveryResult.deliveredCount > 0) {
|
|
20660
|
+
removePendingMirrorDeliveries(subscription, turnsToAttempt.slice(0, deliveryResult.deliveredCount));
|
|
20661
|
+
}
|
|
20662
|
+
if (deliveryResult.error) {
|
|
20663
|
+
const error = deliveryResult.error;
|
|
20040
20664
|
console.warn("[bridge-manager] Mirror delivery failed:", error instanceof Error ? error.message : error);
|
|
20041
20665
|
}
|
|
20042
20666
|
}
|
|
@@ -20210,11 +20834,13 @@ var HEALTH_RECENT_PROGRESS_MS = 10 * 60 * 1e3;
|
|
|
20210
20834
|
var HEALTH_SLOW_OBSERVED_MS = 30 * 60 * 1e3;
|
|
20211
20835
|
var HEALTH_PROGRESS_PERSIST_THROTTLE_MS = 15 * 1e3;
|
|
20212
20836
|
var HEALTH_PROCESS_PROBE_CACHE_MS = 30 * 1e3;
|
|
20837
|
+
var HEALTH_STREAM_UI_STALL_MS = 60 * 1e3;
|
|
20213
20838
|
var RUNNING_HEALTH_STATUSES = /* @__PURE__ */ new Set([
|
|
20214
20839
|
"running_active",
|
|
20215
20840
|
"waiting_tool",
|
|
20216
20841
|
"slow_observed",
|
|
20217
20842
|
"suspected_stall",
|
|
20843
|
+
"suspected_stream_ui_stall",
|
|
20218
20844
|
"suspected_detached"
|
|
20219
20845
|
]);
|
|
20220
20846
|
function parseIsoMs(value) {
|
|
@@ -20282,6 +20908,10 @@ function buildProgressReason(type, detail) {
|
|
|
20282
20908
|
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u684C\u9762\u4F1A\u8BDD\u6D88\u606F\u3002";
|
|
20283
20909
|
case "commentary":
|
|
20284
20910
|
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u6267\u884C\u8FDB\u5C55\u8BF4\u660E\u3002";
|
|
20911
|
+
case "reasoning":
|
|
20912
|
+
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u601D\u8003/\u72B6\u6001\u8BF4\u660E\u3002";
|
|
20913
|
+
case "plan_update":
|
|
20914
|
+
return "\u6700\u8FD1\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
20285
20915
|
case "text":
|
|
20286
20916
|
return "\u6700\u8FD1\u6536\u5230\u4E86\u65B0\u7684\u6B63\u6587\u8F93\u51FA\u3002";
|
|
20287
20917
|
case "permission_wait":
|
|
@@ -20329,6 +20959,12 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
20329
20959
|
const activeToolName = summarizeActiveTools(activeTools) || trimOrNull(session.active_tool_name);
|
|
20330
20960
|
const activeToolStartedAt = getActiveToolStartedAt(activeTools) || trimOrNull(session.active_tool_started_at);
|
|
20331
20961
|
const lastToolFinishedAt = trimOrNull(session.last_tool_finished_at);
|
|
20962
|
+
const lastStreamUiAttemptAt = trimOrNull(session.last_stream_ui_attempt_at);
|
|
20963
|
+
const lastStreamUiUpdateAt = trimOrNull(session.last_stream_ui_update_at);
|
|
20964
|
+
const streamUiFlushStartedAt = trimOrNull(session.stream_ui_flush_started_at);
|
|
20965
|
+
const lastStreamUiErrorAt = trimOrNull(session.last_stream_ui_error_at);
|
|
20966
|
+
const lastStreamUiError = trimOrNull(session.last_stream_ui_error);
|
|
20967
|
+
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;
|
|
20332
20968
|
const sdkSessionId = trimOrNull(session.sdk_session_id);
|
|
20333
20969
|
const lastProgressMs = parseIsoMs(lastProgressAt || void 0);
|
|
20334
20970
|
const previousStatus = session.health_status || "idle";
|
|
@@ -20344,6 +20980,12 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
20344
20980
|
activeToolName,
|
|
20345
20981
|
activeToolStartedAt,
|
|
20346
20982
|
lastToolFinishedAt,
|
|
20983
|
+
lastStreamUiAttemptAt,
|
|
20984
|
+
lastStreamUiUpdateAt,
|
|
20985
|
+
streamUiFlushStartedAt,
|
|
20986
|
+
lastStreamUiErrorAt,
|
|
20987
|
+
lastStreamUiError,
|
|
20988
|
+
streamUiConsecutiveFailures,
|
|
20347
20989
|
sdkSessionId
|
|
20348
20990
|
};
|
|
20349
20991
|
}
|
|
@@ -20375,6 +21017,12 @@ function computeBaseDiagnosis(session, nowMs) {
|
|
|
20375
21017
|
activeToolName,
|
|
20376
21018
|
activeToolStartedAt,
|
|
20377
21019
|
lastToolFinishedAt,
|
|
21020
|
+
lastStreamUiAttemptAt,
|
|
21021
|
+
lastStreamUiUpdateAt,
|
|
21022
|
+
streamUiFlushStartedAt,
|
|
21023
|
+
lastStreamUiErrorAt,
|
|
21024
|
+
lastStreamUiError,
|
|
21025
|
+
streamUiConsecutiveFailures,
|
|
20378
21026
|
sdkSessionId
|
|
20379
21027
|
};
|
|
20380
21028
|
}
|
|
@@ -20415,11 +21063,75 @@ function applyProcessProbeDiagnosis(diagnosis, processProbe) {
|
|
|
20415
21063
|
processProbe
|
|
20416
21064
|
};
|
|
20417
21065
|
}
|
|
21066
|
+
function applyStreamUiDiagnosis(diagnosis, nowMs) {
|
|
21067
|
+
if (!isRunningRuntimeStatus(diagnosis.runtimeStatus)) {
|
|
21068
|
+
return diagnosis;
|
|
21069
|
+
}
|
|
21070
|
+
const lastProgressMs = parseIsoMs(diagnosis.lastProgressAt || void 0);
|
|
21071
|
+
if (!lastProgressMs || nowMs - lastProgressMs > HEALTH_RECENT_PROGRESS_MS) {
|
|
21072
|
+
return diagnosis;
|
|
21073
|
+
}
|
|
21074
|
+
const lastStreamUiUpdateMs = parseIsoMs(diagnosis.lastStreamUiUpdateAt || void 0);
|
|
21075
|
+
const lastStreamUiAttemptMs = parseIsoMs(diagnosis.lastStreamUiAttemptAt || void 0);
|
|
21076
|
+
const streamUiFlushStartedMs = parseIsoMs(diagnosis.streamUiFlushStartedAt || void 0);
|
|
21077
|
+
const lastStreamUiErrorText = diagnosis.lastStreamUiError?.trim();
|
|
21078
|
+
if (streamUiFlushStartedMs && nowMs - streamUiFlushStartedMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
21079
|
+
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"];
|
|
21080
|
+
if (diagnosis.streamUiConsecutiveFailures > 0) {
|
|
21081
|
+
details.push(`\u6700\u8FD1\u8FDE\u7EED\u5931\u8D25 ${diagnosis.streamUiConsecutiveFailures} \u6B21\u3002`);
|
|
21082
|
+
}
|
|
21083
|
+
if (lastStreamUiErrorText) {
|
|
21084
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
21085
|
+
}
|
|
21086
|
+
return {
|
|
21087
|
+
...diagnosis,
|
|
21088
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
21089
|
+
healthReason: details.join(" ")
|
|
21090
|
+
};
|
|
21091
|
+
}
|
|
21092
|
+
if (lastStreamUiUpdateMs && lastProgressMs - lastStreamUiUpdateMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
21093
|
+
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"];
|
|
21094
|
+
if (lastStreamUiErrorText) {
|
|
21095
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
21096
|
+
}
|
|
21097
|
+
return {
|
|
21098
|
+
...diagnosis,
|
|
21099
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
21100
|
+
healthReason: details.join(" ")
|
|
21101
|
+
};
|
|
21102
|
+
}
|
|
21103
|
+
if (!lastStreamUiUpdateMs && lastStreamUiAttemptMs && lastProgressMs - lastStreamUiAttemptMs >= HEALTH_STREAM_UI_STALL_MS) {
|
|
21104
|
+
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"];
|
|
21105
|
+
if (lastStreamUiErrorText) {
|
|
21106
|
+
details.push(`\u6700\u8FD1\u9519\u8BEF\uFF1A${lastStreamUiErrorText}`);
|
|
21107
|
+
}
|
|
21108
|
+
return {
|
|
21109
|
+
...diagnosis,
|
|
21110
|
+
healthStatus: "suspected_stream_ui_stall",
|
|
21111
|
+
healthReason: details.join(" ")
|
|
21112
|
+
};
|
|
21113
|
+
}
|
|
21114
|
+
return diagnosis;
|
|
21115
|
+
}
|
|
20418
21116
|
|
|
20419
21117
|
// src/lib/bridge/session-health-runtime.ts
|
|
20420
21118
|
function createSessionHealthRuntime(deps) {
|
|
20421
21119
|
const lastProgressPersistAt = /* @__PURE__ */ new Map();
|
|
20422
21120
|
const processProbeCache = /* @__PURE__ */ new Map();
|
|
21121
|
+
function summarizePlanUpdate(tasks) {
|
|
21122
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
21123
|
+
return "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u66F4\u65B0\u4E86\u4EFB\u52A1\u8BA1\u5212\u3002";
|
|
21124
|
+
}
|
|
21125
|
+
let inProgress = 0;
|
|
21126
|
+
let pending = 0;
|
|
21127
|
+
let completed = 0;
|
|
21128
|
+
for (const task of tasks) {
|
|
21129
|
+
if (task?.status === "completed") completed += 1;
|
|
21130
|
+
else if (task?.status === "in_progress") inProgress += 1;
|
|
21131
|
+
else pending += 1;
|
|
21132
|
+
}
|
|
21133
|
+
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`;
|
|
21134
|
+
}
|
|
20423
21135
|
function updateSessionHealth(sessionId, updates, options) {
|
|
20424
21136
|
const store = deps.getStore();
|
|
20425
21137
|
const session = store.getSession(sessionId);
|
|
@@ -20462,7 +21174,13 @@ function createSessionHealthRuntime(deps) {
|
|
|
20462
21174
|
active_tools_json: void 0,
|
|
20463
21175
|
active_tool_name: void 0,
|
|
20464
21176
|
active_tool_started_at: void 0,
|
|
20465
|
-
last_tool_finished_at: void 0
|
|
21177
|
+
last_tool_finished_at: void 0,
|
|
21178
|
+
last_stream_ui_attempt_at: void 0,
|
|
21179
|
+
last_stream_ui_update_at: void 0,
|
|
21180
|
+
stream_ui_flush_started_at: void 0,
|
|
21181
|
+
last_stream_ui_error_at: void 0,
|
|
21182
|
+
last_stream_ui_error: void 0,
|
|
21183
|
+
stream_ui_consecutive_failures: void 0
|
|
20466
21184
|
});
|
|
20467
21185
|
}
|
|
20468
21186
|
function recordInteractiveProgress(sessionId, type, detail) {
|
|
@@ -20530,11 +21248,29 @@ function createSessionHealthRuntime(deps) {
|
|
|
20530
21248
|
active_tools_json: void 0,
|
|
20531
21249
|
active_tool_name: void 0,
|
|
20532
21250
|
active_tool_started_at: void 0,
|
|
21251
|
+
stream_ui_flush_started_at: void 0,
|
|
20533
21252
|
last_health_check_at: nowIso4
|
|
20534
21253
|
}, { force: true });
|
|
20535
21254
|
lastProgressPersistAt.set(sessionId, Date.now());
|
|
20536
21255
|
processProbeCache.delete(sessionId);
|
|
20537
21256
|
}
|
|
21257
|
+
function toIso(value) {
|
|
21258
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
|
|
21259
|
+
return new Date(value).toISOString();
|
|
21260
|
+
}
|
|
21261
|
+
function recordStructuredStreamUi(sessionId, snapshot) {
|
|
21262
|
+
const updates = {
|
|
21263
|
+
stream_ui_flush_started_at: snapshot.active && snapshot.flushInFlight ? toIso(snapshot.flushInFlightSince ?? snapshot.lastAttemptAt) : void 0
|
|
21264
|
+
};
|
|
21265
|
+
if (snapshot.active) {
|
|
21266
|
+
updates.last_stream_ui_attempt_at = toIso(snapshot.lastAttemptAt);
|
|
21267
|
+
updates.last_stream_ui_update_at = toIso(snapshot.lastUpdateAt);
|
|
21268
|
+
updates.last_stream_ui_error_at = toIso(snapshot.lastErrorAt);
|
|
21269
|
+
updates.last_stream_ui_error = snapshot.lastError?.trim() || void 0;
|
|
21270
|
+
updates.stream_ui_consecutive_failures = snapshot.consecutiveFailures && snapshot.consecutiveFailures > 0 ? snapshot.consecutiveFailures : void 0;
|
|
21271
|
+
}
|
|
21272
|
+
updateSessionHealth(sessionId, updates);
|
|
21273
|
+
}
|
|
20538
21274
|
function observeDesktopMirrorRecords(sessionId, _threadId, records) {
|
|
20539
21275
|
for (const record of records) {
|
|
20540
21276
|
if (record.type === "task_started") {
|
|
@@ -20545,6 +21281,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
20545
21281
|
recordInteractiveEnd(sessionId, "completed", "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u5B8C\u6210\u5F53\u524D\u4EFB\u52A1\u3002");
|
|
20546
21282
|
continue;
|
|
20547
21283
|
}
|
|
21284
|
+
if (record.type === "task_aborted") {
|
|
21285
|
+
recordInteractiveEnd(sessionId, "aborted", "\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u5DF2\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u3002");
|
|
21286
|
+
continue;
|
|
21287
|
+
}
|
|
20548
21288
|
if (record.type === "tool_started") {
|
|
20549
21289
|
recordToolState(sessionId, record.toolId || record.signature, record.toolName || "tool", "running");
|
|
20550
21290
|
continue;
|
|
@@ -20558,6 +21298,22 @@ function createSessionHealthRuntime(deps) {
|
|
|
20558
21298
|
);
|
|
20559
21299
|
continue;
|
|
20560
21300
|
}
|
|
21301
|
+
if (record.type === "reasoning") {
|
|
21302
|
+
recordInteractiveProgress(
|
|
21303
|
+
sessionId,
|
|
21304
|
+
"reasoning",
|
|
21305
|
+
"\u68C0\u6D4B\u5230\u684C\u9762\u7EBF\u7A0B\u65B0\u7684\u601D\u8003/\u72B6\u6001\u8BF4\u660E\u3002"
|
|
21306
|
+
);
|
|
21307
|
+
continue;
|
|
21308
|
+
}
|
|
21309
|
+
if (record.type === "plan_update") {
|
|
21310
|
+
recordInteractiveProgress(
|
|
21311
|
+
sessionId,
|
|
21312
|
+
"plan_update",
|
|
21313
|
+
summarizePlanUpdate(record.tasks)
|
|
21314
|
+
);
|
|
21315
|
+
continue;
|
|
21316
|
+
}
|
|
20561
21317
|
if (record.type === "message") {
|
|
20562
21318
|
recordInteractiveProgress(
|
|
20563
21319
|
sessionId,
|
|
@@ -20572,7 +21328,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
20572
21328
|
const store = deps.getStore();
|
|
20573
21329
|
for (const session of store.listSessions()) {
|
|
20574
21330
|
if (!shouldTrackSession(session)) continue;
|
|
20575
|
-
const diagnosis =
|
|
21331
|
+
const diagnosis = applyStreamUiDiagnosis(
|
|
21332
|
+
{ ...computeBaseDiagnosis(session, nowMs), processProbe: null },
|
|
21333
|
+
nowMs
|
|
21334
|
+
);
|
|
20576
21335
|
updateSessionHealth(session.id, {
|
|
20577
21336
|
health_status: diagnosis.healthStatus,
|
|
20578
21337
|
health_reason: diagnosis.healthReason
|
|
@@ -20600,7 +21359,10 @@ function createSessionHealthRuntime(deps) {
|
|
|
20600
21359
|
if (!session) return null;
|
|
20601
21360
|
const base = computeBaseDiagnosis(session, Date.now());
|
|
20602
21361
|
const processProbe = await loadProcessProbe(session);
|
|
20603
|
-
const diagnosis =
|
|
21362
|
+
const diagnosis = applyStreamUiDiagnosis(
|
|
21363
|
+
applyProcessProbeDiagnosis(base, processProbe),
|
|
21364
|
+
Date.now()
|
|
21365
|
+
);
|
|
20604
21366
|
updateSessionHealth(sessionId, {
|
|
20605
21367
|
health_status: diagnosis.healthStatus,
|
|
20606
21368
|
health_reason: diagnosis.healthReason,
|
|
@@ -20618,6 +21380,7 @@ function createSessionHealthRuntime(deps) {
|
|
|
20618
21380
|
recordInteractiveStart,
|
|
20619
21381
|
recordInteractiveProgress,
|
|
20620
21382
|
recordToolState,
|
|
21383
|
+
recordStructuredStreamUi,
|
|
20621
21384
|
recordInteractiveEnd,
|
|
20622
21385
|
observeDesktopMirrorRecords,
|
|
20623
21386
|
reconcileSessionHealth,
|
|
@@ -20871,7 +21634,8 @@ function pushMirrorStreamingStatus(subscription, turnState, options = {}) {
|
|
|
20871
21634
|
}
|
|
20872
21635
|
const statusText = formatInteractiveRuntimeStatus(
|
|
20873
21636
|
Math.max(0, nowMs - startedAtMs),
|
|
20874
|
-
options.silentMs
|
|
21637
|
+
options.silentMs,
|
|
21638
|
+
turnState.statusNote
|
|
20875
21639
|
);
|
|
20876
21640
|
if (turnState.lastStatusText === statusText) return;
|
|
20877
21641
|
pushStreamFeedbackStatus(
|
|
@@ -20920,6 +21684,20 @@ function updateMirrorToolProgress(subscription, turnState) {
|
|
|
20920
21684
|
);
|
|
20921
21685
|
pushMirrorStreamingStatus(subscription, turnState);
|
|
20922
21686
|
}
|
|
21687
|
+
function updateMirrorTaskProgress(subscription, turnState) {
|
|
21688
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
21689
|
+
if (!adapter) return;
|
|
21690
|
+
pushStreamFeedbackTasks(
|
|
21691
|
+
createMirrorStreamFeedbackTarget(subscription, turnState, adapter),
|
|
21692
|
+
turnState.taskItems
|
|
21693
|
+
);
|
|
21694
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
21695
|
+
}
|
|
21696
|
+
function updateMirrorStatusProgress(subscription, turnState) {
|
|
21697
|
+
const adapter = getMirrorStreamingAdapter(subscription);
|
|
21698
|
+
if (!adapter) return;
|
|
21699
|
+
pushMirrorStreamingStatus(subscription, turnState);
|
|
21700
|
+
}
|
|
20923
21701
|
function stopMirrorStreaming(subscription, status = "interrupted") {
|
|
20924
21702
|
const adapter = getMirrorStreamingAdapter(subscription);
|
|
20925
21703
|
const pendingTurn = subscription.pendingTurn;
|
|
@@ -20980,12 +21758,21 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
20980
21758
|
subscription.lastDeliveredAt = turn.timestamp || nowIso3();
|
|
20981
21759
|
}
|
|
20982
21760
|
async function deliverMirrorTurns(subscription, turns) {
|
|
20983
|
-
|
|
20984
|
-
|
|
21761
|
+
let deliveredCount = 0;
|
|
21762
|
+
for (const turn of turns.slice(0, MIRROR_EVENT_BATCH_LIMIT)) {
|
|
21763
|
+
try {
|
|
21764
|
+
await deliverMirrorTurn(subscription, turn);
|
|
21765
|
+
deliveredCount += 1;
|
|
21766
|
+
} catch (error) {
|
|
21767
|
+
return { deliveredCount, error };
|
|
21768
|
+
}
|
|
20985
21769
|
}
|
|
21770
|
+
return { deliveredCount };
|
|
20986
21771
|
}
|
|
20987
21772
|
var MIRROR_TURN_HOOKS = {
|
|
20988
21773
|
onStreamText: updateMirrorStreaming,
|
|
21774
|
+
onStatusProgress: updateMirrorStatusProgress,
|
|
21775
|
+
onTaskProgress: updateMirrorTaskProgress,
|
|
20989
21776
|
onToolProgress: updateMirrorToolProgress
|
|
20990
21777
|
};
|
|
20991
21778
|
function consumeMirrorRecords2(subscription, records) {
|
|
@@ -21272,6 +22059,9 @@ async function handleMessage(adapter, msg) {
|
|
|
21272
22059
|
recordInteractiveHealthTool: (sessionId, toolId, toolName, status) => {
|
|
21273
22060
|
SESSION_HEALTH_RUNTIME.recordToolState(sessionId, toolId, toolName, status);
|
|
21274
22061
|
},
|
|
22062
|
+
recordInteractiveStreamUiSnapshot: (sessionId, snapshot) => {
|
|
22063
|
+
SESSION_HEALTH_RUNTIME.recordStructuredStreamUi(sessionId, snapshot);
|
|
22064
|
+
},
|
|
21275
22065
|
recordInteractiveHealthEnd: (sessionId, outcome, detail) => SESSION_HEALTH_RUNTIME.recordInteractiveEnd(sessionId, outcome, detail),
|
|
21276
22066
|
beginMirrorSuppression: beginMirrorSuppression2,
|
|
21277
22067
|
abortMirrorSuppression: abortMirrorSuppression2,
|
|
@@ -22424,10 +23214,87 @@ function setupLogger() {
|
|
|
22424
23214
|
console.warn = (...args) => write("WARN", args);
|
|
22425
23215
|
}
|
|
22426
23216
|
|
|
23217
|
+
// src/bridge-instance-lock.ts
|
|
23218
|
+
import fs14 from "node:fs";
|
|
23219
|
+
import path15 from "node:path";
|
|
23220
|
+
var runtimeDir = path15.join(CTI_HOME, "runtime");
|
|
23221
|
+
var bridgeInstanceLockFile = path15.join(runtimeDir, "bridge.instance.lock");
|
|
23222
|
+
function readJsonFile(filePath, fallback) {
|
|
23223
|
+
try {
|
|
23224
|
+
return JSON.parse(fs14.readFileSync(filePath, "utf-8"));
|
|
23225
|
+
} catch {
|
|
23226
|
+
return fallback;
|
|
23227
|
+
}
|
|
23228
|
+
}
|
|
23229
|
+
function isProcessAlive(pid) {
|
|
23230
|
+
if (!pid) return false;
|
|
23231
|
+
try {
|
|
23232
|
+
process.kill(pid, 0);
|
|
23233
|
+
return true;
|
|
23234
|
+
} catch {
|
|
23235
|
+
return false;
|
|
23236
|
+
}
|
|
23237
|
+
}
|
|
23238
|
+
function readBridgeInstanceLock(filePath = bridgeInstanceLockFile) {
|
|
23239
|
+
const parsed = readJsonFile(filePath, null);
|
|
23240
|
+
const pid = Number(parsed?.pid);
|
|
23241
|
+
const createdAt = typeof parsed?.createdAt === "string" ? parsed.createdAt : "";
|
|
23242
|
+
if (!Number.isFinite(pid) || pid <= 0 || !createdAt) return null;
|
|
23243
|
+
return { pid, createdAt };
|
|
23244
|
+
}
|
|
23245
|
+
function tryAcquireBridgeInstanceLock(options = {}) {
|
|
23246
|
+
const filePath = options.filePath ?? bridgeInstanceLockFile;
|
|
23247
|
+
const ownerPid = options.ownerPid ?? process.pid;
|
|
23248
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
23249
|
+
const isAlive = options.isAlive ?? isProcessAlive;
|
|
23250
|
+
const payload = {
|
|
23251
|
+
pid: ownerPid,
|
|
23252
|
+
createdAt: new Date(nowMs).toISOString()
|
|
23253
|
+
};
|
|
23254
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
23255
|
+
try {
|
|
23256
|
+
fs14.mkdirSync(path15.dirname(filePath), { recursive: true });
|
|
23257
|
+
fs14.writeFileSync(filePath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
|
|
23258
|
+
return { acquired: true };
|
|
23259
|
+
} catch (error) {
|
|
23260
|
+
const code2 = error.code;
|
|
23261
|
+
if (code2 !== "EEXIST") throw error;
|
|
23262
|
+
const existing2 = readBridgeInstanceLock(filePath);
|
|
23263
|
+
if (existing2 && existing2.pid !== ownerPid && isAlive(existing2.pid)) {
|
|
23264
|
+
return { acquired: false, holderPid: existing2.pid };
|
|
23265
|
+
}
|
|
23266
|
+
try {
|
|
23267
|
+
fs14.unlinkSync(filePath);
|
|
23268
|
+
} catch {
|
|
23269
|
+
}
|
|
23270
|
+
}
|
|
23271
|
+
}
|
|
23272
|
+
const existing = readBridgeInstanceLock(filePath);
|
|
23273
|
+
if (existing && existing.pid !== ownerPid && isAlive(existing.pid)) {
|
|
23274
|
+
return { acquired: false, holderPid: existing.pid };
|
|
23275
|
+
}
|
|
23276
|
+
return existing?.pid === ownerPid ? { acquired: true } : { acquired: false, holderPid: existing?.pid };
|
|
23277
|
+
}
|
|
23278
|
+
function releaseBridgeInstanceLock(filePath = bridgeInstanceLockFile, ownerPid = process.pid) {
|
|
23279
|
+
const existing = readBridgeInstanceLock(filePath);
|
|
23280
|
+
if (!existing) {
|
|
23281
|
+
try {
|
|
23282
|
+
fs14.unlinkSync(filePath);
|
|
23283
|
+
} catch {
|
|
23284
|
+
}
|
|
23285
|
+
return;
|
|
23286
|
+
}
|
|
23287
|
+
if (existing.pid !== ownerPid) return;
|
|
23288
|
+
try {
|
|
23289
|
+
fs14.unlinkSync(filePath);
|
|
23290
|
+
} catch {
|
|
23291
|
+
}
|
|
23292
|
+
}
|
|
23293
|
+
|
|
22427
23294
|
// src/main.ts
|
|
22428
|
-
var RUNTIME_DIR =
|
|
22429
|
-
var STATUS_FILE =
|
|
22430
|
-
var PID_FILE =
|
|
23295
|
+
var RUNTIME_DIR = path17.join(CTI_HOME, "runtime");
|
|
23296
|
+
var STATUS_FILE = path17.join(RUNTIME_DIR, "status.json");
|
|
23297
|
+
var PID_FILE = path17.join(RUNTIME_DIR, "bridge.pid");
|
|
22431
23298
|
async function resolveProvider(config2, pendingPerms) {
|
|
22432
23299
|
const runtime = config2.runtime;
|
|
22433
23300
|
if (runtime === "codex") {
|
|
@@ -22477,16 +23344,16 @@ async function resolveProvider(config2, pendingPerms) {
|
|
|
22477
23344
|
return new SDKLLMProvider(pendingPerms, cliPath, config2.autoApprove);
|
|
22478
23345
|
}
|
|
22479
23346
|
function writeStatus(info) {
|
|
22480
|
-
|
|
23347
|
+
fs16.mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
22481
23348
|
let existing = {};
|
|
22482
23349
|
try {
|
|
22483
|
-
existing = JSON.parse(
|
|
23350
|
+
existing = JSON.parse(fs16.readFileSync(STATUS_FILE, "utf-8"));
|
|
22484
23351
|
} catch {
|
|
22485
23352
|
}
|
|
22486
23353
|
const merged = { ...existing, ...info };
|
|
22487
23354
|
const tmp = STATUS_FILE + ".tmp";
|
|
22488
|
-
|
|
22489
|
-
|
|
23355
|
+
fs16.writeFileSync(tmp, JSON.stringify(merged, null, 2), "utf-8");
|
|
23356
|
+
fs16.renameSync(tmp, STATUS_FILE);
|
|
22490
23357
|
}
|
|
22491
23358
|
function getRunningChannels() {
|
|
22492
23359
|
return getStatus().adapters.map((adapter) => adapter.channelType).sort();
|
|
@@ -22495,6 +23362,24 @@ function getAdapterStatuses() {
|
|
|
22495
23362
|
return getStatus().adapters;
|
|
22496
23363
|
}
|
|
22497
23364
|
async function main() {
|
|
23365
|
+
const lockState = tryAcquireBridgeInstanceLock();
|
|
23366
|
+
if (!lockState.acquired) {
|
|
23367
|
+
const holderPid = lockState.holderPid;
|
|
23368
|
+
writeStatus({
|
|
23369
|
+
running: true,
|
|
23370
|
+
...Number.isFinite(holderPid) && holderPid ? { pid: holderPid } : {}
|
|
23371
|
+
});
|
|
23372
|
+
console.log(
|
|
23373
|
+
`[codex-to-im] Another bridge daemon is already running${holderPid ? ` (PID: ${holderPid})` : ""}. Exiting duplicate launcher.`
|
|
23374
|
+
);
|
|
23375
|
+
process.exit(0);
|
|
23376
|
+
}
|
|
23377
|
+
let instanceLockHeld = true;
|
|
23378
|
+
const releaseInstanceLock = () => {
|
|
23379
|
+
if (!instanceLockHeld) return;
|
|
23380
|
+
releaseBridgeInstanceLock(void 0, process.pid);
|
|
23381
|
+
instanceLockHeld = false;
|
|
23382
|
+
};
|
|
22498
23383
|
const config2 = loadConfig();
|
|
22499
23384
|
setupLogger();
|
|
22500
23385
|
const runId = crypto10.randomUUID();
|
|
@@ -22513,8 +23398,8 @@ async function main() {
|
|
|
22513
23398
|
permissions: gateway,
|
|
22514
23399
|
lifecycle: {
|
|
22515
23400
|
onBridgeStart: () => {
|
|
22516
|
-
|
|
22517
|
-
|
|
23401
|
+
fs16.mkdirSync(RUNTIME_DIR, { recursive: true });
|
|
23402
|
+
fs16.writeFileSync(PID_FILE, String(process.pid), "utf-8");
|
|
22518
23403
|
const channels = getRunningChannels();
|
|
22519
23404
|
writeStatus({
|
|
22520
23405
|
running: true,
|
|
@@ -22537,6 +23422,7 @@ async function main() {
|
|
|
22537
23422
|
console.log(`[codex-to-im] Active channels updated: ${channels.join(", ") || "none"}`);
|
|
22538
23423
|
},
|
|
22539
23424
|
onBridgeStop: () => {
|
|
23425
|
+
releaseInstanceLock();
|
|
22540
23426
|
writeStatus({ running: false, channels: [], adapters: [] });
|
|
22541
23427
|
console.log("[codex-to-im] Bridge stopped");
|
|
22542
23428
|
}
|
|
@@ -22551,6 +23437,7 @@ async function main() {
|
|
|
22551
23437
|
console.log(`[codex-to-im] Shutting down (${reason})...`);
|
|
22552
23438
|
pendingPerms.denyAll();
|
|
22553
23439
|
await stop();
|
|
23440
|
+
releaseInstanceLock();
|
|
22554
23441
|
writeStatus({ running: false, lastExitReason: reason });
|
|
22555
23442
|
process.exit(0);
|
|
22556
23443
|
};
|
|
@@ -22563,6 +23450,7 @@ async function main() {
|
|
|
22563
23450
|
});
|
|
22564
23451
|
process.on("uncaughtException", (err) => {
|
|
22565
23452
|
console.error("[codex-to-im] uncaughtException:", err.stack || err.message);
|
|
23453
|
+
releaseInstanceLock();
|
|
22566
23454
|
writeStatus({ running: false, lastExitReason: `uncaughtException: ${err.message}` });
|
|
22567
23455
|
process.exit(1);
|
|
22568
23456
|
});
|
|
@@ -22570,6 +23458,7 @@ async function main() {
|
|
|
22570
23458
|
console.log(`[codex-to-im] beforeExit (code: ${code2})`);
|
|
22571
23459
|
});
|
|
22572
23460
|
process.on("exit", (code2) => {
|
|
23461
|
+
releaseInstanceLock();
|
|
22573
23462
|
console.log(`[codex-to-im] exit (code: ${code2})`);
|
|
22574
23463
|
});
|
|
22575
23464
|
setInterval(() => {
|
|
@@ -22577,6 +23466,7 @@ async function main() {
|
|
|
22577
23466
|
}
|
|
22578
23467
|
main().catch((err) => {
|
|
22579
23468
|
console.error("[codex-to-im] Fatal error:", err instanceof Error ? err.stack || err.message : err);
|
|
23469
|
+
releaseBridgeInstanceLock(void 0, process.pid);
|
|
22580
23470
|
try {
|
|
22581
23471
|
writeStatus({ running: false, lastExitReason: `fatal: ${err instanceof Error ? err.message : String(err)}` });
|
|
22582
23472
|
} catch {
|