@vibe-lark/larkpal 0.1.57 → 0.1.59
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 +834 -695
- 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,43 +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$
|
|
2990
|
-
try {
|
|
2991
|
-
const { loadSkillsFromDir, registerSkills } = await import("@vibe-lark/larkpal-agent");
|
|
2992
|
-
const { fileURLToPath } = await import("node:url");
|
|
2993
|
-
const skillsDir = join(fileURLToPath(import.meta.resolve("@vibe-lark/larkpal-agent")).replace(/\/dist\/index\.js$/, ""), "src", "skills", "definitions");
|
|
2994
|
-
const skills = await loadSkillsFromDir(skillsDir);
|
|
2995
|
-
if (skills.length > 0) {
|
|
2996
|
-
registerSkills(registry, skills, llm);
|
|
2997
|
-
const skillTools = registry.getAll().filter((t) => t.name.startsWith("skill:"));
|
|
2998
|
-
adapter.registerTools(skillTools);
|
|
2999
|
-
log$29.info("Skill Meta-Tools 注册完成", {
|
|
3000
|
-
skillCount: skills.length,
|
|
3001
|
-
skillNames: skills.map((s) => s.name)
|
|
3002
|
-
});
|
|
3003
|
-
} else log$29.info("未找到 Skill 定义文件", { skillsDir });
|
|
3004
|
-
} catch (err) {
|
|
3005
|
-
log$29.warn("Skill 加载失败(非阻塞)", { error: err?.message });
|
|
3006
|
-
}
|
|
2787
|
+
else log$30.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
|
|
3007
2788
|
return adapter;
|
|
3008
2789
|
}
|
|
3009
2790
|
//#endregion
|
|
@@ -3340,6 +3121,7 @@ function createChatAuthRouter() {
|
|
|
3340
3121
|
//#endregion
|
|
3341
3122
|
//#region src/gateway/agent-context.ts
|
|
3342
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;
|
|
3343
3125
|
const MAX_SUMMARY_STRING_LENGTH = 200;
|
|
3344
3126
|
const PROTECTED_CONTEXT_HEADERS = new Set([
|
|
3345
3127
|
"x-tenant-key",
|
|
@@ -3462,7 +3244,7 @@ function summarizeForAudit(value) {
|
|
|
3462
3244
|
};
|
|
3463
3245
|
if (typeof value === "object") {
|
|
3464
3246
|
const result = {};
|
|
3465
|
-
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]";
|
|
3466
3248
|
else result[key] = summarizeForAudit(entry);
|
|
3467
3249
|
return result;
|
|
3468
3250
|
}
|
|
@@ -3568,7 +3350,8 @@ function buildExecutionPolicy(policy, limits) {
|
|
|
3568
3350
|
maxTurns: limits.maxTurns ?? policy?.maxTurns,
|
|
3569
3351
|
maxToolCalls: policy?.maxToolCalls,
|
|
3570
3352
|
maxRuntimeMs: policy?.maxRuntimeMs,
|
|
3571
|
-
maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd
|
|
3353
|
+
maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd,
|
|
3354
|
+
turnPolicy: policy?.turnPolicy
|
|
3572
3355
|
};
|
|
3573
3356
|
}
|
|
3574
3357
|
function buildUserContext$1(identity) {
|
|
@@ -3636,7 +3419,7 @@ function summarizeString(value) {
|
|
|
3636
3419
|
* POST /api/chat/sessions — 创建新会话
|
|
3637
3420
|
* DELETE /api/chat/sessions/:id — 删除会话
|
|
3638
3421
|
*/
|
|
3639
|
-
const log$
|
|
3422
|
+
const log$29 = larkLogger("chat/stream");
|
|
3640
3423
|
const chatRunsBySession = /* @__PURE__ */ new Map();
|
|
3641
3424
|
/** 统一写 SSE 格式数据到响应流 */
|
|
3642
3425
|
function sendSSE(res, event, data) {
|
|
@@ -3647,7 +3430,7 @@ function isChatSessionRunning(sessionId, processManager) {
|
|
|
3647
3430
|
return getActiveChatRun(sessionId) != null || (processManager.isSessionBusy?.(sessionId) ?? false);
|
|
3648
3431
|
}
|
|
3649
3432
|
function logChatAudit(event) {
|
|
3650
|
-
log$
|
|
3433
|
+
log$29.info("[stream] Agent audit event", { ...event });
|
|
3651
3434
|
}
|
|
3652
3435
|
function prepareSSE(res) {
|
|
3653
3436
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -3672,7 +3455,8 @@ function serializeChatRun(run) {
|
|
|
3672
3455
|
completedAt: run.completedAt,
|
|
3673
3456
|
finalStatus: run.finalStatus,
|
|
3674
3457
|
error: run.error,
|
|
3675
|
-
textLength: run.text.length
|
|
3458
|
+
textLength: run.text.length,
|
|
3459
|
+
finalText: run.status === "running" ? void 0 : run.text || void 0
|
|
3676
3460
|
};
|
|
3677
3461
|
}
|
|
3678
3462
|
function isRunVisibleToUser(run, params) {
|
|
@@ -3682,7 +3466,7 @@ function addRunClient(req, res, run) {
|
|
|
3682
3466
|
run.clients.add(res);
|
|
3683
3467
|
req.on("close", () => {
|
|
3684
3468
|
run.clients.delete(res);
|
|
3685
|
-
log$
|
|
3469
|
+
log$29.info("[stream] 客户端断开连接", {
|
|
3686
3470
|
sessionId: run.sessionId,
|
|
3687
3471
|
runId: run.runId
|
|
3688
3472
|
});
|
|
@@ -3691,12 +3475,26 @@ function addRunClient(req, res, run) {
|
|
|
3691
3475
|
function sendRunStatus(res, run) {
|
|
3692
3476
|
sendSSE(res, "run-status", serializeChatRun(run));
|
|
3693
3477
|
}
|
|
3478
|
+
function extractRuntimeEventFinalText(event) {
|
|
3479
|
+
const direct = event.finalText;
|
|
3480
|
+
if (typeof direct === "string" && direct.trim()) return direct;
|
|
3481
|
+
const terminal = event.terminal;
|
|
3482
|
+
if (terminal && typeof terminal === "object" && !Array.isArray(terminal)) {
|
|
3483
|
+
const text = terminal.finalText;
|
|
3484
|
+
if (typeof text === "string" && text.trim()) return text;
|
|
3485
|
+
}
|
|
3486
|
+
const data = event.data;
|
|
3487
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
3488
|
+
const text = data.finalText;
|
|
3489
|
+
if (typeof text === "string" && text.trim()) return text;
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3694
3492
|
function broadcastRunEvent(run, event, data) {
|
|
3695
3493
|
for (const client of Array.from(run.clients)) try {
|
|
3696
3494
|
sendSSE(client, event, data);
|
|
3697
3495
|
} catch (err) {
|
|
3698
3496
|
run.clients.delete(client);
|
|
3699
|
-
log$
|
|
3497
|
+
log$29.warn("[stream] SSE 写入失败,移除客户端", {
|
|
3700
3498
|
sessionId: run.sessionId,
|
|
3701
3499
|
runId: run.runId,
|
|
3702
3500
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3716,7 +3514,7 @@ function queueRunMessage(run, messageStore, message, label) {
|
|
|
3716
3514
|
run.persistQueue = run.persistQueue.catch(() => void 0).then(async () => {
|
|
3717
3515
|
await messageStore.appendMessage(message);
|
|
3718
3516
|
}).catch((err) => {
|
|
3719
|
-
log$
|
|
3517
|
+
log$29.error(label, {
|
|
3720
3518
|
sessionId: run.sessionId,
|
|
3721
3519
|
runId: run.runId,
|
|
3722
3520
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3781,7 +3579,7 @@ function createChatRouter(config) {
|
|
|
3781
3579
|
addRunClient(req, res, activeRun);
|
|
3782
3580
|
sendSSE(res, "session", { sessionId });
|
|
3783
3581
|
sendRunStatus(res, activeRun);
|
|
3784
|
-
log$
|
|
3582
|
+
log$29.info("[stream] 已重新连接到运行中的 run", {
|
|
3785
3583
|
sessionId,
|
|
3786
3584
|
runId: activeRun.runId
|
|
3787
3585
|
});
|
|
@@ -3794,7 +3592,7 @@ function createChatRouter(config) {
|
|
|
3794
3592
|
const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
|
|
3795
3593
|
const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
|
|
3796
3594
|
const sourceArtifacts = getRunInputValue(body.sourceArtifacts, body.source_artifacts, body.options?.sourceArtifacts, body.options?.source_artifacts, body.options?.metadata?.sourceArtifacts, body.options?.metadata?.source_artifacts);
|
|
3797
|
-
log$
|
|
3595
|
+
log$29.info("[stream] 收到流式对话请求", {
|
|
3798
3596
|
userId,
|
|
3799
3597
|
sessionId,
|
|
3800
3598
|
requestId,
|
|
@@ -3811,13 +3609,13 @@ function createChatRouter(config) {
|
|
|
3811
3609
|
tenantKey,
|
|
3812
3610
|
channel: "web"
|
|
3813
3611
|
})).id;
|
|
3814
|
-
log$
|
|
3612
|
+
log$29.info("[stream] 自动创建新会话", {
|
|
3815
3613
|
userId: openId,
|
|
3816
3614
|
sessionId
|
|
3817
3615
|
});
|
|
3818
3616
|
}
|
|
3819
3617
|
if (isChatSessionRunning(sessionId, processManager)) {
|
|
3820
|
-
log$
|
|
3618
|
+
log$29.warn("[stream] session 正在被非 Web Chat run 占用", {
|
|
3821
3619
|
tenantKey,
|
|
3822
3620
|
userId,
|
|
3823
3621
|
sessionId
|
|
@@ -3828,6 +3626,7 @@ function createChatRouter(config) {
|
|
|
3828
3626
|
res.end();
|
|
3829
3627
|
return;
|
|
3830
3628
|
}
|
|
3629
|
+
const runtimePrompt = enrichPromptWithSourceArtifacts(body.prompt, sourceArtifacts);
|
|
3831
3630
|
const runtimeConfig = buildAgentRuntimeConfig({
|
|
3832
3631
|
requestId,
|
|
3833
3632
|
traceId,
|
|
@@ -3835,7 +3634,7 @@ function createChatRouter(config) {
|
|
|
3835
3634
|
conversationId: sessionId,
|
|
3836
3635
|
entrypoint: "chat",
|
|
3837
3636
|
scenarioId,
|
|
3838
|
-
prompt:
|
|
3637
|
+
prompt: runtimePrompt,
|
|
3839
3638
|
maxTurns: body.options?.maxTurns,
|
|
3840
3639
|
maxBudgetUsd: body.options?.maxBudgetUsd,
|
|
3841
3640
|
model: process.env.CLAUDE_MODEL || void 0,
|
|
@@ -3915,7 +3714,7 @@ function createChatRouter(config) {
|
|
|
3915
3714
|
});
|
|
3916
3715
|
} catch (err) {
|
|
3917
3716
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3918
|
-
log$
|
|
3717
|
+
log$29.error("[stream] 保存用户消息失败", {
|
|
3919
3718
|
sessionId,
|
|
3920
3719
|
error: errorMessage
|
|
3921
3720
|
});
|
|
@@ -3928,9 +3727,16 @@ function createChatRouter(config) {
|
|
|
3928
3727
|
addRunClient(req, res, run);
|
|
3929
3728
|
sendSSE(res, "session", { sessionId });
|
|
3930
3729
|
sendRunStatus(res, run);
|
|
3730
|
+
const heartbeatTimer = setInterval(() => {
|
|
3731
|
+
if (run.status !== "running" || run.clients.size === 0) {
|
|
3732
|
+
clearInterval(heartbeatTimer);
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
broadcastRunEvent(run, "heartbeat", { ts: Date.now() });
|
|
3736
|
+
}, 15e3);
|
|
3931
3737
|
const handleBlockedEvent = (event) => {
|
|
3932
3738
|
const sanitized = sanitizeBlockedEvent(event);
|
|
3933
|
-
log$
|
|
3739
|
+
log$29.warn("[stream] Agent blocked event", { ...sanitized });
|
|
3934
3740
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
|
|
3935
3741
|
toolName: sanitized.name,
|
|
3936
3742
|
inputSummary: sanitized.inputSummary,
|
|
@@ -3961,6 +3767,8 @@ function createChatRouter(config) {
|
|
|
3961
3767
|
};
|
|
3962
3768
|
const handleRuntimeEvent = (event) => {
|
|
3963
3769
|
const eventSummary = summarizeForAudit(event);
|
|
3770
|
+
const runtimeFinalText = extractRuntimeEventFinalText(event);
|
|
3771
|
+
if (runtimeFinalText && !run.text.trim()) run.text = runtimeFinalText;
|
|
3964
3772
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
|
|
3965
3773
|
resultSummary: eventSummary,
|
|
3966
3774
|
metadata: buildRuntimeEventAuditMetadata(event)
|
|
@@ -4059,7 +3867,9 @@ function createChatRouter(config) {
|
|
|
4059
3867
|
broadcastRunEvent(run, "turn-end", { stopReason });
|
|
4060
3868
|
},
|
|
4061
3869
|
onResult: async (result) => {
|
|
4062
|
-
const finalText = run.text
|
|
3870
|
+
const finalText = run.text.trim() ? run.text : result.result || "";
|
|
3871
|
+
run.text = finalText;
|
|
3872
|
+
await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
4063
3873
|
await queueRunMessage(run, messageStore, {
|
|
4064
3874
|
sessionId,
|
|
4065
3875
|
role: "assistant",
|
|
@@ -4077,7 +3887,6 @@ function createChatRouter(config) {
|
|
|
4077
3887
|
isError: result.isError
|
|
4078
3888
|
}
|
|
4079
3889
|
}, "[stream] 保存 assistant 消息失败");
|
|
4080
|
-
await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
4081
3890
|
broadcastRunEvent(run, "done", {
|
|
4082
3891
|
sessionId,
|
|
4083
3892
|
runId: run.runId,
|
|
@@ -4087,8 +3896,9 @@ function createChatRouter(config) {
|
|
|
4087
3896
|
totalCostUsd: result.totalCostUsd,
|
|
4088
3897
|
numTurns: result.numTurns
|
|
4089
3898
|
});
|
|
3899
|
+
clearInterval(heartbeatTimer);
|
|
4090
3900
|
endRunClients(run);
|
|
4091
|
-
log$
|
|
3901
|
+
log$29.info("[stream] 对话完成", {
|
|
4092
3902
|
sessionId,
|
|
4093
3903
|
runId: run.runId,
|
|
4094
3904
|
result: result.subtype,
|
|
@@ -4110,7 +3920,7 @@ function createChatRouter(config) {
|
|
|
4110
3920
|
}));
|
|
4111
3921
|
},
|
|
4112
3922
|
onError: async (error) => {
|
|
4113
|
-
log$
|
|
3923
|
+
log$29.error("[stream] Runtime 执行出错", {
|
|
4114
3924
|
sessionId,
|
|
4115
3925
|
runId: run.runId,
|
|
4116
3926
|
error: error.message
|
|
@@ -4147,6 +3957,7 @@ function createChatRouter(config) {
|
|
|
4147
3957
|
runId: run.runId,
|
|
4148
3958
|
message: error.message
|
|
4149
3959
|
});
|
|
3960
|
+
clearInterval(heartbeatTimer);
|
|
4150
3961
|
endRunClients(run);
|
|
4151
3962
|
},
|
|
4152
3963
|
onAuditEvent: (event) => {
|
|
@@ -4196,7 +4007,7 @@ function createChatRouter(config) {
|
|
|
4196
4007
|
scenarioId: runtimeConfig.runContext?.scenarioId,
|
|
4197
4008
|
...buildRunInputAuditMetadata(runtimeConfig)
|
|
4198
4009
|
} }));
|
|
4199
|
-
log$
|
|
4010
|
+
log$29.info("[stream] 开始执行 prompt", {
|
|
4200
4011
|
sessionId,
|
|
4201
4012
|
runId: run.runId,
|
|
4202
4013
|
cwd: runtimeConfig.cwd
|
|
@@ -4204,7 +4015,7 @@ function createChatRouter(config) {
|
|
|
4204
4015
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
4205
4016
|
if (run.status === "running") {
|
|
4206
4017
|
const errorMessage = "Runtime completed without a final result event";
|
|
4207
|
-
log$
|
|
4018
|
+
log$29.error("[stream] executePrompt 未返回终态回调", {
|
|
4208
4019
|
sessionId,
|
|
4209
4020
|
runId: run.runId
|
|
4210
4021
|
});
|
|
@@ -4216,11 +4027,12 @@ function createChatRouter(config) {
|
|
|
4216
4027
|
runId: run.runId,
|
|
4217
4028
|
message: errorMessage
|
|
4218
4029
|
});
|
|
4030
|
+
clearInterval(heartbeatTimer);
|
|
4219
4031
|
endRunClients(run);
|
|
4220
4032
|
}
|
|
4221
4033
|
} catch (err) {
|
|
4222
4034
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4223
|
-
log$
|
|
4035
|
+
log$29.error("[stream] executePrompt 异常", {
|
|
4224
4036
|
sessionId,
|
|
4225
4037
|
runId: run.runId,
|
|
4226
4038
|
error: errorMessage
|
|
@@ -4237,7 +4049,7 @@ function createChatRouter(config) {
|
|
|
4237
4049
|
}
|
|
4238
4050
|
const cursor = req.query.cursor;
|
|
4239
4051
|
const limit = parseInt(req.query.limit, 10) || 50;
|
|
4240
|
-
log$
|
|
4052
|
+
log$29.info("[history] 查询会话历史", {
|
|
4241
4053
|
userId: chatUser.userId,
|
|
4242
4054
|
sessionId,
|
|
4243
4055
|
cursor,
|
|
@@ -4262,7 +4074,7 @@ function createChatRouter(config) {
|
|
|
4262
4074
|
});
|
|
4263
4075
|
} catch (err) {
|
|
4264
4076
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4265
|
-
log$
|
|
4077
|
+
log$29.error("[history] 查询历史消息失败", {
|
|
4266
4078
|
sessionId,
|
|
4267
4079
|
error: errorMessage
|
|
4268
4080
|
});
|
|
@@ -4297,13 +4109,13 @@ function createChatRouter(config) {
|
|
|
4297
4109
|
});
|
|
4298
4110
|
router.get("/api/chat/sessions", chatAuthMiddleware, async (_req, res) => {
|
|
4299
4111
|
const chatUser = res.locals.chatUser;
|
|
4300
|
-
log$
|
|
4112
|
+
log$29.info("[sessions] 列出用户会话", { userId: chatUser.userId });
|
|
4301
4113
|
try {
|
|
4302
4114
|
const sessions = await messageStore.listSessions(chatUser.openId);
|
|
4303
4115
|
res.json({ sessions });
|
|
4304
4116
|
} catch (err) {
|
|
4305
4117
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4306
|
-
log$
|
|
4118
|
+
log$29.error("[sessions] 列出会话失败", {
|
|
4307
4119
|
userId: chatUser.userId,
|
|
4308
4120
|
error: errorMessage
|
|
4309
4121
|
});
|
|
@@ -4313,7 +4125,7 @@ function createChatRouter(config) {
|
|
|
4313
4125
|
router.post("/api/chat/sessions", chatAuthMiddleware, async (req, res) => {
|
|
4314
4126
|
const chatUser = res.locals.chatUser;
|
|
4315
4127
|
const body = req.body;
|
|
4316
|
-
log$
|
|
4128
|
+
log$29.info("[sessions] 创建新会话", {
|
|
4317
4129
|
userId: chatUser.userId,
|
|
4318
4130
|
title: body.title
|
|
4319
4131
|
});
|
|
@@ -4328,7 +4140,7 @@ function createChatRouter(config) {
|
|
|
4328
4140
|
res.json({ session });
|
|
4329
4141
|
} catch (err) {
|
|
4330
4142
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4331
|
-
log$
|
|
4143
|
+
log$29.error("[sessions] 创建会话失败", {
|
|
4332
4144
|
userId: chatUser.userId,
|
|
4333
4145
|
error: errorMessage
|
|
4334
4146
|
});
|
|
@@ -4338,7 +4150,7 @@ function createChatRouter(config) {
|
|
|
4338
4150
|
router.delete("/api/chat/sessions/:id", chatAuthMiddleware, async (req, res) => {
|
|
4339
4151
|
const chatUser = res.locals.chatUser;
|
|
4340
4152
|
const sessionId = String(req.params.id);
|
|
4341
|
-
log$
|
|
4153
|
+
log$29.info("[sessions] 删除会话", {
|
|
4342
4154
|
userId: chatUser.userId,
|
|
4343
4155
|
sessionId
|
|
4344
4156
|
});
|
|
@@ -4348,7 +4160,7 @@ function createChatRouter(config) {
|
|
|
4348
4160
|
res.json({ success: true });
|
|
4349
4161
|
} catch (err) {
|
|
4350
4162
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4351
|
-
log$
|
|
4163
|
+
log$29.error("[sessions] 删除会话失败", {
|
|
4352
4164
|
sessionId,
|
|
4353
4165
|
error: errorMessage
|
|
4354
4166
|
});
|
|
@@ -4357,6 +4169,34 @@ function createChatRouter(config) {
|
|
|
4357
4169
|
});
|
|
4358
4170
|
return router;
|
|
4359
4171
|
}
|
|
4172
|
+
/**
|
|
4173
|
+
* 将 sourceArtifacts 中的文件信息(fileToken、fileName 等)注入到用户 prompt 中,
|
|
4174
|
+
* 使 LLM 能看到实际的 file token 值并直接用于下载。
|
|
4175
|
+
*
|
|
4176
|
+
* 如果没有 sourceArtifacts 或无法提取有效信息,返回原始 prompt。
|
|
4177
|
+
*/
|
|
4178
|
+
function enrichPromptWithSourceArtifacts(prompt, sourceArtifacts) {
|
|
4179
|
+
if (!sourceArtifacts) return prompt;
|
|
4180
|
+
const lines = [];
|
|
4181
|
+
const extractInfo = (artifact) => {
|
|
4182
|
+
if (!artifact || typeof artifact !== "object") return;
|
|
4183
|
+
const record = artifact;
|
|
4184
|
+
const fileToken = record.fileToken ?? record.file_token ?? record.driveFileToken ?? record.drive_file_token;
|
|
4185
|
+
const fileName = record.fileName ?? record.file_name ?? record.name ?? record.title;
|
|
4186
|
+
const fileType = record.fileType ?? record.file_type ?? record.type ?? record.mimeType;
|
|
4187
|
+
const fileUrl = record.fileUrl ?? record.file_url ?? record.url ?? record.uri;
|
|
4188
|
+
if (fileToken || fileName || fileUrl) {
|
|
4189
|
+
if (fileName) lines.push(`- fileName: ${String(fileName)}`);
|
|
4190
|
+
if (fileToken) lines.push(`- fileToken: ${String(fileToken)}`);
|
|
4191
|
+
if (fileType) lines.push(`- fileType: ${String(fileType)}`);
|
|
4192
|
+
if (fileUrl) lines.push(`- fileUrl: ${String(fileUrl)}`);
|
|
4193
|
+
}
|
|
4194
|
+
};
|
|
4195
|
+
if (Array.isArray(sourceArtifacts)) for (const item of sourceArtifacts) extractInfo(item);
|
|
4196
|
+
else extractInfo(sourceArtifacts);
|
|
4197
|
+
if (lines.length === 0) return prompt;
|
|
4198
|
+
return `${prompt}\n\n[Attached Source File]\n${lines.join("\n")}`;
|
|
4199
|
+
}
|
|
4360
4200
|
//#endregion
|
|
4361
4201
|
//#region src/core/app-info-sync.ts
|
|
4362
4202
|
/**
|
|
@@ -4374,7 +4214,7 @@ function createChatRouter(config) {
|
|
|
4374
4214
|
* - GET /open-apis/application/v6/applications/me?lang=zh_cn(需要 application:application:self_manage 权限,返回完整信息)
|
|
4375
4215
|
* 如果后者无权限则 fallback 到前者
|
|
4376
4216
|
*/
|
|
4377
|
-
const log$
|
|
4217
|
+
const log$28 = larkLogger("core/app-info-sync");
|
|
4378
4218
|
/**
|
|
4379
4219
|
* 从飞书获取应用信息
|
|
4380
4220
|
*
|
|
@@ -4385,7 +4225,7 @@ async function fetchAppInfo(credentials) {
|
|
|
4385
4225
|
const { appId, appSecret } = credentials;
|
|
4386
4226
|
const token = await getTenantAccessToken(appId, appSecret);
|
|
4387
4227
|
if (!token) {
|
|
4388
|
-
log$
|
|
4228
|
+
log$28.error("获取 tenant_access_token 失败,无法同步应用信息");
|
|
4389
4229
|
return null;
|
|
4390
4230
|
}
|
|
4391
4231
|
const fullInfo = await fetchFromApplicationApi(token);
|
|
@@ -4396,13 +4236,13 @@ async function fetchAppInfo(credentials) {
|
|
|
4396
4236
|
async function fetchFromApplicationApi(token) {
|
|
4397
4237
|
try {
|
|
4398
4238
|
const data = await (await fetch("https://open.feishu.cn/open-apis/application/v6/applications/me?lang=zh_cn", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
4399
|
-
log$
|
|
4239
|
+
log$28.info("application/v6 API 响应", {
|
|
4400
4240
|
code: data.code,
|
|
4401
4241
|
msg: data.msg,
|
|
4402
4242
|
hasApp: !!data.data?.app
|
|
4403
4243
|
});
|
|
4404
4244
|
if (data.code !== 0 || !data.data?.app) {
|
|
4405
|
-
log$
|
|
4245
|
+
log$28.warn("application/v6 API 返回非零或无数据,将 fallback", {
|
|
4406
4246
|
code: data.code,
|
|
4407
4247
|
msg: data.msg
|
|
4408
4248
|
});
|
|
@@ -4417,7 +4257,7 @@ async function fetchFromApplicationApi(token) {
|
|
|
4417
4257
|
helpDocUrl: zhInfo?.help_use
|
|
4418
4258
|
};
|
|
4419
4259
|
} catch (err) {
|
|
4420
|
-
log$
|
|
4260
|
+
log$28.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4421
4261
|
return null;
|
|
4422
4262
|
}
|
|
4423
4263
|
}
|
|
@@ -4425,13 +4265,13 @@ async function fetchFromApplicationApi(token) {
|
|
|
4425
4265
|
async function fetchFromBotApi(token) {
|
|
4426
4266
|
try {
|
|
4427
4267
|
const data = await (await fetch("https://open.feishu.cn/open-apis/bot/v3/info", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
4428
|
-
log$
|
|
4268
|
+
log$28.info("bot/v3/info API 响应", {
|
|
4429
4269
|
code: data.code,
|
|
4430
4270
|
msg: data.msg,
|
|
4431
4271
|
hasBot: !!data.bot
|
|
4432
4272
|
});
|
|
4433
4273
|
if (data.code !== 0 || !data.bot) {
|
|
4434
|
-
log$
|
|
4274
|
+
log$28.error("bot/v3/info API 失败", {
|
|
4435
4275
|
code: data.code,
|
|
4436
4276
|
msg: data.msg
|
|
4437
4277
|
});
|
|
@@ -4442,7 +4282,7 @@ async function fetchFromBotApi(token) {
|
|
|
4442
4282
|
avatarUrl: data.bot.avatar_url
|
|
4443
4283
|
};
|
|
4444
4284
|
} catch (err) {
|
|
4445
|
-
log$
|
|
4285
|
+
log$28.error("bot/v3/info API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4446
4286
|
return null;
|
|
4447
4287
|
}
|
|
4448
4288
|
}
|
|
@@ -4458,7 +4298,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
4458
4298
|
})
|
|
4459
4299
|
})).json();
|
|
4460
4300
|
if (data.code !== 0 || !data.tenant_access_token) {
|
|
4461
|
-
log$
|
|
4301
|
+
log$28.error("获取 tenant_access_token 失败", {
|
|
4462
4302
|
code: data.code,
|
|
4463
4303
|
msg: data.msg
|
|
4464
4304
|
});
|
|
@@ -4466,7 +4306,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
4466
4306
|
}
|
|
4467
4307
|
return data.tenant_access_token;
|
|
4468
4308
|
} catch (err) {
|
|
4469
|
-
log$
|
|
4309
|
+
log$28.error("获取 tenant_access_token 异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4470
4310
|
return null;
|
|
4471
4311
|
}
|
|
4472
4312
|
}
|
|
@@ -4504,10 +4344,10 @@ function parseDocTokenFromUrl(url) {
|
|
|
4504
4344
|
async function fetchDocContent(docUrl, accessToken) {
|
|
4505
4345
|
const parsed = parseDocTokenFromUrl(docUrl);
|
|
4506
4346
|
if (!parsed) {
|
|
4507
|
-
log$
|
|
4347
|
+
log$28.warn("无法从 URL 中解析文档 token", { url: docUrl });
|
|
4508
4348
|
return null;
|
|
4509
4349
|
}
|
|
4510
|
-
log$
|
|
4350
|
+
log$28.info("开始读取人设文档", {
|
|
4511
4351
|
url: docUrl,
|
|
4512
4352
|
type: parsed.type,
|
|
4513
4353
|
token: parsed.token
|
|
@@ -4515,18 +4355,18 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4515
4355
|
let docToken = parsed.token;
|
|
4516
4356
|
if (parsed.type === "wiki") {
|
|
4517
4357
|
const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
|
|
4518
|
-
if (!realToken) log$
|
|
4358
|
+
if (!realToken) log$28.warn("wiki 节点解析失败,尝试直接使用 token 读取");
|
|
4519
4359
|
else docToken = realToken;
|
|
4520
4360
|
}
|
|
4521
4361
|
try {
|
|
4522
4362
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
4523
|
-
log$
|
|
4363
|
+
log$28.info("文档 raw_content API 响应", {
|
|
4524
4364
|
code: data.code,
|
|
4525
4365
|
msg: data.msg,
|
|
4526
4366
|
contentLength: data.data?.content?.length
|
|
4527
4367
|
});
|
|
4528
4368
|
if (data.code !== 0 || !data.data?.content) {
|
|
4529
|
-
log$
|
|
4369
|
+
log$28.warn("读取文档内容失败", {
|
|
4530
4370
|
code: data.code,
|
|
4531
4371
|
msg: data.msg,
|
|
4532
4372
|
docToken
|
|
@@ -4535,7 +4375,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4535
4375
|
}
|
|
4536
4376
|
return data.data.content.trim();
|
|
4537
4377
|
} catch (err) {
|
|
4538
|
-
log$
|
|
4378
|
+
log$28.error("读取文档内容异常", {
|
|
4539
4379
|
error: err instanceof Error ? err.message : String(err),
|
|
4540
4380
|
docToken
|
|
4541
4381
|
});
|
|
@@ -4546,7 +4386,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4546
4386
|
async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
4547
4387
|
try {
|
|
4548
4388
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
4549
|
-
log$
|
|
4389
|
+
log$28.info("wiki get_node API 响应", {
|
|
4550
4390
|
code: data.code,
|
|
4551
4391
|
msg: data.msg,
|
|
4552
4392
|
objType: data.data?.node?.obj_type
|
|
@@ -4554,7 +4394,7 @@ async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
|
4554
4394
|
if (data.code !== 0 || !data.data?.node?.obj_token) return null;
|
|
4555
4395
|
return data.data.node.obj_token;
|
|
4556
4396
|
} catch (err) {
|
|
4557
|
-
log$
|
|
4397
|
+
log$28.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4558
4398
|
return null;
|
|
4559
4399
|
}
|
|
4560
4400
|
}
|
|
@@ -4576,7 +4416,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4576
4416
|
const infoBlock = buildAppInfoBlock(appInfo);
|
|
4577
4417
|
if (!existsSync(claudeMdPath)) {
|
|
4578
4418
|
await writeFile(claudeMdPath, infoBlock + "\n\n" + getDefaultClaudeMdBody(), "utf-8");
|
|
4579
|
-
log$
|
|
4419
|
+
log$28.info("CLAUDE.md 已创建(含应用信息)", { appName: appInfo.appName });
|
|
4580
4420
|
return;
|
|
4581
4421
|
}
|
|
4582
4422
|
let content = await readFile(claudeMdPath, "utf-8");
|
|
@@ -4588,7 +4428,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4588
4428
|
content = before + infoBlock + after;
|
|
4589
4429
|
} else content = infoBlock + "\n\n" + content;
|
|
4590
4430
|
await writeFile(claudeMdPath, content, "utf-8");
|
|
4591
|
-
log$
|
|
4431
|
+
log$28.info("CLAUDE.md 应用信息已同步", {
|
|
4592
4432
|
appName: appInfo.appName,
|
|
4593
4433
|
hasDescription: !!appInfo.description,
|
|
4594
4434
|
hasAvatar: !!appInfo.avatarUrl
|
|
@@ -4603,7 +4443,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4603
4443
|
async function syncPersonaDocToClaudeMd(personaContent) {
|
|
4604
4444
|
const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
|
|
4605
4445
|
if (!existsSync(claudeMdPath)) {
|
|
4606
|
-
log$
|
|
4446
|
+
log$28.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
|
|
4607
4447
|
return;
|
|
4608
4448
|
}
|
|
4609
4449
|
let content = await readFile(claudeMdPath, "utf-8");
|
|
@@ -4624,7 +4464,7 @@ async function syncPersonaDocToClaudeMd(personaContent) {
|
|
|
4624
4464
|
content = before + personaBlock + after;
|
|
4625
4465
|
} else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
|
|
4626
4466
|
await writeFile(claudeMdPath, content, "utf-8");
|
|
4627
|
-
log$
|
|
4467
|
+
log$28.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
|
|
4628
4468
|
}
|
|
4629
4469
|
/** 构建应用信息标记区块 */
|
|
4630
4470
|
function buildAppInfoBlock(appInfo) {
|
|
@@ -4716,24 +4556,24 @@ function getDefaultClaudeMdBody() {
|
|
|
4716
4556
|
* @returns 同步后的应用信息,如果失败返回 null
|
|
4717
4557
|
*/
|
|
4718
4558
|
async function syncAppInfo(credentials) {
|
|
4719
|
-
log$
|
|
4559
|
+
log$28.info("开始同步应用信息", { appId: credentials.appId });
|
|
4720
4560
|
const appInfo = await fetchAppInfo(credentials);
|
|
4721
4561
|
if (!appInfo) {
|
|
4722
|
-
log$
|
|
4562
|
+
log$28.warn("获取应用信息失败,跳过同步");
|
|
4723
4563
|
return null;
|
|
4724
4564
|
}
|
|
4725
4565
|
await syncAppInfoToClaudeMd(appInfo);
|
|
4726
4566
|
if (appInfo.helpDocUrl) {
|
|
4727
|
-
log$
|
|
4567
|
+
log$28.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
|
|
4728
4568
|
const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
|
|
4729
4569
|
if (token) {
|
|
4730
4570
|
const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
|
|
4731
4571
|
if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
|
|
4732
|
-
else log$
|
|
4572
|
+
else log$28.warn("人设文档内容为空或读取失败,跳过同步");
|
|
4733
4573
|
}
|
|
4734
4574
|
}
|
|
4735
4575
|
await installSyncSkill();
|
|
4736
|
-
log$
|
|
4576
|
+
log$28.info("应用信息同步完成", {
|
|
4737
4577
|
appName: appInfo.appName,
|
|
4738
4578
|
description: appInfo.description?.substring(0, 50),
|
|
4739
4579
|
hasPersonaDoc: !!appInfo.helpDocUrl
|
|
@@ -4751,7 +4591,7 @@ async function installSyncSkill() {
|
|
|
4751
4591
|
if ((await readFile(SYNC_SKILL_PATH, "utf-8")).includes(`skill-version: ${SYNC_SKILL_VERSION}`)) return;
|
|
4752
4592
|
}
|
|
4753
4593
|
await writeFile(SYNC_SKILL_PATH, SYNC_SKILL_CONTENT, "utf-8");
|
|
4754
|
-
log$
|
|
4594
|
+
log$28.info("sync-app-info 技能已安装", { path: SYNC_SKILL_PATH });
|
|
4755
4595
|
}
|
|
4756
4596
|
const SYNC_SKILL_CONTENT = `---
|
|
4757
4597
|
skill-version: ${SYNC_SKILL_VERSION}
|
|
@@ -5747,7 +5587,7 @@ function createHooksRouter() {
|
|
|
5747
5587
|
}
|
|
5748
5588
|
//#endregion
|
|
5749
5589
|
//#region src/memory/store.ts
|
|
5750
|
-
const log$
|
|
5590
|
+
const log$27 = larkLogger("memory/store");
|
|
5751
5591
|
const MEMORY_VERSION = 1;
|
|
5752
5592
|
const MAX_TEAMMATE_RECENT = 3;
|
|
5753
5593
|
const MAX_LONG_TERM = 100;
|
|
@@ -5773,7 +5613,7 @@ var MemoryStore = class MemoryStore {
|
|
|
5773
5613
|
const raw = await readFile(path, "utf-8");
|
|
5774
5614
|
return normalizeMemoryFile(JSON.parse(raw));
|
|
5775
5615
|
} catch (err) {
|
|
5776
|
-
log$
|
|
5616
|
+
log$27.warn("memory file load failed, using empty state", {
|
|
5777
5617
|
cwd,
|
|
5778
5618
|
path,
|
|
5779
5619
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -6269,7 +6109,7 @@ function createMcpCallbackRouter() {
|
|
|
6269
6109
|
* 提供 /api/scheduled-tasks 的 RESTful CRUD 接口。
|
|
6270
6110
|
* 每个请求和响应都记录详细日志,便于后续错误排查。
|
|
6271
6111
|
*/
|
|
6272
|
-
const log$
|
|
6112
|
+
const log$26 = larkLogger("gateway/scheduler-handler");
|
|
6273
6113
|
/**
|
|
6274
6114
|
* 创建定时任务路由
|
|
6275
6115
|
*
|
|
@@ -6280,16 +6120,16 @@ function createSchedulerRouter(taskManager) {
|
|
|
6280
6120
|
const router = Router();
|
|
6281
6121
|
router.get("/api/scheduled-tasks", (req, res) => {
|
|
6282
6122
|
const requestId = req.headers["x-request-id"];
|
|
6283
|
-
log$
|
|
6123
|
+
log$26.info("请求列出所有定时任务", { requestId });
|
|
6284
6124
|
try {
|
|
6285
6125
|
const tasks = taskManager.listTasks();
|
|
6286
|
-
log$
|
|
6126
|
+
log$26.info("返回定时任务列表", {
|
|
6287
6127
|
requestId,
|
|
6288
6128
|
count: tasks.length
|
|
6289
6129
|
});
|
|
6290
6130
|
res.json({ tasks });
|
|
6291
6131
|
} catch (err) {
|
|
6292
|
-
log$
|
|
6132
|
+
log$26.error("列出定时任务失败", {
|
|
6293
6133
|
requestId,
|
|
6294
6134
|
error: String(err)
|
|
6295
6135
|
});
|
|
@@ -6302,13 +6142,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
6302
6142
|
router.post("/api/scheduled-tasks", (req, res) => {
|
|
6303
6143
|
const requestId = req.headers["x-request-id"];
|
|
6304
6144
|
const body = req.body;
|
|
6305
|
-
log$
|
|
6145
|
+
log$26.info("请求创建定时任务", {
|
|
6306
6146
|
requestId,
|
|
6307
6147
|
body: JSON.stringify(body)
|
|
6308
6148
|
});
|
|
6309
6149
|
const { name, cron: cronExpr, session_id, cwd, prompt, example_output, constraints } = body;
|
|
6310
6150
|
if (!name || typeof name !== "string") {
|
|
6311
|
-
log$
|
|
6151
|
+
log$26.warn("创建任务参数缺失: name", { requestId });
|
|
6312
6152
|
res.status(400).json({
|
|
6313
6153
|
error: "参数错误",
|
|
6314
6154
|
detail: "name 为必填字符串"
|
|
@@ -6316,7 +6156,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6316
6156
|
return;
|
|
6317
6157
|
}
|
|
6318
6158
|
if (!cronExpr || typeof cronExpr !== "string") {
|
|
6319
|
-
log$
|
|
6159
|
+
log$26.warn("创建任务参数缺失: cron", { requestId });
|
|
6320
6160
|
res.status(400).json({
|
|
6321
6161
|
error: "参数错误",
|
|
6322
6162
|
detail: "cron 为必填字符串"
|
|
@@ -6324,7 +6164,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6324
6164
|
return;
|
|
6325
6165
|
}
|
|
6326
6166
|
if (!session_id || typeof session_id !== "string") {
|
|
6327
|
-
log$
|
|
6167
|
+
log$26.warn("创建任务参数缺失: session_id", { requestId });
|
|
6328
6168
|
res.status(400).json({
|
|
6329
6169
|
error: "参数错误",
|
|
6330
6170
|
detail: "session_id 为必填字符串"
|
|
@@ -6332,7 +6172,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6332
6172
|
return;
|
|
6333
6173
|
}
|
|
6334
6174
|
if (!cwd || typeof cwd !== "string") {
|
|
6335
|
-
log$
|
|
6175
|
+
log$26.warn("创建任务参数缺失: cwd", { requestId });
|
|
6336
6176
|
res.status(400).json({
|
|
6337
6177
|
error: "参数错误",
|
|
6338
6178
|
detail: "cwd 为必填字符串"
|
|
@@ -6340,7 +6180,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6340
6180
|
return;
|
|
6341
6181
|
}
|
|
6342
6182
|
if (!prompt || typeof prompt !== "string") {
|
|
6343
|
-
log$
|
|
6183
|
+
log$26.warn("创建任务参数缺失: prompt", { requestId });
|
|
6344
6184
|
res.status(400).json({
|
|
6345
6185
|
error: "参数错误",
|
|
6346
6186
|
detail: "prompt 为必填字符串"
|
|
@@ -6348,7 +6188,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6348
6188
|
return;
|
|
6349
6189
|
}
|
|
6350
6190
|
if (!cron.validate(cronExpr)) {
|
|
6351
|
-
log$
|
|
6191
|
+
log$26.warn("cron 表达式不合法", {
|
|
6352
6192
|
requestId,
|
|
6353
6193
|
cron: cronExpr
|
|
6354
6194
|
});
|
|
@@ -6369,7 +6209,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6369
6209
|
constraints
|
|
6370
6210
|
};
|
|
6371
6211
|
const task = taskManager.createTask(params);
|
|
6372
|
-
log$
|
|
6212
|
+
log$26.info("定时任务创建成功", {
|
|
6373
6213
|
requestId,
|
|
6374
6214
|
taskId: task.id,
|
|
6375
6215
|
name: task.name,
|
|
@@ -6377,7 +6217,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6377
6217
|
});
|
|
6378
6218
|
res.status(201).json({ task });
|
|
6379
6219
|
} catch (err) {
|
|
6380
|
-
log$
|
|
6220
|
+
log$26.error("创建定时任务失败", {
|
|
6381
6221
|
requestId,
|
|
6382
6222
|
error: String(err)
|
|
6383
6223
|
});
|
|
@@ -6391,13 +6231,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
6391
6231
|
const requestId = req.headers["x-request-id"];
|
|
6392
6232
|
const id = String(req.params.id);
|
|
6393
6233
|
const body = req.body;
|
|
6394
|
-
log$
|
|
6234
|
+
log$26.info("请求更新定时任务", {
|
|
6395
6235
|
requestId,
|
|
6396
6236
|
taskId: id,
|
|
6397
6237
|
body: JSON.stringify(body)
|
|
6398
6238
|
});
|
|
6399
6239
|
if (!taskManager.getTask(id)) {
|
|
6400
|
-
log$
|
|
6240
|
+
log$26.warn("更新的任务不存在", {
|
|
6401
6241
|
requestId,
|
|
6402
6242
|
taskId: id
|
|
6403
6243
|
});
|
|
@@ -6409,7 +6249,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6409
6249
|
}
|
|
6410
6250
|
if (body.cron !== void 0) {
|
|
6411
6251
|
if (typeof body.cron !== "string" || !cron.validate(body.cron)) {
|
|
6412
|
-
log$
|
|
6252
|
+
log$26.warn("更新的 cron 表达式不合法", {
|
|
6413
6253
|
requestId,
|
|
6414
6254
|
taskId: id,
|
|
6415
6255
|
cron: body.cron
|
|
@@ -6430,7 +6270,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6430
6270
|
if (body.constraints !== void 0) updates.constraints = body.constraints;
|
|
6431
6271
|
if (body.enabled !== void 0) updates.enabled = body.enabled;
|
|
6432
6272
|
const task = taskManager.updateTask(id, updates);
|
|
6433
|
-
log$
|
|
6273
|
+
log$26.info("定时任务更新成功", {
|
|
6434
6274
|
requestId,
|
|
6435
6275
|
taskId: task.id,
|
|
6436
6276
|
name: task.name,
|
|
@@ -6438,7 +6278,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6438
6278
|
});
|
|
6439
6279
|
res.json({ task });
|
|
6440
6280
|
} catch (err) {
|
|
6441
|
-
log$
|
|
6281
|
+
log$26.error("更新定时任务失败", {
|
|
6442
6282
|
requestId,
|
|
6443
6283
|
taskId: id,
|
|
6444
6284
|
error: String(err)
|
|
@@ -6452,12 +6292,12 @@ function createSchedulerRouter(taskManager) {
|
|
|
6452
6292
|
router.delete("/api/scheduled-tasks/:id", (req, res) => {
|
|
6453
6293
|
const requestId = req.headers["x-request-id"];
|
|
6454
6294
|
const id = String(req.params.id);
|
|
6455
|
-
log$
|
|
6295
|
+
log$26.info("请求删除定时任务", {
|
|
6456
6296
|
requestId,
|
|
6457
6297
|
taskId: id
|
|
6458
6298
|
});
|
|
6459
6299
|
if (!taskManager.getTask(id)) {
|
|
6460
|
-
log$
|
|
6300
|
+
log$26.warn("删除的任务不存在", {
|
|
6461
6301
|
requestId,
|
|
6462
6302
|
taskId: id
|
|
6463
6303
|
});
|
|
@@ -6469,7 +6309,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6469
6309
|
}
|
|
6470
6310
|
try {
|
|
6471
6311
|
const deleted = taskManager.deleteTask(id);
|
|
6472
|
-
log$
|
|
6312
|
+
log$26.info("定时任务删除结果", {
|
|
6473
6313
|
requestId,
|
|
6474
6314
|
taskId: id,
|
|
6475
6315
|
deleted
|
|
@@ -6479,7 +6319,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6479
6319
|
deleted: true
|
|
6480
6320
|
});
|
|
6481
6321
|
} catch (err) {
|
|
6482
|
-
log$
|
|
6322
|
+
log$26.error("删除定时任务失败", {
|
|
6483
6323
|
requestId,
|
|
6484
6324
|
taskId: id,
|
|
6485
6325
|
error: String(err)
|
|
@@ -7036,7 +6876,7 @@ function createGatewayServer(config) {
|
|
|
7036
6876
|
*
|
|
7037
6877
|
* 持久化文件: /workspace/config/scheduled-tasks.json
|
|
7038
6878
|
*/
|
|
7039
|
-
const log$
|
|
6879
|
+
const log$25 = larkLogger("gateway/scheduler");
|
|
7040
6880
|
/** 持久化文件路径 */
|
|
7041
6881
|
const PERSIST_PATH = "/workspace/config/scheduled-tasks.json";
|
|
7042
6882
|
/**
|
|
@@ -7054,7 +6894,7 @@ var ScheduledTaskManager = class {
|
|
|
7054
6894
|
processManager;
|
|
7055
6895
|
constructor(processManager) {
|
|
7056
6896
|
this.processManager = processManager;
|
|
7057
|
-
log$
|
|
6897
|
+
log$25.info("ScheduledTaskManager 已创建");
|
|
7058
6898
|
}
|
|
7059
6899
|
/**
|
|
7060
6900
|
* 创建定时任务
|
|
@@ -7074,7 +6914,7 @@ var ScheduledTaskManager = class {
|
|
|
7074
6914
|
enabled: true,
|
|
7075
6915
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7076
6916
|
};
|
|
7077
|
-
log$
|
|
6917
|
+
log$25.info("创建定时任务", {
|
|
7078
6918
|
taskId: task.id,
|
|
7079
6919
|
name: task.name,
|
|
7080
6920
|
cron: task.cron,
|
|
@@ -7110,7 +6950,7 @@ var ScheduledTaskManager = class {
|
|
|
7110
6950
|
if (updates.example_output !== void 0) task.example_output = updates.example_output;
|
|
7111
6951
|
if (updates.constraints !== void 0) task.constraints = updates.constraints;
|
|
7112
6952
|
if (updates.enabled !== void 0) task.enabled = updates.enabled;
|
|
7113
|
-
log$
|
|
6953
|
+
log$25.info("更新定时任务", {
|
|
7114
6954
|
taskId: id,
|
|
7115
6955
|
name: task.name,
|
|
7116
6956
|
cronChanged: oldCron !== task.cron,
|
|
@@ -7132,10 +6972,10 @@ var ScheduledTaskManager = class {
|
|
|
7132
6972
|
deleteTask(id) {
|
|
7133
6973
|
const task = this.tasks.get(id);
|
|
7134
6974
|
if (!task) {
|
|
7135
|
-
log$
|
|
6975
|
+
log$25.warn("尝试删除不存在的任务", { taskId: id });
|
|
7136
6976
|
return false;
|
|
7137
6977
|
}
|
|
7138
|
-
log$
|
|
6978
|
+
log$25.info("删除定时任务", {
|
|
7139
6979
|
taskId: id,
|
|
7140
6980
|
name: task.name
|
|
7141
6981
|
});
|
|
@@ -7150,28 +6990,28 @@ var ScheduledTaskManager = class {
|
|
|
7150
6990
|
* 启动时调用,读取 JSON 文件并为每个 enabled 的任务注册 cron。
|
|
7151
6991
|
*/
|
|
7152
6992
|
async loadFromDisk() {
|
|
7153
|
-
log$
|
|
6993
|
+
log$25.info("从磁盘加载定时任务", { path: PERSIST_PATH });
|
|
7154
6994
|
try {
|
|
7155
6995
|
const content = await readFile(PERSIST_PATH, "utf-8");
|
|
7156
6996
|
const data = JSON.parse(content);
|
|
7157
6997
|
if (!Array.isArray(data)) {
|
|
7158
|
-
log$
|
|
6998
|
+
log$25.warn("持久化文件格式异常,跳过加载", { path: PERSIST_PATH });
|
|
7159
6999
|
return;
|
|
7160
7000
|
}
|
|
7161
7001
|
for (const task of data) {
|
|
7162
7002
|
this.tasks.set(task.id, task);
|
|
7163
7003
|
if (task.enabled) this.registerCronJob(task);
|
|
7164
7004
|
}
|
|
7165
|
-
log$
|
|
7005
|
+
log$25.info("定时任务加载完成", {
|
|
7166
7006
|
total: data.length,
|
|
7167
7007
|
enabled: data.filter((t) => t.enabled).length
|
|
7168
7008
|
});
|
|
7169
7009
|
} catch (err) {
|
|
7170
7010
|
if (err.code === "ENOENT") {
|
|
7171
|
-
log$
|
|
7011
|
+
log$25.info("持久化文件不存在,跳过加载(首次启动)", { path: PERSIST_PATH });
|
|
7172
7012
|
return;
|
|
7173
7013
|
}
|
|
7174
|
-
log$
|
|
7014
|
+
log$25.error("加载定时任务失败", {
|
|
7175
7015
|
path: PERSIST_PATH,
|
|
7176
7016
|
error: String(err)
|
|
7177
7017
|
});
|
|
@@ -7187,12 +7027,12 @@ var ScheduledTaskManager = class {
|
|
|
7187
7027
|
try {
|
|
7188
7028
|
await mkdir(dirname(PERSIST_PATH), { recursive: true });
|
|
7189
7029
|
await writeFile(PERSIST_PATH, JSON.stringify(tasks, null, 2), "utf-8");
|
|
7190
|
-
log$
|
|
7030
|
+
log$25.info("定时任务已持久化到磁盘", {
|
|
7191
7031
|
path: PERSIST_PATH,
|
|
7192
7032
|
count: tasks.length
|
|
7193
7033
|
});
|
|
7194
7034
|
} catch (err) {
|
|
7195
|
-
log$
|
|
7035
|
+
log$25.error("持久化定时任务失败", {
|
|
7196
7036
|
path: PERSIST_PATH,
|
|
7197
7037
|
error: String(err)
|
|
7198
7038
|
});
|
|
@@ -7205,13 +7045,13 @@ var ScheduledTaskManager = class {
|
|
|
7205
7045
|
*/
|
|
7206
7046
|
registerCronJob(task) {
|
|
7207
7047
|
this.unregisterCronJob(task.id);
|
|
7208
|
-
log$
|
|
7048
|
+
log$25.info("注册 cron 调度", {
|
|
7209
7049
|
taskId: task.id,
|
|
7210
7050
|
name: task.name,
|
|
7211
7051
|
cron: task.cron
|
|
7212
7052
|
});
|
|
7213
7053
|
const job = cron.schedule(task.cron, () => {
|
|
7214
|
-
log$
|
|
7054
|
+
log$25.info("cron 触发任务执行", {
|
|
7215
7055
|
taskId: task.id,
|
|
7216
7056
|
name: task.name
|
|
7217
7057
|
});
|
|
@@ -7227,7 +7067,7 @@ var ScheduledTaskManager = class {
|
|
|
7227
7067
|
if (job) {
|
|
7228
7068
|
job.stop();
|
|
7229
7069
|
this.cronJobs.delete(taskId);
|
|
7230
|
-
log$
|
|
7070
|
+
log$25.info("已注销 cron 调度", { taskId });
|
|
7231
7071
|
}
|
|
7232
7072
|
}
|
|
7233
7073
|
/**
|
|
@@ -7237,10 +7077,10 @@ var ScheduledTaskManager = class {
|
|
|
7237
7077
|
*/
|
|
7238
7078
|
stopAll() {
|
|
7239
7079
|
const count = this.cronJobs.size;
|
|
7240
|
-
log$
|
|
7080
|
+
log$25.info("停止所有 cron 调度", { count });
|
|
7241
7081
|
for (const [taskId, job] of this.cronJobs) {
|
|
7242
7082
|
job.stop();
|
|
7243
|
-
log$
|
|
7083
|
+
log$25.debug("已停止 cron 调度", { taskId });
|
|
7244
7084
|
}
|
|
7245
7085
|
this.cronJobs.clear();
|
|
7246
7086
|
}
|
|
@@ -7254,17 +7094,17 @@ var ScheduledTaskManager = class {
|
|
|
7254
7094
|
async executeTask(taskId) {
|
|
7255
7095
|
const task = this.tasks.get(taskId);
|
|
7256
7096
|
if (!task) {
|
|
7257
|
-
log$
|
|
7097
|
+
log$25.warn("任务执行时未找到任务", { taskId });
|
|
7258
7098
|
return;
|
|
7259
7099
|
}
|
|
7260
7100
|
if (!task.enabled) {
|
|
7261
|
-
log$
|
|
7101
|
+
log$25.info("任务已禁用,跳过执行", {
|
|
7262
7102
|
taskId,
|
|
7263
7103
|
name: task.name
|
|
7264
7104
|
});
|
|
7265
7105
|
return;
|
|
7266
7106
|
}
|
|
7267
|
-
log$
|
|
7107
|
+
log$25.info("开始执行定时任务", {
|
|
7268
7108
|
taskId: task.id,
|
|
7269
7109
|
name: task.name,
|
|
7270
7110
|
sessionId: task.session_id,
|
|
@@ -7285,7 +7125,7 @@ var ScheduledTaskManager = class {
|
|
|
7285
7125
|
onResult: (result) => {
|
|
7286
7126
|
const summary = result.result ?? resultText;
|
|
7287
7127
|
task.last_result = summary.slice(0, 2e3);
|
|
7288
|
-
log$
|
|
7128
|
+
log$25.info("定时任务执行完成", {
|
|
7289
7129
|
taskId: task.id,
|
|
7290
7130
|
name: task.name,
|
|
7291
7131
|
subtype: result.subtype,
|
|
@@ -7298,7 +7138,7 @@ var ScheduledTaskManager = class {
|
|
|
7298
7138
|
},
|
|
7299
7139
|
onError: (error) => {
|
|
7300
7140
|
task.last_result = `执行失败: ${error.message}`;
|
|
7301
|
-
log$
|
|
7141
|
+
log$25.error("定时任务执行失败", {
|
|
7302
7142
|
taskId: task.id,
|
|
7303
7143
|
name: task.name,
|
|
7304
7144
|
error: error.message
|
|
@@ -7310,7 +7150,7 @@ var ScheduledTaskManager = class {
|
|
|
7310
7150
|
await this.processManager.executePrompt(config, callbacks);
|
|
7311
7151
|
} catch (err) {
|
|
7312
7152
|
task.last_result = `执行异常: ${String(err)}`;
|
|
7313
|
-
log$
|
|
7153
|
+
log$25.error("定时任务执行异常", {
|
|
7314
7154
|
taskId: task.id,
|
|
7315
7155
|
name: task.name,
|
|
7316
7156
|
error: String(err)
|
|
@@ -7339,17 +7179,18 @@ var ScheduledTaskManager = class {
|
|
|
7339
7179
|
* - 会话元数据:{workspaceRoot}/.chat-history/sessions/{userId}.json
|
|
7340
7180
|
* - 消息数据:{workspaceRoot}/.chat-history/messages/{sessionId}.json
|
|
7341
7181
|
*/
|
|
7342
|
-
const log$
|
|
7182
|
+
const log$24 = larkLogger("chat/message-store");
|
|
7343
7183
|
var SessionMessageStore = class {
|
|
7344
7184
|
baseDir;
|
|
7345
7185
|
sessionsDir;
|
|
7346
7186
|
messagesDir;
|
|
7187
|
+
jsonCache = /* @__PURE__ */ new Map();
|
|
7347
7188
|
constructor(workspaceRoot) {
|
|
7348
7189
|
const root = workspaceRoot || resolveWorkspaceRoot();
|
|
7349
7190
|
this.baseDir = getWorkspaceChatHistoryRoot(root);
|
|
7350
7191
|
this.sessionsDir = join(this.baseDir, "sessions");
|
|
7351
7192
|
this.messagesDir = join(this.baseDir, "messages");
|
|
7352
|
-
log$
|
|
7193
|
+
log$24.info("初始化消息存储", { baseDir: this.baseDir });
|
|
7353
7194
|
}
|
|
7354
7195
|
/**
|
|
7355
7196
|
* 创建新会话
|
|
@@ -7362,7 +7203,7 @@ var SessionMessageStore = class {
|
|
|
7362
7203
|
updatedAt: now,
|
|
7363
7204
|
messageCount: 0
|
|
7364
7205
|
};
|
|
7365
|
-
log$
|
|
7206
|
+
log$24.info("创建会话", {
|
|
7366
7207
|
sessionId: fullSession.id,
|
|
7367
7208
|
userId: fullSession.userId
|
|
7368
7209
|
});
|
|
@@ -7375,7 +7216,7 @@ var SessionMessageStore = class {
|
|
|
7375
7216
|
* 获取指定会话
|
|
7376
7217
|
*/
|
|
7377
7218
|
async getSession(sessionId) {
|
|
7378
|
-
log$
|
|
7219
|
+
log$24.debug("获取会话", { sessionId });
|
|
7379
7220
|
try {
|
|
7380
7221
|
const files = await this.listSessionFiles();
|
|
7381
7222
|
for (const file of files) {
|
|
@@ -7385,7 +7226,7 @@ var SessionMessageStore = class {
|
|
|
7385
7226
|
if (found) return found;
|
|
7386
7227
|
}
|
|
7387
7228
|
} catch (err) {
|
|
7388
|
-
log$
|
|
7229
|
+
log$24.error("获取会话失败", {
|
|
7389
7230
|
sessionId,
|
|
7390
7231
|
error: String(err)
|
|
7391
7232
|
});
|
|
@@ -7396,14 +7237,14 @@ var SessionMessageStore = class {
|
|
|
7396
7237
|
* 列出指定用户的所有会话
|
|
7397
7238
|
*/
|
|
7398
7239
|
async listSessions(userId) {
|
|
7399
|
-
log$
|
|
7240
|
+
log$24.debug("列出用户会话", { userId });
|
|
7400
7241
|
return await this.readUserSessions(userId);
|
|
7401
7242
|
}
|
|
7402
7243
|
/**
|
|
7403
7244
|
* 删除指定会话(同时删除消息文件)
|
|
7404
7245
|
*/
|
|
7405
7246
|
async deleteSession(sessionId) {
|
|
7406
|
-
log$
|
|
7247
|
+
log$24.info("删除会话", { sessionId });
|
|
7407
7248
|
try {
|
|
7408
7249
|
const files = await this.listSessionFiles();
|
|
7409
7250
|
for (const file of files) {
|
|
@@ -7418,9 +7259,9 @@ var SessionMessageStore = class {
|
|
|
7418
7259
|
}
|
|
7419
7260
|
const msgFile = this.getMessageFilePath(sessionId);
|
|
7420
7261
|
await this.writeJsonFile(msgFile, []);
|
|
7421
|
-
log$
|
|
7262
|
+
log$24.info("会话删除完成", { sessionId });
|
|
7422
7263
|
} catch (err) {
|
|
7423
|
-
log$
|
|
7264
|
+
log$24.error("删除会话失败", {
|
|
7424
7265
|
sessionId,
|
|
7425
7266
|
error: String(err)
|
|
7426
7267
|
});
|
|
@@ -7437,7 +7278,7 @@ var SessionMessageStore = class {
|
|
|
7437
7278
|
id: v4(),
|
|
7438
7279
|
timestamp: Date.now()
|
|
7439
7280
|
};
|
|
7440
|
-
log$
|
|
7281
|
+
log$24.info("追加消息", {
|
|
7441
7282
|
messageId: fullMessage.id,
|
|
7442
7283
|
sessionId: fullMessage.sessionId,
|
|
7443
7284
|
role: fullMessage.role
|
|
@@ -7448,9 +7289,9 @@ var SessionMessageStore = class {
|
|
|
7448
7289
|
messages.push(fullMessage);
|
|
7449
7290
|
await this.writeJsonFile(msgFile, messages);
|
|
7450
7291
|
await this.updateSessionMeta(fullMessage.sessionId, messages.length);
|
|
7451
|
-
log$
|
|
7292
|
+
log$24.debug("消息追加完成", { messageId: fullMessage.id });
|
|
7452
7293
|
} catch (err) {
|
|
7453
|
-
log$
|
|
7294
|
+
log$24.error("追加消息失败", {
|
|
7454
7295
|
sessionId: fullMessage.sessionId,
|
|
7455
7296
|
error: String(err)
|
|
7456
7297
|
});
|
|
@@ -7465,7 +7306,7 @@ var SessionMessageStore = class {
|
|
|
7465
7306
|
async getMessages(sessionId, options) {
|
|
7466
7307
|
const limit = options?.limit ?? 50;
|
|
7467
7308
|
const cursor = options?.cursor;
|
|
7468
|
-
log$
|
|
7309
|
+
log$24.debug("获取消息列表", {
|
|
7469
7310
|
sessionId,
|
|
7470
7311
|
cursor,
|
|
7471
7312
|
limit
|
|
@@ -7484,7 +7325,7 @@ var SessionMessageStore = class {
|
|
|
7484
7325
|
nextCursor: startIdx + limit < allMessages.length ? sliced[sliced.length - 1]?.id : void 0
|
|
7485
7326
|
};
|
|
7486
7327
|
} catch (err) {
|
|
7487
|
-
log$
|
|
7328
|
+
log$24.error("获取消息列表失败", {
|
|
7488
7329
|
sessionId,
|
|
7489
7330
|
error: String(err)
|
|
7490
7331
|
});
|
|
@@ -7495,12 +7336,12 @@ var SessionMessageStore = class {
|
|
|
7495
7336
|
* 获取会话消息总数
|
|
7496
7337
|
*/
|
|
7497
7338
|
async getMessageCount(sessionId) {
|
|
7498
|
-
log$
|
|
7339
|
+
log$24.debug("获取消息数量", { sessionId });
|
|
7499
7340
|
try {
|
|
7500
7341
|
const msgFile = this.getMessageFilePath(sessionId);
|
|
7501
7342
|
return (await this.readJsonFile(msgFile) || []).length;
|
|
7502
7343
|
} catch (err) {
|
|
7503
|
-
log$
|
|
7344
|
+
log$24.error("获取消息数量失败", {
|
|
7504
7345
|
sessionId,
|
|
7505
7346
|
error: String(err)
|
|
7506
7347
|
});
|
|
@@ -7551,7 +7392,7 @@ var SessionMessageStore = class {
|
|
|
7551
7392
|
}
|
|
7552
7393
|
}
|
|
7553
7394
|
} catch (err) {
|
|
7554
|
-
log$
|
|
7395
|
+
log$24.warn("更新会话元数据失败", {
|
|
7555
7396
|
sessionId,
|
|
7556
7397
|
error: String(err)
|
|
7557
7398
|
});
|
|
@@ -7566,7 +7407,7 @@ var SessionMessageStore = class {
|
|
|
7566
7407
|
await mkdir(this.sessionsDir, { recursive: true });
|
|
7567
7408
|
return (await readdir(this.sessionsDir)).filter((e) => e.endsWith(".json")).map((e) => join(this.sessionsDir, e));
|
|
7568
7409
|
} catch (err) {
|
|
7569
|
-
log$
|
|
7410
|
+
log$24.error("列出会话索引文件失败", { error: String(err) });
|
|
7570
7411
|
return [];
|
|
7571
7412
|
}
|
|
7572
7413
|
}
|
|
@@ -7576,13 +7417,19 @@ var SessionMessageStore = class {
|
|
|
7576
7417
|
async readJsonFile(filePath) {
|
|
7577
7418
|
try {
|
|
7578
7419
|
const raw = await readFile(filePath, "utf-8");
|
|
7579
|
-
|
|
7420
|
+
const parsed = JSON.parse(raw);
|
|
7421
|
+
this.jsonCache.set(filePath, parsed);
|
|
7422
|
+
return parsed;
|
|
7580
7423
|
} catch (err) {
|
|
7581
7424
|
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
|
|
7582
|
-
log$
|
|
7425
|
+
log$24.error("读取 JSON 文件失败", {
|
|
7583
7426
|
filePath,
|
|
7584
7427
|
error: String(err)
|
|
7585
7428
|
});
|
|
7429
|
+
if (this.jsonCache.has(filePath)) {
|
|
7430
|
+
log$24.warn("读取 JSON 文件失败,使用内存中的上一份完整快照", { filePath });
|
|
7431
|
+
return this.jsonCache.get(filePath);
|
|
7432
|
+
}
|
|
7586
7433
|
return null;
|
|
7587
7434
|
}
|
|
7588
7435
|
}
|
|
@@ -7590,18 +7437,39 @@ var SessionMessageStore = class {
|
|
|
7590
7437
|
* 通用 JSON 文件写入(自动创建目录)
|
|
7591
7438
|
*/
|
|
7592
7439
|
async writeJsonFile(filePath, data) {
|
|
7440
|
+
const dir = dirname(filePath);
|
|
7441
|
+
const tmpPath = join(dir, `.${basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
7593
7442
|
try {
|
|
7594
|
-
|
|
7595
|
-
await
|
|
7596
|
-
|
|
7443
|
+
await mkdir(dir, { recursive: true });
|
|
7444
|
+
const handle = await open(tmpPath, "w");
|
|
7445
|
+
try {
|
|
7446
|
+
await handle.writeFile(JSON.stringify(data, null, 2), "utf-8");
|
|
7447
|
+
await handle.sync();
|
|
7448
|
+
} finally {
|
|
7449
|
+
await handle.close();
|
|
7450
|
+
}
|
|
7451
|
+
await rename(tmpPath, filePath);
|
|
7452
|
+
this.jsonCache.set(filePath, data);
|
|
7453
|
+
await this.fsyncDirectory(dir);
|
|
7597
7454
|
} catch (err) {
|
|
7598
|
-
log$
|
|
7455
|
+
log$24.error("写入 JSON 文件失败", {
|
|
7599
7456
|
filePath,
|
|
7600
7457
|
error: String(err)
|
|
7601
7458
|
});
|
|
7459
|
+
await unlink(tmpPath).catch(() => void 0);
|
|
7602
7460
|
throw err;
|
|
7603
7461
|
}
|
|
7604
7462
|
}
|
|
7463
|
+
async fsyncDirectory(dir) {
|
|
7464
|
+
try {
|
|
7465
|
+
const handle = await open(dir, "r");
|
|
7466
|
+
try {
|
|
7467
|
+
await handle.sync();
|
|
7468
|
+
} finally {
|
|
7469
|
+
await handle.close();
|
|
7470
|
+
}
|
|
7471
|
+
} catch {}
|
|
7472
|
+
}
|
|
7605
7473
|
};
|
|
7606
7474
|
//#endregion
|
|
7607
7475
|
//#region src/messaging/inbound/user-name-cache-store.ts
|
|
@@ -7876,7 +7744,7 @@ function getUserAgent() {
|
|
|
7876
7744
|
* - `LarkClient.fromCredentials(credentials)` — ephemeral instance (not cached)
|
|
7877
7745
|
* - `LarkClient.fromProvider(provider, opts)` — from ICredentialProvider (not cached)
|
|
7878
7746
|
*/
|
|
7879
|
-
const log$
|
|
7747
|
+
const log$22 = larkLogger("core/lark-client");
|
|
7880
7748
|
const GLOBAL_LARK_USER_AGENT_KEY = "LARK_USER_AGENT";
|
|
7881
7749
|
function installGlobalUserAgent() {
|
|
7882
7750
|
globalThis[GLOBAL_LARK_USER_AGENT_KEY] = getUserAgent();
|
|
@@ -7968,7 +7836,7 @@ var LarkClient = class LarkClient {
|
|
|
7968
7836
|
const existing = cache.get(account.accountId);
|
|
7969
7837
|
if (existing && existing.account.appId === account.appId && credentialsEqual(existing.account.appSecret, account.appSecret)) return existing;
|
|
7970
7838
|
if (existing) {
|
|
7971
|
-
log$
|
|
7839
|
+
log$22.info(`credentials changed, disposing stale instance`, { accountId: account.accountId });
|
|
7972
7840
|
existing.dispose();
|
|
7973
7841
|
}
|
|
7974
7842
|
const instance = new LarkClient(account);
|
|
@@ -8011,7 +7879,7 @@ var LarkClient = class LarkClient {
|
|
|
8011
7879
|
static fromProvider(provider, opts) {
|
|
8012
7880
|
const appId = provider.getAppId();
|
|
8013
7881
|
const appSecret = provider.getAppSecret();
|
|
8014
|
-
log$
|
|
7882
|
+
log$22.info("通过 ICredentialProvider 创建 LarkClient", {
|
|
8015
7883
|
appId,
|
|
8016
7884
|
accountId: opts?.accountId ?? "default",
|
|
8017
7885
|
brand: opts?.brand ?? "feishu"
|
|
@@ -8047,7 +7915,7 @@ var LarkClient = class LarkClient {
|
|
|
8047
7915
|
get sdk() {
|
|
8048
7916
|
if (!this._sdk) {
|
|
8049
7917
|
const { appId, appSecret } = this.requireCredentials();
|
|
8050
|
-
log$
|
|
7918
|
+
log$22.info("创建 Lark SDK 客户端实例", {
|
|
8051
7919
|
accountId: this.accountId,
|
|
8052
7920
|
appId,
|
|
8053
7921
|
brand: this.account.brand
|
|
@@ -8062,7 +7930,7 @@ var LarkClient = class LarkClient {
|
|
|
8062
7930
|
if (sdkAny.httpInstance?.interceptors) {
|
|
8063
7931
|
const accountId = this.accountId;
|
|
8064
7932
|
sdkAny.httpInstance.interceptors.request.use((req) => {
|
|
8065
|
-
log$
|
|
7933
|
+
log$22.debug("飞书 API 请求", {
|
|
8066
7934
|
accountId,
|
|
8067
7935
|
method: req.method,
|
|
8068
7936
|
url: req.url,
|
|
@@ -8072,7 +7940,7 @@ var LarkClient = class LarkClient {
|
|
|
8072
7940
|
return req;
|
|
8073
7941
|
}, void 0, { synchronous: true });
|
|
8074
7942
|
sdkAny.httpInstance.interceptors.response.use((res) => {
|
|
8075
|
-
log$
|
|
7943
|
+
log$22.debug("飞书 API 响应", {
|
|
8076
7944
|
accountId,
|
|
8077
7945
|
code: res?.code,
|
|
8078
7946
|
msg: res?.msg,
|
|
@@ -8080,7 +7948,7 @@ var LarkClient = class LarkClient {
|
|
|
8080
7948
|
});
|
|
8081
7949
|
return res;
|
|
8082
7950
|
}, (err) => {
|
|
8083
|
-
log$
|
|
7951
|
+
log$22.error("飞书 API 请求失败", {
|
|
8084
7952
|
accountId,
|
|
8085
7953
|
error: err instanceof Error ? err.message : String(err),
|
|
8086
7954
|
url: err?.config?.url
|
|
@@ -8167,7 +8035,7 @@ var LarkClient = class LarkClient {
|
|
|
8167
8035
|
dispatcher.register(handlers);
|
|
8168
8036
|
const { appId, appSecret } = this.requireCredentials();
|
|
8169
8037
|
if (this._wsClient) {
|
|
8170
|
-
log$
|
|
8038
|
+
log$22.warn(`closing previous WSClient before reconnect`, { accountId: this.accountId });
|
|
8171
8039
|
try {
|
|
8172
8040
|
this._wsClient.close({ force: true });
|
|
8173
8041
|
} catch {}
|
|
@@ -8184,7 +8052,7 @@ var LarkClient = class LarkClient {
|
|
|
8184
8052
|
wsClientAny.handleEventData = (data) => {
|
|
8185
8053
|
const msgType = data.headers?.find?.((h) => h.key === "type")?.value;
|
|
8186
8054
|
const eventType = data.headers?.find?.((h) => h.key === "event_type")?.value;
|
|
8187
|
-
log$
|
|
8055
|
+
log$22.info("WS 收到原始事件", {
|
|
8188
8056
|
accountId: this.accountId,
|
|
8189
8057
|
msgType,
|
|
8190
8058
|
eventType,
|
|
@@ -8210,14 +8078,14 @@ var LarkClient = class LarkClient {
|
|
|
8210
8078
|
/** Disconnect WebSocket but keep instance in cache. */
|
|
8211
8079
|
disconnect() {
|
|
8212
8080
|
if (this._wsClient) {
|
|
8213
|
-
log$
|
|
8081
|
+
log$22.info(`disconnecting WebSocket`, { accountId: this.accountId });
|
|
8214
8082
|
try {
|
|
8215
8083
|
this._wsClient.close({ force: true });
|
|
8216
8084
|
} catch {}
|
|
8217
8085
|
}
|
|
8218
8086
|
this._wsClient = null;
|
|
8219
8087
|
if (this.messageDedup) {
|
|
8220
|
-
log$
|
|
8088
|
+
log$22.info(`disposing message dedup`, {
|
|
8221
8089
|
accountId: this.accountId,
|
|
8222
8090
|
size: this.messageDedup.size
|
|
8223
8091
|
});
|
|
@@ -8523,7 +8391,7 @@ function sortTraceValue(value) {
|
|
|
8523
8391
|
}
|
|
8524
8392
|
//#endregion
|
|
8525
8393
|
//#region src/card/cc-stream-bridge.ts
|
|
8526
|
-
const log$
|
|
8394
|
+
const log$21 = larkLogger("card/cc-stream-bridge");
|
|
8527
8395
|
const CC_INTERNAL_PLACEHOLDER = "No response requested.";
|
|
8528
8396
|
/**
|
|
8529
8397
|
* CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
|
|
@@ -8548,7 +8416,7 @@ var CCStreamBridge = class {
|
|
|
8548
8416
|
sessionKey: options?.sessionKey
|
|
8549
8417
|
};
|
|
8550
8418
|
if (this.options.sessionKey) startToolUseTraceRun(this.options.sessionKey);
|
|
8551
|
-
log$
|
|
8419
|
+
log$21.info("CCStreamBridge 初始化", {
|
|
8552
8420
|
autoCompleteOnTurnEnd: this.options.autoCompleteOnTurnEnd,
|
|
8553
8421
|
sessionKey: this.options.sessionKey ?? "(none)"
|
|
8554
8422
|
});
|
|
@@ -8556,7 +8424,7 @@ var CCStreamBridge = class {
|
|
|
8556
8424
|
/** 文本增量 → 累积后调用 controller.onPartialReply */
|
|
8557
8425
|
onTextDelta(text) {
|
|
8558
8426
|
this.accumulatedText += text;
|
|
8559
|
-
log$
|
|
8427
|
+
log$21.debug("textDelta 事件", {
|
|
8560
8428
|
deltaLen: text.length,
|
|
8561
8429
|
totalLen: this.accumulatedText.length
|
|
8562
8430
|
});
|
|
@@ -8565,7 +8433,7 @@ var CCStreamBridge = class {
|
|
|
8565
8433
|
/** 思考增量 → 累积后调用 controller.onReasoningStream */
|
|
8566
8434
|
onThinkingDelta(text) {
|
|
8567
8435
|
this.accumulatedThinkingText += text;
|
|
8568
|
-
log$
|
|
8436
|
+
log$21.debug("thinkingDelta 事件", {
|
|
8569
8437
|
deltaLen: text.length,
|
|
8570
8438
|
totalLen: this.accumulatedThinkingText.length
|
|
8571
8439
|
});
|
|
@@ -8577,7 +8445,7 @@ var CCStreamBridge = class {
|
|
|
8577
8445
|
const displayName = getToolDisplayName(toolName);
|
|
8578
8446
|
const toolParams = typeof _toolInput === "object" && _toolInput != null ? _toolInput : void 0;
|
|
8579
8447
|
const hasParams = toolParams && Object.keys(toolParams).length > 0;
|
|
8580
|
-
log$
|
|
8448
|
+
log$21.info("toolUseStart 事件", {
|
|
8581
8449
|
toolName,
|
|
8582
8450
|
displayName,
|
|
8583
8451
|
activeToolsCount: this.activeTools.size,
|
|
@@ -8590,7 +8458,7 @@ var CCStreamBridge = class {
|
|
|
8590
8458
|
toolName,
|
|
8591
8459
|
toolParams
|
|
8592
8460
|
})) {
|
|
8593
|
-
log$
|
|
8461
|
+
log$21.info("toolUseStart: 更新已有步骤的 params", { toolName });
|
|
8594
8462
|
this.controller.onToolStart({
|
|
8595
8463
|
name: toolName,
|
|
8596
8464
|
phase: "start"
|
|
@@ -8614,7 +8482,7 @@ var CCStreamBridge = class {
|
|
|
8614
8482
|
const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
|
|
8615
8483
|
const displayName = getToolDisplayName(toolName);
|
|
8616
8484
|
this.activeTools.delete(toolUseId);
|
|
8617
|
-
log$
|
|
8485
|
+
log$21.info("toolResult 事件", {
|
|
8618
8486
|
toolUseId,
|
|
8619
8487
|
toolName,
|
|
8620
8488
|
displayName,
|
|
@@ -8629,7 +8497,7 @@ var CCStreamBridge = class {
|
|
|
8629
8497
|
}
|
|
8630
8498
|
/** 工具执行进度 → 记录日志 */
|
|
8631
8499
|
onToolProgress(toolName, elapsedSeconds) {
|
|
8632
|
-
log$
|
|
8500
|
+
log$21.debug("toolProgress 事件", {
|
|
8633
8501
|
toolName,
|
|
8634
8502
|
displayName: getToolDisplayName(toolName),
|
|
8635
8503
|
elapsedSeconds
|
|
@@ -8637,7 +8505,7 @@ var CCStreamBridge = class {
|
|
|
8637
8505
|
}
|
|
8638
8506
|
/** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
|
|
8639
8507
|
onTurnEnd(stopReason) {
|
|
8640
|
-
log$
|
|
8508
|
+
log$21.info("turnEnd 事件", {
|
|
8641
8509
|
stopReason,
|
|
8642
8510
|
accumulatedTextLen: this.accumulatedText.length,
|
|
8643
8511
|
accumulatedThinkingTextLen: this.accumulatedThinkingText.length
|
|
@@ -8645,7 +8513,7 @@ var CCStreamBridge = class {
|
|
|
8645
8513
|
if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
|
|
8646
8514
|
const trimmedText = this.accumulatedText.trim();
|
|
8647
8515
|
if (trimmedText === CC_INTERNAL_PLACEHOLDER) {
|
|
8648
|
-
log$
|
|
8516
|
+
log$21.info("检测到 CC 内部占位消息,静默丢弃", {
|
|
8649
8517
|
text: trimmedText.slice(0, 50),
|
|
8650
8518
|
sessionKey: this.options.sessionKey
|
|
8651
8519
|
});
|
|
@@ -8653,7 +8521,7 @@ var CCStreamBridge = class {
|
|
|
8653
8521
|
return;
|
|
8654
8522
|
}
|
|
8655
8523
|
if (trimmedText === "") {
|
|
8656
|
-
log$
|
|
8524
|
+
log$21.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
|
|
8657
8525
|
return;
|
|
8658
8526
|
}
|
|
8659
8527
|
this.controller.markFullyComplete();
|
|
@@ -8662,7 +8530,7 @@ var CCStreamBridge = class {
|
|
|
8662
8530
|
}
|
|
8663
8531
|
/** 最终结果 → 兜底最终化 + 错误处理 */
|
|
8664
8532
|
onResult(data) {
|
|
8665
|
-
log$
|
|
8533
|
+
log$21.info("result 事件", {
|
|
8666
8534
|
subtype: data.subtype,
|
|
8667
8535
|
isError: data.isError,
|
|
8668
8536
|
durationMs: data.durationMs,
|
|
@@ -8671,7 +8539,7 @@ var CCStreamBridge = class {
|
|
|
8671
8539
|
resultLen: data.result?.length ?? 0
|
|
8672
8540
|
});
|
|
8673
8541
|
if (data.aborted) {
|
|
8674
|
-
log$
|
|
8542
|
+
log$21.info("运行时报告用户主动中断,按正常完成处理", {
|
|
8675
8543
|
subtype: data.subtype,
|
|
8676
8544
|
sessionId: this.options.sessionKey
|
|
8677
8545
|
});
|
|
@@ -8682,7 +8550,7 @@ var CCStreamBridge = class {
|
|
|
8682
8550
|
const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
|
|
8683
8551
|
const sessionId = this.options.sessionKey;
|
|
8684
8552
|
if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
|
|
8685
|
-
log$
|
|
8553
|
+
log$21.info("用户主动中断,按正常完成处理", {
|
|
8686
8554
|
subtype: data.subtype,
|
|
8687
8555
|
sessionId
|
|
8688
8556
|
});
|
|
@@ -8691,14 +8559,14 @@ var CCStreamBridge = class {
|
|
|
8691
8559
|
this.controller.onIdle();
|
|
8692
8560
|
} else {
|
|
8693
8561
|
const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
|
|
8694
|
-
log$
|
|
8562
|
+
log$21.error("CC 执行返回错误", {
|
|
8695
8563
|
subtype: data.subtype,
|
|
8696
8564
|
errorMessage: errorMessage.slice(0, 200)
|
|
8697
8565
|
});
|
|
8698
8566
|
this.controller.onError(new Error(errorMessage), { kind: "cc-execution" });
|
|
8699
8567
|
}
|
|
8700
8568
|
} else if (!this.accumulatedText.trim()) if (data.result) {
|
|
8701
|
-
log$
|
|
8569
|
+
log$21.info("result 兜底:使用 result.result 作为最终回复", {
|
|
8702
8570
|
resultLen: data.result.length,
|
|
8703
8571
|
sessionKey: this.options.sessionKey
|
|
8704
8572
|
});
|
|
@@ -8706,7 +8574,7 @@ var CCStreamBridge = class {
|
|
|
8706
8574
|
this.controller.markFullyComplete();
|
|
8707
8575
|
this.controller.onIdle();
|
|
8708
8576
|
} else {
|
|
8709
|
-
log$
|
|
8577
|
+
log$21.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
|
|
8710
8578
|
this.controller.abortCard();
|
|
8711
8579
|
}
|
|
8712
8580
|
else {
|
|
@@ -8720,7 +8588,7 @@ var CCStreamBridge = class {
|
|
|
8720
8588
|
* 内部复用上面的直接调用方法。
|
|
8721
8589
|
*/
|
|
8722
8590
|
bindParser(parser) {
|
|
8723
|
-
log$
|
|
8591
|
+
log$21.info("绑定 CCStreamParser 事件到卡片控制器");
|
|
8724
8592
|
parser.on("textDelta", (text) => this.onTextDelta(text));
|
|
8725
8593
|
parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
|
|
8726
8594
|
parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
|
|
@@ -8728,7 +8596,7 @@ var CCStreamBridge = class {
|
|
|
8728
8596
|
parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
|
|
8729
8597
|
parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
|
|
8730
8598
|
parser.on("result", (data) => this.onResult(data));
|
|
8731
|
-
log$
|
|
8599
|
+
log$21.info("CCStreamParser 事件绑定完成");
|
|
8732
8600
|
}
|
|
8733
8601
|
};
|
|
8734
8602
|
//#endregion
|
|
@@ -10747,7 +10615,7 @@ function resolveLarkSdk(cfg, accountId) {
|
|
|
10747
10615
|
if (cached) return cached.sdk;
|
|
10748
10616
|
return LarkClient.fromCfg(cfg, accountId).sdk;
|
|
10749
10617
|
}
|
|
10750
|
-
const log$
|
|
10618
|
+
const log$20 = larkLogger("card/cardkit");
|
|
10751
10619
|
/**
|
|
10752
10620
|
* 记录 CardKit API 响应日志,检测错误码并抛出异常。
|
|
10753
10621
|
*
|
|
@@ -10757,13 +10625,13 @@ const log$19 = larkLogger("card/cardkit");
|
|
|
10757
10625
|
function logCardKitResponse(params) {
|
|
10758
10626
|
const { resp, api, context } = params;
|
|
10759
10627
|
const { code, msg } = resp;
|
|
10760
|
-
log$
|
|
10628
|
+
log$20.info(`cardkit ${api} response`, {
|
|
10761
10629
|
code,
|
|
10762
10630
|
msg,
|
|
10763
10631
|
context
|
|
10764
10632
|
});
|
|
10765
10633
|
if (code && code !== 0) {
|
|
10766
|
-
log$
|
|
10634
|
+
log$20.warn(`cardkit ${api} FAILED`, {
|
|
10767
10635
|
code,
|
|
10768
10636
|
msg,
|
|
10769
10637
|
context,
|
|
@@ -11174,7 +11042,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
|
|
|
11174
11042
|
* Feishu messages, uploading media to the Feishu IM storage, and
|
|
11175
11043
|
* sending image / file messages to chats.
|
|
11176
11044
|
*/
|
|
11177
|
-
const log$
|
|
11045
|
+
const log$19 = larkLogger("outbound/media");
|
|
11178
11046
|
/**
|
|
11179
11047
|
* Upload an image to Feishu IM storage.
|
|
11180
11048
|
*
|
|
@@ -11242,7 +11110,7 @@ async function validateRemoteUrl(raw) {
|
|
|
11242
11110
|
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}"`);
|
|
11243
11111
|
} catch (err) {
|
|
11244
11112
|
if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
|
|
11245
|
-
log$
|
|
11113
|
+
log$19.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
|
|
11246
11114
|
}
|
|
11247
11115
|
}
|
|
11248
11116
|
/**
|
|
@@ -11260,21 +11128,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
|
|
|
11260
11128
|
if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
|
|
11261
11129
|
else throw new Error(`[feishu-media] Local file access denied for "${filePath}": mediaLocalRoots is not configured. Configure mediaLocalRoots to explicitly allow local file access.`);
|
|
11262
11130
|
const buf = fs.readFileSync(filePath);
|
|
11263
|
-
log$
|
|
11131
|
+
log$19.debug(`local file read: "${filePath}", ${buf.length} bytes`);
|
|
11264
11132
|
return buf;
|
|
11265
11133
|
}
|
|
11266
11134
|
await validateRemoteUrl(raw);
|
|
11267
11135
|
const FETCH_TIMEOUT_MS = 3e4;
|
|
11268
|
-
log$
|
|
11136
|
+
log$19.info(`fetching remote media: ${raw}`);
|
|
11269
11137
|
const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
11270
11138
|
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.`);
|
|
11271
11139
|
const arrayBuffer = await response.arrayBuffer();
|
|
11272
|
-
log$
|
|
11140
|
+
log$19.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
|
|
11273
11141
|
return Buffer.from(arrayBuffer);
|
|
11274
11142
|
}
|
|
11275
11143
|
//#endregion
|
|
11276
11144
|
//#region src/card/image-resolver.ts
|
|
11277
|
-
const log$
|
|
11145
|
+
const log$18 = larkLogger("card/image-resolver");
|
|
11278
11146
|
/** Matches complete markdown image syntax: `` */
|
|
11279
11147
|
const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
|
|
11280
11148
|
var ImageResolver = class {
|
|
@@ -11320,14 +11188,14 @@ var ImageResolver = class {
|
|
|
11320
11188
|
async resolveImagesAwait(text, timeoutMs) {
|
|
11321
11189
|
this.resolveImages(text);
|
|
11322
11190
|
if (this.pending.size > 0) {
|
|
11323
|
-
log$
|
|
11191
|
+
log$18.info("resolveImagesAwait: waiting for uploads", {
|
|
11324
11192
|
count: this.pending.size,
|
|
11325
11193
|
timeoutMs
|
|
11326
11194
|
});
|
|
11327
11195
|
const allUploads = Promise.all(this.pending.values());
|
|
11328
11196
|
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
11329
11197
|
await Promise.race([allUploads, timeout]);
|
|
11330
|
-
if (this.pending.size > 0) log$
|
|
11198
|
+
if (this.pending.size > 0) log$18.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
|
|
11331
11199
|
}
|
|
11332
11200
|
return this.resolveImages(text);
|
|
11333
11201
|
}
|
|
@@ -11337,7 +11205,7 @@ var ImageResolver = class {
|
|
|
11337
11205
|
}
|
|
11338
11206
|
async doUpload(url) {
|
|
11339
11207
|
try {
|
|
11340
|
-
log$
|
|
11208
|
+
log$18.info("uploading image", { url });
|
|
11341
11209
|
const buffer = await fetchRemoteImageBuffer(url);
|
|
11342
11210
|
const { imageKey } = await uploadImageLark({
|
|
11343
11211
|
cfg: this.cfg,
|
|
@@ -11345,7 +11213,7 @@ var ImageResolver = class {
|
|
|
11345
11213
|
imageType: "message",
|
|
11346
11214
|
accountId: this.accountId
|
|
11347
11215
|
});
|
|
11348
|
-
log$
|
|
11216
|
+
log$18.info("image uploaded", {
|
|
11349
11217
|
url,
|
|
11350
11218
|
imageKey
|
|
11351
11219
|
});
|
|
@@ -11354,7 +11222,7 @@ var ImageResolver = class {
|
|
|
11354
11222
|
this.onImageResolved();
|
|
11355
11223
|
return imageKey;
|
|
11356
11224
|
} catch (err) {
|
|
11357
|
-
log$
|
|
11225
|
+
log$18.warn("image upload failed", {
|
|
11358
11226
|
url,
|
|
11359
11227
|
error: String(err)
|
|
11360
11228
|
});
|
|
@@ -11375,7 +11243,7 @@ var ImageResolver = class {
|
|
|
11375
11243
|
* Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
|
|
11376
11244
|
* logic previously scattered as closures in reply-dispatcher.ts.
|
|
11377
11245
|
*/
|
|
11378
|
-
const log$
|
|
11246
|
+
const log$17 = larkLogger("card/unavailable-guard");
|
|
11379
11247
|
var UnavailableGuard = class {
|
|
11380
11248
|
terminated = false;
|
|
11381
11249
|
replyToMessageId;
|
|
@@ -11428,7 +11296,7 @@ var UnavailableGuard = class {
|
|
|
11428
11296
|
this.terminated = true;
|
|
11429
11297
|
this.onTerminate();
|
|
11430
11298
|
const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
|
|
11431
|
-
log$
|
|
11299
|
+
log$17.warn("reply pipeline terminated by unavailable message", {
|
|
11432
11300
|
source,
|
|
11433
11301
|
apiCode,
|
|
11434
11302
|
messageId: affectedMessageId
|
|
@@ -11465,7 +11333,7 @@ function getActiveCard(sessionId) {
|
|
|
11465
11333
|
* Delegates throttling to FlushController and message-unavailable
|
|
11466
11334
|
* detection to UnavailableGuard.
|
|
11467
11335
|
*/
|
|
11468
|
-
const log$
|
|
11336
|
+
const log$16 = larkLogger("card/streaming");
|
|
11469
11337
|
var StreamingCardController = class StreamingCardController {
|
|
11470
11338
|
phase = "idle";
|
|
11471
11339
|
cardKit = {
|
|
@@ -11553,7 +11421,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11553
11421
|
}
|
|
11554
11422
|
}
|
|
11555
11423
|
if (!entry) {
|
|
11556
|
-
log$
|
|
11424
|
+
log$16.debug("footer metrics lookup: session entry missing", {
|
|
11557
11425
|
sessionKey: this.deps.sessionKey,
|
|
11558
11426
|
candidateKeys,
|
|
11559
11427
|
storePath,
|
|
@@ -11571,7 +11439,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11571
11439
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
11572
11440
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
11573
11441
|
};
|
|
11574
|
-
log$
|
|
11442
|
+
log$16.debug("footer metrics lookup: session entry found", {
|
|
11575
11443
|
sessionKey: this.deps.sessionKey,
|
|
11576
11444
|
matchedKey,
|
|
11577
11445
|
storePath,
|
|
@@ -11596,7 +11464,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11596
11464
|
}
|
|
11597
11465
|
}
|
|
11598
11466
|
if (!entry) {
|
|
11599
|
-
log$
|
|
11467
|
+
log$16.debug("footer metrics lookup: session entry missing", {
|
|
11600
11468
|
sessionKey: this.deps.sessionKey,
|
|
11601
11469
|
candidateKeys,
|
|
11602
11470
|
storePath,
|
|
@@ -11614,7 +11482,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11614
11482
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
11615
11483
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
11616
11484
|
};
|
|
11617
|
-
log$
|
|
11485
|
+
log$16.debug("footer metrics lookup: session entry found", {
|
|
11618
11486
|
sessionKey: this.deps.sessionKey,
|
|
11619
11487
|
matchedKey,
|
|
11620
11488
|
storePath,
|
|
@@ -11622,7 +11490,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11622
11490
|
});
|
|
11623
11491
|
return metrics;
|
|
11624
11492
|
} catch (err) {
|
|
11625
|
-
log$
|
|
11493
|
+
log$16.warn("footer metrics lookup failed", {
|
|
11626
11494
|
error: String(err),
|
|
11627
11495
|
sessionKey: this.deps.sessionKey
|
|
11628
11496
|
});
|
|
@@ -11729,7 +11597,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11729
11597
|
const from = this.phase;
|
|
11730
11598
|
if (from === to) return false;
|
|
11731
11599
|
if (!PHASE_TRANSITIONS[from].has(to)) {
|
|
11732
|
-
log$
|
|
11600
|
+
log$16.warn("phase transition rejected", {
|
|
11733
11601
|
from,
|
|
11734
11602
|
to,
|
|
11735
11603
|
source
|
|
@@ -11737,7 +11605,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11737
11605
|
return false;
|
|
11738
11606
|
}
|
|
11739
11607
|
this.phase = to;
|
|
11740
|
-
log$
|
|
11608
|
+
log$16.info("phase transition", {
|
|
11741
11609
|
from,
|
|
11742
11610
|
to,
|
|
11743
11611
|
source,
|
|
@@ -11857,7 +11725,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11857
11725
|
this.reasoning.dirty = true;
|
|
11858
11726
|
}
|
|
11859
11727
|
const text = split.answerText ?? stripReasoningTags(rawText);
|
|
11860
|
-
log$
|
|
11728
|
+
log$16.debug("onPartialReply", { len: text.length });
|
|
11861
11729
|
if (!text) return;
|
|
11862
11730
|
this.captureToolUseElapsed();
|
|
11863
11731
|
if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
|
|
@@ -11869,7 +11737,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11869
11737
|
this.text.lastPartialText = text;
|
|
11870
11738
|
this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
|
|
11871
11739
|
if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
|
|
11872
|
-
log$
|
|
11740
|
+
log$16.debug("onPartialReply: buffering NO_REPLY prefix");
|
|
11873
11741
|
return;
|
|
11874
11742
|
}
|
|
11875
11743
|
await this.ensureCardCreated();
|
|
@@ -11879,7 +11747,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11879
11747
|
}
|
|
11880
11748
|
async onError(err, info) {
|
|
11881
11749
|
if (this.guard.terminate("onError", err)) return;
|
|
11882
|
-
log$
|
|
11750
|
+
log$16.error(`${info.kind} reply failed`, { error: String(err) });
|
|
11883
11751
|
this.captureToolUseElapsed();
|
|
11884
11752
|
this.finalizeCard("onError", "error");
|
|
11885
11753
|
await this.flush.waitForFlush();
|
|
@@ -11935,7 +11803,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11935
11803
|
if (this.cardKit.cardMessageId) {
|
|
11936
11804
|
const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
|
|
11937
11805
|
const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
|
|
11938
|
-
if (!this.text.completedText && !this.text.accumulatedText) log$
|
|
11806
|
+
if (!this.text.completedText && !this.text.accumulatedText) log$16.warn("reply completed without visible text, using empty-reply fallback");
|
|
11939
11807
|
const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
|
|
11940
11808
|
const idleToolUseDisplay = this.computeToolUseDisplay();
|
|
11941
11809
|
const terminalContent = prepareTerminalCardContent({
|
|
@@ -11963,7 +11831,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11963
11831
|
const rewindSessionId = this.deps.sessionKey;
|
|
11964
11832
|
const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
|
|
11965
11833
|
const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
|
|
11966
|
-
log$
|
|
11834
|
+
log$16.info("onIdle: rewind button conditions", {
|
|
11967
11835
|
sessionId: rewindSessionId,
|
|
11968
11836
|
messageId: rewindMessageId,
|
|
11969
11837
|
hasFileChanges: rewindHasFileChanges,
|
|
@@ -11976,7 +11844,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11976
11844
|
if (idleEffectiveCardId) {
|
|
11977
11845
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
11978
11846
|
this.cardKit.cardKitSequence += 1;
|
|
11979
|
-
log$
|
|
11847
|
+
log$16.info("onIdle: closing streaming mode", {
|
|
11980
11848
|
attempt,
|
|
11981
11849
|
seqBefore: seqBeforeClose,
|
|
11982
11850
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -11990,7 +11858,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11990
11858
|
});
|
|
11991
11859
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
11992
11860
|
this.cardKit.cardKitSequence += 1;
|
|
11993
|
-
log$
|
|
11861
|
+
log$16.info("onIdle: updating final card", {
|
|
11994
11862
|
attempt,
|
|
11995
11863
|
seqBefore: seqBeforeUpdate,
|
|
11996
11864
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -12008,33 +11876,33 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12008
11876
|
card: completeCard,
|
|
12009
11877
|
accountId: this.deps.accountId
|
|
12010
11878
|
});
|
|
12011
|
-
log$
|
|
11879
|
+
log$16.info("reply completed, card finalized", {
|
|
12012
11880
|
elapsedMs: this.elapsed(),
|
|
12013
11881
|
isCardKit: !!idleEffectiveCardId,
|
|
12014
11882
|
attempt
|
|
12015
11883
|
});
|
|
12016
11884
|
break;
|
|
12017
11885
|
} catch (retryErr) {
|
|
12018
|
-
log$
|
|
11886
|
+
log$16.warn("final card update attempt failed", {
|
|
12019
11887
|
attempt,
|
|
12020
11888
|
maxRetries: MAX_FINAL_UPDATE_RETRIES,
|
|
12021
11889
|
error: String(retryErr)
|
|
12022
11890
|
});
|
|
12023
11891
|
if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
|
|
12024
|
-
else log$
|
|
11892
|
+
else log$16.error("final card update exhausted all retries, card may remain in streaming state", {
|
|
12025
11893
|
cardId: idleEffectiveCardId,
|
|
12026
11894
|
messageId: this.cardKit.cardMessageId
|
|
12027
11895
|
});
|
|
12028
11896
|
}
|
|
12029
11897
|
}
|
|
12030
11898
|
} catch (err) {
|
|
12031
|
-
log$
|
|
11899
|
+
log$16.error("final card update failed (outer)", { error: String(err) });
|
|
12032
11900
|
} finally {
|
|
12033
11901
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12034
11902
|
}
|
|
12035
11903
|
}
|
|
12036
11904
|
markFullyComplete() {
|
|
12037
|
-
log$
|
|
11905
|
+
log$16.debug("markFullyComplete", {
|
|
12038
11906
|
completedTextLen: this.text.completedText.length,
|
|
12039
11907
|
accumulatedTextLen: this.text.accumulatedText.length
|
|
12040
11908
|
});
|
|
@@ -12073,7 +11941,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12073
11941
|
footerMetrics
|
|
12074
11942
|
});
|
|
12075
11943
|
await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
|
|
12076
|
-
log$
|
|
11944
|
+
log$16.info("abortCard completed", { effectiveCardId });
|
|
12077
11945
|
} else if (this.cardKit.cardMessageId) {
|
|
12078
11946
|
const abortCard = buildCardContent("complete", {
|
|
12079
11947
|
text: terminalContent.text,
|
|
@@ -12094,10 +11962,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12094
11962
|
card: abortCard,
|
|
12095
11963
|
accountId: this.deps.accountId
|
|
12096
11964
|
});
|
|
12097
|
-
log$
|
|
11965
|
+
log$16.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
|
|
12098
11966
|
}
|
|
12099
11967
|
} catch (err) {
|
|
12100
|
-
log$
|
|
11968
|
+
log$16.warn("abortCard failed", { error: String(err) });
|
|
12101
11969
|
} finally {
|
|
12102
11970
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12103
11971
|
}
|
|
@@ -12112,7 +11980,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12112
11980
|
if (effectiveCardId) {
|
|
12113
11981
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
12114
11982
|
this.cardKit.cardKitSequence += 1;
|
|
12115
|
-
log$
|
|
11983
|
+
log$16.info("replaceWithClarificationCard: closing streaming mode", {
|
|
12116
11984
|
cardId: effectiveCardId,
|
|
12117
11985
|
seqBefore: seqBeforeClose,
|
|
12118
11986
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -12133,7 +12001,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12133
12001
|
sequence: this.cardKit.cardKitSequence,
|
|
12134
12002
|
accountId: this.deps.accountId
|
|
12135
12003
|
});
|
|
12136
|
-
log$
|
|
12004
|
+
log$16.info("replaceWithClarificationCard: existing card replaced", {
|
|
12137
12005
|
cardId: effectiveCardId,
|
|
12138
12006
|
seqBefore: seqBeforeUpdate,
|
|
12139
12007
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -12156,7 +12024,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12156
12024
|
this.cardKit.cardKitCardId = cardId;
|
|
12157
12025
|
this.cardKit.originalCardKitCardId = cardId;
|
|
12158
12026
|
this.cardKit.cardMessageId = result.messageId;
|
|
12159
|
-
log$
|
|
12027
|
+
log$16.info("replaceWithClarificationCard: new clarification card sent", {
|
|
12160
12028
|
cardId,
|
|
12161
12029
|
messageId: result.messageId
|
|
12162
12030
|
});
|
|
@@ -12165,7 +12033,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12165
12033
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12166
12034
|
return true;
|
|
12167
12035
|
} catch (err) {
|
|
12168
|
-
log$
|
|
12036
|
+
log$16.error("replaceWithClarificationCard failed", { error: String(err) });
|
|
12169
12037
|
return false;
|
|
12170
12038
|
}
|
|
12171
12039
|
}
|
|
@@ -12179,10 +12047,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12179
12047
|
async forceCloseStreaming() {
|
|
12180
12048
|
const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
12181
12049
|
if (!effectiveCardId && !this.cardKit.cardMessageId) {
|
|
12182
|
-
log$
|
|
12050
|
+
log$16.warn("forceCloseStreaming: no card to close");
|
|
12183
12051
|
return;
|
|
12184
12052
|
}
|
|
12185
|
-
log$
|
|
12053
|
+
log$16.info("forceCloseStreaming: 强制终态化卡片", {
|
|
12186
12054
|
cardId: effectiveCardId,
|
|
12187
12055
|
messageId: this.cardKit.cardMessageId,
|
|
12188
12056
|
phase: this.phase
|
|
@@ -12232,10 +12100,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12232
12100
|
card: completeCard,
|
|
12233
12101
|
accountId: this.deps.accountId
|
|
12234
12102
|
});
|
|
12235
|
-
log$
|
|
12103
|
+
log$16.info("forceCloseStreaming: 卡片已强制终态化");
|
|
12236
12104
|
if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
|
|
12237
12105
|
} catch (err) {
|
|
12238
|
-
log$
|
|
12106
|
+
log$16.error("forceCloseStreaming failed", { error: String(err) });
|
|
12239
12107
|
} finally {
|
|
12240
12108
|
unregisterActiveCard(this.deps.sessionKey);
|
|
12241
12109
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
@@ -12259,7 +12127,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12259
12127
|
this.disposeShutdownHook = null;
|
|
12260
12128
|
return;
|
|
12261
12129
|
}
|
|
12262
|
-
log$
|
|
12130
|
+
log$16.info("reusing placeholder card", {
|
|
12263
12131
|
cardId: this.deps.placeholderCardId,
|
|
12264
12132
|
messageId: this.deps.placeholderMessageId
|
|
12265
12133
|
});
|
|
@@ -12283,7 +12151,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12283
12151
|
accountId: this.deps.accountId
|
|
12284
12152
|
});
|
|
12285
12153
|
if (this.isStaleCreate(epoch)) {
|
|
12286
|
-
log$
|
|
12154
|
+
log$16.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
|
|
12287
12155
|
epoch,
|
|
12288
12156
|
phase: this.phase
|
|
12289
12157
|
});
|
|
@@ -12294,7 +12162,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12294
12162
|
this.cardKit.originalCardKitCardId = cId;
|
|
12295
12163
|
this.cardKit.cardKitSequence = 1;
|
|
12296
12164
|
this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
|
|
12297
|
-
log$
|
|
12165
|
+
log$16.info("created CardKit entity", {
|
|
12298
12166
|
cardId: cId,
|
|
12299
12167
|
initialSequence: this.cardKit.cardKitSequence
|
|
12300
12168
|
});
|
|
@@ -12307,7 +12175,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12307
12175
|
accountId: this.deps.accountId
|
|
12308
12176
|
});
|
|
12309
12177
|
if (this.isStaleCreate(epoch)) {
|
|
12310
|
-
log$
|
|
12178
|
+
log$16.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
|
|
12311
12179
|
epoch,
|
|
12312
12180
|
phase: this.phase
|
|
12313
12181
|
});
|
|
@@ -12322,13 +12190,13 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12322
12190
|
this.disposeShutdownHook = null;
|
|
12323
12191
|
return;
|
|
12324
12192
|
}
|
|
12325
|
-
log$
|
|
12193
|
+
log$16.info("sent CardKit card", { messageId: result.messageId });
|
|
12326
12194
|
} else throw new Error("card.create returned empty card_id");
|
|
12327
12195
|
} catch (cardKitErr) {
|
|
12328
12196
|
if (this.isStaleCreate(epoch)) return;
|
|
12329
12197
|
if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
|
|
12330
12198
|
const apiDetail = extractApiDetail(cardKitErr);
|
|
12331
|
-
log$
|
|
12199
|
+
log$16.warn("CardKit flow failed, falling back to IM", { apiDetail });
|
|
12332
12200
|
this.cardKit.cardKitCardId = null;
|
|
12333
12201
|
this.cardKit.originalCardKitCardId = null;
|
|
12334
12202
|
const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
|
|
@@ -12341,7 +12209,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12341
12209
|
accountId: this.deps.accountId
|
|
12342
12210
|
});
|
|
12343
12211
|
if (this.isStaleCreate(epoch)) {
|
|
12344
|
-
log$
|
|
12212
|
+
log$16.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
|
|
12345
12213
|
epoch,
|
|
12346
12214
|
phase: this.phase
|
|
12347
12215
|
});
|
|
@@ -12350,12 +12218,12 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12350
12218
|
this.cardKit.cardMessageId = result.messageId;
|
|
12351
12219
|
this.flush.setCardMessageReady(true);
|
|
12352
12220
|
if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
|
|
12353
|
-
log$
|
|
12221
|
+
log$16.info("sent fallback IM card", { messageId: result.messageId });
|
|
12354
12222
|
}
|
|
12355
12223
|
} catch (err) {
|
|
12356
12224
|
if (this.isStaleCreate(epoch)) return;
|
|
12357
12225
|
if (this.guard.terminate("ensureCardCreated.outer", err)) return;
|
|
12358
|
-
log$
|
|
12226
|
+
log$16.warn("thinking card failed, falling back to static", { error: String(err) });
|
|
12359
12227
|
this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
|
|
12360
12228
|
}
|
|
12361
12229
|
})();
|
|
@@ -12364,10 +12232,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12364
12232
|
async performFlush() {
|
|
12365
12233
|
if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
|
|
12366
12234
|
if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
|
|
12367
|
-
log$
|
|
12235
|
+
log$16.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
|
|
12368
12236
|
return;
|
|
12369
12237
|
}
|
|
12370
|
-
log$
|
|
12238
|
+
log$16.debug("flushCardUpdate: enter", {
|
|
12371
12239
|
seq: this.cardKit.cardKitSequence,
|
|
12372
12240
|
isCardKit: !!this.cardKit.cardKitCardId
|
|
12373
12241
|
});
|
|
@@ -12389,7 +12257,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12389
12257
|
reasoningText: this.reasoning.accumulatedReasoningText || void 0
|
|
12390
12258
|
});
|
|
12391
12259
|
this.cardKit.cardKitSequence += 1;
|
|
12392
|
-
log$
|
|
12260
|
+
log$16.debug("flushCardUpdate: full card update (dirty)", {
|
|
12393
12261
|
seq: this.cardKit.cardKitSequence,
|
|
12394
12262
|
stepCount: display?.stepCount,
|
|
12395
12263
|
hasReasoning: !!this.reasoning.accumulatedReasoningText
|
|
@@ -12405,7 +12273,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12405
12273
|
} else if (resolvedText !== this.text.lastFlushedText) {
|
|
12406
12274
|
const prevSeq = this.cardKit.cardKitSequence;
|
|
12407
12275
|
this.cardKit.cardKitSequence += 1;
|
|
12408
|
-
log$
|
|
12276
|
+
log$16.debug("flushCardUpdate: answer seq bump", {
|
|
12409
12277
|
seqBefore: prevSeq,
|
|
12410
12278
|
seqAfter: this.cardKit.cardKitSequence
|
|
12411
12279
|
});
|
|
@@ -12420,7 +12288,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12420
12288
|
this.text.lastFlushedText = resolvedText;
|
|
12421
12289
|
}
|
|
12422
12290
|
} else {
|
|
12423
|
-
log$
|
|
12291
|
+
log$16.debug("flushCardUpdate: IM patch fallback");
|
|
12424
12292
|
const flushDisplay = this.computeToolUseDisplay();
|
|
12425
12293
|
const card = buildCardContent("streaming", {
|
|
12426
12294
|
text: this.reasoning.isReasoningPhase ? "" : resolvedText,
|
|
@@ -12440,31 +12308,31 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12440
12308
|
if (this.guard.terminate("flushCardUpdate", err)) return;
|
|
12441
12309
|
const apiCode = extractLarkApiCode(err);
|
|
12442
12310
|
if (isCardRateLimitError(err)) {
|
|
12443
|
-
log$
|
|
12311
|
+
log$16.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
|
|
12444
12312
|
return;
|
|
12445
12313
|
}
|
|
12446
12314
|
if (isCardTableLimitError(err)) {
|
|
12447
|
-
log$
|
|
12315
|
+
log$16.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
|
|
12448
12316
|
this.cardKit.cardKitCardId = null;
|
|
12449
12317
|
return;
|
|
12450
12318
|
}
|
|
12451
12319
|
if (apiCode === 300317 && this.cardKit.cardKitCardId) {
|
|
12452
12320
|
const oldSeq = this.cardKit.cardKitSequence;
|
|
12453
12321
|
this.cardKit.cardKitSequence += 100;
|
|
12454
|
-
log$
|
|
12322
|
+
log$16.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
|
|
12455
12323
|
oldSeq,
|
|
12456
12324
|
newSeq: this.cardKit.cardKitSequence
|
|
12457
12325
|
});
|
|
12458
12326
|
return;
|
|
12459
12327
|
}
|
|
12460
12328
|
const apiDetail = extractApiDetail(err);
|
|
12461
|
-
log$
|
|
12329
|
+
log$16.error("card stream update failed", {
|
|
12462
12330
|
apiCode,
|
|
12463
12331
|
seq: this.cardKit.cardKitSequence,
|
|
12464
12332
|
apiDetail
|
|
12465
12333
|
});
|
|
12466
12334
|
if (this.cardKit.cardKitCardId) {
|
|
12467
|
-
log$
|
|
12335
|
+
log$16.warn("disabling CardKit streaming, falling back to im.message.patch");
|
|
12468
12336
|
this.cardKit.cardKitCardId = null;
|
|
12469
12337
|
}
|
|
12470
12338
|
}
|
|
@@ -12489,7 +12357,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12489
12357
|
if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
|
|
12490
12358
|
try {
|
|
12491
12359
|
const display = this.computeToolUseDisplay();
|
|
12492
|
-
log$
|
|
12360
|
+
log$16.info("updateToolUseStatus", {
|
|
12493
12361
|
hasDisplay: !!display,
|
|
12494
12362
|
stepCount: display?.stepCount,
|
|
12495
12363
|
steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
|
|
@@ -12511,7 +12379,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12511
12379
|
accountId: this.deps.accountId
|
|
12512
12380
|
});
|
|
12513
12381
|
} catch (err) {
|
|
12514
|
-
log$
|
|
12382
|
+
log$16.debug("updateToolUseStatus failed", { error: String(err) });
|
|
12515
12383
|
}
|
|
12516
12384
|
}
|
|
12517
12385
|
finalizeCard(source, reason) {
|
|
@@ -12522,7 +12390,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12522
12390
|
const from = this.phase;
|
|
12523
12391
|
this.phase = "completed";
|
|
12524
12392
|
this._terminalReason = "normal";
|
|
12525
|
-
log$
|
|
12393
|
+
log$16.info("phase transition", {
|
|
12526
12394
|
from,
|
|
12527
12395
|
to: this.phase,
|
|
12528
12396
|
source: "replaceWithClarificationCard",
|
|
@@ -12536,7 +12404,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12536
12404
|
async closeStreamingAndUpdate(cardId, card, label) {
|
|
12537
12405
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
12538
12406
|
this.cardKit.cardKitSequence += 1;
|
|
12539
|
-
log$
|
|
12407
|
+
log$16.info(`${label}: closing streaming mode`, {
|
|
12540
12408
|
seqBefore: seqBeforeClose,
|
|
12541
12409
|
seqAfter: this.cardKit.cardKitSequence
|
|
12542
12410
|
});
|
|
@@ -12549,7 +12417,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12549
12417
|
});
|
|
12550
12418
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
12551
12419
|
this.cardKit.cardKitSequence += 1;
|
|
12552
|
-
log$
|
|
12420
|
+
log$16.info(`${label}: updating card`, {
|
|
12553
12421
|
seqBefore: seqBeforeUpdate,
|
|
12554
12422
|
seqAfter: this.cardKit.cardKitSequence
|
|
12555
12423
|
});
|
|
@@ -12583,7 +12451,7 @@ function extractApiDetail(err) {
|
|
|
12583
12451
|
}
|
|
12584
12452
|
//#endregion
|
|
12585
12453
|
//#region src/messaging/format-for-cc.ts
|
|
12586
|
-
const log$
|
|
12454
|
+
const log$15 = larkLogger("messaging/format-for-cc");
|
|
12587
12455
|
function safeParseJSON(raw) {
|
|
12588
12456
|
try {
|
|
12589
12457
|
return JSON.parse(raw);
|
|
@@ -12639,7 +12507,7 @@ function extractPostText(rawContent) {
|
|
|
12639
12507
|
*/
|
|
12640
12508
|
function formatContentByType(ctx) {
|
|
12641
12509
|
const { contentType, content, resources } = ctx;
|
|
12642
|
-
log$
|
|
12510
|
+
log$15.debug("formatContentByType 开始格式化", {
|
|
12643
12511
|
contentType,
|
|
12644
12512
|
contentLength: content?.length ?? 0,
|
|
12645
12513
|
resourceCount: resources?.length ?? 0
|
|
@@ -12681,7 +12549,7 @@ function formatContentByType(ctx) {
|
|
|
12681
12549
|
case "todo":
|
|
12682
12550
|
case "vote": return content;
|
|
12683
12551
|
default:
|
|
12684
|
-
log$
|
|
12552
|
+
log$15.warn("遇到不支持的消息类型", { contentType });
|
|
12685
12553
|
return `[不支持的消息类型: ${contentType}]`;
|
|
12686
12554
|
}
|
|
12687
12555
|
}
|
|
@@ -12724,7 +12592,7 @@ function formatMessageTime(createTime) {
|
|
|
12724
12592
|
* @returns 格式化后的纯文本字符串
|
|
12725
12593
|
*/
|
|
12726
12594
|
function formatForCC(ctx, isGroup) {
|
|
12727
|
-
log$
|
|
12595
|
+
log$15.info("formatForCC 开始处理", {
|
|
12728
12596
|
messageId: ctx.messageId,
|
|
12729
12597
|
contentType: ctx.contentType,
|
|
12730
12598
|
chatType: ctx.chatType,
|
|
@@ -12740,7 +12608,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
12740
12608
|
if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
|
|
12741
12609
|
if (ctx.threadId) parts.push("[话题回复]");
|
|
12742
12610
|
const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
|
|
12743
|
-
log$
|
|
12611
|
+
log$15.info("formatForCC 完成", {
|
|
12744
12612
|
messageId: ctx.messageId,
|
|
12745
12613
|
resultLength: result.length,
|
|
12746
12614
|
hasTime: !!timeStr,
|
|
@@ -12748,6 +12616,41 @@ function formatForCC(ctx, isGroup) {
|
|
|
12748
12616
|
});
|
|
12749
12617
|
return result;
|
|
12750
12618
|
}
|
|
12619
|
+
function formatPromptMetadata(ctx) {
|
|
12620
|
+
const senderOpenId = ctx.rawSender.sender_id.open_id || ctx.senderId;
|
|
12621
|
+
const senderUserId = ctx.rawSender.sender_id.user_id;
|
|
12622
|
+
const fields = [
|
|
12623
|
+
`message_id=${ctx.messageId}`,
|
|
12624
|
+
`chat_id=${ctx.chatId}`,
|
|
12625
|
+
`chat_type=${ctx.chatType}`,
|
|
12626
|
+
`sender_open_id=${senderOpenId}`
|
|
12627
|
+
];
|
|
12628
|
+
if (senderUserId) fields.push(`sender_user_id=${senderUserId}`);
|
|
12629
|
+
if (ctx.senderName) fields.push(`sender_name=${ctx.senderName}`);
|
|
12630
|
+
if (ctx.parentId) fields.push(`reply_to_message_id=${ctx.parentId}`);
|
|
12631
|
+
if (ctx.rootId) fields.push(`root_message_id=${ctx.rootId}`);
|
|
12632
|
+
if (ctx.threadId) fields.push(`thread_id=${ctx.threadId}`);
|
|
12633
|
+
const mentionedUsers = ctx.mentions.filter((mention) => !mention.isBot && mention.openId).map((mention) => `${mention.name || mention.openId}(open_id=${mention.openId})`);
|
|
12634
|
+
if (mentionedUsers.length > 0) fields.push(`mentioned_users=${mentionedUsers.join(", ")}`);
|
|
12635
|
+
return `[消息元数据] ${fields.join("; ")}`;
|
|
12636
|
+
}
|
|
12637
|
+
/**
|
|
12638
|
+
* Build the full text prompt sent to the agent runtime.
|
|
12639
|
+
*
|
|
12640
|
+
* Quoted/replied-to content is intentionally injected outside the parser:
|
|
12641
|
+
* resolving it requires Feishu API calls and can fail independently from
|
|
12642
|
+
* parsing the current event.
|
|
12643
|
+
*/
|
|
12644
|
+
function formatPromptForCC(ctx, isGroup, quotedContent) {
|
|
12645
|
+
let currentMessage = formatForCC(ctx, isGroup);
|
|
12646
|
+
if (isGroup && ctx.threadId) currentMessage = `[群聊话题] ${currentMessage}`;
|
|
12647
|
+
else if (isGroup) currentMessage = `[群聊] ${currentMessage}`;
|
|
12648
|
+
else currentMessage = `[私聊] ${currentMessage}`;
|
|
12649
|
+
const quote = quotedContent?.trim();
|
|
12650
|
+
const metadata = formatPromptMetadata(ctx);
|
|
12651
|
+
if (!quote) return `${metadata}\n${currentMessage}`;
|
|
12652
|
+
return `${metadata}\n\n[引用/回复消息上下文]\n${quote}\n\n[当前消息]\n${currentMessage}`;
|
|
12653
|
+
}
|
|
12751
12654
|
//#endregion
|
|
12752
12655
|
//#region src/card/interactive-cards.ts
|
|
12753
12656
|
/**
|
|
@@ -12763,7 +12666,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
12763
12666
|
* - CC 输出文本后处理(检测特殊标记)
|
|
12764
12667
|
* - 操作 ID 生命周期管理
|
|
12765
12668
|
*/
|
|
12766
|
-
const log$
|
|
12669
|
+
const log$14 = larkLogger("card/interactive-cards");
|
|
12767
12670
|
const pendingOperations = /* @__PURE__ */ new Map();
|
|
12768
12671
|
const OPERATION_TTL_MS = 1800 * 1e3;
|
|
12769
12672
|
setInterval(() => {
|
|
@@ -12861,7 +12764,7 @@ function buildAuthCard(ctx) {
|
|
|
12861
12764
|
scopes: ctx.scopes
|
|
12862
12765
|
}
|
|
12863
12766
|
});
|
|
12864
|
-
log$
|
|
12767
|
+
log$14.info("已创建授权卡片操作", {
|
|
12865
12768
|
operationId,
|
|
12866
12769
|
scopes: ctx.scopes,
|
|
12867
12770
|
sessionId: ctx.sessionId
|
|
@@ -12899,7 +12802,7 @@ function buildAskCard(ctx, phaseIndex = 0) {
|
|
|
12899
12802
|
freeTextAnswers: {}
|
|
12900
12803
|
}
|
|
12901
12804
|
});
|
|
12902
|
-
log$
|
|
12805
|
+
log$14.info("已创建提问卡片操作", {
|
|
12903
12806
|
operationId,
|
|
12904
12807
|
phaseId: phase?.id,
|
|
12905
12808
|
phaseIndex,
|
|
@@ -13064,7 +12967,7 @@ function toggleAskSelection(operationId, phaseId, value) {
|
|
|
13064
12967
|
else next = current[0] === value ? [] : [value];
|
|
13065
12968
|
if (next.length > 0) op.askContext.answers[phaseId] = next;
|
|
13066
12969
|
else delete op.askContext.answers[phaseId];
|
|
13067
|
-
log$
|
|
12970
|
+
log$14.info("更新提问卡片勾选状态", {
|
|
13068
12971
|
operationId,
|
|
13069
12972
|
phaseId,
|
|
13070
12973
|
selectedValues: next
|
|
@@ -13080,7 +12983,7 @@ function setAskFreeTextAnswer(operationId, phaseId, value) {
|
|
|
13080
12983
|
const text = normalizeFreeText(value);
|
|
13081
12984
|
if (text) op.askContext.freeTextAnswers[phaseId] = text;
|
|
13082
12985
|
else delete op.askContext.freeTextAnswers[phaseId];
|
|
13083
|
-
log$
|
|
12986
|
+
log$14.info("更新提问卡片自由文本", {
|
|
13084
12987
|
operationId,
|
|
13085
12988
|
phaseId,
|
|
13086
12989
|
hasText: text.length > 0
|
|
@@ -13324,7 +13227,7 @@ async function trySendClarificationFallback(params) {
|
|
|
13324
13227
|
}
|
|
13325
13228
|
//#endregion
|
|
13326
13229
|
//#region src/messaging/inbound/dispatch-cc.ts
|
|
13327
|
-
const log$
|
|
13230
|
+
const log$13 = larkLogger("inbound/dispatch-cc");
|
|
13328
13231
|
function buildFeishuRuntimeConfig(params) {
|
|
13329
13232
|
const { ctx, account, route } = params;
|
|
13330
13233
|
const senderUserId = ctx.rawSender.sender_id.user_id;
|
|
@@ -13381,7 +13284,7 @@ async function dispatchToCC(params) {
|
|
|
13381
13284
|
const ccModel = params.model;
|
|
13382
13285
|
const maxTurns = params.maxTurns;
|
|
13383
13286
|
const maxBudgetUsd = params.maxBudgetUsd;
|
|
13384
|
-
log$
|
|
13287
|
+
log$13.info("dispatchToCC 开始", {
|
|
13385
13288
|
msgId,
|
|
13386
13289
|
chatId,
|
|
13387
13290
|
chatType,
|
|
@@ -13394,12 +13297,12 @@ async function dispatchToCC(params) {
|
|
|
13394
13297
|
threadId,
|
|
13395
13298
|
userId: ctx.senderId
|
|
13396
13299
|
});
|
|
13397
|
-
log$
|
|
13300
|
+
log$13.info("会话路由解析完成", {
|
|
13398
13301
|
sessionId: route.sessionId,
|
|
13399
13302
|
cwd: route.cwd,
|
|
13400
13303
|
type: route.type
|
|
13401
13304
|
});
|
|
13402
|
-
if (params.userContext) log$
|
|
13305
|
+
if (params.userContext) log$13.info("用户上下文已注入", {
|
|
13403
13306
|
userId: params.userContext.userId,
|
|
13404
13307
|
isTenantIdentity: params.userContext.isTenantIdentity,
|
|
13405
13308
|
credentialDir: params.userContext.credentialDir
|
|
@@ -13408,10 +13311,7 @@ async function dispatchToCC(params) {
|
|
|
13408
13311
|
const isGroup = chatType === "group";
|
|
13409
13312
|
const replyInThread = !!threadId;
|
|
13410
13313
|
const replyToMessageId = threadId ? void 0 : ctx.messageId;
|
|
13411
|
-
|
|
13412
|
-
if (isGroup && replyInThread) textPrompt = `[群聊话题] ${textPrompt}`;
|
|
13413
|
-
else if (isGroup) textPrompt = `[群聊] ${textPrompt}`;
|
|
13414
|
-
else textPrompt = `[私聊] ${textPrompt}`;
|
|
13314
|
+
const textPrompt = formatPromptForCC(ctx, isGroup, params.quotedContent);
|
|
13415
13315
|
const imageResources = ctx.resources.filter((r) => r.type === "image");
|
|
13416
13316
|
let prompt = textPrompt;
|
|
13417
13317
|
if (imageResources.length > 0) {
|
|
@@ -13438,7 +13338,7 @@ async function dispatchToCC(params) {
|
|
|
13438
13338
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
13439
13339
|
buffer = Buffer.concat(chunks);
|
|
13440
13340
|
} else {
|
|
13441
|
-
log$
|
|
13341
|
+
log$13.warn("图片资源下载返回未知格式,跳过", {
|
|
13442
13342
|
fileKey: imgRes.fileKey,
|
|
13443
13343
|
responseType: typeof response
|
|
13444
13344
|
});
|
|
@@ -13446,7 +13346,7 @@ async function dispatchToCC(params) {
|
|
|
13446
13346
|
}
|
|
13447
13347
|
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
13448
13348
|
} else {
|
|
13449
|
-
log$
|
|
13349
|
+
log$13.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
|
|
13450
13350
|
continue;
|
|
13451
13351
|
}
|
|
13452
13352
|
const imgFileName = `${imgRes.fileKey}.png`;
|
|
@@ -13456,13 +13356,13 @@ async function dispatchToCC(params) {
|
|
|
13456
13356
|
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
13457
13357
|
await mkdir(filesDir, { recursive: true });
|
|
13458
13358
|
await writeFile(imgFilePath, buffer);
|
|
13459
|
-
log$
|
|
13359
|
+
log$13.info("图片已保存到工作目录", {
|
|
13460
13360
|
fileKey: imgRes.fileKey,
|
|
13461
13361
|
path: imgFilePath,
|
|
13462
13362
|
sizeBytes: buffer.length
|
|
13463
13363
|
});
|
|
13464
13364
|
} catch (saveErr) {
|
|
13465
|
-
log$
|
|
13365
|
+
log$13.warn("图片保存到工作目录失败", {
|
|
13466
13366
|
fileKey: imgRes.fileKey,
|
|
13467
13367
|
path: imgFilePath,
|
|
13468
13368
|
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
@@ -13477,13 +13377,13 @@ async function dispatchToCC(params) {
|
|
|
13477
13377
|
data: base64Data
|
|
13478
13378
|
}
|
|
13479
13379
|
});
|
|
13480
|
-
log$
|
|
13380
|
+
log$13.info("图片资源下载成功", {
|
|
13481
13381
|
fileKey: imgRes.fileKey,
|
|
13482
13382
|
mediaType,
|
|
13483
13383
|
sizeBytes: buffer.length
|
|
13484
13384
|
});
|
|
13485
13385
|
} catch (err) {
|
|
13486
|
-
log$
|
|
13386
|
+
log$13.warn("图片资源下载失败,跳过", {
|
|
13487
13387
|
fileKey: imgRes.fileKey,
|
|
13488
13388
|
error: err instanceof Error ? err.message : String(err)
|
|
13489
13389
|
});
|
|
@@ -13493,7 +13393,7 @@ async function dispatchToCC(params) {
|
|
|
13493
13393
|
type: "text",
|
|
13494
13394
|
text: textPrompt
|
|
13495
13395
|
}, ...imageBlocks];
|
|
13496
|
-
log$
|
|
13396
|
+
log$13.info("已构建多模态 prompt", {
|
|
13497
13397
|
textLength: textPrompt.length,
|
|
13498
13398
|
imageCount: imageBlocks.length
|
|
13499
13399
|
});
|
|
@@ -13501,12 +13401,12 @@ async function dispatchToCC(params) {
|
|
|
13501
13401
|
const textWithoutImageRefs = textPrompt.replace(/!\[image\]\([^)]*\)/g, "").replace(/\[图片\]\s*/g, "").trim();
|
|
13502
13402
|
if (textWithoutImageRefs.length > 0) {
|
|
13503
13403
|
prompt = textPrompt.replace(/!\[image\]\([^)]*\)/g, "[图片加载失败]");
|
|
13504
|
-
log$
|
|
13404
|
+
log$13.info("图片下载失败但保留文字内容继续调度", {
|
|
13505
13405
|
messageId: ctx.messageId,
|
|
13506
13406
|
textLength: textWithoutImageRefs.length
|
|
13507
13407
|
});
|
|
13508
13408
|
} else {
|
|
13509
|
-
log$
|
|
13409
|
+
log$13.warn("纯图片消息且图片全部下载失败,中止调度", {
|
|
13510
13410
|
messageId: ctx.messageId,
|
|
13511
13411
|
expectedImages: imageResources.length
|
|
13512
13412
|
});
|
|
@@ -13528,7 +13428,7 @@ async function dispatchToCC(params) {
|
|
|
13528
13428
|
await fsMkdir(filesDir, { recursive: true });
|
|
13529
13429
|
let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
|
|
13530
13430
|
for (const res of attachmentResources) try {
|
|
13531
|
-
log$
|
|
13431
|
+
log$13.info("开始下载非图片附件", {
|
|
13532
13432
|
type: res.type,
|
|
13533
13433
|
fileKey: res.fileKey,
|
|
13534
13434
|
fileName: res.fileName,
|
|
@@ -13542,7 +13442,7 @@ async function dispatchToCC(params) {
|
|
|
13542
13442
|
},
|
|
13543
13443
|
params: { type: "file" }
|
|
13544
13444
|
});
|
|
13545
|
-
log$
|
|
13445
|
+
log$13.info("非图片附件 API 响应已收到", {
|
|
13546
13446
|
type: res.type,
|
|
13547
13447
|
fileKey: res.fileKey,
|
|
13548
13448
|
responseType: typeof response,
|
|
@@ -13561,7 +13461,7 @@ async function dispatchToCC(params) {
|
|
|
13561
13461
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
13562
13462
|
buffer = Buffer.concat(chunks);
|
|
13563
13463
|
} else {
|
|
13564
|
-
log$
|
|
13464
|
+
log$13.warn("非图片附件下载返回未知格式,跳过", {
|
|
13565
13465
|
type: res.type,
|
|
13566
13466
|
fileKey: res.fileKey,
|
|
13567
13467
|
responseType: typeof response
|
|
@@ -13569,7 +13469,7 @@ async function dispatchToCC(params) {
|
|
|
13569
13469
|
continue;
|
|
13570
13470
|
}
|
|
13571
13471
|
} else {
|
|
13572
|
-
log$
|
|
13472
|
+
log$13.warn("非图片附件下载返回空,跳过", {
|
|
13573
13473
|
type: res.type,
|
|
13574
13474
|
fileKey: res.fileKey
|
|
13575
13475
|
});
|
|
@@ -13592,7 +13492,7 @@ async function dispatchToCC(params) {
|
|
|
13592
13492
|
}
|
|
13593
13493
|
const filePath = path.join(filesDir, savedFileName);
|
|
13594
13494
|
await fsWriteFile(filePath, buffer);
|
|
13595
|
-
log$
|
|
13495
|
+
log$13.info("非图片附件已保存到工作目录", {
|
|
13596
13496
|
type: res.type,
|
|
13597
13497
|
fileKey: res.fileKey,
|
|
13598
13498
|
savedFileName,
|
|
@@ -13626,7 +13526,7 @@ async function dispatchToCC(params) {
|
|
|
13626
13526
|
}
|
|
13627
13527
|
}
|
|
13628
13528
|
} catch (err) {
|
|
13629
|
-
log$
|
|
13529
|
+
log$13.warn("非图片附件下载失败,替换为降级描述", {
|
|
13630
13530
|
type: res.type,
|
|
13631
13531
|
fileKey: res.fileKey,
|
|
13632
13532
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -13645,12 +13545,12 @@ async function dispatchToCC(params) {
|
|
|
13645
13545
|
const textBlock = prompt.find((b) => b.type === "text");
|
|
13646
13546
|
if (textBlock) textBlock.text = updatedTextPrompt;
|
|
13647
13547
|
}
|
|
13648
|
-
log$
|
|
13548
|
+
log$13.info("非图片附件处理完成", {
|
|
13649
13549
|
totalAttachments: attachmentResources.length,
|
|
13650
13550
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
|
|
13651
13551
|
});
|
|
13652
13552
|
}
|
|
13653
|
-
log$
|
|
13553
|
+
log$13.info("消息格式化完成", {
|
|
13654
13554
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
|
|
13655
13555
|
isMultimodal: Array.isArray(prompt),
|
|
13656
13556
|
isGroup
|
|
@@ -13682,19 +13582,19 @@ async function dispatchToCC(params) {
|
|
|
13682
13582
|
};
|
|
13683
13583
|
const cardController = new StreamingCardController(cardDeps);
|
|
13684
13584
|
registerActiveCard(route.sessionId, cardController);
|
|
13685
|
-
log$
|
|
13585
|
+
log$13.info("StreamingCardController 已创建", {
|
|
13686
13586
|
sessionId: route.sessionId,
|
|
13687
13587
|
chatId,
|
|
13688
13588
|
replyToMessageId: cardDeps.replyToMessageId
|
|
13689
13589
|
});
|
|
13690
13590
|
cardController.ensureCardCreated().catch((err) => {
|
|
13691
|
-
log$
|
|
13591
|
+
log$13.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
|
|
13692
13592
|
});
|
|
13693
13593
|
let promptForRuntime = prompt;
|
|
13694
13594
|
try {
|
|
13695
13595
|
promptForRuntime = await new MemoryContextBuilder().injectPrompt(route.cwd, prompt, promptToText(prompt));
|
|
13696
13596
|
} catch (err) {
|
|
13697
|
-
log$
|
|
13597
|
+
log$13.warn("memory context 注入失败,继续使用原始 prompt", {
|
|
13698
13598
|
sessionId: route.sessionId,
|
|
13699
13599
|
error: err instanceof Error ? err.message : String(err)
|
|
13700
13600
|
});
|
|
@@ -13715,7 +13615,7 @@ async function dispatchToCC(params) {
|
|
|
13715
13615
|
});
|
|
13716
13616
|
} catch (err) {
|
|
13717
13617
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
13718
|
-
log$
|
|
13618
|
+
log$13.error("飞书事件 run context 构造失败,终止调度", {
|
|
13719
13619
|
sessionId: route.sessionId,
|
|
13720
13620
|
messageId: msgId,
|
|
13721
13621
|
error: errorMessage
|
|
@@ -13733,7 +13633,7 @@ async function dispatchToCC(params) {
|
|
|
13733
13633
|
channel: "feishu-bot",
|
|
13734
13634
|
title: userContent.slice(0, 30) + (userContent.length > 30 ? "..." : "")
|
|
13735
13635
|
}).catch((err) => {
|
|
13736
|
-
log$
|
|
13636
|
+
log$13.warn("创建飞书端会话记录失败", { error: String(err) });
|
|
13737
13637
|
});
|
|
13738
13638
|
params.messageStore.appendMessage({
|
|
13739
13639
|
sessionId: storeSessionId,
|
|
@@ -13747,14 +13647,14 @@ async function dispatchToCC(params) {
|
|
|
13747
13647
|
scenarioId: runtimeConfig.runContext?.scenarioId
|
|
13748
13648
|
}
|
|
13749
13649
|
}).catch((err) => {
|
|
13750
|
-
log$
|
|
13650
|
+
log$13.warn("记录飞书用户消息到 store 失败", { error: String(err) });
|
|
13751
13651
|
});
|
|
13752
13652
|
}
|
|
13753
13653
|
const bridge = new CCStreamBridge(cardController, {
|
|
13754
13654
|
autoCompleteOnTurnEnd: true,
|
|
13755
13655
|
sessionKey: route.sessionId
|
|
13756
13656
|
});
|
|
13757
|
-
log$
|
|
13657
|
+
log$13.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
|
|
13758
13658
|
let feishuAccumText = "";
|
|
13759
13659
|
let usedAskUserTool = false;
|
|
13760
13660
|
let pendingEndTurn = null;
|
|
@@ -13776,12 +13676,12 @@ async function dispatchToCC(params) {
|
|
|
13776
13676
|
...metadata
|
|
13777
13677
|
}
|
|
13778
13678
|
}).catch((err) => {
|
|
13779
|
-
log$
|
|
13679
|
+
log$13.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
|
|
13780
13680
|
});
|
|
13781
13681
|
};
|
|
13782
13682
|
const appendRuntimeEventMessage = (event) => {
|
|
13783
13683
|
const eventSummary = summarizeForAudit(event);
|
|
13784
|
-
log$
|
|
13684
|
+
log$13.info("CC onRuntimeEvent", {
|
|
13785
13685
|
sessionId: route.sessionId,
|
|
13786
13686
|
type: event.type,
|
|
13787
13687
|
traceId: runtimeConfig.runContext?.traceId,
|
|
@@ -13819,11 +13719,11 @@ async function dispatchToCC(params) {
|
|
|
13819
13719
|
finalStatus: event.finalStatus
|
|
13820
13720
|
}
|
|
13821
13721
|
}).catch((err) => {
|
|
13822
|
-
log$
|
|
13722
|
+
log$13.warn("记录飞书 runtime event 到 store 失败", { error: String(err) });
|
|
13823
13723
|
});
|
|
13824
13724
|
};
|
|
13825
13725
|
const handleResult = async (result) => {
|
|
13826
|
-
log$
|
|
13726
|
+
log$13.info("CC onResult", {
|
|
13827
13727
|
sessionId: route.sessionId,
|
|
13828
13728
|
subtype: result.subtype,
|
|
13829
13729
|
isError: result.isError,
|
|
@@ -13851,13 +13751,13 @@ async function dispatchToCC(params) {
|
|
|
13851
13751
|
})
|
|
13852
13752
|
});
|
|
13853
13753
|
} catch (err) {
|
|
13854
|
-
log$
|
|
13754
|
+
log$13.warn("clarification gate: 兜底处理失败,回退到普通回复", {
|
|
13855
13755
|
sessionId: route.sessionId,
|
|
13856
13756
|
error: err instanceof Error ? err.message : String(err)
|
|
13857
13757
|
});
|
|
13858
13758
|
}
|
|
13859
13759
|
if (clarificationFallback.handled) {
|
|
13860
|
-
log$
|
|
13760
|
+
log$13.info("clarification gate: 普通文本提问已转为提问卡片", {
|
|
13861
13761
|
sessionId: route.sessionId,
|
|
13862
13762
|
operationId: clarificationFallback.operationId,
|
|
13863
13763
|
reason: clarificationFallback.reason,
|
|
@@ -13865,7 +13765,7 @@ async function dispatchToCC(params) {
|
|
|
13865
13765
|
});
|
|
13866
13766
|
return;
|
|
13867
13767
|
}
|
|
13868
|
-
if (clarificationFallback.matched) log$
|
|
13768
|
+
if (clarificationFallback.matched) log$13.warn("clarification gate: 提问卡片替换失败,回退到普通回复", {
|
|
13869
13769
|
sessionId: route.sessionId,
|
|
13870
13770
|
operationId: clarificationFallback.operationId
|
|
13871
13771
|
});
|
|
@@ -13878,7 +13778,7 @@ async function dispatchToCC(params) {
|
|
|
13878
13778
|
};
|
|
13879
13779
|
const handleBlockedEvent = (event) => {
|
|
13880
13780
|
const sanitized = sanitizeBlockedEvent(event);
|
|
13881
|
-
log$
|
|
13781
|
+
log$13.warn("CC onBlockedEvent", {
|
|
13882
13782
|
...sanitized,
|
|
13883
13783
|
traceId: runtimeConfig.runContext?.traceId,
|
|
13884
13784
|
scenarioId: runtimeConfig.runContext?.scenarioId
|
|
@@ -13886,7 +13786,7 @@ async function dispatchToCC(params) {
|
|
|
13886
13786
|
};
|
|
13887
13787
|
const callbacks = {
|
|
13888
13788
|
onTextDelta: (text) => {
|
|
13889
|
-
log$
|
|
13789
|
+
log$13.debug("CC onTextDelta", {
|
|
13890
13790
|
sessionId: route.sessionId,
|
|
13891
13791
|
deltaLen: text.length
|
|
13892
13792
|
});
|
|
@@ -13894,14 +13794,14 @@ async function dispatchToCC(params) {
|
|
|
13894
13794
|
feishuAccumText += text;
|
|
13895
13795
|
},
|
|
13896
13796
|
onThinkingDelta: (text) => {
|
|
13897
|
-
log$
|
|
13797
|
+
log$13.debug("CC onThinkingDelta", {
|
|
13898
13798
|
sessionId: route.sessionId,
|
|
13899
13799
|
deltaLen: text.length
|
|
13900
13800
|
});
|
|
13901
13801
|
bridge.onThinkingDelta(text);
|
|
13902
13802
|
},
|
|
13903
13803
|
onToolUseStart: (toolName, toolInput) => {
|
|
13904
|
-
log$
|
|
13804
|
+
log$13.info("CC onToolUseStart", {
|
|
13905
13805
|
sessionId: route.sessionId,
|
|
13906
13806
|
toolName
|
|
13907
13807
|
});
|
|
@@ -13909,7 +13809,7 @@ async function dispatchToCC(params) {
|
|
|
13909
13809
|
bridge.onToolUseStart(toolName, toolInput);
|
|
13910
13810
|
},
|
|
13911
13811
|
onToolResult: (toolUseId, result) => {
|
|
13912
|
-
log$
|
|
13812
|
+
log$13.info("CC onToolResult", {
|
|
13913
13813
|
sessionId: route.sessionId,
|
|
13914
13814
|
toolUseId,
|
|
13915
13815
|
traceId: runtimeConfig.runContext?.traceId,
|
|
@@ -13918,7 +13818,7 @@ async function dispatchToCC(params) {
|
|
|
13918
13818
|
bridge.onToolResult(toolUseId);
|
|
13919
13819
|
},
|
|
13920
13820
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
13921
|
-
log$
|
|
13821
|
+
log$13.debug("CC onToolProgress", {
|
|
13922
13822
|
sessionId: route.sessionId,
|
|
13923
13823
|
toolName,
|
|
13924
13824
|
elapsedSeconds
|
|
@@ -13926,7 +13826,7 @@ async function dispatchToCC(params) {
|
|
|
13926
13826
|
bridge.onToolProgress(toolName, elapsedSeconds);
|
|
13927
13827
|
},
|
|
13928
13828
|
onTurnEnd: (stopReason) => {
|
|
13929
|
-
log$
|
|
13829
|
+
log$13.info("CC onTurnEnd", {
|
|
13930
13830
|
sessionId: route.sessionId,
|
|
13931
13831
|
stopReason
|
|
13932
13832
|
});
|
|
@@ -13938,14 +13838,14 @@ async function dispatchToCC(params) {
|
|
|
13938
13838
|
},
|
|
13939
13839
|
onResult: (result) => {
|
|
13940
13840
|
resultHandlingPromise = handleResult(result).catch((err) => {
|
|
13941
|
-
log$
|
|
13841
|
+
log$13.error("CC onResult 收尾处理失败", {
|
|
13942
13842
|
sessionId: route.sessionId,
|
|
13943
13843
|
error: err instanceof Error ? err.message : String(err)
|
|
13944
13844
|
});
|
|
13945
13845
|
});
|
|
13946
13846
|
},
|
|
13947
13847
|
onError: (error) => {
|
|
13948
|
-
log$
|
|
13848
|
+
log$13.error("CC 进程错误", {
|
|
13949
13849
|
sessionId: route.sessionId,
|
|
13950
13850
|
traceId: runtimeConfig.runContext?.traceId,
|
|
13951
13851
|
error: error.message
|
|
@@ -13953,7 +13853,7 @@ async function dispatchToCC(params) {
|
|
|
13953
13853
|
cardController.onError(error, { kind: "cc-process" });
|
|
13954
13854
|
},
|
|
13955
13855
|
onAuditEvent: (event) => {
|
|
13956
|
-
log$
|
|
13856
|
+
log$13.info("CC onAuditEvent", {
|
|
13957
13857
|
...event,
|
|
13958
13858
|
inputSummary: summarizeForAudit(event.inputSummary),
|
|
13959
13859
|
resultSummary: summarizeForAudit(event.resultSummary)
|
|
@@ -13964,7 +13864,7 @@ async function dispatchToCC(params) {
|
|
|
13964
13864
|
onBlocked: handleBlockedEvent,
|
|
13965
13865
|
onBlockedEvent: handleBlockedEvent
|
|
13966
13866
|
};
|
|
13967
|
-
log$
|
|
13867
|
+
log$13.info("开始执行 CC 进程", {
|
|
13968
13868
|
sessionId: route.sessionId,
|
|
13969
13869
|
cwd: route.cwd,
|
|
13970
13870
|
promptLength: prompt.length,
|
|
@@ -13987,11 +13887,11 @@ async function dispatchToCC(params) {
|
|
|
13987
13887
|
onMcpCallback.on(`callback:${route.sessionId}`, mcpCallbackListener);
|
|
13988
13888
|
try {
|
|
13989
13889
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
13990
|
-
log$
|
|
13890
|
+
log$13.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
|
|
13991
13891
|
if (resultHandlingPromise) await resultHandlingPromise;
|
|
13992
13892
|
} catch (err) {
|
|
13993
13893
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
13994
|
-
log$
|
|
13894
|
+
log$13.error("CC 进程 executePrompt 调用异常", {
|
|
13995
13895
|
sessionId: route.sessionId,
|
|
13996
13896
|
error: errorMessage
|
|
13997
13897
|
});
|
|
@@ -14015,7 +13915,7 @@ async function dispatchToCC(params) {
|
|
|
14015
13915
|
*/
|
|
14016
13916
|
async function dispatchTeammateEval(params) {
|
|
14017
13917
|
const { chatId, prompt, account, sessionRouter, processManager } = params;
|
|
14018
|
-
log$
|
|
13918
|
+
log$13.info("dispatchTeammateEval 开始", {
|
|
14019
13919
|
chatId,
|
|
14020
13920
|
promptLength: prompt.length
|
|
14021
13921
|
});
|
|
@@ -14028,7 +13928,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14028
13928
|
userName: void 0
|
|
14029
13929
|
});
|
|
14030
13930
|
if (processManager.isSessionBusy?.(route.sessionId)) {
|
|
14031
|
-
log$
|
|
13931
|
+
log$13.info("dispatchTeammateEval 跳过:主 session 正在执行", {
|
|
14032
13932
|
chatId,
|
|
14033
13933
|
sessionId: route.sessionId
|
|
14034
13934
|
});
|
|
@@ -14091,7 +13991,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14091
13991
|
}
|
|
14092
13992
|
});
|
|
14093
13993
|
} catch (err) {
|
|
14094
|
-
log$
|
|
13994
|
+
log$13.error("teammate run context 构造失败,跳过评估", {
|
|
14095
13995
|
chatId,
|
|
14096
13996
|
error: err instanceof Error ? err.message : String(err)
|
|
14097
13997
|
});
|
|
@@ -14115,7 +14015,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14115
14015
|
sourceRefs: params.sourceRefs ?? (params.requestId ? [params.requestId] : [])
|
|
14116
14016
|
});
|
|
14117
14017
|
if (evalDecision.decision === "NO_REPLY") {
|
|
14118
|
-
log$
|
|
14018
|
+
log$13.info("teammate evaluator 决定静默", {
|
|
14119
14019
|
chatId,
|
|
14120
14020
|
evalSessionId,
|
|
14121
14021
|
summary: evalDecision.summary.slice(0, 120)
|
|
@@ -14149,7 +14049,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14149
14049
|
const confirmReply = () => {
|
|
14150
14050
|
if (confirmed) return;
|
|
14151
14051
|
confirmed = true;
|
|
14152
|
-
log$
|
|
14052
|
+
log$13.info("teammate 确认回复,创建流式卡片", {
|
|
14153
14053
|
chatId,
|
|
14154
14054
|
thinkingLen: thinkingBuffer.length,
|
|
14155
14055
|
textLen: fullTextBuffer.length
|
|
@@ -14206,7 +14106,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14206
14106
|
fullTextBuffer += text;
|
|
14207
14107
|
if (fullTextBuffer.trimStart().startsWith(NO_REPLY_TOKEN)) {
|
|
14208
14108
|
silenced = true;
|
|
14209
|
-
log$
|
|
14109
|
+
log$13.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
|
|
14210
14110
|
chatId,
|
|
14211
14111
|
bufferLen: fullTextBuffer.length
|
|
14212
14112
|
});
|
|
@@ -14239,7 +14139,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14239
14139
|
toolName: currentToolName,
|
|
14240
14140
|
toolCallId: toolUseId
|
|
14241
14141
|
});
|
|
14242
|
-
log$
|
|
14142
|
+
log$13.info("teammate onToolResult", {
|
|
14243
14143
|
chatId,
|
|
14244
14144
|
toolUseId,
|
|
14245
14145
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14257,7 +14157,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14257
14157
|
onTurnEnd: (stopReason) => {
|
|
14258
14158
|
finalStopReason = stopReason;
|
|
14259
14159
|
if (stopReason === "tool_use") {
|
|
14260
|
-
log$
|
|
14160
|
+
log$13.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
|
|
14261
14161
|
if (confirmed && bridge) bridge.onTurnEnd(stopReason);
|
|
14262
14162
|
return;
|
|
14263
14163
|
}
|
|
@@ -14265,7 +14165,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14265
14165
|
const trimmed = fullTextBuffer.trim();
|
|
14266
14166
|
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
|
|
14267
14167
|
silenced = true;
|
|
14268
|
-
log$
|
|
14168
|
+
log$13.info("teammate turnEnd 最终判断: 静默", {
|
|
14269
14169
|
chatId,
|
|
14270
14170
|
trimmed: trimmed.slice(0, 60)
|
|
14271
14171
|
});
|
|
@@ -14280,7 +14180,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14280
14180
|
resolve();
|
|
14281
14181
|
},
|
|
14282
14182
|
onError: (error) => {
|
|
14283
|
-
log$
|
|
14183
|
+
log$13.error("teammate 评估 CC 进程错误", {
|
|
14284
14184
|
chatId,
|
|
14285
14185
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
14286
14186
|
error: error.message
|
|
@@ -14288,14 +14188,14 @@ async function dispatchTeammateEval(params) {
|
|
|
14288
14188
|
resolve();
|
|
14289
14189
|
},
|
|
14290
14190
|
onAuditEvent: (event) => {
|
|
14291
|
-
log$
|
|
14191
|
+
log$13.info("teammate onAuditEvent", {
|
|
14292
14192
|
...event,
|
|
14293
14193
|
inputSummary: summarizeForAudit(event.inputSummary),
|
|
14294
14194
|
resultSummary: summarizeForAudit(event.resultSummary)
|
|
14295
14195
|
});
|
|
14296
14196
|
},
|
|
14297
14197
|
onRuntimeEvent: (event) => {
|
|
14298
|
-
log$
|
|
14198
|
+
log$13.info("teammate onRuntimeEvent", {
|
|
14299
14199
|
type: event.type,
|
|
14300
14200
|
chatId,
|
|
14301
14201
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14306,7 +14206,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14306
14206
|
});
|
|
14307
14207
|
},
|
|
14308
14208
|
onScenarioEvent: (event) => {
|
|
14309
|
-
log$
|
|
14209
|
+
log$13.info("teammate onScenarioEvent", {
|
|
14310
14210
|
type: event.type,
|
|
14311
14211
|
chatId,
|
|
14312
14212
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14318,28 +14218,28 @@ async function dispatchTeammateEval(params) {
|
|
|
14318
14218
|
},
|
|
14319
14219
|
onBlocked: (event) => {
|
|
14320
14220
|
const sanitized = sanitizeBlockedEvent(event);
|
|
14321
|
-
log$
|
|
14221
|
+
log$13.warn("teammate onBlocked", {
|
|
14322
14222
|
...sanitized,
|
|
14323
14223
|
traceId: mainRuntimeConfig.runContext?.traceId
|
|
14324
14224
|
});
|
|
14325
14225
|
},
|
|
14326
14226
|
onBlockedEvent: (event) => {
|
|
14327
14227
|
const sanitized = sanitizeBlockedEvent(event);
|
|
14328
|
-
log$
|
|
14228
|
+
log$13.warn("teammate onBlockedEvent", {
|
|
14329
14229
|
...sanitized,
|
|
14330
14230
|
traceId: mainRuntimeConfig.runContext?.traceId
|
|
14331
14231
|
});
|
|
14332
14232
|
}
|
|
14333
14233
|
};
|
|
14334
14234
|
processManager.executePrompt(mainRuntimeConfig, callbacks).catch((err) => {
|
|
14335
|
-
log$
|
|
14235
|
+
log$13.error("teammate executePrompt 异常", {
|
|
14336
14236
|
chatId,
|
|
14337
14237
|
error: err instanceof Error ? err.message : String(err)
|
|
14338
14238
|
});
|
|
14339
14239
|
resolve();
|
|
14340
14240
|
});
|
|
14341
14241
|
});
|
|
14342
|
-
log$
|
|
14242
|
+
log$13.info("dispatchTeammateEval 完成", {
|
|
14343
14243
|
chatId,
|
|
14344
14244
|
confirmed,
|
|
14345
14245
|
silenced,
|
|
@@ -14350,7 +14250,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14350
14250
|
return { replied: confirmed };
|
|
14351
14251
|
} catch (err) {
|
|
14352
14252
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
14353
|
-
log$
|
|
14253
|
+
log$13.error("dispatchTeammateEval 异常", {
|
|
14354
14254
|
chatId,
|
|
14355
14255
|
error: errorMessage
|
|
14356
14256
|
});
|
|
@@ -14374,7 +14274,7 @@ async function runTeammateDecisionEval(params) {
|
|
|
14374
14274
|
resolve();
|
|
14375
14275
|
},
|
|
14376
14276
|
onError: (error) => {
|
|
14377
|
-
log$
|
|
14277
|
+
log$13.error("teammate evaluator 执行错误", {
|
|
14378
14278
|
chatId,
|
|
14379
14279
|
sessionId: runtimeConfig.sessionId,
|
|
14380
14280
|
error: error.message
|
|
@@ -14382,21 +14282,21 @@ async function runTeammateDecisionEval(params) {
|
|
|
14382
14282
|
resolve();
|
|
14383
14283
|
},
|
|
14384
14284
|
onToolUseStart: (toolName, toolInput) => {
|
|
14385
|
-
log$
|
|
14285
|
+
log$13.info("teammate evaluator toolUseStart", {
|
|
14386
14286
|
chatId,
|
|
14387
14287
|
toolName,
|
|
14388
14288
|
toolInput: summarizeForAudit(toolInput)
|
|
14389
14289
|
});
|
|
14390
14290
|
},
|
|
14391
14291
|
onToolResult: (toolUseId, result) => {
|
|
14392
|
-
log$
|
|
14292
|
+
log$13.info("teammate evaluator toolResult", {
|
|
14393
14293
|
chatId,
|
|
14394
14294
|
toolUseId,
|
|
14395
14295
|
result: summarizeForAudit(result)
|
|
14396
14296
|
});
|
|
14397
14297
|
}
|
|
14398
14298
|
}).catch((err) => {
|
|
14399
|
-
log$
|
|
14299
|
+
log$13.error("teammate evaluator executePrompt 异常", {
|
|
14400
14300
|
chatId,
|
|
14401
14301
|
sessionId: runtimeConfig.sessionId,
|
|
14402
14302
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -14411,7 +14311,7 @@ async function runTeammateDecisionEval(params) {
|
|
|
14411
14311
|
const decision = trimmed !== "" && trimmed !== placeholderText && firstToken === "HANDOFF_TO_MAIN" ? "HANDOFF_TO_MAIN" : "NO_REPLY";
|
|
14412
14312
|
const cleanedSummary = trimmed.replace(/^(HANDOFF_TO_MAIN|NO_REPLY)\b[\s::-]*/i, "").trim();
|
|
14413
14313
|
const summary = malformed ? "NO_REPLY (malformed evaluator output)" : cleanedSummary.slice(0, 600) || `${decision} (thinking ${thinkingLength} chars)`;
|
|
14414
|
-
log$
|
|
14314
|
+
log$13.info("teammate evaluator 完成", {
|
|
14415
14315
|
chatId,
|
|
14416
14316
|
sessionId: runtimeConfig.sessionId,
|
|
14417
14317
|
decision,
|
|
@@ -14469,7 +14369,7 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14469
14369
|
try {
|
|
14470
14370
|
if (req.tool === "ask_user") {
|
|
14471
14371
|
const params = req.params;
|
|
14472
|
-
log$
|
|
14372
|
+
log$13.info("[MCP] 收到 ask_user 回调,准备发送提问卡片", {
|
|
14473
14373
|
requestId: req.requestId,
|
|
14474
14374
|
sessionId: req.sessionId,
|
|
14475
14375
|
question: params.question?.slice(0, 50)
|
|
@@ -14482,14 +14382,14 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14482
14382
|
});
|
|
14483
14383
|
mcpOperationMap.set(operationId, req.requestId);
|
|
14484
14384
|
await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
|
|
14485
|
-
log$
|
|
14385
|
+
log$13.info("[MCP] 提问卡片已发送", {
|
|
14486
14386
|
requestId: req.requestId,
|
|
14487
14387
|
operationId,
|
|
14488
14388
|
chatId
|
|
14489
14389
|
});
|
|
14490
14390
|
} else if (req.tool === "request_permission") {
|
|
14491
14391
|
const params = req.params;
|
|
14492
|
-
log$
|
|
14392
|
+
log$13.info("[MCP] 收到 request_permission 回调,准备发送授权卡片", {
|
|
14493
14393
|
requestId: req.requestId,
|
|
14494
14394
|
scopes: params.scopes
|
|
14495
14395
|
});
|
|
@@ -14503,14 +14403,14 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14503
14403
|
});
|
|
14504
14404
|
mcpOperationMap.set(operationId, req.requestId);
|
|
14505
14405
|
await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
|
|
14506
|
-
log$
|
|
14406
|
+
log$13.info("[MCP] 授权卡片已发送", {
|
|
14507
14407
|
requestId: req.requestId,
|
|
14508
14408
|
operationId,
|
|
14509
14409
|
chatId
|
|
14510
14410
|
});
|
|
14511
14411
|
}
|
|
14512
14412
|
} catch (err) {
|
|
14513
|
-
log$
|
|
14413
|
+
log$13.error("[MCP] 交互卡片发送失败", {
|
|
14514
14414
|
requestId: req.requestId,
|
|
14515
14415
|
tool: req.tool,
|
|
14516
14416
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -14535,7 +14435,7 @@ async function sendInteractiveCardViaCardKit(card, chatId, account, replyInThrea
|
|
|
14535
14435
|
} });
|
|
14536
14436
|
const cardId = createResp?.data?.card_id;
|
|
14537
14437
|
if (!cardId) throw new Error(`CardKit card.create 失败: code=${createResp?.code}, msg=${createResp?.msg}`);
|
|
14538
|
-
log$
|
|
14438
|
+
log$13.info("[MCP] CardKit 卡片实体已创建", { cardId });
|
|
14539
14439
|
const contentPayload = JSON.stringify({
|
|
14540
14440
|
type: "card",
|
|
14541
14441
|
data: { card_id: cardId }
|
|
@@ -16065,7 +15965,7 @@ const convertLocation = (raw) => {
|
|
|
16065
15965
|
* injected via callbacks in `ConvertContext`. Callers are responsible
|
|
16066
15966
|
* for creating the appropriate callbacks (UAT / TAT / event push).
|
|
16067
15967
|
*/
|
|
16068
|
-
const log$
|
|
15968
|
+
const log$12 = larkLogger("converters/merge-forward");
|
|
16069
15969
|
/**
|
|
16070
15970
|
* Recursively expand a merge_forward message.
|
|
16071
15971
|
*
|
|
@@ -16080,7 +15980,7 @@ const log$11 = larkLogger("converters/merge-forward");
|
|
|
16080
15980
|
const convertMergeForward = async (_raw, ctx) => {
|
|
16081
15981
|
const { accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent } = ctx;
|
|
16082
15982
|
if (!fetchSubMessages) {
|
|
16083
|
-
log$
|
|
15983
|
+
log$12.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
|
|
16084
15984
|
messageId,
|
|
16085
15985
|
accountId
|
|
16086
15986
|
});
|
|
@@ -16098,23 +15998,23 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
16098
15998
|
let items;
|
|
16099
15999
|
try {
|
|
16100
16000
|
items = await fetchSubMessages(messageId);
|
|
16101
|
-
log$
|
|
16001
|
+
log$12.info("fetchSubMessages 成功", {
|
|
16102
16002
|
messageId,
|
|
16103
16003
|
itemCount: items.length
|
|
16104
16004
|
});
|
|
16105
16005
|
} catch (error) {
|
|
16106
|
-
log$
|
|
16006
|
+
log$12.error("fetch sub-messages failed", {
|
|
16107
16007
|
messageId,
|
|
16108
16008
|
error: error instanceof Error ? error.message : String(error)
|
|
16109
16009
|
});
|
|
16110
16010
|
return "<forwarded_messages/>";
|
|
16111
16011
|
}
|
|
16112
16012
|
if (items.length === 0) {
|
|
16113
|
-
log$
|
|
16013
|
+
log$12.warn("fetchSubMessages 返回空 items", { messageId });
|
|
16114
16014
|
return "<forwarded_messages/>";
|
|
16115
16015
|
}
|
|
16116
16016
|
const childrenMap = buildChildrenMap(items, messageId);
|
|
16117
|
-
log$
|
|
16017
|
+
log$12.info("buildChildrenMap 完成", {
|
|
16118
16018
|
messageId,
|
|
16119
16019
|
parentKeys: [...childrenMap.keys()],
|
|
16120
16020
|
childCounts: [...childrenMap.entries()].map(([k, v]) => `${k}:${v.length}`)
|
|
@@ -16123,7 +16023,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
16123
16023
|
if (senderIds.length > 0 && batchResolveNames) try {
|
|
16124
16024
|
await batchResolveNames(senderIds);
|
|
16125
16025
|
} catch (err) {
|
|
16126
|
-
log$
|
|
16026
|
+
log$12.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
|
|
16127
16027
|
}
|
|
16128
16028
|
return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
|
|
16129
16029
|
}
|
|
@@ -16203,7 +16103,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
|
|
|
16203
16103
|
const indented = indentLines(content, " ");
|
|
16204
16104
|
parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
|
|
16205
16105
|
} catch (err) {
|
|
16206
|
-
log$
|
|
16106
|
+
log$12.warn("failed to convert sub-message", {
|
|
16207
16107
|
messageId: item.message_id,
|
|
16208
16108
|
msgType: item.msg_type ?? "unknown",
|
|
16209
16109
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -16417,7 +16317,7 @@ async function convertMessageContent(raw, messageType, ctx) {
|
|
|
16417
16317
|
}
|
|
16418
16318
|
//#endregion
|
|
16419
16319
|
//#region src/messaging/inbound/parse-io.ts
|
|
16420
|
-
const log$
|
|
16320
|
+
const log$11 = larkLogger("inbound/parse-io");
|
|
16421
16321
|
/**
|
|
16422
16322
|
* 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
|
|
16423
16323
|
* 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
|
|
@@ -16437,7 +16337,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
16437
16337
|
}
|
|
16438
16338
|
}))?.data?.items?.[0]?.body?.content ?? void 0;
|
|
16439
16339
|
} catch (err) {
|
|
16440
|
-
log$
|
|
16340
|
+
log$11.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16441
16341
|
return;
|
|
16442
16342
|
}
|
|
16443
16343
|
}
|
|
@@ -16452,7 +16352,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
16452
16352
|
*/
|
|
16453
16353
|
function createFetchSubMessages(larkClient) {
|
|
16454
16354
|
return async (msgId) => {
|
|
16455
|
-
log$
|
|
16355
|
+
log$11.info("fetchSubMessages 请求", {
|
|
16456
16356
|
msgId,
|
|
16457
16357
|
url: `/open-apis/im/v1/messages/${msgId}`
|
|
16458
16358
|
});
|
|
@@ -16464,7 +16364,7 @@ function createFetchSubMessages(larkClient) {
|
|
|
16464
16364
|
card_msg_content_type: "raw_card_content"
|
|
16465
16365
|
}
|
|
16466
16366
|
});
|
|
16467
|
-
log$
|
|
16367
|
+
log$11.info("fetchSubMessages 响应", {
|
|
16468
16368
|
msgId,
|
|
16469
16369
|
code: response?.code,
|
|
16470
16370
|
msg: response?.msg,
|
|
@@ -16481,11 +16381,11 @@ function createFetchSubMessages(larkClient) {
|
|
|
16481
16381
|
* the account and log function.
|
|
16482
16382
|
*/
|
|
16483
16383
|
function createParseResolveNames(account) {
|
|
16484
|
-
return createBatchResolveNames(account, (...args) => log$
|
|
16384
|
+
return createBatchResolveNames(account, (...args) => log$11.info(args.map(String).join(" ")));
|
|
16485
16385
|
}
|
|
16486
16386
|
//#endregion
|
|
16487
16387
|
//#region src/messaging/inbound/parse.ts
|
|
16488
|
-
const log$
|
|
16388
|
+
const log$10 = larkLogger("inbound/parse");
|
|
16489
16389
|
/**
|
|
16490
16390
|
* Parse a raw Feishu message event into a normalised MessageContext.
|
|
16491
16391
|
*
|
|
@@ -16535,7 +16435,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
16535
16435
|
const fullContent = await fetchCardContent(event.message.message_id, larkClient);
|
|
16536
16436
|
if (fullContent) {
|
|
16537
16437
|
effectiveContent = fullContent;
|
|
16538
|
-
log$
|
|
16438
|
+
log$10.info("replaced interactive content with full v2 card data");
|
|
16539
16439
|
}
|
|
16540
16440
|
}
|
|
16541
16441
|
const convertCtx = {
|
|
@@ -16575,6 +16475,219 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
16575
16475
|
};
|
|
16576
16476
|
}
|
|
16577
16477
|
//#endregion
|
|
16478
|
+
//#region src/messaging/shared/message-lookup.ts
|
|
16479
|
+
const log$9 = larkLogger("shared/message-lookup");
|
|
16480
|
+
/**
|
|
16481
|
+
* Retrieve a single message by its ID from the Feishu IM API.
|
|
16482
|
+
*
|
|
16483
|
+
* Returns a normalised {@link FeishuMessageInfo} object, or `null` if the
|
|
16484
|
+
* message cannot be found or the API returns an error.
|
|
16485
|
+
*
|
|
16486
|
+
* @param params.cfg - Plugin configuration with Feishu credentials.
|
|
16487
|
+
* @param params.messageId - The message ID to fetch.
|
|
16488
|
+
* @param params.accountId - Optional account identifier for multi-account setups.
|
|
16489
|
+
*/
|
|
16490
|
+
async function getMessageFeishu(params) {
|
|
16491
|
+
const { cfg, messageId, accountId, expandForward } = params;
|
|
16492
|
+
const larkClient = LarkClient.fromCfg(cfg, accountId);
|
|
16493
|
+
const sdk = larkClient.sdk;
|
|
16494
|
+
try {
|
|
16495
|
+
const requestOpts = {
|
|
16496
|
+
method: "GET",
|
|
16497
|
+
url: `/open-apis/im/v1/messages/mget`,
|
|
16498
|
+
params: {
|
|
16499
|
+
message_ids: messageId,
|
|
16500
|
+
user_id_type: "open_id",
|
|
16501
|
+
card_msg_content_type: "raw_card_content"
|
|
16502
|
+
}
|
|
16503
|
+
};
|
|
16504
|
+
const items = (await sdk.request(requestOpts))?.data?.items;
|
|
16505
|
+
if (!items || items.length === 0) {
|
|
16506
|
+
log$9.info(`getMessageFeishu: no items returned for ${messageId}`);
|
|
16507
|
+
return null;
|
|
16508
|
+
}
|
|
16509
|
+
const expandCtx = expandForward ? {
|
|
16510
|
+
cfg,
|
|
16511
|
+
accountId,
|
|
16512
|
+
fetchSubMessages: async (msgId) => {
|
|
16513
|
+
const res = await larkClient.sdk.request({
|
|
16514
|
+
method: "GET",
|
|
16515
|
+
url: `/open-apis/im/v1/messages/${msgId}`,
|
|
16516
|
+
params: {
|
|
16517
|
+
user_id_type: "open_id",
|
|
16518
|
+
card_msg_content_type: "raw_card_content"
|
|
16519
|
+
}
|
|
16520
|
+
});
|
|
16521
|
+
if (res?.code !== 0) throw new Error(`API error: code=${res?.code} msg=${res?.msg}`);
|
|
16522
|
+
return res?.data?.items ?? [];
|
|
16523
|
+
},
|
|
16524
|
+
batchResolveNames: createBatchResolveNames(getLarkAccount(cfg, accountId), (...args) => log$9.info(args.map(String).join(" ")))
|
|
16525
|
+
} : void 0;
|
|
16526
|
+
return await parseMessageItem(items[0], messageId, expandCtx);
|
|
16527
|
+
} catch (error) {
|
|
16528
|
+
log$9.error(`get message failed (${messageId}): ${error instanceof Error ? error.message : String(error)}`);
|
|
16529
|
+
return null;
|
|
16530
|
+
}
|
|
16531
|
+
}
|
|
16532
|
+
/**
|
|
16533
|
+
* Parse a single message item from the Feishu IM API response into a
|
|
16534
|
+
* normalised {@link FeishuMessageInfo}.
|
|
16535
|
+
*
|
|
16536
|
+
* Content parsing is delegated to the shared converter system so that
|
|
16537
|
+
* every message-type mapping is defined in exactly one place.
|
|
16538
|
+
*/
|
|
16539
|
+
async function parseMessageItem(msg, fallbackMessageId, expandCtx) {
|
|
16540
|
+
const msgType = msg.msg_type ?? "text";
|
|
16541
|
+
const rawContent = msg.body?.content ?? "{}";
|
|
16542
|
+
const messageId = msg.message_id ?? fallbackMessageId;
|
|
16543
|
+
const acctId = expandCtx?.accountId;
|
|
16544
|
+
const { content } = await convertMessageContent(rawContent, msgType, {
|
|
16545
|
+
...buildConvertContextFromItem(msg, fallbackMessageId, acctId),
|
|
16546
|
+
cfg: expandCtx?.cfg,
|
|
16547
|
+
accountId: acctId,
|
|
16548
|
+
fetchSubMessages: expandCtx?.fetchSubMessages,
|
|
16549
|
+
batchResolveNames: expandCtx?.batchResolveNames
|
|
16550
|
+
});
|
|
16551
|
+
const senderId = msg.sender?.id ?? void 0;
|
|
16552
|
+
const senderType = msg.sender?.sender_type ?? void 0;
|
|
16553
|
+
const senderName = senderId && acctId ? getUserNameCache(acctId).get(senderId) : void 0;
|
|
16554
|
+
return {
|
|
16555
|
+
messageId,
|
|
16556
|
+
chatId: msg.chat_id ?? "",
|
|
16557
|
+
chatType: msg.chat_type ?? void 0,
|
|
16558
|
+
senderId,
|
|
16559
|
+
senderName,
|
|
16560
|
+
senderType,
|
|
16561
|
+
content,
|
|
16562
|
+
contentType: msgType,
|
|
16563
|
+
createTime: msg.create_time ? parseInt(String(msg.create_time), 10) : void 0,
|
|
16564
|
+
threadId: msg.thread_id || void 0
|
|
16565
|
+
};
|
|
16566
|
+
}
|
|
16567
|
+
//#endregion
|
|
16568
|
+
//#region src/messaging/inbound/enrich.ts
|
|
16569
|
+
/** 从飞书 IM API 获取指定消息的 parent_id 和 root_id 字段 */
|
|
16570
|
+
async function getMessageParentId(params) {
|
|
16571
|
+
const { messageId, account, accountScopedCfg, log } = params;
|
|
16572
|
+
try {
|
|
16573
|
+
const items = (await LarkClient.fromCfg(accountScopedCfg, account.accountId).sdk.request({
|
|
16574
|
+
method: "GET",
|
|
16575
|
+
url: `/open-apis/im/v1/messages/mget`,
|
|
16576
|
+
params: {
|
|
16577
|
+
message_ids: messageId,
|
|
16578
|
+
user_id_type: "open_id"
|
|
16579
|
+
}
|
|
16580
|
+
}))?.data?.items;
|
|
16581
|
+
if (!items || items.length === 0) {
|
|
16582
|
+
log(`feishu[${account.accountId}]: getMessageParentId: no items returned for ${messageId}`);
|
|
16583
|
+
return null;
|
|
16584
|
+
}
|
|
16585
|
+
const item = items[0];
|
|
16586
|
+
log(`feishu[${account.accountId}]: getMessageParentId(${messageId}): parent_id=${item.parent_id || "none"}, root_id=${item.root_id || "none"}`);
|
|
16587
|
+
return {
|
|
16588
|
+
parentId: item.parent_id || void 0,
|
|
16589
|
+
rootId: item.root_id || void 0
|
|
16590
|
+
};
|
|
16591
|
+
} catch (error) {
|
|
16592
|
+
log(`feishu[${account.accountId}]: getMessageParentId failed (${messageId}): ${error instanceof Error ? error.message : String(error)}`);
|
|
16593
|
+
return null;
|
|
16594
|
+
}
|
|
16595
|
+
}
|
|
16596
|
+
/** 将消息格式化为带缩进层级的回复链文本行 */
|
|
16597
|
+
function formatChainMessage(msg, depth) {
|
|
16598
|
+
const prefix = `[message_id=${msg.messageId}]`;
|
|
16599
|
+
const text = msg.senderName ? `${prefix} ${msg.senderName}: ${msg.content}` : `${prefix} ${msg.content}`;
|
|
16600
|
+
if (depth === 0) return text;
|
|
16601
|
+
return `${" ".repeat(depth)}↳ ${text}`;
|
|
16602
|
+
}
|
|
16603
|
+
/**
|
|
16604
|
+
* 递归解析回复链上下文,支持 rootId 追溯和 N 层 parentId 递归。
|
|
16605
|
+
*
|
|
16606
|
+
* 解析逻辑:
|
|
16607
|
+
* 1. 如果存在 rootId 且与 parentId 不同,先获取话题根消息作为链的起点
|
|
16608
|
+
* 2. 从 parentId 开始,递归向上追溯每条消息的 parentId,最多追溯 maxQuoteDepth 层
|
|
16609
|
+
* 3. 对结果去重后按时间顺序(最旧在前)输出完整的对话链上下文
|
|
16610
|
+
*
|
|
16611
|
+
* 输出格式:
|
|
16612
|
+
* ```
|
|
16613
|
+
* [回复链上下文]:
|
|
16614
|
+
* [message_id=xxx] senderName: content
|
|
16615
|
+
* ↳ [message_id=yyy] senderName2: content2
|
|
16616
|
+
* ↳ [message_id=zzz] senderName3: content3
|
|
16617
|
+
* ```
|
|
16618
|
+
*/
|
|
16619
|
+
async function resolveQuotedContent(params) {
|
|
16620
|
+
const { ctx, accountScopedCfg, account, log, maxQuoteDepth = 3 } = params;
|
|
16621
|
+
if (!ctx.parentId) return void 0;
|
|
16622
|
+
log(`feishu[${account.accountId}]: resolveQuotedContent 开始, parentId=${ctx.parentId}, rootId=${ctx.rootId || "none"}, maxDepth=${maxQuoteDepth}`);
|
|
16623
|
+
const chainMessages = [];
|
|
16624
|
+
const visited = /* @__PURE__ */ new Set();
|
|
16625
|
+
let currentMsgId = ctx.parentId;
|
|
16626
|
+
let depth = 0;
|
|
16627
|
+
while (currentMsgId && depth < maxQuoteDepth) {
|
|
16628
|
+
if (visited.has(currentMsgId)) {
|
|
16629
|
+
log(`feishu[${account.accountId}]: 检测到循环引用, messageId=${currentMsgId}, 停止递归`);
|
|
16630
|
+
break;
|
|
16631
|
+
}
|
|
16632
|
+
visited.add(currentMsgId);
|
|
16633
|
+
try {
|
|
16634
|
+
const msg = await getMessageFeishu({
|
|
16635
|
+
cfg: accountScopedCfg,
|
|
16636
|
+
messageId: currentMsgId,
|
|
16637
|
+
accountId: account.accountId,
|
|
16638
|
+
expandForward: true
|
|
16639
|
+
});
|
|
16640
|
+
if (!msg) {
|
|
16641
|
+
log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层消息获取为空, messageId=${currentMsgId}, 停止递归`);
|
|
16642
|
+
break;
|
|
16643
|
+
}
|
|
16644
|
+
log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层: messageId=${currentMsgId}, content=${msg.content?.slice(0, 80)}`);
|
|
16645
|
+
chainMessages.push(msg);
|
|
16646
|
+
const parentInfo = await getMessageParentId({
|
|
16647
|
+
messageId: currentMsgId,
|
|
16648
|
+
account,
|
|
16649
|
+
accountScopedCfg,
|
|
16650
|
+
log
|
|
16651
|
+
});
|
|
16652
|
+
if (!parentInfo?.parentId) {
|
|
16653
|
+
log(`feishu[${account.accountId}]: 第 ${depth + 1} 层消息无 parentId, 到达链顶端`);
|
|
16654
|
+
break;
|
|
16655
|
+
}
|
|
16656
|
+
currentMsgId = parentInfo.parentId;
|
|
16657
|
+
depth++;
|
|
16658
|
+
} catch (err) {
|
|
16659
|
+
log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层获取失败, messageId=${currentMsgId}: ${String(err)}, 停止递归`);
|
|
16660
|
+
break;
|
|
16661
|
+
}
|
|
16662
|
+
}
|
|
16663
|
+
if (depth >= maxQuoteDepth) log(`feishu[${account.accountId}]: 已达到最大递归深度 ${maxQuoteDepth}, 停止追溯`);
|
|
16664
|
+
if (ctx.rootId && ctx.rootId !== ctx.parentId && !visited.has(ctx.rootId)) try {
|
|
16665
|
+
const rootMsg = await getMessageFeishu({
|
|
16666
|
+
cfg: accountScopedCfg,
|
|
16667
|
+
messageId: ctx.rootId,
|
|
16668
|
+
accountId: account.accountId,
|
|
16669
|
+
expandForward: true
|
|
16670
|
+
});
|
|
16671
|
+
if (rootMsg) {
|
|
16672
|
+
log(`feishu[${account.accountId}]: 获取到话题根消息: rootId=${ctx.rootId}, content=${rootMsg.content?.slice(0, 80)}`);
|
|
16673
|
+
chainMessages.push(rootMsg);
|
|
16674
|
+
visited.add(ctx.rootId);
|
|
16675
|
+
} else log(`feishu[${account.accountId}]: 话题根消息获取为空, rootId=${ctx.rootId}`);
|
|
16676
|
+
} catch (err) {
|
|
16677
|
+
log(`feishu[${account.accountId}]: 话题根消息获取失败, rootId=${ctx.rootId}: ${String(err)}`);
|
|
16678
|
+
}
|
|
16679
|
+
if (chainMessages.length === 0) {
|
|
16680
|
+
log(`feishu[${account.accountId}]: 回复链解析完成, 未获取到任何消息`);
|
|
16681
|
+
return;
|
|
16682
|
+
}
|
|
16683
|
+
chainMessages.reverse();
|
|
16684
|
+
const lines = ["[回复链上下文]:"];
|
|
16685
|
+
for (let i = 0; i < chainMessages.length; i++) lines.push(formatChainMessage(chainMessages[i], i));
|
|
16686
|
+
const result = lines.join("\n");
|
|
16687
|
+
log(`feishu[${account.accountId}]: 回复链解析完成, 共 ${chainMessages.length} 条消息`);
|
|
16688
|
+
return result;
|
|
16689
|
+
}
|
|
16690
|
+
//#endregion
|
|
16578
16691
|
//#region src/messaging/inbound/dedup.ts
|
|
16579
16692
|
const DEFAULT_TTL_MS = 720 * 60 * 1e3;
|
|
16580
16693
|
const DEFAULT_MAX_ENTRIES = 5e3;
|
|
@@ -18204,6 +18317,30 @@ async function main() {
|
|
|
18204
18317
|
brand: "feishu",
|
|
18205
18318
|
config: {}
|
|
18206
18319
|
};
|
|
18320
|
+
const accountScopedCfg = { channels: { feishu: {
|
|
18321
|
+
...account.config,
|
|
18322
|
+
appId: account.appId,
|
|
18323
|
+
appSecret: account.appSecret,
|
|
18324
|
+
domain: account.brand
|
|
18325
|
+
} } };
|
|
18326
|
+
const resolveQuotedContentForMessage = async (ctx) => {
|
|
18327
|
+
if (!ctx.parentId) return void 0;
|
|
18328
|
+
try {
|
|
18329
|
+
return await resolveQuotedContent({
|
|
18330
|
+
ctx,
|
|
18331
|
+
accountScopedCfg,
|
|
18332
|
+
account,
|
|
18333
|
+
log: (...args) => logger.info("resolveQuotedContent", { detail: args.map(String).join(" ") })
|
|
18334
|
+
});
|
|
18335
|
+
} catch (err) {
|
|
18336
|
+
logger.warn("引用消息解析失败,继续仅处理当前消息", {
|
|
18337
|
+
msgId: ctx.messageId,
|
|
18338
|
+
parentId: ctx.parentId,
|
|
18339
|
+
error: err instanceof Error ? err.message : String(err)
|
|
18340
|
+
});
|
|
18341
|
+
return;
|
|
18342
|
+
}
|
|
18343
|
+
};
|
|
18207
18344
|
const larkClient = LarkClient.fromAccount(account);
|
|
18208
18345
|
const probeResult = await larkClient.probe();
|
|
18209
18346
|
if (probeResult.ok) logger.info("Bot 身份探测成功", {
|
|
@@ -18342,7 +18479,7 @@ async function main() {
|
|
|
18342
18479
|
});
|
|
18343
18480
|
if (nameResult.name) parsed.senderName = nameResult.name;
|
|
18344
18481
|
} catch {}
|
|
18345
|
-
const formattedText =
|
|
18482
|
+
const formattedText = formatPromptForCC(parsed, true, await resolveQuotedContentForMessage(parsed));
|
|
18346
18483
|
teammateBuffer.push(parsed.chatId, {
|
|
18347
18484
|
formattedText,
|
|
18348
18485
|
bufferedAt: Date.now(),
|
|
@@ -18394,6 +18531,7 @@ async function main() {
|
|
|
18394
18531
|
workspaceRoot
|
|
18395
18532
|
});
|
|
18396
18533
|
await ensureUserDirectory(userContext);
|
|
18534
|
+
const quotedContent = await resolveQuotedContentForMessage(parsed);
|
|
18397
18535
|
await dispatchToCC({
|
|
18398
18536
|
ctx: parsed,
|
|
18399
18537
|
account,
|
|
@@ -18403,7 +18541,8 @@ async function main() {
|
|
|
18403
18541
|
userContext,
|
|
18404
18542
|
placeholderCardId,
|
|
18405
18543
|
placeholderMessageId,
|
|
18406
|
-
messageStore
|
|
18544
|
+
messageStore,
|
|
18545
|
+
quotedContent
|
|
18407
18546
|
});
|
|
18408
18547
|
if (threadId) markBotInvolvedInThread(threadId);
|
|
18409
18548
|
} catch (err) {
|