@vibe-lark/larkpal 0.1.58 → 0.1.60
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/main.mjs +978 -715
- package/package.json +2 -2
package/dist/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import * as path$2 from "node:path";
|
|
|
6
6
|
import path, { basename, dirname, extname, join, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import "dotenv/config";
|
|
9
|
-
import { access, lstat, mkdir, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
9
|
+
import { access, lstat, mkdir, open, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { execFileSync, spawn } from "node:child_process";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
@@ -38,7 +38,7 @@ import { Readable } from "node:stream";
|
|
|
38
38
|
*
|
|
39
39
|
* 本模块在启动时检测它们是否已安装,缺失时给出明确的安装指引。
|
|
40
40
|
*/
|
|
41
|
-
const log$
|
|
41
|
+
const log$39 = larkLogger("preflight");
|
|
42
42
|
function detectCli(name) {
|
|
43
43
|
try {
|
|
44
44
|
const cliPath = execFileSync("which", [name], {
|
|
@@ -76,14 +76,14 @@ function detectCli(name) {
|
|
|
76
76
|
* 缺失任一依赖时打印安装指引并抛出错误,阻止启动。
|
|
77
77
|
*/
|
|
78
78
|
function preflight() {
|
|
79
|
-
log$
|
|
79
|
+
log$39.info("开始前置环境检查...");
|
|
80
80
|
const useCodex = (process.env.LARKPAL_RUNTIME || "claude-code") === "codex-app-server";
|
|
81
81
|
const claude = detectCli("claude");
|
|
82
82
|
const codex = useCodex ? detectCli(process.env.LARKPAL_CODEX_BIN || "codex") : void 0;
|
|
83
83
|
const larkCli = detectCli("lark-cli");
|
|
84
84
|
const status = (dep) => dep.installed ? `✅ ${dep.name} (${dep.version ?? "unknown"}) → ${dep.path}` : `❌ ${dep.name} 未安装`;
|
|
85
|
-
log$
|
|
86
|
-
log$
|
|
85
|
+
log$39.info(` ${status(useCodex && codex ? codex : claude)}`);
|
|
86
|
+
log$39.info(` ${status(larkCli)}`);
|
|
87
87
|
const missing = [];
|
|
88
88
|
if (useCodex) {
|
|
89
89
|
if (!codex?.installed) missing.push([
|
|
@@ -119,10 +119,10 @@ function preflight() {
|
|
|
119
119
|
"",
|
|
120
120
|
"请安装缺失的依赖后重新启动 LarkPal。"
|
|
121
121
|
].join("\n");
|
|
122
|
-
log$
|
|
122
|
+
log$39.error(msg);
|
|
123
123
|
throw new Error("前置环境检查未通过,缺少必要依赖");
|
|
124
124
|
}
|
|
125
|
-
log$
|
|
125
|
+
log$39.info("前置环境检查通过 ✓");
|
|
126
126
|
return {
|
|
127
127
|
claude,
|
|
128
128
|
codex,
|
|
@@ -147,7 +147,7 @@ function preflight() {
|
|
|
147
147
|
* 同时生成新版 (~/.lark-cli/config.json) 和旧版 (~/.config/lark/config.json)
|
|
148
148
|
* 格式的配置文件,保证 lark-cli 无论通过哪种路径读取都能正常工作。
|
|
149
149
|
*/
|
|
150
|
-
const log$
|
|
150
|
+
const log$38 = larkLogger("config/ensure-lark-cli");
|
|
151
151
|
/** 新版 lark-cli 配置路径 */
|
|
152
152
|
const NEW_CONFIG_DIR = join(homedir(), ".lark-cli");
|
|
153
153
|
const NEW_CONFIG_PATH = join(NEW_CONFIG_DIR, "config.json");
|
|
@@ -164,19 +164,19 @@ function ensureLarkCliConfig() {
|
|
|
164
164
|
const appId = process.env.LARK_APP_ID;
|
|
165
165
|
const appSecret = process.env.LARK_APP_SECRET;
|
|
166
166
|
if (!appId || !appSecret) {
|
|
167
|
-
log$
|
|
167
|
+
log$38.info("环境变量凭证未设置,跳过 lark-cli 配置同步");
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
const hasNewConfig = existsSync(NEW_CONFIG_PATH);
|
|
171
171
|
const hasLegacyConfig = existsSync(LEGACY_CONFIG_PATH);
|
|
172
172
|
if (hasNewConfig && hasLegacyConfig) {
|
|
173
|
-
log$
|
|
173
|
+
log$38.info("lark-cli 配置文件已存在,跳过同步", {
|
|
174
174
|
newConfig: NEW_CONFIG_PATH,
|
|
175
175
|
legacyConfig: LEGACY_CONFIG_PATH
|
|
176
176
|
});
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
-
log$
|
|
179
|
+
log$38.info("检测到环境变量凭证但 lark-cli 配置文件缺失,开始同步", {
|
|
180
180
|
appId,
|
|
181
181
|
hasNewConfig,
|
|
182
182
|
hasLegacyConfig
|
|
@@ -195,9 +195,9 @@ function ensureLarkCliConfig() {
|
|
|
195
195
|
encoding: "utf-8",
|
|
196
196
|
mode: 384
|
|
197
197
|
});
|
|
198
|
-
log$
|
|
198
|
+
log$38.info("已生成新版 lark-cli 配置文件", { path: NEW_CONFIG_PATH });
|
|
199
199
|
} catch (err) {
|
|
200
|
-
log$
|
|
200
|
+
log$38.warn("生成新版 lark-cli 配置文件失败", {
|
|
201
201
|
path: NEW_CONFIG_PATH,
|
|
202
202
|
error: err instanceof Error ? err.message : String(err)
|
|
203
203
|
});
|
|
@@ -216,9 +216,9 @@ function ensureLarkCliConfig() {
|
|
|
216
216
|
encoding: "utf-8",
|
|
217
217
|
mode: 384
|
|
218
218
|
});
|
|
219
|
-
log$
|
|
219
|
+
log$38.info("已生成旧版 lark-cli 配置文件", { path: LEGACY_CONFIG_PATH });
|
|
220
220
|
} catch (err) {
|
|
221
|
-
log$
|
|
221
|
+
log$38.warn("生成旧版 lark-cli 配置文件失败", {
|
|
222
222
|
path: LEGACY_CONFIG_PATH,
|
|
223
223
|
error: err instanceof Error ? err.message : String(err)
|
|
224
224
|
});
|
|
@@ -439,7 +439,7 @@ async function ensureDefaults() {
|
|
|
439
439
|
* - github: { type: "api_key", token }
|
|
440
440
|
* - custom-api: { type: "http_bearer", base_url, token }
|
|
441
441
|
*/
|
|
442
|
-
const log$
|
|
442
|
+
const log$37 = larkLogger("user/credential-vault");
|
|
443
443
|
var CredentialVault = class {
|
|
444
444
|
credentialPath;
|
|
445
445
|
encryptionKey;
|
|
@@ -451,12 +451,12 @@ var CredentialVault = class {
|
|
|
451
451
|
if (keyEnv) {
|
|
452
452
|
this.encryptionKey = keyEnv.length === 64 ? Buffer.from(keyEnv, "hex") : Buffer.from(keyEnv, "base64");
|
|
453
453
|
if (this.encryptionKey.length !== 32) {
|
|
454
|
-
log$
|
|
454
|
+
log$37.error("LARKPAL_ENCRYPTION_KEY 长度不正确,需要 32 字节", { actualLength: this.encryptionKey.length });
|
|
455
455
|
this.encryptionKey = null;
|
|
456
|
-
} else log$
|
|
456
|
+
} else log$37.info("凭证加密已启用");
|
|
457
457
|
} else {
|
|
458
458
|
this.encryptionKey = null;
|
|
459
|
-
log$
|
|
459
|
+
log$37.warn("LARKPAL_ENCRYPTION_KEY 未设置,凭证将以明文存储(仅限开发模式)");
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
/**
|
|
@@ -464,7 +464,7 @@ var CredentialVault = class {
|
|
|
464
464
|
*/
|
|
465
465
|
async load() {
|
|
466
466
|
if (!existsSync$1(this.credentialPath)) {
|
|
467
|
-
log$
|
|
467
|
+
log$37.info("凭证文件不存在,使用空凭证", { path: this.credentialPath });
|
|
468
468
|
this.store = {};
|
|
469
469
|
this.loaded = true;
|
|
470
470
|
return;
|
|
@@ -473,12 +473,12 @@ var CredentialVault = class {
|
|
|
473
473
|
const raw = await readFile$1(this.credentialPath, "utf-8");
|
|
474
474
|
this.store = JSON.parse(raw);
|
|
475
475
|
this.loaded = true;
|
|
476
|
-
log$
|
|
476
|
+
log$37.info("凭证文件加载完成", {
|
|
477
477
|
path: this.credentialPath,
|
|
478
478
|
credentialCount: Object.keys(this.store).length
|
|
479
479
|
});
|
|
480
480
|
} catch (err) {
|
|
481
|
-
log$
|
|
481
|
+
log$37.error("凭证文件加载失败", {
|
|
482
482
|
path: this.credentialPath,
|
|
483
483
|
error: err instanceof Error ? err.message : String(err)
|
|
484
484
|
});
|
|
@@ -492,12 +492,12 @@ var CredentialVault = class {
|
|
|
492
492
|
async save() {
|
|
493
493
|
try {
|
|
494
494
|
await writeFile$1(this.credentialPath, JSON.stringify(this.store, null, 2), "utf-8");
|
|
495
|
-
log$
|
|
495
|
+
log$37.info("凭证文件保存完成", {
|
|
496
496
|
path: this.credentialPath,
|
|
497
497
|
credentialCount: Object.keys(this.store).length
|
|
498
498
|
});
|
|
499
499
|
} catch (err) {
|
|
500
|
-
log$
|
|
500
|
+
log$37.error("凭证文件保存失败", {
|
|
501
501
|
path: this.credentialPath,
|
|
502
502
|
error: err instanceof Error ? err.message : String(err)
|
|
503
503
|
});
|
|
@@ -519,7 +519,7 @@ var CredentialVault = class {
|
|
|
519
519
|
*/
|
|
520
520
|
async setCredential(name, credential) {
|
|
521
521
|
if (!this.loaded) await this.load();
|
|
522
|
-
log$
|
|
522
|
+
log$37.info("设置凭证", {
|
|
523
523
|
name,
|
|
524
524
|
fields: Object.keys(credential)
|
|
525
525
|
});
|
|
@@ -535,7 +535,7 @@ var CredentialVault = class {
|
|
|
535
535
|
if (!(name in this.store)) return false;
|
|
536
536
|
delete this.store[name];
|
|
537
537
|
await this.save();
|
|
538
|
-
log$
|
|
538
|
+
log$37.info("凭证已删除", { name });
|
|
539
539
|
return true;
|
|
540
540
|
}
|
|
541
541
|
/**
|
|
@@ -564,7 +564,7 @@ var CredentialVault = class {
|
|
|
564
564
|
envVars[envKey] = String(fieldValue);
|
|
565
565
|
}
|
|
566
566
|
} catch (err) {
|
|
567
|
-
log$
|
|
567
|
+
log$37.warn("凭证解密失败,跳过", {
|
|
568
568
|
name,
|
|
569
569
|
error: err instanceof Error ? err.message : String(err)
|
|
570
570
|
});
|
|
@@ -621,7 +621,7 @@ var CredentialVault = class {
|
|
|
621
621
|
* - B 用户的 CC 进程连接 B 的数据库 MCP Server
|
|
622
622
|
* - 全局工具(lark-cli、文件操作等)所有用户共享
|
|
623
623
|
*/
|
|
624
|
-
const log$
|
|
624
|
+
const log$36 = larkLogger("user/mcp-merge");
|
|
625
625
|
/** 全局 MCP 配置路径 */
|
|
626
626
|
const GLOBAL_MCP_CONFIG_PATH = path$1.join(os.homedir(), ".claude", "mcp-servers.json");
|
|
627
627
|
/**
|
|
@@ -677,9 +677,9 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
677
677
|
if (existsSync$1(GLOBAL_MCP_CONFIG_PATH)) try {
|
|
678
678
|
const raw = await readFile$1(GLOBAL_MCP_CONFIG_PATH, "utf-8");
|
|
679
679
|
globalConfig = JSON.parse(raw);
|
|
680
|
-
log$
|
|
680
|
+
log$36.info("全局 MCP 配置加载完成", { serverCount: Object.keys(globalConfig).length });
|
|
681
681
|
} catch (err) {
|
|
682
|
-
log$
|
|
682
|
+
log$36.warn("全局 MCP 配置文件解析失败", {
|
|
683
683
|
path: GLOBAL_MCP_CONFIG_PATH,
|
|
684
684
|
error: err instanceof Error ? err.message : String(err)
|
|
685
685
|
});
|
|
@@ -690,13 +690,13 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
690
690
|
if (existsSync$1(userMcpPath)) try {
|
|
691
691
|
const raw = await readFile$1(userMcpPath, "utf-8");
|
|
692
692
|
userConfig = JSON.parse(raw);
|
|
693
|
-
log$
|
|
693
|
+
log$36.info("用户 MCP 配置加载完成", {
|
|
694
694
|
userId: userCtx.userId,
|
|
695
695
|
serverCount: Object.keys(userConfig).length,
|
|
696
696
|
servers: Object.keys(userConfig)
|
|
697
697
|
});
|
|
698
698
|
} catch (err) {
|
|
699
|
-
log$
|
|
699
|
+
log$36.warn("用户 MCP 配置文件解析失败", {
|
|
700
700
|
userId: userCtx?.userId,
|
|
701
701
|
path: userMcpPath,
|
|
702
702
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -708,7 +708,7 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
708
708
|
...globalConfig,
|
|
709
709
|
...userConfig
|
|
710
710
|
};
|
|
711
|
-
log$
|
|
711
|
+
log$36.info("MCP 配置合并完成", {
|
|
712
712
|
userId: userCtx?.userId,
|
|
713
713
|
builtinServers: Object.keys(builtinConfig),
|
|
714
714
|
globalServers: Object.keys(globalConfig),
|
|
@@ -717,7 +717,7 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
717
717
|
});
|
|
718
718
|
const tmpPath = path$1.join(os.tmpdir(), `mcp-${sessionId}.json`);
|
|
719
719
|
await writeFile$1(tmpPath, JSON.stringify({ mcpServers: merged }, null, 2), "utf-8");
|
|
720
|
-
log$
|
|
720
|
+
log$36.info("MCP 合并配置已写入临时文件", {
|
|
721
721
|
path: tmpPath,
|
|
722
722
|
serverCount: Object.keys(merged).length
|
|
723
723
|
});
|
|
@@ -731,7 +731,7 @@ async function cleanupMcpConfig(sessionId) {
|
|
|
731
731
|
try {
|
|
732
732
|
if (existsSync$1(tmpPath)) {
|
|
733
733
|
await unlink$1(tmpPath);
|
|
734
|
-
log$
|
|
734
|
+
log$36.debug("MCP 临时配置文件已清理", { path: tmpPath });
|
|
735
735
|
}
|
|
736
736
|
} catch {}
|
|
737
737
|
}
|
|
@@ -755,8 +755,7 @@ const TOOL_DISPLAY_NAMES = {
|
|
|
755
755
|
WebSearch: "正在搜索网页",
|
|
756
756
|
WebFetch: "正在获取网页内容",
|
|
757
757
|
TodoWrite: "正在更新任务列表",
|
|
758
|
-
Task: "正在执行子任务"
|
|
759
|
-
_AutoCompact: "正在压缩对话历史"
|
|
758
|
+
Task: "正在执行子任务"
|
|
760
759
|
};
|
|
761
760
|
/** 获取工具的中文显示名称,未知工具返回通用描述 */
|
|
762
761
|
function getToolDisplayName(toolName) {
|
|
@@ -780,7 +779,7 @@ function getToolDisplayName(toolName) {
|
|
|
780
779
|
* - result(data) — 最终结果
|
|
781
780
|
* - parseError(error, rawLine) — 解析错误(不中断流)
|
|
782
781
|
*/
|
|
783
|
-
const log$
|
|
782
|
+
const log$35 = larkLogger("cc-runtime/stream-parser");
|
|
784
783
|
var CCStreamParser = class extends EventEmitter {
|
|
785
784
|
/**
|
|
786
785
|
* 解析一行 NDJSON 文本
|
|
@@ -794,7 +793,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
794
793
|
try {
|
|
795
794
|
msg = JSON.parse(trimmed);
|
|
796
795
|
} catch (err) {
|
|
797
|
-
log$
|
|
796
|
+
log$35.warn("NDJSON 解析失败,跳过该行", {
|
|
798
797
|
error: String(err),
|
|
799
798
|
rawLine: trimmed.slice(0, 200)
|
|
800
799
|
});
|
|
@@ -802,7 +801,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
802
801
|
return;
|
|
803
802
|
}
|
|
804
803
|
if (msg.parent_tool_use_id != null && msg.type !== "result") {
|
|
805
|
-
log$
|
|
804
|
+
log$35.debug("跳过 subagent 内部事件", {
|
|
806
805
|
type: msg.type,
|
|
807
806
|
parentToolUseId: msg.parent_tool_use_id
|
|
808
807
|
});
|
|
@@ -825,14 +824,14 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
825
824
|
this.handleResult(msg);
|
|
826
825
|
break;
|
|
827
826
|
case "system":
|
|
828
|
-
log$
|
|
827
|
+
log$35.info("收到 system 消息", {
|
|
829
828
|
subtype: msg.subtype,
|
|
830
829
|
detail: JSON.stringify(msg).slice(0, 500)
|
|
831
830
|
});
|
|
832
831
|
this.emit("system", msg);
|
|
833
832
|
break;
|
|
834
833
|
default:
|
|
835
|
-
log$
|
|
834
|
+
log$35.debug("收到未知消息类型,已忽略", { type: msg.type });
|
|
836
835
|
break;
|
|
837
836
|
}
|
|
838
837
|
}
|
|
@@ -851,13 +850,13 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
851
850
|
switch (event.type) {
|
|
852
851
|
case "content_block_start": {
|
|
853
852
|
const block = event.content_block;
|
|
854
|
-
log$
|
|
853
|
+
log$35.debug("content_block_start 事件", {
|
|
855
854
|
blockType: block?.type,
|
|
856
855
|
toolName: block?.name,
|
|
857
856
|
toolUseId: block?.id
|
|
858
857
|
});
|
|
859
858
|
if (block?.type === "tool_use" && block.name) {
|
|
860
|
-
log$
|
|
859
|
+
log$35.info("工具调用开始", {
|
|
861
860
|
toolName: block.name,
|
|
862
861
|
displayName: getToolDisplayName(block.name),
|
|
863
862
|
toolUseId: block.id
|
|
@@ -876,7 +875,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
876
875
|
case "message_delta": {
|
|
877
876
|
const stopReason = event.delta?.stop_reason;
|
|
878
877
|
if (stopReason) {
|
|
879
|
-
log$
|
|
878
|
+
log$35.info("轮次结束", { stopReason });
|
|
880
879
|
this.emit("turnEnd", stopReason);
|
|
881
880
|
}
|
|
882
881
|
break;
|
|
@@ -896,7 +895,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
896
895
|
const content = msg.message?.content;
|
|
897
896
|
const stopReason = msg.message?.stop_reason;
|
|
898
897
|
const blockTypes = Array.isArray(content) ? content.map((b) => `${b.type}${b.name ? ":" + b.name : ""}`).join(", ") : "none";
|
|
899
|
-
log$
|
|
898
|
+
log$35.info("收到完整 assistant 消息", {
|
|
900
899
|
model: msg.message?.model,
|
|
901
900
|
stopReason,
|
|
902
901
|
contentBlocks: content?.length,
|
|
@@ -904,7 +903,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
904
903
|
});
|
|
905
904
|
if (Array.isArray(content)) {
|
|
906
905
|
for (const block of content) if (block.type === "tool_use" && block.name) {
|
|
907
|
-
log$
|
|
906
|
+
log$35.info("从 assistant 消息提取工具调用", {
|
|
908
907
|
toolName: block.name,
|
|
909
908
|
displayName: getToolDisplayName(block.name),
|
|
910
909
|
toolUseId: block.id
|
|
@@ -912,11 +911,6 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
912
911
|
this.emit("toolUseStart", block.name, block.input ?? {});
|
|
913
912
|
}
|
|
914
913
|
}
|
|
915
|
-
const usage = msg.message?.usage;
|
|
916
|
-
if (usage && typeof usage.input_tokens === "number" && usage.input_tokens > 0) this.emit("usage", {
|
|
917
|
-
input_tokens: usage.input_tokens,
|
|
918
|
-
output_tokens: usage.output_tokens ?? 0
|
|
919
|
-
});
|
|
920
914
|
this.emit("assistant", msg);
|
|
921
915
|
}
|
|
922
916
|
/**
|
|
@@ -928,7 +922,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
928
922
|
const content = msg.message?.content;
|
|
929
923
|
if (!Array.isArray(content)) return;
|
|
930
924
|
for (const item of content) if (item.type === "tool_result" && item.tool_use_id) {
|
|
931
|
-
log$
|
|
925
|
+
log$35.debug("工具结果返回", { toolUseId: item.tool_use_id });
|
|
932
926
|
this.emit("toolResult", item.tool_use_id);
|
|
933
927
|
}
|
|
934
928
|
}
|
|
@@ -936,7 +930,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
936
930
|
* 处理 tool_progress 消息 — 工具执行进度
|
|
937
931
|
*/
|
|
938
932
|
handleToolProgress(msg) {
|
|
939
|
-
log$
|
|
933
|
+
log$35.debug("工具执行进度", {
|
|
940
934
|
toolName: msg.tool_name,
|
|
941
935
|
elapsed: msg.elapsed_time_seconds
|
|
942
936
|
});
|
|
@@ -946,18 +940,13 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
946
940
|
* 处理 result 消息 — CC 执行完成的最终结果
|
|
947
941
|
*/
|
|
948
942
|
handleResult(msg) {
|
|
949
|
-
log$
|
|
943
|
+
log$35.info("CC 执行完成", {
|
|
950
944
|
subtype: msg.subtype,
|
|
951
945
|
isError: msg.is_error,
|
|
952
946
|
durationMs: msg.duration_ms,
|
|
953
947
|
totalCostUsd: msg.total_cost_usd,
|
|
954
948
|
numTurns: msg.num_turns
|
|
955
949
|
});
|
|
956
|
-
const usage = msg.usage;
|
|
957
|
-
if (usage && typeof usage.input_tokens === "number" && usage.input_tokens > 0) this.emit("usage", {
|
|
958
|
-
input_tokens: usage.input_tokens,
|
|
959
|
-
output_tokens: usage.output_tokens ?? 0
|
|
960
|
-
});
|
|
961
950
|
this.emit("result", {
|
|
962
951
|
subtype: msg.subtype,
|
|
963
952
|
result: msg.result,
|
|
@@ -984,7 +973,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
984
973
|
* 多轮对话:进程内自动保持完整对话上下文(mutableMessages 数组累积),
|
|
985
974
|
* 无需外部维护历史。
|
|
986
975
|
*/
|
|
987
|
-
const log$
|
|
976
|
+
const log$34 = larkLogger("cc-runtime/process-manager");
|
|
988
977
|
/**
|
|
989
978
|
* LarkPal 会话 UUID v5 命名空间
|
|
990
979
|
*
|
|
@@ -1005,25 +994,6 @@ const IDLE_TIMEOUT_MS = (() => {
|
|
|
1005
994
|
return seconds * 1e3;
|
|
1006
995
|
})();
|
|
1007
996
|
/**
|
|
1008
|
-
* 自动压缩阈值(input_tokens 数)。
|
|
1009
|
-
*
|
|
1010
|
-
* 当 CC 返回的 input_tokens 超过此阈值时,在下一次用户消息发送前
|
|
1011
|
-
* 自动执行 /compact 命令压缩对话历史。
|
|
1012
|
-
*
|
|
1013
|
-
* 通过 LARKPAL_CC_AUTO_COMPACT_THRESHOLD 环境变量配置,默认 0(禁用)。
|
|
1014
|
-
* 设为 0 表示禁用自动压缩。
|
|
1015
|
-
*
|
|
1016
|
-
* LarkPal 现在优先通过 runtime-owned memory/ephemeral teammate eval 管理上下文;
|
|
1017
|
-
* 该开关只作为显式启用的应急制动。
|
|
1018
|
-
*/
|
|
1019
|
-
const AUTO_COMPACT_THRESHOLD = (() => {
|
|
1020
|
-
const envVal = process.env.LARKPAL_CC_AUTO_COMPACT_THRESHOLD;
|
|
1021
|
-
if (envVal === void 0 || envVal === "") return 0;
|
|
1022
|
-
const tokens = parseInt(envVal, 10);
|
|
1023
|
-
if (isNaN(tokens) || tokens < 0) return 5e4;
|
|
1024
|
-
return tokens;
|
|
1025
|
-
})();
|
|
1026
|
-
/**
|
|
1027
997
|
* CC 推理努力级别。
|
|
1028
998
|
*
|
|
1029
999
|
* 控制模型的 thinking budget(思维深度),影响延迟和成本:
|
|
@@ -1079,17 +1049,6 @@ var SessionProcessManager = class {
|
|
|
1079
1049
|
messageCompletionResolvers = /* @__PURE__ */ new Map();
|
|
1080
1050
|
/** 消息级串行锁:确保同一 session 的消息按顺序处理完成 */
|
|
1081
1051
|
sessionLocks = /* @__PURE__ */ new Map();
|
|
1082
|
-
/** 等待下一次 result 的维护任务 resolver(如 auto compact),不参与用户消息完成语义 */
|
|
1083
|
-
resultWaiters = /* @__PURE__ */ new Map();
|
|
1084
|
-
/**
|
|
1085
|
-
* 最近一次 CC 响应的 input_tokens:sessionId → token 数
|
|
1086
|
-
*
|
|
1087
|
-
* 由 parser 的 usage 事件更新。用于在下一次 executePrompt 前判断
|
|
1088
|
-
* 是否需要自动触发 /compact 压缩对话历史。
|
|
1089
|
-
*/
|
|
1090
|
-
lastInputTokens = /* @__PURE__ */ new Map();
|
|
1091
|
-
/** auto compact 失败熔断:sessionId → 下次允许尝试的时间戳 */
|
|
1092
|
-
compactRetryAfter = /* @__PURE__ */ new Map();
|
|
1093
1052
|
/** 标记 session 为用户主动中断 */
|
|
1094
1053
|
markAborted(sessionId) {
|
|
1095
1054
|
this.abortedSessions.add(sessionId);
|
|
@@ -1135,9 +1094,9 @@ var SessionProcessManager = class {
|
|
|
1135
1094
|
const t0 = Date.now();
|
|
1136
1095
|
const prevLock = this.sessionLocks.get(sessionId);
|
|
1137
1096
|
if (prevLock) {
|
|
1138
|
-
log$
|
|
1097
|
+
log$34.info("等待上一条消息处理完成", { sessionId });
|
|
1139
1098
|
await prevLock;
|
|
1140
|
-
log$
|
|
1099
|
+
log$34.info("[perf] 串行锁等待完成", {
|
|
1141
1100
|
sessionId,
|
|
1142
1101
|
waitMs: Date.now() - t0
|
|
1143
1102
|
});
|
|
@@ -1151,36 +1110,24 @@ var SessionProcessManager = class {
|
|
|
1151
1110
|
try {
|
|
1152
1111
|
const existing = this.processes.get(sessionId);
|
|
1153
1112
|
if (existing && existing.status === "running" && existing.useStreamInput) {
|
|
1154
|
-
log$
|
|
1113
|
+
log$34.info("向常驻进程发送消息", {
|
|
1155
1114
|
sessionId,
|
|
1156
1115
|
promptLength: config.prompt.length,
|
|
1157
1116
|
pid: existing.childProcess.pid,
|
|
1158
1117
|
prepareMs: Date.now() - t0
|
|
1159
1118
|
});
|
|
1160
|
-
await this.autoCompactIfNeeded(sessionId);
|
|
1161
1119
|
const completionPromise = this.createMessageCompletionPromise(sessionId);
|
|
1162
|
-
const current = this.processes.get(sessionId);
|
|
1163
|
-
if (!current || current.status !== "running" || !current.useStreamInput) {
|
|
1164
|
-
log$33.warn("[auto-compact] 压缩后热进程不可用,重新冷启动发送用户消息", { sessionId });
|
|
1165
|
-
await this.startPersistentProcess(config);
|
|
1166
|
-
await completionPromise;
|
|
1167
|
-
log$33.info("[perf] executePrompt 完成(compact 后冷启动)", {
|
|
1168
|
-
sessionId,
|
|
1169
|
-
totalMs: Date.now() - t0
|
|
1170
|
-
});
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
1120
|
this.sendMessage(sessionId, config.prompt);
|
|
1174
1121
|
this.resetIdleTimer(sessionId);
|
|
1175
1122
|
await completionPromise;
|
|
1176
|
-
log$
|
|
1123
|
+
log$34.info("[perf] executePrompt 完成(热进程)", {
|
|
1177
1124
|
sessionId,
|
|
1178
1125
|
totalMs: Date.now() - t0
|
|
1179
1126
|
});
|
|
1180
1127
|
return;
|
|
1181
1128
|
}
|
|
1182
1129
|
if (existing && (existing.status === "stopped" || existing.status === "crashed")) {
|
|
1183
|
-
log$
|
|
1130
|
+
log$34.info("清理已终止的旧进程,准备重启", {
|
|
1184
1131
|
sessionId,
|
|
1185
1132
|
oldStatus: existing.status
|
|
1186
1133
|
});
|
|
@@ -1188,25 +1135,25 @@ var SessionProcessManager = class {
|
|
|
1188
1135
|
this.sessionLocks.set(sessionId, sessionLock);
|
|
1189
1136
|
}
|
|
1190
1137
|
if (existing && existing.status === "running" && !existing.useStreamInput) {
|
|
1191
|
-
log$
|
|
1138
|
+
log$34.warn("停止旧的单次进程后重新启动为常驻模式", { sessionId });
|
|
1192
1139
|
await this.stopProcess(sessionId);
|
|
1193
1140
|
this.sessionLocks.set(sessionId, sessionLock);
|
|
1194
1141
|
}
|
|
1195
1142
|
const completionPromise = this.createMessageCompletionPromise(sessionId);
|
|
1196
1143
|
const tStart = Date.now();
|
|
1197
1144
|
await this.startPersistentProcess(config);
|
|
1198
|
-
log$
|
|
1145
|
+
log$34.info("[perf] 冷启动进程完成", {
|
|
1199
1146
|
sessionId,
|
|
1200
1147
|
coldStartMs: Date.now() - tStart
|
|
1201
1148
|
});
|
|
1202
1149
|
await completionPromise;
|
|
1203
|
-
log$
|
|
1150
|
+
log$34.info("[perf] executePrompt 完成(冷启动)", {
|
|
1204
1151
|
sessionId,
|
|
1205
1152
|
totalMs: Date.now() - t0
|
|
1206
1153
|
});
|
|
1207
1154
|
} finally {
|
|
1208
1155
|
if (config.transcriptMode === "ephemeral") await this.stopProcess(sessionId).catch((err) => {
|
|
1209
|
-
log$
|
|
1156
|
+
log$34.warn("ephemeral CC 进程清理失败", {
|
|
1210
1157
|
sessionId,
|
|
1211
1158
|
error: err instanceof Error ? err.message : String(err)
|
|
1212
1159
|
});
|
|
@@ -1245,13 +1192,13 @@ var SessionProcessManager = class {
|
|
|
1245
1192
|
try {
|
|
1246
1193
|
const mcpConfigPath = await mergeAndWriteMcpConfig(config.userContext && !config.userContext.isTenantIdentity ? config.userContext : null, sessionId, cwd);
|
|
1247
1194
|
args.push("--mcp-config", mcpConfigPath);
|
|
1248
|
-
log$
|
|
1195
|
+
log$34.info("MCP 配置已合并并注入到 CC 进程", {
|
|
1249
1196
|
sessionId,
|
|
1250
1197
|
userId: config.userContext?.userId,
|
|
1251
1198
|
mcpConfigPath
|
|
1252
1199
|
});
|
|
1253
1200
|
} catch (err) {
|
|
1254
|
-
log$
|
|
1201
|
+
log$34.warn("MCP 配置合并失败,CC 进程将使用全局默认配置", {
|
|
1255
1202
|
sessionId,
|
|
1256
1203
|
error: err instanceof Error ? err.message : String(err)
|
|
1257
1204
|
});
|
|
@@ -1278,12 +1225,12 @@ var SessionProcessManager = class {
|
|
|
1278
1225
|
if (larkCred?.user_access_token && typeof larkCred.user_access_token === "string") processEnv.LARK_USER_ACCESS_TOKEN = larkCred.user_access_token;
|
|
1279
1226
|
} else {
|
|
1280
1227
|
delete processEnv.LARK_USER_ACCESS_TOKEN;
|
|
1281
|
-
log$
|
|
1228
|
+
log$34.info("LARKPAL_DISABLE_USER_AUTH 已启用,跳过飞书 user_access_token 注入", {
|
|
1282
1229
|
sessionId,
|
|
1283
1230
|
userId: userContext.userId
|
|
1284
1231
|
});
|
|
1285
1232
|
}
|
|
1286
|
-
log$
|
|
1233
|
+
log$34.info("用户凭证已注入到 CC 进程环境", {
|
|
1287
1234
|
sessionId,
|
|
1288
1235
|
userId: userContext.userId,
|
|
1289
1236
|
credentialCount: Object.keys(credentialEnvVars).length,
|
|
@@ -1291,14 +1238,14 @@ var SessionProcessManager = class {
|
|
|
1291
1238
|
disableUserAuth
|
|
1292
1239
|
});
|
|
1293
1240
|
} catch (err) {
|
|
1294
|
-
log$
|
|
1241
|
+
log$34.warn("用户凭证加载失败,CC 进程将使用默认环境", {
|
|
1295
1242
|
sessionId,
|
|
1296
1243
|
userId: userContext.userId,
|
|
1297
1244
|
error: err instanceof Error ? err.message : String(err)
|
|
1298
1245
|
});
|
|
1299
1246
|
}
|
|
1300
1247
|
}
|
|
1301
|
-
log$
|
|
1248
|
+
log$34.info("启动常驻 CC 进程", {
|
|
1302
1249
|
sessionId,
|
|
1303
1250
|
claudeSessionId,
|
|
1304
1251
|
isResuming,
|
|
@@ -1338,7 +1285,7 @@ var SessionProcessManager = class {
|
|
|
1338
1285
|
if (pending) {
|
|
1339
1286
|
clearTimeout(pending.timer);
|
|
1340
1287
|
this.pendingControlRequests.delete(msg.response.request_id);
|
|
1341
|
-
log$
|
|
1288
|
+
log$34.info("收到 control_response", {
|
|
1342
1289
|
sessionId,
|
|
1343
1290
|
requestId: msg.response.request_id,
|
|
1344
1291
|
subtype: msg.response.subtype,
|
|
@@ -1355,7 +1302,7 @@ var SessionProcessManager = class {
|
|
|
1355
1302
|
if (child.stderr) createInterface({ input: child.stderr }).on("line", (line) => {
|
|
1356
1303
|
if (line.includes("no stdin data received")) return;
|
|
1357
1304
|
if (line.toLowerCase().includes("already in use")) stderrSessionInUse = true;
|
|
1358
|
-
log$
|
|
1305
|
+
log$34.warn("CC 进程 stderr", {
|
|
1359
1306
|
sessionId,
|
|
1360
1307
|
line: line.slice(0, 500)
|
|
1361
1308
|
});
|
|
@@ -1364,7 +1311,7 @@ var SessionProcessManager = class {
|
|
|
1364
1311
|
const exitCode = code ?? -1;
|
|
1365
1312
|
const exitSignal = signal ?? "none";
|
|
1366
1313
|
if (exitCode === 1 && stderrSessionInUse && !isResuming) {
|
|
1367
|
-
log$
|
|
1314
|
+
log$34.info("Session ID 已被占用,准备使用 --resume 模式重试", {
|
|
1368
1315
|
sessionId,
|
|
1369
1316
|
claudeSessionId
|
|
1370
1317
|
});
|
|
@@ -1377,21 +1324,21 @@ var SessionProcessManager = class {
|
|
|
1377
1324
|
});
|
|
1378
1325
|
return;
|
|
1379
1326
|
} catch (retryErr) {
|
|
1380
|
-
log$
|
|
1327
|
+
log$34.error("Session ID 重试失败", {
|
|
1381
1328
|
sessionId,
|
|
1382
1329
|
error: retryErr instanceof Error ? retryErr.message : String(retryErr)
|
|
1383
1330
|
});
|
|
1384
1331
|
}
|
|
1385
1332
|
}
|
|
1386
1333
|
if (exitCode === 0) {
|
|
1387
|
-
log$
|
|
1334
|
+
log$34.info("CC 常驻进程正常退出", {
|
|
1388
1335
|
sessionId,
|
|
1389
1336
|
exitCode,
|
|
1390
1337
|
signal: exitSignal
|
|
1391
1338
|
});
|
|
1392
1339
|
ccProcess.status = "stopped";
|
|
1393
1340
|
} else {
|
|
1394
|
-
log$
|
|
1341
|
+
log$34.error("CC 常驻进程异常退出", {
|
|
1395
1342
|
sessionId,
|
|
1396
1343
|
exitCode,
|
|
1397
1344
|
signal: exitSignal
|
|
@@ -1403,7 +1350,7 @@ var SessionProcessManager = class {
|
|
|
1403
1350
|
this.cleanup(sessionId);
|
|
1404
1351
|
});
|
|
1405
1352
|
child.on("error", (err) => {
|
|
1406
|
-
log$
|
|
1353
|
+
log$34.error("CC 常驻进程启动失败", {
|
|
1407
1354
|
sessionId,
|
|
1408
1355
|
error: err.message
|
|
1409
1356
|
});
|
|
@@ -1413,7 +1360,7 @@ var SessionProcessManager = class {
|
|
|
1413
1360
|
this.cleanup(sessionId);
|
|
1414
1361
|
});
|
|
1415
1362
|
ccProcess.status = "running";
|
|
1416
|
-
log$
|
|
1363
|
+
log$34.info("CC 常驻进程已启动", {
|
|
1417
1364
|
sessionId,
|
|
1418
1365
|
pid: child.pid,
|
|
1419
1366
|
isResuming
|
|
@@ -1441,7 +1388,7 @@ var SessionProcessManager = class {
|
|
|
1441
1388
|
parser.on("textDelta", (text) => {
|
|
1442
1389
|
if (!firstTokenLogged) {
|
|
1443
1390
|
firstTokenLogged = true;
|
|
1444
|
-
log$
|
|
1391
|
+
log$34.info("[perf] 首 token (text)", {
|
|
1445
1392
|
sessionId,
|
|
1446
1393
|
ttftMs: Date.now() - messageSentAt
|
|
1447
1394
|
});
|
|
@@ -1451,7 +1398,7 @@ var SessionProcessManager = class {
|
|
|
1451
1398
|
parser.on("thinkingDelta", (text) => {
|
|
1452
1399
|
if (!firstTokenLogged) {
|
|
1453
1400
|
firstTokenLogged = true;
|
|
1454
|
-
log$
|
|
1401
|
+
log$34.info("[perf] 首 token (thinking)", {
|
|
1455
1402
|
sessionId,
|
|
1456
1403
|
ttftMs: Date.now() - messageSentAt
|
|
1457
1404
|
});
|
|
@@ -1468,7 +1415,7 @@ var SessionProcessManager = class {
|
|
|
1468
1415
|
getCallbacks()?.onToolProgress?.(toolName, elapsedSeconds);
|
|
1469
1416
|
});
|
|
1470
1417
|
parser.on("turnEnd", (stopReason) => {
|
|
1471
|
-
log$
|
|
1418
|
+
log$34.info("[perf] turnEnd", {
|
|
1472
1419
|
sessionId,
|
|
1473
1420
|
stopReason,
|
|
1474
1421
|
sinceMessageMs: Date.now() - messageSentAt
|
|
@@ -1476,27 +1423,15 @@ var SessionProcessManager = class {
|
|
|
1476
1423
|
getCallbacks()?.onTurnEnd?.(stopReason);
|
|
1477
1424
|
});
|
|
1478
1425
|
parser.on("result", (result) => {
|
|
1479
|
-
log$
|
|
1426
|
+
log$34.info("[perf] result 事件", {
|
|
1480
1427
|
sessionId,
|
|
1481
1428
|
sinceMessageMs: Date.now() - messageSentAt
|
|
1482
1429
|
});
|
|
1483
|
-
this.resolveResultWaiters(sessionId);
|
|
1484
1430
|
getCallbacks()?.onResult?.(result);
|
|
1485
1431
|
this.resolveMessageCompletion(sessionId);
|
|
1486
1432
|
});
|
|
1487
|
-
parser.on("usage", (usage) => {
|
|
1488
|
-
if (usage.input_tokens <= 0) return;
|
|
1489
|
-
this.lastInputTokens.set(sessionId, usage.input_tokens);
|
|
1490
|
-
log$33.info("[auto-compact] 更新 input_tokens", {
|
|
1491
|
-
sessionId,
|
|
1492
|
-
inputTokens: usage.input_tokens,
|
|
1493
|
-
outputTokens: usage.output_tokens,
|
|
1494
|
-
threshold: AUTO_COMPACT_THRESHOLD,
|
|
1495
|
-
needsCompact: AUTO_COMPACT_THRESHOLD > 0 && usage.input_tokens > AUTO_COMPACT_THRESHOLD
|
|
1496
|
-
});
|
|
1497
|
-
});
|
|
1498
1433
|
parser.on("parseError", (error, rawLine) => {
|
|
1499
|
-
log$
|
|
1434
|
+
log$34.warn("流解析错误", {
|
|
1500
1435
|
sessionId,
|
|
1501
1436
|
error: error.message,
|
|
1502
1437
|
rawLine: rawLine.slice(0, 200)
|
|
@@ -1512,7 +1447,7 @@ var SessionProcessManager = class {
|
|
|
1512
1447
|
sendMessage(sessionId, message) {
|
|
1513
1448
|
const proc = this.processes.get(sessionId);
|
|
1514
1449
|
if (!proc || !proc.childProcess.stdin) {
|
|
1515
|
-
log$
|
|
1450
|
+
log$34.error("无法发送消息:进程不存在或 stdin 不可用", { sessionId });
|
|
1516
1451
|
return;
|
|
1517
1452
|
}
|
|
1518
1453
|
proc._resetPerfTimer?.();
|
|
@@ -1528,7 +1463,7 @@ var SessionProcessManager = class {
|
|
|
1528
1463
|
uuid: userMessageId
|
|
1529
1464
|
});
|
|
1530
1465
|
const messageLength = typeof message === "string" ? message.length : message.length;
|
|
1531
|
-
log$
|
|
1466
|
+
log$34.info("通过 stdin 发送用户消息", {
|
|
1532
1467
|
sessionId,
|
|
1533
1468
|
messageLength,
|
|
1534
1469
|
isMultimodal: Array.isArray(message),
|
|
@@ -1537,7 +1472,7 @@ var SessionProcessManager = class {
|
|
|
1537
1472
|
});
|
|
1538
1473
|
proc.childProcess.stdin.write(payload + "\n", (err) => {
|
|
1539
1474
|
if (err) {
|
|
1540
|
-
log$
|
|
1475
|
+
log$34.error("stdin 写入失败", {
|
|
1541
1476
|
sessionId,
|
|
1542
1477
|
error: err.message
|
|
1543
1478
|
});
|
|
@@ -1567,7 +1502,7 @@ var SessionProcessManager = class {
|
|
|
1567
1502
|
request_id: requestId,
|
|
1568
1503
|
request
|
|
1569
1504
|
});
|
|
1570
|
-
log$
|
|
1505
|
+
log$34.info("发送控制请求", {
|
|
1571
1506
|
sessionId,
|
|
1572
1507
|
subtype,
|
|
1573
1508
|
requestId,
|
|
@@ -1594,144 +1529,6 @@ var SessionProcessManager = class {
|
|
|
1594
1529
|
});
|
|
1595
1530
|
}
|
|
1596
1531
|
/**
|
|
1597
|
-
* 检查是否需要自动压缩,如需要则执行 /compact 命令
|
|
1598
|
-
*
|
|
1599
|
-
* 判断逻辑:
|
|
1600
|
-
* 1. AUTO_COMPACT_THRESHOLD > 0(未禁用)
|
|
1601
|
-
* 2. lastInputTokens[sessionId] > threshold
|
|
1602
|
-
*
|
|
1603
|
-
* 执行方式:通过 stdin 发送 "/compact" 作为用户消息,等待 CC 返回 result。
|
|
1604
|
-
* compact 期间:
|
|
1605
|
-
* - 通过 callbacks.onToolUseStart 通知 UI 显示"正在压缩对话历史"
|
|
1606
|
-
* - compact 产生的文本/result 被静默回调吞掉,不泄露到用户卡片
|
|
1607
|
-
* - compact 完成后通过 callbacks.onToolResult 通知 UI 压缩完成
|
|
1608
|
-
*/
|
|
1609
|
-
async autoCompactIfNeeded(sessionId) {
|
|
1610
|
-
if (AUTO_COMPACT_THRESHOLD <= 0) return;
|
|
1611
|
-
const lastTokens = this.lastInputTokens.get(sessionId);
|
|
1612
|
-
if (!lastTokens || lastTokens <= AUTO_COMPACT_THRESHOLD) return;
|
|
1613
|
-
const retryAfter = this.compactRetryAfter.get(sessionId);
|
|
1614
|
-
if (retryAfter && Date.now() < retryAfter) {
|
|
1615
|
-
log$33.info("[auto-compact] 熔断窗口内跳过压缩", {
|
|
1616
|
-
sessionId,
|
|
1617
|
-
retryAfter
|
|
1618
|
-
});
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
log$33.info("[auto-compact] 触发自动压缩", {
|
|
1622
|
-
sessionId,
|
|
1623
|
-
lastInputTokens: lastTokens,
|
|
1624
|
-
threshold: AUTO_COMPACT_THRESHOLD
|
|
1625
|
-
});
|
|
1626
|
-
const tCompact = Date.now();
|
|
1627
|
-
const realCallbacks = this.activeCallbacks.get(sessionId);
|
|
1628
|
-
const compactToolUseId = `compact-${Date.now()}`;
|
|
1629
|
-
realCallbacks?.onToolUseStart?.("_AutoCompact", {
|
|
1630
|
-
inputTokens: lastTokens,
|
|
1631
|
-
threshold: AUTO_COMPACT_THRESHOLD
|
|
1632
|
-
});
|
|
1633
|
-
this.activeCallbacks.set(sessionId, {
|
|
1634
|
-
onTextDelta: () => {},
|
|
1635
|
-
onThinkingDelta: () => {},
|
|
1636
|
-
onToolUseStart: () => {},
|
|
1637
|
-
onToolResult: () => {},
|
|
1638
|
-
onToolProgress: () => {},
|
|
1639
|
-
onTurnEnd: () => {},
|
|
1640
|
-
onResult: () => {},
|
|
1641
|
-
onError: (err) => {
|
|
1642
|
-
log$33.warn("[auto-compact] compact 期间收到错误", {
|
|
1643
|
-
sessionId,
|
|
1644
|
-
error: err.message
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
});
|
|
1648
|
-
try {
|
|
1649
|
-
await this.sendCompactCommand(sessionId);
|
|
1650
|
-
this.lastInputTokens.delete(sessionId);
|
|
1651
|
-
this.compactRetryAfter.delete(sessionId);
|
|
1652
|
-
log$33.info("[auto-compact] 压缩完成", {
|
|
1653
|
-
sessionId,
|
|
1654
|
-
durationMs: Date.now() - tCompact
|
|
1655
|
-
});
|
|
1656
|
-
} catch (err) {
|
|
1657
|
-
log$33.warn("[auto-compact] 压缩失败,继续发送原始消息", {
|
|
1658
|
-
sessionId,
|
|
1659
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1660
|
-
durationMs: Date.now() - tCompact
|
|
1661
|
-
});
|
|
1662
|
-
this.compactRetryAfter.set(sessionId, Date.now() + 1800 * 1e3);
|
|
1663
|
-
} finally {
|
|
1664
|
-
if (realCallbacks) this.activeCallbacks.set(sessionId, realCallbacks);
|
|
1665
|
-
realCallbacks?.onToolResult?.(compactToolUseId);
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* 向 CC 进程发送 /compact 命令并等待完成
|
|
1670
|
-
*
|
|
1671
|
-
* /compact 是 CC 的内置 slash command,通过 stdin 作为用户消息发送。
|
|
1672
|
-
* CC 会压缩对话历史并返回 result 事件表示完成。
|
|
1673
|
-
*
|
|
1674
|
-
* 超时 120 秒(compact 可能需要处理大量历史)。
|
|
1675
|
-
*/
|
|
1676
|
-
sendCompactCommand(sessionId) {
|
|
1677
|
-
const proc = this.processes.get(sessionId);
|
|
1678
|
-
if (!proc || proc.status !== "running") return Promise.reject(/* @__PURE__ */ new Error(`会话 ${sessionId} 无运行中的进程,无法执行 compact`));
|
|
1679
|
-
if (!proc.childProcess.stdin || proc.childProcess.stdin.destroyed) return Promise.reject(/* @__PURE__ */ new Error(`会话 ${sessionId} 的 stdin 不可用,无法执行 compact`));
|
|
1680
|
-
const COMPACT_TIMEOUT_MS = 12e4;
|
|
1681
|
-
return new Promise((resolve, reject) => {
|
|
1682
|
-
const onResult = () => {
|
|
1683
|
-
clearTimeout(timer);
|
|
1684
|
-
resolve();
|
|
1685
|
-
};
|
|
1686
|
-
const timer = setTimeout(() => {
|
|
1687
|
-
this.removeResultWaiter(sessionId, onResult);
|
|
1688
|
-
this.stopProcess(sessionId);
|
|
1689
|
-
reject(/* @__PURE__ */ new Error(`/compact 超时 (${COMPACT_TIMEOUT_MS}ms)`));
|
|
1690
|
-
}, COMPACT_TIMEOUT_MS);
|
|
1691
|
-
this.addResultWaiter(sessionId, onResult);
|
|
1692
|
-
const compactPayload = JSON.stringify({
|
|
1693
|
-
type: "user",
|
|
1694
|
-
message: {
|
|
1695
|
-
role: "user",
|
|
1696
|
-
content: "/compact"
|
|
1697
|
-
},
|
|
1698
|
-
parent_tool_use_id: null,
|
|
1699
|
-
uuid: v4()
|
|
1700
|
-
});
|
|
1701
|
-
log$33.info("[auto-compact] 发送 /compact 命令", {
|
|
1702
|
-
sessionId,
|
|
1703
|
-
pid: proc.childProcess.pid
|
|
1704
|
-
});
|
|
1705
|
-
proc.childProcess.stdin.write(compactPayload + "\n", (err) => {
|
|
1706
|
-
if (err) {
|
|
1707
|
-
clearTimeout(timer);
|
|
1708
|
-
this.removeResultWaiter(sessionId, onResult);
|
|
1709
|
-
reject(/* @__PURE__ */ new Error(`/compact stdin 写入失败: ${err.message}`));
|
|
1710
|
-
}
|
|
1711
|
-
});
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1714
|
-
addResultWaiter(sessionId, waiter) {
|
|
1715
|
-
let waiters = this.resultWaiters.get(sessionId);
|
|
1716
|
-
if (!waiters) {
|
|
1717
|
-
waiters = /* @__PURE__ */ new Set();
|
|
1718
|
-
this.resultWaiters.set(sessionId, waiters);
|
|
1719
|
-
}
|
|
1720
|
-
waiters.add(waiter);
|
|
1721
|
-
}
|
|
1722
|
-
removeResultWaiter(sessionId, waiter) {
|
|
1723
|
-
const waiters = this.resultWaiters.get(sessionId);
|
|
1724
|
-
if (!waiters) return;
|
|
1725
|
-
waiters.delete(waiter);
|
|
1726
|
-
if (waiters.size === 0) this.resultWaiters.delete(sessionId);
|
|
1727
|
-
}
|
|
1728
|
-
resolveResultWaiters(sessionId) {
|
|
1729
|
-
const waiters = this.resultWaiters.get(sessionId);
|
|
1730
|
-
if (!waiters) return;
|
|
1731
|
-
this.resultWaiters.delete(sessionId);
|
|
1732
|
-
for (const waiter of waiters) waiter();
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
1532
|
* 获取指定会话的最近一条用户消息 ID
|
|
1736
1533
|
*
|
|
1737
1534
|
* 用于构建回退按钮的 action value。
|
|
@@ -1761,16 +1558,16 @@ var SessionProcessManager = class {
|
|
|
1761
1558
|
async ensureProcessForRewind(sessionId) {
|
|
1762
1559
|
const existing = this.processes.get(sessionId);
|
|
1763
1560
|
if (existing && existing.status === "running") {
|
|
1764
|
-
log$
|
|
1561
|
+
log$34.info("ensureProcessForRewind: 进程已在运行中,直接使用", { sessionId });
|
|
1765
1562
|
return existing;
|
|
1766
1563
|
}
|
|
1767
1564
|
const cwd = this.sessionCwdCache.get(sessionId);
|
|
1768
1565
|
if (!cwd) {
|
|
1769
|
-
log$
|
|
1566
|
+
log$34.warn("ensureProcessForRewind: 无法获取会话 cwd", { sessionId });
|
|
1770
1567
|
return;
|
|
1771
1568
|
}
|
|
1772
1569
|
if (existing) this.cleanup(sessionId);
|
|
1773
|
-
log$
|
|
1570
|
+
log$34.info("ensureProcessForRewind: 启动临时进程用于 rewind", {
|
|
1774
1571
|
sessionId,
|
|
1775
1572
|
cwd
|
|
1776
1573
|
});
|
|
@@ -1781,7 +1578,7 @@ var SessionProcessManager = class {
|
|
|
1781
1578
|
}, { skipInitialMessage: true });
|
|
1782
1579
|
const proc = this.processes.get(sessionId);
|
|
1783
1580
|
if (!proc || proc.status !== "running") {
|
|
1784
|
-
log$
|
|
1581
|
+
log$34.warn("ensureProcessForRewind: 进程启动失败", {
|
|
1785
1582
|
sessionId,
|
|
1786
1583
|
status: proc?.status
|
|
1787
1584
|
});
|
|
@@ -1803,11 +1600,11 @@ var SessionProcessManager = class {
|
|
|
1803
1600
|
async stopProcess(sessionId) {
|
|
1804
1601
|
const proc = this.processes.get(sessionId);
|
|
1805
1602
|
if (!proc) {
|
|
1806
|
-
log$
|
|
1603
|
+
log$34.warn("尝试停止不存在的进程", { sessionId });
|
|
1807
1604
|
return;
|
|
1808
1605
|
}
|
|
1809
1606
|
if (proc.status === "stopped" || proc.status === "crashed") {
|
|
1810
|
-
log$
|
|
1607
|
+
log$34.info("进程已经停止,直接清理", {
|
|
1811
1608
|
sessionId,
|
|
1812
1609
|
status: proc.status
|
|
1813
1610
|
});
|
|
@@ -1815,7 +1612,7 @@ var SessionProcessManager = class {
|
|
|
1815
1612
|
return;
|
|
1816
1613
|
}
|
|
1817
1614
|
proc.status = "stopping";
|
|
1818
|
-
log$
|
|
1615
|
+
log$34.info("正在停止 CC 常驻进程", {
|
|
1819
1616
|
sessionId,
|
|
1820
1617
|
pid: proc.childProcess.pid
|
|
1821
1618
|
});
|
|
@@ -1831,7 +1628,7 @@ var SessionProcessManager = class {
|
|
|
1831
1628
|
const child = proc.childProcess;
|
|
1832
1629
|
const forceKillTimer = setTimeout(() => {
|
|
1833
1630
|
if (!child.killed) {
|
|
1834
|
-
log$
|
|
1631
|
+
log$34.warn("CC 常驻进程未在规定时间内退出,强制终止", { sessionId });
|
|
1835
1632
|
child.kill("SIGKILL");
|
|
1836
1633
|
}
|
|
1837
1634
|
}, GRACEFUL_SHUTDOWN_MS);
|
|
@@ -1851,9 +1648,9 @@ var SessionProcessManager = class {
|
|
|
1851
1648
|
async stopAll() {
|
|
1852
1649
|
const sessionIds = Array.from(this.processes.keys());
|
|
1853
1650
|
if (sessionIds.length === 0) return;
|
|
1854
|
-
log$
|
|
1651
|
+
log$34.info("正在停止所有 CC 常驻进程", { count: sessionIds.length });
|
|
1855
1652
|
await Promise.all(sessionIds.map((id) => this.stopProcess(id)));
|
|
1856
|
-
log$
|
|
1653
|
+
log$34.info("所有 CC 常驻进程已停止");
|
|
1857
1654
|
}
|
|
1858
1655
|
/**
|
|
1859
1656
|
* 重置空闲计时器
|
|
@@ -1863,7 +1660,7 @@ var SessionProcessManager = class {
|
|
|
1863
1660
|
if (existing) clearTimeout(existing);
|
|
1864
1661
|
if (IDLE_TIMEOUT_MS <= 0) return;
|
|
1865
1662
|
const timer = setTimeout(() => {
|
|
1866
|
-
log$
|
|
1663
|
+
log$34.info("CC 常驻进程空闲超时,自动停止", {
|
|
1867
1664
|
sessionId,
|
|
1868
1665
|
timeoutMs: IDLE_TIMEOUT_MS
|
|
1869
1666
|
});
|
|
@@ -1880,9 +1677,6 @@ var SessionProcessManager = class {
|
|
|
1880
1677
|
this.processes.delete(sessionId);
|
|
1881
1678
|
this.activeCallbacks.delete(sessionId);
|
|
1882
1679
|
this.lastUserMessageIds.delete(sessionId);
|
|
1883
|
-
this.lastInputTokens.delete(sessionId);
|
|
1884
|
-
this.resultWaiters.delete(sessionId);
|
|
1885
|
-
this.compactRetryAfter.delete(sessionId);
|
|
1886
1680
|
for (const [reqId, pending] of this.pendingControlRequests) if (pending.sessionId === sessionId) {
|
|
1887
1681
|
clearTimeout(pending.timer);
|
|
1888
1682
|
this.pendingControlRequests.delete(reqId);
|
|
@@ -1894,7 +1688,7 @@ var SessionProcessManager = class {
|
|
|
1894
1688
|
this.idleTimers.delete(sessionId);
|
|
1895
1689
|
}
|
|
1896
1690
|
cleanupMcpConfig(sessionId);
|
|
1897
|
-
log$
|
|
1691
|
+
log$34.debug("已清理常驻进程状态", { sessionId });
|
|
1898
1692
|
}
|
|
1899
1693
|
/**
|
|
1900
1694
|
* 将 cwd 编码为 CC 的 projects 子目录名
|
|
@@ -1923,7 +1717,7 @@ var SessionProcessManager = class {
|
|
|
1923
1717
|
const cwdEncoded = this.encodeCwdForCC(cwd);
|
|
1924
1718
|
const sessionFile = join(homedir(), ".claude", "projects", cwdEncoded, `${claudeSessionId}.jsonl`);
|
|
1925
1719
|
const exists = existsSync(sessionFile);
|
|
1926
|
-
log$
|
|
1720
|
+
log$34.debug("检查 CC session 文件", {
|
|
1927
1721
|
cwd,
|
|
1928
1722
|
cwdEncoded,
|
|
1929
1723
|
claudeSessionId,
|
|
@@ -1932,7 +1726,7 @@ var SessionProcessManager = class {
|
|
|
1932
1726
|
});
|
|
1933
1727
|
return exists;
|
|
1934
1728
|
} catch (err) {
|
|
1935
|
-
log$
|
|
1729
|
+
log$34.warn("检查 CC session 文件失败,默认为首次创建", { error: err instanceof Error ? err.message : String(err) });
|
|
1936
1730
|
return false;
|
|
1937
1731
|
}
|
|
1938
1732
|
}
|
|
@@ -1947,7 +1741,7 @@ var SessionProcessManager = class {
|
|
|
1947
1741
|
*
|
|
1948
1742
|
* 这是 LarkPal 的默认 AI 引擎适配器。
|
|
1949
1743
|
*/
|
|
1950
|
-
const log$
|
|
1744
|
+
const log$33 = larkLogger("runtime/claude-code-adapter");
|
|
1951
1745
|
var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
1952
1746
|
name = "claude-code";
|
|
1953
1747
|
processManager;
|
|
@@ -1963,7 +1757,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1963
1757
|
constructor() {
|
|
1964
1758
|
this.processManager = new SessionProcessManager();
|
|
1965
1759
|
ClaudeCodeAdapter._instance = this;
|
|
1966
|
-
log$
|
|
1760
|
+
log$33.info("ClaudeCodeAdapter 初始化完成");
|
|
1967
1761
|
}
|
|
1968
1762
|
/**
|
|
1969
1763
|
* 获取底层 SessionProcessManager 实例
|
|
@@ -1976,7 +1770,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1976
1770
|
}
|
|
1977
1771
|
async executePrompt(config, callbacks) {
|
|
1978
1772
|
this.lastRuntimeConfigs.set(config.sessionId, config);
|
|
1979
|
-
log$
|
|
1773
|
+
log$33.info("executePrompt via ClaudeCodeAdapter", {
|
|
1980
1774
|
sessionId: config.sessionId,
|
|
1981
1775
|
cwd: config.cwd,
|
|
1982
1776
|
promptLength: config.prompt.length,
|
|
@@ -1985,11 +1779,11 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1985
1779
|
await this.processManager.executePrompt(config, callbacks);
|
|
1986
1780
|
}
|
|
1987
1781
|
async stopProcess(sessionId) {
|
|
1988
|
-
log$
|
|
1782
|
+
log$33.info("stopProcess via ClaudeCodeAdapter", { sessionId });
|
|
1989
1783
|
await this.processManager.stopProcess(sessionId);
|
|
1990
1784
|
}
|
|
1991
1785
|
async stopAll() {
|
|
1992
|
-
log$
|
|
1786
|
+
log$33.info("stopAll via ClaudeCodeAdapter");
|
|
1993
1787
|
await this.processManager.stopAll();
|
|
1994
1788
|
}
|
|
1995
1789
|
getProcessInfo(sessionId) {
|
|
@@ -2025,7 +1819,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
2025
1819
|
};
|
|
2026
1820
|
//#endregion
|
|
2027
1821
|
//#region src/codex-runtime/app-server-client.ts
|
|
2028
|
-
const log$
|
|
1822
|
+
const log$32 = larkLogger("codex-runtime/app-server-client");
|
|
2029
1823
|
const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
2030
1824
|
var CodexAppServerClient = class extends EventEmitter {
|
|
2031
1825
|
sessionId;
|
|
@@ -2056,7 +1850,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2056
1850
|
"pipe"
|
|
2057
1851
|
]
|
|
2058
1852
|
});
|
|
2059
|
-
log$
|
|
1853
|
+
log$32.info("Codex app-server 进程已启动", {
|
|
2060
1854
|
sessionId: this.sessionId,
|
|
2061
1855
|
cwd: this.cwd,
|
|
2062
1856
|
pid: this.childProcess.pid,
|
|
@@ -2135,14 +1929,14 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2135
1929
|
bindStreams() {
|
|
2136
1930
|
if (this.childProcess.stdout) createInterface({ input: this.childProcess.stdout }).on("line", (line) => this.handleLine(line));
|
|
2137
1931
|
if (this.childProcess.stderr) createInterface({ input: this.childProcess.stderr }).on("line", (line) => {
|
|
2138
|
-
log$
|
|
1932
|
+
log$32.warn("Codex app-server stderr", {
|
|
2139
1933
|
sessionId: this.sessionId,
|
|
2140
1934
|
line: line.slice(0, 500)
|
|
2141
1935
|
});
|
|
2142
1936
|
this.emit("stderr", line);
|
|
2143
1937
|
});
|
|
2144
1938
|
this.childProcess.on("error", (error) => {
|
|
2145
|
-
log$
|
|
1939
|
+
log$32.error("Codex app-server 进程错误", {
|
|
2146
1940
|
sessionId: this.sessionId,
|
|
2147
1941
|
error: error.message
|
|
2148
1942
|
});
|
|
@@ -2151,7 +1945,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2151
1945
|
});
|
|
2152
1946
|
this.childProcess.on("close", (code, signal) => {
|
|
2153
1947
|
this.closed = true;
|
|
2154
|
-
log$
|
|
1948
|
+
log$32.info("Codex app-server 进程退出", {
|
|
2155
1949
|
sessionId: this.sessionId,
|
|
2156
1950
|
code,
|
|
2157
1951
|
signal
|
|
@@ -2168,7 +1962,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2168
1962
|
message = JSON.parse(trimmed);
|
|
2169
1963
|
} catch (err) {
|
|
2170
1964
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
2171
|
-
log$
|
|
1965
|
+
log$32.warn("Codex app-server JSONL 解析失败", {
|
|
2172
1966
|
sessionId: this.sessionId,
|
|
2173
1967
|
error: error.message,
|
|
2174
1968
|
line: trimmed.slice(0, 200)
|
|
@@ -2188,7 +1982,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2188
1982
|
}
|
|
2189
1983
|
handleResponse(message) {
|
|
2190
1984
|
if (message.id == null) {
|
|
2191
|
-
log$
|
|
1985
|
+
log$32.warn("Codex app-server 返回无 id 错误响应", {
|
|
2192
1986
|
sessionId: this.sessionId,
|
|
2193
1987
|
message
|
|
2194
1988
|
});
|
|
@@ -2197,7 +1991,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2197
1991
|
const idKey = String(message.id);
|
|
2198
1992
|
const pending = this.pending.get(idKey);
|
|
2199
1993
|
if (!pending) {
|
|
2200
|
-
log$
|
|
1994
|
+
log$32.warn("Codex app-server 返回未知请求响应", {
|
|
2201
1995
|
sessionId: this.sessionId,
|
|
2202
1996
|
id: message.id
|
|
2203
1997
|
});
|
|
@@ -2243,7 +2037,7 @@ function isNotification(message) {
|
|
|
2243
2037
|
}
|
|
2244
2038
|
//#endregion
|
|
2245
2039
|
//#region src/runtime/codex-app-server-adapter.ts
|
|
2246
|
-
const log$
|
|
2040
|
+
const log$31 = larkLogger("runtime/codex-app-server-adapter");
|
|
2247
2041
|
const STATE_PATH = join(homedir(), ".larkpal", "runtime", "codex-app-server-threads.json");
|
|
2248
2042
|
const REQUEST_TIMEOUT_MS = 12e4;
|
|
2249
2043
|
var CodexAppServerAdapter = class {
|
|
@@ -2269,14 +2063,14 @@ var CodexAppServerAdapter = class {
|
|
|
2269
2063
|
extraArgs: params.extraArgs,
|
|
2270
2064
|
requestTimeoutMs: REQUEST_TIMEOUT_MS
|
|
2271
2065
|
}));
|
|
2272
|
-
log$
|
|
2066
|
+
log$31.info("CodexAppServerAdapter 初始化完成", { statePath: this.statePath });
|
|
2273
2067
|
}
|
|
2274
2068
|
async executePrompt(config, callbacks) {
|
|
2275
2069
|
await this.loadState();
|
|
2276
2070
|
const { sessionId } = config;
|
|
2277
2071
|
const prevLock = this.sessionLocks.get(sessionId);
|
|
2278
2072
|
if (prevLock) {
|
|
2279
|
-
log$
|
|
2073
|
+
log$31.info("等待上一条 Codex 消息处理完成", { sessionId });
|
|
2280
2074
|
await prevLock;
|
|
2281
2075
|
}
|
|
2282
2076
|
const completion = this.createCompletionPromise(sessionId);
|
|
@@ -2307,7 +2101,7 @@ var CodexAppServerAdapter = class {
|
|
|
2307
2101
|
if (config.transcriptMode === "ephemeral") {
|
|
2308
2102
|
const record = this.processes.get(sessionId);
|
|
2309
2103
|
if (record) await this.closeRecord(sessionId, record).catch((err) => {
|
|
2310
|
-
log$
|
|
2104
|
+
log$31.warn("ephemeral Codex app-server session 清理失败", {
|
|
2311
2105
|
sessionId,
|
|
2312
2106
|
error: err instanceof Error ? err.message : String(err)
|
|
2313
2107
|
});
|
|
@@ -2322,12 +2116,12 @@ var CodexAppServerAdapter = class {
|
|
|
2322
2116
|
async stopProcess(sessionId) {
|
|
2323
2117
|
const record = this.processes.get(sessionId);
|
|
2324
2118
|
if (!record) {
|
|
2325
|
-
log$
|
|
2119
|
+
log$31.warn("尝试停止不存在的 Codex app-server session", { sessionId });
|
|
2326
2120
|
return;
|
|
2327
2121
|
}
|
|
2328
2122
|
if (this.busySessions.has(sessionId) && record.process.currentTurnId) {
|
|
2329
2123
|
this.abortedSessions.add(sessionId);
|
|
2330
|
-
log$
|
|
2124
|
+
log$31.info("发送 Codex turn/interrupt", {
|
|
2331
2125
|
sessionId,
|
|
2332
2126
|
threadId: record.process.threadId,
|
|
2333
2127
|
turnId: record.process.currentTurnId
|
|
@@ -2414,7 +2208,7 @@ var CodexAppServerAdapter = class {
|
|
|
2414
2208
|
if (!isEphemeral) await this.setThreadId(config.sessionId, resumed.thread.id, config.cwd);
|
|
2415
2209
|
return;
|
|
2416
2210
|
} catch (err) {
|
|
2417
|
-
log$
|
|
2211
|
+
log$31.warn("Codex thread/resume 失败,将新建 thread", {
|
|
2418
2212
|
sessionId: config.sessionId,
|
|
2419
2213
|
threadId: storedThreadId,
|
|
2420
2214
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2480,7 +2274,7 @@ var CodexAppServerAdapter = class {
|
|
|
2480
2274
|
this.handleErrorNotification(sessionId, message.params);
|
|
2481
2275
|
break;
|
|
2482
2276
|
default:
|
|
2483
|
-
log$
|
|
2277
|
+
log$31.debug("忽略 Codex app-server notification", {
|
|
2484
2278
|
sessionId,
|
|
2485
2279
|
method: message.method
|
|
2486
2280
|
});
|
|
@@ -2508,7 +2302,7 @@ var CodexAppServerAdapter = class {
|
|
|
2508
2302
|
handleMcpProgress(sessionId, params) {
|
|
2509
2303
|
const toolName = this.lookupToolName(sessionId, params.itemId) ?? "mcp";
|
|
2510
2304
|
this.activeCallbacks.get(sessionId)?.onToolProgress?.(toolName, 0);
|
|
2511
|
-
log$
|
|
2305
|
+
log$31.debug("Codex MCP tool progress", {
|
|
2512
2306
|
sessionId,
|
|
2513
2307
|
itemId: params.itemId,
|
|
2514
2308
|
message: params.message
|
|
@@ -2538,7 +2332,7 @@ var CodexAppServerAdapter = class {
|
|
|
2538
2332
|
this.resolveCompletion(sessionId);
|
|
2539
2333
|
}
|
|
2540
2334
|
async handleServerRequest(client, request) {
|
|
2541
|
-
log$
|
|
2335
|
+
log$31.warn("Codex app-server server request 默认拒绝", {
|
|
2542
2336
|
method: request.method,
|
|
2543
2337
|
id: request.id
|
|
2544
2338
|
});
|
|
@@ -2704,10 +2498,10 @@ var CodexAppServerAdapter = class {
|
|
|
2704
2498
|
];
|
|
2705
2499
|
}
|
|
2706
2500
|
logUnsupportedConfig(config) {
|
|
2707
|
-
if (config.llmConfig) log$
|
|
2708
|
-
if (config.maxBudgetUsd != null) log$
|
|
2709
|
-
if (config.maxTurns != null) log$
|
|
2710
|
-
if (config.policy?.allowedTools || config.policy?.disallowedTools) log$
|
|
2501
|
+
if (config.llmConfig) log$31.warn("Codex app-server v1 暂不支持 per-request llmConfig,已忽略", { sessionId: config.sessionId });
|
|
2502
|
+
if (config.maxBudgetUsd != null) log$31.warn("Codex app-server v1 暂不支持 maxBudgetUsd,已忽略", { sessionId: config.sessionId });
|
|
2503
|
+
if (config.maxTurns != null) log$31.warn("Codex app-server v1 暂不支持 maxTurns,已忽略", { sessionId: config.sessionId });
|
|
2504
|
+
if (config.policy?.allowedTools || config.policy?.disallowedTools) log$31.warn("Codex app-server v1 暂不强制执行 tool allow/deny policy,已忽略", { sessionId: config.sessionId });
|
|
2711
2505
|
}
|
|
2712
2506
|
createCompletionPromise(sessionId) {
|
|
2713
2507
|
return new Promise((resolve) => {
|
|
@@ -2723,7 +2517,7 @@ var CodexAppServerAdapter = class {
|
|
|
2723
2517
|
async closeRecord(sessionId, record) {
|
|
2724
2518
|
record.process.status = "stopping";
|
|
2725
2519
|
if (record.process.threadId) await record.client.request("thread/unsubscribe", { threadId: record.process.threadId }).catch((err) => {
|
|
2726
|
-
log$
|
|
2520
|
+
log$31.warn("Codex thread/unsubscribe 失败", {
|
|
2727
2521
|
sessionId,
|
|
2728
2522
|
threadId: record.process.threadId,
|
|
2729
2523
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2756,7 +2550,7 @@ var CodexAppServerAdapter = class {
|
|
|
2756
2550
|
const parsed = JSON.parse(raw);
|
|
2757
2551
|
for (const [sessionId, entry] of Object.entries(parsed.sessions ?? {})) if (entry.threadId) this.threadIds.set(sessionId, entry.threadId);
|
|
2758
2552
|
} catch (err) {
|
|
2759
|
-
log$
|
|
2553
|
+
log$31.warn("Codex thread state 加载失败,将从空状态继续", {
|
|
2760
2554
|
path: this.statePath,
|
|
2761
2555
|
error: err instanceof Error ? err.message : String(err)
|
|
2762
2556
|
});
|
|
@@ -2824,18 +2618,18 @@ function getLarkpalMcpServerCommand() {
|
|
|
2824
2618
|
*
|
|
2825
2619
|
* 仅当 LARKPAL_RUNTIME=larkpal-agent 时使用。
|
|
2826
2620
|
*/
|
|
2827
|
-
const log$
|
|
2621
|
+
const log$30 = larkLogger("runtime/larkpal-agent-factory");
|
|
2828
2622
|
const AGENT_MD_PATH = join(homedir(), ".claude", "CLAUDE.md");
|
|
2829
2623
|
async function loadSystemPrompt() {
|
|
2830
2624
|
try {
|
|
2831
2625
|
const content = await readFile(AGENT_MD_PATH, "utf-8");
|
|
2832
|
-
log$
|
|
2626
|
+
log$30.info("systemPrompt 加载成功", {
|
|
2833
2627
|
path: AGENT_MD_PATH,
|
|
2834
2628
|
length: content.length
|
|
2835
2629
|
});
|
|
2836
2630
|
return content;
|
|
2837
2631
|
} catch {
|
|
2838
|
-
log$
|
|
2632
|
+
log$30.warn("systemPrompt 文件不存在,使用默认", { path: AGENT_MD_PATH });
|
|
2839
2633
|
return "你是一个 AI 助手,帮助用户完成飞书知识管理任务。";
|
|
2840
2634
|
}
|
|
2841
2635
|
}
|
|
@@ -2951,7 +2745,7 @@ async function createLarkpalAgentAdapter() {
|
|
|
2951
2745
|
const scenarioManifestDirs = loadScenarioManifestDirs();
|
|
2952
2746
|
const scenarioProviderConfig = loadScenarioProviderConfig(scenarioManifestDirs);
|
|
2953
2747
|
const mcpTimeoutMs = loadAgentMcpTimeoutMs();
|
|
2954
|
-
log$
|
|
2748
|
+
log$30.info("创建 LarkpalAgentAdapter", {
|
|
2955
2749
|
baseURL: llm.baseURL,
|
|
2956
2750
|
model: llm.defaultModel,
|
|
2957
2751
|
maxTurns: defaultMaxTurns,
|
|
@@ -2967,26 +2761,30 @@ async function createLarkpalAgentAdapter() {
|
|
|
2967
2761
|
defaultMaxTurns,
|
|
2968
2762
|
registry,
|
|
2969
2763
|
scenarioManifestDirs,
|
|
2970
|
-
scenarioProviderConfig
|
|
2764
|
+
scenarioProviderConfig,
|
|
2765
|
+
visualRuntime: {
|
|
2766
|
+
maxBatchSize: parseInt(process.env.LARKPAL_AGENT_VISUAL_MAX_BATCH_SIZE || "3", 10),
|
|
2767
|
+
allowedDirs: [process.env.LARK_CLI_WORKDIR || "/tmp/ai-knowledge-lark-cli", "/tmp"].filter(Boolean)
|
|
2768
|
+
}
|
|
2971
2769
|
});
|
|
2972
2770
|
const mcpServerUrl = process.env.LARKPAL_AGENT_MCP_SERVER_URL;
|
|
2973
2771
|
if (mcpServerUrl) try {
|
|
2974
2772
|
const toolCount = await registry.connectMcpServer(mcpServerUrl, { timeoutMs: mcpTimeoutMs });
|
|
2975
2773
|
const tools = registry.getAll();
|
|
2976
2774
|
adapter.registerTools(tools);
|
|
2977
|
-
log$
|
|
2775
|
+
log$30.info("MCP 工具注册完成", {
|
|
2978
2776
|
serverUrl: mcpServerUrl,
|
|
2979
2777
|
timeoutMs: mcpTimeoutMs,
|
|
2980
2778
|
toolCount,
|
|
2981
2779
|
toolNames: tools.map((t) => t.name)
|
|
2982
2780
|
});
|
|
2983
2781
|
} catch (err) {
|
|
2984
|
-
log$
|
|
2782
|
+
log$30.error("MCP Server 连接失败,Agent 将无法使用远程工具", {
|
|
2985
2783
|
serverUrl: mcpServerUrl,
|
|
2986
2784
|
error: err?.message
|
|
2987
2785
|
});
|
|
2988
2786
|
}
|
|
2989
|
-
else log$
|
|
2787
|
+
else log$30.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
|
|
2990
2788
|
return adapter;
|
|
2991
2789
|
}
|
|
2992
2790
|
//#endregion
|
|
@@ -3323,6 +3121,7 @@ function createChatAuthRouter() {
|
|
|
3323
3121
|
//#endregion
|
|
3324
3122
|
//#region src/gateway/agent-context.ts
|
|
3325
3123
|
const SENSITIVE_KEY_PATTERN = /(authorization|cookie|token|api[-_]?key|secret|password|credential)/i;
|
|
3124
|
+
const SAFE_KEY_PATTERN = /^(file[-_]?token|drive[-_]?file[-_]?token|doc[-_]?token|sheet[-_]?token|wiki[-_]?token|node[-_]?token)$/i;
|
|
3326
3125
|
const MAX_SUMMARY_STRING_LENGTH = 200;
|
|
3327
3126
|
const PROTECTED_CONTEXT_HEADERS = new Set([
|
|
3328
3127
|
"x-tenant-key",
|
|
@@ -3445,7 +3244,7 @@ function summarizeForAudit(value) {
|
|
|
3445
3244
|
};
|
|
3446
3245
|
if (typeof value === "object") {
|
|
3447
3246
|
const result = {};
|
|
3448
|
-
for (const [key, entry] of Object.entries(value)) if (SENSITIVE_KEY_PATTERN.test(key)) result[key] = "[redacted]";
|
|
3247
|
+
for (const [key, entry] of Object.entries(value)) if (SENSITIVE_KEY_PATTERN.test(key) && !SAFE_KEY_PATTERN.test(key)) result[key] = "[redacted]";
|
|
3449
3248
|
else result[key] = summarizeForAudit(entry);
|
|
3450
3249
|
return result;
|
|
3451
3250
|
}
|
|
@@ -3551,7 +3350,8 @@ function buildExecutionPolicy(policy, limits) {
|
|
|
3551
3350
|
maxTurns: limits.maxTurns ?? policy?.maxTurns,
|
|
3552
3351
|
maxToolCalls: policy?.maxToolCalls,
|
|
3553
3352
|
maxRuntimeMs: policy?.maxRuntimeMs,
|
|
3554
|
-
maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd
|
|
3353
|
+
maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd,
|
|
3354
|
+
turnPolicy: policy?.turnPolicy
|
|
3555
3355
|
};
|
|
3556
3356
|
}
|
|
3557
3357
|
function buildUserContext$1(identity) {
|
|
@@ -3619,8 +3419,9 @@ function summarizeString(value) {
|
|
|
3619
3419
|
* POST /api/chat/sessions — 创建新会话
|
|
3620
3420
|
* DELETE /api/chat/sessions/:id — 删除会话
|
|
3621
3421
|
*/
|
|
3622
|
-
const log$
|
|
3422
|
+
const log$29 = larkLogger("chat/stream");
|
|
3623
3423
|
const chatRunsBySession = /* @__PURE__ */ new Map();
|
|
3424
|
+
const DEFAULT_STALE_RUN_IDLE_MS = 1800 * 1e3;
|
|
3624
3425
|
/** 统一写 SSE 格式数据到响应流 */
|
|
3625
3426
|
function sendSSE(res, event, data) {
|
|
3626
3427
|
const payload = typeof data === "string" ? data : JSON.stringify(data);
|
|
@@ -3630,7 +3431,7 @@ function isChatSessionRunning(sessionId, processManager) {
|
|
|
3630
3431
|
return getActiveChatRun(sessionId) != null || (processManager.isSessionBusy?.(sessionId) ?? false);
|
|
3631
3432
|
}
|
|
3632
3433
|
function logChatAudit(event) {
|
|
3633
|
-
log$
|
|
3434
|
+
log$29.info("[stream] Agent audit event", { ...event });
|
|
3634
3435
|
}
|
|
3635
3436
|
function prepareSSE(res) {
|
|
3636
3437
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -3644,6 +3445,7 @@ function getActiveChatRun(sessionId) {
|
|
|
3644
3445
|
return run?.status === "running" ? run : void 0;
|
|
3645
3446
|
}
|
|
3646
3447
|
function serializeChatRun(run) {
|
|
3448
|
+
const idleSeconds = Math.max(0, Math.floor((Date.now() - run.lastEventAt) / 1e3));
|
|
3647
3449
|
return {
|
|
3648
3450
|
runId: run.runId,
|
|
3649
3451
|
sessionId: run.sessionId,
|
|
@@ -3655,9 +3457,24 @@ function serializeChatRun(run) {
|
|
|
3655
3457
|
completedAt: run.completedAt,
|
|
3656
3458
|
finalStatus: run.finalStatus,
|
|
3657
3459
|
error: run.error,
|
|
3658
|
-
textLength: run.text.length
|
|
3460
|
+
textLength: run.text.length,
|
|
3461
|
+
finalText: run.status === "running" ? void 0 : run.text || void 0,
|
|
3462
|
+
lastEventAt: run.lastEventAt,
|
|
3463
|
+
lastEventType: run.lastEventType,
|
|
3464
|
+
lastToolName: run.lastToolName,
|
|
3465
|
+
idleSeconds,
|
|
3466
|
+
adapterBusy: run.adapterBusy,
|
|
3467
|
+
staleReason: run.staleReason
|
|
3659
3468
|
};
|
|
3660
3469
|
}
|
|
3470
|
+
function getRuntimeEventDedupeKey(event) {
|
|
3471
|
+
const record = event;
|
|
3472
|
+
const eventId = record.eventId;
|
|
3473
|
+
if (typeof eventId === "string" && eventId.length > 0) return eventId;
|
|
3474
|
+
const runId = typeof record.runId === "string" ? record.runId : void 0;
|
|
3475
|
+
const seq = typeof record.seq === "number" || typeof record.seq === "string" ? String(record.seq) : void 0;
|
|
3476
|
+
return runId && seq ? `${runId}:${seq}` : void 0;
|
|
3477
|
+
}
|
|
3661
3478
|
function isRunVisibleToUser(run, params) {
|
|
3662
3479
|
return run.tenantKey === params.tenantKey && run.openId === params.openId;
|
|
3663
3480
|
}
|
|
@@ -3665,7 +3482,7 @@ function addRunClient(req, res, run) {
|
|
|
3665
3482
|
run.clients.add(res);
|
|
3666
3483
|
req.on("close", () => {
|
|
3667
3484
|
run.clients.delete(res);
|
|
3668
|
-
log$
|
|
3485
|
+
log$29.info("[stream] 客户端断开连接", {
|
|
3669
3486
|
sessionId: run.sessionId,
|
|
3670
3487
|
runId: run.runId
|
|
3671
3488
|
});
|
|
@@ -3674,12 +3491,37 @@ function addRunClient(req, res, run) {
|
|
|
3674
3491
|
function sendRunStatus(res, run) {
|
|
3675
3492
|
sendSSE(res, "run-status", serializeChatRun(run));
|
|
3676
3493
|
}
|
|
3494
|
+
function getStaleRunIdleMs() {
|
|
3495
|
+
const raw = process.env.LARKPAL_CHAT_RUN_STALE_IDLE_MS;
|
|
3496
|
+
if (!raw) return DEFAULT_STALE_RUN_IDLE_MS;
|
|
3497
|
+
const parsed = Number.parseInt(raw, 10);
|
|
3498
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_STALE_RUN_IDLE_MS;
|
|
3499
|
+
}
|
|
3500
|
+
function recordRunEvent(run, eventType, params) {
|
|
3501
|
+
run.lastEventAt = Date.now();
|
|
3502
|
+
run.lastEventType = eventType;
|
|
3503
|
+
if (params?.toolName) run.lastToolName = params.toolName;
|
|
3504
|
+
}
|
|
3505
|
+
function extractRuntimeEventFinalText(event) {
|
|
3506
|
+
const direct = event.finalText;
|
|
3507
|
+
if (typeof direct === "string" && direct.trim()) return direct;
|
|
3508
|
+
const terminal = event.terminal;
|
|
3509
|
+
if (terminal && typeof terminal === "object" && !Array.isArray(terminal)) {
|
|
3510
|
+
const text = terminal.finalText;
|
|
3511
|
+
if (typeof text === "string" && text.trim()) return text;
|
|
3512
|
+
}
|
|
3513
|
+
const data = event.data;
|
|
3514
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
3515
|
+
const text = data.finalText;
|
|
3516
|
+
if (typeof text === "string" && text.trim()) return text;
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3677
3519
|
function broadcastRunEvent(run, event, data) {
|
|
3678
3520
|
for (const client of Array.from(run.clients)) try {
|
|
3679
3521
|
sendSSE(client, event, data);
|
|
3680
3522
|
} catch (err) {
|
|
3681
3523
|
run.clients.delete(client);
|
|
3682
|
-
log$
|
|
3524
|
+
log$29.warn("[stream] SSE 写入失败,移除客户端", {
|
|
3683
3525
|
sessionId: run.sessionId,
|
|
3684
3526
|
runId: run.runId,
|
|
3685
3527
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3699,7 +3541,7 @@ function queueRunMessage(run, messageStore, message, label) {
|
|
|
3699
3541
|
run.persistQueue = run.persistQueue.catch(() => void 0).then(async () => {
|
|
3700
3542
|
await messageStore.appendMessage(message);
|
|
3701
3543
|
}).catch((err) => {
|
|
3702
|
-
log$
|
|
3544
|
+
log$29.error(label, {
|
|
3703
3545
|
sessionId: run.sessionId,
|
|
3704
3546
|
runId: run.runId,
|
|
3705
3547
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3707,6 +3549,71 @@ function queueRunMessage(run, messageStore, message, label) {
|
|
|
3707
3549
|
});
|
|
3708
3550
|
return run.persistQueue;
|
|
3709
3551
|
}
|
|
3552
|
+
function appendRunStatusMessage(run, messageStore) {
|
|
3553
|
+
return queueRunMessage(run, messageStore, {
|
|
3554
|
+
sessionId: run.sessionId,
|
|
3555
|
+
role: "tool",
|
|
3556
|
+
content: JSON.stringify({
|
|
3557
|
+
runtimeEventType: "run-status",
|
|
3558
|
+
run: serializeChatRun(run)
|
|
3559
|
+
}),
|
|
3560
|
+
channel: "web",
|
|
3561
|
+
metadata: {
|
|
3562
|
+
runId: run.runId,
|
|
3563
|
+
requestId: run.requestId,
|
|
3564
|
+
traceId: run.traceId,
|
|
3565
|
+
scenarioId: run.scenarioId,
|
|
3566
|
+
runtimeEventType: "run-status",
|
|
3567
|
+
finalStatus: run.finalStatus,
|
|
3568
|
+
errorMessage: run.error,
|
|
3569
|
+
staleReason: run.staleReason
|
|
3570
|
+
}
|
|
3571
|
+
}, "[stream] 保存 run status 消息失败");
|
|
3572
|
+
}
|
|
3573
|
+
async function markRunTerminal(run, messageStore, status, params) {
|
|
3574
|
+
if (run.status !== "running") return false;
|
|
3575
|
+
run.status = status;
|
|
3576
|
+
run.completedAt = Date.now();
|
|
3577
|
+
run.finalStatus = params.finalStatus;
|
|
3578
|
+
run.error = params.error;
|
|
3579
|
+
run.staleReason = params.staleReason;
|
|
3580
|
+
recordRunEvent(run, `run-${status}`);
|
|
3581
|
+
broadcastRunStatus(run);
|
|
3582
|
+
await appendRunStatusMessage(run, messageStore);
|
|
3583
|
+
return true;
|
|
3584
|
+
}
|
|
3585
|
+
async function reconcileRunWithAdapter(run, messageStore, processManager) {
|
|
3586
|
+
if (run.status !== "running") return;
|
|
3587
|
+
const processInfo = processManager.getProcessInfo(run.sessionId);
|
|
3588
|
+
const adapterBusy = processManager.isSessionBusy?.(run.sessionId) ?? processInfo?.status === "running";
|
|
3589
|
+
run.adapterBusy = adapterBusy;
|
|
3590
|
+
const idleMs = Date.now() - run.lastEventAt;
|
|
3591
|
+
let staleReason;
|
|
3592
|
+
if (!adapterBusy) staleReason = processInfo ? `runtime process is ${processInfo.status}` : "runtime adapter reports session is not busy";
|
|
3593
|
+
else if (idleMs >= getStaleRunIdleMs()) staleReason = `run idle for ${Math.floor(idleMs / 1e3)}s`;
|
|
3594
|
+
if (!staleReason) return;
|
|
3595
|
+
const error = `Chat run became stale: ${staleReason}`;
|
|
3596
|
+
log$29.warn("[stream] activeRun 与 runtime 状态不一致,标记为 stale", {
|
|
3597
|
+
sessionId: run.sessionId,
|
|
3598
|
+
runId: run.runId,
|
|
3599
|
+
staleReason,
|
|
3600
|
+
idleMs,
|
|
3601
|
+
adapterBusy,
|
|
3602
|
+
processStatus: processInfo?.status
|
|
3603
|
+
});
|
|
3604
|
+
if (await markRunTerminal(run, messageStore, "failed", {
|
|
3605
|
+
finalStatus: "stale",
|
|
3606
|
+
error,
|
|
3607
|
+
staleReason
|
|
3608
|
+
})) {
|
|
3609
|
+
broadcastRunEvent(run, "error", {
|
|
3610
|
+
runId: run.runId,
|
|
3611
|
+
message: error,
|
|
3612
|
+
staleReason
|
|
3613
|
+
});
|
|
3614
|
+
endRunClients(run);
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3710
3617
|
function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
|
|
3711
3618
|
return {
|
|
3712
3619
|
runtimeEventType: event.type,
|
|
@@ -3764,7 +3671,7 @@ function createChatRouter(config) {
|
|
|
3764
3671
|
addRunClient(req, res, activeRun);
|
|
3765
3672
|
sendSSE(res, "session", { sessionId });
|
|
3766
3673
|
sendRunStatus(res, activeRun);
|
|
3767
|
-
log$
|
|
3674
|
+
log$29.info("[stream] 已重新连接到运行中的 run", {
|
|
3768
3675
|
sessionId,
|
|
3769
3676
|
runId: activeRun.runId
|
|
3770
3677
|
});
|
|
@@ -3777,7 +3684,7 @@ function createChatRouter(config) {
|
|
|
3777
3684
|
const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
|
|
3778
3685
|
const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
|
|
3779
3686
|
const sourceArtifacts = getRunInputValue(body.sourceArtifacts, body.source_artifacts, body.options?.sourceArtifacts, body.options?.source_artifacts, body.options?.metadata?.sourceArtifacts, body.options?.metadata?.source_artifacts);
|
|
3780
|
-
log$
|
|
3687
|
+
log$29.info("[stream] 收到流式对话请求", {
|
|
3781
3688
|
userId,
|
|
3782
3689
|
sessionId,
|
|
3783
3690
|
requestId,
|
|
@@ -3794,13 +3701,13 @@ function createChatRouter(config) {
|
|
|
3794
3701
|
tenantKey,
|
|
3795
3702
|
channel: "web"
|
|
3796
3703
|
})).id;
|
|
3797
|
-
log$
|
|
3704
|
+
log$29.info("[stream] 自动创建新会话", {
|
|
3798
3705
|
userId: openId,
|
|
3799
3706
|
sessionId
|
|
3800
3707
|
});
|
|
3801
3708
|
}
|
|
3802
3709
|
if (isChatSessionRunning(sessionId, processManager)) {
|
|
3803
|
-
log$
|
|
3710
|
+
log$29.warn("[stream] session 正在被非 Web Chat run 占用", {
|
|
3804
3711
|
tenantKey,
|
|
3805
3712
|
userId,
|
|
3806
3713
|
sessionId
|
|
@@ -3811,6 +3718,7 @@ function createChatRouter(config) {
|
|
|
3811
3718
|
res.end();
|
|
3812
3719
|
return;
|
|
3813
3720
|
}
|
|
3721
|
+
const runtimePrompt = enrichPromptWithSourceArtifacts(body.prompt, sourceArtifacts);
|
|
3814
3722
|
const runtimeConfig = buildAgentRuntimeConfig({
|
|
3815
3723
|
requestId,
|
|
3816
3724
|
traceId,
|
|
@@ -3818,7 +3726,7 @@ function createChatRouter(config) {
|
|
|
3818
3726
|
conversationId: sessionId,
|
|
3819
3727
|
entrypoint: "chat",
|
|
3820
3728
|
scenarioId,
|
|
3821
|
-
prompt:
|
|
3729
|
+
prompt: runtimePrompt,
|
|
3822
3730
|
maxTurns: body.options?.maxTurns,
|
|
3823
3731
|
maxBudgetUsd: body.options?.maxBudgetUsd,
|
|
3824
3732
|
model: process.env.CLAUDE_MODEL || void 0,
|
|
@@ -3847,37 +3755,10 @@ function createChatRouter(config) {
|
|
|
3847
3755
|
text: "",
|
|
3848
3756
|
clients: /* @__PURE__ */ new Set(),
|
|
3849
3757
|
runtimeConfig,
|
|
3850
|
-
persistQueue: Promise.resolve()
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
sessionId,
|
|
3855
|
-
role: "tool",
|
|
3856
|
-
content: JSON.stringify({
|
|
3857
|
-
runtimeEventType: "run-status",
|
|
3858
|
-
run: serializeChatRun(run)
|
|
3859
|
-
}),
|
|
3860
|
-
channel: "web",
|
|
3861
|
-
metadata: {
|
|
3862
|
-
runId: run.runId,
|
|
3863
|
-
requestId,
|
|
3864
|
-
traceId,
|
|
3865
|
-
scenarioId,
|
|
3866
|
-
runtimeEventType: "run-status",
|
|
3867
|
-
finalStatus: run.finalStatus,
|
|
3868
|
-
errorMessage: run.error
|
|
3869
|
-
}
|
|
3870
|
-
}, "[stream] 保存 run status 消息失败");
|
|
3871
|
-
};
|
|
3872
|
-
const markRunTerminal = async (status, params) => {
|
|
3873
|
-
if (run.status !== "running") return false;
|
|
3874
|
-
run.status = status;
|
|
3875
|
-
run.completedAt = Date.now();
|
|
3876
|
-
run.finalStatus = params.finalStatus;
|
|
3877
|
-
run.error = params.error;
|
|
3878
|
-
broadcastRunStatus(run);
|
|
3879
|
-
await appendRunStatusMessage();
|
|
3880
|
-
return true;
|
|
3758
|
+
persistQueue: Promise.resolve(),
|
|
3759
|
+
seenRuntimeEventIds: /* @__PURE__ */ new Set(),
|
|
3760
|
+
lastEventAt: Date.now(),
|
|
3761
|
+
lastEventType: "run-started"
|
|
3881
3762
|
};
|
|
3882
3763
|
try {
|
|
3883
3764
|
await messageStore.appendMessage({
|
|
@@ -3898,7 +3779,7 @@ function createChatRouter(config) {
|
|
|
3898
3779
|
});
|
|
3899
3780
|
} catch (err) {
|
|
3900
3781
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3901
|
-
log$
|
|
3782
|
+
log$29.error("[stream] 保存用户消息失败", {
|
|
3902
3783
|
sessionId,
|
|
3903
3784
|
error: errorMessage
|
|
3904
3785
|
});
|
|
@@ -3906,14 +3787,22 @@ function createChatRouter(config) {
|
|
|
3906
3787
|
return;
|
|
3907
3788
|
}
|
|
3908
3789
|
chatRunsBySession.set(sessionId, run);
|
|
3909
|
-
appendRunStatusMessage();
|
|
3790
|
+
appendRunStatusMessage(run, messageStore);
|
|
3910
3791
|
prepareSSE(res);
|
|
3911
3792
|
addRunClient(req, res, run);
|
|
3912
3793
|
sendSSE(res, "session", { sessionId });
|
|
3913
3794
|
sendRunStatus(res, run);
|
|
3795
|
+
const heartbeatTimer = setInterval(() => {
|
|
3796
|
+
if (run.status !== "running" || run.clients.size === 0) {
|
|
3797
|
+
clearInterval(heartbeatTimer);
|
|
3798
|
+
return;
|
|
3799
|
+
}
|
|
3800
|
+
broadcastRunEvent(run, "heartbeat", { ts: Date.now() });
|
|
3801
|
+
}, 15e3);
|
|
3914
3802
|
const handleBlockedEvent = (event) => {
|
|
3915
3803
|
const sanitized = sanitizeBlockedEvent(event);
|
|
3916
|
-
|
|
3804
|
+
recordRunEvent(run, "blocked", { toolName: sanitized.name });
|
|
3805
|
+
log$29.warn("[stream] Agent blocked event", { ...sanitized });
|
|
3917
3806
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
|
|
3918
3807
|
toolName: sanitized.name,
|
|
3919
3808
|
inputSummary: sanitized.inputSummary,
|
|
@@ -3943,7 +3832,23 @@ function createChatRouter(config) {
|
|
|
3943
3832
|
broadcastRunEvent(run, "blocked", { event: sanitized });
|
|
3944
3833
|
};
|
|
3945
3834
|
const handleRuntimeEvent = (event) => {
|
|
3835
|
+
const dedupeKey = getRuntimeEventDedupeKey(event);
|
|
3836
|
+
if (dedupeKey) {
|
|
3837
|
+
if (run.seenRuntimeEventIds.has(dedupeKey)) {
|
|
3838
|
+
log$29.debug("[stream] 跳过重复 runtime event", {
|
|
3839
|
+
sessionId,
|
|
3840
|
+
runId: run.runId,
|
|
3841
|
+
eventId: dedupeKey,
|
|
3842
|
+
eventType: event.type
|
|
3843
|
+
});
|
|
3844
|
+
return;
|
|
3845
|
+
}
|
|
3846
|
+
run.seenRuntimeEventIds.add(dedupeKey);
|
|
3847
|
+
}
|
|
3946
3848
|
const eventSummary = summarizeForAudit(event);
|
|
3849
|
+
recordRunEvent(run, event.type, { toolName: event.capabilityId });
|
|
3850
|
+
const runtimeFinalText = extractRuntimeEventFinalText(event);
|
|
3851
|
+
if (runtimeFinalText && !run.text.trim()) run.text = runtimeFinalText;
|
|
3947
3852
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
|
|
3948
3853
|
resultSummary: eventSummary,
|
|
3949
3854
|
metadata: buildRuntimeEventAuditMetadata(event)
|
|
@@ -3968,13 +3873,16 @@ function createChatRouter(config) {
|
|
|
3968
3873
|
};
|
|
3969
3874
|
const callbacks = {
|
|
3970
3875
|
onTextDelta: (text) => {
|
|
3876
|
+
recordRunEvent(run, "text-delta");
|
|
3971
3877
|
run.text += text;
|
|
3972
3878
|
broadcastRunEvent(run, "text-delta", { text });
|
|
3973
3879
|
},
|
|
3974
3880
|
onThinkingDelta: (text) => {
|
|
3881
|
+
recordRunEvent(run, "thinking-delta");
|
|
3975
3882
|
broadcastRunEvent(run, "thinking-delta", { text });
|
|
3976
3883
|
},
|
|
3977
3884
|
onToolUseStart: (toolName, toolInput) => {
|
|
3885
|
+
recordRunEvent(run, "tool-use-start", { toolName });
|
|
3978
3886
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
|
|
3979
3887
|
toolName,
|
|
3980
3888
|
inputSummary: summarizeForAudit(toolInput)
|
|
@@ -4002,6 +3910,7 @@ function createChatRouter(config) {
|
|
|
4002
3910
|
});
|
|
4003
3911
|
},
|
|
4004
3912
|
onToolResult: (toolUseId, result) => {
|
|
3913
|
+
recordRunEvent(run, "tool-result");
|
|
4005
3914
|
const resultSummary = summarizeForAudit(result);
|
|
4006
3915
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_finished", {
|
|
4007
3916
|
toolCallId: toolUseId,
|
|
@@ -4032,18 +3941,22 @@ function createChatRouter(config) {
|
|
|
4032
3941
|
});
|
|
4033
3942
|
},
|
|
4034
3943
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
3944
|
+
recordRunEvent(run, "tool-progress", { toolName });
|
|
4035
3945
|
broadcastRunEvent(run, "tool-progress", {
|
|
4036
3946
|
toolName,
|
|
4037
3947
|
elapsedSeconds
|
|
4038
3948
|
});
|
|
4039
3949
|
},
|
|
4040
3950
|
onTurnEnd: (stopReason) => {
|
|
3951
|
+
recordRunEvent(run, "turn-end");
|
|
4041
3952
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
|
|
4042
3953
|
broadcastRunEvent(run, "turn-end", { stopReason });
|
|
4043
3954
|
},
|
|
4044
3955
|
onResult: async (result) => {
|
|
4045
|
-
|
|
4046
|
-
|
|
3956
|
+
recordRunEvent(run, "result");
|
|
3957
|
+
const finalText = run.text.trim() ? run.text : result.result || "";
|
|
3958
|
+
run.text = finalText;
|
|
3959
|
+
await markRunTerminal(run, messageStore, result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
4047
3960
|
await queueRunMessage(run, messageStore, {
|
|
4048
3961
|
sessionId,
|
|
4049
3962
|
role: "assistant",
|
|
@@ -4070,8 +3983,9 @@ function createChatRouter(config) {
|
|
|
4070
3983
|
totalCostUsd: result.totalCostUsd,
|
|
4071
3984
|
numTurns: result.numTurns
|
|
4072
3985
|
});
|
|
3986
|
+
clearInterval(heartbeatTimer);
|
|
4073
3987
|
endRunClients(run);
|
|
4074
|
-
log$
|
|
3988
|
+
log$29.info("[stream] 对话完成", {
|
|
4075
3989
|
sessionId,
|
|
4076
3990
|
runId: run.runId,
|
|
4077
3991
|
result: result.subtype,
|
|
@@ -4093,7 +4007,8 @@ function createChatRouter(config) {
|
|
|
4093
4007
|
}));
|
|
4094
4008
|
},
|
|
4095
4009
|
onError: async (error) => {
|
|
4096
|
-
|
|
4010
|
+
recordRunEvent(run, "error");
|
|
4011
|
+
log$29.error("[stream] Runtime 执行出错", {
|
|
4097
4012
|
sessionId,
|
|
4098
4013
|
runId: run.runId,
|
|
4099
4014
|
error: error.message
|
|
@@ -4122,7 +4037,7 @@ function createChatRouter(config) {
|
|
|
4122
4037
|
requestId
|
|
4123
4038
|
}
|
|
4124
4039
|
}));
|
|
4125
|
-
await markRunTerminal("failed", {
|
|
4040
|
+
await markRunTerminal(run, messageStore, "failed", {
|
|
4126
4041
|
finalStatus: "error",
|
|
4127
4042
|
error: error.message
|
|
4128
4043
|
});
|
|
@@ -4130,6 +4045,7 @@ function createChatRouter(config) {
|
|
|
4130
4045
|
runId: run.runId,
|
|
4131
4046
|
message: error.message
|
|
4132
4047
|
});
|
|
4048
|
+
clearInterval(heartbeatTimer);
|
|
4133
4049
|
endRunClients(run);
|
|
4134
4050
|
},
|
|
4135
4051
|
onAuditEvent: (event) => {
|
|
@@ -4140,6 +4056,7 @@ function createChatRouter(config) {
|
|
|
4140
4056
|
});
|
|
4141
4057
|
},
|
|
4142
4058
|
onVerifierResult: (result) => {
|
|
4059
|
+
recordRunEvent(run, "verifier-result");
|
|
4143
4060
|
const resultSummary = summarizeForAudit(result);
|
|
4144
4061
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "verifier_result", {
|
|
4145
4062
|
resultSummary,
|
|
@@ -4179,7 +4096,7 @@ function createChatRouter(config) {
|
|
|
4179
4096
|
scenarioId: runtimeConfig.runContext?.scenarioId,
|
|
4180
4097
|
...buildRunInputAuditMetadata(runtimeConfig)
|
|
4181
4098
|
} }));
|
|
4182
|
-
log$
|
|
4099
|
+
log$29.info("[stream] 开始执行 prompt", {
|
|
4183
4100
|
sessionId,
|
|
4184
4101
|
runId: run.runId,
|
|
4185
4102
|
cwd: runtimeConfig.cwd
|
|
@@ -4187,11 +4104,11 @@ function createChatRouter(config) {
|
|
|
4187
4104
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
4188
4105
|
if (run.status === "running") {
|
|
4189
4106
|
const errorMessage = "Runtime completed without a final result event";
|
|
4190
|
-
log$
|
|
4107
|
+
log$29.error("[stream] executePrompt 未返回终态回调", {
|
|
4191
4108
|
sessionId,
|
|
4192
4109
|
runId: run.runId
|
|
4193
4110
|
});
|
|
4194
|
-
await markRunTerminal("failed", {
|
|
4111
|
+
await markRunTerminal(run, messageStore, "failed", {
|
|
4195
4112
|
finalStatus: "error",
|
|
4196
4113
|
error: errorMessage
|
|
4197
4114
|
});
|
|
@@ -4199,11 +4116,12 @@ function createChatRouter(config) {
|
|
|
4199
4116
|
runId: run.runId,
|
|
4200
4117
|
message: errorMessage
|
|
4201
4118
|
});
|
|
4119
|
+
clearInterval(heartbeatTimer);
|
|
4202
4120
|
endRunClients(run);
|
|
4203
4121
|
}
|
|
4204
4122
|
} catch (err) {
|
|
4205
4123
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4206
|
-
log$
|
|
4124
|
+
log$29.error("[stream] executePrompt 异常", {
|
|
4207
4125
|
sessionId,
|
|
4208
4126
|
runId: run.runId,
|
|
4209
4127
|
error: errorMessage
|
|
@@ -4220,7 +4138,7 @@ function createChatRouter(config) {
|
|
|
4220
4138
|
}
|
|
4221
4139
|
const cursor = req.query.cursor;
|
|
4222
4140
|
const limit = parseInt(req.query.limit, 10) || 50;
|
|
4223
|
-
log$
|
|
4141
|
+
log$29.info("[history] 查询会话历史", {
|
|
4224
4142
|
userId: chatUser.userId,
|
|
4225
4143
|
sessionId,
|
|
4226
4144
|
cursor,
|
|
@@ -4236,6 +4154,7 @@ function createChatRouter(config) {
|
|
|
4236
4154
|
tenantKey: chatUser.tenantKey,
|
|
4237
4155
|
openId: chatUser.openId
|
|
4238
4156
|
}) ? run : void 0;
|
|
4157
|
+
if (visibleRun) await reconcileRunWithAdapter(visibleRun, messageStore, processManager);
|
|
4239
4158
|
res.json({
|
|
4240
4159
|
messages: result.messages,
|
|
4241
4160
|
nextCursor: result.nextCursor,
|
|
@@ -4245,7 +4164,7 @@ function createChatRouter(config) {
|
|
|
4245
4164
|
});
|
|
4246
4165
|
} catch (err) {
|
|
4247
4166
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4248
|
-
log$
|
|
4167
|
+
log$29.error("[history] 查询历史消息失败", {
|
|
4249
4168
|
sessionId,
|
|
4250
4169
|
error: errorMessage
|
|
4251
4170
|
});
|
|
@@ -4271,6 +4190,7 @@ function createChatRouter(config) {
|
|
|
4271
4190
|
});
|
|
4272
4191
|
return;
|
|
4273
4192
|
}
|
|
4193
|
+
await reconcileRunWithAdapter(run, messageStore, processManager);
|
|
4274
4194
|
const serialized = serializeChatRun(run);
|
|
4275
4195
|
res.json({
|
|
4276
4196
|
sessionId,
|
|
@@ -4280,13 +4200,13 @@ function createChatRouter(config) {
|
|
|
4280
4200
|
});
|
|
4281
4201
|
router.get("/api/chat/sessions", chatAuthMiddleware, async (_req, res) => {
|
|
4282
4202
|
const chatUser = res.locals.chatUser;
|
|
4283
|
-
log$
|
|
4203
|
+
log$29.info("[sessions] 列出用户会话", { userId: chatUser.userId });
|
|
4284
4204
|
try {
|
|
4285
4205
|
const sessions = await messageStore.listSessions(chatUser.openId);
|
|
4286
4206
|
res.json({ sessions });
|
|
4287
4207
|
} catch (err) {
|
|
4288
4208
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4289
|
-
log$
|
|
4209
|
+
log$29.error("[sessions] 列出会话失败", {
|
|
4290
4210
|
userId: chatUser.userId,
|
|
4291
4211
|
error: errorMessage
|
|
4292
4212
|
});
|
|
@@ -4296,7 +4216,7 @@ function createChatRouter(config) {
|
|
|
4296
4216
|
router.post("/api/chat/sessions", chatAuthMiddleware, async (req, res) => {
|
|
4297
4217
|
const chatUser = res.locals.chatUser;
|
|
4298
4218
|
const body = req.body;
|
|
4299
|
-
log$
|
|
4219
|
+
log$29.info("[sessions] 创建新会话", {
|
|
4300
4220
|
userId: chatUser.userId,
|
|
4301
4221
|
title: body.title
|
|
4302
4222
|
});
|
|
@@ -4311,7 +4231,7 @@ function createChatRouter(config) {
|
|
|
4311
4231
|
res.json({ session });
|
|
4312
4232
|
} catch (err) {
|
|
4313
4233
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4314
|
-
log$
|
|
4234
|
+
log$29.error("[sessions] 创建会话失败", {
|
|
4315
4235
|
userId: chatUser.userId,
|
|
4316
4236
|
error: errorMessage
|
|
4317
4237
|
});
|
|
@@ -4321,7 +4241,7 @@ function createChatRouter(config) {
|
|
|
4321
4241
|
router.delete("/api/chat/sessions/:id", chatAuthMiddleware, async (req, res) => {
|
|
4322
4242
|
const chatUser = res.locals.chatUser;
|
|
4323
4243
|
const sessionId = String(req.params.id);
|
|
4324
|
-
log$
|
|
4244
|
+
log$29.info("[sessions] 删除会话", {
|
|
4325
4245
|
userId: chatUser.userId,
|
|
4326
4246
|
sessionId
|
|
4327
4247
|
});
|
|
@@ -4331,7 +4251,7 @@ function createChatRouter(config) {
|
|
|
4331
4251
|
res.json({ success: true });
|
|
4332
4252
|
} catch (err) {
|
|
4333
4253
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4334
|
-
log$
|
|
4254
|
+
log$29.error("[sessions] 删除会话失败", {
|
|
4335
4255
|
sessionId,
|
|
4336
4256
|
error: errorMessage
|
|
4337
4257
|
});
|
|
@@ -4340,6 +4260,34 @@ function createChatRouter(config) {
|
|
|
4340
4260
|
});
|
|
4341
4261
|
return router;
|
|
4342
4262
|
}
|
|
4263
|
+
/**
|
|
4264
|
+
* 将 sourceArtifacts 中的文件信息(fileToken、fileName 等)注入到用户 prompt 中,
|
|
4265
|
+
* 使 LLM 能看到实际的 file token 值并直接用于下载。
|
|
4266
|
+
*
|
|
4267
|
+
* 如果没有 sourceArtifacts 或无法提取有效信息,返回原始 prompt。
|
|
4268
|
+
*/
|
|
4269
|
+
function enrichPromptWithSourceArtifacts(prompt, sourceArtifacts) {
|
|
4270
|
+
if (!sourceArtifacts) return prompt;
|
|
4271
|
+
const lines = [];
|
|
4272
|
+
const extractInfo = (artifact) => {
|
|
4273
|
+
if (!artifact || typeof artifact !== "object") return;
|
|
4274
|
+
const record = artifact;
|
|
4275
|
+
const fileToken = record.fileToken ?? record.file_token ?? record.driveFileToken ?? record.drive_file_token;
|
|
4276
|
+
const fileName = record.fileName ?? record.file_name ?? record.name ?? record.title;
|
|
4277
|
+
const fileType = record.fileType ?? record.file_type ?? record.type ?? record.mimeType;
|
|
4278
|
+
const fileUrl = record.fileUrl ?? record.file_url ?? record.url ?? record.uri;
|
|
4279
|
+
if (fileToken || fileName || fileUrl) {
|
|
4280
|
+
if (fileName) lines.push(`- fileName: ${String(fileName)}`);
|
|
4281
|
+
if (fileToken) lines.push(`- fileToken: ${String(fileToken)}`);
|
|
4282
|
+
if (fileType) lines.push(`- fileType: ${String(fileType)}`);
|
|
4283
|
+
if (fileUrl) lines.push(`- fileUrl: ${String(fileUrl)}`);
|
|
4284
|
+
}
|
|
4285
|
+
};
|
|
4286
|
+
if (Array.isArray(sourceArtifacts)) for (const item of sourceArtifacts) extractInfo(item);
|
|
4287
|
+
else extractInfo(sourceArtifacts);
|
|
4288
|
+
if (lines.length === 0) return prompt;
|
|
4289
|
+
return `${prompt}\n\n[Attached Source File]\n${lines.join("\n")}`;
|
|
4290
|
+
}
|
|
4343
4291
|
//#endregion
|
|
4344
4292
|
//#region src/core/app-info-sync.ts
|
|
4345
4293
|
/**
|
|
@@ -4357,7 +4305,7 @@ function createChatRouter(config) {
|
|
|
4357
4305
|
* - GET /open-apis/application/v6/applications/me?lang=zh_cn(需要 application:application:self_manage 权限,返回完整信息)
|
|
4358
4306
|
* 如果后者无权限则 fallback 到前者
|
|
4359
4307
|
*/
|
|
4360
|
-
const log$
|
|
4308
|
+
const log$28 = larkLogger("core/app-info-sync");
|
|
4361
4309
|
/**
|
|
4362
4310
|
* 从飞书获取应用信息
|
|
4363
4311
|
*
|
|
@@ -4368,7 +4316,7 @@ async function fetchAppInfo(credentials) {
|
|
|
4368
4316
|
const { appId, appSecret } = credentials;
|
|
4369
4317
|
const token = await getTenantAccessToken(appId, appSecret);
|
|
4370
4318
|
if (!token) {
|
|
4371
|
-
log$
|
|
4319
|
+
log$28.error("获取 tenant_access_token 失败,无法同步应用信息");
|
|
4372
4320
|
return null;
|
|
4373
4321
|
}
|
|
4374
4322
|
const fullInfo = await fetchFromApplicationApi(token);
|
|
@@ -4379,13 +4327,13 @@ async function fetchAppInfo(credentials) {
|
|
|
4379
4327
|
async function fetchFromApplicationApi(token) {
|
|
4380
4328
|
try {
|
|
4381
4329
|
const data = await (await fetch("https://open.feishu.cn/open-apis/application/v6/applications/me?lang=zh_cn", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
4382
|
-
log$
|
|
4330
|
+
log$28.info("application/v6 API 响应", {
|
|
4383
4331
|
code: data.code,
|
|
4384
4332
|
msg: data.msg,
|
|
4385
4333
|
hasApp: !!data.data?.app
|
|
4386
4334
|
});
|
|
4387
4335
|
if (data.code !== 0 || !data.data?.app) {
|
|
4388
|
-
log$
|
|
4336
|
+
log$28.warn("application/v6 API 返回非零或无数据,将 fallback", {
|
|
4389
4337
|
code: data.code,
|
|
4390
4338
|
msg: data.msg
|
|
4391
4339
|
});
|
|
@@ -4400,7 +4348,7 @@ async function fetchFromApplicationApi(token) {
|
|
|
4400
4348
|
helpDocUrl: zhInfo?.help_use
|
|
4401
4349
|
};
|
|
4402
4350
|
} catch (err) {
|
|
4403
|
-
log$
|
|
4351
|
+
log$28.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4404
4352
|
return null;
|
|
4405
4353
|
}
|
|
4406
4354
|
}
|
|
@@ -4408,13 +4356,13 @@ async function fetchFromApplicationApi(token) {
|
|
|
4408
4356
|
async function fetchFromBotApi(token) {
|
|
4409
4357
|
try {
|
|
4410
4358
|
const data = await (await fetch("https://open.feishu.cn/open-apis/bot/v3/info", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
4411
|
-
log$
|
|
4359
|
+
log$28.info("bot/v3/info API 响应", {
|
|
4412
4360
|
code: data.code,
|
|
4413
4361
|
msg: data.msg,
|
|
4414
4362
|
hasBot: !!data.bot
|
|
4415
4363
|
});
|
|
4416
4364
|
if (data.code !== 0 || !data.bot) {
|
|
4417
|
-
log$
|
|
4365
|
+
log$28.error("bot/v3/info API 失败", {
|
|
4418
4366
|
code: data.code,
|
|
4419
4367
|
msg: data.msg
|
|
4420
4368
|
});
|
|
@@ -4425,7 +4373,7 @@ async function fetchFromBotApi(token) {
|
|
|
4425
4373
|
avatarUrl: data.bot.avatar_url
|
|
4426
4374
|
};
|
|
4427
4375
|
} catch (err) {
|
|
4428
|
-
log$
|
|
4376
|
+
log$28.error("bot/v3/info API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4429
4377
|
return null;
|
|
4430
4378
|
}
|
|
4431
4379
|
}
|
|
@@ -4441,7 +4389,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
4441
4389
|
})
|
|
4442
4390
|
})).json();
|
|
4443
4391
|
if (data.code !== 0 || !data.tenant_access_token) {
|
|
4444
|
-
log$
|
|
4392
|
+
log$28.error("获取 tenant_access_token 失败", {
|
|
4445
4393
|
code: data.code,
|
|
4446
4394
|
msg: data.msg
|
|
4447
4395
|
});
|
|
@@ -4449,7 +4397,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
4449
4397
|
}
|
|
4450
4398
|
return data.tenant_access_token;
|
|
4451
4399
|
} catch (err) {
|
|
4452
|
-
log$
|
|
4400
|
+
log$28.error("获取 tenant_access_token 异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4453
4401
|
return null;
|
|
4454
4402
|
}
|
|
4455
4403
|
}
|
|
@@ -4487,10 +4435,10 @@ function parseDocTokenFromUrl(url) {
|
|
|
4487
4435
|
async function fetchDocContent(docUrl, accessToken) {
|
|
4488
4436
|
const parsed = parseDocTokenFromUrl(docUrl);
|
|
4489
4437
|
if (!parsed) {
|
|
4490
|
-
log$
|
|
4438
|
+
log$28.warn("无法从 URL 中解析文档 token", { url: docUrl });
|
|
4491
4439
|
return null;
|
|
4492
4440
|
}
|
|
4493
|
-
log$
|
|
4441
|
+
log$28.info("开始读取人设文档", {
|
|
4494
4442
|
url: docUrl,
|
|
4495
4443
|
type: parsed.type,
|
|
4496
4444
|
token: parsed.token
|
|
@@ -4498,18 +4446,18 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4498
4446
|
let docToken = parsed.token;
|
|
4499
4447
|
if (parsed.type === "wiki") {
|
|
4500
4448
|
const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
|
|
4501
|
-
if (!realToken) log$
|
|
4449
|
+
if (!realToken) log$28.warn("wiki 节点解析失败,尝试直接使用 token 读取");
|
|
4502
4450
|
else docToken = realToken;
|
|
4503
4451
|
}
|
|
4504
4452
|
try {
|
|
4505
4453
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
4506
|
-
log$
|
|
4454
|
+
log$28.info("文档 raw_content API 响应", {
|
|
4507
4455
|
code: data.code,
|
|
4508
4456
|
msg: data.msg,
|
|
4509
4457
|
contentLength: data.data?.content?.length
|
|
4510
4458
|
});
|
|
4511
4459
|
if (data.code !== 0 || !data.data?.content) {
|
|
4512
|
-
log$
|
|
4460
|
+
log$28.warn("读取文档内容失败", {
|
|
4513
4461
|
code: data.code,
|
|
4514
4462
|
msg: data.msg,
|
|
4515
4463
|
docToken
|
|
@@ -4518,7 +4466,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4518
4466
|
}
|
|
4519
4467
|
return data.data.content.trim();
|
|
4520
4468
|
} catch (err) {
|
|
4521
|
-
log$
|
|
4469
|
+
log$28.error("读取文档内容异常", {
|
|
4522
4470
|
error: err instanceof Error ? err.message : String(err),
|
|
4523
4471
|
docToken
|
|
4524
4472
|
});
|
|
@@ -4529,7 +4477,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4529
4477
|
async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
4530
4478
|
try {
|
|
4531
4479
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
4532
|
-
log$
|
|
4480
|
+
log$28.info("wiki get_node API 响应", {
|
|
4533
4481
|
code: data.code,
|
|
4534
4482
|
msg: data.msg,
|
|
4535
4483
|
objType: data.data?.node?.obj_type
|
|
@@ -4537,7 +4485,7 @@ async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
|
4537
4485
|
if (data.code !== 0 || !data.data?.node?.obj_token) return null;
|
|
4538
4486
|
return data.data.node.obj_token;
|
|
4539
4487
|
} catch (err) {
|
|
4540
|
-
log$
|
|
4488
|
+
log$28.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4541
4489
|
return null;
|
|
4542
4490
|
}
|
|
4543
4491
|
}
|
|
@@ -4559,7 +4507,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4559
4507
|
const infoBlock = buildAppInfoBlock(appInfo);
|
|
4560
4508
|
if (!existsSync(claudeMdPath)) {
|
|
4561
4509
|
await writeFile(claudeMdPath, infoBlock + "\n\n" + getDefaultClaudeMdBody(), "utf-8");
|
|
4562
|
-
log$
|
|
4510
|
+
log$28.info("CLAUDE.md 已创建(含应用信息)", { appName: appInfo.appName });
|
|
4563
4511
|
return;
|
|
4564
4512
|
}
|
|
4565
4513
|
let content = await readFile(claudeMdPath, "utf-8");
|
|
@@ -4571,7 +4519,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4571
4519
|
content = before + infoBlock + after;
|
|
4572
4520
|
} else content = infoBlock + "\n\n" + content;
|
|
4573
4521
|
await writeFile(claudeMdPath, content, "utf-8");
|
|
4574
|
-
log$
|
|
4522
|
+
log$28.info("CLAUDE.md 应用信息已同步", {
|
|
4575
4523
|
appName: appInfo.appName,
|
|
4576
4524
|
hasDescription: !!appInfo.description,
|
|
4577
4525
|
hasAvatar: !!appInfo.avatarUrl
|
|
@@ -4586,7 +4534,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4586
4534
|
async function syncPersonaDocToClaudeMd(personaContent) {
|
|
4587
4535
|
const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
|
|
4588
4536
|
if (!existsSync(claudeMdPath)) {
|
|
4589
|
-
log$
|
|
4537
|
+
log$28.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
|
|
4590
4538
|
return;
|
|
4591
4539
|
}
|
|
4592
4540
|
let content = await readFile(claudeMdPath, "utf-8");
|
|
@@ -4607,7 +4555,7 @@ async function syncPersonaDocToClaudeMd(personaContent) {
|
|
|
4607
4555
|
content = before + personaBlock + after;
|
|
4608
4556
|
} else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
|
|
4609
4557
|
await writeFile(claudeMdPath, content, "utf-8");
|
|
4610
|
-
log$
|
|
4558
|
+
log$28.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
|
|
4611
4559
|
}
|
|
4612
4560
|
/** 构建应用信息标记区块 */
|
|
4613
4561
|
function buildAppInfoBlock(appInfo) {
|
|
@@ -4699,24 +4647,24 @@ function getDefaultClaudeMdBody() {
|
|
|
4699
4647
|
* @returns 同步后的应用信息,如果失败返回 null
|
|
4700
4648
|
*/
|
|
4701
4649
|
async function syncAppInfo(credentials) {
|
|
4702
|
-
log$
|
|
4650
|
+
log$28.info("开始同步应用信息", { appId: credentials.appId });
|
|
4703
4651
|
const appInfo = await fetchAppInfo(credentials);
|
|
4704
4652
|
if (!appInfo) {
|
|
4705
|
-
log$
|
|
4653
|
+
log$28.warn("获取应用信息失败,跳过同步");
|
|
4706
4654
|
return null;
|
|
4707
4655
|
}
|
|
4708
4656
|
await syncAppInfoToClaudeMd(appInfo);
|
|
4709
4657
|
if (appInfo.helpDocUrl) {
|
|
4710
|
-
log$
|
|
4658
|
+
log$28.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
|
|
4711
4659
|
const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
|
|
4712
4660
|
if (token) {
|
|
4713
4661
|
const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
|
|
4714
4662
|
if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
|
|
4715
|
-
else log$
|
|
4663
|
+
else log$28.warn("人设文档内容为空或读取失败,跳过同步");
|
|
4716
4664
|
}
|
|
4717
4665
|
}
|
|
4718
4666
|
await installSyncSkill();
|
|
4719
|
-
log$
|
|
4667
|
+
log$28.info("应用信息同步完成", {
|
|
4720
4668
|
appName: appInfo.appName,
|
|
4721
4669
|
description: appInfo.description?.substring(0, 50),
|
|
4722
4670
|
hasPersonaDoc: !!appInfo.helpDocUrl
|
|
@@ -4734,7 +4682,7 @@ async function installSyncSkill() {
|
|
|
4734
4682
|
if ((await readFile(SYNC_SKILL_PATH, "utf-8")).includes(`skill-version: ${SYNC_SKILL_VERSION}`)) return;
|
|
4735
4683
|
}
|
|
4736
4684
|
await writeFile(SYNC_SKILL_PATH, SYNC_SKILL_CONTENT, "utf-8");
|
|
4737
|
-
log$
|
|
4685
|
+
log$28.info("sync-app-info 技能已安装", { path: SYNC_SKILL_PATH });
|
|
4738
4686
|
}
|
|
4739
4687
|
const SYNC_SKILL_CONTENT = `---
|
|
4740
4688
|
skill-version: ${SYNC_SKILL_VERSION}
|
|
@@ -5730,7 +5678,7 @@ function createHooksRouter() {
|
|
|
5730
5678
|
}
|
|
5731
5679
|
//#endregion
|
|
5732
5680
|
//#region src/memory/store.ts
|
|
5733
|
-
const log$
|
|
5681
|
+
const log$27 = larkLogger("memory/store");
|
|
5734
5682
|
const MEMORY_VERSION = 1;
|
|
5735
5683
|
const MAX_TEAMMATE_RECENT = 3;
|
|
5736
5684
|
const MAX_LONG_TERM = 100;
|
|
@@ -5756,7 +5704,7 @@ var MemoryStore = class MemoryStore {
|
|
|
5756
5704
|
const raw = await readFile(path, "utf-8");
|
|
5757
5705
|
return normalizeMemoryFile(JSON.parse(raw));
|
|
5758
5706
|
} catch (err) {
|
|
5759
|
-
log$
|
|
5707
|
+
log$27.warn("memory file load failed, using empty state", {
|
|
5760
5708
|
cwd,
|
|
5761
5709
|
path,
|
|
5762
5710
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -6252,7 +6200,7 @@ function createMcpCallbackRouter() {
|
|
|
6252
6200
|
* 提供 /api/scheduled-tasks 的 RESTful CRUD 接口。
|
|
6253
6201
|
* 每个请求和响应都记录详细日志,便于后续错误排查。
|
|
6254
6202
|
*/
|
|
6255
|
-
const log$
|
|
6203
|
+
const log$26 = larkLogger("gateway/scheduler-handler");
|
|
6256
6204
|
/**
|
|
6257
6205
|
* 创建定时任务路由
|
|
6258
6206
|
*
|
|
@@ -6263,16 +6211,16 @@ function createSchedulerRouter(taskManager) {
|
|
|
6263
6211
|
const router = Router();
|
|
6264
6212
|
router.get("/api/scheduled-tasks", (req, res) => {
|
|
6265
6213
|
const requestId = req.headers["x-request-id"];
|
|
6266
|
-
log$
|
|
6214
|
+
log$26.info("请求列出所有定时任务", { requestId });
|
|
6267
6215
|
try {
|
|
6268
6216
|
const tasks = taskManager.listTasks();
|
|
6269
|
-
log$
|
|
6217
|
+
log$26.info("返回定时任务列表", {
|
|
6270
6218
|
requestId,
|
|
6271
6219
|
count: tasks.length
|
|
6272
6220
|
});
|
|
6273
6221
|
res.json({ tasks });
|
|
6274
6222
|
} catch (err) {
|
|
6275
|
-
log$
|
|
6223
|
+
log$26.error("列出定时任务失败", {
|
|
6276
6224
|
requestId,
|
|
6277
6225
|
error: String(err)
|
|
6278
6226
|
});
|
|
@@ -6285,13 +6233,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
6285
6233
|
router.post("/api/scheduled-tasks", (req, res) => {
|
|
6286
6234
|
const requestId = req.headers["x-request-id"];
|
|
6287
6235
|
const body = req.body;
|
|
6288
|
-
log$
|
|
6236
|
+
log$26.info("请求创建定时任务", {
|
|
6289
6237
|
requestId,
|
|
6290
6238
|
body: JSON.stringify(body)
|
|
6291
6239
|
});
|
|
6292
6240
|
const { name, cron: cronExpr, session_id, cwd, prompt, example_output, constraints } = body;
|
|
6293
6241
|
if (!name || typeof name !== "string") {
|
|
6294
|
-
log$
|
|
6242
|
+
log$26.warn("创建任务参数缺失: name", { requestId });
|
|
6295
6243
|
res.status(400).json({
|
|
6296
6244
|
error: "参数错误",
|
|
6297
6245
|
detail: "name 为必填字符串"
|
|
@@ -6299,7 +6247,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6299
6247
|
return;
|
|
6300
6248
|
}
|
|
6301
6249
|
if (!cronExpr || typeof cronExpr !== "string") {
|
|
6302
|
-
log$
|
|
6250
|
+
log$26.warn("创建任务参数缺失: cron", { requestId });
|
|
6303
6251
|
res.status(400).json({
|
|
6304
6252
|
error: "参数错误",
|
|
6305
6253
|
detail: "cron 为必填字符串"
|
|
@@ -6307,7 +6255,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6307
6255
|
return;
|
|
6308
6256
|
}
|
|
6309
6257
|
if (!session_id || typeof session_id !== "string") {
|
|
6310
|
-
log$
|
|
6258
|
+
log$26.warn("创建任务参数缺失: session_id", { requestId });
|
|
6311
6259
|
res.status(400).json({
|
|
6312
6260
|
error: "参数错误",
|
|
6313
6261
|
detail: "session_id 为必填字符串"
|
|
@@ -6315,7 +6263,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6315
6263
|
return;
|
|
6316
6264
|
}
|
|
6317
6265
|
if (!cwd || typeof cwd !== "string") {
|
|
6318
|
-
log$
|
|
6266
|
+
log$26.warn("创建任务参数缺失: cwd", { requestId });
|
|
6319
6267
|
res.status(400).json({
|
|
6320
6268
|
error: "参数错误",
|
|
6321
6269
|
detail: "cwd 为必填字符串"
|
|
@@ -6323,7 +6271,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6323
6271
|
return;
|
|
6324
6272
|
}
|
|
6325
6273
|
if (!prompt || typeof prompt !== "string") {
|
|
6326
|
-
log$
|
|
6274
|
+
log$26.warn("创建任务参数缺失: prompt", { requestId });
|
|
6327
6275
|
res.status(400).json({
|
|
6328
6276
|
error: "参数错误",
|
|
6329
6277
|
detail: "prompt 为必填字符串"
|
|
@@ -6331,7 +6279,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6331
6279
|
return;
|
|
6332
6280
|
}
|
|
6333
6281
|
if (!cron.validate(cronExpr)) {
|
|
6334
|
-
log$
|
|
6282
|
+
log$26.warn("cron 表达式不合法", {
|
|
6335
6283
|
requestId,
|
|
6336
6284
|
cron: cronExpr
|
|
6337
6285
|
});
|
|
@@ -6352,7 +6300,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6352
6300
|
constraints
|
|
6353
6301
|
};
|
|
6354
6302
|
const task = taskManager.createTask(params);
|
|
6355
|
-
log$
|
|
6303
|
+
log$26.info("定时任务创建成功", {
|
|
6356
6304
|
requestId,
|
|
6357
6305
|
taskId: task.id,
|
|
6358
6306
|
name: task.name,
|
|
@@ -6360,7 +6308,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6360
6308
|
});
|
|
6361
6309
|
res.status(201).json({ task });
|
|
6362
6310
|
} catch (err) {
|
|
6363
|
-
log$
|
|
6311
|
+
log$26.error("创建定时任务失败", {
|
|
6364
6312
|
requestId,
|
|
6365
6313
|
error: String(err)
|
|
6366
6314
|
});
|
|
@@ -6374,13 +6322,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
6374
6322
|
const requestId = req.headers["x-request-id"];
|
|
6375
6323
|
const id = String(req.params.id);
|
|
6376
6324
|
const body = req.body;
|
|
6377
|
-
log$
|
|
6325
|
+
log$26.info("请求更新定时任务", {
|
|
6378
6326
|
requestId,
|
|
6379
6327
|
taskId: id,
|
|
6380
6328
|
body: JSON.stringify(body)
|
|
6381
6329
|
});
|
|
6382
6330
|
if (!taskManager.getTask(id)) {
|
|
6383
|
-
log$
|
|
6331
|
+
log$26.warn("更新的任务不存在", {
|
|
6384
6332
|
requestId,
|
|
6385
6333
|
taskId: id
|
|
6386
6334
|
});
|
|
@@ -6392,7 +6340,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6392
6340
|
}
|
|
6393
6341
|
if (body.cron !== void 0) {
|
|
6394
6342
|
if (typeof body.cron !== "string" || !cron.validate(body.cron)) {
|
|
6395
|
-
log$
|
|
6343
|
+
log$26.warn("更新的 cron 表达式不合法", {
|
|
6396
6344
|
requestId,
|
|
6397
6345
|
taskId: id,
|
|
6398
6346
|
cron: body.cron
|
|
@@ -6413,7 +6361,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6413
6361
|
if (body.constraints !== void 0) updates.constraints = body.constraints;
|
|
6414
6362
|
if (body.enabled !== void 0) updates.enabled = body.enabled;
|
|
6415
6363
|
const task = taskManager.updateTask(id, updates);
|
|
6416
|
-
log$
|
|
6364
|
+
log$26.info("定时任务更新成功", {
|
|
6417
6365
|
requestId,
|
|
6418
6366
|
taskId: task.id,
|
|
6419
6367
|
name: task.name,
|
|
@@ -6421,7 +6369,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6421
6369
|
});
|
|
6422
6370
|
res.json({ task });
|
|
6423
6371
|
} catch (err) {
|
|
6424
|
-
log$
|
|
6372
|
+
log$26.error("更新定时任务失败", {
|
|
6425
6373
|
requestId,
|
|
6426
6374
|
taskId: id,
|
|
6427
6375
|
error: String(err)
|
|
@@ -6435,12 +6383,12 @@ function createSchedulerRouter(taskManager) {
|
|
|
6435
6383
|
router.delete("/api/scheduled-tasks/:id", (req, res) => {
|
|
6436
6384
|
const requestId = req.headers["x-request-id"];
|
|
6437
6385
|
const id = String(req.params.id);
|
|
6438
|
-
log$
|
|
6386
|
+
log$26.info("请求删除定时任务", {
|
|
6439
6387
|
requestId,
|
|
6440
6388
|
taskId: id
|
|
6441
6389
|
});
|
|
6442
6390
|
if (!taskManager.getTask(id)) {
|
|
6443
|
-
log$
|
|
6391
|
+
log$26.warn("删除的任务不存在", {
|
|
6444
6392
|
requestId,
|
|
6445
6393
|
taskId: id
|
|
6446
6394
|
});
|
|
@@ -6452,7 +6400,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6452
6400
|
}
|
|
6453
6401
|
try {
|
|
6454
6402
|
const deleted = taskManager.deleteTask(id);
|
|
6455
|
-
log$
|
|
6403
|
+
log$26.info("定时任务删除结果", {
|
|
6456
6404
|
requestId,
|
|
6457
6405
|
taskId: id,
|
|
6458
6406
|
deleted
|
|
@@ -6462,7 +6410,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6462
6410
|
deleted: true
|
|
6463
6411
|
});
|
|
6464
6412
|
} catch (err) {
|
|
6465
|
-
log$
|
|
6413
|
+
log$26.error("删除定时任务失败", {
|
|
6466
6414
|
requestId,
|
|
6467
6415
|
taskId: id,
|
|
6468
6416
|
error: String(err)
|
|
@@ -6712,6 +6660,7 @@ const PERSONA_FILE = join(homedir(), ".claude", "CLAUDE.md");
|
|
|
6712
6660
|
function createStatusRouter(config) {
|
|
6713
6661
|
const router = express.Router();
|
|
6714
6662
|
const sessionsDir = getWorkspaceChatsRoot(config?.workspaceRoot ?? resolveWorkspaceRoot("/workspace"));
|
|
6663
|
+
const processManager = config?.processManager;
|
|
6715
6664
|
router.get("/sessions", async (req, res) => {
|
|
6716
6665
|
logger$2.info("list sessions request", {
|
|
6717
6666
|
method: req.method,
|
|
@@ -6812,14 +6761,18 @@ function createStatusRouter(config) {
|
|
|
6812
6761
|
try {
|
|
6813
6762
|
const uptimeSeconds = process.uptime();
|
|
6814
6763
|
const memory = process.memoryUsage();
|
|
6764
|
+
const processes = processManager?.getAllProcessInfo().map((processInfo) => {
|
|
6765
|
+
return serializeRuntimeProcess(processInfo, processManager);
|
|
6766
|
+
}) ?? [];
|
|
6815
6767
|
const statusData = {
|
|
6816
6768
|
uptime: uptimeSeconds,
|
|
6817
|
-
processes
|
|
6769
|
+
processes,
|
|
6818
6770
|
memory
|
|
6819
6771
|
};
|
|
6820
6772
|
logger$2.info("get status response", {
|
|
6821
6773
|
uptime: uptimeSeconds,
|
|
6822
|
-
memoryRss: memory.rss
|
|
6774
|
+
memoryRss: memory.rss,
|
|
6775
|
+
processCount: processes.length
|
|
6823
6776
|
});
|
|
6824
6777
|
res.json(statusData);
|
|
6825
6778
|
} catch (err) {
|
|
@@ -6863,6 +6816,14 @@ function createStatusRouter(config) {
|
|
|
6863
6816
|
});
|
|
6864
6817
|
return router;
|
|
6865
6818
|
}
|
|
6819
|
+
function serializeRuntimeProcess(processInfo, processManager) {
|
|
6820
|
+
return {
|
|
6821
|
+
...processInfo,
|
|
6822
|
+
startedAt: processInfo.startedAt.toISOString(),
|
|
6823
|
+
lastActiveAt: processInfo.lastActiveAt.toISOString(),
|
|
6824
|
+
busy: processManager.isSessionBusy?.(processInfo.sessionId) ?? processInfo.status === "running"
|
|
6825
|
+
};
|
|
6826
|
+
}
|
|
6866
6827
|
//#endregion
|
|
6867
6828
|
//#region src/gateway/server.ts
|
|
6868
6829
|
const logger$1 = larkLogger("gateway");
|
|
@@ -6926,7 +6887,10 @@ function registerRoutes(app, processManager, scheduledTaskManager, appCredential
|
|
|
6926
6887
|
app.put("/api/scheduled-tasks/:id", notImplemented);
|
|
6927
6888
|
app.delete("/api/scheduled-tasks/:id", notImplemented);
|
|
6928
6889
|
}
|
|
6929
|
-
app.use("/api", createStatusRouter({
|
|
6890
|
+
app.use("/api", createStatusRouter({
|
|
6891
|
+
workspaceRoot,
|
|
6892
|
+
processManager
|
|
6893
|
+
}));
|
|
6930
6894
|
app.use("/hooks", createHooksRouter());
|
|
6931
6895
|
app.use("/api/mcp", createMcpCallbackRouter());
|
|
6932
6896
|
app.use(createChatAuthRouter());
|
|
@@ -7019,7 +6983,7 @@ function createGatewayServer(config) {
|
|
|
7019
6983
|
*
|
|
7020
6984
|
* 持久化文件: /workspace/config/scheduled-tasks.json
|
|
7021
6985
|
*/
|
|
7022
|
-
const log$
|
|
6986
|
+
const log$25 = larkLogger("gateway/scheduler");
|
|
7023
6987
|
/** 持久化文件路径 */
|
|
7024
6988
|
const PERSIST_PATH = "/workspace/config/scheduled-tasks.json";
|
|
7025
6989
|
/**
|
|
@@ -7037,7 +7001,7 @@ var ScheduledTaskManager = class {
|
|
|
7037
7001
|
processManager;
|
|
7038
7002
|
constructor(processManager) {
|
|
7039
7003
|
this.processManager = processManager;
|
|
7040
|
-
log$
|
|
7004
|
+
log$25.info("ScheduledTaskManager 已创建");
|
|
7041
7005
|
}
|
|
7042
7006
|
/**
|
|
7043
7007
|
* 创建定时任务
|
|
@@ -7057,7 +7021,7 @@ var ScheduledTaskManager = class {
|
|
|
7057
7021
|
enabled: true,
|
|
7058
7022
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7059
7023
|
};
|
|
7060
|
-
log$
|
|
7024
|
+
log$25.info("创建定时任务", {
|
|
7061
7025
|
taskId: task.id,
|
|
7062
7026
|
name: task.name,
|
|
7063
7027
|
cron: task.cron,
|
|
@@ -7093,7 +7057,7 @@ var ScheduledTaskManager = class {
|
|
|
7093
7057
|
if (updates.example_output !== void 0) task.example_output = updates.example_output;
|
|
7094
7058
|
if (updates.constraints !== void 0) task.constraints = updates.constraints;
|
|
7095
7059
|
if (updates.enabled !== void 0) task.enabled = updates.enabled;
|
|
7096
|
-
log$
|
|
7060
|
+
log$25.info("更新定时任务", {
|
|
7097
7061
|
taskId: id,
|
|
7098
7062
|
name: task.name,
|
|
7099
7063
|
cronChanged: oldCron !== task.cron,
|
|
@@ -7115,10 +7079,10 @@ var ScheduledTaskManager = class {
|
|
|
7115
7079
|
deleteTask(id) {
|
|
7116
7080
|
const task = this.tasks.get(id);
|
|
7117
7081
|
if (!task) {
|
|
7118
|
-
log$
|
|
7082
|
+
log$25.warn("尝试删除不存在的任务", { taskId: id });
|
|
7119
7083
|
return false;
|
|
7120
7084
|
}
|
|
7121
|
-
log$
|
|
7085
|
+
log$25.info("删除定时任务", {
|
|
7122
7086
|
taskId: id,
|
|
7123
7087
|
name: task.name
|
|
7124
7088
|
});
|
|
@@ -7133,28 +7097,28 @@ var ScheduledTaskManager = class {
|
|
|
7133
7097
|
* 启动时调用,读取 JSON 文件并为每个 enabled 的任务注册 cron。
|
|
7134
7098
|
*/
|
|
7135
7099
|
async loadFromDisk() {
|
|
7136
|
-
log$
|
|
7100
|
+
log$25.info("从磁盘加载定时任务", { path: PERSIST_PATH });
|
|
7137
7101
|
try {
|
|
7138
7102
|
const content = await readFile(PERSIST_PATH, "utf-8");
|
|
7139
7103
|
const data = JSON.parse(content);
|
|
7140
7104
|
if (!Array.isArray(data)) {
|
|
7141
|
-
log$
|
|
7105
|
+
log$25.warn("持久化文件格式异常,跳过加载", { path: PERSIST_PATH });
|
|
7142
7106
|
return;
|
|
7143
7107
|
}
|
|
7144
7108
|
for (const task of data) {
|
|
7145
7109
|
this.tasks.set(task.id, task);
|
|
7146
7110
|
if (task.enabled) this.registerCronJob(task);
|
|
7147
7111
|
}
|
|
7148
|
-
log$
|
|
7112
|
+
log$25.info("定时任务加载完成", {
|
|
7149
7113
|
total: data.length,
|
|
7150
7114
|
enabled: data.filter((t) => t.enabled).length
|
|
7151
7115
|
});
|
|
7152
7116
|
} catch (err) {
|
|
7153
7117
|
if (err.code === "ENOENT") {
|
|
7154
|
-
log$
|
|
7118
|
+
log$25.info("持久化文件不存在,跳过加载(首次启动)", { path: PERSIST_PATH });
|
|
7155
7119
|
return;
|
|
7156
7120
|
}
|
|
7157
|
-
log$
|
|
7121
|
+
log$25.error("加载定时任务失败", {
|
|
7158
7122
|
path: PERSIST_PATH,
|
|
7159
7123
|
error: String(err)
|
|
7160
7124
|
});
|
|
@@ -7170,12 +7134,12 @@ var ScheduledTaskManager = class {
|
|
|
7170
7134
|
try {
|
|
7171
7135
|
await mkdir(dirname(PERSIST_PATH), { recursive: true });
|
|
7172
7136
|
await writeFile(PERSIST_PATH, JSON.stringify(tasks, null, 2), "utf-8");
|
|
7173
|
-
log$
|
|
7137
|
+
log$25.info("定时任务已持久化到磁盘", {
|
|
7174
7138
|
path: PERSIST_PATH,
|
|
7175
7139
|
count: tasks.length
|
|
7176
7140
|
});
|
|
7177
7141
|
} catch (err) {
|
|
7178
|
-
log$
|
|
7142
|
+
log$25.error("持久化定时任务失败", {
|
|
7179
7143
|
path: PERSIST_PATH,
|
|
7180
7144
|
error: String(err)
|
|
7181
7145
|
});
|
|
@@ -7188,13 +7152,13 @@ var ScheduledTaskManager = class {
|
|
|
7188
7152
|
*/
|
|
7189
7153
|
registerCronJob(task) {
|
|
7190
7154
|
this.unregisterCronJob(task.id);
|
|
7191
|
-
log$
|
|
7155
|
+
log$25.info("注册 cron 调度", {
|
|
7192
7156
|
taskId: task.id,
|
|
7193
7157
|
name: task.name,
|
|
7194
7158
|
cron: task.cron
|
|
7195
7159
|
});
|
|
7196
7160
|
const job = cron.schedule(task.cron, () => {
|
|
7197
|
-
log$
|
|
7161
|
+
log$25.info("cron 触发任务执行", {
|
|
7198
7162
|
taskId: task.id,
|
|
7199
7163
|
name: task.name
|
|
7200
7164
|
});
|
|
@@ -7210,7 +7174,7 @@ var ScheduledTaskManager = class {
|
|
|
7210
7174
|
if (job) {
|
|
7211
7175
|
job.stop();
|
|
7212
7176
|
this.cronJobs.delete(taskId);
|
|
7213
|
-
log$
|
|
7177
|
+
log$25.info("已注销 cron 调度", { taskId });
|
|
7214
7178
|
}
|
|
7215
7179
|
}
|
|
7216
7180
|
/**
|
|
@@ -7220,10 +7184,10 @@ var ScheduledTaskManager = class {
|
|
|
7220
7184
|
*/
|
|
7221
7185
|
stopAll() {
|
|
7222
7186
|
const count = this.cronJobs.size;
|
|
7223
|
-
log$
|
|
7187
|
+
log$25.info("停止所有 cron 调度", { count });
|
|
7224
7188
|
for (const [taskId, job] of this.cronJobs) {
|
|
7225
7189
|
job.stop();
|
|
7226
|
-
log$
|
|
7190
|
+
log$25.debug("已停止 cron 调度", { taskId });
|
|
7227
7191
|
}
|
|
7228
7192
|
this.cronJobs.clear();
|
|
7229
7193
|
}
|
|
@@ -7237,17 +7201,17 @@ var ScheduledTaskManager = class {
|
|
|
7237
7201
|
async executeTask(taskId) {
|
|
7238
7202
|
const task = this.tasks.get(taskId);
|
|
7239
7203
|
if (!task) {
|
|
7240
|
-
log$
|
|
7204
|
+
log$25.warn("任务执行时未找到任务", { taskId });
|
|
7241
7205
|
return;
|
|
7242
7206
|
}
|
|
7243
7207
|
if (!task.enabled) {
|
|
7244
|
-
log$
|
|
7208
|
+
log$25.info("任务已禁用,跳过执行", {
|
|
7245
7209
|
taskId,
|
|
7246
7210
|
name: task.name
|
|
7247
7211
|
});
|
|
7248
7212
|
return;
|
|
7249
7213
|
}
|
|
7250
|
-
log$
|
|
7214
|
+
log$25.info("开始执行定时任务", {
|
|
7251
7215
|
taskId: task.id,
|
|
7252
7216
|
name: task.name,
|
|
7253
7217
|
sessionId: task.session_id,
|
|
@@ -7268,7 +7232,7 @@ var ScheduledTaskManager = class {
|
|
|
7268
7232
|
onResult: (result) => {
|
|
7269
7233
|
const summary = result.result ?? resultText;
|
|
7270
7234
|
task.last_result = summary.slice(0, 2e3);
|
|
7271
|
-
log$
|
|
7235
|
+
log$25.info("定时任务执行完成", {
|
|
7272
7236
|
taskId: task.id,
|
|
7273
7237
|
name: task.name,
|
|
7274
7238
|
subtype: result.subtype,
|
|
@@ -7281,7 +7245,7 @@ var ScheduledTaskManager = class {
|
|
|
7281
7245
|
},
|
|
7282
7246
|
onError: (error) => {
|
|
7283
7247
|
task.last_result = `执行失败: ${error.message}`;
|
|
7284
|
-
log$
|
|
7248
|
+
log$25.error("定时任务执行失败", {
|
|
7285
7249
|
taskId: task.id,
|
|
7286
7250
|
name: task.name,
|
|
7287
7251
|
error: error.message
|
|
@@ -7293,7 +7257,7 @@ var ScheduledTaskManager = class {
|
|
|
7293
7257
|
await this.processManager.executePrompt(config, callbacks);
|
|
7294
7258
|
} catch (err) {
|
|
7295
7259
|
task.last_result = `执行异常: ${String(err)}`;
|
|
7296
|
-
log$
|
|
7260
|
+
log$25.error("定时任务执行异常", {
|
|
7297
7261
|
taskId: task.id,
|
|
7298
7262
|
name: task.name,
|
|
7299
7263
|
error: String(err)
|
|
@@ -7322,17 +7286,18 @@ var ScheduledTaskManager = class {
|
|
|
7322
7286
|
* - 会话元数据:{workspaceRoot}/.chat-history/sessions/{userId}.json
|
|
7323
7287
|
* - 消息数据:{workspaceRoot}/.chat-history/messages/{sessionId}.json
|
|
7324
7288
|
*/
|
|
7325
|
-
const log$
|
|
7289
|
+
const log$24 = larkLogger("chat/message-store");
|
|
7326
7290
|
var SessionMessageStore = class {
|
|
7327
7291
|
baseDir;
|
|
7328
7292
|
sessionsDir;
|
|
7329
7293
|
messagesDir;
|
|
7294
|
+
jsonCache = /* @__PURE__ */ new Map();
|
|
7330
7295
|
constructor(workspaceRoot) {
|
|
7331
7296
|
const root = workspaceRoot || resolveWorkspaceRoot();
|
|
7332
7297
|
this.baseDir = getWorkspaceChatHistoryRoot(root);
|
|
7333
7298
|
this.sessionsDir = join(this.baseDir, "sessions");
|
|
7334
7299
|
this.messagesDir = join(this.baseDir, "messages");
|
|
7335
|
-
log$
|
|
7300
|
+
log$24.info("初始化消息存储", { baseDir: this.baseDir });
|
|
7336
7301
|
}
|
|
7337
7302
|
/**
|
|
7338
7303
|
* 创建新会话
|
|
@@ -7345,7 +7310,7 @@ var SessionMessageStore = class {
|
|
|
7345
7310
|
updatedAt: now,
|
|
7346
7311
|
messageCount: 0
|
|
7347
7312
|
};
|
|
7348
|
-
log$
|
|
7313
|
+
log$24.info("创建会话", {
|
|
7349
7314
|
sessionId: fullSession.id,
|
|
7350
7315
|
userId: fullSession.userId
|
|
7351
7316
|
});
|
|
@@ -7358,7 +7323,7 @@ var SessionMessageStore = class {
|
|
|
7358
7323
|
* 获取指定会话
|
|
7359
7324
|
*/
|
|
7360
7325
|
async getSession(sessionId) {
|
|
7361
|
-
log$
|
|
7326
|
+
log$24.debug("获取会话", { sessionId });
|
|
7362
7327
|
try {
|
|
7363
7328
|
const files = await this.listSessionFiles();
|
|
7364
7329
|
for (const file of files) {
|
|
@@ -7368,7 +7333,7 @@ var SessionMessageStore = class {
|
|
|
7368
7333
|
if (found) return found;
|
|
7369
7334
|
}
|
|
7370
7335
|
} catch (err) {
|
|
7371
|
-
log$
|
|
7336
|
+
log$24.error("获取会话失败", {
|
|
7372
7337
|
sessionId,
|
|
7373
7338
|
error: String(err)
|
|
7374
7339
|
});
|
|
@@ -7379,14 +7344,14 @@ var SessionMessageStore = class {
|
|
|
7379
7344
|
* 列出指定用户的所有会话
|
|
7380
7345
|
*/
|
|
7381
7346
|
async listSessions(userId) {
|
|
7382
|
-
log$
|
|
7347
|
+
log$24.debug("列出用户会话", { userId });
|
|
7383
7348
|
return await this.readUserSessions(userId);
|
|
7384
7349
|
}
|
|
7385
7350
|
/**
|
|
7386
7351
|
* 删除指定会话(同时删除消息文件)
|
|
7387
7352
|
*/
|
|
7388
7353
|
async deleteSession(sessionId) {
|
|
7389
|
-
log$
|
|
7354
|
+
log$24.info("删除会话", { sessionId });
|
|
7390
7355
|
try {
|
|
7391
7356
|
const files = await this.listSessionFiles();
|
|
7392
7357
|
for (const file of files) {
|
|
@@ -7401,9 +7366,9 @@ var SessionMessageStore = class {
|
|
|
7401
7366
|
}
|
|
7402
7367
|
const msgFile = this.getMessageFilePath(sessionId);
|
|
7403
7368
|
await this.writeJsonFile(msgFile, []);
|
|
7404
|
-
log$
|
|
7369
|
+
log$24.info("会话删除完成", { sessionId });
|
|
7405
7370
|
} catch (err) {
|
|
7406
|
-
log$
|
|
7371
|
+
log$24.error("删除会话失败", {
|
|
7407
7372
|
sessionId,
|
|
7408
7373
|
error: String(err)
|
|
7409
7374
|
});
|
|
@@ -7420,7 +7385,7 @@ var SessionMessageStore = class {
|
|
|
7420
7385
|
id: v4(),
|
|
7421
7386
|
timestamp: Date.now()
|
|
7422
7387
|
};
|
|
7423
|
-
log$
|
|
7388
|
+
log$24.info("追加消息", {
|
|
7424
7389
|
messageId: fullMessage.id,
|
|
7425
7390
|
sessionId: fullMessage.sessionId,
|
|
7426
7391
|
role: fullMessage.role
|
|
@@ -7431,9 +7396,9 @@ var SessionMessageStore = class {
|
|
|
7431
7396
|
messages.push(fullMessage);
|
|
7432
7397
|
await this.writeJsonFile(msgFile, messages);
|
|
7433
7398
|
await this.updateSessionMeta(fullMessage.sessionId, messages.length);
|
|
7434
|
-
log$
|
|
7399
|
+
log$24.debug("消息追加完成", { messageId: fullMessage.id });
|
|
7435
7400
|
} catch (err) {
|
|
7436
|
-
log$
|
|
7401
|
+
log$24.error("追加消息失败", {
|
|
7437
7402
|
sessionId: fullMessage.sessionId,
|
|
7438
7403
|
error: String(err)
|
|
7439
7404
|
});
|
|
@@ -7448,7 +7413,7 @@ var SessionMessageStore = class {
|
|
|
7448
7413
|
async getMessages(sessionId, options) {
|
|
7449
7414
|
const limit = options?.limit ?? 50;
|
|
7450
7415
|
const cursor = options?.cursor;
|
|
7451
|
-
log$
|
|
7416
|
+
log$24.debug("获取消息列表", {
|
|
7452
7417
|
sessionId,
|
|
7453
7418
|
cursor,
|
|
7454
7419
|
limit
|
|
@@ -7467,7 +7432,7 @@ var SessionMessageStore = class {
|
|
|
7467
7432
|
nextCursor: startIdx + limit < allMessages.length ? sliced[sliced.length - 1]?.id : void 0
|
|
7468
7433
|
};
|
|
7469
7434
|
} catch (err) {
|
|
7470
|
-
log$
|
|
7435
|
+
log$24.error("获取消息列表失败", {
|
|
7471
7436
|
sessionId,
|
|
7472
7437
|
error: String(err)
|
|
7473
7438
|
});
|
|
@@ -7478,12 +7443,12 @@ var SessionMessageStore = class {
|
|
|
7478
7443
|
* 获取会话消息总数
|
|
7479
7444
|
*/
|
|
7480
7445
|
async getMessageCount(sessionId) {
|
|
7481
|
-
log$
|
|
7446
|
+
log$24.debug("获取消息数量", { sessionId });
|
|
7482
7447
|
try {
|
|
7483
7448
|
const msgFile = this.getMessageFilePath(sessionId);
|
|
7484
7449
|
return (await this.readJsonFile(msgFile) || []).length;
|
|
7485
7450
|
} catch (err) {
|
|
7486
|
-
log$
|
|
7451
|
+
log$24.error("获取消息数量失败", {
|
|
7487
7452
|
sessionId,
|
|
7488
7453
|
error: String(err)
|
|
7489
7454
|
});
|
|
@@ -7534,7 +7499,7 @@ var SessionMessageStore = class {
|
|
|
7534
7499
|
}
|
|
7535
7500
|
}
|
|
7536
7501
|
} catch (err) {
|
|
7537
|
-
log$
|
|
7502
|
+
log$24.warn("更新会话元数据失败", {
|
|
7538
7503
|
sessionId,
|
|
7539
7504
|
error: String(err)
|
|
7540
7505
|
});
|
|
@@ -7549,7 +7514,7 @@ var SessionMessageStore = class {
|
|
|
7549
7514
|
await mkdir(this.sessionsDir, { recursive: true });
|
|
7550
7515
|
return (await readdir(this.sessionsDir)).filter((e) => e.endsWith(".json")).map((e) => join(this.sessionsDir, e));
|
|
7551
7516
|
} catch (err) {
|
|
7552
|
-
log$
|
|
7517
|
+
log$24.error("列出会话索引文件失败", { error: String(err) });
|
|
7553
7518
|
return [];
|
|
7554
7519
|
}
|
|
7555
7520
|
}
|
|
@@ -7559,13 +7524,19 @@ var SessionMessageStore = class {
|
|
|
7559
7524
|
async readJsonFile(filePath) {
|
|
7560
7525
|
try {
|
|
7561
7526
|
const raw = await readFile(filePath, "utf-8");
|
|
7562
|
-
|
|
7527
|
+
const parsed = JSON.parse(raw);
|
|
7528
|
+
this.jsonCache.set(filePath, parsed);
|
|
7529
|
+
return parsed;
|
|
7563
7530
|
} catch (err) {
|
|
7564
7531
|
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
|
|
7565
|
-
log$
|
|
7532
|
+
log$24.error("读取 JSON 文件失败", {
|
|
7566
7533
|
filePath,
|
|
7567
7534
|
error: String(err)
|
|
7568
7535
|
});
|
|
7536
|
+
if (this.jsonCache.has(filePath)) {
|
|
7537
|
+
log$24.warn("读取 JSON 文件失败,使用内存中的上一份完整快照", { filePath });
|
|
7538
|
+
return this.jsonCache.get(filePath);
|
|
7539
|
+
}
|
|
7569
7540
|
return null;
|
|
7570
7541
|
}
|
|
7571
7542
|
}
|
|
@@ -7573,18 +7544,39 @@ var SessionMessageStore = class {
|
|
|
7573
7544
|
* 通用 JSON 文件写入(自动创建目录)
|
|
7574
7545
|
*/
|
|
7575
7546
|
async writeJsonFile(filePath, data) {
|
|
7547
|
+
const dir = dirname(filePath);
|
|
7548
|
+
const tmpPath = join(dir, `.${basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
7576
7549
|
try {
|
|
7577
|
-
|
|
7578
|
-
await
|
|
7579
|
-
|
|
7550
|
+
await mkdir(dir, { recursive: true });
|
|
7551
|
+
const handle = await open(tmpPath, "w");
|
|
7552
|
+
try {
|
|
7553
|
+
await handle.writeFile(JSON.stringify(data, null, 2), "utf-8");
|
|
7554
|
+
await handle.sync();
|
|
7555
|
+
} finally {
|
|
7556
|
+
await handle.close();
|
|
7557
|
+
}
|
|
7558
|
+
await rename(tmpPath, filePath);
|
|
7559
|
+
this.jsonCache.set(filePath, data);
|
|
7560
|
+
await this.fsyncDirectory(dir);
|
|
7580
7561
|
} catch (err) {
|
|
7581
|
-
log$
|
|
7562
|
+
log$24.error("写入 JSON 文件失败", {
|
|
7582
7563
|
filePath,
|
|
7583
7564
|
error: String(err)
|
|
7584
7565
|
});
|
|
7566
|
+
await unlink(tmpPath).catch(() => void 0);
|
|
7585
7567
|
throw err;
|
|
7586
7568
|
}
|
|
7587
7569
|
}
|
|
7570
|
+
async fsyncDirectory(dir) {
|
|
7571
|
+
try {
|
|
7572
|
+
const handle = await open(dir, "r");
|
|
7573
|
+
try {
|
|
7574
|
+
await handle.sync();
|
|
7575
|
+
} finally {
|
|
7576
|
+
await handle.close();
|
|
7577
|
+
}
|
|
7578
|
+
} catch {}
|
|
7579
|
+
}
|
|
7588
7580
|
};
|
|
7589
7581
|
//#endregion
|
|
7590
7582
|
//#region src/messaging/inbound/user-name-cache-store.ts
|
|
@@ -7859,7 +7851,7 @@ function getUserAgent() {
|
|
|
7859
7851
|
* - `LarkClient.fromCredentials(credentials)` — ephemeral instance (not cached)
|
|
7860
7852
|
* - `LarkClient.fromProvider(provider, opts)` — from ICredentialProvider (not cached)
|
|
7861
7853
|
*/
|
|
7862
|
-
const log$
|
|
7854
|
+
const log$22 = larkLogger("core/lark-client");
|
|
7863
7855
|
const GLOBAL_LARK_USER_AGENT_KEY = "LARK_USER_AGENT";
|
|
7864
7856
|
function installGlobalUserAgent() {
|
|
7865
7857
|
globalThis[GLOBAL_LARK_USER_AGENT_KEY] = getUserAgent();
|
|
@@ -7951,7 +7943,7 @@ var LarkClient = class LarkClient {
|
|
|
7951
7943
|
const existing = cache.get(account.accountId);
|
|
7952
7944
|
if (existing && existing.account.appId === account.appId && credentialsEqual(existing.account.appSecret, account.appSecret)) return existing;
|
|
7953
7945
|
if (existing) {
|
|
7954
|
-
log$
|
|
7946
|
+
log$22.info(`credentials changed, disposing stale instance`, { accountId: account.accountId });
|
|
7955
7947
|
existing.dispose();
|
|
7956
7948
|
}
|
|
7957
7949
|
const instance = new LarkClient(account);
|
|
@@ -7994,7 +7986,7 @@ var LarkClient = class LarkClient {
|
|
|
7994
7986
|
static fromProvider(provider, opts) {
|
|
7995
7987
|
const appId = provider.getAppId();
|
|
7996
7988
|
const appSecret = provider.getAppSecret();
|
|
7997
|
-
log$
|
|
7989
|
+
log$22.info("通过 ICredentialProvider 创建 LarkClient", {
|
|
7998
7990
|
appId,
|
|
7999
7991
|
accountId: opts?.accountId ?? "default",
|
|
8000
7992
|
brand: opts?.brand ?? "feishu"
|
|
@@ -8030,7 +8022,7 @@ var LarkClient = class LarkClient {
|
|
|
8030
8022
|
get sdk() {
|
|
8031
8023
|
if (!this._sdk) {
|
|
8032
8024
|
const { appId, appSecret } = this.requireCredentials();
|
|
8033
|
-
log$
|
|
8025
|
+
log$22.info("创建 Lark SDK 客户端实例", {
|
|
8034
8026
|
accountId: this.accountId,
|
|
8035
8027
|
appId,
|
|
8036
8028
|
brand: this.account.brand
|
|
@@ -8045,7 +8037,7 @@ var LarkClient = class LarkClient {
|
|
|
8045
8037
|
if (sdkAny.httpInstance?.interceptors) {
|
|
8046
8038
|
const accountId = this.accountId;
|
|
8047
8039
|
sdkAny.httpInstance.interceptors.request.use((req) => {
|
|
8048
|
-
log$
|
|
8040
|
+
log$22.debug("飞书 API 请求", {
|
|
8049
8041
|
accountId,
|
|
8050
8042
|
method: req.method,
|
|
8051
8043
|
url: req.url,
|
|
@@ -8055,7 +8047,7 @@ var LarkClient = class LarkClient {
|
|
|
8055
8047
|
return req;
|
|
8056
8048
|
}, void 0, { synchronous: true });
|
|
8057
8049
|
sdkAny.httpInstance.interceptors.response.use((res) => {
|
|
8058
|
-
log$
|
|
8050
|
+
log$22.debug("飞书 API 响应", {
|
|
8059
8051
|
accountId,
|
|
8060
8052
|
code: res?.code,
|
|
8061
8053
|
msg: res?.msg,
|
|
@@ -8063,7 +8055,7 @@ var LarkClient = class LarkClient {
|
|
|
8063
8055
|
});
|
|
8064
8056
|
return res;
|
|
8065
8057
|
}, (err) => {
|
|
8066
|
-
log$
|
|
8058
|
+
log$22.error("飞书 API 请求失败", {
|
|
8067
8059
|
accountId,
|
|
8068
8060
|
error: err instanceof Error ? err.message : String(err),
|
|
8069
8061
|
url: err?.config?.url
|
|
@@ -8150,7 +8142,7 @@ var LarkClient = class LarkClient {
|
|
|
8150
8142
|
dispatcher.register(handlers);
|
|
8151
8143
|
const { appId, appSecret } = this.requireCredentials();
|
|
8152
8144
|
if (this._wsClient) {
|
|
8153
|
-
log$
|
|
8145
|
+
log$22.warn(`closing previous WSClient before reconnect`, { accountId: this.accountId });
|
|
8154
8146
|
try {
|
|
8155
8147
|
this._wsClient.close({ force: true });
|
|
8156
8148
|
} catch {}
|
|
@@ -8167,7 +8159,7 @@ var LarkClient = class LarkClient {
|
|
|
8167
8159
|
wsClientAny.handleEventData = (data) => {
|
|
8168
8160
|
const msgType = data.headers?.find?.((h) => h.key === "type")?.value;
|
|
8169
8161
|
const eventType = data.headers?.find?.((h) => h.key === "event_type")?.value;
|
|
8170
|
-
log$
|
|
8162
|
+
log$22.info("WS 收到原始事件", {
|
|
8171
8163
|
accountId: this.accountId,
|
|
8172
8164
|
msgType,
|
|
8173
8165
|
eventType,
|
|
@@ -8193,14 +8185,14 @@ var LarkClient = class LarkClient {
|
|
|
8193
8185
|
/** Disconnect WebSocket but keep instance in cache. */
|
|
8194
8186
|
disconnect() {
|
|
8195
8187
|
if (this._wsClient) {
|
|
8196
|
-
log$
|
|
8188
|
+
log$22.info(`disconnecting WebSocket`, { accountId: this.accountId });
|
|
8197
8189
|
try {
|
|
8198
8190
|
this._wsClient.close({ force: true });
|
|
8199
8191
|
} catch {}
|
|
8200
8192
|
}
|
|
8201
8193
|
this._wsClient = null;
|
|
8202
8194
|
if (this.messageDedup) {
|
|
8203
|
-
log$
|
|
8195
|
+
log$22.info(`disposing message dedup`, {
|
|
8204
8196
|
accountId: this.accountId,
|
|
8205
8197
|
size: this.messageDedup.size
|
|
8206
8198
|
});
|
|
@@ -8506,7 +8498,7 @@ function sortTraceValue(value) {
|
|
|
8506
8498
|
}
|
|
8507
8499
|
//#endregion
|
|
8508
8500
|
//#region src/card/cc-stream-bridge.ts
|
|
8509
|
-
const log$
|
|
8501
|
+
const log$21 = larkLogger("card/cc-stream-bridge");
|
|
8510
8502
|
const CC_INTERNAL_PLACEHOLDER = "No response requested.";
|
|
8511
8503
|
/**
|
|
8512
8504
|
* CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
|
|
@@ -8531,7 +8523,7 @@ var CCStreamBridge = class {
|
|
|
8531
8523
|
sessionKey: options?.sessionKey
|
|
8532
8524
|
};
|
|
8533
8525
|
if (this.options.sessionKey) startToolUseTraceRun(this.options.sessionKey);
|
|
8534
|
-
log$
|
|
8526
|
+
log$21.info("CCStreamBridge 初始化", {
|
|
8535
8527
|
autoCompleteOnTurnEnd: this.options.autoCompleteOnTurnEnd,
|
|
8536
8528
|
sessionKey: this.options.sessionKey ?? "(none)"
|
|
8537
8529
|
});
|
|
@@ -8539,7 +8531,7 @@ var CCStreamBridge = class {
|
|
|
8539
8531
|
/** 文本增量 → 累积后调用 controller.onPartialReply */
|
|
8540
8532
|
onTextDelta(text) {
|
|
8541
8533
|
this.accumulatedText += text;
|
|
8542
|
-
log$
|
|
8534
|
+
log$21.debug("textDelta 事件", {
|
|
8543
8535
|
deltaLen: text.length,
|
|
8544
8536
|
totalLen: this.accumulatedText.length
|
|
8545
8537
|
});
|
|
@@ -8548,7 +8540,7 @@ var CCStreamBridge = class {
|
|
|
8548
8540
|
/** 思考增量 → 累积后调用 controller.onReasoningStream */
|
|
8549
8541
|
onThinkingDelta(text) {
|
|
8550
8542
|
this.accumulatedThinkingText += text;
|
|
8551
|
-
log$
|
|
8543
|
+
log$21.debug("thinkingDelta 事件", {
|
|
8552
8544
|
deltaLen: text.length,
|
|
8553
8545
|
totalLen: this.accumulatedThinkingText.length
|
|
8554
8546
|
});
|
|
@@ -8560,7 +8552,7 @@ var CCStreamBridge = class {
|
|
|
8560
8552
|
const displayName = getToolDisplayName(toolName);
|
|
8561
8553
|
const toolParams = typeof _toolInput === "object" && _toolInput != null ? _toolInput : void 0;
|
|
8562
8554
|
const hasParams = toolParams && Object.keys(toolParams).length > 0;
|
|
8563
|
-
log$
|
|
8555
|
+
log$21.info("toolUseStart 事件", {
|
|
8564
8556
|
toolName,
|
|
8565
8557
|
displayName,
|
|
8566
8558
|
activeToolsCount: this.activeTools.size,
|
|
@@ -8573,7 +8565,7 @@ var CCStreamBridge = class {
|
|
|
8573
8565
|
toolName,
|
|
8574
8566
|
toolParams
|
|
8575
8567
|
})) {
|
|
8576
|
-
log$
|
|
8568
|
+
log$21.info("toolUseStart: 更新已有步骤的 params", { toolName });
|
|
8577
8569
|
this.controller.onToolStart({
|
|
8578
8570
|
name: toolName,
|
|
8579
8571
|
phase: "start"
|
|
@@ -8597,7 +8589,7 @@ var CCStreamBridge = class {
|
|
|
8597
8589
|
const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
|
|
8598
8590
|
const displayName = getToolDisplayName(toolName);
|
|
8599
8591
|
this.activeTools.delete(toolUseId);
|
|
8600
|
-
log$
|
|
8592
|
+
log$21.info("toolResult 事件", {
|
|
8601
8593
|
toolUseId,
|
|
8602
8594
|
toolName,
|
|
8603
8595
|
displayName,
|
|
@@ -8612,7 +8604,7 @@ var CCStreamBridge = class {
|
|
|
8612
8604
|
}
|
|
8613
8605
|
/** 工具执行进度 → 记录日志 */
|
|
8614
8606
|
onToolProgress(toolName, elapsedSeconds) {
|
|
8615
|
-
log$
|
|
8607
|
+
log$21.debug("toolProgress 事件", {
|
|
8616
8608
|
toolName,
|
|
8617
8609
|
displayName: getToolDisplayName(toolName),
|
|
8618
8610
|
elapsedSeconds
|
|
@@ -8620,7 +8612,7 @@ var CCStreamBridge = class {
|
|
|
8620
8612
|
}
|
|
8621
8613
|
/** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
|
|
8622
8614
|
onTurnEnd(stopReason) {
|
|
8623
|
-
log$
|
|
8615
|
+
log$21.info("turnEnd 事件", {
|
|
8624
8616
|
stopReason,
|
|
8625
8617
|
accumulatedTextLen: this.accumulatedText.length,
|
|
8626
8618
|
accumulatedThinkingTextLen: this.accumulatedThinkingText.length
|
|
@@ -8628,7 +8620,7 @@ var CCStreamBridge = class {
|
|
|
8628
8620
|
if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
|
|
8629
8621
|
const trimmedText = this.accumulatedText.trim();
|
|
8630
8622
|
if (trimmedText === CC_INTERNAL_PLACEHOLDER) {
|
|
8631
|
-
log$
|
|
8623
|
+
log$21.info("检测到 CC 内部占位消息,静默丢弃", {
|
|
8632
8624
|
text: trimmedText.slice(0, 50),
|
|
8633
8625
|
sessionKey: this.options.sessionKey
|
|
8634
8626
|
});
|
|
@@ -8636,7 +8628,7 @@ var CCStreamBridge = class {
|
|
|
8636
8628
|
return;
|
|
8637
8629
|
}
|
|
8638
8630
|
if (trimmedText === "") {
|
|
8639
|
-
log$
|
|
8631
|
+
log$21.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
|
|
8640
8632
|
return;
|
|
8641
8633
|
}
|
|
8642
8634
|
this.controller.markFullyComplete();
|
|
@@ -8645,7 +8637,7 @@ var CCStreamBridge = class {
|
|
|
8645
8637
|
}
|
|
8646
8638
|
/** 最终结果 → 兜底最终化 + 错误处理 */
|
|
8647
8639
|
onResult(data) {
|
|
8648
|
-
log$
|
|
8640
|
+
log$21.info("result 事件", {
|
|
8649
8641
|
subtype: data.subtype,
|
|
8650
8642
|
isError: data.isError,
|
|
8651
8643
|
durationMs: data.durationMs,
|
|
@@ -8654,7 +8646,7 @@ var CCStreamBridge = class {
|
|
|
8654
8646
|
resultLen: data.result?.length ?? 0
|
|
8655
8647
|
});
|
|
8656
8648
|
if (data.aborted) {
|
|
8657
|
-
log$
|
|
8649
|
+
log$21.info("运行时报告用户主动中断,按正常完成处理", {
|
|
8658
8650
|
subtype: data.subtype,
|
|
8659
8651
|
sessionId: this.options.sessionKey
|
|
8660
8652
|
});
|
|
@@ -8665,7 +8657,7 @@ var CCStreamBridge = class {
|
|
|
8665
8657
|
const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
|
|
8666
8658
|
const sessionId = this.options.sessionKey;
|
|
8667
8659
|
if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
|
|
8668
|
-
log$
|
|
8660
|
+
log$21.info("用户主动中断,按正常完成处理", {
|
|
8669
8661
|
subtype: data.subtype,
|
|
8670
8662
|
sessionId
|
|
8671
8663
|
});
|
|
@@ -8674,14 +8666,14 @@ var CCStreamBridge = class {
|
|
|
8674
8666
|
this.controller.onIdle();
|
|
8675
8667
|
} else {
|
|
8676
8668
|
const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
|
|
8677
|
-
log$
|
|
8669
|
+
log$21.error("CC 执行返回错误", {
|
|
8678
8670
|
subtype: data.subtype,
|
|
8679
8671
|
errorMessage: errorMessage.slice(0, 200)
|
|
8680
8672
|
});
|
|
8681
8673
|
this.controller.onError(new Error(errorMessage), { kind: "cc-execution" });
|
|
8682
8674
|
}
|
|
8683
8675
|
} else if (!this.accumulatedText.trim()) if (data.result) {
|
|
8684
|
-
log$
|
|
8676
|
+
log$21.info("result 兜底:使用 result.result 作为最终回复", {
|
|
8685
8677
|
resultLen: data.result.length,
|
|
8686
8678
|
sessionKey: this.options.sessionKey
|
|
8687
8679
|
});
|
|
@@ -8689,7 +8681,7 @@ var CCStreamBridge = class {
|
|
|
8689
8681
|
this.controller.markFullyComplete();
|
|
8690
8682
|
this.controller.onIdle();
|
|
8691
8683
|
} else {
|
|
8692
|
-
log$
|
|
8684
|
+
log$21.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
|
|
8693
8685
|
this.controller.abortCard();
|
|
8694
8686
|
}
|
|
8695
8687
|
else {
|
|
@@ -8703,7 +8695,7 @@ var CCStreamBridge = class {
|
|
|
8703
8695
|
* 内部复用上面的直接调用方法。
|
|
8704
8696
|
*/
|
|
8705
8697
|
bindParser(parser) {
|
|
8706
|
-
log$
|
|
8698
|
+
log$21.info("绑定 CCStreamParser 事件到卡片控制器");
|
|
8707
8699
|
parser.on("textDelta", (text) => this.onTextDelta(text));
|
|
8708
8700
|
parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
|
|
8709
8701
|
parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
|
|
@@ -8711,7 +8703,7 @@ var CCStreamBridge = class {
|
|
|
8711
8703
|
parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
|
|
8712
8704
|
parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
|
|
8713
8705
|
parser.on("result", (data) => this.onResult(data));
|
|
8714
|
-
log$
|
|
8706
|
+
log$21.info("CCStreamParser 事件绑定完成");
|
|
8715
8707
|
}
|
|
8716
8708
|
};
|
|
8717
8709
|
//#endregion
|
|
@@ -10730,7 +10722,7 @@ function resolveLarkSdk(cfg, accountId) {
|
|
|
10730
10722
|
if (cached) return cached.sdk;
|
|
10731
10723
|
return LarkClient.fromCfg(cfg, accountId).sdk;
|
|
10732
10724
|
}
|
|
10733
|
-
const log$
|
|
10725
|
+
const log$20 = larkLogger("card/cardkit");
|
|
10734
10726
|
/**
|
|
10735
10727
|
* 记录 CardKit API 响应日志,检测错误码并抛出异常。
|
|
10736
10728
|
*
|
|
@@ -10740,13 +10732,13 @@ const log$19 = larkLogger("card/cardkit");
|
|
|
10740
10732
|
function logCardKitResponse(params) {
|
|
10741
10733
|
const { resp, api, context } = params;
|
|
10742
10734
|
const { code, msg } = resp;
|
|
10743
|
-
log$
|
|
10735
|
+
log$20.info(`cardkit ${api} response`, {
|
|
10744
10736
|
code,
|
|
10745
10737
|
msg,
|
|
10746
10738
|
context
|
|
10747
10739
|
});
|
|
10748
10740
|
if (code && code !== 0) {
|
|
10749
|
-
log$
|
|
10741
|
+
log$20.warn(`cardkit ${api} FAILED`, {
|
|
10750
10742
|
code,
|
|
10751
10743
|
msg,
|
|
10752
10744
|
context,
|
|
@@ -11157,7 +11149,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
|
|
|
11157
11149
|
* Feishu messages, uploading media to the Feishu IM storage, and
|
|
11158
11150
|
* sending image / file messages to chats.
|
|
11159
11151
|
*/
|
|
11160
|
-
const log$
|
|
11152
|
+
const log$19 = larkLogger("outbound/media");
|
|
11161
11153
|
/**
|
|
11162
11154
|
* Upload an image to Feishu IM storage.
|
|
11163
11155
|
*
|
|
@@ -11225,7 +11217,7 @@ async function validateRemoteUrl(raw) {
|
|
|
11225
11217
|
for (const addr of addresses) if (isPrivateIP(addr)) throw new Error(`[feishu-media] Domain "${hostname}" resolves to private/reserved IP "${addr}" (SSRF protection). URL: "${raw}"`);
|
|
11226
11218
|
} catch (err) {
|
|
11227
11219
|
if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
|
|
11228
|
-
log$
|
|
11220
|
+
log$19.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
|
|
11229
11221
|
}
|
|
11230
11222
|
}
|
|
11231
11223
|
/**
|
|
@@ -11243,21 +11235,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
|
|
|
11243
11235
|
if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
|
|
11244
11236
|
else throw new Error(`[feishu-media] Local file access denied for "${filePath}": mediaLocalRoots is not configured. Configure mediaLocalRoots to explicitly allow local file access.`);
|
|
11245
11237
|
const buf = fs.readFileSync(filePath);
|
|
11246
|
-
log$
|
|
11238
|
+
log$19.debug(`local file read: "${filePath}", ${buf.length} bytes`);
|
|
11247
11239
|
return buf;
|
|
11248
11240
|
}
|
|
11249
11241
|
await validateRemoteUrl(raw);
|
|
11250
11242
|
const FETCH_TIMEOUT_MS = 3e4;
|
|
11251
|
-
log$
|
|
11243
|
+
log$19.info(`fetching remote media: ${raw}`);
|
|
11252
11244
|
const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
11253
11245
|
if (!response.ok) throw new Error(`[feishu-media] Failed to fetch media from "${raw}": HTTP ${response.status} ${response.statusText}. Verify the URL is accessible and returns a valid media resource.`);
|
|
11254
11246
|
const arrayBuffer = await response.arrayBuffer();
|
|
11255
|
-
log$
|
|
11247
|
+
log$19.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
|
|
11256
11248
|
return Buffer.from(arrayBuffer);
|
|
11257
11249
|
}
|
|
11258
11250
|
//#endregion
|
|
11259
11251
|
//#region src/card/image-resolver.ts
|
|
11260
|
-
const log$
|
|
11252
|
+
const log$18 = larkLogger("card/image-resolver");
|
|
11261
11253
|
/** Matches complete markdown image syntax: `` */
|
|
11262
11254
|
const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
|
|
11263
11255
|
var ImageResolver = class {
|
|
@@ -11303,14 +11295,14 @@ var ImageResolver = class {
|
|
|
11303
11295
|
async resolveImagesAwait(text, timeoutMs) {
|
|
11304
11296
|
this.resolveImages(text);
|
|
11305
11297
|
if (this.pending.size > 0) {
|
|
11306
|
-
log$
|
|
11298
|
+
log$18.info("resolveImagesAwait: waiting for uploads", {
|
|
11307
11299
|
count: this.pending.size,
|
|
11308
11300
|
timeoutMs
|
|
11309
11301
|
});
|
|
11310
11302
|
const allUploads = Promise.all(this.pending.values());
|
|
11311
11303
|
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
11312
11304
|
await Promise.race([allUploads, timeout]);
|
|
11313
|
-
if (this.pending.size > 0) log$
|
|
11305
|
+
if (this.pending.size > 0) log$18.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
|
|
11314
11306
|
}
|
|
11315
11307
|
return this.resolveImages(text);
|
|
11316
11308
|
}
|
|
@@ -11320,7 +11312,7 @@ var ImageResolver = class {
|
|
|
11320
11312
|
}
|
|
11321
11313
|
async doUpload(url) {
|
|
11322
11314
|
try {
|
|
11323
|
-
log$
|
|
11315
|
+
log$18.info("uploading image", { url });
|
|
11324
11316
|
const buffer = await fetchRemoteImageBuffer(url);
|
|
11325
11317
|
const { imageKey } = await uploadImageLark({
|
|
11326
11318
|
cfg: this.cfg,
|
|
@@ -11328,7 +11320,7 @@ var ImageResolver = class {
|
|
|
11328
11320
|
imageType: "message",
|
|
11329
11321
|
accountId: this.accountId
|
|
11330
11322
|
});
|
|
11331
|
-
log$
|
|
11323
|
+
log$18.info("image uploaded", {
|
|
11332
11324
|
url,
|
|
11333
11325
|
imageKey
|
|
11334
11326
|
});
|
|
@@ -11337,7 +11329,7 @@ var ImageResolver = class {
|
|
|
11337
11329
|
this.onImageResolved();
|
|
11338
11330
|
return imageKey;
|
|
11339
11331
|
} catch (err) {
|
|
11340
|
-
log$
|
|
11332
|
+
log$18.warn("image upload failed", {
|
|
11341
11333
|
url,
|
|
11342
11334
|
error: String(err)
|
|
11343
11335
|
});
|
|
@@ -11358,7 +11350,7 @@ var ImageResolver = class {
|
|
|
11358
11350
|
* Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
|
|
11359
11351
|
* logic previously scattered as closures in reply-dispatcher.ts.
|
|
11360
11352
|
*/
|
|
11361
|
-
const log$
|
|
11353
|
+
const log$17 = larkLogger("card/unavailable-guard");
|
|
11362
11354
|
var UnavailableGuard = class {
|
|
11363
11355
|
terminated = false;
|
|
11364
11356
|
replyToMessageId;
|
|
@@ -11411,7 +11403,7 @@ var UnavailableGuard = class {
|
|
|
11411
11403
|
this.terminated = true;
|
|
11412
11404
|
this.onTerminate();
|
|
11413
11405
|
const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
|
|
11414
|
-
log$
|
|
11406
|
+
log$17.warn("reply pipeline terminated by unavailable message", {
|
|
11415
11407
|
source,
|
|
11416
11408
|
apiCode,
|
|
11417
11409
|
messageId: affectedMessageId
|
|
@@ -11448,7 +11440,7 @@ function getActiveCard(sessionId) {
|
|
|
11448
11440
|
* Delegates throttling to FlushController and message-unavailable
|
|
11449
11441
|
* detection to UnavailableGuard.
|
|
11450
11442
|
*/
|
|
11451
|
-
const log$
|
|
11443
|
+
const log$16 = larkLogger("card/streaming");
|
|
11452
11444
|
var StreamingCardController = class StreamingCardController {
|
|
11453
11445
|
phase = "idle";
|
|
11454
11446
|
cardKit = {
|
|
@@ -11536,7 +11528,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11536
11528
|
}
|
|
11537
11529
|
}
|
|
11538
11530
|
if (!entry) {
|
|
11539
|
-
log$
|
|
11531
|
+
log$16.debug("footer metrics lookup: session entry missing", {
|
|
11540
11532
|
sessionKey: this.deps.sessionKey,
|
|
11541
11533
|
candidateKeys,
|
|
11542
11534
|
storePath,
|
|
@@ -11554,7 +11546,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11554
11546
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
11555
11547
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
11556
11548
|
};
|
|
11557
|
-
log$
|
|
11549
|
+
log$16.debug("footer metrics lookup: session entry found", {
|
|
11558
11550
|
sessionKey: this.deps.sessionKey,
|
|
11559
11551
|
matchedKey,
|
|
11560
11552
|
storePath,
|
|
@@ -11579,7 +11571,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11579
11571
|
}
|
|
11580
11572
|
}
|
|
11581
11573
|
if (!entry) {
|
|
11582
|
-
log$
|
|
11574
|
+
log$16.debug("footer metrics lookup: session entry missing", {
|
|
11583
11575
|
sessionKey: this.deps.sessionKey,
|
|
11584
11576
|
candidateKeys,
|
|
11585
11577
|
storePath,
|
|
@@ -11597,7 +11589,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11597
11589
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
11598
11590
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
11599
11591
|
};
|
|
11600
|
-
log$
|
|
11592
|
+
log$16.debug("footer metrics lookup: session entry found", {
|
|
11601
11593
|
sessionKey: this.deps.sessionKey,
|
|
11602
11594
|
matchedKey,
|
|
11603
11595
|
storePath,
|
|
@@ -11605,7 +11597,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11605
11597
|
});
|
|
11606
11598
|
return metrics;
|
|
11607
11599
|
} catch (err) {
|
|
11608
|
-
log$
|
|
11600
|
+
log$16.warn("footer metrics lookup failed", {
|
|
11609
11601
|
error: String(err),
|
|
11610
11602
|
sessionKey: this.deps.sessionKey
|
|
11611
11603
|
});
|
|
@@ -11712,7 +11704,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11712
11704
|
const from = this.phase;
|
|
11713
11705
|
if (from === to) return false;
|
|
11714
11706
|
if (!PHASE_TRANSITIONS[from].has(to)) {
|
|
11715
|
-
log$
|
|
11707
|
+
log$16.warn("phase transition rejected", {
|
|
11716
11708
|
from,
|
|
11717
11709
|
to,
|
|
11718
11710
|
source
|
|
@@ -11720,7 +11712,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11720
11712
|
return false;
|
|
11721
11713
|
}
|
|
11722
11714
|
this.phase = to;
|
|
11723
|
-
log$
|
|
11715
|
+
log$16.info("phase transition", {
|
|
11724
11716
|
from,
|
|
11725
11717
|
to,
|
|
11726
11718
|
source,
|
|
@@ -11840,7 +11832,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11840
11832
|
this.reasoning.dirty = true;
|
|
11841
11833
|
}
|
|
11842
11834
|
const text = split.answerText ?? stripReasoningTags(rawText);
|
|
11843
|
-
log$
|
|
11835
|
+
log$16.debug("onPartialReply", { len: text.length });
|
|
11844
11836
|
if (!text) return;
|
|
11845
11837
|
this.captureToolUseElapsed();
|
|
11846
11838
|
if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
|
|
@@ -11852,7 +11844,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11852
11844
|
this.text.lastPartialText = text;
|
|
11853
11845
|
this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
|
|
11854
11846
|
if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
|
|
11855
|
-
log$
|
|
11847
|
+
log$16.debug("onPartialReply: buffering NO_REPLY prefix");
|
|
11856
11848
|
return;
|
|
11857
11849
|
}
|
|
11858
11850
|
await this.ensureCardCreated();
|
|
@@ -11862,7 +11854,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11862
11854
|
}
|
|
11863
11855
|
async onError(err, info) {
|
|
11864
11856
|
if (this.guard.terminate("onError", err)) return;
|
|
11865
|
-
log$
|
|
11857
|
+
log$16.error(`${info.kind} reply failed`, { error: String(err) });
|
|
11866
11858
|
this.captureToolUseElapsed();
|
|
11867
11859
|
this.finalizeCard("onError", "error");
|
|
11868
11860
|
await this.flush.waitForFlush();
|
|
@@ -11918,7 +11910,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11918
11910
|
if (this.cardKit.cardMessageId) {
|
|
11919
11911
|
const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
|
|
11920
11912
|
const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
|
|
11921
|
-
if (!this.text.completedText && !this.text.accumulatedText) log$
|
|
11913
|
+
if (!this.text.completedText && !this.text.accumulatedText) log$16.warn("reply completed without visible text, using empty-reply fallback");
|
|
11922
11914
|
const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
|
|
11923
11915
|
const idleToolUseDisplay = this.computeToolUseDisplay();
|
|
11924
11916
|
const terminalContent = prepareTerminalCardContent({
|
|
@@ -11946,7 +11938,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11946
11938
|
const rewindSessionId = this.deps.sessionKey;
|
|
11947
11939
|
const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
|
|
11948
11940
|
const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
|
|
11949
|
-
log$
|
|
11941
|
+
log$16.info("onIdle: rewind button conditions", {
|
|
11950
11942
|
sessionId: rewindSessionId,
|
|
11951
11943
|
messageId: rewindMessageId,
|
|
11952
11944
|
hasFileChanges: rewindHasFileChanges,
|
|
@@ -11959,7 +11951,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11959
11951
|
if (idleEffectiveCardId) {
|
|
11960
11952
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
11961
11953
|
this.cardKit.cardKitSequence += 1;
|
|
11962
|
-
log$
|
|
11954
|
+
log$16.info("onIdle: closing streaming mode", {
|
|
11963
11955
|
attempt,
|
|
11964
11956
|
seqBefore: seqBeforeClose,
|
|
11965
11957
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -11973,7 +11965,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11973
11965
|
});
|
|
11974
11966
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
11975
11967
|
this.cardKit.cardKitSequence += 1;
|
|
11976
|
-
log$
|
|
11968
|
+
log$16.info("onIdle: updating final card", {
|
|
11977
11969
|
attempt,
|
|
11978
11970
|
seqBefore: seqBeforeUpdate,
|
|
11979
11971
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -11991,33 +11983,33 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11991
11983
|
card: completeCard,
|
|
11992
11984
|
accountId: this.deps.accountId
|
|
11993
11985
|
});
|
|
11994
|
-
log$
|
|
11986
|
+
log$16.info("reply completed, card finalized", {
|
|
11995
11987
|
elapsedMs: this.elapsed(),
|
|
11996
11988
|
isCardKit: !!idleEffectiveCardId,
|
|
11997
11989
|
attempt
|
|
11998
11990
|
});
|
|
11999
11991
|
break;
|
|
12000
11992
|
} catch (retryErr) {
|
|
12001
|
-
log$
|
|
11993
|
+
log$16.warn("final card update attempt failed", {
|
|
12002
11994
|
attempt,
|
|
12003
11995
|
maxRetries: MAX_FINAL_UPDATE_RETRIES,
|
|
12004
11996
|
error: String(retryErr)
|
|
12005
11997
|
});
|
|
12006
11998
|
if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
|
|
12007
|
-
else log$
|
|
11999
|
+
else log$16.error("final card update exhausted all retries, card may remain in streaming state", {
|
|
12008
12000
|
cardId: idleEffectiveCardId,
|
|
12009
12001
|
messageId: this.cardKit.cardMessageId
|
|
12010
12002
|
});
|
|
12011
12003
|
}
|
|
12012
12004
|
}
|
|
12013
12005
|
} catch (err) {
|
|
12014
|
-
log$
|
|
12006
|
+
log$16.error("final card update failed (outer)", { error: String(err) });
|
|
12015
12007
|
} finally {
|
|
12016
12008
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12017
12009
|
}
|
|
12018
12010
|
}
|
|
12019
12011
|
markFullyComplete() {
|
|
12020
|
-
log$
|
|
12012
|
+
log$16.debug("markFullyComplete", {
|
|
12021
12013
|
completedTextLen: this.text.completedText.length,
|
|
12022
12014
|
accumulatedTextLen: this.text.accumulatedText.length
|
|
12023
12015
|
});
|
|
@@ -12056,7 +12048,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12056
12048
|
footerMetrics
|
|
12057
12049
|
});
|
|
12058
12050
|
await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
|
|
12059
|
-
log$
|
|
12051
|
+
log$16.info("abortCard completed", { effectiveCardId });
|
|
12060
12052
|
} else if (this.cardKit.cardMessageId) {
|
|
12061
12053
|
const abortCard = buildCardContent("complete", {
|
|
12062
12054
|
text: terminalContent.text,
|
|
@@ -12077,10 +12069,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12077
12069
|
card: abortCard,
|
|
12078
12070
|
accountId: this.deps.accountId
|
|
12079
12071
|
});
|
|
12080
|
-
log$
|
|
12072
|
+
log$16.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
|
|
12081
12073
|
}
|
|
12082
12074
|
} catch (err) {
|
|
12083
|
-
log$
|
|
12075
|
+
log$16.warn("abortCard failed", { error: String(err) });
|
|
12084
12076
|
} finally {
|
|
12085
12077
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12086
12078
|
}
|
|
@@ -12095,7 +12087,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12095
12087
|
if (effectiveCardId) {
|
|
12096
12088
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
12097
12089
|
this.cardKit.cardKitSequence += 1;
|
|
12098
|
-
log$
|
|
12090
|
+
log$16.info("replaceWithClarificationCard: closing streaming mode", {
|
|
12099
12091
|
cardId: effectiveCardId,
|
|
12100
12092
|
seqBefore: seqBeforeClose,
|
|
12101
12093
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -12116,7 +12108,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12116
12108
|
sequence: this.cardKit.cardKitSequence,
|
|
12117
12109
|
accountId: this.deps.accountId
|
|
12118
12110
|
});
|
|
12119
|
-
log$
|
|
12111
|
+
log$16.info("replaceWithClarificationCard: existing card replaced", {
|
|
12120
12112
|
cardId: effectiveCardId,
|
|
12121
12113
|
seqBefore: seqBeforeUpdate,
|
|
12122
12114
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -12139,7 +12131,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12139
12131
|
this.cardKit.cardKitCardId = cardId;
|
|
12140
12132
|
this.cardKit.originalCardKitCardId = cardId;
|
|
12141
12133
|
this.cardKit.cardMessageId = result.messageId;
|
|
12142
|
-
log$
|
|
12134
|
+
log$16.info("replaceWithClarificationCard: new clarification card sent", {
|
|
12143
12135
|
cardId,
|
|
12144
12136
|
messageId: result.messageId
|
|
12145
12137
|
});
|
|
@@ -12148,7 +12140,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12148
12140
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12149
12141
|
return true;
|
|
12150
12142
|
} catch (err) {
|
|
12151
|
-
log$
|
|
12143
|
+
log$16.error("replaceWithClarificationCard failed", { error: String(err) });
|
|
12152
12144
|
return false;
|
|
12153
12145
|
}
|
|
12154
12146
|
}
|
|
@@ -12162,10 +12154,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12162
12154
|
async forceCloseStreaming() {
|
|
12163
12155
|
const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
12164
12156
|
if (!effectiveCardId && !this.cardKit.cardMessageId) {
|
|
12165
|
-
log$
|
|
12157
|
+
log$16.warn("forceCloseStreaming: no card to close");
|
|
12166
12158
|
return;
|
|
12167
12159
|
}
|
|
12168
|
-
log$
|
|
12160
|
+
log$16.info("forceCloseStreaming: 强制终态化卡片", {
|
|
12169
12161
|
cardId: effectiveCardId,
|
|
12170
12162
|
messageId: this.cardKit.cardMessageId,
|
|
12171
12163
|
phase: this.phase
|
|
@@ -12215,10 +12207,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12215
12207
|
card: completeCard,
|
|
12216
12208
|
accountId: this.deps.accountId
|
|
12217
12209
|
});
|
|
12218
|
-
log$
|
|
12210
|
+
log$16.info("forceCloseStreaming: 卡片已强制终态化");
|
|
12219
12211
|
if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
|
|
12220
12212
|
} catch (err) {
|
|
12221
|
-
log$
|
|
12213
|
+
log$16.error("forceCloseStreaming failed", { error: String(err) });
|
|
12222
12214
|
} finally {
|
|
12223
12215
|
unregisterActiveCard(this.deps.sessionKey);
|
|
12224
12216
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
@@ -12242,7 +12234,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12242
12234
|
this.disposeShutdownHook = null;
|
|
12243
12235
|
return;
|
|
12244
12236
|
}
|
|
12245
|
-
log$
|
|
12237
|
+
log$16.info("reusing placeholder card", {
|
|
12246
12238
|
cardId: this.deps.placeholderCardId,
|
|
12247
12239
|
messageId: this.deps.placeholderMessageId
|
|
12248
12240
|
});
|
|
@@ -12266,7 +12258,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12266
12258
|
accountId: this.deps.accountId
|
|
12267
12259
|
});
|
|
12268
12260
|
if (this.isStaleCreate(epoch)) {
|
|
12269
|
-
log$
|
|
12261
|
+
log$16.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
|
|
12270
12262
|
epoch,
|
|
12271
12263
|
phase: this.phase
|
|
12272
12264
|
});
|
|
@@ -12277,7 +12269,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12277
12269
|
this.cardKit.originalCardKitCardId = cId;
|
|
12278
12270
|
this.cardKit.cardKitSequence = 1;
|
|
12279
12271
|
this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
|
|
12280
|
-
log$
|
|
12272
|
+
log$16.info("created CardKit entity", {
|
|
12281
12273
|
cardId: cId,
|
|
12282
12274
|
initialSequence: this.cardKit.cardKitSequence
|
|
12283
12275
|
});
|
|
@@ -12290,7 +12282,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12290
12282
|
accountId: this.deps.accountId
|
|
12291
12283
|
});
|
|
12292
12284
|
if (this.isStaleCreate(epoch)) {
|
|
12293
|
-
log$
|
|
12285
|
+
log$16.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
|
|
12294
12286
|
epoch,
|
|
12295
12287
|
phase: this.phase
|
|
12296
12288
|
});
|
|
@@ -12305,13 +12297,13 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12305
12297
|
this.disposeShutdownHook = null;
|
|
12306
12298
|
return;
|
|
12307
12299
|
}
|
|
12308
|
-
log$
|
|
12300
|
+
log$16.info("sent CardKit card", { messageId: result.messageId });
|
|
12309
12301
|
} else throw new Error("card.create returned empty card_id");
|
|
12310
12302
|
} catch (cardKitErr) {
|
|
12311
12303
|
if (this.isStaleCreate(epoch)) return;
|
|
12312
12304
|
if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
|
|
12313
12305
|
const apiDetail = extractApiDetail(cardKitErr);
|
|
12314
|
-
log$
|
|
12306
|
+
log$16.warn("CardKit flow failed, falling back to IM", { apiDetail });
|
|
12315
12307
|
this.cardKit.cardKitCardId = null;
|
|
12316
12308
|
this.cardKit.originalCardKitCardId = null;
|
|
12317
12309
|
const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
|
|
@@ -12324,7 +12316,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12324
12316
|
accountId: this.deps.accountId
|
|
12325
12317
|
});
|
|
12326
12318
|
if (this.isStaleCreate(epoch)) {
|
|
12327
|
-
log$
|
|
12319
|
+
log$16.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
|
|
12328
12320
|
epoch,
|
|
12329
12321
|
phase: this.phase
|
|
12330
12322
|
});
|
|
@@ -12333,12 +12325,12 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12333
12325
|
this.cardKit.cardMessageId = result.messageId;
|
|
12334
12326
|
this.flush.setCardMessageReady(true);
|
|
12335
12327
|
if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
|
|
12336
|
-
log$
|
|
12328
|
+
log$16.info("sent fallback IM card", { messageId: result.messageId });
|
|
12337
12329
|
}
|
|
12338
12330
|
} catch (err) {
|
|
12339
12331
|
if (this.isStaleCreate(epoch)) return;
|
|
12340
12332
|
if (this.guard.terminate("ensureCardCreated.outer", err)) return;
|
|
12341
|
-
log$
|
|
12333
|
+
log$16.warn("thinking card failed, falling back to static", { error: String(err) });
|
|
12342
12334
|
this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
|
|
12343
12335
|
}
|
|
12344
12336
|
})();
|
|
@@ -12347,10 +12339,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12347
12339
|
async performFlush() {
|
|
12348
12340
|
if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
|
|
12349
12341
|
if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
|
|
12350
|
-
log$
|
|
12342
|
+
log$16.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
|
|
12351
12343
|
return;
|
|
12352
12344
|
}
|
|
12353
|
-
log$
|
|
12345
|
+
log$16.debug("flushCardUpdate: enter", {
|
|
12354
12346
|
seq: this.cardKit.cardKitSequence,
|
|
12355
12347
|
isCardKit: !!this.cardKit.cardKitCardId
|
|
12356
12348
|
});
|
|
@@ -12372,7 +12364,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12372
12364
|
reasoningText: this.reasoning.accumulatedReasoningText || void 0
|
|
12373
12365
|
});
|
|
12374
12366
|
this.cardKit.cardKitSequence += 1;
|
|
12375
|
-
log$
|
|
12367
|
+
log$16.debug("flushCardUpdate: full card update (dirty)", {
|
|
12376
12368
|
seq: this.cardKit.cardKitSequence,
|
|
12377
12369
|
stepCount: display?.stepCount,
|
|
12378
12370
|
hasReasoning: !!this.reasoning.accumulatedReasoningText
|
|
@@ -12388,7 +12380,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12388
12380
|
} else if (resolvedText !== this.text.lastFlushedText) {
|
|
12389
12381
|
const prevSeq = this.cardKit.cardKitSequence;
|
|
12390
12382
|
this.cardKit.cardKitSequence += 1;
|
|
12391
|
-
log$
|
|
12383
|
+
log$16.debug("flushCardUpdate: answer seq bump", {
|
|
12392
12384
|
seqBefore: prevSeq,
|
|
12393
12385
|
seqAfter: this.cardKit.cardKitSequence
|
|
12394
12386
|
});
|
|
@@ -12403,7 +12395,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12403
12395
|
this.text.lastFlushedText = resolvedText;
|
|
12404
12396
|
}
|
|
12405
12397
|
} else {
|
|
12406
|
-
log$
|
|
12398
|
+
log$16.debug("flushCardUpdate: IM patch fallback");
|
|
12407
12399
|
const flushDisplay = this.computeToolUseDisplay();
|
|
12408
12400
|
const card = buildCardContent("streaming", {
|
|
12409
12401
|
text: this.reasoning.isReasoningPhase ? "" : resolvedText,
|
|
@@ -12423,31 +12415,31 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12423
12415
|
if (this.guard.terminate("flushCardUpdate", err)) return;
|
|
12424
12416
|
const apiCode = extractLarkApiCode(err);
|
|
12425
12417
|
if (isCardRateLimitError(err)) {
|
|
12426
|
-
log$
|
|
12418
|
+
log$16.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
|
|
12427
12419
|
return;
|
|
12428
12420
|
}
|
|
12429
12421
|
if (isCardTableLimitError(err)) {
|
|
12430
|
-
log$
|
|
12422
|
+
log$16.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
|
|
12431
12423
|
this.cardKit.cardKitCardId = null;
|
|
12432
12424
|
return;
|
|
12433
12425
|
}
|
|
12434
12426
|
if (apiCode === 300317 && this.cardKit.cardKitCardId) {
|
|
12435
12427
|
const oldSeq = this.cardKit.cardKitSequence;
|
|
12436
12428
|
this.cardKit.cardKitSequence += 100;
|
|
12437
|
-
log$
|
|
12429
|
+
log$16.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
|
|
12438
12430
|
oldSeq,
|
|
12439
12431
|
newSeq: this.cardKit.cardKitSequence
|
|
12440
12432
|
});
|
|
12441
12433
|
return;
|
|
12442
12434
|
}
|
|
12443
12435
|
const apiDetail = extractApiDetail(err);
|
|
12444
|
-
log$
|
|
12436
|
+
log$16.error("card stream update failed", {
|
|
12445
12437
|
apiCode,
|
|
12446
12438
|
seq: this.cardKit.cardKitSequence,
|
|
12447
12439
|
apiDetail
|
|
12448
12440
|
});
|
|
12449
12441
|
if (this.cardKit.cardKitCardId) {
|
|
12450
|
-
log$
|
|
12442
|
+
log$16.warn("disabling CardKit streaming, falling back to im.message.patch");
|
|
12451
12443
|
this.cardKit.cardKitCardId = null;
|
|
12452
12444
|
}
|
|
12453
12445
|
}
|
|
@@ -12472,7 +12464,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12472
12464
|
if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
|
|
12473
12465
|
try {
|
|
12474
12466
|
const display = this.computeToolUseDisplay();
|
|
12475
|
-
log$
|
|
12467
|
+
log$16.info("updateToolUseStatus", {
|
|
12476
12468
|
hasDisplay: !!display,
|
|
12477
12469
|
stepCount: display?.stepCount,
|
|
12478
12470
|
steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
|
|
@@ -12494,7 +12486,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12494
12486
|
accountId: this.deps.accountId
|
|
12495
12487
|
});
|
|
12496
12488
|
} catch (err) {
|
|
12497
|
-
log$
|
|
12489
|
+
log$16.debug("updateToolUseStatus failed", { error: String(err) });
|
|
12498
12490
|
}
|
|
12499
12491
|
}
|
|
12500
12492
|
finalizeCard(source, reason) {
|
|
@@ -12505,7 +12497,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12505
12497
|
const from = this.phase;
|
|
12506
12498
|
this.phase = "completed";
|
|
12507
12499
|
this._terminalReason = "normal";
|
|
12508
|
-
log$
|
|
12500
|
+
log$16.info("phase transition", {
|
|
12509
12501
|
from,
|
|
12510
12502
|
to: this.phase,
|
|
12511
12503
|
source: "replaceWithClarificationCard",
|
|
@@ -12519,7 +12511,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12519
12511
|
async closeStreamingAndUpdate(cardId, card, label) {
|
|
12520
12512
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
12521
12513
|
this.cardKit.cardKitSequence += 1;
|
|
12522
|
-
log$
|
|
12514
|
+
log$16.info(`${label}: closing streaming mode`, {
|
|
12523
12515
|
seqBefore: seqBeforeClose,
|
|
12524
12516
|
seqAfter: this.cardKit.cardKitSequence
|
|
12525
12517
|
});
|
|
@@ -12532,7 +12524,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12532
12524
|
});
|
|
12533
12525
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
12534
12526
|
this.cardKit.cardKitSequence += 1;
|
|
12535
|
-
log$
|
|
12527
|
+
log$16.info(`${label}: updating card`, {
|
|
12536
12528
|
seqBefore: seqBeforeUpdate,
|
|
12537
12529
|
seqAfter: this.cardKit.cardKitSequence
|
|
12538
12530
|
});
|
|
@@ -12566,7 +12558,7 @@ function extractApiDetail(err) {
|
|
|
12566
12558
|
}
|
|
12567
12559
|
//#endregion
|
|
12568
12560
|
//#region src/messaging/format-for-cc.ts
|
|
12569
|
-
const log$
|
|
12561
|
+
const log$15 = larkLogger("messaging/format-for-cc");
|
|
12570
12562
|
function safeParseJSON(raw) {
|
|
12571
12563
|
try {
|
|
12572
12564
|
return JSON.parse(raw);
|
|
@@ -12622,7 +12614,7 @@ function extractPostText(rawContent) {
|
|
|
12622
12614
|
*/
|
|
12623
12615
|
function formatContentByType(ctx) {
|
|
12624
12616
|
const { contentType, content, resources } = ctx;
|
|
12625
|
-
log$
|
|
12617
|
+
log$15.debug("formatContentByType 开始格式化", {
|
|
12626
12618
|
contentType,
|
|
12627
12619
|
contentLength: content?.length ?? 0,
|
|
12628
12620
|
resourceCount: resources?.length ?? 0
|
|
@@ -12664,7 +12656,7 @@ function formatContentByType(ctx) {
|
|
|
12664
12656
|
case "todo":
|
|
12665
12657
|
case "vote": return content;
|
|
12666
12658
|
default:
|
|
12667
|
-
log$
|
|
12659
|
+
log$15.warn("遇到不支持的消息类型", { contentType });
|
|
12668
12660
|
return `[不支持的消息类型: ${contentType}]`;
|
|
12669
12661
|
}
|
|
12670
12662
|
}
|
|
@@ -12707,7 +12699,7 @@ function formatMessageTime(createTime) {
|
|
|
12707
12699
|
* @returns 格式化后的纯文本字符串
|
|
12708
12700
|
*/
|
|
12709
12701
|
function formatForCC(ctx, isGroup) {
|
|
12710
|
-
log$
|
|
12702
|
+
log$15.info("formatForCC 开始处理", {
|
|
12711
12703
|
messageId: ctx.messageId,
|
|
12712
12704
|
contentType: ctx.contentType,
|
|
12713
12705
|
chatType: ctx.chatType,
|
|
@@ -12723,7 +12715,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
12723
12715
|
if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
|
|
12724
12716
|
if (ctx.threadId) parts.push("[话题回复]");
|
|
12725
12717
|
const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
|
|
12726
|
-
log$
|
|
12718
|
+
log$15.info("formatForCC 完成", {
|
|
12727
12719
|
messageId: ctx.messageId,
|
|
12728
12720
|
resultLength: result.length,
|
|
12729
12721
|
hasTime: !!timeStr,
|
|
@@ -12731,6 +12723,41 @@ function formatForCC(ctx, isGroup) {
|
|
|
12731
12723
|
});
|
|
12732
12724
|
return result;
|
|
12733
12725
|
}
|
|
12726
|
+
function formatPromptMetadata(ctx) {
|
|
12727
|
+
const senderOpenId = ctx.rawSender.sender_id.open_id || ctx.senderId;
|
|
12728
|
+
const senderUserId = ctx.rawSender.sender_id.user_id;
|
|
12729
|
+
const fields = [
|
|
12730
|
+
`message_id=${ctx.messageId}`,
|
|
12731
|
+
`chat_id=${ctx.chatId}`,
|
|
12732
|
+
`chat_type=${ctx.chatType}`,
|
|
12733
|
+
`sender_open_id=${senderOpenId}`
|
|
12734
|
+
];
|
|
12735
|
+
if (senderUserId) fields.push(`sender_user_id=${senderUserId}`);
|
|
12736
|
+
if (ctx.senderName) fields.push(`sender_name=${ctx.senderName}`);
|
|
12737
|
+
if (ctx.parentId) fields.push(`reply_to_message_id=${ctx.parentId}`);
|
|
12738
|
+
if (ctx.rootId) fields.push(`root_message_id=${ctx.rootId}`);
|
|
12739
|
+
if (ctx.threadId) fields.push(`thread_id=${ctx.threadId}`);
|
|
12740
|
+
const mentionedUsers = ctx.mentions.filter((mention) => !mention.isBot && mention.openId).map((mention) => `${mention.name || mention.openId}(open_id=${mention.openId})`);
|
|
12741
|
+
if (mentionedUsers.length > 0) fields.push(`mentioned_users=${mentionedUsers.join(", ")}`);
|
|
12742
|
+
return `[消息元数据] ${fields.join("; ")}`;
|
|
12743
|
+
}
|
|
12744
|
+
/**
|
|
12745
|
+
* Build the full text prompt sent to the agent runtime.
|
|
12746
|
+
*
|
|
12747
|
+
* Quoted/replied-to content is intentionally injected outside the parser:
|
|
12748
|
+
* resolving it requires Feishu API calls and can fail independently from
|
|
12749
|
+
* parsing the current event.
|
|
12750
|
+
*/
|
|
12751
|
+
function formatPromptForCC(ctx, isGroup, quotedContent) {
|
|
12752
|
+
let currentMessage = formatForCC(ctx, isGroup);
|
|
12753
|
+
if (isGroup && ctx.threadId) currentMessage = `[群聊话题] ${currentMessage}`;
|
|
12754
|
+
else if (isGroup) currentMessage = `[群聊] ${currentMessage}`;
|
|
12755
|
+
else currentMessage = `[私聊] ${currentMessage}`;
|
|
12756
|
+
const quote = quotedContent?.trim();
|
|
12757
|
+
const metadata = formatPromptMetadata(ctx);
|
|
12758
|
+
if (!quote) return `${metadata}\n${currentMessage}`;
|
|
12759
|
+
return `${metadata}\n\n[引用/回复消息上下文]\n${quote}\n\n[当前消息]\n${currentMessage}`;
|
|
12760
|
+
}
|
|
12734
12761
|
//#endregion
|
|
12735
12762
|
//#region src/card/interactive-cards.ts
|
|
12736
12763
|
/**
|
|
@@ -12746,7 +12773,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
12746
12773
|
* - CC 输出文本后处理(检测特殊标记)
|
|
12747
12774
|
* - 操作 ID 生命周期管理
|
|
12748
12775
|
*/
|
|
12749
|
-
const log$
|
|
12776
|
+
const log$14 = larkLogger("card/interactive-cards");
|
|
12750
12777
|
const pendingOperations = /* @__PURE__ */ new Map();
|
|
12751
12778
|
const OPERATION_TTL_MS = 1800 * 1e3;
|
|
12752
12779
|
setInterval(() => {
|
|
@@ -12844,7 +12871,7 @@ function buildAuthCard(ctx) {
|
|
|
12844
12871
|
scopes: ctx.scopes
|
|
12845
12872
|
}
|
|
12846
12873
|
});
|
|
12847
|
-
log$
|
|
12874
|
+
log$14.info("已创建授权卡片操作", {
|
|
12848
12875
|
operationId,
|
|
12849
12876
|
scopes: ctx.scopes,
|
|
12850
12877
|
sessionId: ctx.sessionId
|
|
@@ -12882,7 +12909,7 @@ function buildAskCard(ctx, phaseIndex = 0) {
|
|
|
12882
12909
|
freeTextAnswers: {}
|
|
12883
12910
|
}
|
|
12884
12911
|
});
|
|
12885
|
-
log$
|
|
12912
|
+
log$14.info("已创建提问卡片操作", {
|
|
12886
12913
|
operationId,
|
|
12887
12914
|
phaseId: phase?.id,
|
|
12888
12915
|
phaseIndex,
|
|
@@ -13047,7 +13074,7 @@ function toggleAskSelection(operationId, phaseId, value) {
|
|
|
13047
13074
|
else next = current[0] === value ? [] : [value];
|
|
13048
13075
|
if (next.length > 0) op.askContext.answers[phaseId] = next;
|
|
13049
13076
|
else delete op.askContext.answers[phaseId];
|
|
13050
|
-
log$
|
|
13077
|
+
log$14.info("更新提问卡片勾选状态", {
|
|
13051
13078
|
operationId,
|
|
13052
13079
|
phaseId,
|
|
13053
13080
|
selectedValues: next
|
|
@@ -13063,7 +13090,7 @@ function setAskFreeTextAnswer(operationId, phaseId, value) {
|
|
|
13063
13090
|
const text = normalizeFreeText(value);
|
|
13064
13091
|
if (text) op.askContext.freeTextAnswers[phaseId] = text;
|
|
13065
13092
|
else delete op.askContext.freeTextAnswers[phaseId];
|
|
13066
|
-
log$
|
|
13093
|
+
log$14.info("更新提问卡片自由文本", {
|
|
13067
13094
|
operationId,
|
|
13068
13095
|
phaseId,
|
|
13069
13096
|
hasText: text.length > 0
|
|
@@ -13307,7 +13334,7 @@ async function trySendClarificationFallback(params) {
|
|
|
13307
13334
|
}
|
|
13308
13335
|
//#endregion
|
|
13309
13336
|
//#region src/messaging/inbound/dispatch-cc.ts
|
|
13310
|
-
const log$
|
|
13337
|
+
const log$13 = larkLogger("inbound/dispatch-cc");
|
|
13311
13338
|
function buildFeishuRuntimeConfig(params) {
|
|
13312
13339
|
const { ctx, account, route } = params;
|
|
13313
13340
|
const senderUserId = ctx.rawSender.sender_id.user_id;
|
|
@@ -13364,7 +13391,7 @@ async function dispatchToCC(params) {
|
|
|
13364
13391
|
const ccModel = params.model;
|
|
13365
13392
|
const maxTurns = params.maxTurns;
|
|
13366
13393
|
const maxBudgetUsd = params.maxBudgetUsd;
|
|
13367
|
-
log$
|
|
13394
|
+
log$13.info("dispatchToCC 开始", {
|
|
13368
13395
|
msgId,
|
|
13369
13396
|
chatId,
|
|
13370
13397
|
chatType,
|
|
@@ -13377,12 +13404,12 @@ async function dispatchToCC(params) {
|
|
|
13377
13404
|
threadId,
|
|
13378
13405
|
userId: ctx.senderId
|
|
13379
13406
|
});
|
|
13380
|
-
log$
|
|
13407
|
+
log$13.info("会话路由解析完成", {
|
|
13381
13408
|
sessionId: route.sessionId,
|
|
13382
13409
|
cwd: route.cwd,
|
|
13383
13410
|
type: route.type
|
|
13384
13411
|
});
|
|
13385
|
-
if (params.userContext) log$
|
|
13412
|
+
if (params.userContext) log$13.info("用户上下文已注入", {
|
|
13386
13413
|
userId: params.userContext.userId,
|
|
13387
13414
|
isTenantIdentity: params.userContext.isTenantIdentity,
|
|
13388
13415
|
credentialDir: params.userContext.credentialDir
|
|
@@ -13391,10 +13418,7 @@ async function dispatchToCC(params) {
|
|
|
13391
13418
|
const isGroup = chatType === "group";
|
|
13392
13419
|
const replyInThread = !!threadId;
|
|
13393
13420
|
const replyToMessageId = threadId ? void 0 : ctx.messageId;
|
|
13394
|
-
|
|
13395
|
-
if (isGroup && replyInThread) textPrompt = `[群聊话题] ${textPrompt}`;
|
|
13396
|
-
else if (isGroup) textPrompt = `[群聊] ${textPrompt}`;
|
|
13397
|
-
else textPrompt = `[私聊] ${textPrompt}`;
|
|
13421
|
+
const textPrompt = formatPromptForCC(ctx, isGroup, params.quotedContent);
|
|
13398
13422
|
const imageResources = ctx.resources.filter((r) => r.type === "image");
|
|
13399
13423
|
let prompt = textPrompt;
|
|
13400
13424
|
if (imageResources.length > 0) {
|
|
@@ -13421,7 +13445,7 @@ async function dispatchToCC(params) {
|
|
|
13421
13445
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
13422
13446
|
buffer = Buffer.concat(chunks);
|
|
13423
13447
|
} else {
|
|
13424
|
-
log$
|
|
13448
|
+
log$13.warn("图片资源下载返回未知格式,跳过", {
|
|
13425
13449
|
fileKey: imgRes.fileKey,
|
|
13426
13450
|
responseType: typeof response
|
|
13427
13451
|
});
|
|
@@ -13429,7 +13453,7 @@ async function dispatchToCC(params) {
|
|
|
13429
13453
|
}
|
|
13430
13454
|
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
13431
13455
|
} else {
|
|
13432
|
-
log$
|
|
13456
|
+
log$13.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
|
|
13433
13457
|
continue;
|
|
13434
13458
|
}
|
|
13435
13459
|
const imgFileName = `${imgRes.fileKey}.png`;
|
|
@@ -13439,13 +13463,13 @@ async function dispatchToCC(params) {
|
|
|
13439
13463
|
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
13440
13464
|
await mkdir(filesDir, { recursive: true });
|
|
13441
13465
|
await writeFile(imgFilePath, buffer);
|
|
13442
|
-
log$
|
|
13466
|
+
log$13.info("图片已保存到工作目录", {
|
|
13443
13467
|
fileKey: imgRes.fileKey,
|
|
13444
13468
|
path: imgFilePath,
|
|
13445
13469
|
sizeBytes: buffer.length
|
|
13446
13470
|
});
|
|
13447
13471
|
} catch (saveErr) {
|
|
13448
|
-
log$
|
|
13472
|
+
log$13.warn("图片保存到工作目录失败", {
|
|
13449
13473
|
fileKey: imgRes.fileKey,
|
|
13450
13474
|
path: imgFilePath,
|
|
13451
13475
|
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
@@ -13460,13 +13484,13 @@ async function dispatchToCC(params) {
|
|
|
13460
13484
|
data: base64Data
|
|
13461
13485
|
}
|
|
13462
13486
|
});
|
|
13463
|
-
log$
|
|
13487
|
+
log$13.info("图片资源下载成功", {
|
|
13464
13488
|
fileKey: imgRes.fileKey,
|
|
13465
13489
|
mediaType,
|
|
13466
13490
|
sizeBytes: buffer.length
|
|
13467
13491
|
});
|
|
13468
13492
|
} catch (err) {
|
|
13469
|
-
log$
|
|
13493
|
+
log$13.warn("图片资源下载失败,跳过", {
|
|
13470
13494
|
fileKey: imgRes.fileKey,
|
|
13471
13495
|
error: err instanceof Error ? err.message : String(err)
|
|
13472
13496
|
});
|
|
@@ -13476,7 +13500,7 @@ async function dispatchToCC(params) {
|
|
|
13476
13500
|
type: "text",
|
|
13477
13501
|
text: textPrompt
|
|
13478
13502
|
}, ...imageBlocks];
|
|
13479
|
-
log$
|
|
13503
|
+
log$13.info("已构建多模态 prompt", {
|
|
13480
13504
|
textLength: textPrompt.length,
|
|
13481
13505
|
imageCount: imageBlocks.length
|
|
13482
13506
|
});
|
|
@@ -13484,12 +13508,12 @@ async function dispatchToCC(params) {
|
|
|
13484
13508
|
const textWithoutImageRefs = textPrompt.replace(/!\[image\]\([^)]*\)/g, "").replace(/\[图片\]\s*/g, "").trim();
|
|
13485
13509
|
if (textWithoutImageRefs.length > 0) {
|
|
13486
13510
|
prompt = textPrompt.replace(/!\[image\]\([^)]*\)/g, "[图片加载失败]");
|
|
13487
|
-
log$
|
|
13511
|
+
log$13.info("图片下载失败但保留文字内容继续调度", {
|
|
13488
13512
|
messageId: ctx.messageId,
|
|
13489
13513
|
textLength: textWithoutImageRefs.length
|
|
13490
13514
|
});
|
|
13491
13515
|
} else {
|
|
13492
|
-
log$
|
|
13516
|
+
log$13.warn("纯图片消息且图片全部下载失败,中止调度", {
|
|
13493
13517
|
messageId: ctx.messageId,
|
|
13494
13518
|
expectedImages: imageResources.length
|
|
13495
13519
|
});
|
|
@@ -13511,7 +13535,7 @@ async function dispatchToCC(params) {
|
|
|
13511
13535
|
await fsMkdir(filesDir, { recursive: true });
|
|
13512
13536
|
let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
|
|
13513
13537
|
for (const res of attachmentResources) try {
|
|
13514
|
-
log$
|
|
13538
|
+
log$13.info("开始下载非图片附件", {
|
|
13515
13539
|
type: res.type,
|
|
13516
13540
|
fileKey: res.fileKey,
|
|
13517
13541
|
fileName: res.fileName,
|
|
@@ -13525,7 +13549,7 @@ async function dispatchToCC(params) {
|
|
|
13525
13549
|
},
|
|
13526
13550
|
params: { type: "file" }
|
|
13527
13551
|
});
|
|
13528
|
-
log$
|
|
13552
|
+
log$13.info("非图片附件 API 响应已收到", {
|
|
13529
13553
|
type: res.type,
|
|
13530
13554
|
fileKey: res.fileKey,
|
|
13531
13555
|
responseType: typeof response,
|
|
@@ -13544,7 +13568,7 @@ async function dispatchToCC(params) {
|
|
|
13544
13568
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
13545
13569
|
buffer = Buffer.concat(chunks);
|
|
13546
13570
|
} else {
|
|
13547
|
-
log$
|
|
13571
|
+
log$13.warn("非图片附件下载返回未知格式,跳过", {
|
|
13548
13572
|
type: res.type,
|
|
13549
13573
|
fileKey: res.fileKey,
|
|
13550
13574
|
responseType: typeof response
|
|
@@ -13552,7 +13576,7 @@ async function dispatchToCC(params) {
|
|
|
13552
13576
|
continue;
|
|
13553
13577
|
}
|
|
13554
13578
|
} else {
|
|
13555
|
-
log$
|
|
13579
|
+
log$13.warn("非图片附件下载返回空,跳过", {
|
|
13556
13580
|
type: res.type,
|
|
13557
13581
|
fileKey: res.fileKey
|
|
13558
13582
|
});
|
|
@@ -13575,7 +13599,7 @@ async function dispatchToCC(params) {
|
|
|
13575
13599
|
}
|
|
13576
13600
|
const filePath = path.join(filesDir, savedFileName);
|
|
13577
13601
|
await fsWriteFile(filePath, buffer);
|
|
13578
|
-
log$
|
|
13602
|
+
log$13.info("非图片附件已保存到工作目录", {
|
|
13579
13603
|
type: res.type,
|
|
13580
13604
|
fileKey: res.fileKey,
|
|
13581
13605
|
savedFileName,
|
|
@@ -13609,7 +13633,7 @@ async function dispatchToCC(params) {
|
|
|
13609
13633
|
}
|
|
13610
13634
|
}
|
|
13611
13635
|
} catch (err) {
|
|
13612
|
-
log$
|
|
13636
|
+
log$13.warn("非图片附件下载失败,替换为降级描述", {
|
|
13613
13637
|
type: res.type,
|
|
13614
13638
|
fileKey: res.fileKey,
|
|
13615
13639
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -13628,12 +13652,12 @@ async function dispatchToCC(params) {
|
|
|
13628
13652
|
const textBlock = prompt.find((b) => b.type === "text");
|
|
13629
13653
|
if (textBlock) textBlock.text = updatedTextPrompt;
|
|
13630
13654
|
}
|
|
13631
|
-
log$
|
|
13655
|
+
log$13.info("非图片附件处理完成", {
|
|
13632
13656
|
totalAttachments: attachmentResources.length,
|
|
13633
13657
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
|
|
13634
13658
|
});
|
|
13635
13659
|
}
|
|
13636
|
-
log$
|
|
13660
|
+
log$13.info("消息格式化完成", {
|
|
13637
13661
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
|
|
13638
13662
|
isMultimodal: Array.isArray(prompt),
|
|
13639
13663
|
isGroup
|
|
@@ -13665,19 +13689,19 @@ async function dispatchToCC(params) {
|
|
|
13665
13689
|
};
|
|
13666
13690
|
const cardController = new StreamingCardController(cardDeps);
|
|
13667
13691
|
registerActiveCard(route.sessionId, cardController);
|
|
13668
|
-
log$
|
|
13692
|
+
log$13.info("StreamingCardController 已创建", {
|
|
13669
13693
|
sessionId: route.sessionId,
|
|
13670
13694
|
chatId,
|
|
13671
13695
|
replyToMessageId: cardDeps.replyToMessageId
|
|
13672
13696
|
});
|
|
13673
13697
|
cardController.ensureCardCreated().catch((err) => {
|
|
13674
|
-
log$
|
|
13698
|
+
log$13.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
|
|
13675
13699
|
});
|
|
13676
13700
|
let promptForRuntime = prompt;
|
|
13677
13701
|
try {
|
|
13678
13702
|
promptForRuntime = await new MemoryContextBuilder().injectPrompt(route.cwd, prompt, promptToText(prompt));
|
|
13679
13703
|
} catch (err) {
|
|
13680
|
-
log$
|
|
13704
|
+
log$13.warn("memory context 注入失败,继续使用原始 prompt", {
|
|
13681
13705
|
sessionId: route.sessionId,
|
|
13682
13706
|
error: err instanceof Error ? err.message : String(err)
|
|
13683
13707
|
});
|
|
@@ -13698,7 +13722,7 @@ async function dispatchToCC(params) {
|
|
|
13698
13722
|
});
|
|
13699
13723
|
} catch (err) {
|
|
13700
13724
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
13701
|
-
log$
|
|
13725
|
+
log$13.error("飞书事件 run context 构造失败,终止调度", {
|
|
13702
13726
|
sessionId: route.sessionId,
|
|
13703
13727
|
messageId: msgId,
|
|
13704
13728
|
error: errorMessage
|
|
@@ -13716,7 +13740,7 @@ async function dispatchToCC(params) {
|
|
|
13716
13740
|
channel: "feishu-bot",
|
|
13717
13741
|
title: userContent.slice(0, 30) + (userContent.length > 30 ? "..." : "")
|
|
13718
13742
|
}).catch((err) => {
|
|
13719
|
-
log$
|
|
13743
|
+
log$13.warn("创建飞书端会话记录失败", { error: String(err) });
|
|
13720
13744
|
});
|
|
13721
13745
|
params.messageStore.appendMessage({
|
|
13722
13746
|
sessionId: storeSessionId,
|
|
@@ -13730,14 +13754,14 @@ async function dispatchToCC(params) {
|
|
|
13730
13754
|
scenarioId: runtimeConfig.runContext?.scenarioId
|
|
13731
13755
|
}
|
|
13732
13756
|
}).catch((err) => {
|
|
13733
|
-
log$
|
|
13757
|
+
log$13.warn("记录飞书用户消息到 store 失败", { error: String(err) });
|
|
13734
13758
|
});
|
|
13735
13759
|
}
|
|
13736
13760
|
const bridge = new CCStreamBridge(cardController, {
|
|
13737
13761
|
autoCompleteOnTurnEnd: true,
|
|
13738
13762
|
sessionKey: route.sessionId
|
|
13739
13763
|
});
|
|
13740
|
-
log$
|
|
13764
|
+
log$13.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
|
|
13741
13765
|
let feishuAccumText = "";
|
|
13742
13766
|
let usedAskUserTool = false;
|
|
13743
13767
|
let pendingEndTurn = null;
|
|
@@ -13759,12 +13783,12 @@ async function dispatchToCC(params) {
|
|
|
13759
13783
|
...metadata
|
|
13760
13784
|
}
|
|
13761
13785
|
}).catch((err) => {
|
|
13762
|
-
log$
|
|
13786
|
+
log$13.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
|
|
13763
13787
|
});
|
|
13764
13788
|
};
|
|
13765
13789
|
const appendRuntimeEventMessage = (event) => {
|
|
13766
13790
|
const eventSummary = summarizeForAudit(event);
|
|
13767
|
-
log$
|
|
13791
|
+
log$13.info("CC onRuntimeEvent", {
|
|
13768
13792
|
sessionId: route.sessionId,
|
|
13769
13793
|
type: event.type,
|
|
13770
13794
|
traceId: runtimeConfig.runContext?.traceId,
|
|
@@ -13802,11 +13826,11 @@ async function dispatchToCC(params) {
|
|
|
13802
13826
|
finalStatus: event.finalStatus
|
|
13803
13827
|
}
|
|
13804
13828
|
}).catch((err) => {
|
|
13805
|
-
log$
|
|
13829
|
+
log$13.warn("记录飞书 runtime event 到 store 失败", { error: String(err) });
|
|
13806
13830
|
});
|
|
13807
13831
|
};
|
|
13808
13832
|
const handleResult = async (result) => {
|
|
13809
|
-
log$
|
|
13833
|
+
log$13.info("CC onResult", {
|
|
13810
13834
|
sessionId: route.sessionId,
|
|
13811
13835
|
subtype: result.subtype,
|
|
13812
13836
|
isError: result.isError,
|
|
@@ -13834,13 +13858,13 @@ async function dispatchToCC(params) {
|
|
|
13834
13858
|
})
|
|
13835
13859
|
});
|
|
13836
13860
|
} catch (err) {
|
|
13837
|
-
log$
|
|
13861
|
+
log$13.warn("clarification gate: 兜底处理失败,回退到普通回复", {
|
|
13838
13862
|
sessionId: route.sessionId,
|
|
13839
13863
|
error: err instanceof Error ? err.message : String(err)
|
|
13840
13864
|
});
|
|
13841
13865
|
}
|
|
13842
13866
|
if (clarificationFallback.handled) {
|
|
13843
|
-
log$
|
|
13867
|
+
log$13.info("clarification gate: 普通文本提问已转为提问卡片", {
|
|
13844
13868
|
sessionId: route.sessionId,
|
|
13845
13869
|
operationId: clarificationFallback.operationId,
|
|
13846
13870
|
reason: clarificationFallback.reason,
|
|
@@ -13848,7 +13872,7 @@ async function dispatchToCC(params) {
|
|
|
13848
13872
|
});
|
|
13849
13873
|
return;
|
|
13850
13874
|
}
|
|
13851
|
-
if (clarificationFallback.matched) log$
|
|
13875
|
+
if (clarificationFallback.matched) log$13.warn("clarification gate: 提问卡片替换失败,回退到普通回复", {
|
|
13852
13876
|
sessionId: route.sessionId,
|
|
13853
13877
|
operationId: clarificationFallback.operationId
|
|
13854
13878
|
});
|
|
@@ -13861,7 +13885,7 @@ async function dispatchToCC(params) {
|
|
|
13861
13885
|
};
|
|
13862
13886
|
const handleBlockedEvent = (event) => {
|
|
13863
13887
|
const sanitized = sanitizeBlockedEvent(event);
|
|
13864
|
-
log$
|
|
13888
|
+
log$13.warn("CC onBlockedEvent", {
|
|
13865
13889
|
...sanitized,
|
|
13866
13890
|
traceId: runtimeConfig.runContext?.traceId,
|
|
13867
13891
|
scenarioId: runtimeConfig.runContext?.scenarioId
|
|
@@ -13869,7 +13893,7 @@ async function dispatchToCC(params) {
|
|
|
13869
13893
|
};
|
|
13870
13894
|
const callbacks = {
|
|
13871
13895
|
onTextDelta: (text) => {
|
|
13872
|
-
log$
|
|
13896
|
+
log$13.debug("CC onTextDelta", {
|
|
13873
13897
|
sessionId: route.sessionId,
|
|
13874
13898
|
deltaLen: text.length
|
|
13875
13899
|
});
|
|
@@ -13877,14 +13901,14 @@ async function dispatchToCC(params) {
|
|
|
13877
13901
|
feishuAccumText += text;
|
|
13878
13902
|
},
|
|
13879
13903
|
onThinkingDelta: (text) => {
|
|
13880
|
-
log$
|
|
13904
|
+
log$13.debug("CC onThinkingDelta", {
|
|
13881
13905
|
sessionId: route.sessionId,
|
|
13882
13906
|
deltaLen: text.length
|
|
13883
13907
|
});
|
|
13884
13908
|
bridge.onThinkingDelta(text);
|
|
13885
13909
|
},
|
|
13886
13910
|
onToolUseStart: (toolName, toolInput) => {
|
|
13887
|
-
log$
|
|
13911
|
+
log$13.info("CC onToolUseStart", {
|
|
13888
13912
|
sessionId: route.sessionId,
|
|
13889
13913
|
toolName
|
|
13890
13914
|
});
|
|
@@ -13892,7 +13916,7 @@ async function dispatchToCC(params) {
|
|
|
13892
13916
|
bridge.onToolUseStart(toolName, toolInput);
|
|
13893
13917
|
},
|
|
13894
13918
|
onToolResult: (toolUseId, result) => {
|
|
13895
|
-
log$
|
|
13919
|
+
log$13.info("CC onToolResult", {
|
|
13896
13920
|
sessionId: route.sessionId,
|
|
13897
13921
|
toolUseId,
|
|
13898
13922
|
traceId: runtimeConfig.runContext?.traceId,
|
|
@@ -13901,7 +13925,7 @@ async function dispatchToCC(params) {
|
|
|
13901
13925
|
bridge.onToolResult(toolUseId);
|
|
13902
13926
|
},
|
|
13903
13927
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
13904
|
-
log$
|
|
13928
|
+
log$13.debug("CC onToolProgress", {
|
|
13905
13929
|
sessionId: route.sessionId,
|
|
13906
13930
|
toolName,
|
|
13907
13931
|
elapsedSeconds
|
|
@@ -13909,7 +13933,7 @@ async function dispatchToCC(params) {
|
|
|
13909
13933
|
bridge.onToolProgress(toolName, elapsedSeconds);
|
|
13910
13934
|
},
|
|
13911
13935
|
onTurnEnd: (stopReason) => {
|
|
13912
|
-
log$
|
|
13936
|
+
log$13.info("CC onTurnEnd", {
|
|
13913
13937
|
sessionId: route.sessionId,
|
|
13914
13938
|
stopReason
|
|
13915
13939
|
});
|
|
@@ -13921,14 +13945,14 @@ async function dispatchToCC(params) {
|
|
|
13921
13945
|
},
|
|
13922
13946
|
onResult: (result) => {
|
|
13923
13947
|
resultHandlingPromise = handleResult(result).catch((err) => {
|
|
13924
|
-
log$
|
|
13948
|
+
log$13.error("CC onResult 收尾处理失败", {
|
|
13925
13949
|
sessionId: route.sessionId,
|
|
13926
13950
|
error: err instanceof Error ? err.message : String(err)
|
|
13927
13951
|
});
|
|
13928
13952
|
});
|
|
13929
13953
|
},
|
|
13930
13954
|
onError: (error) => {
|
|
13931
|
-
log$
|
|
13955
|
+
log$13.error("CC 进程错误", {
|
|
13932
13956
|
sessionId: route.sessionId,
|
|
13933
13957
|
traceId: runtimeConfig.runContext?.traceId,
|
|
13934
13958
|
error: error.message
|
|
@@ -13936,7 +13960,7 @@ async function dispatchToCC(params) {
|
|
|
13936
13960
|
cardController.onError(error, { kind: "cc-process" });
|
|
13937
13961
|
},
|
|
13938
13962
|
onAuditEvent: (event) => {
|
|
13939
|
-
log$
|
|
13963
|
+
log$13.info("CC onAuditEvent", {
|
|
13940
13964
|
...event,
|
|
13941
13965
|
inputSummary: summarizeForAudit(event.inputSummary),
|
|
13942
13966
|
resultSummary: summarizeForAudit(event.resultSummary)
|
|
@@ -13947,7 +13971,7 @@ async function dispatchToCC(params) {
|
|
|
13947
13971
|
onBlocked: handleBlockedEvent,
|
|
13948
13972
|
onBlockedEvent: handleBlockedEvent
|
|
13949
13973
|
};
|
|
13950
|
-
log$
|
|
13974
|
+
log$13.info("开始执行 CC 进程", {
|
|
13951
13975
|
sessionId: route.sessionId,
|
|
13952
13976
|
cwd: route.cwd,
|
|
13953
13977
|
promptLength: prompt.length,
|
|
@@ -13970,11 +13994,11 @@ async function dispatchToCC(params) {
|
|
|
13970
13994
|
onMcpCallback.on(`callback:${route.sessionId}`, mcpCallbackListener);
|
|
13971
13995
|
try {
|
|
13972
13996
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
13973
|
-
log$
|
|
13997
|
+
log$13.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
|
|
13974
13998
|
if (resultHandlingPromise) await resultHandlingPromise;
|
|
13975
13999
|
} catch (err) {
|
|
13976
14000
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
13977
|
-
log$
|
|
14001
|
+
log$13.error("CC 进程 executePrompt 调用异常", {
|
|
13978
14002
|
sessionId: route.sessionId,
|
|
13979
14003
|
error: errorMessage
|
|
13980
14004
|
});
|
|
@@ -13998,7 +14022,7 @@ async function dispatchToCC(params) {
|
|
|
13998
14022
|
*/
|
|
13999
14023
|
async function dispatchTeammateEval(params) {
|
|
14000
14024
|
const { chatId, prompt, account, sessionRouter, processManager } = params;
|
|
14001
|
-
log$
|
|
14025
|
+
log$13.info("dispatchTeammateEval 开始", {
|
|
14002
14026
|
chatId,
|
|
14003
14027
|
promptLength: prompt.length
|
|
14004
14028
|
});
|
|
@@ -14011,7 +14035,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14011
14035
|
userName: void 0
|
|
14012
14036
|
});
|
|
14013
14037
|
if (processManager.isSessionBusy?.(route.sessionId)) {
|
|
14014
|
-
log$
|
|
14038
|
+
log$13.info("dispatchTeammateEval 跳过:主 session 正在执行", {
|
|
14015
14039
|
chatId,
|
|
14016
14040
|
sessionId: route.sessionId
|
|
14017
14041
|
});
|
|
@@ -14074,7 +14098,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14074
14098
|
}
|
|
14075
14099
|
});
|
|
14076
14100
|
} catch (err) {
|
|
14077
|
-
log$
|
|
14101
|
+
log$13.error("teammate run context 构造失败,跳过评估", {
|
|
14078
14102
|
chatId,
|
|
14079
14103
|
error: err instanceof Error ? err.message : String(err)
|
|
14080
14104
|
});
|
|
@@ -14098,7 +14122,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14098
14122
|
sourceRefs: params.sourceRefs ?? (params.requestId ? [params.requestId] : [])
|
|
14099
14123
|
});
|
|
14100
14124
|
if (evalDecision.decision === "NO_REPLY") {
|
|
14101
|
-
log$
|
|
14125
|
+
log$13.info("teammate evaluator 决定静默", {
|
|
14102
14126
|
chatId,
|
|
14103
14127
|
evalSessionId,
|
|
14104
14128
|
summary: evalDecision.summary.slice(0, 120)
|
|
@@ -14132,7 +14156,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14132
14156
|
const confirmReply = () => {
|
|
14133
14157
|
if (confirmed) return;
|
|
14134
14158
|
confirmed = true;
|
|
14135
|
-
log$
|
|
14159
|
+
log$13.info("teammate 确认回复,创建流式卡片", {
|
|
14136
14160
|
chatId,
|
|
14137
14161
|
thinkingLen: thinkingBuffer.length,
|
|
14138
14162
|
textLen: fullTextBuffer.length
|
|
@@ -14189,7 +14213,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14189
14213
|
fullTextBuffer += text;
|
|
14190
14214
|
if (fullTextBuffer.trimStart().startsWith(NO_REPLY_TOKEN)) {
|
|
14191
14215
|
silenced = true;
|
|
14192
|
-
log$
|
|
14216
|
+
log$13.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
|
|
14193
14217
|
chatId,
|
|
14194
14218
|
bufferLen: fullTextBuffer.length
|
|
14195
14219
|
});
|
|
@@ -14222,7 +14246,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14222
14246
|
toolName: currentToolName,
|
|
14223
14247
|
toolCallId: toolUseId
|
|
14224
14248
|
});
|
|
14225
|
-
log$
|
|
14249
|
+
log$13.info("teammate onToolResult", {
|
|
14226
14250
|
chatId,
|
|
14227
14251
|
toolUseId,
|
|
14228
14252
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14240,7 +14264,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14240
14264
|
onTurnEnd: (stopReason) => {
|
|
14241
14265
|
finalStopReason = stopReason;
|
|
14242
14266
|
if (stopReason === "tool_use") {
|
|
14243
|
-
log$
|
|
14267
|
+
log$13.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
|
|
14244
14268
|
if (confirmed && bridge) bridge.onTurnEnd(stopReason);
|
|
14245
14269
|
return;
|
|
14246
14270
|
}
|
|
@@ -14248,7 +14272,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14248
14272
|
const trimmed = fullTextBuffer.trim();
|
|
14249
14273
|
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
|
|
14250
14274
|
silenced = true;
|
|
14251
|
-
log$
|
|
14275
|
+
log$13.info("teammate turnEnd 最终判断: 静默", {
|
|
14252
14276
|
chatId,
|
|
14253
14277
|
trimmed: trimmed.slice(0, 60)
|
|
14254
14278
|
});
|
|
@@ -14263,7 +14287,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14263
14287
|
resolve();
|
|
14264
14288
|
},
|
|
14265
14289
|
onError: (error) => {
|
|
14266
|
-
log$
|
|
14290
|
+
log$13.error("teammate 评估 CC 进程错误", {
|
|
14267
14291
|
chatId,
|
|
14268
14292
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
14269
14293
|
error: error.message
|
|
@@ -14271,14 +14295,14 @@ async function dispatchTeammateEval(params) {
|
|
|
14271
14295
|
resolve();
|
|
14272
14296
|
},
|
|
14273
14297
|
onAuditEvent: (event) => {
|
|
14274
|
-
log$
|
|
14298
|
+
log$13.info("teammate onAuditEvent", {
|
|
14275
14299
|
...event,
|
|
14276
14300
|
inputSummary: summarizeForAudit(event.inputSummary),
|
|
14277
14301
|
resultSummary: summarizeForAudit(event.resultSummary)
|
|
14278
14302
|
});
|
|
14279
14303
|
},
|
|
14280
14304
|
onRuntimeEvent: (event) => {
|
|
14281
|
-
log$
|
|
14305
|
+
log$13.info("teammate onRuntimeEvent", {
|
|
14282
14306
|
type: event.type,
|
|
14283
14307
|
chatId,
|
|
14284
14308
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14289,7 +14313,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14289
14313
|
});
|
|
14290
14314
|
},
|
|
14291
14315
|
onScenarioEvent: (event) => {
|
|
14292
|
-
log$
|
|
14316
|
+
log$13.info("teammate onScenarioEvent", {
|
|
14293
14317
|
type: event.type,
|
|
14294
14318
|
chatId,
|
|
14295
14319
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14301,28 +14325,28 @@ async function dispatchTeammateEval(params) {
|
|
|
14301
14325
|
},
|
|
14302
14326
|
onBlocked: (event) => {
|
|
14303
14327
|
const sanitized = sanitizeBlockedEvent(event);
|
|
14304
|
-
log$
|
|
14328
|
+
log$13.warn("teammate onBlocked", {
|
|
14305
14329
|
...sanitized,
|
|
14306
14330
|
traceId: mainRuntimeConfig.runContext?.traceId
|
|
14307
14331
|
});
|
|
14308
14332
|
},
|
|
14309
14333
|
onBlockedEvent: (event) => {
|
|
14310
14334
|
const sanitized = sanitizeBlockedEvent(event);
|
|
14311
|
-
log$
|
|
14335
|
+
log$13.warn("teammate onBlockedEvent", {
|
|
14312
14336
|
...sanitized,
|
|
14313
14337
|
traceId: mainRuntimeConfig.runContext?.traceId
|
|
14314
14338
|
});
|
|
14315
14339
|
}
|
|
14316
14340
|
};
|
|
14317
14341
|
processManager.executePrompt(mainRuntimeConfig, callbacks).catch((err) => {
|
|
14318
|
-
log$
|
|
14342
|
+
log$13.error("teammate executePrompt 异常", {
|
|
14319
14343
|
chatId,
|
|
14320
14344
|
error: err instanceof Error ? err.message : String(err)
|
|
14321
14345
|
});
|
|
14322
14346
|
resolve();
|
|
14323
14347
|
});
|
|
14324
14348
|
});
|
|
14325
|
-
log$
|
|
14349
|
+
log$13.info("dispatchTeammateEval 完成", {
|
|
14326
14350
|
chatId,
|
|
14327
14351
|
confirmed,
|
|
14328
14352
|
silenced,
|
|
@@ -14333,7 +14357,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14333
14357
|
return { replied: confirmed };
|
|
14334
14358
|
} catch (err) {
|
|
14335
14359
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
14336
|
-
log$
|
|
14360
|
+
log$13.error("dispatchTeammateEval 异常", {
|
|
14337
14361
|
chatId,
|
|
14338
14362
|
error: errorMessage
|
|
14339
14363
|
});
|
|
@@ -14357,7 +14381,7 @@ async function runTeammateDecisionEval(params) {
|
|
|
14357
14381
|
resolve();
|
|
14358
14382
|
},
|
|
14359
14383
|
onError: (error) => {
|
|
14360
|
-
log$
|
|
14384
|
+
log$13.error("teammate evaluator 执行错误", {
|
|
14361
14385
|
chatId,
|
|
14362
14386
|
sessionId: runtimeConfig.sessionId,
|
|
14363
14387
|
error: error.message
|
|
@@ -14365,21 +14389,21 @@ async function runTeammateDecisionEval(params) {
|
|
|
14365
14389
|
resolve();
|
|
14366
14390
|
},
|
|
14367
14391
|
onToolUseStart: (toolName, toolInput) => {
|
|
14368
|
-
log$
|
|
14392
|
+
log$13.info("teammate evaluator toolUseStart", {
|
|
14369
14393
|
chatId,
|
|
14370
14394
|
toolName,
|
|
14371
14395
|
toolInput: summarizeForAudit(toolInput)
|
|
14372
14396
|
});
|
|
14373
14397
|
},
|
|
14374
14398
|
onToolResult: (toolUseId, result) => {
|
|
14375
|
-
log$
|
|
14399
|
+
log$13.info("teammate evaluator toolResult", {
|
|
14376
14400
|
chatId,
|
|
14377
14401
|
toolUseId,
|
|
14378
14402
|
result: summarizeForAudit(result)
|
|
14379
14403
|
});
|
|
14380
14404
|
}
|
|
14381
14405
|
}).catch((err) => {
|
|
14382
|
-
log$
|
|
14406
|
+
log$13.error("teammate evaluator executePrompt 异常", {
|
|
14383
14407
|
chatId,
|
|
14384
14408
|
sessionId: runtimeConfig.sessionId,
|
|
14385
14409
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -14394,7 +14418,7 @@ async function runTeammateDecisionEval(params) {
|
|
|
14394
14418
|
const decision = trimmed !== "" && trimmed !== placeholderText && firstToken === "HANDOFF_TO_MAIN" ? "HANDOFF_TO_MAIN" : "NO_REPLY";
|
|
14395
14419
|
const cleanedSummary = trimmed.replace(/^(HANDOFF_TO_MAIN|NO_REPLY)\b[\s::-]*/i, "").trim();
|
|
14396
14420
|
const summary = malformed ? "NO_REPLY (malformed evaluator output)" : cleanedSummary.slice(0, 600) || `${decision} (thinking ${thinkingLength} chars)`;
|
|
14397
|
-
log$
|
|
14421
|
+
log$13.info("teammate evaluator 完成", {
|
|
14398
14422
|
chatId,
|
|
14399
14423
|
sessionId: runtimeConfig.sessionId,
|
|
14400
14424
|
decision,
|
|
@@ -14452,7 +14476,7 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14452
14476
|
try {
|
|
14453
14477
|
if (req.tool === "ask_user") {
|
|
14454
14478
|
const params = req.params;
|
|
14455
|
-
log$
|
|
14479
|
+
log$13.info("[MCP] 收到 ask_user 回调,准备发送提问卡片", {
|
|
14456
14480
|
requestId: req.requestId,
|
|
14457
14481
|
sessionId: req.sessionId,
|
|
14458
14482
|
question: params.question?.slice(0, 50)
|
|
@@ -14465,14 +14489,14 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14465
14489
|
});
|
|
14466
14490
|
mcpOperationMap.set(operationId, req.requestId);
|
|
14467
14491
|
await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
|
|
14468
|
-
log$
|
|
14492
|
+
log$13.info("[MCP] 提问卡片已发送", {
|
|
14469
14493
|
requestId: req.requestId,
|
|
14470
14494
|
operationId,
|
|
14471
14495
|
chatId
|
|
14472
14496
|
});
|
|
14473
14497
|
} else if (req.tool === "request_permission") {
|
|
14474
14498
|
const params = req.params;
|
|
14475
|
-
log$
|
|
14499
|
+
log$13.info("[MCP] 收到 request_permission 回调,准备发送授权卡片", {
|
|
14476
14500
|
requestId: req.requestId,
|
|
14477
14501
|
scopes: params.scopes
|
|
14478
14502
|
});
|
|
@@ -14486,14 +14510,14 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14486
14510
|
});
|
|
14487
14511
|
mcpOperationMap.set(operationId, req.requestId);
|
|
14488
14512
|
await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
|
|
14489
|
-
log$
|
|
14513
|
+
log$13.info("[MCP] 授权卡片已发送", {
|
|
14490
14514
|
requestId: req.requestId,
|
|
14491
14515
|
operationId,
|
|
14492
14516
|
chatId
|
|
14493
14517
|
});
|
|
14494
14518
|
}
|
|
14495
14519
|
} catch (err) {
|
|
14496
|
-
log$
|
|
14520
|
+
log$13.error("[MCP] 交互卡片发送失败", {
|
|
14497
14521
|
requestId: req.requestId,
|
|
14498
14522
|
tool: req.tool,
|
|
14499
14523
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -14518,7 +14542,7 @@ async function sendInteractiveCardViaCardKit(card, chatId, account, replyInThrea
|
|
|
14518
14542
|
} });
|
|
14519
14543
|
const cardId = createResp?.data?.card_id;
|
|
14520
14544
|
if (!cardId) throw new Error(`CardKit card.create 失败: code=${createResp?.code}, msg=${createResp?.msg}`);
|
|
14521
|
-
log$
|
|
14545
|
+
log$13.info("[MCP] CardKit 卡片实体已创建", { cardId });
|
|
14522
14546
|
const contentPayload = JSON.stringify({
|
|
14523
14547
|
type: "card",
|
|
14524
14548
|
data: { card_id: cardId }
|
|
@@ -16048,7 +16072,7 @@ const convertLocation = (raw) => {
|
|
|
16048
16072
|
* injected via callbacks in `ConvertContext`. Callers are responsible
|
|
16049
16073
|
* for creating the appropriate callbacks (UAT / TAT / event push).
|
|
16050
16074
|
*/
|
|
16051
|
-
const log$
|
|
16075
|
+
const log$12 = larkLogger("converters/merge-forward");
|
|
16052
16076
|
/**
|
|
16053
16077
|
* Recursively expand a merge_forward message.
|
|
16054
16078
|
*
|
|
@@ -16063,7 +16087,7 @@ const log$11 = larkLogger("converters/merge-forward");
|
|
|
16063
16087
|
const convertMergeForward = async (_raw, ctx) => {
|
|
16064
16088
|
const { accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent } = ctx;
|
|
16065
16089
|
if (!fetchSubMessages) {
|
|
16066
|
-
log$
|
|
16090
|
+
log$12.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
|
|
16067
16091
|
messageId,
|
|
16068
16092
|
accountId
|
|
16069
16093
|
});
|
|
@@ -16081,23 +16105,23 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
16081
16105
|
let items;
|
|
16082
16106
|
try {
|
|
16083
16107
|
items = await fetchSubMessages(messageId);
|
|
16084
|
-
log$
|
|
16108
|
+
log$12.info("fetchSubMessages 成功", {
|
|
16085
16109
|
messageId,
|
|
16086
16110
|
itemCount: items.length
|
|
16087
16111
|
});
|
|
16088
16112
|
} catch (error) {
|
|
16089
|
-
log$
|
|
16113
|
+
log$12.error("fetch sub-messages failed", {
|
|
16090
16114
|
messageId,
|
|
16091
16115
|
error: error instanceof Error ? error.message : String(error)
|
|
16092
16116
|
});
|
|
16093
16117
|
return "<forwarded_messages/>";
|
|
16094
16118
|
}
|
|
16095
16119
|
if (items.length === 0) {
|
|
16096
|
-
log$
|
|
16120
|
+
log$12.warn("fetchSubMessages 返回空 items", { messageId });
|
|
16097
16121
|
return "<forwarded_messages/>";
|
|
16098
16122
|
}
|
|
16099
16123
|
const childrenMap = buildChildrenMap(items, messageId);
|
|
16100
|
-
log$
|
|
16124
|
+
log$12.info("buildChildrenMap 完成", {
|
|
16101
16125
|
messageId,
|
|
16102
16126
|
parentKeys: [...childrenMap.keys()],
|
|
16103
16127
|
childCounts: [...childrenMap.entries()].map(([k, v]) => `${k}:${v.length}`)
|
|
@@ -16106,7 +16130,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
16106
16130
|
if (senderIds.length > 0 && batchResolveNames) try {
|
|
16107
16131
|
await batchResolveNames(senderIds);
|
|
16108
16132
|
} catch (err) {
|
|
16109
|
-
log$
|
|
16133
|
+
log$12.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
|
|
16110
16134
|
}
|
|
16111
16135
|
return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
|
|
16112
16136
|
}
|
|
@@ -16186,7 +16210,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
|
|
|
16186
16210
|
const indented = indentLines(content, " ");
|
|
16187
16211
|
parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
|
|
16188
16212
|
} catch (err) {
|
|
16189
|
-
log$
|
|
16213
|
+
log$12.warn("failed to convert sub-message", {
|
|
16190
16214
|
messageId: item.message_id,
|
|
16191
16215
|
msgType: item.msg_type ?? "unknown",
|
|
16192
16216
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -16400,7 +16424,7 @@ async function convertMessageContent(raw, messageType, ctx) {
|
|
|
16400
16424
|
}
|
|
16401
16425
|
//#endregion
|
|
16402
16426
|
//#region src/messaging/inbound/parse-io.ts
|
|
16403
|
-
const log$
|
|
16427
|
+
const log$11 = larkLogger("inbound/parse-io");
|
|
16404
16428
|
/**
|
|
16405
16429
|
* 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
|
|
16406
16430
|
* 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
|
|
@@ -16420,7 +16444,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
16420
16444
|
}
|
|
16421
16445
|
}))?.data?.items?.[0]?.body?.content ?? void 0;
|
|
16422
16446
|
} catch (err) {
|
|
16423
|
-
log$
|
|
16447
|
+
log$11.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16424
16448
|
return;
|
|
16425
16449
|
}
|
|
16426
16450
|
}
|
|
@@ -16435,7 +16459,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
16435
16459
|
*/
|
|
16436
16460
|
function createFetchSubMessages(larkClient) {
|
|
16437
16461
|
return async (msgId) => {
|
|
16438
|
-
log$
|
|
16462
|
+
log$11.info("fetchSubMessages 请求", {
|
|
16439
16463
|
msgId,
|
|
16440
16464
|
url: `/open-apis/im/v1/messages/${msgId}`
|
|
16441
16465
|
});
|
|
@@ -16447,7 +16471,7 @@ function createFetchSubMessages(larkClient) {
|
|
|
16447
16471
|
card_msg_content_type: "raw_card_content"
|
|
16448
16472
|
}
|
|
16449
16473
|
});
|
|
16450
|
-
log$
|
|
16474
|
+
log$11.info("fetchSubMessages 响应", {
|
|
16451
16475
|
msgId,
|
|
16452
16476
|
code: response?.code,
|
|
16453
16477
|
msg: response?.msg,
|
|
@@ -16464,11 +16488,11 @@ function createFetchSubMessages(larkClient) {
|
|
|
16464
16488
|
* the account and log function.
|
|
16465
16489
|
*/
|
|
16466
16490
|
function createParseResolveNames(account) {
|
|
16467
|
-
return createBatchResolveNames(account, (...args) => log$
|
|
16491
|
+
return createBatchResolveNames(account, (...args) => log$11.info(args.map(String).join(" ")));
|
|
16468
16492
|
}
|
|
16469
16493
|
//#endregion
|
|
16470
16494
|
//#region src/messaging/inbound/parse.ts
|
|
16471
|
-
const log$
|
|
16495
|
+
const log$10 = larkLogger("inbound/parse");
|
|
16472
16496
|
/**
|
|
16473
16497
|
* Parse a raw Feishu message event into a normalised MessageContext.
|
|
16474
16498
|
*
|
|
@@ -16518,7 +16542,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
16518
16542
|
const fullContent = await fetchCardContent(event.message.message_id, larkClient);
|
|
16519
16543
|
if (fullContent) {
|
|
16520
16544
|
effectiveContent = fullContent;
|
|
16521
|
-
log$
|
|
16545
|
+
log$10.info("replaced interactive content with full v2 card data");
|
|
16522
16546
|
}
|
|
16523
16547
|
}
|
|
16524
16548
|
const convertCtx = {
|
|
@@ -16558,6 +16582,219 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
16558
16582
|
};
|
|
16559
16583
|
}
|
|
16560
16584
|
//#endregion
|
|
16585
|
+
//#region src/messaging/shared/message-lookup.ts
|
|
16586
|
+
const log$9 = larkLogger("shared/message-lookup");
|
|
16587
|
+
/**
|
|
16588
|
+
* Retrieve a single message by its ID from the Feishu IM API.
|
|
16589
|
+
*
|
|
16590
|
+
* Returns a normalised {@link FeishuMessageInfo} object, or `null` if the
|
|
16591
|
+
* message cannot be found or the API returns an error.
|
|
16592
|
+
*
|
|
16593
|
+
* @param params.cfg - Plugin configuration with Feishu credentials.
|
|
16594
|
+
* @param params.messageId - The message ID to fetch.
|
|
16595
|
+
* @param params.accountId - Optional account identifier for multi-account setups.
|
|
16596
|
+
*/
|
|
16597
|
+
async function getMessageFeishu(params) {
|
|
16598
|
+
const { cfg, messageId, accountId, expandForward } = params;
|
|
16599
|
+
const larkClient = LarkClient.fromCfg(cfg, accountId);
|
|
16600
|
+
const sdk = larkClient.sdk;
|
|
16601
|
+
try {
|
|
16602
|
+
const requestOpts = {
|
|
16603
|
+
method: "GET",
|
|
16604
|
+
url: `/open-apis/im/v1/messages/mget`,
|
|
16605
|
+
params: {
|
|
16606
|
+
message_ids: messageId,
|
|
16607
|
+
user_id_type: "open_id",
|
|
16608
|
+
card_msg_content_type: "raw_card_content"
|
|
16609
|
+
}
|
|
16610
|
+
};
|
|
16611
|
+
const items = (await sdk.request(requestOpts))?.data?.items;
|
|
16612
|
+
if (!items || items.length === 0) {
|
|
16613
|
+
log$9.info(`getMessageFeishu: no items returned for ${messageId}`);
|
|
16614
|
+
return null;
|
|
16615
|
+
}
|
|
16616
|
+
const expandCtx = expandForward ? {
|
|
16617
|
+
cfg,
|
|
16618
|
+
accountId,
|
|
16619
|
+
fetchSubMessages: async (msgId) => {
|
|
16620
|
+
const res = await larkClient.sdk.request({
|
|
16621
|
+
method: "GET",
|
|
16622
|
+
url: `/open-apis/im/v1/messages/${msgId}`,
|
|
16623
|
+
params: {
|
|
16624
|
+
user_id_type: "open_id",
|
|
16625
|
+
card_msg_content_type: "raw_card_content"
|
|
16626
|
+
}
|
|
16627
|
+
});
|
|
16628
|
+
if (res?.code !== 0) throw new Error(`API error: code=${res?.code} msg=${res?.msg}`);
|
|
16629
|
+
return res?.data?.items ?? [];
|
|
16630
|
+
},
|
|
16631
|
+
batchResolveNames: createBatchResolveNames(getLarkAccount(cfg, accountId), (...args) => log$9.info(args.map(String).join(" ")))
|
|
16632
|
+
} : void 0;
|
|
16633
|
+
return await parseMessageItem(items[0], messageId, expandCtx);
|
|
16634
|
+
} catch (error) {
|
|
16635
|
+
log$9.error(`get message failed (${messageId}): ${error instanceof Error ? error.message : String(error)}`);
|
|
16636
|
+
return null;
|
|
16637
|
+
}
|
|
16638
|
+
}
|
|
16639
|
+
/**
|
|
16640
|
+
* Parse a single message item from the Feishu IM API response into a
|
|
16641
|
+
* normalised {@link FeishuMessageInfo}.
|
|
16642
|
+
*
|
|
16643
|
+
* Content parsing is delegated to the shared converter system so that
|
|
16644
|
+
* every message-type mapping is defined in exactly one place.
|
|
16645
|
+
*/
|
|
16646
|
+
async function parseMessageItem(msg, fallbackMessageId, expandCtx) {
|
|
16647
|
+
const msgType = msg.msg_type ?? "text";
|
|
16648
|
+
const rawContent = msg.body?.content ?? "{}";
|
|
16649
|
+
const messageId = msg.message_id ?? fallbackMessageId;
|
|
16650
|
+
const acctId = expandCtx?.accountId;
|
|
16651
|
+
const { content } = await convertMessageContent(rawContent, msgType, {
|
|
16652
|
+
...buildConvertContextFromItem(msg, fallbackMessageId, acctId),
|
|
16653
|
+
cfg: expandCtx?.cfg,
|
|
16654
|
+
accountId: acctId,
|
|
16655
|
+
fetchSubMessages: expandCtx?.fetchSubMessages,
|
|
16656
|
+
batchResolveNames: expandCtx?.batchResolveNames
|
|
16657
|
+
});
|
|
16658
|
+
const senderId = msg.sender?.id ?? void 0;
|
|
16659
|
+
const senderType = msg.sender?.sender_type ?? void 0;
|
|
16660
|
+
const senderName = senderId && acctId ? getUserNameCache(acctId).get(senderId) : void 0;
|
|
16661
|
+
return {
|
|
16662
|
+
messageId,
|
|
16663
|
+
chatId: msg.chat_id ?? "",
|
|
16664
|
+
chatType: msg.chat_type ?? void 0,
|
|
16665
|
+
senderId,
|
|
16666
|
+
senderName,
|
|
16667
|
+
senderType,
|
|
16668
|
+
content,
|
|
16669
|
+
contentType: msgType,
|
|
16670
|
+
createTime: msg.create_time ? parseInt(String(msg.create_time), 10) : void 0,
|
|
16671
|
+
threadId: msg.thread_id || void 0
|
|
16672
|
+
};
|
|
16673
|
+
}
|
|
16674
|
+
//#endregion
|
|
16675
|
+
//#region src/messaging/inbound/enrich.ts
|
|
16676
|
+
/** 从飞书 IM API 获取指定消息的 parent_id 和 root_id 字段 */
|
|
16677
|
+
async function getMessageParentId(params) {
|
|
16678
|
+
const { messageId, account, accountScopedCfg, log } = params;
|
|
16679
|
+
try {
|
|
16680
|
+
const items = (await LarkClient.fromCfg(accountScopedCfg, account.accountId).sdk.request({
|
|
16681
|
+
method: "GET",
|
|
16682
|
+
url: `/open-apis/im/v1/messages/mget`,
|
|
16683
|
+
params: {
|
|
16684
|
+
message_ids: messageId,
|
|
16685
|
+
user_id_type: "open_id"
|
|
16686
|
+
}
|
|
16687
|
+
}))?.data?.items;
|
|
16688
|
+
if (!items || items.length === 0) {
|
|
16689
|
+
log(`feishu[${account.accountId}]: getMessageParentId: no items returned for ${messageId}`);
|
|
16690
|
+
return null;
|
|
16691
|
+
}
|
|
16692
|
+
const item = items[0];
|
|
16693
|
+
log(`feishu[${account.accountId}]: getMessageParentId(${messageId}): parent_id=${item.parent_id || "none"}, root_id=${item.root_id || "none"}`);
|
|
16694
|
+
return {
|
|
16695
|
+
parentId: item.parent_id || void 0,
|
|
16696
|
+
rootId: item.root_id || void 0
|
|
16697
|
+
};
|
|
16698
|
+
} catch (error) {
|
|
16699
|
+
log(`feishu[${account.accountId}]: getMessageParentId failed (${messageId}): ${error instanceof Error ? error.message : String(error)}`);
|
|
16700
|
+
return null;
|
|
16701
|
+
}
|
|
16702
|
+
}
|
|
16703
|
+
/** 将消息格式化为带缩进层级的回复链文本行 */
|
|
16704
|
+
function formatChainMessage(msg, depth) {
|
|
16705
|
+
const prefix = `[message_id=${msg.messageId}]`;
|
|
16706
|
+
const text = msg.senderName ? `${prefix} ${msg.senderName}: ${msg.content}` : `${prefix} ${msg.content}`;
|
|
16707
|
+
if (depth === 0) return text;
|
|
16708
|
+
return `${" ".repeat(depth)}↳ ${text}`;
|
|
16709
|
+
}
|
|
16710
|
+
/**
|
|
16711
|
+
* 递归解析回复链上下文,支持 rootId 追溯和 N 层 parentId 递归。
|
|
16712
|
+
*
|
|
16713
|
+
* 解析逻辑:
|
|
16714
|
+
* 1. 如果存在 rootId 且与 parentId 不同,先获取话题根消息作为链的起点
|
|
16715
|
+
* 2. 从 parentId 开始,递归向上追溯每条消息的 parentId,最多追溯 maxQuoteDepth 层
|
|
16716
|
+
* 3. 对结果去重后按时间顺序(最旧在前)输出完整的对话链上下文
|
|
16717
|
+
*
|
|
16718
|
+
* 输出格式:
|
|
16719
|
+
* ```
|
|
16720
|
+
* [回复链上下文]:
|
|
16721
|
+
* [message_id=xxx] senderName: content
|
|
16722
|
+
* ↳ [message_id=yyy] senderName2: content2
|
|
16723
|
+
* ↳ [message_id=zzz] senderName3: content3
|
|
16724
|
+
* ```
|
|
16725
|
+
*/
|
|
16726
|
+
async function resolveQuotedContent(params) {
|
|
16727
|
+
const { ctx, accountScopedCfg, account, log, maxQuoteDepth = 3 } = params;
|
|
16728
|
+
if (!ctx.parentId) return void 0;
|
|
16729
|
+
log(`feishu[${account.accountId}]: resolveQuotedContent 开始, parentId=${ctx.parentId}, rootId=${ctx.rootId || "none"}, maxDepth=${maxQuoteDepth}`);
|
|
16730
|
+
const chainMessages = [];
|
|
16731
|
+
const visited = /* @__PURE__ */ new Set();
|
|
16732
|
+
let currentMsgId = ctx.parentId;
|
|
16733
|
+
let depth = 0;
|
|
16734
|
+
while (currentMsgId && depth < maxQuoteDepth) {
|
|
16735
|
+
if (visited.has(currentMsgId)) {
|
|
16736
|
+
log(`feishu[${account.accountId}]: 检测到循环引用, messageId=${currentMsgId}, 停止递归`);
|
|
16737
|
+
break;
|
|
16738
|
+
}
|
|
16739
|
+
visited.add(currentMsgId);
|
|
16740
|
+
try {
|
|
16741
|
+
const msg = await getMessageFeishu({
|
|
16742
|
+
cfg: accountScopedCfg,
|
|
16743
|
+
messageId: currentMsgId,
|
|
16744
|
+
accountId: account.accountId,
|
|
16745
|
+
expandForward: true
|
|
16746
|
+
});
|
|
16747
|
+
if (!msg) {
|
|
16748
|
+
log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层消息获取为空, messageId=${currentMsgId}, 停止递归`);
|
|
16749
|
+
break;
|
|
16750
|
+
}
|
|
16751
|
+
log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层: messageId=${currentMsgId}, content=${msg.content?.slice(0, 80)}`);
|
|
16752
|
+
chainMessages.push(msg);
|
|
16753
|
+
const parentInfo = await getMessageParentId({
|
|
16754
|
+
messageId: currentMsgId,
|
|
16755
|
+
account,
|
|
16756
|
+
accountScopedCfg,
|
|
16757
|
+
log
|
|
16758
|
+
});
|
|
16759
|
+
if (!parentInfo?.parentId) {
|
|
16760
|
+
log(`feishu[${account.accountId}]: 第 ${depth + 1} 层消息无 parentId, 到达链顶端`);
|
|
16761
|
+
break;
|
|
16762
|
+
}
|
|
16763
|
+
currentMsgId = parentInfo.parentId;
|
|
16764
|
+
depth++;
|
|
16765
|
+
} catch (err) {
|
|
16766
|
+
log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层获取失败, messageId=${currentMsgId}: ${String(err)}, 停止递归`);
|
|
16767
|
+
break;
|
|
16768
|
+
}
|
|
16769
|
+
}
|
|
16770
|
+
if (depth >= maxQuoteDepth) log(`feishu[${account.accountId}]: 已达到最大递归深度 ${maxQuoteDepth}, 停止追溯`);
|
|
16771
|
+
if (ctx.rootId && ctx.rootId !== ctx.parentId && !visited.has(ctx.rootId)) try {
|
|
16772
|
+
const rootMsg = await getMessageFeishu({
|
|
16773
|
+
cfg: accountScopedCfg,
|
|
16774
|
+
messageId: ctx.rootId,
|
|
16775
|
+
accountId: account.accountId,
|
|
16776
|
+
expandForward: true
|
|
16777
|
+
});
|
|
16778
|
+
if (rootMsg) {
|
|
16779
|
+
log(`feishu[${account.accountId}]: 获取到话题根消息: rootId=${ctx.rootId}, content=${rootMsg.content?.slice(0, 80)}`);
|
|
16780
|
+
chainMessages.push(rootMsg);
|
|
16781
|
+
visited.add(ctx.rootId);
|
|
16782
|
+
} else log(`feishu[${account.accountId}]: 话题根消息获取为空, rootId=${ctx.rootId}`);
|
|
16783
|
+
} catch (err) {
|
|
16784
|
+
log(`feishu[${account.accountId}]: 话题根消息获取失败, rootId=${ctx.rootId}: ${String(err)}`);
|
|
16785
|
+
}
|
|
16786
|
+
if (chainMessages.length === 0) {
|
|
16787
|
+
log(`feishu[${account.accountId}]: 回复链解析完成, 未获取到任何消息`);
|
|
16788
|
+
return;
|
|
16789
|
+
}
|
|
16790
|
+
chainMessages.reverse();
|
|
16791
|
+
const lines = ["[回复链上下文]:"];
|
|
16792
|
+
for (let i = 0; i < chainMessages.length; i++) lines.push(formatChainMessage(chainMessages[i], i));
|
|
16793
|
+
const result = lines.join("\n");
|
|
16794
|
+
log(`feishu[${account.accountId}]: 回复链解析完成, 共 ${chainMessages.length} 条消息`);
|
|
16795
|
+
return result;
|
|
16796
|
+
}
|
|
16797
|
+
//#endregion
|
|
16561
16798
|
//#region src/messaging/inbound/dedup.ts
|
|
16562
16799
|
const DEFAULT_TTL_MS = 720 * 60 * 1e3;
|
|
16563
16800
|
const DEFAULT_MAX_ENTRIES = 5e3;
|
|
@@ -18187,6 +18424,30 @@ async function main() {
|
|
|
18187
18424
|
brand: "feishu",
|
|
18188
18425
|
config: {}
|
|
18189
18426
|
};
|
|
18427
|
+
const accountScopedCfg = { channels: { feishu: {
|
|
18428
|
+
...account.config,
|
|
18429
|
+
appId: account.appId,
|
|
18430
|
+
appSecret: account.appSecret,
|
|
18431
|
+
domain: account.brand
|
|
18432
|
+
} } };
|
|
18433
|
+
const resolveQuotedContentForMessage = async (ctx) => {
|
|
18434
|
+
if (!ctx.parentId) return void 0;
|
|
18435
|
+
try {
|
|
18436
|
+
return await resolveQuotedContent({
|
|
18437
|
+
ctx,
|
|
18438
|
+
accountScopedCfg,
|
|
18439
|
+
account,
|
|
18440
|
+
log: (...args) => logger.info("resolveQuotedContent", { detail: args.map(String).join(" ") })
|
|
18441
|
+
});
|
|
18442
|
+
} catch (err) {
|
|
18443
|
+
logger.warn("引用消息解析失败,继续仅处理当前消息", {
|
|
18444
|
+
msgId: ctx.messageId,
|
|
18445
|
+
parentId: ctx.parentId,
|
|
18446
|
+
error: err instanceof Error ? err.message : String(err)
|
|
18447
|
+
});
|
|
18448
|
+
return;
|
|
18449
|
+
}
|
|
18450
|
+
};
|
|
18190
18451
|
const larkClient = LarkClient.fromAccount(account);
|
|
18191
18452
|
const probeResult = await larkClient.probe();
|
|
18192
18453
|
if (probeResult.ok) logger.info("Bot 身份探测成功", {
|
|
@@ -18325,7 +18586,7 @@ async function main() {
|
|
|
18325
18586
|
});
|
|
18326
18587
|
if (nameResult.name) parsed.senderName = nameResult.name;
|
|
18327
18588
|
} catch {}
|
|
18328
|
-
const formattedText =
|
|
18589
|
+
const formattedText = formatPromptForCC(parsed, true, await resolveQuotedContentForMessage(parsed));
|
|
18329
18590
|
teammateBuffer.push(parsed.chatId, {
|
|
18330
18591
|
formattedText,
|
|
18331
18592
|
bufferedAt: Date.now(),
|
|
@@ -18377,6 +18638,7 @@ async function main() {
|
|
|
18377
18638
|
workspaceRoot
|
|
18378
18639
|
});
|
|
18379
18640
|
await ensureUserDirectory(userContext);
|
|
18641
|
+
const quotedContent = await resolveQuotedContentForMessage(parsed);
|
|
18380
18642
|
await dispatchToCC({
|
|
18381
18643
|
ctx: parsed,
|
|
18382
18644
|
account,
|
|
@@ -18386,7 +18648,8 @@ async function main() {
|
|
|
18386
18648
|
userContext,
|
|
18387
18649
|
placeholderCardId,
|
|
18388
18650
|
placeholderMessageId,
|
|
18389
|
-
messageStore
|
|
18651
|
+
messageStore,
|
|
18652
|
+
quotedContent
|
|
18390
18653
|
});
|
|
18391
18654
|
if (threadId) markBotInvolvedInThread(threadId);
|
|
18392
18655
|
} catch (err) {
|