@vibe-lark/larkpal 0.1.58 → 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 +833 -677
- package/package.json +2 -2
package/dist/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import * as path$2 from "node:path";
|
|
|
6
6
|
import path, { basename, dirname, extname, join, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import "dotenv/config";
|
|
9
|
-
import { access, lstat, mkdir, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
9
|
+
import { access, lstat, mkdir, open, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { execFileSync, spawn } from "node:child_process";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
@@ -38,7 +38,7 @@ import { Readable } from "node:stream";
|
|
|
38
38
|
*
|
|
39
39
|
* 本模块在启动时检测它们是否已安装,缺失时给出明确的安装指引。
|
|
40
40
|
*/
|
|
41
|
-
const log$
|
|
41
|
+
const log$39 = larkLogger("preflight");
|
|
42
42
|
function detectCli(name) {
|
|
43
43
|
try {
|
|
44
44
|
const cliPath = execFileSync("which", [name], {
|
|
@@ -76,14 +76,14 @@ function detectCli(name) {
|
|
|
76
76
|
* 缺失任一依赖时打印安装指引并抛出错误,阻止启动。
|
|
77
77
|
*/
|
|
78
78
|
function preflight() {
|
|
79
|
-
log$
|
|
79
|
+
log$39.info("开始前置环境检查...");
|
|
80
80
|
const useCodex = (process.env.LARKPAL_RUNTIME || "claude-code") === "codex-app-server";
|
|
81
81
|
const claude = detectCli("claude");
|
|
82
82
|
const codex = useCodex ? detectCli(process.env.LARKPAL_CODEX_BIN || "codex") : void 0;
|
|
83
83
|
const larkCli = detectCli("lark-cli");
|
|
84
84
|
const status = (dep) => dep.installed ? `✅ ${dep.name} (${dep.version ?? "unknown"}) → ${dep.path}` : `❌ ${dep.name} 未安装`;
|
|
85
|
-
log$
|
|
86
|
-
log$
|
|
85
|
+
log$39.info(` ${status(useCodex && codex ? codex : claude)}`);
|
|
86
|
+
log$39.info(` ${status(larkCli)}`);
|
|
87
87
|
const missing = [];
|
|
88
88
|
if (useCodex) {
|
|
89
89
|
if (!codex?.installed) missing.push([
|
|
@@ -119,10 +119,10 @@ function preflight() {
|
|
|
119
119
|
"",
|
|
120
120
|
"请安装缺失的依赖后重新启动 LarkPal。"
|
|
121
121
|
].join("\n");
|
|
122
|
-
log$
|
|
122
|
+
log$39.error(msg);
|
|
123
123
|
throw new Error("前置环境检查未通过,缺少必要依赖");
|
|
124
124
|
}
|
|
125
|
-
log$
|
|
125
|
+
log$39.info("前置环境检查通过 ✓");
|
|
126
126
|
return {
|
|
127
127
|
claude,
|
|
128
128
|
codex,
|
|
@@ -147,7 +147,7 @@ function preflight() {
|
|
|
147
147
|
* 同时生成新版 (~/.lark-cli/config.json) 和旧版 (~/.config/lark/config.json)
|
|
148
148
|
* 格式的配置文件,保证 lark-cli 无论通过哪种路径读取都能正常工作。
|
|
149
149
|
*/
|
|
150
|
-
const log$
|
|
150
|
+
const log$38 = larkLogger("config/ensure-lark-cli");
|
|
151
151
|
/** 新版 lark-cli 配置路径 */
|
|
152
152
|
const NEW_CONFIG_DIR = join(homedir(), ".lark-cli");
|
|
153
153
|
const NEW_CONFIG_PATH = join(NEW_CONFIG_DIR, "config.json");
|
|
@@ -164,19 +164,19 @@ function ensureLarkCliConfig() {
|
|
|
164
164
|
const appId = process.env.LARK_APP_ID;
|
|
165
165
|
const appSecret = process.env.LARK_APP_SECRET;
|
|
166
166
|
if (!appId || !appSecret) {
|
|
167
|
-
log$
|
|
167
|
+
log$38.info("环境变量凭证未设置,跳过 lark-cli 配置同步");
|
|
168
168
|
return;
|
|
169
169
|
}
|
|
170
170
|
const hasNewConfig = existsSync(NEW_CONFIG_PATH);
|
|
171
171
|
const hasLegacyConfig = existsSync(LEGACY_CONFIG_PATH);
|
|
172
172
|
if (hasNewConfig && hasLegacyConfig) {
|
|
173
|
-
log$
|
|
173
|
+
log$38.info("lark-cli 配置文件已存在,跳过同步", {
|
|
174
174
|
newConfig: NEW_CONFIG_PATH,
|
|
175
175
|
legacyConfig: LEGACY_CONFIG_PATH
|
|
176
176
|
});
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
-
log$
|
|
179
|
+
log$38.info("检测到环境变量凭证但 lark-cli 配置文件缺失,开始同步", {
|
|
180
180
|
appId,
|
|
181
181
|
hasNewConfig,
|
|
182
182
|
hasLegacyConfig
|
|
@@ -195,9 +195,9 @@ function ensureLarkCliConfig() {
|
|
|
195
195
|
encoding: "utf-8",
|
|
196
196
|
mode: 384
|
|
197
197
|
});
|
|
198
|
-
log$
|
|
198
|
+
log$38.info("已生成新版 lark-cli 配置文件", { path: NEW_CONFIG_PATH });
|
|
199
199
|
} catch (err) {
|
|
200
|
-
log$
|
|
200
|
+
log$38.warn("生成新版 lark-cli 配置文件失败", {
|
|
201
201
|
path: NEW_CONFIG_PATH,
|
|
202
202
|
error: err instanceof Error ? err.message : String(err)
|
|
203
203
|
});
|
|
@@ -216,9 +216,9 @@ function ensureLarkCliConfig() {
|
|
|
216
216
|
encoding: "utf-8",
|
|
217
217
|
mode: 384
|
|
218
218
|
});
|
|
219
|
-
log$
|
|
219
|
+
log$38.info("已生成旧版 lark-cli 配置文件", { path: LEGACY_CONFIG_PATH });
|
|
220
220
|
} catch (err) {
|
|
221
|
-
log$
|
|
221
|
+
log$38.warn("生成旧版 lark-cli 配置文件失败", {
|
|
222
222
|
path: LEGACY_CONFIG_PATH,
|
|
223
223
|
error: err instanceof Error ? err.message : String(err)
|
|
224
224
|
});
|
|
@@ -439,7 +439,7 @@ async function ensureDefaults() {
|
|
|
439
439
|
* - github: { type: "api_key", token }
|
|
440
440
|
* - custom-api: { type: "http_bearer", base_url, token }
|
|
441
441
|
*/
|
|
442
|
-
const log$
|
|
442
|
+
const log$37 = larkLogger("user/credential-vault");
|
|
443
443
|
var CredentialVault = class {
|
|
444
444
|
credentialPath;
|
|
445
445
|
encryptionKey;
|
|
@@ -451,12 +451,12 @@ var CredentialVault = class {
|
|
|
451
451
|
if (keyEnv) {
|
|
452
452
|
this.encryptionKey = keyEnv.length === 64 ? Buffer.from(keyEnv, "hex") : Buffer.from(keyEnv, "base64");
|
|
453
453
|
if (this.encryptionKey.length !== 32) {
|
|
454
|
-
log$
|
|
454
|
+
log$37.error("LARKPAL_ENCRYPTION_KEY 长度不正确,需要 32 字节", { actualLength: this.encryptionKey.length });
|
|
455
455
|
this.encryptionKey = null;
|
|
456
|
-
} else log$
|
|
456
|
+
} else log$37.info("凭证加密已启用");
|
|
457
457
|
} else {
|
|
458
458
|
this.encryptionKey = null;
|
|
459
|
-
log$
|
|
459
|
+
log$37.warn("LARKPAL_ENCRYPTION_KEY 未设置,凭证将以明文存储(仅限开发模式)");
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
/**
|
|
@@ -464,7 +464,7 @@ var CredentialVault = class {
|
|
|
464
464
|
*/
|
|
465
465
|
async load() {
|
|
466
466
|
if (!existsSync$1(this.credentialPath)) {
|
|
467
|
-
log$
|
|
467
|
+
log$37.info("凭证文件不存在,使用空凭证", { path: this.credentialPath });
|
|
468
468
|
this.store = {};
|
|
469
469
|
this.loaded = true;
|
|
470
470
|
return;
|
|
@@ -473,12 +473,12 @@ var CredentialVault = class {
|
|
|
473
473
|
const raw = await readFile$1(this.credentialPath, "utf-8");
|
|
474
474
|
this.store = JSON.parse(raw);
|
|
475
475
|
this.loaded = true;
|
|
476
|
-
log$
|
|
476
|
+
log$37.info("凭证文件加载完成", {
|
|
477
477
|
path: this.credentialPath,
|
|
478
478
|
credentialCount: Object.keys(this.store).length
|
|
479
479
|
});
|
|
480
480
|
} catch (err) {
|
|
481
|
-
log$
|
|
481
|
+
log$37.error("凭证文件加载失败", {
|
|
482
482
|
path: this.credentialPath,
|
|
483
483
|
error: err instanceof Error ? err.message : String(err)
|
|
484
484
|
});
|
|
@@ -492,12 +492,12 @@ var CredentialVault = class {
|
|
|
492
492
|
async save() {
|
|
493
493
|
try {
|
|
494
494
|
await writeFile$1(this.credentialPath, JSON.stringify(this.store, null, 2), "utf-8");
|
|
495
|
-
log$
|
|
495
|
+
log$37.info("凭证文件保存完成", {
|
|
496
496
|
path: this.credentialPath,
|
|
497
497
|
credentialCount: Object.keys(this.store).length
|
|
498
498
|
});
|
|
499
499
|
} catch (err) {
|
|
500
|
-
log$
|
|
500
|
+
log$37.error("凭证文件保存失败", {
|
|
501
501
|
path: this.credentialPath,
|
|
502
502
|
error: err instanceof Error ? err.message : String(err)
|
|
503
503
|
});
|
|
@@ -519,7 +519,7 @@ var CredentialVault = class {
|
|
|
519
519
|
*/
|
|
520
520
|
async setCredential(name, credential) {
|
|
521
521
|
if (!this.loaded) await this.load();
|
|
522
|
-
log$
|
|
522
|
+
log$37.info("设置凭证", {
|
|
523
523
|
name,
|
|
524
524
|
fields: Object.keys(credential)
|
|
525
525
|
});
|
|
@@ -535,7 +535,7 @@ var CredentialVault = class {
|
|
|
535
535
|
if (!(name in this.store)) return false;
|
|
536
536
|
delete this.store[name];
|
|
537
537
|
await this.save();
|
|
538
|
-
log$
|
|
538
|
+
log$37.info("凭证已删除", { name });
|
|
539
539
|
return true;
|
|
540
540
|
}
|
|
541
541
|
/**
|
|
@@ -564,7 +564,7 @@ var CredentialVault = class {
|
|
|
564
564
|
envVars[envKey] = String(fieldValue);
|
|
565
565
|
}
|
|
566
566
|
} catch (err) {
|
|
567
|
-
log$
|
|
567
|
+
log$37.warn("凭证解密失败,跳过", {
|
|
568
568
|
name,
|
|
569
569
|
error: err instanceof Error ? err.message : String(err)
|
|
570
570
|
});
|
|
@@ -621,7 +621,7 @@ var CredentialVault = class {
|
|
|
621
621
|
* - B 用户的 CC 进程连接 B 的数据库 MCP Server
|
|
622
622
|
* - 全局工具(lark-cli、文件操作等)所有用户共享
|
|
623
623
|
*/
|
|
624
|
-
const log$
|
|
624
|
+
const log$36 = larkLogger("user/mcp-merge");
|
|
625
625
|
/** 全局 MCP 配置路径 */
|
|
626
626
|
const GLOBAL_MCP_CONFIG_PATH = path$1.join(os.homedir(), ".claude", "mcp-servers.json");
|
|
627
627
|
/**
|
|
@@ -677,9 +677,9 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
677
677
|
if (existsSync$1(GLOBAL_MCP_CONFIG_PATH)) try {
|
|
678
678
|
const raw = await readFile$1(GLOBAL_MCP_CONFIG_PATH, "utf-8");
|
|
679
679
|
globalConfig = JSON.parse(raw);
|
|
680
|
-
log$
|
|
680
|
+
log$36.info("全局 MCP 配置加载完成", { serverCount: Object.keys(globalConfig).length });
|
|
681
681
|
} catch (err) {
|
|
682
|
-
log$
|
|
682
|
+
log$36.warn("全局 MCP 配置文件解析失败", {
|
|
683
683
|
path: GLOBAL_MCP_CONFIG_PATH,
|
|
684
684
|
error: err instanceof Error ? err.message : String(err)
|
|
685
685
|
});
|
|
@@ -690,13 +690,13 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
690
690
|
if (existsSync$1(userMcpPath)) try {
|
|
691
691
|
const raw = await readFile$1(userMcpPath, "utf-8");
|
|
692
692
|
userConfig = JSON.parse(raw);
|
|
693
|
-
log$
|
|
693
|
+
log$36.info("用户 MCP 配置加载完成", {
|
|
694
694
|
userId: userCtx.userId,
|
|
695
695
|
serverCount: Object.keys(userConfig).length,
|
|
696
696
|
servers: Object.keys(userConfig)
|
|
697
697
|
});
|
|
698
698
|
} catch (err) {
|
|
699
|
-
log$
|
|
699
|
+
log$36.warn("用户 MCP 配置文件解析失败", {
|
|
700
700
|
userId: userCtx?.userId,
|
|
701
701
|
path: userMcpPath,
|
|
702
702
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -708,7 +708,7 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
708
708
|
...globalConfig,
|
|
709
709
|
...userConfig
|
|
710
710
|
};
|
|
711
|
-
log$
|
|
711
|
+
log$36.info("MCP 配置合并完成", {
|
|
712
712
|
userId: userCtx?.userId,
|
|
713
713
|
builtinServers: Object.keys(builtinConfig),
|
|
714
714
|
globalServers: Object.keys(globalConfig),
|
|
@@ -717,7 +717,7 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId, cwd) {
|
|
|
717
717
|
});
|
|
718
718
|
const tmpPath = path$1.join(os.tmpdir(), `mcp-${sessionId}.json`);
|
|
719
719
|
await writeFile$1(tmpPath, JSON.stringify({ mcpServers: merged }, null, 2), "utf-8");
|
|
720
|
-
log$
|
|
720
|
+
log$36.info("MCP 合并配置已写入临时文件", {
|
|
721
721
|
path: tmpPath,
|
|
722
722
|
serverCount: Object.keys(merged).length
|
|
723
723
|
});
|
|
@@ -731,7 +731,7 @@ async function cleanupMcpConfig(sessionId) {
|
|
|
731
731
|
try {
|
|
732
732
|
if (existsSync$1(tmpPath)) {
|
|
733
733
|
await unlink$1(tmpPath);
|
|
734
|
-
log$
|
|
734
|
+
log$36.debug("MCP 临时配置文件已清理", { path: tmpPath });
|
|
735
735
|
}
|
|
736
736
|
} catch {}
|
|
737
737
|
}
|
|
@@ -755,8 +755,7 @@ const TOOL_DISPLAY_NAMES = {
|
|
|
755
755
|
WebSearch: "正在搜索网页",
|
|
756
756
|
WebFetch: "正在获取网页内容",
|
|
757
757
|
TodoWrite: "正在更新任务列表",
|
|
758
|
-
Task: "正在执行子任务"
|
|
759
|
-
_AutoCompact: "正在压缩对话历史"
|
|
758
|
+
Task: "正在执行子任务"
|
|
760
759
|
};
|
|
761
760
|
/** 获取工具的中文显示名称,未知工具返回通用描述 */
|
|
762
761
|
function getToolDisplayName(toolName) {
|
|
@@ -780,7 +779,7 @@ function getToolDisplayName(toolName) {
|
|
|
780
779
|
* - result(data) — 最终结果
|
|
781
780
|
* - parseError(error, rawLine) — 解析错误(不中断流)
|
|
782
781
|
*/
|
|
783
|
-
const log$
|
|
782
|
+
const log$35 = larkLogger("cc-runtime/stream-parser");
|
|
784
783
|
var CCStreamParser = class extends EventEmitter {
|
|
785
784
|
/**
|
|
786
785
|
* 解析一行 NDJSON 文本
|
|
@@ -794,7 +793,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
794
793
|
try {
|
|
795
794
|
msg = JSON.parse(trimmed);
|
|
796
795
|
} catch (err) {
|
|
797
|
-
log$
|
|
796
|
+
log$35.warn("NDJSON 解析失败,跳过该行", {
|
|
798
797
|
error: String(err),
|
|
799
798
|
rawLine: trimmed.slice(0, 200)
|
|
800
799
|
});
|
|
@@ -802,7 +801,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
802
801
|
return;
|
|
803
802
|
}
|
|
804
803
|
if (msg.parent_tool_use_id != null && msg.type !== "result") {
|
|
805
|
-
log$
|
|
804
|
+
log$35.debug("跳过 subagent 内部事件", {
|
|
806
805
|
type: msg.type,
|
|
807
806
|
parentToolUseId: msg.parent_tool_use_id
|
|
808
807
|
});
|
|
@@ -825,14 +824,14 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
825
824
|
this.handleResult(msg);
|
|
826
825
|
break;
|
|
827
826
|
case "system":
|
|
828
|
-
log$
|
|
827
|
+
log$35.info("收到 system 消息", {
|
|
829
828
|
subtype: msg.subtype,
|
|
830
829
|
detail: JSON.stringify(msg).slice(0, 500)
|
|
831
830
|
});
|
|
832
831
|
this.emit("system", msg);
|
|
833
832
|
break;
|
|
834
833
|
default:
|
|
835
|
-
log$
|
|
834
|
+
log$35.debug("收到未知消息类型,已忽略", { type: msg.type });
|
|
836
835
|
break;
|
|
837
836
|
}
|
|
838
837
|
}
|
|
@@ -851,13 +850,13 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
851
850
|
switch (event.type) {
|
|
852
851
|
case "content_block_start": {
|
|
853
852
|
const block = event.content_block;
|
|
854
|
-
log$
|
|
853
|
+
log$35.debug("content_block_start 事件", {
|
|
855
854
|
blockType: block?.type,
|
|
856
855
|
toolName: block?.name,
|
|
857
856
|
toolUseId: block?.id
|
|
858
857
|
});
|
|
859
858
|
if (block?.type === "tool_use" && block.name) {
|
|
860
|
-
log$
|
|
859
|
+
log$35.info("工具调用开始", {
|
|
861
860
|
toolName: block.name,
|
|
862
861
|
displayName: getToolDisplayName(block.name),
|
|
863
862
|
toolUseId: block.id
|
|
@@ -876,7 +875,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
876
875
|
case "message_delta": {
|
|
877
876
|
const stopReason = event.delta?.stop_reason;
|
|
878
877
|
if (stopReason) {
|
|
879
|
-
log$
|
|
878
|
+
log$35.info("轮次结束", { stopReason });
|
|
880
879
|
this.emit("turnEnd", stopReason);
|
|
881
880
|
}
|
|
882
881
|
break;
|
|
@@ -896,7 +895,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
896
895
|
const content = msg.message?.content;
|
|
897
896
|
const stopReason = msg.message?.stop_reason;
|
|
898
897
|
const blockTypes = Array.isArray(content) ? content.map((b) => `${b.type}${b.name ? ":" + b.name : ""}`).join(", ") : "none";
|
|
899
|
-
log$
|
|
898
|
+
log$35.info("收到完整 assistant 消息", {
|
|
900
899
|
model: msg.message?.model,
|
|
901
900
|
stopReason,
|
|
902
901
|
contentBlocks: content?.length,
|
|
@@ -904,7 +903,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
904
903
|
});
|
|
905
904
|
if (Array.isArray(content)) {
|
|
906
905
|
for (const block of content) if (block.type === "tool_use" && block.name) {
|
|
907
|
-
log$
|
|
906
|
+
log$35.info("从 assistant 消息提取工具调用", {
|
|
908
907
|
toolName: block.name,
|
|
909
908
|
displayName: getToolDisplayName(block.name),
|
|
910
909
|
toolUseId: block.id
|
|
@@ -912,11 +911,6 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
912
911
|
this.emit("toolUseStart", block.name, block.input ?? {});
|
|
913
912
|
}
|
|
914
913
|
}
|
|
915
|
-
const usage = msg.message?.usage;
|
|
916
|
-
if (usage && typeof usage.input_tokens === "number" && usage.input_tokens > 0) this.emit("usage", {
|
|
917
|
-
input_tokens: usage.input_tokens,
|
|
918
|
-
output_tokens: usage.output_tokens ?? 0
|
|
919
|
-
});
|
|
920
914
|
this.emit("assistant", msg);
|
|
921
915
|
}
|
|
922
916
|
/**
|
|
@@ -928,7 +922,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
928
922
|
const content = msg.message?.content;
|
|
929
923
|
if (!Array.isArray(content)) return;
|
|
930
924
|
for (const item of content) if (item.type === "tool_result" && item.tool_use_id) {
|
|
931
|
-
log$
|
|
925
|
+
log$35.debug("工具结果返回", { toolUseId: item.tool_use_id });
|
|
932
926
|
this.emit("toolResult", item.tool_use_id);
|
|
933
927
|
}
|
|
934
928
|
}
|
|
@@ -936,7 +930,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
936
930
|
* 处理 tool_progress 消息 — 工具执行进度
|
|
937
931
|
*/
|
|
938
932
|
handleToolProgress(msg) {
|
|
939
|
-
log$
|
|
933
|
+
log$35.debug("工具执行进度", {
|
|
940
934
|
toolName: msg.tool_name,
|
|
941
935
|
elapsed: msg.elapsed_time_seconds
|
|
942
936
|
});
|
|
@@ -946,18 +940,13 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
946
940
|
* 处理 result 消息 — CC 执行完成的最终结果
|
|
947
941
|
*/
|
|
948
942
|
handleResult(msg) {
|
|
949
|
-
log$
|
|
943
|
+
log$35.info("CC 执行完成", {
|
|
950
944
|
subtype: msg.subtype,
|
|
951
945
|
isError: msg.is_error,
|
|
952
946
|
durationMs: msg.duration_ms,
|
|
953
947
|
totalCostUsd: msg.total_cost_usd,
|
|
954
948
|
numTurns: msg.num_turns
|
|
955
949
|
});
|
|
956
|
-
const usage = msg.usage;
|
|
957
|
-
if (usage && typeof usage.input_tokens === "number" && usage.input_tokens > 0) this.emit("usage", {
|
|
958
|
-
input_tokens: usage.input_tokens,
|
|
959
|
-
output_tokens: usage.output_tokens ?? 0
|
|
960
|
-
});
|
|
961
950
|
this.emit("result", {
|
|
962
951
|
subtype: msg.subtype,
|
|
963
952
|
result: msg.result,
|
|
@@ -984,7 +973,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
984
973
|
* 多轮对话:进程内自动保持完整对话上下文(mutableMessages 数组累积),
|
|
985
974
|
* 无需外部维护历史。
|
|
986
975
|
*/
|
|
987
|
-
const log$
|
|
976
|
+
const log$34 = larkLogger("cc-runtime/process-manager");
|
|
988
977
|
/**
|
|
989
978
|
* LarkPal 会话 UUID v5 命名空间
|
|
990
979
|
*
|
|
@@ -1005,25 +994,6 @@ const IDLE_TIMEOUT_MS = (() => {
|
|
|
1005
994
|
return seconds * 1e3;
|
|
1006
995
|
})();
|
|
1007
996
|
/**
|
|
1008
|
-
* 自动压缩阈值(input_tokens 数)。
|
|
1009
|
-
*
|
|
1010
|
-
* 当 CC 返回的 input_tokens 超过此阈值时,在下一次用户消息发送前
|
|
1011
|
-
* 自动执行 /compact 命令压缩对话历史。
|
|
1012
|
-
*
|
|
1013
|
-
* 通过 LARKPAL_CC_AUTO_COMPACT_THRESHOLD 环境变量配置,默认 0(禁用)。
|
|
1014
|
-
* 设为 0 表示禁用自动压缩。
|
|
1015
|
-
*
|
|
1016
|
-
* LarkPal 现在优先通过 runtime-owned memory/ephemeral teammate eval 管理上下文;
|
|
1017
|
-
* 该开关只作为显式启用的应急制动。
|
|
1018
|
-
*/
|
|
1019
|
-
const AUTO_COMPACT_THRESHOLD = (() => {
|
|
1020
|
-
const envVal = process.env.LARKPAL_CC_AUTO_COMPACT_THRESHOLD;
|
|
1021
|
-
if (envVal === void 0 || envVal === "") return 0;
|
|
1022
|
-
const tokens = parseInt(envVal, 10);
|
|
1023
|
-
if (isNaN(tokens) || tokens < 0) return 5e4;
|
|
1024
|
-
return tokens;
|
|
1025
|
-
})();
|
|
1026
|
-
/**
|
|
1027
997
|
* CC 推理努力级别。
|
|
1028
998
|
*
|
|
1029
999
|
* 控制模型的 thinking budget(思维深度),影响延迟和成本:
|
|
@@ -1079,17 +1049,6 @@ var SessionProcessManager = class {
|
|
|
1079
1049
|
messageCompletionResolvers = /* @__PURE__ */ new Map();
|
|
1080
1050
|
/** 消息级串行锁:确保同一 session 的消息按顺序处理完成 */
|
|
1081
1051
|
sessionLocks = /* @__PURE__ */ new Map();
|
|
1082
|
-
/** 等待下一次 result 的维护任务 resolver(如 auto compact),不参与用户消息完成语义 */
|
|
1083
|
-
resultWaiters = /* @__PURE__ */ new Map();
|
|
1084
|
-
/**
|
|
1085
|
-
* 最近一次 CC 响应的 input_tokens:sessionId → token 数
|
|
1086
|
-
*
|
|
1087
|
-
* 由 parser 的 usage 事件更新。用于在下一次 executePrompt 前判断
|
|
1088
|
-
* 是否需要自动触发 /compact 压缩对话历史。
|
|
1089
|
-
*/
|
|
1090
|
-
lastInputTokens = /* @__PURE__ */ new Map();
|
|
1091
|
-
/** auto compact 失败熔断:sessionId → 下次允许尝试的时间戳 */
|
|
1092
|
-
compactRetryAfter = /* @__PURE__ */ new Map();
|
|
1093
1052
|
/** 标记 session 为用户主动中断 */
|
|
1094
1053
|
markAborted(sessionId) {
|
|
1095
1054
|
this.abortedSessions.add(sessionId);
|
|
@@ -1135,9 +1094,9 @@ var SessionProcessManager = class {
|
|
|
1135
1094
|
const t0 = Date.now();
|
|
1136
1095
|
const prevLock = this.sessionLocks.get(sessionId);
|
|
1137
1096
|
if (prevLock) {
|
|
1138
|
-
log$
|
|
1097
|
+
log$34.info("等待上一条消息处理完成", { sessionId });
|
|
1139
1098
|
await prevLock;
|
|
1140
|
-
log$
|
|
1099
|
+
log$34.info("[perf] 串行锁等待完成", {
|
|
1141
1100
|
sessionId,
|
|
1142
1101
|
waitMs: Date.now() - t0
|
|
1143
1102
|
});
|
|
@@ -1151,36 +1110,24 @@ var SessionProcessManager = class {
|
|
|
1151
1110
|
try {
|
|
1152
1111
|
const existing = this.processes.get(sessionId);
|
|
1153
1112
|
if (existing && existing.status === "running" && existing.useStreamInput) {
|
|
1154
|
-
log$
|
|
1113
|
+
log$34.info("向常驻进程发送消息", {
|
|
1155
1114
|
sessionId,
|
|
1156
1115
|
promptLength: config.prompt.length,
|
|
1157
1116
|
pid: existing.childProcess.pid,
|
|
1158
1117
|
prepareMs: Date.now() - t0
|
|
1159
1118
|
});
|
|
1160
|
-
await this.autoCompactIfNeeded(sessionId);
|
|
1161
1119
|
const completionPromise = this.createMessageCompletionPromise(sessionId);
|
|
1162
|
-
const current = this.processes.get(sessionId);
|
|
1163
|
-
if (!current || current.status !== "running" || !current.useStreamInput) {
|
|
1164
|
-
log$33.warn("[auto-compact] 压缩后热进程不可用,重新冷启动发送用户消息", { sessionId });
|
|
1165
|
-
await this.startPersistentProcess(config);
|
|
1166
|
-
await completionPromise;
|
|
1167
|
-
log$33.info("[perf] executePrompt 完成(compact 后冷启动)", {
|
|
1168
|
-
sessionId,
|
|
1169
|
-
totalMs: Date.now() - t0
|
|
1170
|
-
});
|
|
1171
|
-
return;
|
|
1172
|
-
}
|
|
1173
1120
|
this.sendMessage(sessionId, config.prompt);
|
|
1174
1121
|
this.resetIdleTimer(sessionId);
|
|
1175
1122
|
await completionPromise;
|
|
1176
|
-
log$
|
|
1123
|
+
log$34.info("[perf] executePrompt 完成(热进程)", {
|
|
1177
1124
|
sessionId,
|
|
1178
1125
|
totalMs: Date.now() - t0
|
|
1179
1126
|
});
|
|
1180
1127
|
return;
|
|
1181
1128
|
}
|
|
1182
1129
|
if (existing && (existing.status === "stopped" || existing.status === "crashed")) {
|
|
1183
|
-
log$
|
|
1130
|
+
log$34.info("清理已终止的旧进程,准备重启", {
|
|
1184
1131
|
sessionId,
|
|
1185
1132
|
oldStatus: existing.status
|
|
1186
1133
|
});
|
|
@@ -1188,25 +1135,25 @@ var SessionProcessManager = class {
|
|
|
1188
1135
|
this.sessionLocks.set(sessionId, sessionLock);
|
|
1189
1136
|
}
|
|
1190
1137
|
if (existing && existing.status === "running" && !existing.useStreamInput) {
|
|
1191
|
-
log$
|
|
1138
|
+
log$34.warn("停止旧的单次进程后重新启动为常驻模式", { sessionId });
|
|
1192
1139
|
await this.stopProcess(sessionId);
|
|
1193
1140
|
this.sessionLocks.set(sessionId, sessionLock);
|
|
1194
1141
|
}
|
|
1195
1142
|
const completionPromise = this.createMessageCompletionPromise(sessionId);
|
|
1196
1143
|
const tStart = Date.now();
|
|
1197
1144
|
await this.startPersistentProcess(config);
|
|
1198
|
-
log$
|
|
1145
|
+
log$34.info("[perf] 冷启动进程完成", {
|
|
1199
1146
|
sessionId,
|
|
1200
1147
|
coldStartMs: Date.now() - tStart
|
|
1201
1148
|
});
|
|
1202
1149
|
await completionPromise;
|
|
1203
|
-
log$
|
|
1150
|
+
log$34.info("[perf] executePrompt 完成(冷启动)", {
|
|
1204
1151
|
sessionId,
|
|
1205
1152
|
totalMs: Date.now() - t0
|
|
1206
1153
|
});
|
|
1207
1154
|
} finally {
|
|
1208
1155
|
if (config.transcriptMode === "ephemeral") await this.stopProcess(sessionId).catch((err) => {
|
|
1209
|
-
log$
|
|
1156
|
+
log$34.warn("ephemeral CC 进程清理失败", {
|
|
1210
1157
|
sessionId,
|
|
1211
1158
|
error: err instanceof Error ? err.message : String(err)
|
|
1212
1159
|
});
|
|
@@ -1245,13 +1192,13 @@ var SessionProcessManager = class {
|
|
|
1245
1192
|
try {
|
|
1246
1193
|
const mcpConfigPath = await mergeAndWriteMcpConfig(config.userContext && !config.userContext.isTenantIdentity ? config.userContext : null, sessionId, cwd);
|
|
1247
1194
|
args.push("--mcp-config", mcpConfigPath);
|
|
1248
|
-
log$
|
|
1195
|
+
log$34.info("MCP 配置已合并并注入到 CC 进程", {
|
|
1249
1196
|
sessionId,
|
|
1250
1197
|
userId: config.userContext?.userId,
|
|
1251
1198
|
mcpConfigPath
|
|
1252
1199
|
});
|
|
1253
1200
|
} catch (err) {
|
|
1254
|
-
log$
|
|
1201
|
+
log$34.warn("MCP 配置合并失败,CC 进程将使用全局默认配置", {
|
|
1255
1202
|
sessionId,
|
|
1256
1203
|
error: err instanceof Error ? err.message : String(err)
|
|
1257
1204
|
});
|
|
@@ -1278,12 +1225,12 @@ var SessionProcessManager = class {
|
|
|
1278
1225
|
if (larkCred?.user_access_token && typeof larkCred.user_access_token === "string") processEnv.LARK_USER_ACCESS_TOKEN = larkCred.user_access_token;
|
|
1279
1226
|
} else {
|
|
1280
1227
|
delete processEnv.LARK_USER_ACCESS_TOKEN;
|
|
1281
|
-
log$
|
|
1228
|
+
log$34.info("LARKPAL_DISABLE_USER_AUTH 已启用,跳过飞书 user_access_token 注入", {
|
|
1282
1229
|
sessionId,
|
|
1283
1230
|
userId: userContext.userId
|
|
1284
1231
|
});
|
|
1285
1232
|
}
|
|
1286
|
-
log$
|
|
1233
|
+
log$34.info("用户凭证已注入到 CC 进程环境", {
|
|
1287
1234
|
sessionId,
|
|
1288
1235
|
userId: userContext.userId,
|
|
1289
1236
|
credentialCount: Object.keys(credentialEnvVars).length,
|
|
@@ -1291,14 +1238,14 @@ var SessionProcessManager = class {
|
|
|
1291
1238
|
disableUserAuth
|
|
1292
1239
|
});
|
|
1293
1240
|
} catch (err) {
|
|
1294
|
-
log$
|
|
1241
|
+
log$34.warn("用户凭证加载失败,CC 进程将使用默认环境", {
|
|
1295
1242
|
sessionId,
|
|
1296
1243
|
userId: userContext.userId,
|
|
1297
1244
|
error: err instanceof Error ? err.message : String(err)
|
|
1298
1245
|
});
|
|
1299
1246
|
}
|
|
1300
1247
|
}
|
|
1301
|
-
log$
|
|
1248
|
+
log$34.info("启动常驻 CC 进程", {
|
|
1302
1249
|
sessionId,
|
|
1303
1250
|
claudeSessionId,
|
|
1304
1251
|
isResuming,
|
|
@@ -1338,7 +1285,7 @@ var SessionProcessManager = class {
|
|
|
1338
1285
|
if (pending) {
|
|
1339
1286
|
clearTimeout(pending.timer);
|
|
1340
1287
|
this.pendingControlRequests.delete(msg.response.request_id);
|
|
1341
|
-
log$
|
|
1288
|
+
log$34.info("收到 control_response", {
|
|
1342
1289
|
sessionId,
|
|
1343
1290
|
requestId: msg.response.request_id,
|
|
1344
1291
|
subtype: msg.response.subtype,
|
|
@@ -1355,7 +1302,7 @@ var SessionProcessManager = class {
|
|
|
1355
1302
|
if (child.stderr) createInterface({ input: child.stderr }).on("line", (line) => {
|
|
1356
1303
|
if (line.includes("no stdin data received")) return;
|
|
1357
1304
|
if (line.toLowerCase().includes("already in use")) stderrSessionInUse = true;
|
|
1358
|
-
log$
|
|
1305
|
+
log$34.warn("CC 进程 stderr", {
|
|
1359
1306
|
sessionId,
|
|
1360
1307
|
line: line.slice(0, 500)
|
|
1361
1308
|
});
|
|
@@ -1364,7 +1311,7 @@ var SessionProcessManager = class {
|
|
|
1364
1311
|
const exitCode = code ?? -1;
|
|
1365
1312
|
const exitSignal = signal ?? "none";
|
|
1366
1313
|
if (exitCode === 1 && stderrSessionInUse && !isResuming) {
|
|
1367
|
-
log$
|
|
1314
|
+
log$34.info("Session ID 已被占用,准备使用 --resume 模式重试", {
|
|
1368
1315
|
sessionId,
|
|
1369
1316
|
claudeSessionId
|
|
1370
1317
|
});
|
|
@@ -1377,21 +1324,21 @@ var SessionProcessManager = class {
|
|
|
1377
1324
|
});
|
|
1378
1325
|
return;
|
|
1379
1326
|
} catch (retryErr) {
|
|
1380
|
-
log$
|
|
1327
|
+
log$34.error("Session ID 重试失败", {
|
|
1381
1328
|
sessionId,
|
|
1382
1329
|
error: retryErr instanceof Error ? retryErr.message : String(retryErr)
|
|
1383
1330
|
});
|
|
1384
1331
|
}
|
|
1385
1332
|
}
|
|
1386
1333
|
if (exitCode === 0) {
|
|
1387
|
-
log$
|
|
1334
|
+
log$34.info("CC 常驻进程正常退出", {
|
|
1388
1335
|
sessionId,
|
|
1389
1336
|
exitCode,
|
|
1390
1337
|
signal: exitSignal
|
|
1391
1338
|
});
|
|
1392
1339
|
ccProcess.status = "stopped";
|
|
1393
1340
|
} else {
|
|
1394
|
-
log$
|
|
1341
|
+
log$34.error("CC 常驻进程异常退出", {
|
|
1395
1342
|
sessionId,
|
|
1396
1343
|
exitCode,
|
|
1397
1344
|
signal: exitSignal
|
|
@@ -1403,7 +1350,7 @@ var SessionProcessManager = class {
|
|
|
1403
1350
|
this.cleanup(sessionId);
|
|
1404
1351
|
});
|
|
1405
1352
|
child.on("error", (err) => {
|
|
1406
|
-
log$
|
|
1353
|
+
log$34.error("CC 常驻进程启动失败", {
|
|
1407
1354
|
sessionId,
|
|
1408
1355
|
error: err.message
|
|
1409
1356
|
});
|
|
@@ -1413,7 +1360,7 @@ var SessionProcessManager = class {
|
|
|
1413
1360
|
this.cleanup(sessionId);
|
|
1414
1361
|
});
|
|
1415
1362
|
ccProcess.status = "running";
|
|
1416
|
-
log$
|
|
1363
|
+
log$34.info("CC 常驻进程已启动", {
|
|
1417
1364
|
sessionId,
|
|
1418
1365
|
pid: child.pid,
|
|
1419
1366
|
isResuming
|
|
@@ -1441,7 +1388,7 @@ var SessionProcessManager = class {
|
|
|
1441
1388
|
parser.on("textDelta", (text) => {
|
|
1442
1389
|
if (!firstTokenLogged) {
|
|
1443
1390
|
firstTokenLogged = true;
|
|
1444
|
-
log$
|
|
1391
|
+
log$34.info("[perf] 首 token (text)", {
|
|
1445
1392
|
sessionId,
|
|
1446
1393
|
ttftMs: Date.now() - messageSentAt
|
|
1447
1394
|
});
|
|
@@ -1451,7 +1398,7 @@ var SessionProcessManager = class {
|
|
|
1451
1398
|
parser.on("thinkingDelta", (text) => {
|
|
1452
1399
|
if (!firstTokenLogged) {
|
|
1453
1400
|
firstTokenLogged = true;
|
|
1454
|
-
log$
|
|
1401
|
+
log$34.info("[perf] 首 token (thinking)", {
|
|
1455
1402
|
sessionId,
|
|
1456
1403
|
ttftMs: Date.now() - messageSentAt
|
|
1457
1404
|
});
|
|
@@ -1468,7 +1415,7 @@ var SessionProcessManager = class {
|
|
|
1468
1415
|
getCallbacks()?.onToolProgress?.(toolName, elapsedSeconds);
|
|
1469
1416
|
});
|
|
1470
1417
|
parser.on("turnEnd", (stopReason) => {
|
|
1471
|
-
log$
|
|
1418
|
+
log$34.info("[perf] turnEnd", {
|
|
1472
1419
|
sessionId,
|
|
1473
1420
|
stopReason,
|
|
1474
1421
|
sinceMessageMs: Date.now() - messageSentAt
|
|
@@ -1476,27 +1423,15 @@ var SessionProcessManager = class {
|
|
|
1476
1423
|
getCallbacks()?.onTurnEnd?.(stopReason);
|
|
1477
1424
|
});
|
|
1478
1425
|
parser.on("result", (result) => {
|
|
1479
|
-
log$
|
|
1426
|
+
log$34.info("[perf] result 事件", {
|
|
1480
1427
|
sessionId,
|
|
1481
1428
|
sinceMessageMs: Date.now() - messageSentAt
|
|
1482
1429
|
});
|
|
1483
|
-
this.resolveResultWaiters(sessionId);
|
|
1484
1430
|
getCallbacks()?.onResult?.(result);
|
|
1485
1431
|
this.resolveMessageCompletion(sessionId);
|
|
1486
1432
|
});
|
|
1487
|
-
parser.on("usage", (usage) => {
|
|
1488
|
-
if (usage.input_tokens <= 0) return;
|
|
1489
|
-
this.lastInputTokens.set(sessionId, usage.input_tokens);
|
|
1490
|
-
log$33.info("[auto-compact] 更新 input_tokens", {
|
|
1491
|
-
sessionId,
|
|
1492
|
-
inputTokens: usage.input_tokens,
|
|
1493
|
-
outputTokens: usage.output_tokens,
|
|
1494
|
-
threshold: AUTO_COMPACT_THRESHOLD,
|
|
1495
|
-
needsCompact: AUTO_COMPACT_THRESHOLD > 0 && usage.input_tokens > AUTO_COMPACT_THRESHOLD
|
|
1496
|
-
});
|
|
1497
|
-
});
|
|
1498
1433
|
parser.on("parseError", (error, rawLine) => {
|
|
1499
|
-
log$
|
|
1434
|
+
log$34.warn("流解析错误", {
|
|
1500
1435
|
sessionId,
|
|
1501
1436
|
error: error.message,
|
|
1502
1437
|
rawLine: rawLine.slice(0, 200)
|
|
@@ -1512,7 +1447,7 @@ var SessionProcessManager = class {
|
|
|
1512
1447
|
sendMessage(sessionId, message) {
|
|
1513
1448
|
const proc = this.processes.get(sessionId);
|
|
1514
1449
|
if (!proc || !proc.childProcess.stdin) {
|
|
1515
|
-
log$
|
|
1450
|
+
log$34.error("无法发送消息:进程不存在或 stdin 不可用", { sessionId });
|
|
1516
1451
|
return;
|
|
1517
1452
|
}
|
|
1518
1453
|
proc._resetPerfTimer?.();
|
|
@@ -1528,7 +1463,7 @@ var SessionProcessManager = class {
|
|
|
1528
1463
|
uuid: userMessageId
|
|
1529
1464
|
});
|
|
1530
1465
|
const messageLength = typeof message === "string" ? message.length : message.length;
|
|
1531
|
-
log$
|
|
1466
|
+
log$34.info("通过 stdin 发送用户消息", {
|
|
1532
1467
|
sessionId,
|
|
1533
1468
|
messageLength,
|
|
1534
1469
|
isMultimodal: Array.isArray(message),
|
|
@@ -1537,7 +1472,7 @@ var SessionProcessManager = class {
|
|
|
1537
1472
|
});
|
|
1538
1473
|
proc.childProcess.stdin.write(payload + "\n", (err) => {
|
|
1539
1474
|
if (err) {
|
|
1540
|
-
log$
|
|
1475
|
+
log$34.error("stdin 写入失败", {
|
|
1541
1476
|
sessionId,
|
|
1542
1477
|
error: err.message
|
|
1543
1478
|
});
|
|
@@ -1567,7 +1502,7 @@ var SessionProcessManager = class {
|
|
|
1567
1502
|
request_id: requestId,
|
|
1568
1503
|
request
|
|
1569
1504
|
});
|
|
1570
|
-
log$
|
|
1505
|
+
log$34.info("发送控制请求", {
|
|
1571
1506
|
sessionId,
|
|
1572
1507
|
subtype,
|
|
1573
1508
|
requestId,
|
|
@@ -1594,144 +1529,6 @@ var SessionProcessManager = class {
|
|
|
1594
1529
|
});
|
|
1595
1530
|
}
|
|
1596
1531
|
/**
|
|
1597
|
-
* 检查是否需要自动压缩,如需要则执行 /compact 命令
|
|
1598
|
-
*
|
|
1599
|
-
* 判断逻辑:
|
|
1600
|
-
* 1. AUTO_COMPACT_THRESHOLD > 0(未禁用)
|
|
1601
|
-
* 2. lastInputTokens[sessionId] > threshold
|
|
1602
|
-
*
|
|
1603
|
-
* 执行方式:通过 stdin 发送 "/compact" 作为用户消息,等待 CC 返回 result。
|
|
1604
|
-
* compact 期间:
|
|
1605
|
-
* - 通过 callbacks.onToolUseStart 通知 UI 显示"正在压缩对话历史"
|
|
1606
|
-
* - compact 产生的文本/result 被静默回调吞掉,不泄露到用户卡片
|
|
1607
|
-
* - compact 完成后通过 callbacks.onToolResult 通知 UI 压缩完成
|
|
1608
|
-
*/
|
|
1609
|
-
async autoCompactIfNeeded(sessionId) {
|
|
1610
|
-
if (AUTO_COMPACT_THRESHOLD <= 0) return;
|
|
1611
|
-
const lastTokens = this.lastInputTokens.get(sessionId);
|
|
1612
|
-
if (!lastTokens || lastTokens <= AUTO_COMPACT_THRESHOLD) return;
|
|
1613
|
-
const retryAfter = this.compactRetryAfter.get(sessionId);
|
|
1614
|
-
if (retryAfter && Date.now() < retryAfter) {
|
|
1615
|
-
log$33.info("[auto-compact] 熔断窗口内跳过压缩", {
|
|
1616
|
-
sessionId,
|
|
1617
|
-
retryAfter
|
|
1618
|
-
});
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
log$33.info("[auto-compact] 触发自动压缩", {
|
|
1622
|
-
sessionId,
|
|
1623
|
-
lastInputTokens: lastTokens,
|
|
1624
|
-
threshold: AUTO_COMPACT_THRESHOLD
|
|
1625
|
-
});
|
|
1626
|
-
const tCompact = Date.now();
|
|
1627
|
-
const realCallbacks = this.activeCallbacks.get(sessionId);
|
|
1628
|
-
const compactToolUseId = `compact-${Date.now()}`;
|
|
1629
|
-
realCallbacks?.onToolUseStart?.("_AutoCompact", {
|
|
1630
|
-
inputTokens: lastTokens,
|
|
1631
|
-
threshold: AUTO_COMPACT_THRESHOLD
|
|
1632
|
-
});
|
|
1633
|
-
this.activeCallbacks.set(sessionId, {
|
|
1634
|
-
onTextDelta: () => {},
|
|
1635
|
-
onThinkingDelta: () => {},
|
|
1636
|
-
onToolUseStart: () => {},
|
|
1637
|
-
onToolResult: () => {},
|
|
1638
|
-
onToolProgress: () => {},
|
|
1639
|
-
onTurnEnd: () => {},
|
|
1640
|
-
onResult: () => {},
|
|
1641
|
-
onError: (err) => {
|
|
1642
|
-
log$33.warn("[auto-compact] compact 期间收到错误", {
|
|
1643
|
-
sessionId,
|
|
1644
|
-
error: err.message
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
});
|
|
1648
|
-
try {
|
|
1649
|
-
await this.sendCompactCommand(sessionId);
|
|
1650
|
-
this.lastInputTokens.delete(sessionId);
|
|
1651
|
-
this.compactRetryAfter.delete(sessionId);
|
|
1652
|
-
log$33.info("[auto-compact] 压缩完成", {
|
|
1653
|
-
sessionId,
|
|
1654
|
-
durationMs: Date.now() - tCompact
|
|
1655
|
-
});
|
|
1656
|
-
} catch (err) {
|
|
1657
|
-
log$33.warn("[auto-compact] 压缩失败,继续发送原始消息", {
|
|
1658
|
-
sessionId,
|
|
1659
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1660
|
-
durationMs: Date.now() - tCompact
|
|
1661
|
-
});
|
|
1662
|
-
this.compactRetryAfter.set(sessionId, Date.now() + 1800 * 1e3);
|
|
1663
|
-
} finally {
|
|
1664
|
-
if (realCallbacks) this.activeCallbacks.set(sessionId, realCallbacks);
|
|
1665
|
-
realCallbacks?.onToolResult?.(compactToolUseId);
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
/**
|
|
1669
|
-
* 向 CC 进程发送 /compact 命令并等待完成
|
|
1670
|
-
*
|
|
1671
|
-
* /compact 是 CC 的内置 slash command,通过 stdin 作为用户消息发送。
|
|
1672
|
-
* CC 会压缩对话历史并返回 result 事件表示完成。
|
|
1673
|
-
*
|
|
1674
|
-
* 超时 120 秒(compact 可能需要处理大量历史)。
|
|
1675
|
-
*/
|
|
1676
|
-
sendCompactCommand(sessionId) {
|
|
1677
|
-
const proc = this.processes.get(sessionId);
|
|
1678
|
-
if (!proc || proc.status !== "running") return Promise.reject(/* @__PURE__ */ new Error(`会话 ${sessionId} 无运行中的进程,无法执行 compact`));
|
|
1679
|
-
if (!proc.childProcess.stdin || proc.childProcess.stdin.destroyed) return Promise.reject(/* @__PURE__ */ new Error(`会话 ${sessionId} 的 stdin 不可用,无法执行 compact`));
|
|
1680
|
-
const COMPACT_TIMEOUT_MS = 12e4;
|
|
1681
|
-
return new Promise((resolve, reject) => {
|
|
1682
|
-
const onResult = () => {
|
|
1683
|
-
clearTimeout(timer);
|
|
1684
|
-
resolve();
|
|
1685
|
-
};
|
|
1686
|
-
const timer = setTimeout(() => {
|
|
1687
|
-
this.removeResultWaiter(sessionId, onResult);
|
|
1688
|
-
this.stopProcess(sessionId);
|
|
1689
|
-
reject(/* @__PURE__ */ new Error(`/compact 超时 (${COMPACT_TIMEOUT_MS}ms)`));
|
|
1690
|
-
}, COMPACT_TIMEOUT_MS);
|
|
1691
|
-
this.addResultWaiter(sessionId, onResult);
|
|
1692
|
-
const compactPayload = JSON.stringify({
|
|
1693
|
-
type: "user",
|
|
1694
|
-
message: {
|
|
1695
|
-
role: "user",
|
|
1696
|
-
content: "/compact"
|
|
1697
|
-
},
|
|
1698
|
-
parent_tool_use_id: null,
|
|
1699
|
-
uuid: v4()
|
|
1700
|
-
});
|
|
1701
|
-
log$33.info("[auto-compact] 发送 /compact 命令", {
|
|
1702
|
-
sessionId,
|
|
1703
|
-
pid: proc.childProcess.pid
|
|
1704
|
-
});
|
|
1705
|
-
proc.childProcess.stdin.write(compactPayload + "\n", (err) => {
|
|
1706
|
-
if (err) {
|
|
1707
|
-
clearTimeout(timer);
|
|
1708
|
-
this.removeResultWaiter(sessionId, onResult);
|
|
1709
|
-
reject(/* @__PURE__ */ new Error(`/compact stdin 写入失败: ${err.message}`));
|
|
1710
|
-
}
|
|
1711
|
-
});
|
|
1712
|
-
});
|
|
1713
|
-
}
|
|
1714
|
-
addResultWaiter(sessionId, waiter) {
|
|
1715
|
-
let waiters = this.resultWaiters.get(sessionId);
|
|
1716
|
-
if (!waiters) {
|
|
1717
|
-
waiters = /* @__PURE__ */ new Set();
|
|
1718
|
-
this.resultWaiters.set(sessionId, waiters);
|
|
1719
|
-
}
|
|
1720
|
-
waiters.add(waiter);
|
|
1721
|
-
}
|
|
1722
|
-
removeResultWaiter(sessionId, waiter) {
|
|
1723
|
-
const waiters = this.resultWaiters.get(sessionId);
|
|
1724
|
-
if (!waiters) return;
|
|
1725
|
-
waiters.delete(waiter);
|
|
1726
|
-
if (waiters.size === 0) this.resultWaiters.delete(sessionId);
|
|
1727
|
-
}
|
|
1728
|
-
resolveResultWaiters(sessionId) {
|
|
1729
|
-
const waiters = this.resultWaiters.get(sessionId);
|
|
1730
|
-
if (!waiters) return;
|
|
1731
|
-
this.resultWaiters.delete(sessionId);
|
|
1732
|
-
for (const waiter of waiters) waiter();
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
1532
|
* 获取指定会话的最近一条用户消息 ID
|
|
1736
1533
|
*
|
|
1737
1534
|
* 用于构建回退按钮的 action value。
|
|
@@ -1761,16 +1558,16 @@ var SessionProcessManager = class {
|
|
|
1761
1558
|
async ensureProcessForRewind(sessionId) {
|
|
1762
1559
|
const existing = this.processes.get(sessionId);
|
|
1763
1560
|
if (existing && existing.status === "running") {
|
|
1764
|
-
log$
|
|
1561
|
+
log$34.info("ensureProcessForRewind: 进程已在运行中,直接使用", { sessionId });
|
|
1765
1562
|
return existing;
|
|
1766
1563
|
}
|
|
1767
1564
|
const cwd = this.sessionCwdCache.get(sessionId);
|
|
1768
1565
|
if (!cwd) {
|
|
1769
|
-
log$
|
|
1566
|
+
log$34.warn("ensureProcessForRewind: 无法获取会话 cwd", { sessionId });
|
|
1770
1567
|
return;
|
|
1771
1568
|
}
|
|
1772
1569
|
if (existing) this.cleanup(sessionId);
|
|
1773
|
-
log$
|
|
1570
|
+
log$34.info("ensureProcessForRewind: 启动临时进程用于 rewind", {
|
|
1774
1571
|
sessionId,
|
|
1775
1572
|
cwd
|
|
1776
1573
|
});
|
|
@@ -1781,7 +1578,7 @@ var SessionProcessManager = class {
|
|
|
1781
1578
|
}, { skipInitialMessage: true });
|
|
1782
1579
|
const proc = this.processes.get(sessionId);
|
|
1783
1580
|
if (!proc || proc.status !== "running") {
|
|
1784
|
-
log$
|
|
1581
|
+
log$34.warn("ensureProcessForRewind: 进程启动失败", {
|
|
1785
1582
|
sessionId,
|
|
1786
1583
|
status: proc?.status
|
|
1787
1584
|
});
|
|
@@ -1803,11 +1600,11 @@ var SessionProcessManager = class {
|
|
|
1803
1600
|
async stopProcess(sessionId) {
|
|
1804
1601
|
const proc = this.processes.get(sessionId);
|
|
1805
1602
|
if (!proc) {
|
|
1806
|
-
log$
|
|
1603
|
+
log$34.warn("尝试停止不存在的进程", { sessionId });
|
|
1807
1604
|
return;
|
|
1808
1605
|
}
|
|
1809
1606
|
if (proc.status === "stopped" || proc.status === "crashed") {
|
|
1810
|
-
log$
|
|
1607
|
+
log$34.info("进程已经停止,直接清理", {
|
|
1811
1608
|
sessionId,
|
|
1812
1609
|
status: proc.status
|
|
1813
1610
|
});
|
|
@@ -1815,7 +1612,7 @@ var SessionProcessManager = class {
|
|
|
1815
1612
|
return;
|
|
1816
1613
|
}
|
|
1817
1614
|
proc.status = "stopping";
|
|
1818
|
-
log$
|
|
1615
|
+
log$34.info("正在停止 CC 常驻进程", {
|
|
1819
1616
|
sessionId,
|
|
1820
1617
|
pid: proc.childProcess.pid
|
|
1821
1618
|
});
|
|
@@ -1831,7 +1628,7 @@ var SessionProcessManager = class {
|
|
|
1831
1628
|
const child = proc.childProcess;
|
|
1832
1629
|
const forceKillTimer = setTimeout(() => {
|
|
1833
1630
|
if (!child.killed) {
|
|
1834
|
-
log$
|
|
1631
|
+
log$34.warn("CC 常驻进程未在规定时间内退出,强制终止", { sessionId });
|
|
1835
1632
|
child.kill("SIGKILL");
|
|
1836
1633
|
}
|
|
1837
1634
|
}, GRACEFUL_SHUTDOWN_MS);
|
|
@@ -1851,9 +1648,9 @@ var SessionProcessManager = class {
|
|
|
1851
1648
|
async stopAll() {
|
|
1852
1649
|
const sessionIds = Array.from(this.processes.keys());
|
|
1853
1650
|
if (sessionIds.length === 0) return;
|
|
1854
|
-
log$
|
|
1651
|
+
log$34.info("正在停止所有 CC 常驻进程", { count: sessionIds.length });
|
|
1855
1652
|
await Promise.all(sessionIds.map((id) => this.stopProcess(id)));
|
|
1856
|
-
log$
|
|
1653
|
+
log$34.info("所有 CC 常驻进程已停止");
|
|
1857
1654
|
}
|
|
1858
1655
|
/**
|
|
1859
1656
|
* 重置空闲计时器
|
|
@@ -1863,7 +1660,7 @@ var SessionProcessManager = class {
|
|
|
1863
1660
|
if (existing) clearTimeout(existing);
|
|
1864
1661
|
if (IDLE_TIMEOUT_MS <= 0) return;
|
|
1865
1662
|
const timer = setTimeout(() => {
|
|
1866
|
-
log$
|
|
1663
|
+
log$34.info("CC 常驻进程空闲超时,自动停止", {
|
|
1867
1664
|
sessionId,
|
|
1868
1665
|
timeoutMs: IDLE_TIMEOUT_MS
|
|
1869
1666
|
});
|
|
@@ -1880,9 +1677,6 @@ var SessionProcessManager = class {
|
|
|
1880
1677
|
this.processes.delete(sessionId);
|
|
1881
1678
|
this.activeCallbacks.delete(sessionId);
|
|
1882
1679
|
this.lastUserMessageIds.delete(sessionId);
|
|
1883
|
-
this.lastInputTokens.delete(sessionId);
|
|
1884
|
-
this.resultWaiters.delete(sessionId);
|
|
1885
|
-
this.compactRetryAfter.delete(sessionId);
|
|
1886
1680
|
for (const [reqId, pending] of this.pendingControlRequests) if (pending.sessionId === sessionId) {
|
|
1887
1681
|
clearTimeout(pending.timer);
|
|
1888
1682
|
this.pendingControlRequests.delete(reqId);
|
|
@@ -1894,7 +1688,7 @@ var SessionProcessManager = class {
|
|
|
1894
1688
|
this.idleTimers.delete(sessionId);
|
|
1895
1689
|
}
|
|
1896
1690
|
cleanupMcpConfig(sessionId);
|
|
1897
|
-
log$
|
|
1691
|
+
log$34.debug("已清理常驻进程状态", { sessionId });
|
|
1898
1692
|
}
|
|
1899
1693
|
/**
|
|
1900
1694
|
* 将 cwd 编码为 CC 的 projects 子目录名
|
|
@@ -1923,7 +1717,7 @@ var SessionProcessManager = class {
|
|
|
1923
1717
|
const cwdEncoded = this.encodeCwdForCC(cwd);
|
|
1924
1718
|
const sessionFile = join(homedir(), ".claude", "projects", cwdEncoded, `${claudeSessionId}.jsonl`);
|
|
1925
1719
|
const exists = existsSync(sessionFile);
|
|
1926
|
-
log$
|
|
1720
|
+
log$34.debug("检查 CC session 文件", {
|
|
1927
1721
|
cwd,
|
|
1928
1722
|
cwdEncoded,
|
|
1929
1723
|
claudeSessionId,
|
|
@@ -1932,7 +1726,7 @@ var SessionProcessManager = class {
|
|
|
1932
1726
|
});
|
|
1933
1727
|
return exists;
|
|
1934
1728
|
} catch (err) {
|
|
1935
|
-
log$
|
|
1729
|
+
log$34.warn("检查 CC session 文件失败,默认为首次创建", { error: err instanceof Error ? err.message : String(err) });
|
|
1936
1730
|
return false;
|
|
1937
1731
|
}
|
|
1938
1732
|
}
|
|
@@ -1947,7 +1741,7 @@ var SessionProcessManager = class {
|
|
|
1947
1741
|
*
|
|
1948
1742
|
* 这是 LarkPal 的默认 AI 引擎适配器。
|
|
1949
1743
|
*/
|
|
1950
|
-
const log$
|
|
1744
|
+
const log$33 = larkLogger("runtime/claude-code-adapter");
|
|
1951
1745
|
var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
1952
1746
|
name = "claude-code";
|
|
1953
1747
|
processManager;
|
|
@@ -1963,7 +1757,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1963
1757
|
constructor() {
|
|
1964
1758
|
this.processManager = new SessionProcessManager();
|
|
1965
1759
|
ClaudeCodeAdapter._instance = this;
|
|
1966
|
-
log$
|
|
1760
|
+
log$33.info("ClaudeCodeAdapter 初始化完成");
|
|
1967
1761
|
}
|
|
1968
1762
|
/**
|
|
1969
1763
|
* 获取底层 SessionProcessManager 实例
|
|
@@ -1976,7 +1770,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1976
1770
|
}
|
|
1977
1771
|
async executePrompt(config, callbacks) {
|
|
1978
1772
|
this.lastRuntimeConfigs.set(config.sessionId, config);
|
|
1979
|
-
log$
|
|
1773
|
+
log$33.info("executePrompt via ClaudeCodeAdapter", {
|
|
1980
1774
|
sessionId: config.sessionId,
|
|
1981
1775
|
cwd: config.cwd,
|
|
1982
1776
|
promptLength: config.prompt.length,
|
|
@@ -1985,11 +1779,11 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1985
1779
|
await this.processManager.executePrompt(config, callbacks);
|
|
1986
1780
|
}
|
|
1987
1781
|
async stopProcess(sessionId) {
|
|
1988
|
-
log$
|
|
1782
|
+
log$33.info("stopProcess via ClaudeCodeAdapter", { sessionId });
|
|
1989
1783
|
await this.processManager.stopProcess(sessionId);
|
|
1990
1784
|
}
|
|
1991
1785
|
async stopAll() {
|
|
1992
|
-
log$
|
|
1786
|
+
log$33.info("stopAll via ClaudeCodeAdapter");
|
|
1993
1787
|
await this.processManager.stopAll();
|
|
1994
1788
|
}
|
|
1995
1789
|
getProcessInfo(sessionId) {
|
|
@@ -2025,7 +1819,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
2025
1819
|
};
|
|
2026
1820
|
//#endregion
|
|
2027
1821
|
//#region src/codex-runtime/app-server-client.ts
|
|
2028
|
-
const log$
|
|
1822
|
+
const log$32 = larkLogger("codex-runtime/app-server-client");
|
|
2029
1823
|
const DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
2030
1824
|
var CodexAppServerClient = class extends EventEmitter {
|
|
2031
1825
|
sessionId;
|
|
@@ -2056,7 +1850,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2056
1850
|
"pipe"
|
|
2057
1851
|
]
|
|
2058
1852
|
});
|
|
2059
|
-
log$
|
|
1853
|
+
log$32.info("Codex app-server 进程已启动", {
|
|
2060
1854
|
sessionId: this.sessionId,
|
|
2061
1855
|
cwd: this.cwd,
|
|
2062
1856
|
pid: this.childProcess.pid,
|
|
@@ -2135,14 +1929,14 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2135
1929
|
bindStreams() {
|
|
2136
1930
|
if (this.childProcess.stdout) createInterface({ input: this.childProcess.stdout }).on("line", (line) => this.handleLine(line));
|
|
2137
1931
|
if (this.childProcess.stderr) createInterface({ input: this.childProcess.stderr }).on("line", (line) => {
|
|
2138
|
-
log$
|
|
1932
|
+
log$32.warn("Codex app-server stderr", {
|
|
2139
1933
|
sessionId: this.sessionId,
|
|
2140
1934
|
line: line.slice(0, 500)
|
|
2141
1935
|
});
|
|
2142
1936
|
this.emit("stderr", line);
|
|
2143
1937
|
});
|
|
2144
1938
|
this.childProcess.on("error", (error) => {
|
|
2145
|
-
log$
|
|
1939
|
+
log$32.error("Codex app-server 进程错误", {
|
|
2146
1940
|
sessionId: this.sessionId,
|
|
2147
1941
|
error: error.message
|
|
2148
1942
|
});
|
|
@@ -2151,7 +1945,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2151
1945
|
});
|
|
2152
1946
|
this.childProcess.on("close", (code, signal) => {
|
|
2153
1947
|
this.closed = true;
|
|
2154
|
-
log$
|
|
1948
|
+
log$32.info("Codex app-server 进程退出", {
|
|
2155
1949
|
sessionId: this.sessionId,
|
|
2156
1950
|
code,
|
|
2157
1951
|
signal
|
|
@@ -2168,7 +1962,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2168
1962
|
message = JSON.parse(trimmed);
|
|
2169
1963
|
} catch (err) {
|
|
2170
1964
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
2171
|
-
log$
|
|
1965
|
+
log$32.warn("Codex app-server JSONL 解析失败", {
|
|
2172
1966
|
sessionId: this.sessionId,
|
|
2173
1967
|
error: error.message,
|
|
2174
1968
|
line: trimmed.slice(0, 200)
|
|
@@ -2188,7 +1982,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2188
1982
|
}
|
|
2189
1983
|
handleResponse(message) {
|
|
2190
1984
|
if (message.id == null) {
|
|
2191
|
-
log$
|
|
1985
|
+
log$32.warn("Codex app-server 返回无 id 错误响应", {
|
|
2192
1986
|
sessionId: this.sessionId,
|
|
2193
1987
|
message
|
|
2194
1988
|
});
|
|
@@ -2197,7 +1991,7 @@ var CodexAppServerClient = class extends EventEmitter {
|
|
|
2197
1991
|
const idKey = String(message.id);
|
|
2198
1992
|
const pending = this.pending.get(idKey);
|
|
2199
1993
|
if (!pending) {
|
|
2200
|
-
log$
|
|
1994
|
+
log$32.warn("Codex app-server 返回未知请求响应", {
|
|
2201
1995
|
sessionId: this.sessionId,
|
|
2202
1996
|
id: message.id
|
|
2203
1997
|
});
|
|
@@ -2243,7 +2037,7 @@ function isNotification(message) {
|
|
|
2243
2037
|
}
|
|
2244
2038
|
//#endregion
|
|
2245
2039
|
//#region src/runtime/codex-app-server-adapter.ts
|
|
2246
|
-
const log$
|
|
2040
|
+
const log$31 = larkLogger("runtime/codex-app-server-adapter");
|
|
2247
2041
|
const STATE_PATH = join(homedir(), ".larkpal", "runtime", "codex-app-server-threads.json");
|
|
2248
2042
|
const REQUEST_TIMEOUT_MS = 12e4;
|
|
2249
2043
|
var CodexAppServerAdapter = class {
|
|
@@ -2269,14 +2063,14 @@ var CodexAppServerAdapter = class {
|
|
|
2269
2063
|
extraArgs: params.extraArgs,
|
|
2270
2064
|
requestTimeoutMs: REQUEST_TIMEOUT_MS
|
|
2271
2065
|
}));
|
|
2272
|
-
log$
|
|
2066
|
+
log$31.info("CodexAppServerAdapter 初始化完成", { statePath: this.statePath });
|
|
2273
2067
|
}
|
|
2274
2068
|
async executePrompt(config, callbacks) {
|
|
2275
2069
|
await this.loadState();
|
|
2276
2070
|
const { sessionId } = config;
|
|
2277
2071
|
const prevLock = this.sessionLocks.get(sessionId);
|
|
2278
2072
|
if (prevLock) {
|
|
2279
|
-
log$
|
|
2073
|
+
log$31.info("等待上一条 Codex 消息处理完成", { sessionId });
|
|
2280
2074
|
await prevLock;
|
|
2281
2075
|
}
|
|
2282
2076
|
const completion = this.createCompletionPromise(sessionId);
|
|
@@ -2307,7 +2101,7 @@ var CodexAppServerAdapter = class {
|
|
|
2307
2101
|
if (config.transcriptMode === "ephemeral") {
|
|
2308
2102
|
const record = this.processes.get(sessionId);
|
|
2309
2103
|
if (record) await this.closeRecord(sessionId, record).catch((err) => {
|
|
2310
|
-
log$
|
|
2104
|
+
log$31.warn("ephemeral Codex app-server session 清理失败", {
|
|
2311
2105
|
sessionId,
|
|
2312
2106
|
error: err instanceof Error ? err.message : String(err)
|
|
2313
2107
|
});
|
|
@@ -2322,12 +2116,12 @@ var CodexAppServerAdapter = class {
|
|
|
2322
2116
|
async stopProcess(sessionId) {
|
|
2323
2117
|
const record = this.processes.get(sessionId);
|
|
2324
2118
|
if (!record) {
|
|
2325
|
-
log$
|
|
2119
|
+
log$31.warn("尝试停止不存在的 Codex app-server session", { sessionId });
|
|
2326
2120
|
return;
|
|
2327
2121
|
}
|
|
2328
2122
|
if (this.busySessions.has(sessionId) && record.process.currentTurnId) {
|
|
2329
2123
|
this.abortedSessions.add(sessionId);
|
|
2330
|
-
log$
|
|
2124
|
+
log$31.info("发送 Codex turn/interrupt", {
|
|
2331
2125
|
sessionId,
|
|
2332
2126
|
threadId: record.process.threadId,
|
|
2333
2127
|
turnId: record.process.currentTurnId
|
|
@@ -2414,7 +2208,7 @@ var CodexAppServerAdapter = class {
|
|
|
2414
2208
|
if (!isEphemeral) await this.setThreadId(config.sessionId, resumed.thread.id, config.cwd);
|
|
2415
2209
|
return;
|
|
2416
2210
|
} catch (err) {
|
|
2417
|
-
log$
|
|
2211
|
+
log$31.warn("Codex thread/resume 失败,将新建 thread", {
|
|
2418
2212
|
sessionId: config.sessionId,
|
|
2419
2213
|
threadId: storedThreadId,
|
|
2420
2214
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2480,7 +2274,7 @@ var CodexAppServerAdapter = class {
|
|
|
2480
2274
|
this.handleErrorNotification(sessionId, message.params);
|
|
2481
2275
|
break;
|
|
2482
2276
|
default:
|
|
2483
|
-
log$
|
|
2277
|
+
log$31.debug("忽略 Codex app-server notification", {
|
|
2484
2278
|
sessionId,
|
|
2485
2279
|
method: message.method
|
|
2486
2280
|
});
|
|
@@ -2508,7 +2302,7 @@ var CodexAppServerAdapter = class {
|
|
|
2508
2302
|
handleMcpProgress(sessionId, params) {
|
|
2509
2303
|
const toolName = this.lookupToolName(sessionId, params.itemId) ?? "mcp";
|
|
2510
2304
|
this.activeCallbacks.get(sessionId)?.onToolProgress?.(toolName, 0);
|
|
2511
|
-
log$
|
|
2305
|
+
log$31.debug("Codex MCP tool progress", {
|
|
2512
2306
|
sessionId,
|
|
2513
2307
|
itemId: params.itemId,
|
|
2514
2308
|
message: params.message
|
|
@@ -2538,7 +2332,7 @@ var CodexAppServerAdapter = class {
|
|
|
2538
2332
|
this.resolveCompletion(sessionId);
|
|
2539
2333
|
}
|
|
2540
2334
|
async handleServerRequest(client, request) {
|
|
2541
|
-
log$
|
|
2335
|
+
log$31.warn("Codex app-server server request 默认拒绝", {
|
|
2542
2336
|
method: request.method,
|
|
2543
2337
|
id: request.id
|
|
2544
2338
|
});
|
|
@@ -2704,10 +2498,10 @@ var CodexAppServerAdapter = class {
|
|
|
2704
2498
|
];
|
|
2705
2499
|
}
|
|
2706
2500
|
logUnsupportedConfig(config) {
|
|
2707
|
-
if (config.llmConfig) log$
|
|
2708
|
-
if (config.maxBudgetUsd != null) log$
|
|
2709
|
-
if (config.maxTurns != null) log$
|
|
2710
|
-
if (config.policy?.allowedTools || config.policy?.disallowedTools) log$
|
|
2501
|
+
if (config.llmConfig) log$31.warn("Codex app-server v1 暂不支持 per-request llmConfig,已忽略", { sessionId: config.sessionId });
|
|
2502
|
+
if (config.maxBudgetUsd != null) log$31.warn("Codex app-server v1 暂不支持 maxBudgetUsd,已忽略", { sessionId: config.sessionId });
|
|
2503
|
+
if (config.maxTurns != null) log$31.warn("Codex app-server v1 暂不支持 maxTurns,已忽略", { sessionId: config.sessionId });
|
|
2504
|
+
if (config.policy?.allowedTools || config.policy?.disallowedTools) log$31.warn("Codex app-server v1 暂不强制执行 tool allow/deny policy,已忽略", { sessionId: config.sessionId });
|
|
2711
2505
|
}
|
|
2712
2506
|
createCompletionPromise(sessionId) {
|
|
2713
2507
|
return new Promise((resolve) => {
|
|
@@ -2723,7 +2517,7 @@ var CodexAppServerAdapter = class {
|
|
|
2723
2517
|
async closeRecord(sessionId, record) {
|
|
2724
2518
|
record.process.status = "stopping";
|
|
2725
2519
|
if (record.process.threadId) await record.client.request("thread/unsubscribe", { threadId: record.process.threadId }).catch((err) => {
|
|
2726
|
-
log$
|
|
2520
|
+
log$31.warn("Codex thread/unsubscribe 失败", {
|
|
2727
2521
|
sessionId,
|
|
2728
2522
|
threadId: record.process.threadId,
|
|
2729
2523
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2756,7 +2550,7 @@ var CodexAppServerAdapter = class {
|
|
|
2756
2550
|
const parsed = JSON.parse(raw);
|
|
2757
2551
|
for (const [sessionId, entry] of Object.entries(parsed.sessions ?? {})) if (entry.threadId) this.threadIds.set(sessionId, entry.threadId);
|
|
2758
2552
|
} catch (err) {
|
|
2759
|
-
log$
|
|
2553
|
+
log$31.warn("Codex thread state 加载失败,将从空状态继续", {
|
|
2760
2554
|
path: this.statePath,
|
|
2761
2555
|
error: err instanceof Error ? err.message : String(err)
|
|
2762
2556
|
});
|
|
@@ -2824,18 +2618,18 @@ function getLarkpalMcpServerCommand() {
|
|
|
2824
2618
|
*
|
|
2825
2619
|
* 仅当 LARKPAL_RUNTIME=larkpal-agent 时使用。
|
|
2826
2620
|
*/
|
|
2827
|
-
const log$
|
|
2621
|
+
const log$30 = larkLogger("runtime/larkpal-agent-factory");
|
|
2828
2622
|
const AGENT_MD_PATH = join(homedir(), ".claude", "CLAUDE.md");
|
|
2829
2623
|
async function loadSystemPrompt() {
|
|
2830
2624
|
try {
|
|
2831
2625
|
const content = await readFile(AGENT_MD_PATH, "utf-8");
|
|
2832
|
-
log$
|
|
2626
|
+
log$30.info("systemPrompt 加载成功", {
|
|
2833
2627
|
path: AGENT_MD_PATH,
|
|
2834
2628
|
length: content.length
|
|
2835
2629
|
});
|
|
2836
2630
|
return content;
|
|
2837
2631
|
} catch {
|
|
2838
|
-
log$
|
|
2632
|
+
log$30.warn("systemPrompt 文件不存在,使用默认", { path: AGENT_MD_PATH });
|
|
2839
2633
|
return "你是一个 AI 助手,帮助用户完成飞书知识管理任务。";
|
|
2840
2634
|
}
|
|
2841
2635
|
}
|
|
@@ -2951,7 +2745,7 @@ async function createLarkpalAgentAdapter() {
|
|
|
2951
2745
|
const scenarioManifestDirs = loadScenarioManifestDirs();
|
|
2952
2746
|
const scenarioProviderConfig = loadScenarioProviderConfig(scenarioManifestDirs);
|
|
2953
2747
|
const mcpTimeoutMs = loadAgentMcpTimeoutMs();
|
|
2954
|
-
log$
|
|
2748
|
+
log$30.info("创建 LarkpalAgentAdapter", {
|
|
2955
2749
|
baseURL: llm.baseURL,
|
|
2956
2750
|
model: llm.defaultModel,
|
|
2957
2751
|
maxTurns: defaultMaxTurns,
|
|
@@ -2967,26 +2761,30 @@ async function createLarkpalAgentAdapter() {
|
|
|
2967
2761
|
defaultMaxTurns,
|
|
2968
2762
|
registry,
|
|
2969
2763
|
scenarioManifestDirs,
|
|
2970
|
-
scenarioProviderConfig
|
|
2764
|
+
scenarioProviderConfig,
|
|
2765
|
+
visualRuntime: {
|
|
2766
|
+
maxBatchSize: parseInt(process.env.LARKPAL_AGENT_VISUAL_MAX_BATCH_SIZE || "3", 10),
|
|
2767
|
+
allowedDirs: [process.env.LARK_CLI_WORKDIR || "/tmp/ai-knowledge-lark-cli", "/tmp"].filter(Boolean)
|
|
2768
|
+
}
|
|
2971
2769
|
});
|
|
2972
2770
|
const mcpServerUrl = process.env.LARKPAL_AGENT_MCP_SERVER_URL;
|
|
2973
2771
|
if (mcpServerUrl) try {
|
|
2974
2772
|
const toolCount = await registry.connectMcpServer(mcpServerUrl, { timeoutMs: mcpTimeoutMs });
|
|
2975
2773
|
const tools = registry.getAll();
|
|
2976
2774
|
adapter.registerTools(tools);
|
|
2977
|
-
log$
|
|
2775
|
+
log$30.info("MCP 工具注册完成", {
|
|
2978
2776
|
serverUrl: mcpServerUrl,
|
|
2979
2777
|
timeoutMs: mcpTimeoutMs,
|
|
2980
2778
|
toolCount,
|
|
2981
2779
|
toolNames: tools.map((t) => t.name)
|
|
2982
2780
|
});
|
|
2983
2781
|
} catch (err) {
|
|
2984
|
-
log$
|
|
2782
|
+
log$30.error("MCP Server 连接失败,Agent 将无法使用远程工具", {
|
|
2985
2783
|
serverUrl: mcpServerUrl,
|
|
2986
2784
|
error: err?.message
|
|
2987
2785
|
});
|
|
2988
2786
|
}
|
|
2989
|
-
else log$
|
|
2787
|
+
else log$30.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
|
|
2990
2788
|
return adapter;
|
|
2991
2789
|
}
|
|
2992
2790
|
//#endregion
|
|
@@ -3323,6 +3121,7 @@ function createChatAuthRouter() {
|
|
|
3323
3121
|
//#endregion
|
|
3324
3122
|
//#region src/gateway/agent-context.ts
|
|
3325
3123
|
const SENSITIVE_KEY_PATTERN = /(authorization|cookie|token|api[-_]?key|secret|password|credential)/i;
|
|
3124
|
+
const SAFE_KEY_PATTERN = /^(file[-_]?token|drive[-_]?file[-_]?token|doc[-_]?token|sheet[-_]?token|wiki[-_]?token|node[-_]?token)$/i;
|
|
3326
3125
|
const MAX_SUMMARY_STRING_LENGTH = 200;
|
|
3327
3126
|
const PROTECTED_CONTEXT_HEADERS = new Set([
|
|
3328
3127
|
"x-tenant-key",
|
|
@@ -3445,7 +3244,7 @@ function summarizeForAudit(value) {
|
|
|
3445
3244
|
};
|
|
3446
3245
|
if (typeof value === "object") {
|
|
3447
3246
|
const result = {};
|
|
3448
|
-
for (const [key, entry] of Object.entries(value)) if (SENSITIVE_KEY_PATTERN.test(key)) result[key] = "[redacted]";
|
|
3247
|
+
for (const [key, entry] of Object.entries(value)) if (SENSITIVE_KEY_PATTERN.test(key) && !SAFE_KEY_PATTERN.test(key)) result[key] = "[redacted]";
|
|
3449
3248
|
else result[key] = summarizeForAudit(entry);
|
|
3450
3249
|
return result;
|
|
3451
3250
|
}
|
|
@@ -3551,7 +3350,8 @@ function buildExecutionPolicy(policy, limits) {
|
|
|
3551
3350
|
maxTurns: limits.maxTurns ?? policy?.maxTurns,
|
|
3552
3351
|
maxToolCalls: policy?.maxToolCalls,
|
|
3553
3352
|
maxRuntimeMs: policy?.maxRuntimeMs,
|
|
3554
|
-
maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd
|
|
3353
|
+
maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd,
|
|
3354
|
+
turnPolicy: policy?.turnPolicy
|
|
3555
3355
|
};
|
|
3556
3356
|
}
|
|
3557
3357
|
function buildUserContext$1(identity) {
|
|
@@ -3619,7 +3419,7 @@ function summarizeString(value) {
|
|
|
3619
3419
|
* POST /api/chat/sessions — 创建新会话
|
|
3620
3420
|
* DELETE /api/chat/sessions/:id — 删除会话
|
|
3621
3421
|
*/
|
|
3622
|
-
const log$
|
|
3422
|
+
const log$29 = larkLogger("chat/stream");
|
|
3623
3423
|
const chatRunsBySession = /* @__PURE__ */ new Map();
|
|
3624
3424
|
/** 统一写 SSE 格式数据到响应流 */
|
|
3625
3425
|
function sendSSE(res, event, data) {
|
|
@@ -3630,7 +3430,7 @@ function isChatSessionRunning(sessionId, processManager) {
|
|
|
3630
3430
|
return getActiveChatRun(sessionId) != null || (processManager.isSessionBusy?.(sessionId) ?? false);
|
|
3631
3431
|
}
|
|
3632
3432
|
function logChatAudit(event) {
|
|
3633
|
-
log$
|
|
3433
|
+
log$29.info("[stream] Agent audit event", { ...event });
|
|
3634
3434
|
}
|
|
3635
3435
|
function prepareSSE(res) {
|
|
3636
3436
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -3655,7 +3455,8 @@ function serializeChatRun(run) {
|
|
|
3655
3455
|
completedAt: run.completedAt,
|
|
3656
3456
|
finalStatus: run.finalStatus,
|
|
3657
3457
|
error: run.error,
|
|
3658
|
-
textLength: run.text.length
|
|
3458
|
+
textLength: run.text.length,
|
|
3459
|
+
finalText: run.status === "running" ? void 0 : run.text || void 0
|
|
3659
3460
|
};
|
|
3660
3461
|
}
|
|
3661
3462
|
function isRunVisibleToUser(run, params) {
|
|
@@ -3665,7 +3466,7 @@ function addRunClient(req, res, run) {
|
|
|
3665
3466
|
run.clients.add(res);
|
|
3666
3467
|
req.on("close", () => {
|
|
3667
3468
|
run.clients.delete(res);
|
|
3668
|
-
log$
|
|
3469
|
+
log$29.info("[stream] 客户端断开连接", {
|
|
3669
3470
|
sessionId: run.sessionId,
|
|
3670
3471
|
runId: run.runId
|
|
3671
3472
|
});
|
|
@@ -3674,12 +3475,26 @@ function addRunClient(req, res, run) {
|
|
|
3674
3475
|
function sendRunStatus(res, run) {
|
|
3675
3476
|
sendSSE(res, "run-status", serializeChatRun(run));
|
|
3676
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
|
+
}
|
|
3677
3492
|
function broadcastRunEvent(run, event, data) {
|
|
3678
3493
|
for (const client of Array.from(run.clients)) try {
|
|
3679
3494
|
sendSSE(client, event, data);
|
|
3680
3495
|
} catch (err) {
|
|
3681
3496
|
run.clients.delete(client);
|
|
3682
|
-
log$
|
|
3497
|
+
log$29.warn("[stream] SSE 写入失败,移除客户端", {
|
|
3683
3498
|
sessionId: run.sessionId,
|
|
3684
3499
|
runId: run.runId,
|
|
3685
3500
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3699,7 +3514,7 @@ function queueRunMessage(run, messageStore, message, label) {
|
|
|
3699
3514
|
run.persistQueue = run.persistQueue.catch(() => void 0).then(async () => {
|
|
3700
3515
|
await messageStore.appendMessage(message);
|
|
3701
3516
|
}).catch((err) => {
|
|
3702
|
-
log$
|
|
3517
|
+
log$29.error(label, {
|
|
3703
3518
|
sessionId: run.sessionId,
|
|
3704
3519
|
runId: run.runId,
|
|
3705
3520
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -3764,7 +3579,7 @@ function createChatRouter(config) {
|
|
|
3764
3579
|
addRunClient(req, res, activeRun);
|
|
3765
3580
|
sendSSE(res, "session", { sessionId });
|
|
3766
3581
|
sendRunStatus(res, activeRun);
|
|
3767
|
-
log$
|
|
3582
|
+
log$29.info("[stream] 已重新连接到运行中的 run", {
|
|
3768
3583
|
sessionId,
|
|
3769
3584
|
runId: activeRun.runId
|
|
3770
3585
|
});
|
|
@@ -3777,7 +3592,7 @@ function createChatRouter(config) {
|
|
|
3777
3592
|
const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
|
|
3778
3593
|
const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
|
|
3779
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);
|
|
3780
|
-
log$
|
|
3595
|
+
log$29.info("[stream] 收到流式对话请求", {
|
|
3781
3596
|
userId,
|
|
3782
3597
|
sessionId,
|
|
3783
3598
|
requestId,
|
|
@@ -3794,13 +3609,13 @@ function createChatRouter(config) {
|
|
|
3794
3609
|
tenantKey,
|
|
3795
3610
|
channel: "web"
|
|
3796
3611
|
})).id;
|
|
3797
|
-
log$
|
|
3612
|
+
log$29.info("[stream] 自动创建新会话", {
|
|
3798
3613
|
userId: openId,
|
|
3799
3614
|
sessionId
|
|
3800
3615
|
});
|
|
3801
3616
|
}
|
|
3802
3617
|
if (isChatSessionRunning(sessionId, processManager)) {
|
|
3803
|
-
log$
|
|
3618
|
+
log$29.warn("[stream] session 正在被非 Web Chat run 占用", {
|
|
3804
3619
|
tenantKey,
|
|
3805
3620
|
userId,
|
|
3806
3621
|
sessionId
|
|
@@ -3811,6 +3626,7 @@ function createChatRouter(config) {
|
|
|
3811
3626
|
res.end();
|
|
3812
3627
|
return;
|
|
3813
3628
|
}
|
|
3629
|
+
const runtimePrompt = enrichPromptWithSourceArtifacts(body.prompt, sourceArtifacts);
|
|
3814
3630
|
const runtimeConfig = buildAgentRuntimeConfig({
|
|
3815
3631
|
requestId,
|
|
3816
3632
|
traceId,
|
|
@@ -3818,7 +3634,7 @@ function createChatRouter(config) {
|
|
|
3818
3634
|
conversationId: sessionId,
|
|
3819
3635
|
entrypoint: "chat",
|
|
3820
3636
|
scenarioId,
|
|
3821
|
-
prompt:
|
|
3637
|
+
prompt: runtimePrompt,
|
|
3822
3638
|
maxTurns: body.options?.maxTurns,
|
|
3823
3639
|
maxBudgetUsd: body.options?.maxBudgetUsd,
|
|
3824
3640
|
model: process.env.CLAUDE_MODEL || void 0,
|
|
@@ -3898,7 +3714,7 @@ function createChatRouter(config) {
|
|
|
3898
3714
|
});
|
|
3899
3715
|
} catch (err) {
|
|
3900
3716
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
3901
|
-
log$
|
|
3717
|
+
log$29.error("[stream] 保存用户消息失败", {
|
|
3902
3718
|
sessionId,
|
|
3903
3719
|
error: errorMessage
|
|
3904
3720
|
});
|
|
@@ -3911,9 +3727,16 @@ function createChatRouter(config) {
|
|
|
3911
3727
|
addRunClient(req, res, run);
|
|
3912
3728
|
sendSSE(res, "session", { sessionId });
|
|
3913
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);
|
|
3914
3737
|
const handleBlockedEvent = (event) => {
|
|
3915
3738
|
const sanitized = sanitizeBlockedEvent(event);
|
|
3916
|
-
log$
|
|
3739
|
+
log$29.warn("[stream] Agent blocked event", { ...sanitized });
|
|
3917
3740
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
|
|
3918
3741
|
toolName: sanitized.name,
|
|
3919
3742
|
inputSummary: sanitized.inputSummary,
|
|
@@ -3944,6 +3767,8 @@ function createChatRouter(config) {
|
|
|
3944
3767
|
};
|
|
3945
3768
|
const handleRuntimeEvent = (event) => {
|
|
3946
3769
|
const eventSummary = summarizeForAudit(event);
|
|
3770
|
+
const runtimeFinalText = extractRuntimeEventFinalText(event);
|
|
3771
|
+
if (runtimeFinalText && !run.text.trim()) run.text = runtimeFinalText;
|
|
3947
3772
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
|
|
3948
3773
|
resultSummary: eventSummary,
|
|
3949
3774
|
metadata: buildRuntimeEventAuditMetadata(event)
|
|
@@ -4042,7 +3867,8 @@ function createChatRouter(config) {
|
|
|
4042
3867
|
broadcastRunEvent(run, "turn-end", { stopReason });
|
|
4043
3868
|
},
|
|
4044
3869
|
onResult: async (result) => {
|
|
4045
|
-
const finalText = run.text
|
|
3870
|
+
const finalText = run.text.trim() ? run.text : result.result || "";
|
|
3871
|
+
run.text = finalText;
|
|
4046
3872
|
await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
4047
3873
|
await queueRunMessage(run, messageStore, {
|
|
4048
3874
|
sessionId,
|
|
@@ -4070,8 +3896,9 @@ function createChatRouter(config) {
|
|
|
4070
3896
|
totalCostUsd: result.totalCostUsd,
|
|
4071
3897
|
numTurns: result.numTurns
|
|
4072
3898
|
});
|
|
3899
|
+
clearInterval(heartbeatTimer);
|
|
4073
3900
|
endRunClients(run);
|
|
4074
|
-
log$
|
|
3901
|
+
log$29.info("[stream] 对话完成", {
|
|
4075
3902
|
sessionId,
|
|
4076
3903
|
runId: run.runId,
|
|
4077
3904
|
result: result.subtype,
|
|
@@ -4093,7 +3920,7 @@ function createChatRouter(config) {
|
|
|
4093
3920
|
}));
|
|
4094
3921
|
},
|
|
4095
3922
|
onError: async (error) => {
|
|
4096
|
-
log$
|
|
3923
|
+
log$29.error("[stream] Runtime 执行出错", {
|
|
4097
3924
|
sessionId,
|
|
4098
3925
|
runId: run.runId,
|
|
4099
3926
|
error: error.message
|
|
@@ -4130,6 +3957,7 @@ function createChatRouter(config) {
|
|
|
4130
3957
|
runId: run.runId,
|
|
4131
3958
|
message: error.message
|
|
4132
3959
|
});
|
|
3960
|
+
clearInterval(heartbeatTimer);
|
|
4133
3961
|
endRunClients(run);
|
|
4134
3962
|
},
|
|
4135
3963
|
onAuditEvent: (event) => {
|
|
@@ -4179,7 +4007,7 @@ function createChatRouter(config) {
|
|
|
4179
4007
|
scenarioId: runtimeConfig.runContext?.scenarioId,
|
|
4180
4008
|
...buildRunInputAuditMetadata(runtimeConfig)
|
|
4181
4009
|
} }));
|
|
4182
|
-
log$
|
|
4010
|
+
log$29.info("[stream] 开始执行 prompt", {
|
|
4183
4011
|
sessionId,
|
|
4184
4012
|
runId: run.runId,
|
|
4185
4013
|
cwd: runtimeConfig.cwd
|
|
@@ -4187,7 +4015,7 @@ function createChatRouter(config) {
|
|
|
4187
4015
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
4188
4016
|
if (run.status === "running") {
|
|
4189
4017
|
const errorMessage = "Runtime completed without a final result event";
|
|
4190
|
-
log$
|
|
4018
|
+
log$29.error("[stream] executePrompt 未返回终态回调", {
|
|
4191
4019
|
sessionId,
|
|
4192
4020
|
runId: run.runId
|
|
4193
4021
|
});
|
|
@@ -4199,11 +4027,12 @@ function createChatRouter(config) {
|
|
|
4199
4027
|
runId: run.runId,
|
|
4200
4028
|
message: errorMessage
|
|
4201
4029
|
});
|
|
4030
|
+
clearInterval(heartbeatTimer);
|
|
4202
4031
|
endRunClients(run);
|
|
4203
4032
|
}
|
|
4204
4033
|
} catch (err) {
|
|
4205
4034
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4206
|
-
log$
|
|
4035
|
+
log$29.error("[stream] executePrompt 异常", {
|
|
4207
4036
|
sessionId,
|
|
4208
4037
|
runId: run.runId,
|
|
4209
4038
|
error: errorMessage
|
|
@@ -4220,7 +4049,7 @@ function createChatRouter(config) {
|
|
|
4220
4049
|
}
|
|
4221
4050
|
const cursor = req.query.cursor;
|
|
4222
4051
|
const limit = parseInt(req.query.limit, 10) || 50;
|
|
4223
|
-
log$
|
|
4052
|
+
log$29.info("[history] 查询会话历史", {
|
|
4224
4053
|
userId: chatUser.userId,
|
|
4225
4054
|
sessionId,
|
|
4226
4055
|
cursor,
|
|
@@ -4245,7 +4074,7 @@ function createChatRouter(config) {
|
|
|
4245
4074
|
});
|
|
4246
4075
|
} catch (err) {
|
|
4247
4076
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4248
|
-
log$
|
|
4077
|
+
log$29.error("[history] 查询历史消息失败", {
|
|
4249
4078
|
sessionId,
|
|
4250
4079
|
error: errorMessage
|
|
4251
4080
|
});
|
|
@@ -4280,13 +4109,13 @@ function createChatRouter(config) {
|
|
|
4280
4109
|
});
|
|
4281
4110
|
router.get("/api/chat/sessions", chatAuthMiddleware, async (_req, res) => {
|
|
4282
4111
|
const chatUser = res.locals.chatUser;
|
|
4283
|
-
log$
|
|
4112
|
+
log$29.info("[sessions] 列出用户会话", { userId: chatUser.userId });
|
|
4284
4113
|
try {
|
|
4285
4114
|
const sessions = await messageStore.listSessions(chatUser.openId);
|
|
4286
4115
|
res.json({ sessions });
|
|
4287
4116
|
} catch (err) {
|
|
4288
4117
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4289
|
-
log$
|
|
4118
|
+
log$29.error("[sessions] 列出会话失败", {
|
|
4290
4119
|
userId: chatUser.userId,
|
|
4291
4120
|
error: errorMessage
|
|
4292
4121
|
});
|
|
@@ -4296,7 +4125,7 @@ function createChatRouter(config) {
|
|
|
4296
4125
|
router.post("/api/chat/sessions", chatAuthMiddleware, async (req, res) => {
|
|
4297
4126
|
const chatUser = res.locals.chatUser;
|
|
4298
4127
|
const body = req.body;
|
|
4299
|
-
log$
|
|
4128
|
+
log$29.info("[sessions] 创建新会话", {
|
|
4300
4129
|
userId: chatUser.userId,
|
|
4301
4130
|
title: body.title
|
|
4302
4131
|
});
|
|
@@ -4311,7 +4140,7 @@ function createChatRouter(config) {
|
|
|
4311
4140
|
res.json({ session });
|
|
4312
4141
|
} catch (err) {
|
|
4313
4142
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4314
|
-
log$
|
|
4143
|
+
log$29.error("[sessions] 创建会话失败", {
|
|
4315
4144
|
userId: chatUser.userId,
|
|
4316
4145
|
error: errorMessage
|
|
4317
4146
|
});
|
|
@@ -4321,7 +4150,7 @@ function createChatRouter(config) {
|
|
|
4321
4150
|
router.delete("/api/chat/sessions/:id", chatAuthMiddleware, async (req, res) => {
|
|
4322
4151
|
const chatUser = res.locals.chatUser;
|
|
4323
4152
|
const sessionId = String(req.params.id);
|
|
4324
|
-
log$
|
|
4153
|
+
log$29.info("[sessions] 删除会话", {
|
|
4325
4154
|
userId: chatUser.userId,
|
|
4326
4155
|
sessionId
|
|
4327
4156
|
});
|
|
@@ -4331,7 +4160,7 @@ function createChatRouter(config) {
|
|
|
4331
4160
|
res.json({ success: true });
|
|
4332
4161
|
} catch (err) {
|
|
4333
4162
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
4334
|
-
log$
|
|
4163
|
+
log$29.error("[sessions] 删除会话失败", {
|
|
4335
4164
|
sessionId,
|
|
4336
4165
|
error: errorMessage
|
|
4337
4166
|
});
|
|
@@ -4340,6 +4169,34 @@ function createChatRouter(config) {
|
|
|
4340
4169
|
});
|
|
4341
4170
|
return router;
|
|
4342
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
|
+
}
|
|
4343
4200
|
//#endregion
|
|
4344
4201
|
//#region src/core/app-info-sync.ts
|
|
4345
4202
|
/**
|
|
@@ -4357,7 +4214,7 @@ function createChatRouter(config) {
|
|
|
4357
4214
|
* - GET /open-apis/application/v6/applications/me?lang=zh_cn(需要 application:application:self_manage 权限,返回完整信息)
|
|
4358
4215
|
* 如果后者无权限则 fallback 到前者
|
|
4359
4216
|
*/
|
|
4360
|
-
const log$
|
|
4217
|
+
const log$28 = larkLogger("core/app-info-sync");
|
|
4361
4218
|
/**
|
|
4362
4219
|
* 从飞书获取应用信息
|
|
4363
4220
|
*
|
|
@@ -4368,7 +4225,7 @@ async function fetchAppInfo(credentials) {
|
|
|
4368
4225
|
const { appId, appSecret } = credentials;
|
|
4369
4226
|
const token = await getTenantAccessToken(appId, appSecret);
|
|
4370
4227
|
if (!token) {
|
|
4371
|
-
log$
|
|
4228
|
+
log$28.error("获取 tenant_access_token 失败,无法同步应用信息");
|
|
4372
4229
|
return null;
|
|
4373
4230
|
}
|
|
4374
4231
|
const fullInfo = await fetchFromApplicationApi(token);
|
|
@@ -4379,13 +4236,13 @@ async function fetchAppInfo(credentials) {
|
|
|
4379
4236
|
async function fetchFromApplicationApi(token) {
|
|
4380
4237
|
try {
|
|
4381
4238
|
const data = await (await fetch("https://open.feishu.cn/open-apis/application/v6/applications/me?lang=zh_cn", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
4382
|
-
log$
|
|
4239
|
+
log$28.info("application/v6 API 响应", {
|
|
4383
4240
|
code: data.code,
|
|
4384
4241
|
msg: data.msg,
|
|
4385
4242
|
hasApp: !!data.data?.app
|
|
4386
4243
|
});
|
|
4387
4244
|
if (data.code !== 0 || !data.data?.app) {
|
|
4388
|
-
log$
|
|
4245
|
+
log$28.warn("application/v6 API 返回非零或无数据,将 fallback", {
|
|
4389
4246
|
code: data.code,
|
|
4390
4247
|
msg: data.msg
|
|
4391
4248
|
});
|
|
@@ -4400,7 +4257,7 @@ async function fetchFromApplicationApi(token) {
|
|
|
4400
4257
|
helpDocUrl: zhInfo?.help_use
|
|
4401
4258
|
};
|
|
4402
4259
|
} catch (err) {
|
|
4403
|
-
log$
|
|
4260
|
+
log$28.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4404
4261
|
return null;
|
|
4405
4262
|
}
|
|
4406
4263
|
}
|
|
@@ -4408,13 +4265,13 @@ async function fetchFromApplicationApi(token) {
|
|
|
4408
4265
|
async function fetchFromBotApi(token) {
|
|
4409
4266
|
try {
|
|
4410
4267
|
const data = await (await fetch("https://open.feishu.cn/open-apis/bot/v3/info", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
4411
|
-
log$
|
|
4268
|
+
log$28.info("bot/v3/info API 响应", {
|
|
4412
4269
|
code: data.code,
|
|
4413
4270
|
msg: data.msg,
|
|
4414
4271
|
hasBot: !!data.bot
|
|
4415
4272
|
});
|
|
4416
4273
|
if (data.code !== 0 || !data.bot) {
|
|
4417
|
-
log$
|
|
4274
|
+
log$28.error("bot/v3/info API 失败", {
|
|
4418
4275
|
code: data.code,
|
|
4419
4276
|
msg: data.msg
|
|
4420
4277
|
});
|
|
@@ -4425,7 +4282,7 @@ async function fetchFromBotApi(token) {
|
|
|
4425
4282
|
avatarUrl: data.bot.avatar_url
|
|
4426
4283
|
};
|
|
4427
4284
|
} catch (err) {
|
|
4428
|
-
log$
|
|
4285
|
+
log$28.error("bot/v3/info API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4429
4286
|
return null;
|
|
4430
4287
|
}
|
|
4431
4288
|
}
|
|
@@ -4441,7 +4298,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
4441
4298
|
})
|
|
4442
4299
|
})).json();
|
|
4443
4300
|
if (data.code !== 0 || !data.tenant_access_token) {
|
|
4444
|
-
log$
|
|
4301
|
+
log$28.error("获取 tenant_access_token 失败", {
|
|
4445
4302
|
code: data.code,
|
|
4446
4303
|
msg: data.msg
|
|
4447
4304
|
});
|
|
@@ -4449,7 +4306,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
4449
4306
|
}
|
|
4450
4307
|
return data.tenant_access_token;
|
|
4451
4308
|
} catch (err) {
|
|
4452
|
-
log$
|
|
4309
|
+
log$28.error("获取 tenant_access_token 异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4453
4310
|
return null;
|
|
4454
4311
|
}
|
|
4455
4312
|
}
|
|
@@ -4487,10 +4344,10 @@ function parseDocTokenFromUrl(url) {
|
|
|
4487
4344
|
async function fetchDocContent(docUrl, accessToken) {
|
|
4488
4345
|
const parsed = parseDocTokenFromUrl(docUrl);
|
|
4489
4346
|
if (!parsed) {
|
|
4490
|
-
log$
|
|
4347
|
+
log$28.warn("无法从 URL 中解析文档 token", { url: docUrl });
|
|
4491
4348
|
return null;
|
|
4492
4349
|
}
|
|
4493
|
-
log$
|
|
4350
|
+
log$28.info("开始读取人设文档", {
|
|
4494
4351
|
url: docUrl,
|
|
4495
4352
|
type: parsed.type,
|
|
4496
4353
|
token: parsed.token
|
|
@@ -4498,18 +4355,18 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4498
4355
|
let docToken = parsed.token;
|
|
4499
4356
|
if (parsed.type === "wiki") {
|
|
4500
4357
|
const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
|
|
4501
|
-
if (!realToken) log$
|
|
4358
|
+
if (!realToken) log$28.warn("wiki 节点解析失败,尝试直接使用 token 读取");
|
|
4502
4359
|
else docToken = realToken;
|
|
4503
4360
|
}
|
|
4504
4361
|
try {
|
|
4505
4362
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
4506
|
-
log$
|
|
4363
|
+
log$28.info("文档 raw_content API 响应", {
|
|
4507
4364
|
code: data.code,
|
|
4508
4365
|
msg: data.msg,
|
|
4509
4366
|
contentLength: data.data?.content?.length
|
|
4510
4367
|
});
|
|
4511
4368
|
if (data.code !== 0 || !data.data?.content) {
|
|
4512
|
-
log$
|
|
4369
|
+
log$28.warn("读取文档内容失败", {
|
|
4513
4370
|
code: data.code,
|
|
4514
4371
|
msg: data.msg,
|
|
4515
4372
|
docToken
|
|
@@ -4518,7 +4375,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4518
4375
|
}
|
|
4519
4376
|
return data.data.content.trim();
|
|
4520
4377
|
} catch (err) {
|
|
4521
|
-
log$
|
|
4378
|
+
log$28.error("读取文档内容异常", {
|
|
4522
4379
|
error: err instanceof Error ? err.message : String(err),
|
|
4523
4380
|
docToken
|
|
4524
4381
|
});
|
|
@@ -4529,7 +4386,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
4529
4386
|
async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
4530
4387
|
try {
|
|
4531
4388
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
4532
|
-
log$
|
|
4389
|
+
log$28.info("wiki get_node API 响应", {
|
|
4533
4390
|
code: data.code,
|
|
4534
4391
|
msg: data.msg,
|
|
4535
4392
|
objType: data.data?.node?.obj_type
|
|
@@ -4537,7 +4394,7 @@ async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
|
4537
4394
|
if (data.code !== 0 || !data.data?.node?.obj_token) return null;
|
|
4538
4395
|
return data.data.node.obj_token;
|
|
4539
4396
|
} catch (err) {
|
|
4540
|
-
log$
|
|
4397
|
+
log$28.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
4541
4398
|
return null;
|
|
4542
4399
|
}
|
|
4543
4400
|
}
|
|
@@ -4559,7 +4416,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4559
4416
|
const infoBlock = buildAppInfoBlock(appInfo);
|
|
4560
4417
|
if (!existsSync(claudeMdPath)) {
|
|
4561
4418
|
await writeFile(claudeMdPath, infoBlock + "\n\n" + getDefaultClaudeMdBody(), "utf-8");
|
|
4562
|
-
log$
|
|
4419
|
+
log$28.info("CLAUDE.md 已创建(含应用信息)", { appName: appInfo.appName });
|
|
4563
4420
|
return;
|
|
4564
4421
|
}
|
|
4565
4422
|
let content = await readFile(claudeMdPath, "utf-8");
|
|
@@ -4571,7 +4428,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4571
4428
|
content = before + infoBlock + after;
|
|
4572
4429
|
} else content = infoBlock + "\n\n" + content;
|
|
4573
4430
|
await writeFile(claudeMdPath, content, "utf-8");
|
|
4574
|
-
log$
|
|
4431
|
+
log$28.info("CLAUDE.md 应用信息已同步", {
|
|
4575
4432
|
appName: appInfo.appName,
|
|
4576
4433
|
hasDescription: !!appInfo.description,
|
|
4577
4434
|
hasAvatar: !!appInfo.avatarUrl
|
|
@@ -4586,7 +4443,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
4586
4443
|
async function syncPersonaDocToClaudeMd(personaContent) {
|
|
4587
4444
|
const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
|
|
4588
4445
|
if (!existsSync(claudeMdPath)) {
|
|
4589
|
-
log$
|
|
4446
|
+
log$28.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
|
|
4590
4447
|
return;
|
|
4591
4448
|
}
|
|
4592
4449
|
let content = await readFile(claudeMdPath, "utf-8");
|
|
@@ -4607,7 +4464,7 @@ async function syncPersonaDocToClaudeMd(personaContent) {
|
|
|
4607
4464
|
content = before + personaBlock + after;
|
|
4608
4465
|
} else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
|
|
4609
4466
|
await writeFile(claudeMdPath, content, "utf-8");
|
|
4610
|
-
log$
|
|
4467
|
+
log$28.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
|
|
4611
4468
|
}
|
|
4612
4469
|
/** 构建应用信息标记区块 */
|
|
4613
4470
|
function buildAppInfoBlock(appInfo) {
|
|
@@ -4699,24 +4556,24 @@ function getDefaultClaudeMdBody() {
|
|
|
4699
4556
|
* @returns 同步后的应用信息,如果失败返回 null
|
|
4700
4557
|
*/
|
|
4701
4558
|
async function syncAppInfo(credentials) {
|
|
4702
|
-
log$
|
|
4559
|
+
log$28.info("开始同步应用信息", { appId: credentials.appId });
|
|
4703
4560
|
const appInfo = await fetchAppInfo(credentials);
|
|
4704
4561
|
if (!appInfo) {
|
|
4705
|
-
log$
|
|
4562
|
+
log$28.warn("获取应用信息失败,跳过同步");
|
|
4706
4563
|
return null;
|
|
4707
4564
|
}
|
|
4708
4565
|
await syncAppInfoToClaudeMd(appInfo);
|
|
4709
4566
|
if (appInfo.helpDocUrl) {
|
|
4710
|
-
log$
|
|
4567
|
+
log$28.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
|
|
4711
4568
|
const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
|
|
4712
4569
|
if (token) {
|
|
4713
4570
|
const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
|
|
4714
4571
|
if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
|
|
4715
|
-
else log$
|
|
4572
|
+
else log$28.warn("人设文档内容为空或读取失败,跳过同步");
|
|
4716
4573
|
}
|
|
4717
4574
|
}
|
|
4718
4575
|
await installSyncSkill();
|
|
4719
|
-
log$
|
|
4576
|
+
log$28.info("应用信息同步完成", {
|
|
4720
4577
|
appName: appInfo.appName,
|
|
4721
4578
|
description: appInfo.description?.substring(0, 50),
|
|
4722
4579
|
hasPersonaDoc: !!appInfo.helpDocUrl
|
|
@@ -4734,7 +4591,7 @@ async function installSyncSkill() {
|
|
|
4734
4591
|
if ((await readFile(SYNC_SKILL_PATH, "utf-8")).includes(`skill-version: ${SYNC_SKILL_VERSION}`)) return;
|
|
4735
4592
|
}
|
|
4736
4593
|
await writeFile(SYNC_SKILL_PATH, SYNC_SKILL_CONTENT, "utf-8");
|
|
4737
|
-
log$
|
|
4594
|
+
log$28.info("sync-app-info 技能已安装", { path: SYNC_SKILL_PATH });
|
|
4738
4595
|
}
|
|
4739
4596
|
const SYNC_SKILL_CONTENT = `---
|
|
4740
4597
|
skill-version: ${SYNC_SKILL_VERSION}
|
|
@@ -5730,7 +5587,7 @@ function createHooksRouter() {
|
|
|
5730
5587
|
}
|
|
5731
5588
|
//#endregion
|
|
5732
5589
|
//#region src/memory/store.ts
|
|
5733
|
-
const log$
|
|
5590
|
+
const log$27 = larkLogger("memory/store");
|
|
5734
5591
|
const MEMORY_VERSION = 1;
|
|
5735
5592
|
const MAX_TEAMMATE_RECENT = 3;
|
|
5736
5593
|
const MAX_LONG_TERM = 100;
|
|
@@ -5756,7 +5613,7 @@ var MemoryStore = class MemoryStore {
|
|
|
5756
5613
|
const raw = await readFile(path, "utf-8");
|
|
5757
5614
|
return normalizeMemoryFile(JSON.parse(raw));
|
|
5758
5615
|
} catch (err) {
|
|
5759
|
-
log$
|
|
5616
|
+
log$27.warn("memory file load failed, using empty state", {
|
|
5760
5617
|
cwd,
|
|
5761
5618
|
path,
|
|
5762
5619
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -6252,7 +6109,7 @@ function createMcpCallbackRouter() {
|
|
|
6252
6109
|
* 提供 /api/scheduled-tasks 的 RESTful CRUD 接口。
|
|
6253
6110
|
* 每个请求和响应都记录详细日志,便于后续错误排查。
|
|
6254
6111
|
*/
|
|
6255
|
-
const log$
|
|
6112
|
+
const log$26 = larkLogger("gateway/scheduler-handler");
|
|
6256
6113
|
/**
|
|
6257
6114
|
* 创建定时任务路由
|
|
6258
6115
|
*
|
|
@@ -6263,16 +6120,16 @@ function createSchedulerRouter(taskManager) {
|
|
|
6263
6120
|
const router = Router();
|
|
6264
6121
|
router.get("/api/scheduled-tasks", (req, res) => {
|
|
6265
6122
|
const requestId = req.headers["x-request-id"];
|
|
6266
|
-
log$
|
|
6123
|
+
log$26.info("请求列出所有定时任务", { requestId });
|
|
6267
6124
|
try {
|
|
6268
6125
|
const tasks = taskManager.listTasks();
|
|
6269
|
-
log$
|
|
6126
|
+
log$26.info("返回定时任务列表", {
|
|
6270
6127
|
requestId,
|
|
6271
6128
|
count: tasks.length
|
|
6272
6129
|
});
|
|
6273
6130
|
res.json({ tasks });
|
|
6274
6131
|
} catch (err) {
|
|
6275
|
-
log$
|
|
6132
|
+
log$26.error("列出定时任务失败", {
|
|
6276
6133
|
requestId,
|
|
6277
6134
|
error: String(err)
|
|
6278
6135
|
});
|
|
@@ -6285,13 +6142,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
6285
6142
|
router.post("/api/scheduled-tasks", (req, res) => {
|
|
6286
6143
|
const requestId = req.headers["x-request-id"];
|
|
6287
6144
|
const body = req.body;
|
|
6288
|
-
log$
|
|
6145
|
+
log$26.info("请求创建定时任务", {
|
|
6289
6146
|
requestId,
|
|
6290
6147
|
body: JSON.stringify(body)
|
|
6291
6148
|
});
|
|
6292
6149
|
const { name, cron: cronExpr, session_id, cwd, prompt, example_output, constraints } = body;
|
|
6293
6150
|
if (!name || typeof name !== "string") {
|
|
6294
|
-
log$
|
|
6151
|
+
log$26.warn("创建任务参数缺失: name", { requestId });
|
|
6295
6152
|
res.status(400).json({
|
|
6296
6153
|
error: "参数错误",
|
|
6297
6154
|
detail: "name 为必填字符串"
|
|
@@ -6299,7 +6156,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6299
6156
|
return;
|
|
6300
6157
|
}
|
|
6301
6158
|
if (!cronExpr || typeof cronExpr !== "string") {
|
|
6302
|
-
log$
|
|
6159
|
+
log$26.warn("创建任务参数缺失: cron", { requestId });
|
|
6303
6160
|
res.status(400).json({
|
|
6304
6161
|
error: "参数错误",
|
|
6305
6162
|
detail: "cron 为必填字符串"
|
|
@@ -6307,7 +6164,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6307
6164
|
return;
|
|
6308
6165
|
}
|
|
6309
6166
|
if (!session_id || typeof session_id !== "string") {
|
|
6310
|
-
log$
|
|
6167
|
+
log$26.warn("创建任务参数缺失: session_id", { requestId });
|
|
6311
6168
|
res.status(400).json({
|
|
6312
6169
|
error: "参数错误",
|
|
6313
6170
|
detail: "session_id 为必填字符串"
|
|
@@ -6315,7 +6172,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6315
6172
|
return;
|
|
6316
6173
|
}
|
|
6317
6174
|
if (!cwd || typeof cwd !== "string") {
|
|
6318
|
-
log$
|
|
6175
|
+
log$26.warn("创建任务参数缺失: cwd", { requestId });
|
|
6319
6176
|
res.status(400).json({
|
|
6320
6177
|
error: "参数错误",
|
|
6321
6178
|
detail: "cwd 为必填字符串"
|
|
@@ -6323,7 +6180,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6323
6180
|
return;
|
|
6324
6181
|
}
|
|
6325
6182
|
if (!prompt || typeof prompt !== "string") {
|
|
6326
|
-
log$
|
|
6183
|
+
log$26.warn("创建任务参数缺失: prompt", { requestId });
|
|
6327
6184
|
res.status(400).json({
|
|
6328
6185
|
error: "参数错误",
|
|
6329
6186
|
detail: "prompt 为必填字符串"
|
|
@@ -6331,7 +6188,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6331
6188
|
return;
|
|
6332
6189
|
}
|
|
6333
6190
|
if (!cron.validate(cronExpr)) {
|
|
6334
|
-
log$
|
|
6191
|
+
log$26.warn("cron 表达式不合法", {
|
|
6335
6192
|
requestId,
|
|
6336
6193
|
cron: cronExpr
|
|
6337
6194
|
});
|
|
@@ -6352,7 +6209,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6352
6209
|
constraints
|
|
6353
6210
|
};
|
|
6354
6211
|
const task = taskManager.createTask(params);
|
|
6355
|
-
log$
|
|
6212
|
+
log$26.info("定时任务创建成功", {
|
|
6356
6213
|
requestId,
|
|
6357
6214
|
taskId: task.id,
|
|
6358
6215
|
name: task.name,
|
|
@@ -6360,7 +6217,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6360
6217
|
});
|
|
6361
6218
|
res.status(201).json({ task });
|
|
6362
6219
|
} catch (err) {
|
|
6363
|
-
log$
|
|
6220
|
+
log$26.error("创建定时任务失败", {
|
|
6364
6221
|
requestId,
|
|
6365
6222
|
error: String(err)
|
|
6366
6223
|
});
|
|
@@ -6374,13 +6231,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
6374
6231
|
const requestId = req.headers["x-request-id"];
|
|
6375
6232
|
const id = String(req.params.id);
|
|
6376
6233
|
const body = req.body;
|
|
6377
|
-
log$
|
|
6234
|
+
log$26.info("请求更新定时任务", {
|
|
6378
6235
|
requestId,
|
|
6379
6236
|
taskId: id,
|
|
6380
6237
|
body: JSON.stringify(body)
|
|
6381
6238
|
});
|
|
6382
6239
|
if (!taskManager.getTask(id)) {
|
|
6383
|
-
log$
|
|
6240
|
+
log$26.warn("更新的任务不存在", {
|
|
6384
6241
|
requestId,
|
|
6385
6242
|
taskId: id
|
|
6386
6243
|
});
|
|
@@ -6392,7 +6249,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6392
6249
|
}
|
|
6393
6250
|
if (body.cron !== void 0) {
|
|
6394
6251
|
if (typeof body.cron !== "string" || !cron.validate(body.cron)) {
|
|
6395
|
-
log$
|
|
6252
|
+
log$26.warn("更新的 cron 表达式不合法", {
|
|
6396
6253
|
requestId,
|
|
6397
6254
|
taskId: id,
|
|
6398
6255
|
cron: body.cron
|
|
@@ -6413,7 +6270,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6413
6270
|
if (body.constraints !== void 0) updates.constraints = body.constraints;
|
|
6414
6271
|
if (body.enabled !== void 0) updates.enabled = body.enabled;
|
|
6415
6272
|
const task = taskManager.updateTask(id, updates);
|
|
6416
|
-
log$
|
|
6273
|
+
log$26.info("定时任务更新成功", {
|
|
6417
6274
|
requestId,
|
|
6418
6275
|
taskId: task.id,
|
|
6419
6276
|
name: task.name,
|
|
@@ -6421,7 +6278,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6421
6278
|
});
|
|
6422
6279
|
res.json({ task });
|
|
6423
6280
|
} catch (err) {
|
|
6424
|
-
log$
|
|
6281
|
+
log$26.error("更新定时任务失败", {
|
|
6425
6282
|
requestId,
|
|
6426
6283
|
taskId: id,
|
|
6427
6284
|
error: String(err)
|
|
@@ -6435,12 +6292,12 @@ function createSchedulerRouter(taskManager) {
|
|
|
6435
6292
|
router.delete("/api/scheduled-tasks/:id", (req, res) => {
|
|
6436
6293
|
const requestId = req.headers["x-request-id"];
|
|
6437
6294
|
const id = String(req.params.id);
|
|
6438
|
-
log$
|
|
6295
|
+
log$26.info("请求删除定时任务", {
|
|
6439
6296
|
requestId,
|
|
6440
6297
|
taskId: id
|
|
6441
6298
|
});
|
|
6442
6299
|
if (!taskManager.getTask(id)) {
|
|
6443
|
-
log$
|
|
6300
|
+
log$26.warn("删除的任务不存在", {
|
|
6444
6301
|
requestId,
|
|
6445
6302
|
taskId: id
|
|
6446
6303
|
});
|
|
@@ -6452,7 +6309,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6452
6309
|
}
|
|
6453
6310
|
try {
|
|
6454
6311
|
const deleted = taskManager.deleteTask(id);
|
|
6455
|
-
log$
|
|
6312
|
+
log$26.info("定时任务删除结果", {
|
|
6456
6313
|
requestId,
|
|
6457
6314
|
taskId: id,
|
|
6458
6315
|
deleted
|
|
@@ -6462,7 +6319,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
6462
6319
|
deleted: true
|
|
6463
6320
|
});
|
|
6464
6321
|
} catch (err) {
|
|
6465
|
-
log$
|
|
6322
|
+
log$26.error("删除定时任务失败", {
|
|
6466
6323
|
requestId,
|
|
6467
6324
|
taskId: id,
|
|
6468
6325
|
error: String(err)
|
|
@@ -7019,7 +6876,7 @@ function createGatewayServer(config) {
|
|
|
7019
6876
|
*
|
|
7020
6877
|
* 持久化文件: /workspace/config/scheduled-tasks.json
|
|
7021
6878
|
*/
|
|
7022
|
-
const log$
|
|
6879
|
+
const log$25 = larkLogger("gateway/scheduler");
|
|
7023
6880
|
/** 持久化文件路径 */
|
|
7024
6881
|
const PERSIST_PATH = "/workspace/config/scheduled-tasks.json";
|
|
7025
6882
|
/**
|
|
@@ -7037,7 +6894,7 @@ var ScheduledTaskManager = class {
|
|
|
7037
6894
|
processManager;
|
|
7038
6895
|
constructor(processManager) {
|
|
7039
6896
|
this.processManager = processManager;
|
|
7040
|
-
log$
|
|
6897
|
+
log$25.info("ScheduledTaskManager 已创建");
|
|
7041
6898
|
}
|
|
7042
6899
|
/**
|
|
7043
6900
|
* 创建定时任务
|
|
@@ -7057,7 +6914,7 @@ var ScheduledTaskManager = class {
|
|
|
7057
6914
|
enabled: true,
|
|
7058
6915
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7059
6916
|
};
|
|
7060
|
-
log$
|
|
6917
|
+
log$25.info("创建定时任务", {
|
|
7061
6918
|
taskId: task.id,
|
|
7062
6919
|
name: task.name,
|
|
7063
6920
|
cron: task.cron,
|
|
@@ -7093,7 +6950,7 @@ var ScheduledTaskManager = class {
|
|
|
7093
6950
|
if (updates.example_output !== void 0) task.example_output = updates.example_output;
|
|
7094
6951
|
if (updates.constraints !== void 0) task.constraints = updates.constraints;
|
|
7095
6952
|
if (updates.enabled !== void 0) task.enabled = updates.enabled;
|
|
7096
|
-
log$
|
|
6953
|
+
log$25.info("更新定时任务", {
|
|
7097
6954
|
taskId: id,
|
|
7098
6955
|
name: task.name,
|
|
7099
6956
|
cronChanged: oldCron !== task.cron,
|
|
@@ -7115,10 +6972,10 @@ var ScheduledTaskManager = class {
|
|
|
7115
6972
|
deleteTask(id) {
|
|
7116
6973
|
const task = this.tasks.get(id);
|
|
7117
6974
|
if (!task) {
|
|
7118
|
-
log$
|
|
6975
|
+
log$25.warn("尝试删除不存在的任务", { taskId: id });
|
|
7119
6976
|
return false;
|
|
7120
6977
|
}
|
|
7121
|
-
log$
|
|
6978
|
+
log$25.info("删除定时任务", {
|
|
7122
6979
|
taskId: id,
|
|
7123
6980
|
name: task.name
|
|
7124
6981
|
});
|
|
@@ -7133,28 +6990,28 @@ var ScheduledTaskManager = class {
|
|
|
7133
6990
|
* 启动时调用,读取 JSON 文件并为每个 enabled 的任务注册 cron。
|
|
7134
6991
|
*/
|
|
7135
6992
|
async loadFromDisk() {
|
|
7136
|
-
log$
|
|
6993
|
+
log$25.info("从磁盘加载定时任务", { path: PERSIST_PATH });
|
|
7137
6994
|
try {
|
|
7138
6995
|
const content = await readFile(PERSIST_PATH, "utf-8");
|
|
7139
6996
|
const data = JSON.parse(content);
|
|
7140
6997
|
if (!Array.isArray(data)) {
|
|
7141
|
-
log$
|
|
6998
|
+
log$25.warn("持久化文件格式异常,跳过加载", { path: PERSIST_PATH });
|
|
7142
6999
|
return;
|
|
7143
7000
|
}
|
|
7144
7001
|
for (const task of data) {
|
|
7145
7002
|
this.tasks.set(task.id, task);
|
|
7146
7003
|
if (task.enabled) this.registerCronJob(task);
|
|
7147
7004
|
}
|
|
7148
|
-
log$
|
|
7005
|
+
log$25.info("定时任务加载完成", {
|
|
7149
7006
|
total: data.length,
|
|
7150
7007
|
enabled: data.filter((t) => t.enabled).length
|
|
7151
7008
|
});
|
|
7152
7009
|
} catch (err) {
|
|
7153
7010
|
if (err.code === "ENOENT") {
|
|
7154
|
-
log$
|
|
7011
|
+
log$25.info("持久化文件不存在,跳过加载(首次启动)", { path: PERSIST_PATH });
|
|
7155
7012
|
return;
|
|
7156
7013
|
}
|
|
7157
|
-
log$
|
|
7014
|
+
log$25.error("加载定时任务失败", {
|
|
7158
7015
|
path: PERSIST_PATH,
|
|
7159
7016
|
error: String(err)
|
|
7160
7017
|
});
|
|
@@ -7170,12 +7027,12 @@ var ScheduledTaskManager = class {
|
|
|
7170
7027
|
try {
|
|
7171
7028
|
await mkdir(dirname(PERSIST_PATH), { recursive: true });
|
|
7172
7029
|
await writeFile(PERSIST_PATH, JSON.stringify(tasks, null, 2), "utf-8");
|
|
7173
|
-
log$
|
|
7030
|
+
log$25.info("定时任务已持久化到磁盘", {
|
|
7174
7031
|
path: PERSIST_PATH,
|
|
7175
7032
|
count: tasks.length
|
|
7176
7033
|
});
|
|
7177
7034
|
} catch (err) {
|
|
7178
|
-
log$
|
|
7035
|
+
log$25.error("持久化定时任务失败", {
|
|
7179
7036
|
path: PERSIST_PATH,
|
|
7180
7037
|
error: String(err)
|
|
7181
7038
|
});
|
|
@@ -7188,13 +7045,13 @@ var ScheduledTaskManager = class {
|
|
|
7188
7045
|
*/
|
|
7189
7046
|
registerCronJob(task) {
|
|
7190
7047
|
this.unregisterCronJob(task.id);
|
|
7191
|
-
log$
|
|
7048
|
+
log$25.info("注册 cron 调度", {
|
|
7192
7049
|
taskId: task.id,
|
|
7193
7050
|
name: task.name,
|
|
7194
7051
|
cron: task.cron
|
|
7195
7052
|
});
|
|
7196
7053
|
const job = cron.schedule(task.cron, () => {
|
|
7197
|
-
log$
|
|
7054
|
+
log$25.info("cron 触发任务执行", {
|
|
7198
7055
|
taskId: task.id,
|
|
7199
7056
|
name: task.name
|
|
7200
7057
|
});
|
|
@@ -7210,7 +7067,7 @@ var ScheduledTaskManager = class {
|
|
|
7210
7067
|
if (job) {
|
|
7211
7068
|
job.stop();
|
|
7212
7069
|
this.cronJobs.delete(taskId);
|
|
7213
|
-
log$
|
|
7070
|
+
log$25.info("已注销 cron 调度", { taskId });
|
|
7214
7071
|
}
|
|
7215
7072
|
}
|
|
7216
7073
|
/**
|
|
@@ -7220,10 +7077,10 @@ var ScheduledTaskManager = class {
|
|
|
7220
7077
|
*/
|
|
7221
7078
|
stopAll() {
|
|
7222
7079
|
const count = this.cronJobs.size;
|
|
7223
|
-
log$
|
|
7080
|
+
log$25.info("停止所有 cron 调度", { count });
|
|
7224
7081
|
for (const [taskId, job] of this.cronJobs) {
|
|
7225
7082
|
job.stop();
|
|
7226
|
-
log$
|
|
7083
|
+
log$25.debug("已停止 cron 调度", { taskId });
|
|
7227
7084
|
}
|
|
7228
7085
|
this.cronJobs.clear();
|
|
7229
7086
|
}
|
|
@@ -7237,17 +7094,17 @@ var ScheduledTaskManager = class {
|
|
|
7237
7094
|
async executeTask(taskId) {
|
|
7238
7095
|
const task = this.tasks.get(taskId);
|
|
7239
7096
|
if (!task) {
|
|
7240
|
-
log$
|
|
7097
|
+
log$25.warn("任务执行时未找到任务", { taskId });
|
|
7241
7098
|
return;
|
|
7242
7099
|
}
|
|
7243
7100
|
if (!task.enabled) {
|
|
7244
|
-
log$
|
|
7101
|
+
log$25.info("任务已禁用,跳过执行", {
|
|
7245
7102
|
taskId,
|
|
7246
7103
|
name: task.name
|
|
7247
7104
|
});
|
|
7248
7105
|
return;
|
|
7249
7106
|
}
|
|
7250
|
-
log$
|
|
7107
|
+
log$25.info("开始执行定时任务", {
|
|
7251
7108
|
taskId: task.id,
|
|
7252
7109
|
name: task.name,
|
|
7253
7110
|
sessionId: task.session_id,
|
|
@@ -7268,7 +7125,7 @@ var ScheduledTaskManager = class {
|
|
|
7268
7125
|
onResult: (result) => {
|
|
7269
7126
|
const summary = result.result ?? resultText;
|
|
7270
7127
|
task.last_result = summary.slice(0, 2e3);
|
|
7271
|
-
log$
|
|
7128
|
+
log$25.info("定时任务执行完成", {
|
|
7272
7129
|
taskId: task.id,
|
|
7273
7130
|
name: task.name,
|
|
7274
7131
|
subtype: result.subtype,
|
|
@@ -7281,7 +7138,7 @@ var ScheduledTaskManager = class {
|
|
|
7281
7138
|
},
|
|
7282
7139
|
onError: (error) => {
|
|
7283
7140
|
task.last_result = `执行失败: ${error.message}`;
|
|
7284
|
-
log$
|
|
7141
|
+
log$25.error("定时任务执行失败", {
|
|
7285
7142
|
taskId: task.id,
|
|
7286
7143
|
name: task.name,
|
|
7287
7144
|
error: error.message
|
|
@@ -7293,7 +7150,7 @@ var ScheduledTaskManager = class {
|
|
|
7293
7150
|
await this.processManager.executePrompt(config, callbacks);
|
|
7294
7151
|
} catch (err) {
|
|
7295
7152
|
task.last_result = `执行异常: ${String(err)}`;
|
|
7296
|
-
log$
|
|
7153
|
+
log$25.error("定时任务执行异常", {
|
|
7297
7154
|
taskId: task.id,
|
|
7298
7155
|
name: task.name,
|
|
7299
7156
|
error: String(err)
|
|
@@ -7322,17 +7179,18 @@ var ScheduledTaskManager = class {
|
|
|
7322
7179
|
* - 会话元数据:{workspaceRoot}/.chat-history/sessions/{userId}.json
|
|
7323
7180
|
* - 消息数据:{workspaceRoot}/.chat-history/messages/{sessionId}.json
|
|
7324
7181
|
*/
|
|
7325
|
-
const log$
|
|
7182
|
+
const log$24 = larkLogger("chat/message-store");
|
|
7326
7183
|
var SessionMessageStore = class {
|
|
7327
7184
|
baseDir;
|
|
7328
7185
|
sessionsDir;
|
|
7329
7186
|
messagesDir;
|
|
7187
|
+
jsonCache = /* @__PURE__ */ new Map();
|
|
7330
7188
|
constructor(workspaceRoot) {
|
|
7331
7189
|
const root = workspaceRoot || resolveWorkspaceRoot();
|
|
7332
7190
|
this.baseDir = getWorkspaceChatHistoryRoot(root);
|
|
7333
7191
|
this.sessionsDir = join(this.baseDir, "sessions");
|
|
7334
7192
|
this.messagesDir = join(this.baseDir, "messages");
|
|
7335
|
-
log$
|
|
7193
|
+
log$24.info("初始化消息存储", { baseDir: this.baseDir });
|
|
7336
7194
|
}
|
|
7337
7195
|
/**
|
|
7338
7196
|
* 创建新会话
|
|
@@ -7345,7 +7203,7 @@ var SessionMessageStore = class {
|
|
|
7345
7203
|
updatedAt: now,
|
|
7346
7204
|
messageCount: 0
|
|
7347
7205
|
};
|
|
7348
|
-
log$
|
|
7206
|
+
log$24.info("创建会话", {
|
|
7349
7207
|
sessionId: fullSession.id,
|
|
7350
7208
|
userId: fullSession.userId
|
|
7351
7209
|
});
|
|
@@ -7358,7 +7216,7 @@ var SessionMessageStore = class {
|
|
|
7358
7216
|
* 获取指定会话
|
|
7359
7217
|
*/
|
|
7360
7218
|
async getSession(sessionId) {
|
|
7361
|
-
log$
|
|
7219
|
+
log$24.debug("获取会话", { sessionId });
|
|
7362
7220
|
try {
|
|
7363
7221
|
const files = await this.listSessionFiles();
|
|
7364
7222
|
for (const file of files) {
|
|
@@ -7368,7 +7226,7 @@ var SessionMessageStore = class {
|
|
|
7368
7226
|
if (found) return found;
|
|
7369
7227
|
}
|
|
7370
7228
|
} catch (err) {
|
|
7371
|
-
log$
|
|
7229
|
+
log$24.error("获取会话失败", {
|
|
7372
7230
|
sessionId,
|
|
7373
7231
|
error: String(err)
|
|
7374
7232
|
});
|
|
@@ -7379,14 +7237,14 @@ var SessionMessageStore = class {
|
|
|
7379
7237
|
* 列出指定用户的所有会话
|
|
7380
7238
|
*/
|
|
7381
7239
|
async listSessions(userId) {
|
|
7382
|
-
log$
|
|
7240
|
+
log$24.debug("列出用户会话", { userId });
|
|
7383
7241
|
return await this.readUserSessions(userId);
|
|
7384
7242
|
}
|
|
7385
7243
|
/**
|
|
7386
7244
|
* 删除指定会话(同时删除消息文件)
|
|
7387
7245
|
*/
|
|
7388
7246
|
async deleteSession(sessionId) {
|
|
7389
|
-
log$
|
|
7247
|
+
log$24.info("删除会话", { sessionId });
|
|
7390
7248
|
try {
|
|
7391
7249
|
const files = await this.listSessionFiles();
|
|
7392
7250
|
for (const file of files) {
|
|
@@ -7401,9 +7259,9 @@ var SessionMessageStore = class {
|
|
|
7401
7259
|
}
|
|
7402
7260
|
const msgFile = this.getMessageFilePath(sessionId);
|
|
7403
7261
|
await this.writeJsonFile(msgFile, []);
|
|
7404
|
-
log$
|
|
7262
|
+
log$24.info("会话删除完成", { sessionId });
|
|
7405
7263
|
} catch (err) {
|
|
7406
|
-
log$
|
|
7264
|
+
log$24.error("删除会话失败", {
|
|
7407
7265
|
sessionId,
|
|
7408
7266
|
error: String(err)
|
|
7409
7267
|
});
|
|
@@ -7420,7 +7278,7 @@ var SessionMessageStore = class {
|
|
|
7420
7278
|
id: v4(),
|
|
7421
7279
|
timestamp: Date.now()
|
|
7422
7280
|
};
|
|
7423
|
-
log$
|
|
7281
|
+
log$24.info("追加消息", {
|
|
7424
7282
|
messageId: fullMessage.id,
|
|
7425
7283
|
sessionId: fullMessage.sessionId,
|
|
7426
7284
|
role: fullMessage.role
|
|
@@ -7431,9 +7289,9 @@ var SessionMessageStore = class {
|
|
|
7431
7289
|
messages.push(fullMessage);
|
|
7432
7290
|
await this.writeJsonFile(msgFile, messages);
|
|
7433
7291
|
await this.updateSessionMeta(fullMessage.sessionId, messages.length);
|
|
7434
|
-
log$
|
|
7292
|
+
log$24.debug("消息追加完成", { messageId: fullMessage.id });
|
|
7435
7293
|
} catch (err) {
|
|
7436
|
-
log$
|
|
7294
|
+
log$24.error("追加消息失败", {
|
|
7437
7295
|
sessionId: fullMessage.sessionId,
|
|
7438
7296
|
error: String(err)
|
|
7439
7297
|
});
|
|
@@ -7448,7 +7306,7 @@ var SessionMessageStore = class {
|
|
|
7448
7306
|
async getMessages(sessionId, options) {
|
|
7449
7307
|
const limit = options?.limit ?? 50;
|
|
7450
7308
|
const cursor = options?.cursor;
|
|
7451
|
-
log$
|
|
7309
|
+
log$24.debug("获取消息列表", {
|
|
7452
7310
|
sessionId,
|
|
7453
7311
|
cursor,
|
|
7454
7312
|
limit
|
|
@@ -7467,7 +7325,7 @@ var SessionMessageStore = class {
|
|
|
7467
7325
|
nextCursor: startIdx + limit < allMessages.length ? sliced[sliced.length - 1]?.id : void 0
|
|
7468
7326
|
};
|
|
7469
7327
|
} catch (err) {
|
|
7470
|
-
log$
|
|
7328
|
+
log$24.error("获取消息列表失败", {
|
|
7471
7329
|
sessionId,
|
|
7472
7330
|
error: String(err)
|
|
7473
7331
|
});
|
|
@@ -7478,12 +7336,12 @@ var SessionMessageStore = class {
|
|
|
7478
7336
|
* 获取会话消息总数
|
|
7479
7337
|
*/
|
|
7480
7338
|
async getMessageCount(sessionId) {
|
|
7481
|
-
log$
|
|
7339
|
+
log$24.debug("获取消息数量", { sessionId });
|
|
7482
7340
|
try {
|
|
7483
7341
|
const msgFile = this.getMessageFilePath(sessionId);
|
|
7484
7342
|
return (await this.readJsonFile(msgFile) || []).length;
|
|
7485
7343
|
} catch (err) {
|
|
7486
|
-
log$
|
|
7344
|
+
log$24.error("获取消息数量失败", {
|
|
7487
7345
|
sessionId,
|
|
7488
7346
|
error: String(err)
|
|
7489
7347
|
});
|
|
@@ -7534,7 +7392,7 @@ var SessionMessageStore = class {
|
|
|
7534
7392
|
}
|
|
7535
7393
|
}
|
|
7536
7394
|
} catch (err) {
|
|
7537
|
-
log$
|
|
7395
|
+
log$24.warn("更新会话元数据失败", {
|
|
7538
7396
|
sessionId,
|
|
7539
7397
|
error: String(err)
|
|
7540
7398
|
});
|
|
@@ -7549,7 +7407,7 @@ var SessionMessageStore = class {
|
|
|
7549
7407
|
await mkdir(this.sessionsDir, { recursive: true });
|
|
7550
7408
|
return (await readdir(this.sessionsDir)).filter((e) => e.endsWith(".json")).map((e) => join(this.sessionsDir, e));
|
|
7551
7409
|
} catch (err) {
|
|
7552
|
-
log$
|
|
7410
|
+
log$24.error("列出会话索引文件失败", { error: String(err) });
|
|
7553
7411
|
return [];
|
|
7554
7412
|
}
|
|
7555
7413
|
}
|
|
@@ -7559,13 +7417,19 @@ var SessionMessageStore = class {
|
|
|
7559
7417
|
async readJsonFile(filePath) {
|
|
7560
7418
|
try {
|
|
7561
7419
|
const raw = await readFile(filePath, "utf-8");
|
|
7562
|
-
|
|
7420
|
+
const parsed = JSON.parse(raw);
|
|
7421
|
+
this.jsonCache.set(filePath, parsed);
|
|
7422
|
+
return parsed;
|
|
7563
7423
|
} catch (err) {
|
|
7564
7424
|
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
|
|
7565
|
-
log$
|
|
7425
|
+
log$24.error("读取 JSON 文件失败", {
|
|
7566
7426
|
filePath,
|
|
7567
7427
|
error: String(err)
|
|
7568
7428
|
});
|
|
7429
|
+
if (this.jsonCache.has(filePath)) {
|
|
7430
|
+
log$24.warn("读取 JSON 文件失败,使用内存中的上一份完整快照", { filePath });
|
|
7431
|
+
return this.jsonCache.get(filePath);
|
|
7432
|
+
}
|
|
7569
7433
|
return null;
|
|
7570
7434
|
}
|
|
7571
7435
|
}
|
|
@@ -7573,18 +7437,39 @@ var SessionMessageStore = class {
|
|
|
7573
7437
|
* 通用 JSON 文件写入(自动创建目录)
|
|
7574
7438
|
*/
|
|
7575
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`);
|
|
7576
7442
|
try {
|
|
7577
|
-
|
|
7578
|
-
await
|
|
7579
|
-
|
|
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);
|
|
7580
7454
|
} catch (err) {
|
|
7581
|
-
log$
|
|
7455
|
+
log$24.error("写入 JSON 文件失败", {
|
|
7582
7456
|
filePath,
|
|
7583
7457
|
error: String(err)
|
|
7584
7458
|
});
|
|
7459
|
+
await unlink(tmpPath).catch(() => void 0);
|
|
7585
7460
|
throw err;
|
|
7586
7461
|
}
|
|
7587
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
|
+
}
|
|
7588
7473
|
};
|
|
7589
7474
|
//#endregion
|
|
7590
7475
|
//#region src/messaging/inbound/user-name-cache-store.ts
|
|
@@ -7859,7 +7744,7 @@ function getUserAgent() {
|
|
|
7859
7744
|
* - `LarkClient.fromCredentials(credentials)` — ephemeral instance (not cached)
|
|
7860
7745
|
* - `LarkClient.fromProvider(provider, opts)` — from ICredentialProvider (not cached)
|
|
7861
7746
|
*/
|
|
7862
|
-
const log$
|
|
7747
|
+
const log$22 = larkLogger("core/lark-client");
|
|
7863
7748
|
const GLOBAL_LARK_USER_AGENT_KEY = "LARK_USER_AGENT";
|
|
7864
7749
|
function installGlobalUserAgent() {
|
|
7865
7750
|
globalThis[GLOBAL_LARK_USER_AGENT_KEY] = getUserAgent();
|
|
@@ -7951,7 +7836,7 @@ var LarkClient = class LarkClient {
|
|
|
7951
7836
|
const existing = cache.get(account.accountId);
|
|
7952
7837
|
if (existing && existing.account.appId === account.appId && credentialsEqual(existing.account.appSecret, account.appSecret)) return existing;
|
|
7953
7838
|
if (existing) {
|
|
7954
|
-
log$
|
|
7839
|
+
log$22.info(`credentials changed, disposing stale instance`, { accountId: account.accountId });
|
|
7955
7840
|
existing.dispose();
|
|
7956
7841
|
}
|
|
7957
7842
|
const instance = new LarkClient(account);
|
|
@@ -7994,7 +7879,7 @@ var LarkClient = class LarkClient {
|
|
|
7994
7879
|
static fromProvider(provider, opts) {
|
|
7995
7880
|
const appId = provider.getAppId();
|
|
7996
7881
|
const appSecret = provider.getAppSecret();
|
|
7997
|
-
log$
|
|
7882
|
+
log$22.info("通过 ICredentialProvider 创建 LarkClient", {
|
|
7998
7883
|
appId,
|
|
7999
7884
|
accountId: opts?.accountId ?? "default",
|
|
8000
7885
|
brand: opts?.brand ?? "feishu"
|
|
@@ -8030,7 +7915,7 @@ var LarkClient = class LarkClient {
|
|
|
8030
7915
|
get sdk() {
|
|
8031
7916
|
if (!this._sdk) {
|
|
8032
7917
|
const { appId, appSecret } = this.requireCredentials();
|
|
8033
|
-
log$
|
|
7918
|
+
log$22.info("创建 Lark SDK 客户端实例", {
|
|
8034
7919
|
accountId: this.accountId,
|
|
8035
7920
|
appId,
|
|
8036
7921
|
brand: this.account.brand
|
|
@@ -8045,7 +7930,7 @@ var LarkClient = class LarkClient {
|
|
|
8045
7930
|
if (sdkAny.httpInstance?.interceptors) {
|
|
8046
7931
|
const accountId = this.accountId;
|
|
8047
7932
|
sdkAny.httpInstance.interceptors.request.use((req) => {
|
|
8048
|
-
log$
|
|
7933
|
+
log$22.debug("飞书 API 请求", {
|
|
8049
7934
|
accountId,
|
|
8050
7935
|
method: req.method,
|
|
8051
7936
|
url: req.url,
|
|
@@ -8055,7 +7940,7 @@ var LarkClient = class LarkClient {
|
|
|
8055
7940
|
return req;
|
|
8056
7941
|
}, void 0, { synchronous: true });
|
|
8057
7942
|
sdkAny.httpInstance.interceptors.response.use((res) => {
|
|
8058
|
-
log$
|
|
7943
|
+
log$22.debug("飞书 API 响应", {
|
|
8059
7944
|
accountId,
|
|
8060
7945
|
code: res?.code,
|
|
8061
7946
|
msg: res?.msg,
|
|
@@ -8063,7 +7948,7 @@ var LarkClient = class LarkClient {
|
|
|
8063
7948
|
});
|
|
8064
7949
|
return res;
|
|
8065
7950
|
}, (err) => {
|
|
8066
|
-
log$
|
|
7951
|
+
log$22.error("飞书 API 请求失败", {
|
|
8067
7952
|
accountId,
|
|
8068
7953
|
error: err instanceof Error ? err.message : String(err),
|
|
8069
7954
|
url: err?.config?.url
|
|
@@ -8150,7 +8035,7 @@ var LarkClient = class LarkClient {
|
|
|
8150
8035
|
dispatcher.register(handlers);
|
|
8151
8036
|
const { appId, appSecret } = this.requireCredentials();
|
|
8152
8037
|
if (this._wsClient) {
|
|
8153
|
-
log$
|
|
8038
|
+
log$22.warn(`closing previous WSClient before reconnect`, { accountId: this.accountId });
|
|
8154
8039
|
try {
|
|
8155
8040
|
this._wsClient.close({ force: true });
|
|
8156
8041
|
} catch {}
|
|
@@ -8167,7 +8052,7 @@ var LarkClient = class LarkClient {
|
|
|
8167
8052
|
wsClientAny.handleEventData = (data) => {
|
|
8168
8053
|
const msgType = data.headers?.find?.((h) => h.key === "type")?.value;
|
|
8169
8054
|
const eventType = data.headers?.find?.((h) => h.key === "event_type")?.value;
|
|
8170
|
-
log$
|
|
8055
|
+
log$22.info("WS 收到原始事件", {
|
|
8171
8056
|
accountId: this.accountId,
|
|
8172
8057
|
msgType,
|
|
8173
8058
|
eventType,
|
|
@@ -8193,14 +8078,14 @@ var LarkClient = class LarkClient {
|
|
|
8193
8078
|
/** Disconnect WebSocket but keep instance in cache. */
|
|
8194
8079
|
disconnect() {
|
|
8195
8080
|
if (this._wsClient) {
|
|
8196
|
-
log$
|
|
8081
|
+
log$22.info(`disconnecting WebSocket`, { accountId: this.accountId });
|
|
8197
8082
|
try {
|
|
8198
8083
|
this._wsClient.close({ force: true });
|
|
8199
8084
|
} catch {}
|
|
8200
8085
|
}
|
|
8201
8086
|
this._wsClient = null;
|
|
8202
8087
|
if (this.messageDedup) {
|
|
8203
|
-
log$
|
|
8088
|
+
log$22.info(`disposing message dedup`, {
|
|
8204
8089
|
accountId: this.accountId,
|
|
8205
8090
|
size: this.messageDedup.size
|
|
8206
8091
|
});
|
|
@@ -8506,7 +8391,7 @@ function sortTraceValue(value) {
|
|
|
8506
8391
|
}
|
|
8507
8392
|
//#endregion
|
|
8508
8393
|
//#region src/card/cc-stream-bridge.ts
|
|
8509
|
-
const log$
|
|
8394
|
+
const log$21 = larkLogger("card/cc-stream-bridge");
|
|
8510
8395
|
const CC_INTERNAL_PLACEHOLDER = "No response requested.";
|
|
8511
8396
|
/**
|
|
8512
8397
|
* CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
|
|
@@ -8531,7 +8416,7 @@ var CCStreamBridge = class {
|
|
|
8531
8416
|
sessionKey: options?.sessionKey
|
|
8532
8417
|
};
|
|
8533
8418
|
if (this.options.sessionKey) startToolUseTraceRun(this.options.sessionKey);
|
|
8534
|
-
log$
|
|
8419
|
+
log$21.info("CCStreamBridge 初始化", {
|
|
8535
8420
|
autoCompleteOnTurnEnd: this.options.autoCompleteOnTurnEnd,
|
|
8536
8421
|
sessionKey: this.options.sessionKey ?? "(none)"
|
|
8537
8422
|
});
|
|
@@ -8539,7 +8424,7 @@ var CCStreamBridge = class {
|
|
|
8539
8424
|
/** 文本增量 → 累积后调用 controller.onPartialReply */
|
|
8540
8425
|
onTextDelta(text) {
|
|
8541
8426
|
this.accumulatedText += text;
|
|
8542
|
-
log$
|
|
8427
|
+
log$21.debug("textDelta 事件", {
|
|
8543
8428
|
deltaLen: text.length,
|
|
8544
8429
|
totalLen: this.accumulatedText.length
|
|
8545
8430
|
});
|
|
@@ -8548,7 +8433,7 @@ var CCStreamBridge = class {
|
|
|
8548
8433
|
/** 思考增量 → 累积后调用 controller.onReasoningStream */
|
|
8549
8434
|
onThinkingDelta(text) {
|
|
8550
8435
|
this.accumulatedThinkingText += text;
|
|
8551
|
-
log$
|
|
8436
|
+
log$21.debug("thinkingDelta 事件", {
|
|
8552
8437
|
deltaLen: text.length,
|
|
8553
8438
|
totalLen: this.accumulatedThinkingText.length
|
|
8554
8439
|
});
|
|
@@ -8560,7 +8445,7 @@ var CCStreamBridge = class {
|
|
|
8560
8445
|
const displayName = getToolDisplayName(toolName);
|
|
8561
8446
|
const toolParams = typeof _toolInput === "object" && _toolInput != null ? _toolInput : void 0;
|
|
8562
8447
|
const hasParams = toolParams && Object.keys(toolParams).length > 0;
|
|
8563
|
-
log$
|
|
8448
|
+
log$21.info("toolUseStart 事件", {
|
|
8564
8449
|
toolName,
|
|
8565
8450
|
displayName,
|
|
8566
8451
|
activeToolsCount: this.activeTools.size,
|
|
@@ -8573,7 +8458,7 @@ var CCStreamBridge = class {
|
|
|
8573
8458
|
toolName,
|
|
8574
8459
|
toolParams
|
|
8575
8460
|
})) {
|
|
8576
|
-
log$
|
|
8461
|
+
log$21.info("toolUseStart: 更新已有步骤的 params", { toolName });
|
|
8577
8462
|
this.controller.onToolStart({
|
|
8578
8463
|
name: toolName,
|
|
8579
8464
|
phase: "start"
|
|
@@ -8597,7 +8482,7 @@ var CCStreamBridge = class {
|
|
|
8597
8482
|
const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
|
|
8598
8483
|
const displayName = getToolDisplayName(toolName);
|
|
8599
8484
|
this.activeTools.delete(toolUseId);
|
|
8600
|
-
log$
|
|
8485
|
+
log$21.info("toolResult 事件", {
|
|
8601
8486
|
toolUseId,
|
|
8602
8487
|
toolName,
|
|
8603
8488
|
displayName,
|
|
@@ -8612,7 +8497,7 @@ var CCStreamBridge = class {
|
|
|
8612
8497
|
}
|
|
8613
8498
|
/** 工具执行进度 → 记录日志 */
|
|
8614
8499
|
onToolProgress(toolName, elapsedSeconds) {
|
|
8615
|
-
log$
|
|
8500
|
+
log$21.debug("toolProgress 事件", {
|
|
8616
8501
|
toolName,
|
|
8617
8502
|
displayName: getToolDisplayName(toolName),
|
|
8618
8503
|
elapsedSeconds
|
|
@@ -8620,7 +8505,7 @@ var CCStreamBridge = class {
|
|
|
8620
8505
|
}
|
|
8621
8506
|
/** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
|
|
8622
8507
|
onTurnEnd(stopReason) {
|
|
8623
|
-
log$
|
|
8508
|
+
log$21.info("turnEnd 事件", {
|
|
8624
8509
|
stopReason,
|
|
8625
8510
|
accumulatedTextLen: this.accumulatedText.length,
|
|
8626
8511
|
accumulatedThinkingTextLen: this.accumulatedThinkingText.length
|
|
@@ -8628,7 +8513,7 @@ var CCStreamBridge = class {
|
|
|
8628
8513
|
if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
|
|
8629
8514
|
const trimmedText = this.accumulatedText.trim();
|
|
8630
8515
|
if (trimmedText === CC_INTERNAL_PLACEHOLDER) {
|
|
8631
|
-
log$
|
|
8516
|
+
log$21.info("检测到 CC 内部占位消息,静默丢弃", {
|
|
8632
8517
|
text: trimmedText.slice(0, 50),
|
|
8633
8518
|
sessionKey: this.options.sessionKey
|
|
8634
8519
|
});
|
|
@@ -8636,7 +8521,7 @@ var CCStreamBridge = class {
|
|
|
8636
8521
|
return;
|
|
8637
8522
|
}
|
|
8638
8523
|
if (trimmedText === "") {
|
|
8639
|
-
log$
|
|
8524
|
+
log$21.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
|
|
8640
8525
|
return;
|
|
8641
8526
|
}
|
|
8642
8527
|
this.controller.markFullyComplete();
|
|
@@ -8645,7 +8530,7 @@ var CCStreamBridge = class {
|
|
|
8645
8530
|
}
|
|
8646
8531
|
/** 最终结果 → 兜底最终化 + 错误处理 */
|
|
8647
8532
|
onResult(data) {
|
|
8648
|
-
log$
|
|
8533
|
+
log$21.info("result 事件", {
|
|
8649
8534
|
subtype: data.subtype,
|
|
8650
8535
|
isError: data.isError,
|
|
8651
8536
|
durationMs: data.durationMs,
|
|
@@ -8654,7 +8539,7 @@ var CCStreamBridge = class {
|
|
|
8654
8539
|
resultLen: data.result?.length ?? 0
|
|
8655
8540
|
});
|
|
8656
8541
|
if (data.aborted) {
|
|
8657
|
-
log$
|
|
8542
|
+
log$21.info("运行时报告用户主动中断,按正常完成处理", {
|
|
8658
8543
|
subtype: data.subtype,
|
|
8659
8544
|
sessionId: this.options.sessionKey
|
|
8660
8545
|
});
|
|
@@ -8665,7 +8550,7 @@ var CCStreamBridge = class {
|
|
|
8665
8550
|
const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
|
|
8666
8551
|
const sessionId = this.options.sessionKey;
|
|
8667
8552
|
if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
|
|
8668
|
-
log$
|
|
8553
|
+
log$21.info("用户主动中断,按正常完成处理", {
|
|
8669
8554
|
subtype: data.subtype,
|
|
8670
8555
|
sessionId
|
|
8671
8556
|
});
|
|
@@ -8674,14 +8559,14 @@ var CCStreamBridge = class {
|
|
|
8674
8559
|
this.controller.onIdle();
|
|
8675
8560
|
} else {
|
|
8676
8561
|
const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
|
|
8677
|
-
log$
|
|
8562
|
+
log$21.error("CC 执行返回错误", {
|
|
8678
8563
|
subtype: data.subtype,
|
|
8679
8564
|
errorMessage: errorMessage.slice(0, 200)
|
|
8680
8565
|
});
|
|
8681
8566
|
this.controller.onError(new Error(errorMessage), { kind: "cc-execution" });
|
|
8682
8567
|
}
|
|
8683
8568
|
} else if (!this.accumulatedText.trim()) if (data.result) {
|
|
8684
|
-
log$
|
|
8569
|
+
log$21.info("result 兜底:使用 result.result 作为最终回复", {
|
|
8685
8570
|
resultLen: data.result.length,
|
|
8686
8571
|
sessionKey: this.options.sessionKey
|
|
8687
8572
|
});
|
|
@@ -8689,7 +8574,7 @@ var CCStreamBridge = class {
|
|
|
8689
8574
|
this.controller.markFullyComplete();
|
|
8690
8575
|
this.controller.onIdle();
|
|
8691
8576
|
} else {
|
|
8692
|
-
log$
|
|
8577
|
+
log$21.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
|
|
8693
8578
|
this.controller.abortCard();
|
|
8694
8579
|
}
|
|
8695
8580
|
else {
|
|
@@ -8703,7 +8588,7 @@ var CCStreamBridge = class {
|
|
|
8703
8588
|
* 内部复用上面的直接调用方法。
|
|
8704
8589
|
*/
|
|
8705
8590
|
bindParser(parser) {
|
|
8706
|
-
log$
|
|
8591
|
+
log$21.info("绑定 CCStreamParser 事件到卡片控制器");
|
|
8707
8592
|
parser.on("textDelta", (text) => this.onTextDelta(text));
|
|
8708
8593
|
parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
|
|
8709
8594
|
parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
|
|
@@ -8711,7 +8596,7 @@ var CCStreamBridge = class {
|
|
|
8711
8596
|
parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
|
|
8712
8597
|
parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
|
|
8713
8598
|
parser.on("result", (data) => this.onResult(data));
|
|
8714
|
-
log$
|
|
8599
|
+
log$21.info("CCStreamParser 事件绑定完成");
|
|
8715
8600
|
}
|
|
8716
8601
|
};
|
|
8717
8602
|
//#endregion
|
|
@@ -10730,7 +10615,7 @@ function resolveLarkSdk(cfg, accountId) {
|
|
|
10730
10615
|
if (cached) return cached.sdk;
|
|
10731
10616
|
return LarkClient.fromCfg(cfg, accountId).sdk;
|
|
10732
10617
|
}
|
|
10733
|
-
const log$
|
|
10618
|
+
const log$20 = larkLogger("card/cardkit");
|
|
10734
10619
|
/**
|
|
10735
10620
|
* 记录 CardKit API 响应日志,检测错误码并抛出异常。
|
|
10736
10621
|
*
|
|
@@ -10740,13 +10625,13 @@ const log$19 = larkLogger("card/cardkit");
|
|
|
10740
10625
|
function logCardKitResponse(params) {
|
|
10741
10626
|
const { resp, api, context } = params;
|
|
10742
10627
|
const { code, msg } = resp;
|
|
10743
|
-
log$
|
|
10628
|
+
log$20.info(`cardkit ${api} response`, {
|
|
10744
10629
|
code,
|
|
10745
10630
|
msg,
|
|
10746
10631
|
context
|
|
10747
10632
|
});
|
|
10748
10633
|
if (code && code !== 0) {
|
|
10749
|
-
log$
|
|
10634
|
+
log$20.warn(`cardkit ${api} FAILED`, {
|
|
10750
10635
|
code,
|
|
10751
10636
|
msg,
|
|
10752
10637
|
context,
|
|
@@ -11157,7 +11042,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
|
|
|
11157
11042
|
* Feishu messages, uploading media to the Feishu IM storage, and
|
|
11158
11043
|
* sending image / file messages to chats.
|
|
11159
11044
|
*/
|
|
11160
|
-
const log$
|
|
11045
|
+
const log$19 = larkLogger("outbound/media");
|
|
11161
11046
|
/**
|
|
11162
11047
|
* Upload an image to Feishu IM storage.
|
|
11163
11048
|
*
|
|
@@ -11225,7 +11110,7 @@ async function validateRemoteUrl(raw) {
|
|
|
11225
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}"`);
|
|
11226
11111
|
} catch (err) {
|
|
11227
11112
|
if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
|
|
11228
|
-
log$
|
|
11113
|
+
log$19.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
|
|
11229
11114
|
}
|
|
11230
11115
|
}
|
|
11231
11116
|
/**
|
|
@@ -11243,21 +11128,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
|
|
|
11243
11128
|
if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
|
|
11244
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.`);
|
|
11245
11130
|
const buf = fs.readFileSync(filePath);
|
|
11246
|
-
log$
|
|
11131
|
+
log$19.debug(`local file read: "${filePath}", ${buf.length} bytes`);
|
|
11247
11132
|
return buf;
|
|
11248
11133
|
}
|
|
11249
11134
|
await validateRemoteUrl(raw);
|
|
11250
11135
|
const FETCH_TIMEOUT_MS = 3e4;
|
|
11251
|
-
log$
|
|
11136
|
+
log$19.info(`fetching remote media: ${raw}`);
|
|
11252
11137
|
const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
11253
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.`);
|
|
11254
11139
|
const arrayBuffer = await response.arrayBuffer();
|
|
11255
|
-
log$
|
|
11140
|
+
log$19.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
|
|
11256
11141
|
return Buffer.from(arrayBuffer);
|
|
11257
11142
|
}
|
|
11258
11143
|
//#endregion
|
|
11259
11144
|
//#region src/card/image-resolver.ts
|
|
11260
|
-
const log$
|
|
11145
|
+
const log$18 = larkLogger("card/image-resolver");
|
|
11261
11146
|
/** Matches complete markdown image syntax: `` */
|
|
11262
11147
|
const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
|
|
11263
11148
|
var ImageResolver = class {
|
|
@@ -11303,14 +11188,14 @@ var ImageResolver = class {
|
|
|
11303
11188
|
async resolveImagesAwait(text, timeoutMs) {
|
|
11304
11189
|
this.resolveImages(text);
|
|
11305
11190
|
if (this.pending.size > 0) {
|
|
11306
|
-
log$
|
|
11191
|
+
log$18.info("resolveImagesAwait: waiting for uploads", {
|
|
11307
11192
|
count: this.pending.size,
|
|
11308
11193
|
timeoutMs
|
|
11309
11194
|
});
|
|
11310
11195
|
const allUploads = Promise.all(this.pending.values());
|
|
11311
11196
|
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
11312
11197
|
await Promise.race([allUploads, timeout]);
|
|
11313
|
-
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 });
|
|
11314
11199
|
}
|
|
11315
11200
|
return this.resolveImages(text);
|
|
11316
11201
|
}
|
|
@@ -11320,7 +11205,7 @@ var ImageResolver = class {
|
|
|
11320
11205
|
}
|
|
11321
11206
|
async doUpload(url) {
|
|
11322
11207
|
try {
|
|
11323
|
-
log$
|
|
11208
|
+
log$18.info("uploading image", { url });
|
|
11324
11209
|
const buffer = await fetchRemoteImageBuffer(url);
|
|
11325
11210
|
const { imageKey } = await uploadImageLark({
|
|
11326
11211
|
cfg: this.cfg,
|
|
@@ -11328,7 +11213,7 @@ var ImageResolver = class {
|
|
|
11328
11213
|
imageType: "message",
|
|
11329
11214
|
accountId: this.accountId
|
|
11330
11215
|
});
|
|
11331
|
-
log$
|
|
11216
|
+
log$18.info("image uploaded", {
|
|
11332
11217
|
url,
|
|
11333
11218
|
imageKey
|
|
11334
11219
|
});
|
|
@@ -11337,7 +11222,7 @@ var ImageResolver = class {
|
|
|
11337
11222
|
this.onImageResolved();
|
|
11338
11223
|
return imageKey;
|
|
11339
11224
|
} catch (err) {
|
|
11340
|
-
log$
|
|
11225
|
+
log$18.warn("image upload failed", {
|
|
11341
11226
|
url,
|
|
11342
11227
|
error: String(err)
|
|
11343
11228
|
});
|
|
@@ -11358,7 +11243,7 @@ var ImageResolver = class {
|
|
|
11358
11243
|
* Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
|
|
11359
11244
|
* logic previously scattered as closures in reply-dispatcher.ts.
|
|
11360
11245
|
*/
|
|
11361
|
-
const log$
|
|
11246
|
+
const log$17 = larkLogger("card/unavailable-guard");
|
|
11362
11247
|
var UnavailableGuard = class {
|
|
11363
11248
|
terminated = false;
|
|
11364
11249
|
replyToMessageId;
|
|
@@ -11411,7 +11296,7 @@ var UnavailableGuard = class {
|
|
|
11411
11296
|
this.terminated = true;
|
|
11412
11297
|
this.onTerminate();
|
|
11413
11298
|
const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
|
|
11414
|
-
log$
|
|
11299
|
+
log$17.warn("reply pipeline terminated by unavailable message", {
|
|
11415
11300
|
source,
|
|
11416
11301
|
apiCode,
|
|
11417
11302
|
messageId: affectedMessageId
|
|
@@ -11448,7 +11333,7 @@ function getActiveCard(sessionId) {
|
|
|
11448
11333
|
* Delegates throttling to FlushController and message-unavailable
|
|
11449
11334
|
* detection to UnavailableGuard.
|
|
11450
11335
|
*/
|
|
11451
|
-
const log$
|
|
11336
|
+
const log$16 = larkLogger("card/streaming");
|
|
11452
11337
|
var StreamingCardController = class StreamingCardController {
|
|
11453
11338
|
phase = "idle";
|
|
11454
11339
|
cardKit = {
|
|
@@ -11536,7 +11421,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11536
11421
|
}
|
|
11537
11422
|
}
|
|
11538
11423
|
if (!entry) {
|
|
11539
|
-
log$
|
|
11424
|
+
log$16.debug("footer metrics lookup: session entry missing", {
|
|
11540
11425
|
sessionKey: this.deps.sessionKey,
|
|
11541
11426
|
candidateKeys,
|
|
11542
11427
|
storePath,
|
|
@@ -11554,7 +11439,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11554
11439
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
11555
11440
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
11556
11441
|
};
|
|
11557
|
-
log$
|
|
11442
|
+
log$16.debug("footer metrics lookup: session entry found", {
|
|
11558
11443
|
sessionKey: this.deps.sessionKey,
|
|
11559
11444
|
matchedKey,
|
|
11560
11445
|
storePath,
|
|
@@ -11579,7 +11464,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11579
11464
|
}
|
|
11580
11465
|
}
|
|
11581
11466
|
if (!entry) {
|
|
11582
|
-
log$
|
|
11467
|
+
log$16.debug("footer metrics lookup: session entry missing", {
|
|
11583
11468
|
sessionKey: this.deps.sessionKey,
|
|
11584
11469
|
candidateKeys,
|
|
11585
11470
|
storePath,
|
|
@@ -11597,7 +11482,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11597
11482
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
11598
11483
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
11599
11484
|
};
|
|
11600
|
-
log$
|
|
11485
|
+
log$16.debug("footer metrics lookup: session entry found", {
|
|
11601
11486
|
sessionKey: this.deps.sessionKey,
|
|
11602
11487
|
matchedKey,
|
|
11603
11488
|
storePath,
|
|
@@ -11605,7 +11490,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11605
11490
|
});
|
|
11606
11491
|
return metrics;
|
|
11607
11492
|
} catch (err) {
|
|
11608
|
-
log$
|
|
11493
|
+
log$16.warn("footer metrics lookup failed", {
|
|
11609
11494
|
error: String(err),
|
|
11610
11495
|
sessionKey: this.deps.sessionKey
|
|
11611
11496
|
});
|
|
@@ -11712,7 +11597,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11712
11597
|
const from = this.phase;
|
|
11713
11598
|
if (from === to) return false;
|
|
11714
11599
|
if (!PHASE_TRANSITIONS[from].has(to)) {
|
|
11715
|
-
log$
|
|
11600
|
+
log$16.warn("phase transition rejected", {
|
|
11716
11601
|
from,
|
|
11717
11602
|
to,
|
|
11718
11603
|
source
|
|
@@ -11720,7 +11605,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11720
11605
|
return false;
|
|
11721
11606
|
}
|
|
11722
11607
|
this.phase = to;
|
|
11723
|
-
log$
|
|
11608
|
+
log$16.info("phase transition", {
|
|
11724
11609
|
from,
|
|
11725
11610
|
to,
|
|
11726
11611
|
source,
|
|
@@ -11840,7 +11725,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11840
11725
|
this.reasoning.dirty = true;
|
|
11841
11726
|
}
|
|
11842
11727
|
const text = split.answerText ?? stripReasoningTags(rawText);
|
|
11843
|
-
log$
|
|
11728
|
+
log$16.debug("onPartialReply", { len: text.length });
|
|
11844
11729
|
if (!text) return;
|
|
11845
11730
|
this.captureToolUseElapsed();
|
|
11846
11731
|
if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
|
|
@@ -11852,7 +11737,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11852
11737
|
this.text.lastPartialText = text;
|
|
11853
11738
|
this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
|
|
11854
11739
|
if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
|
|
11855
|
-
log$
|
|
11740
|
+
log$16.debug("onPartialReply: buffering NO_REPLY prefix");
|
|
11856
11741
|
return;
|
|
11857
11742
|
}
|
|
11858
11743
|
await this.ensureCardCreated();
|
|
@@ -11862,7 +11747,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11862
11747
|
}
|
|
11863
11748
|
async onError(err, info) {
|
|
11864
11749
|
if (this.guard.terminate("onError", err)) return;
|
|
11865
|
-
log$
|
|
11750
|
+
log$16.error(`${info.kind} reply failed`, { error: String(err) });
|
|
11866
11751
|
this.captureToolUseElapsed();
|
|
11867
11752
|
this.finalizeCard("onError", "error");
|
|
11868
11753
|
await this.flush.waitForFlush();
|
|
@@ -11918,7 +11803,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11918
11803
|
if (this.cardKit.cardMessageId) {
|
|
11919
11804
|
const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
|
|
11920
11805
|
const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
|
|
11921
|
-
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");
|
|
11922
11807
|
const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
|
|
11923
11808
|
const idleToolUseDisplay = this.computeToolUseDisplay();
|
|
11924
11809
|
const terminalContent = prepareTerminalCardContent({
|
|
@@ -11946,7 +11831,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11946
11831
|
const rewindSessionId = this.deps.sessionKey;
|
|
11947
11832
|
const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
|
|
11948
11833
|
const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
|
|
11949
|
-
log$
|
|
11834
|
+
log$16.info("onIdle: rewind button conditions", {
|
|
11950
11835
|
sessionId: rewindSessionId,
|
|
11951
11836
|
messageId: rewindMessageId,
|
|
11952
11837
|
hasFileChanges: rewindHasFileChanges,
|
|
@@ -11959,7 +11844,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11959
11844
|
if (idleEffectiveCardId) {
|
|
11960
11845
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
11961
11846
|
this.cardKit.cardKitSequence += 1;
|
|
11962
|
-
log$
|
|
11847
|
+
log$16.info("onIdle: closing streaming mode", {
|
|
11963
11848
|
attempt,
|
|
11964
11849
|
seqBefore: seqBeforeClose,
|
|
11965
11850
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -11973,7 +11858,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11973
11858
|
});
|
|
11974
11859
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
11975
11860
|
this.cardKit.cardKitSequence += 1;
|
|
11976
|
-
log$
|
|
11861
|
+
log$16.info("onIdle: updating final card", {
|
|
11977
11862
|
attempt,
|
|
11978
11863
|
seqBefore: seqBeforeUpdate,
|
|
11979
11864
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -11991,33 +11876,33 @@ var StreamingCardController = class StreamingCardController {
|
|
|
11991
11876
|
card: completeCard,
|
|
11992
11877
|
accountId: this.deps.accountId
|
|
11993
11878
|
});
|
|
11994
|
-
log$
|
|
11879
|
+
log$16.info("reply completed, card finalized", {
|
|
11995
11880
|
elapsedMs: this.elapsed(),
|
|
11996
11881
|
isCardKit: !!idleEffectiveCardId,
|
|
11997
11882
|
attempt
|
|
11998
11883
|
});
|
|
11999
11884
|
break;
|
|
12000
11885
|
} catch (retryErr) {
|
|
12001
|
-
log$
|
|
11886
|
+
log$16.warn("final card update attempt failed", {
|
|
12002
11887
|
attempt,
|
|
12003
11888
|
maxRetries: MAX_FINAL_UPDATE_RETRIES,
|
|
12004
11889
|
error: String(retryErr)
|
|
12005
11890
|
});
|
|
12006
11891
|
if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
|
|
12007
|
-
else log$
|
|
11892
|
+
else log$16.error("final card update exhausted all retries, card may remain in streaming state", {
|
|
12008
11893
|
cardId: idleEffectiveCardId,
|
|
12009
11894
|
messageId: this.cardKit.cardMessageId
|
|
12010
11895
|
});
|
|
12011
11896
|
}
|
|
12012
11897
|
}
|
|
12013
11898
|
} catch (err) {
|
|
12014
|
-
log$
|
|
11899
|
+
log$16.error("final card update failed (outer)", { error: String(err) });
|
|
12015
11900
|
} finally {
|
|
12016
11901
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12017
11902
|
}
|
|
12018
11903
|
}
|
|
12019
11904
|
markFullyComplete() {
|
|
12020
|
-
log$
|
|
11905
|
+
log$16.debug("markFullyComplete", {
|
|
12021
11906
|
completedTextLen: this.text.completedText.length,
|
|
12022
11907
|
accumulatedTextLen: this.text.accumulatedText.length
|
|
12023
11908
|
});
|
|
@@ -12056,7 +11941,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12056
11941
|
footerMetrics
|
|
12057
11942
|
});
|
|
12058
11943
|
await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
|
|
12059
|
-
log$
|
|
11944
|
+
log$16.info("abortCard completed", { effectiveCardId });
|
|
12060
11945
|
} else if (this.cardKit.cardMessageId) {
|
|
12061
11946
|
const abortCard = buildCardContent("complete", {
|
|
12062
11947
|
text: terminalContent.text,
|
|
@@ -12077,10 +11962,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12077
11962
|
card: abortCard,
|
|
12078
11963
|
accountId: this.deps.accountId
|
|
12079
11964
|
});
|
|
12080
|
-
log$
|
|
11965
|
+
log$16.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
|
|
12081
11966
|
}
|
|
12082
11967
|
} catch (err) {
|
|
12083
|
-
log$
|
|
11968
|
+
log$16.warn("abortCard failed", { error: String(err) });
|
|
12084
11969
|
} finally {
|
|
12085
11970
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12086
11971
|
}
|
|
@@ -12095,7 +11980,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12095
11980
|
if (effectiveCardId) {
|
|
12096
11981
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
12097
11982
|
this.cardKit.cardKitSequence += 1;
|
|
12098
|
-
log$
|
|
11983
|
+
log$16.info("replaceWithClarificationCard: closing streaming mode", {
|
|
12099
11984
|
cardId: effectiveCardId,
|
|
12100
11985
|
seqBefore: seqBeforeClose,
|
|
12101
11986
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -12116,7 +12001,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12116
12001
|
sequence: this.cardKit.cardKitSequence,
|
|
12117
12002
|
accountId: this.deps.accountId
|
|
12118
12003
|
});
|
|
12119
|
-
log$
|
|
12004
|
+
log$16.info("replaceWithClarificationCard: existing card replaced", {
|
|
12120
12005
|
cardId: effectiveCardId,
|
|
12121
12006
|
seqBefore: seqBeforeUpdate,
|
|
12122
12007
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -12139,7 +12024,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12139
12024
|
this.cardKit.cardKitCardId = cardId;
|
|
12140
12025
|
this.cardKit.originalCardKitCardId = cardId;
|
|
12141
12026
|
this.cardKit.cardMessageId = result.messageId;
|
|
12142
|
-
log$
|
|
12027
|
+
log$16.info("replaceWithClarificationCard: new clarification card sent", {
|
|
12143
12028
|
cardId,
|
|
12144
12029
|
messageId: result.messageId
|
|
12145
12030
|
});
|
|
@@ -12148,7 +12033,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12148
12033
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
12149
12034
|
return true;
|
|
12150
12035
|
} catch (err) {
|
|
12151
|
-
log$
|
|
12036
|
+
log$16.error("replaceWithClarificationCard failed", { error: String(err) });
|
|
12152
12037
|
return false;
|
|
12153
12038
|
}
|
|
12154
12039
|
}
|
|
@@ -12162,10 +12047,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12162
12047
|
async forceCloseStreaming() {
|
|
12163
12048
|
const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
12164
12049
|
if (!effectiveCardId && !this.cardKit.cardMessageId) {
|
|
12165
|
-
log$
|
|
12050
|
+
log$16.warn("forceCloseStreaming: no card to close");
|
|
12166
12051
|
return;
|
|
12167
12052
|
}
|
|
12168
|
-
log$
|
|
12053
|
+
log$16.info("forceCloseStreaming: 强制终态化卡片", {
|
|
12169
12054
|
cardId: effectiveCardId,
|
|
12170
12055
|
messageId: this.cardKit.cardMessageId,
|
|
12171
12056
|
phase: this.phase
|
|
@@ -12215,10 +12100,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12215
12100
|
card: completeCard,
|
|
12216
12101
|
accountId: this.deps.accountId
|
|
12217
12102
|
});
|
|
12218
|
-
log$
|
|
12103
|
+
log$16.info("forceCloseStreaming: 卡片已强制终态化");
|
|
12219
12104
|
if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
|
|
12220
12105
|
} catch (err) {
|
|
12221
|
-
log$
|
|
12106
|
+
log$16.error("forceCloseStreaming failed", { error: String(err) });
|
|
12222
12107
|
} finally {
|
|
12223
12108
|
unregisterActiveCard(this.deps.sessionKey);
|
|
12224
12109
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
@@ -12242,7 +12127,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12242
12127
|
this.disposeShutdownHook = null;
|
|
12243
12128
|
return;
|
|
12244
12129
|
}
|
|
12245
|
-
log$
|
|
12130
|
+
log$16.info("reusing placeholder card", {
|
|
12246
12131
|
cardId: this.deps.placeholderCardId,
|
|
12247
12132
|
messageId: this.deps.placeholderMessageId
|
|
12248
12133
|
});
|
|
@@ -12266,7 +12151,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12266
12151
|
accountId: this.deps.accountId
|
|
12267
12152
|
});
|
|
12268
12153
|
if (this.isStaleCreate(epoch)) {
|
|
12269
|
-
log$
|
|
12154
|
+
log$16.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
|
|
12270
12155
|
epoch,
|
|
12271
12156
|
phase: this.phase
|
|
12272
12157
|
});
|
|
@@ -12277,7 +12162,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12277
12162
|
this.cardKit.originalCardKitCardId = cId;
|
|
12278
12163
|
this.cardKit.cardKitSequence = 1;
|
|
12279
12164
|
this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
|
|
12280
|
-
log$
|
|
12165
|
+
log$16.info("created CardKit entity", {
|
|
12281
12166
|
cardId: cId,
|
|
12282
12167
|
initialSequence: this.cardKit.cardKitSequence
|
|
12283
12168
|
});
|
|
@@ -12290,7 +12175,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12290
12175
|
accountId: this.deps.accountId
|
|
12291
12176
|
});
|
|
12292
12177
|
if (this.isStaleCreate(epoch)) {
|
|
12293
|
-
log$
|
|
12178
|
+
log$16.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
|
|
12294
12179
|
epoch,
|
|
12295
12180
|
phase: this.phase
|
|
12296
12181
|
});
|
|
@@ -12305,13 +12190,13 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12305
12190
|
this.disposeShutdownHook = null;
|
|
12306
12191
|
return;
|
|
12307
12192
|
}
|
|
12308
|
-
log$
|
|
12193
|
+
log$16.info("sent CardKit card", { messageId: result.messageId });
|
|
12309
12194
|
} else throw new Error("card.create returned empty card_id");
|
|
12310
12195
|
} catch (cardKitErr) {
|
|
12311
12196
|
if (this.isStaleCreate(epoch)) return;
|
|
12312
12197
|
if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
|
|
12313
12198
|
const apiDetail = extractApiDetail(cardKitErr);
|
|
12314
|
-
log$
|
|
12199
|
+
log$16.warn("CardKit flow failed, falling back to IM", { apiDetail });
|
|
12315
12200
|
this.cardKit.cardKitCardId = null;
|
|
12316
12201
|
this.cardKit.originalCardKitCardId = null;
|
|
12317
12202
|
const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
|
|
@@ -12324,7 +12209,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12324
12209
|
accountId: this.deps.accountId
|
|
12325
12210
|
});
|
|
12326
12211
|
if (this.isStaleCreate(epoch)) {
|
|
12327
|
-
log$
|
|
12212
|
+
log$16.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
|
|
12328
12213
|
epoch,
|
|
12329
12214
|
phase: this.phase
|
|
12330
12215
|
});
|
|
@@ -12333,12 +12218,12 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12333
12218
|
this.cardKit.cardMessageId = result.messageId;
|
|
12334
12219
|
this.flush.setCardMessageReady(true);
|
|
12335
12220
|
if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
|
|
12336
|
-
log$
|
|
12221
|
+
log$16.info("sent fallback IM card", { messageId: result.messageId });
|
|
12337
12222
|
}
|
|
12338
12223
|
} catch (err) {
|
|
12339
12224
|
if (this.isStaleCreate(epoch)) return;
|
|
12340
12225
|
if (this.guard.terminate("ensureCardCreated.outer", err)) return;
|
|
12341
|
-
log$
|
|
12226
|
+
log$16.warn("thinking card failed, falling back to static", { error: String(err) });
|
|
12342
12227
|
this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
|
|
12343
12228
|
}
|
|
12344
12229
|
})();
|
|
@@ -12347,10 +12232,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12347
12232
|
async performFlush() {
|
|
12348
12233
|
if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
|
|
12349
12234
|
if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
|
|
12350
|
-
log$
|
|
12235
|
+
log$16.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
|
|
12351
12236
|
return;
|
|
12352
12237
|
}
|
|
12353
|
-
log$
|
|
12238
|
+
log$16.debug("flushCardUpdate: enter", {
|
|
12354
12239
|
seq: this.cardKit.cardKitSequence,
|
|
12355
12240
|
isCardKit: !!this.cardKit.cardKitCardId
|
|
12356
12241
|
});
|
|
@@ -12372,7 +12257,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12372
12257
|
reasoningText: this.reasoning.accumulatedReasoningText || void 0
|
|
12373
12258
|
});
|
|
12374
12259
|
this.cardKit.cardKitSequence += 1;
|
|
12375
|
-
log$
|
|
12260
|
+
log$16.debug("flushCardUpdate: full card update (dirty)", {
|
|
12376
12261
|
seq: this.cardKit.cardKitSequence,
|
|
12377
12262
|
stepCount: display?.stepCount,
|
|
12378
12263
|
hasReasoning: !!this.reasoning.accumulatedReasoningText
|
|
@@ -12388,7 +12273,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12388
12273
|
} else if (resolvedText !== this.text.lastFlushedText) {
|
|
12389
12274
|
const prevSeq = this.cardKit.cardKitSequence;
|
|
12390
12275
|
this.cardKit.cardKitSequence += 1;
|
|
12391
|
-
log$
|
|
12276
|
+
log$16.debug("flushCardUpdate: answer seq bump", {
|
|
12392
12277
|
seqBefore: prevSeq,
|
|
12393
12278
|
seqAfter: this.cardKit.cardKitSequence
|
|
12394
12279
|
});
|
|
@@ -12403,7 +12288,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12403
12288
|
this.text.lastFlushedText = resolvedText;
|
|
12404
12289
|
}
|
|
12405
12290
|
} else {
|
|
12406
|
-
log$
|
|
12291
|
+
log$16.debug("flushCardUpdate: IM patch fallback");
|
|
12407
12292
|
const flushDisplay = this.computeToolUseDisplay();
|
|
12408
12293
|
const card = buildCardContent("streaming", {
|
|
12409
12294
|
text: this.reasoning.isReasoningPhase ? "" : resolvedText,
|
|
@@ -12423,31 +12308,31 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12423
12308
|
if (this.guard.terminate("flushCardUpdate", err)) return;
|
|
12424
12309
|
const apiCode = extractLarkApiCode(err);
|
|
12425
12310
|
if (isCardRateLimitError(err)) {
|
|
12426
|
-
log$
|
|
12311
|
+
log$16.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
|
|
12427
12312
|
return;
|
|
12428
12313
|
}
|
|
12429
12314
|
if (isCardTableLimitError(err)) {
|
|
12430
|
-
log$
|
|
12315
|
+
log$16.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
|
|
12431
12316
|
this.cardKit.cardKitCardId = null;
|
|
12432
12317
|
return;
|
|
12433
12318
|
}
|
|
12434
12319
|
if (apiCode === 300317 && this.cardKit.cardKitCardId) {
|
|
12435
12320
|
const oldSeq = this.cardKit.cardKitSequence;
|
|
12436
12321
|
this.cardKit.cardKitSequence += 100;
|
|
12437
|
-
log$
|
|
12322
|
+
log$16.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
|
|
12438
12323
|
oldSeq,
|
|
12439
12324
|
newSeq: this.cardKit.cardKitSequence
|
|
12440
12325
|
});
|
|
12441
12326
|
return;
|
|
12442
12327
|
}
|
|
12443
12328
|
const apiDetail = extractApiDetail(err);
|
|
12444
|
-
log$
|
|
12329
|
+
log$16.error("card stream update failed", {
|
|
12445
12330
|
apiCode,
|
|
12446
12331
|
seq: this.cardKit.cardKitSequence,
|
|
12447
12332
|
apiDetail
|
|
12448
12333
|
});
|
|
12449
12334
|
if (this.cardKit.cardKitCardId) {
|
|
12450
|
-
log$
|
|
12335
|
+
log$16.warn("disabling CardKit streaming, falling back to im.message.patch");
|
|
12451
12336
|
this.cardKit.cardKitCardId = null;
|
|
12452
12337
|
}
|
|
12453
12338
|
}
|
|
@@ -12472,7 +12357,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12472
12357
|
if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
|
|
12473
12358
|
try {
|
|
12474
12359
|
const display = this.computeToolUseDisplay();
|
|
12475
|
-
log$
|
|
12360
|
+
log$16.info("updateToolUseStatus", {
|
|
12476
12361
|
hasDisplay: !!display,
|
|
12477
12362
|
stepCount: display?.stepCount,
|
|
12478
12363
|
steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
|
|
@@ -12494,7 +12379,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12494
12379
|
accountId: this.deps.accountId
|
|
12495
12380
|
});
|
|
12496
12381
|
} catch (err) {
|
|
12497
|
-
log$
|
|
12382
|
+
log$16.debug("updateToolUseStatus failed", { error: String(err) });
|
|
12498
12383
|
}
|
|
12499
12384
|
}
|
|
12500
12385
|
finalizeCard(source, reason) {
|
|
@@ -12505,7 +12390,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12505
12390
|
const from = this.phase;
|
|
12506
12391
|
this.phase = "completed";
|
|
12507
12392
|
this._terminalReason = "normal";
|
|
12508
|
-
log$
|
|
12393
|
+
log$16.info("phase transition", {
|
|
12509
12394
|
from,
|
|
12510
12395
|
to: this.phase,
|
|
12511
12396
|
source: "replaceWithClarificationCard",
|
|
@@ -12519,7 +12404,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12519
12404
|
async closeStreamingAndUpdate(cardId, card, label) {
|
|
12520
12405
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
12521
12406
|
this.cardKit.cardKitSequence += 1;
|
|
12522
|
-
log$
|
|
12407
|
+
log$16.info(`${label}: closing streaming mode`, {
|
|
12523
12408
|
seqBefore: seqBeforeClose,
|
|
12524
12409
|
seqAfter: this.cardKit.cardKitSequence
|
|
12525
12410
|
});
|
|
@@ -12532,7 +12417,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
12532
12417
|
});
|
|
12533
12418
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
12534
12419
|
this.cardKit.cardKitSequence += 1;
|
|
12535
|
-
log$
|
|
12420
|
+
log$16.info(`${label}: updating card`, {
|
|
12536
12421
|
seqBefore: seqBeforeUpdate,
|
|
12537
12422
|
seqAfter: this.cardKit.cardKitSequence
|
|
12538
12423
|
});
|
|
@@ -12566,7 +12451,7 @@ function extractApiDetail(err) {
|
|
|
12566
12451
|
}
|
|
12567
12452
|
//#endregion
|
|
12568
12453
|
//#region src/messaging/format-for-cc.ts
|
|
12569
|
-
const log$
|
|
12454
|
+
const log$15 = larkLogger("messaging/format-for-cc");
|
|
12570
12455
|
function safeParseJSON(raw) {
|
|
12571
12456
|
try {
|
|
12572
12457
|
return JSON.parse(raw);
|
|
@@ -12622,7 +12507,7 @@ function extractPostText(rawContent) {
|
|
|
12622
12507
|
*/
|
|
12623
12508
|
function formatContentByType(ctx) {
|
|
12624
12509
|
const { contentType, content, resources } = ctx;
|
|
12625
|
-
log$
|
|
12510
|
+
log$15.debug("formatContentByType 开始格式化", {
|
|
12626
12511
|
contentType,
|
|
12627
12512
|
contentLength: content?.length ?? 0,
|
|
12628
12513
|
resourceCount: resources?.length ?? 0
|
|
@@ -12664,7 +12549,7 @@ function formatContentByType(ctx) {
|
|
|
12664
12549
|
case "todo":
|
|
12665
12550
|
case "vote": return content;
|
|
12666
12551
|
default:
|
|
12667
|
-
log$
|
|
12552
|
+
log$15.warn("遇到不支持的消息类型", { contentType });
|
|
12668
12553
|
return `[不支持的消息类型: ${contentType}]`;
|
|
12669
12554
|
}
|
|
12670
12555
|
}
|
|
@@ -12707,7 +12592,7 @@ function formatMessageTime(createTime) {
|
|
|
12707
12592
|
* @returns 格式化后的纯文本字符串
|
|
12708
12593
|
*/
|
|
12709
12594
|
function formatForCC(ctx, isGroup) {
|
|
12710
|
-
log$
|
|
12595
|
+
log$15.info("formatForCC 开始处理", {
|
|
12711
12596
|
messageId: ctx.messageId,
|
|
12712
12597
|
contentType: ctx.contentType,
|
|
12713
12598
|
chatType: ctx.chatType,
|
|
@@ -12723,7 +12608,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
12723
12608
|
if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
|
|
12724
12609
|
if (ctx.threadId) parts.push("[话题回复]");
|
|
12725
12610
|
const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
|
|
12726
|
-
log$
|
|
12611
|
+
log$15.info("formatForCC 完成", {
|
|
12727
12612
|
messageId: ctx.messageId,
|
|
12728
12613
|
resultLength: result.length,
|
|
12729
12614
|
hasTime: !!timeStr,
|
|
@@ -12731,6 +12616,41 @@ function formatForCC(ctx, isGroup) {
|
|
|
12731
12616
|
});
|
|
12732
12617
|
return result;
|
|
12733
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
|
+
}
|
|
12734
12654
|
//#endregion
|
|
12735
12655
|
//#region src/card/interactive-cards.ts
|
|
12736
12656
|
/**
|
|
@@ -12746,7 +12666,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
12746
12666
|
* - CC 输出文本后处理(检测特殊标记)
|
|
12747
12667
|
* - 操作 ID 生命周期管理
|
|
12748
12668
|
*/
|
|
12749
|
-
const log$
|
|
12669
|
+
const log$14 = larkLogger("card/interactive-cards");
|
|
12750
12670
|
const pendingOperations = /* @__PURE__ */ new Map();
|
|
12751
12671
|
const OPERATION_TTL_MS = 1800 * 1e3;
|
|
12752
12672
|
setInterval(() => {
|
|
@@ -12844,7 +12764,7 @@ function buildAuthCard(ctx) {
|
|
|
12844
12764
|
scopes: ctx.scopes
|
|
12845
12765
|
}
|
|
12846
12766
|
});
|
|
12847
|
-
log$
|
|
12767
|
+
log$14.info("已创建授权卡片操作", {
|
|
12848
12768
|
operationId,
|
|
12849
12769
|
scopes: ctx.scopes,
|
|
12850
12770
|
sessionId: ctx.sessionId
|
|
@@ -12882,7 +12802,7 @@ function buildAskCard(ctx, phaseIndex = 0) {
|
|
|
12882
12802
|
freeTextAnswers: {}
|
|
12883
12803
|
}
|
|
12884
12804
|
});
|
|
12885
|
-
log$
|
|
12805
|
+
log$14.info("已创建提问卡片操作", {
|
|
12886
12806
|
operationId,
|
|
12887
12807
|
phaseId: phase?.id,
|
|
12888
12808
|
phaseIndex,
|
|
@@ -13047,7 +12967,7 @@ function toggleAskSelection(operationId, phaseId, value) {
|
|
|
13047
12967
|
else next = current[0] === value ? [] : [value];
|
|
13048
12968
|
if (next.length > 0) op.askContext.answers[phaseId] = next;
|
|
13049
12969
|
else delete op.askContext.answers[phaseId];
|
|
13050
|
-
log$
|
|
12970
|
+
log$14.info("更新提问卡片勾选状态", {
|
|
13051
12971
|
operationId,
|
|
13052
12972
|
phaseId,
|
|
13053
12973
|
selectedValues: next
|
|
@@ -13063,7 +12983,7 @@ function setAskFreeTextAnswer(operationId, phaseId, value) {
|
|
|
13063
12983
|
const text = normalizeFreeText(value);
|
|
13064
12984
|
if (text) op.askContext.freeTextAnswers[phaseId] = text;
|
|
13065
12985
|
else delete op.askContext.freeTextAnswers[phaseId];
|
|
13066
|
-
log$
|
|
12986
|
+
log$14.info("更新提问卡片自由文本", {
|
|
13067
12987
|
operationId,
|
|
13068
12988
|
phaseId,
|
|
13069
12989
|
hasText: text.length > 0
|
|
@@ -13307,7 +13227,7 @@ async function trySendClarificationFallback(params) {
|
|
|
13307
13227
|
}
|
|
13308
13228
|
//#endregion
|
|
13309
13229
|
//#region src/messaging/inbound/dispatch-cc.ts
|
|
13310
|
-
const log$
|
|
13230
|
+
const log$13 = larkLogger("inbound/dispatch-cc");
|
|
13311
13231
|
function buildFeishuRuntimeConfig(params) {
|
|
13312
13232
|
const { ctx, account, route } = params;
|
|
13313
13233
|
const senderUserId = ctx.rawSender.sender_id.user_id;
|
|
@@ -13364,7 +13284,7 @@ async function dispatchToCC(params) {
|
|
|
13364
13284
|
const ccModel = params.model;
|
|
13365
13285
|
const maxTurns = params.maxTurns;
|
|
13366
13286
|
const maxBudgetUsd = params.maxBudgetUsd;
|
|
13367
|
-
log$
|
|
13287
|
+
log$13.info("dispatchToCC 开始", {
|
|
13368
13288
|
msgId,
|
|
13369
13289
|
chatId,
|
|
13370
13290
|
chatType,
|
|
@@ -13377,12 +13297,12 @@ async function dispatchToCC(params) {
|
|
|
13377
13297
|
threadId,
|
|
13378
13298
|
userId: ctx.senderId
|
|
13379
13299
|
});
|
|
13380
|
-
log$
|
|
13300
|
+
log$13.info("会话路由解析完成", {
|
|
13381
13301
|
sessionId: route.sessionId,
|
|
13382
13302
|
cwd: route.cwd,
|
|
13383
13303
|
type: route.type
|
|
13384
13304
|
});
|
|
13385
|
-
if (params.userContext) log$
|
|
13305
|
+
if (params.userContext) log$13.info("用户上下文已注入", {
|
|
13386
13306
|
userId: params.userContext.userId,
|
|
13387
13307
|
isTenantIdentity: params.userContext.isTenantIdentity,
|
|
13388
13308
|
credentialDir: params.userContext.credentialDir
|
|
@@ -13391,10 +13311,7 @@ async function dispatchToCC(params) {
|
|
|
13391
13311
|
const isGroup = chatType === "group";
|
|
13392
13312
|
const replyInThread = !!threadId;
|
|
13393
13313
|
const replyToMessageId = threadId ? void 0 : ctx.messageId;
|
|
13394
|
-
|
|
13395
|
-
if (isGroup && replyInThread) textPrompt = `[群聊话题] ${textPrompt}`;
|
|
13396
|
-
else if (isGroup) textPrompt = `[群聊] ${textPrompt}`;
|
|
13397
|
-
else textPrompt = `[私聊] ${textPrompt}`;
|
|
13314
|
+
const textPrompt = formatPromptForCC(ctx, isGroup, params.quotedContent);
|
|
13398
13315
|
const imageResources = ctx.resources.filter((r) => r.type === "image");
|
|
13399
13316
|
let prompt = textPrompt;
|
|
13400
13317
|
if (imageResources.length > 0) {
|
|
@@ -13421,7 +13338,7 @@ async function dispatchToCC(params) {
|
|
|
13421
13338
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
13422
13339
|
buffer = Buffer.concat(chunks);
|
|
13423
13340
|
} else {
|
|
13424
|
-
log$
|
|
13341
|
+
log$13.warn("图片资源下载返回未知格式,跳过", {
|
|
13425
13342
|
fileKey: imgRes.fileKey,
|
|
13426
13343
|
responseType: typeof response
|
|
13427
13344
|
});
|
|
@@ -13429,7 +13346,7 @@ async function dispatchToCC(params) {
|
|
|
13429
13346
|
}
|
|
13430
13347
|
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
13431
13348
|
} else {
|
|
13432
|
-
log$
|
|
13349
|
+
log$13.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
|
|
13433
13350
|
continue;
|
|
13434
13351
|
}
|
|
13435
13352
|
const imgFileName = `${imgRes.fileKey}.png`;
|
|
@@ -13439,13 +13356,13 @@ async function dispatchToCC(params) {
|
|
|
13439
13356
|
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
13440
13357
|
await mkdir(filesDir, { recursive: true });
|
|
13441
13358
|
await writeFile(imgFilePath, buffer);
|
|
13442
|
-
log$
|
|
13359
|
+
log$13.info("图片已保存到工作目录", {
|
|
13443
13360
|
fileKey: imgRes.fileKey,
|
|
13444
13361
|
path: imgFilePath,
|
|
13445
13362
|
sizeBytes: buffer.length
|
|
13446
13363
|
});
|
|
13447
13364
|
} catch (saveErr) {
|
|
13448
|
-
log$
|
|
13365
|
+
log$13.warn("图片保存到工作目录失败", {
|
|
13449
13366
|
fileKey: imgRes.fileKey,
|
|
13450
13367
|
path: imgFilePath,
|
|
13451
13368
|
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
@@ -13460,13 +13377,13 @@ async function dispatchToCC(params) {
|
|
|
13460
13377
|
data: base64Data
|
|
13461
13378
|
}
|
|
13462
13379
|
});
|
|
13463
|
-
log$
|
|
13380
|
+
log$13.info("图片资源下载成功", {
|
|
13464
13381
|
fileKey: imgRes.fileKey,
|
|
13465
13382
|
mediaType,
|
|
13466
13383
|
sizeBytes: buffer.length
|
|
13467
13384
|
});
|
|
13468
13385
|
} catch (err) {
|
|
13469
|
-
log$
|
|
13386
|
+
log$13.warn("图片资源下载失败,跳过", {
|
|
13470
13387
|
fileKey: imgRes.fileKey,
|
|
13471
13388
|
error: err instanceof Error ? err.message : String(err)
|
|
13472
13389
|
});
|
|
@@ -13476,7 +13393,7 @@ async function dispatchToCC(params) {
|
|
|
13476
13393
|
type: "text",
|
|
13477
13394
|
text: textPrompt
|
|
13478
13395
|
}, ...imageBlocks];
|
|
13479
|
-
log$
|
|
13396
|
+
log$13.info("已构建多模态 prompt", {
|
|
13480
13397
|
textLength: textPrompt.length,
|
|
13481
13398
|
imageCount: imageBlocks.length
|
|
13482
13399
|
});
|
|
@@ -13484,12 +13401,12 @@ async function dispatchToCC(params) {
|
|
|
13484
13401
|
const textWithoutImageRefs = textPrompt.replace(/!\[image\]\([^)]*\)/g, "").replace(/\[图片\]\s*/g, "").trim();
|
|
13485
13402
|
if (textWithoutImageRefs.length > 0) {
|
|
13486
13403
|
prompt = textPrompt.replace(/!\[image\]\([^)]*\)/g, "[图片加载失败]");
|
|
13487
|
-
log$
|
|
13404
|
+
log$13.info("图片下载失败但保留文字内容继续调度", {
|
|
13488
13405
|
messageId: ctx.messageId,
|
|
13489
13406
|
textLength: textWithoutImageRefs.length
|
|
13490
13407
|
});
|
|
13491
13408
|
} else {
|
|
13492
|
-
log$
|
|
13409
|
+
log$13.warn("纯图片消息且图片全部下载失败,中止调度", {
|
|
13493
13410
|
messageId: ctx.messageId,
|
|
13494
13411
|
expectedImages: imageResources.length
|
|
13495
13412
|
});
|
|
@@ -13511,7 +13428,7 @@ async function dispatchToCC(params) {
|
|
|
13511
13428
|
await fsMkdir(filesDir, { recursive: true });
|
|
13512
13429
|
let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
|
|
13513
13430
|
for (const res of attachmentResources) try {
|
|
13514
|
-
log$
|
|
13431
|
+
log$13.info("开始下载非图片附件", {
|
|
13515
13432
|
type: res.type,
|
|
13516
13433
|
fileKey: res.fileKey,
|
|
13517
13434
|
fileName: res.fileName,
|
|
@@ -13525,7 +13442,7 @@ async function dispatchToCC(params) {
|
|
|
13525
13442
|
},
|
|
13526
13443
|
params: { type: "file" }
|
|
13527
13444
|
});
|
|
13528
|
-
log$
|
|
13445
|
+
log$13.info("非图片附件 API 响应已收到", {
|
|
13529
13446
|
type: res.type,
|
|
13530
13447
|
fileKey: res.fileKey,
|
|
13531
13448
|
responseType: typeof response,
|
|
@@ -13544,7 +13461,7 @@ async function dispatchToCC(params) {
|
|
|
13544
13461
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
13545
13462
|
buffer = Buffer.concat(chunks);
|
|
13546
13463
|
} else {
|
|
13547
|
-
log$
|
|
13464
|
+
log$13.warn("非图片附件下载返回未知格式,跳过", {
|
|
13548
13465
|
type: res.type,
|
|
13549
13466
|
fileKey: res.fileKey,
|
|
13550
13467
|
responseType: typeof response
|
|
@@ -13552,7 +13469,7 @@ async function dispatchToCC(params) {
|
|
|
13552
13469
|
continue;
|
|
13553
13470
|
}
|
|
13554
13471
|
} else {
|
|
13555
|
-
log$
|
|
13472
|
+
log$13.warn("非图片附件下载返回空,跳过", {
|
|
13556
13473
|
type: res.type,
|
|
13557
13474
|
fileKey: res.fileKey
|
|
13558
13475
|
});
|
|
@@ -13575,7 +13492,7 @@ async function dispatchToCC(params) {
|
|
|
13575
13492
|
}
|
|
13576
13493
|
const filePath = path.join(filesDir, savedFileName);
|
|
13577
13494
|
await fsWriteFile(filePath, buffer);
|
|
13578
|
-
log$
|
|
13495
|
+
log$13.info("非图片附件已保存到工作目录", {
|
|
13579
13496
|
type: res.type,
|
|
13580
13497
|
fileKey: res.fileKey,
|
|
13581
13498
|
savedFileName,
|
|
@@ -13609,7 +13526,7 @@ async function dispatchToCC(params) {
|
|
|
13609
13526
|
}
|
|
13610
13527
|
}
|
|
13611
13528
|
} catch (err) {
|
|
13612
|
-
log$
|
|
13529
|
+
log$13.warn("非图片附件下载失败,替换为降级描述", {
|
|
13613
13530
|
type: res.type,
|
|
13614
13531
|
fileKey: res.fileKey,
|
|
13615
13532
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -13628,12 +13545,12 @@ async function dispatchToCC(params) {
|
|
|
13628
13545
|
const textBlock = prompt.find((b) => b.type === "text");
|
|
13629
13546
|
if (textBlock) textBlock.text = updatedTextPrompt;
|
|
13630
13547
|
}
|
|
13631
|
-
log$
|
|
13548
|
+
log$13.info("非图片附件处理完成", {
|
|
13632
13549
|
totalAttachments: attachmentResources.length,
|
|
13633
13550
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
|
|
13634
13551
|
});
|
|
13635
13552
|
}
|
|
13636
|
-
log$
|
|
13553
|
+
log$13.info("消息格式化完成", {
|
|
13637
13554
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
|
|
13638
13555
|
isMultimodal: Array.isArray(prompt),
|
|
13639
13556
|
isGroup
|
|
@@ -13665,19 +13582,19 @@ async function dispatchToCC(params) {
|
|
|
13665
13582
|
};
|
|
13666
13583
|
const cardController = new StreamingCardController(cardDeps);
|
|
13667
13584
|
registerActiveCard(route.sessionId, cardController);
|
|
13668
|
-
log$
|
|
13585
|
+
log$13.info("StreamingCardController 已创建", {
|
|
13669
13586
|
sessionId: route.sessionId,
|
|
13670
13587
|
chatId,
|
|
13671
13588
|
replyToMessageId: cardDeps.replyToMessageId
|
|
13672
13589
|
});
|
|
13673
13590
|
cardController.ensureCardCreated().catch((err) => {
|
|
13674
|
-
log$
|
|
13591
|
+
log$13.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
|
|
13675
13592
|
});
|
|
13676
13593
|
let promptForRuntime = prompt;
|
|
13677
13594
|
try {
|
|
13678
13595
|
promptForRuntime = await new MemoryContextBuilder().injectPrompt(route.cwd, prompt, promptToText(prompt));
|
|
13679
13596
|
} catch (err) {
|
|
13680
|
-
log$
|
|
13597
|
+
log$13.warn("memory context 注入失败,继续使用原始 prompt", {
|
|
13681
13598
|
sessionId: route.sessionId,
|
|
13682
13599
|
error: err instanceof Error ? err.message : String(err)
|
|
13683
13600
|
});
|
|
@@ -13698,7 +13615,7 @@ async function dispatchToCC(params) {
|
|
|
13698
13615
|
});
|
|
13699
13616
|
} catch (err) {
|
|
13700
13617
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
13701
|
-
log$
|
|
13618
|
+
log$13.error("飞书事件 run context 构造失败,终止调度", {
|
|
13702
13619
|
sessionId: route.sessionId,
|
|
13703
13620
|
messageId: msgId,
|
|
13704
13621
|
error: errorMessage
|
|
@@ -13716,7 +13633,7 @@ async function dispatchToCC(params) {
|
|
|
13716
13633
|
channel: "feishu-bot",
|
|
13717
13634
|
title: userContent.slice(0, 30) + (userContent.length > 30 ? "..." : "")
|
|
13718
13635
|
}).catch((err) => {
|
|
13719
|
-
log$
|
|
13636
|
+
log$13.warn("创建飞书端会话记录失败", { error: String(err) });
|
|
13720
13637
|
});
|
|
13721
13638
|
params.messageStore.appendMessage({
|
|
13722
13639
|
sessionId: storeSessionId,
|
|
@@ -13730,14 +13647,14 @@ async function dispatchToCC(params) {
|
|
|
13730
13647
|
scenarioId: runtimeConfig.runContext?.scenarioId
|
|
13731
13648
|
}
|
|
13732
13649
|
}).catch((err) => {
|
|
13733
|
-
log$
|
|
13650
|
+
log$13.warn("记录飞书用户消息到 store 失败", { error: String(err) });
|
|
13734
13651
|
});
|
|
13735
13652
|
}
|
|
13736
13653
|
const bridge = new CCStreamBridge(cardController, {
|
|
13737
13654
|
autoCompleteOnTurnEnd: true,
|
|
13738
13655
|
sessionKey: route.sessionId
|
|
13739
13656
|
});
|
|
13740
|
-
log$
|
|
13657
|
+
log$13.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
|
|
13741
13658
|
let feishuAccumText = "";
|
|
13742
13659
|
let usedAskUserTool = false;
|
|
13743
13660
|
let pendingEndTurn = null;
|
|
@@ -13759,12 +13676,12 @@ async function dispatchToCC(params) {
|
|
|
13759
13676
|
...metadata
|
|
13760
13677
|
}
|
|
13761
13678
|
}).catch((err) => {
|
|
13762
|
-
log$
|
|
13679
|
+
log$13.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
|
|
13763
13680
|
});
|
|
13764
13681
|
};
|
|
13765
13682
|
const appendRuntimeEventMessage = (event) => {
|
|
13766
13683
|
const eventSummary = summarizeForAudit(event);
|
|
13767
|
-
log$
|
|
13684
|
+
log$13.info("CC onRuntimeEvent", {
|
|
13768
13685
|
sessionId: route.sessionId,
|
|
13769
13686
|
type: event.type,
|
|
13770
13687
|
traceId: runtimeConfig.runContext?.traceId,
|
|
@@ -13802,11 +13719,11 @@ async function dispatchToCC(params) {
|
|
|
13802
13719
|
finalStatus: event.finalStatus
|
|
13803
13720
|
}
|
|
13804
13721
|
}).catch((err) => {
|
|
13805
|
-
log$
|
|
13722
|
+
log$13.warn("记录飞书 runtime event 到 store 失败", { error: String(err) });
|
|
13806
13723
|
});
|
|
13807
13724
|
};
|
|
13808
13725
|
const handleResult = async (result) => {
|
|
13809
|
-
log$
|
|
13726
|
+
log$13.info("CC onResult", {
|
|
13810
13727
|
sessionId: route.sessionId,
|
|
13811
13728
|
subtype: result.subtype,
|
|
13812
13729
|
isError: result.isError,
|
|
@@ -13834,13 +13751,13 @@ async function dispatchToCC(params) {
|
|
|
13834
13751
|
})
|
|
13835
13752
|
});
|
|
13836
13753
|
} catch (err) {
|
|
13837
|
-
log$
|
|
13754
|
+
log$13.warn("clarification gate: 兜底处理失败,回退到普通回复", {
|
|
13838
13755
|
sessionId: route.sessionId,
|
|
13839
13756
|
error: err instanceof Error ? err.message : String(err)
|
|
13840
13757
|
});
|
|
13841
13758
|
}
|
|
13842
13759
|
if (clarificationFallback.handled) {
|
|
13843
|
-
log$
|
|
13760
|
+
log$13.info("clarification gate: 普通文本提问已转为提问卡片", {
|
|
13844
13761
|
sessionId: route.sessionId,
|
|
13845
13762
|
operationId: clarificationFallback.operationId,
|
|
13846
13763
|
reason: clarificationFallback.reason,
|
|
@@ -13848,7 +13765,7 @@ async function dispatchToCC(params) {
|
|
|
13848
13765
|
});
|
|
13849
13766
|
return;
|
|
13850
13767
|
}
|
|
13851
|
-
if (clarificationFallback.matched) log$
|
|
13768
|
+
if (clarificationFallback.matched) log$13.warn("clarification gate: 提问卡片替换失败,回退到普通回复", {
|
|
13852
13769
|
sessionId: route.sessionId,
|
|
13853
13770
|
operationId: clarificationFallback.operationId
|
|
13854
13771
|
});
|
|
@@ -13861,7 +13778,7 @@ async function dispatchToCC(params) {
|
|
|
13861
13778
|
};
|
|
13862
13779
|
const handleBlockedEvent = (event) => {
|
|
13863
13780
|
const sanitized = sanitizeBlockedEvent(event);
|
|
13864
|
-
log$
|
|
13781
|
+
log$13.warn("CC onBlockedEvent", {
|
|
13865
13782
|
...sanitized,
|
|
13866
13783
|
traceId: runtimeConfig.runContext?.traceId,
|
|
13867
13784
|
scenarioId: runtimeConfig.runContext?.scenarioId
|
|
@@ -13869,7 +13786,7 @@ async function dispatchToCC(params) {
|
|
|
13869
13786
|
};
|
|
13870
13787
|
const callbacks = {
|
|
13871
13788
|
onTextDelta: (text) => {
|
|
13872
|
-
log$
|
|
13789
|
+
log$13.debug("CC onTextDelta", {
|
|
13873
13790
|
sessionId: route.sessionId,
|
|
13874
13791
|
deltaLen: text.length
|
|
13875
13792
|
});
|
|
@@ -13877,14 +13794,14 @@ async function dispatchToCC(params) {
|
|
|
13877
13794
|
feishuAccumText += text;
|
|
13878
13795
|
},
|
|
13879
13796
|
onThinkingDelta: (text) => {
|
|
13880
|
-
log$
|
|
13797
|
+
log$13.debug("CC onThinkingDelta", {
|
|
13881
13798
|
sessionId: route.sessionId,
|
|
13882
13799
|
deltaLen: text.length
|
|
13883
13800
|
});
|
|
13884
13801
|
bridge.onThinkingDelta(text);
|
|
13885
13802
|
},
|
|
13886
13803
|
onToolUseStart: (toolName, toolInput) => {
|
|
13887
|
-
log$
|
|
13804
|
+
log$13.info("CC onToolUseStart", {
|
|
13888
13805
|
sessionId: route.sessionId,
|
|
13889
13806
|
toolName
|
|
13890
13807
|
});
|
|
@@ -13892,7 +13809,7 @@ async function dispatchToCC(params) {
|
|
|
13892
13809
|
bridge.onToolUseStart(toolName, toolInput);
|
|
13893
13810
|
},
|
|
13894
13811
|
onToolResult: (toolUseId, result) => {
|
|
13895
|
-
log$
|
|
13812
|
+
log$13.info("CC onToolResult", {
|
|
13896
13813
|
sessionId: route.sessionId,
|
|
13897
13814
|
toolUseId,
|
|
13898
13815
|
traceId: runtimeConfig.runContext?.traceId,
|
|
@@ -13901,7 +13818,7 @@ async function dispatchToCC(params) {
|
|
|
13901
13818
|
bridge.onToolResult(toolUseId);
|
|
13902
13819
|
},
|
|
13903
13820
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
13904
|
-
log$
|
|
13821
|
+
log$13.debug("CC onToolProgress", {
|
|
13905
13822
|
sessionId: route.sessionId,
|
|
13906
13823
|
toolName,
|
|
13907
13824
|
elapsedSeconds
|
|
@@ -13909,7 +13826,7 @@ async function dispatchToCC(params) {
|
|
|
13909
13826
|
bridge.onToolProgress(toolName, elapsedSeconds);
|
|
13910
13827
|
},
|
|
13911
13828
|
onTurnEnd: (stopReason) => {
|
|
13912
|
-
log$
|
|
13829
|
+
log$13.info("CC onTurnEnd", {
|
|
13913
13830
|
sessionId: route.sessionId,
|
|
13914
13831
|
stopReason
|
|
13915
13832
|
});
|
|
@@ -13921,14 +13838,14 @@ async function dispatchToCC(params) {
|
|
|
13921
13838
|
},
|
|
13922
13839
|
onResult: (result) => {
|
|
13923
13840
|
resultHandlingPromise = handleResult(result).catch((err) => {
|
|
13924
|
-
log$
|
|
13841
|
+
log$13.error("CC onResult 收尾处理失败", {
|
|
13925
13842
|
sessionId: route.sessionId,
|
|
13926
13843
|
error: err instanceof Error ? err.message : String(err)
|
|
13927
13844
|
});
|
|
13928
13845
|
});
|
|
13929
13846
|
},
|
|
13930
13847
|
onError: (error) => {
|
|
13931
|
-
log$
|
|
13848
|
+
log$13.error("CC 进程错误", {
|
|
13932
13849
|
sessionId: route.sessionId,
|
|
13933
13850
|
traceId: runtimeConfig.runContext?.traceId,
|
|
13934
13851
|
error: error.message
|
|
@@ -13936,7 +13853,7 @@ async function dispatchToCC(params) {
|
|
|
13936
13853
|
cardController.onError(error, { kind: "cc-process" });
|
|
13937
13854
|
},
|
|
13938
13855
|
onAuditEvent: (event) => {
|
|
13939
|
-
log$
|
|
13856
|
+
log$13.info("CC onAuditEvent", {
|
|
13940
13857
|
...event,
|
|
13941
13858
|
inputSummary: summarizeForAudit(event.inputSummary),
|
|
13942
13859
|
resultSummary: summarizeForAudit(event.resultSummary)
|
|
@@ -13947,7 +13864,7 @@ async function dispatchToCC(params) {
|
|
|
13947
13864
|
onBlocked: handleBlockedEvent,
|
|
13948
13865
|
onBlockedEvent: handleBlockedEvent
|
|
13949
13866
|
};
|
|
13950
|
-
log$
|
|
13867
|
+
log$13.info("开始执行 CC 进程", {
|
|
13951
13868
|
sessionId: route.sessionId,
|
|
13952
13869
|
cwd: route.cwd,
|
|
13953
13870
|
promptLength: prompt.length,
|
|
@@ -13970,11 +13887,11 @@ async function dispatchToCC(params) {
|
|
|
13970
13887
|
onMcpCallback.on(`callback:${route.sessionId}`, mcpCallbackListener);
|
|
13971
13888
|
try {
|
|
13972
13889
|
await processManager.executePrompt(runtimeConfig, callbacks);
|
|
13973
|
-
log$
|
|
13890
|
+
log$13.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
|
|
13974
13891
|
if (resultHandlingPromise) await resultHandlingPromise;
|
|
13975
13892
|
} catch (err) {
|
|
13976
13893
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
13977
|
-
log$
|
|
13894
|
+
log$13.error("CC 进程 executePrompt 调用异常", {
|
|
13978
13895
|
sessionId: route.sessionId,
|
|
13979
13896
|
error: errorMessage
|
|
13980
13897
|
});
|
|
@@ -13998,7 +13915,7 @@ async function dispatchToCC(params) {
|
|
|
13998
13915
|
*/
|
|
13999
13916
|
async function dispatchTeammateEval(params) {
|
|
14000
13917
|
const { chatId, prompt, account, sessionRouter, processManager } = params;
|
|
14001
|
-
log$
|
|
13918
|
+
log$13.info("dispatchTeammateEval 开始", {
|
|
14002
13919
|
chatId,
|
|
14003
13920
|
promptLength: prompt.length
|
|
14004
13921
|
});
|
|
@@ -14011,7 +13928,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14011
13928
|
userName: void 0
|
|
14012
13929
|
});
|
|
14013
13930
|
if (processManager.isSessionBusy?.(route.sessionId)) {
|
|
14014
|
-
log$
|
|
13931
|
+
log$13.info("dispatchTeammateEval 跳过:主 session 正在执行", {
|
|
14015
13932
|
chatId,
|
|
14016
13933
|
sessionId: route.sessionId
|
|
14017
13934
|
});
|
|
@@ -14074,7 +13991,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14074
13991
|
}
|
|
14075
13992
|
});
|
|
14076
13993
|
} catch (err) {
|
|
14077
|
-
log$
|
|
13994
|
+
log$13.error("teammate run context 构造失败,跳过评估", {
|
|
14078
13995
|
chatId,
|
|
14079
13996
|
error: err instanceof Error ? err.message : String(err)
|
|
14080
13997
|
});
|
|
@@ -14098,7 +14015,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14098
14015
|
sourceRefs: params.sourceRefs ?? (params.requestId ? [params.requestId] : [])
|
|
14099
14016
|
});
|
|
14100
14017
|
if (evalDecision.decision === "NO_REPLY") {
|
|
14101
|
-
log$
|
|
14018
|
+
log$13.info("teammate evaluator 决定静默", {
|
|
14102
14019
|
chatId,
|
|
14103
14020
|
evalSessionId,
|
|
14104
14021
|
summary: evalDecision.summary.slice(0, 120)
|
|
@@ -14132,7 +14049,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14132
14049
|
const confirmReply = () => {
|
|
14133
14050
|
if (confirmed) return;
|
|
14134
14051
|
confirmed = true;
|
|
14135
|
-
log$
|
|
14052
|
+
log$13.info("teammate 确认回复,创建流式卡片", {
|
|
14136
14053
|
chatId,
|
|
14137
14054
|
thinkingLen: thinkingBuffer.length,
|
|
14138
14055
|
textLen: fullTextBuffer.length
|
|
@@ -14189,7 +14106,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14189
14106
|
fullTextBuffer += text;
|
|
14190
14107
|
if (fullTextBuffer.trimStart().startsWith(NO_REPLY_TOKEN)) {
|
|
14191
14108
|
silenced = true;
|
|
14192
|
-
log$
|
|
14109
|
+
log$13.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
|
|
14193
14110
|
chatId,
|
|
14194
14111
|
bufferLen: fullTextBuffer.length
|
|
14195
14112
|
});
|
|
@@ -14222,7 +14139,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14222
14139
|
toolName: currentToolName,
|
|
14223
14140
|
toolCallId: toolUseId
|
|
14224
14141
|
});
|
|
14225
|
-
log$
|
|
14142
|
+
log$13.info("teammate onToolResult", {
|
|
14226
14143
|
chatId,
|
|
14227
14144
|
toolUseId,
|
|
14228
14145
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14240,7 +14157,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14240
14157
|
onTurnEnd: (stopReason) => {
|
|
14241
14158
|
finalStopReason = stopReason;
|
|
14242
14159
|
if (stopReason === "tool_use") {
|
|
14243
|
-
log$
|
|
14160
|
+
log$13.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
|
|
14244
14161
|
if (confirmed && bridge) bridge.onTurnEnd(stopReason);
|
|
14245
14162
|
return;
|
|
14246
14163
|
}
|
|
@@ -14248,7 +14165,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14248
14165
|
const trimmed = fullTextBuffer.trim();
|
|
14249
14166
|
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
|
|
14250
14167
|
silenced = true;
|
|
14251
|
-
log$
|
|
14168
|
+
log$13.info("teammate turnEnd 最终判断: 静默", {
|
|
14252
14169
|
chatId,
|
|
14253
14170
|
trimmed: trimmed.slice(0, 60)
|
|
14254
14171
|
});
|
|
@@ -14263,7 +14180,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14263
14180
|
resolve();
|
|
14264
14181
|
},
|
|
14265
14182
|
onError: (error) => {
|
|
14266
|
-
log$
|
|
14183
|
+
log$13.error("teammate 评估 CC 进程错误", {
|
|
14267
14184
|
chatId,
|
|
14268
14185
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
14269
14186
|
error: error.message
|
|
@@ -14271,14 +14188,14 @@ async function dispatchTeammateEval(params) {
|
|
|
14271
14188
|
resolve();
|
|
14272
14189
|
},
|
|
14273
14190
|
onAuditEvent: (event) => {
|
|
14274
|
-
log$
|
|
14191
|
+
log$13.info("teammate onAuditEvent", {
|
|
14275
14192
|
...event,
|
|
14276
14193
|
inputSummary: summarizeForAudit(event.inputSummary),
|
|
14277
14194
|
resultSummary: summarizeForAudit(event.resultSummary)
|
|
14278
14195
|
});
|
|
14279
14196
|
},
|
|
14280
14197
|
onRuntimeEvent: (event) => {
|
|
14281
|
-
log$
|
|
14198
|
+
log$13.info("teammate onRuntimeEvent", {
|
|
14282
14199
|
type: event.type,
|
|
14283
14200
|
chatId,
|
|
14284
14201
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14289,7 +14206,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14289
14206
|
});
|
|
14290
14207
|
},
|
|
14291
14208
|
onScenarioEvent: (event) => {
|
|
14292
|
-
log$
|
|
14209
|
+
log$13.info("teammate onScenarioEvent", {
|
|
14293
14210
|
type: event.type,
|
|
14294
14211
|
chatId,
|
|
14295
14212
|
traceId: mainRuntimeConfig.runContext?.traceId,
|
|
@@ -14301,28 +14218,28 @@ async function dispatchTeammateEval(params) {
|
|
|
14301
14218
|
},
|
|
14302
14219
|
onBlocked: (event) => {
|
|
14303
14220
|
const sanitized = sanitizeBlockedEvent(event);
|
|
14304
|
-
log$
|
|
14221
|
+
log$13.warn("teammate onBlocked", {
|
|
14305
14222
|
...sanitized,
|
|
14306
14223
|
traceId: mainRuntimeConfig.runContext?.traceId
|
|
14307
14224
|
});
|
|
14308
14225
|
},
|
|
14309
14226
|
onBlockedEvent: (event) => {
|
|
14310
14227
|
const sanitized = sanitizeBlockedEvent(event);
|
|
14311
|
-
log$
|
|
14228
|
+
log$13.warn("teammate onBlockedEvent", {
|
|
14312
14229
|
...sanitized,
|
|
14313
14230
|
traceId: mainRuntimeConfig.runContext?.traceId
|
|
14314
14231
|
});
|
|
14315
14232
|
}
|
|
14316
14233
|
};
|
|
14317
14234
|
processManager.executePrompt(mainRuntimeConfig, callbacks).catch((err) => {
|
|
14318
|
-
log$
|
|
14235
|
+
log$13.error("teammate executePrompt 异常", {
|
|
14319
14236
|
chatId,
|
|
14320
14237
|
error: err instanceof Error ? err.message : String(err)
|
|
14321
14238
|
});
|
|
14322
14239
|
resolve();
|
|
14323
14240
|
});
|
|
14324
14241
|
});
|
|
14325
|
-
log$
|
|
14242
|
+
log$13.info("dispatchTeammateEval 完成", {
|
|
14326
14243
|
chatId,
|
|
14327
14244
|
confirmed,
|
|
14328
14245
|
silenced,
|
|
@@ -14333,7 +14250,7 @@ async function dispatchTeammateEval(params) {
|
|
|
14333
14250
|
return { replied: confirmed };
|
|
14334
14251
|
} catch (err) {
|
|
14335
14252
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
14336
|
-
log$
|
|
14253
|
+
log$13.error("dispatchTeammateEval 异常", {
|
|
14337
14254
|
chatId,
|
|
14338
14255
|
error: errorMessage
|
|
14339
14256
|
});
|
|
@@ -14357,7 +14274,7 @@ async function runTeammateDecisionEval(params) {
|
|
|
14357
14274
|
resolve();
|
|
14358
14275
|
},
|
|
14359
14276
|
onError: (error) => {
|
|
14360
|
-
log$
|
|
14277
|
+
log$13.error("teammate evaluator 执行错误", {
|
|
14361
14278
|
chatId,
|
|
14362
14279
|
sessionId: runtimeConfig.sessionId,
|
|
14363
14280
|
error: error.message
|
|
@@ -14365,21 +14282,21 @@ async function runTeammateDecisionEval(params) {
|
|
|
14365
14282
|
resolve();
|
|
14366
14283
|
},
|
|
14367
14284
|
onToolUseStart: (toolName, toolInput) => {
|
|
14368
|
-
log$
|
|
14285
|
+
log$13.info("teammate evaluator toolUseStart", {
|
|
14369
14286
|
chatId,
|
|
14370
14287
|
toolName,
|
|
14371
14288
|
toolInput: summarizeForAudit(toolInput)
|
|
14372
14289
|
});
|
|
14373
14290
|
},
|
|
14374
14291
|
onToolResult: (toolUseId, result) => {
|
|
14375
|
-
log$
|
|
14292
|
+
log$13.info("teammate evaluator toolResult", {
|
|
14376
14293
|
chatId,
|
|
14377
14294
|
toolUseId,
|
|
14378
14295
|
result: summarizeForAudit(result)
|
|
14379
14296
|
});
|
|
14380
14297
|
}
|
|
14381
14298
|
}).catch((err) => {
|
|
14382
|
-
log$
|
|
14299
|
+
log$13.error("teammate evaluator executePrompt 异常", {
|
|
14383
14300
|
chatId,
|
|
14384
14301
|
sessionId: runtimeConfig.sessionId,
|
|
14385
14302
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -14394,7 +14311,7 @@ async function runTeammateDecisionEval(params) {
|
|
|
14394
14311
|
const decision = trimmed !== "" && trimmed !== placeholderText && firstToken === "HANDOFF_TO_MAIN" ? "HANDOFF_TO_MAIN" : "NO_REPLY";
|
|
14395
14312
|
const cleanedSummary = trimmed.replace(/^(HANDOFF_TO_MAIN|NO_REPLY)\b[\s::-]*/i, "").trim();
|
|
14396
14313
|
const summary = malformed ? "NO_REPLY (malformed evaluator output)" : cleanedSummary.slice(0, 600) || `${decision} (thinking ${thinkingLength} chars)`;
|
|
14397
|
-
log$
|
|
14314
|
+
log$13.info("teammate evaluator 完成", {
|
|
14398
14315
|
chatId,
|
|
14399
14316
|
sessionId: runtimeConfig.sessionId,
|
|
14400
14317
|
decision,
|
|
@@ -14452,7 +14369,7 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14452
14369
|
try {
|
|
14453
14370
|
if (req.tool === "ask_user") {
|
|
14454
14371
|
const params = req.params;
|
|
14455
|
-
log$
|
|
14372
|
+
log$13.info("[MCP] 收到 ask_user 回调,准备发送提问卡片", {
|
|
14456
14373
|
requestId: req.requestId,
|
|
14457
14374
|
sessionId: req.sessionId,
|
|
14458
14375
|
question: params.question?.slice(0, 50)
|
|
@@ -14465,14 +14382,14 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14465
14382
|
});
|
|
14466
14383
|
mcpOperationMap.set(operationId, req.requestId);
|
|
14467
14384
|
await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
|
|
14468
|
-
log$
|
|
14385
|
+
log$13.info("[MCP] 提问卡片已发送", {
|
|
14469
14386
|
requestId: req.requestId,
|
|
14470
14387
|
operationId,
|
|
14471
14388
|
chatId
|
|
14472
14389
|
});
|
|
14473
14390
|
} else if (req.tool === "request_permission") {
|
|
14474
14391
|
const params = req.params;
|
|
14475
|
-
log$
|
|
14392
|
+
log$13.info("[MCP] 收到 request_permission 回调,准备发送授权卡片", {
|
|
14476
14393
|
requestId: req.requestId,
|
|
14477
14394
|
scopes: params.scopes
|
|
14478
14395
|
});
|
|
@@ -14486,14 +14403,14 @@ async function handleMcpToolCallback(req, ctx) {
|
|
|
14486
14403
|
});
|
|
14487
14404
|
mcpOperationMap.set(operationId, req.requestId);
|
|
14488
14405
|
await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
|
|
14489
|
-
log$
|
|
14406
|
+
log$13.info("[MCP] 授权卡片已发送", {
|
|
14490
14407
|
requestId: req.requestId,
|
|
14491
14408
|
operationId,
|
|
14492
14409
|
chatId
|
|
14493
14410
|
});
|
|
14494
14411
|
}
|
|
14495
14412
|
} catch (err) {
|
|
14496
|
-
log$
|
|
14413
|
+
log$13.error("[MCP] 交互卡片发送失败", {
|
|
14497
14414
|
requestId: req.requestId,
|
|
14498
14415
|
tool: req.tool,
|
|
14499
14416
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -14518,7 +14435,7 @@ async function sendInteractiveCardViaCardKit(card, chatId, account, replyInThrea
|
|
|
14518
14435
|
} });
|
|
14519
14436
|
const cardId = createResp?.data?.card_id;
|
|
14520
14437
|
if (!cardId) throw new Error(`CardKit card.create 失败: code=${createResp?.code}, msg=${createResp?.msg}`);
|
|
14521
|
-
log$
|
|
14438
|
+
log$13.info("[MCP] CardKit 卡片实体已创建", { cardId });
|
|
14522
14439
|
const contentPayload = JSON.stringify({
|
|
14523
14440
|
type: "card",
|
|
14524
14441
|
data: { card_id: cardId }
|
|
@@ -16048,7 +15965,7 @@ const convertLocation = (raw) => {
|
|
|
16048
15965
|
* injected via callbacks in `ConvertContext`. Callers are responsible
|
|
16049
15966
|
* for creating the appropriate callbacks (UAT / TAT / event push).
|
|
16050
15967
|
*/
|
|
16051
|
-
const log$
|
|
15968
|
+
const log$12 = larkLogger("converters/merge-forward");
|
|
16052
15969
|
/**
|
|
16053
15970
|
* Recursively expand a merge_forward message.
|
|
16054
15971
|
*
|
|
@@ -16063,7 +15980,7 @@ const log$11 = larkLogger("converters/merge-forward");
|
|
|
16063
15980
|
const convertMergeForward = async (_raw, ctx) => {
|
|
16064
15981
|
const { accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent } = ctx;
|
|
16065
15982
|
if (!fetchSubMessages) {
|
|
16066
|
-
log$
|
|
15983
|
+
log$12.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
|
|
16067
15984
|
messageId,
|
|
16068
15985
|
accountId
|
|
16069
15986
|
});
|
|
@@ -16081,23 +15998,23 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
16081
15998
|
let items;
|
|
16082
15999
|
try {
|
|
16083
16000
|
items = await fetchSubMessages(messageId);
|
|
16084
|
-
log$
|
|
16001
|
+
log$12.info("fetchSubMessages 成功", {
|
|
16085
16002
|
messageId,
|
|
16086
16003
|
itemCount: items.length
|
|
16087
16004
|
});
|
|
16088
16005
|
} catch (error) {
|
|
16089
|
-
log$
|
|
16006
|
+
log$12.error("fetch sub-messages failed", {
|
|
16090
16007
|
messageId,
|
|
16091
16008
|
error: error instanceof Error ? error.message : String(error)
|
|
16092
16009
|
});
|
|
16093
16010
|
return "<forwarded_messages/>";
|
|
16094
16011
|
}
|
|
16095
16012
|
if (items.length === 0) {
|
|
16096
|
-
log$
|
|
16013
|
+
log$12.warn("fetchSubMessages 返回空 items", { messageId });
|
|
16097
16014
|
return "<forwarded_messages/>";
|
|
16098
16015
|
}
|
|
16099
16016
|
const childrenMap = buildChildrenMap(items, messageId);
|
|
16100
|
-
log$
|
|
16017
|
+
log$12.info("buildChildrenMap 完成", {
|
|
16101
16018
|
messageId,
|
|
16102
16019
|
parentKeys: [...childrenMap.keys()],
|
|
16103
16020
|
childCounts: [...childrenMap.entries()].map(([k, v]) => `${k}:${v.length}`)
|
|
@@ -16106,7 +16023,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
16106
16023
|
if (senderIds.length > 0 && batchResolveNames) try {
|
|
16107
16024
|
await batchResolveNames(senderIds);
|
|
16108
16025
|
} catch (err) {
|
|
16109
|
-
log$
|
|
16026
|
+
log$12.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
|
|
16110
16027
|
}
|
|
16111
16028
|
return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
|
|
16112
16029
|
}
|
|
@@ -16186,7 +16103,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
|
|
|
16186
16103
|
const indented = indentLines(content, " ");
|
|
16187
16104
|
parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
|
|
16188
16105
|
} catch (err) {
|
|
16189
|
-
log$
|
|
16106
|
+
log$12.warn("failed to convert sub-message", {
|
|
16190
16107
|
messageId: item.message_id,
|
|
16191
16108
|
msgType: item.msg_type ?? "unknown",
|
|
16192
16109
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -16400,7 +16317,7 @@ async function convertMessageContent(raw, messageType, ctx) {
|
|
|
16400
16317
|
}
|
|
16401
16318
|
//#endregion
|
|
16402
16319
|
//#region src/messaging/inbound/parse-io.ts
|
|
16403
|
-
const log$
|
|
16320
|
+
const log$11 = larkLogger("inbound/parse-io");
|
|
16404
16321
|
/**
|
|
16405
16322
|
* 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
|
|
16406
16323
|
* 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
|
|
@@ -16420,7 +16337,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
16420
16337
|
}
|
|
16421
16338
|
}))?.data?.items?.[0]?.body?.content ?? void 0;
|
|
16422
16339
|
} catch (err) {
|
|
16423
|
-
log$
|
|
16340
|
+
log$11.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16424
16341
|
return;
|
|
16425
16342
|
}
|
|
16426
16343
|
}
|
|
@@ -16435,7 +16352,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
16435
16352
|
*/
|
|
16436
16353
|
function createFetchSubMessages(larkClient) {
|
|
16437
16354
|
return async (msgId) => {
|
|
16438
|
-
log$
|
|
16355
|
+
log$11.info("fetchSubMessages 请求", {
|
|
16439
16356
|
msgId,
|
|
16440
16357
|
url: `/open-apis/im/v1/messages/${msgId}`
|
|
16441
16358
|
});
|
|
@@ -16447,7 +16364,7 @@ function createFetchSubMessages(larkClient) {
|
|
|
16447
16364
|
card_msg_content_type: "raw_card_content"
|
|
16448
16365
|
}
|
|
16449
16366
|
});
|
|
16450
|
-
log$
|
|
16367
|
+
log$11.info("fetchSubMessages 响应", {
|
|
16451
16368
|
msgId,
|
|
16452
16369
|
code: response?.code,
|
|
16453
16370
|
msg: response?.msg,
|
|
@@ -16464,11 +16381,11 @@ function createFetchSubMessages(larkClient) {
|
|
|
16464
16381
|
* the account and log function.
|
|
16465
16382
|
*/
|
|
16466
16383
|
function createParseResolveNames(account) {
|
|
16467
|
-
return createBatchResolveNames(account, (...args) => log$
|
|
16384
|
+
return createBatchResolveNames(account, (...args) => log$11.info(args.map(String).join(" ")));
|
|
16468
16385
|
}
|
|
16469
16386
|
//#endregion
|
|
16470
16387
|
//#region src/messaging/inbound/parse.ts
|
|
16471
|
-
const log$
|
|
16388
|
+
const log$10 = larkLogger("inbound/parse");
|
|
16472
16389
|
/**
|
|
16473
16390
|
* Parse a raw Feishu message event into a normalised MessageContext.
|
|
16474
16391
|
*
|
|
@@ -16518,7 +16435,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
16518
16435
|
const fullContent = await fetchCardContent(event.message.message_id, larkClient);
|
|
16519
16436
|
if (fullContent) {
|
|
16520
16437
|
effectiveContent = fullContent;
|
|
16521
|
-
log$
|
|
16438
|
+
log$10.info("replaced interactive content with full v2 card data");
|
|
16522
16439
|
}
|
|
16523
16440
|
}
|
|
16524
16441
|
const convertCtx = {
|
|
@@ -16558,6 +16475,219 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
16558
16475
|
};
|
|
16559
16476
|
}
|
|
16560
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
|
|
16561
16691
|
//#region src/messaging/inbound/dedup.ts
|
|
16562
16692
|
const DEFAULT_TTL_MS = 720 * 60 * 1e3;
|
|
16563
16693
|
const DEFAULT_MAX_ENTRIES = 5e3;
|
|
@@ -18187,6 +18317,30 @@ async function main() {
|
|
|
18187
18317
|
brand: "feishu",
|
|
18188
18318
|
config: {}
|
|
18189
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
|
+
};
|
|
18190
18344
|
const larkClient = LarkClient.fromAccount(account);
|
|
18191
18345
|
const probeResult = await larkClient.probe();
|
|
18192
18346
|
if (probeResult.ok) logger.info("Bot 身份探测成功", {
|
|
@@ -18325,7 +18479,7 @@ async function main() {
|
|
|
18325
18479
|
});
|
|
18326
18480
|
if (nameResult.name) parsed.senderName = nameResult.name;
|
|
18327
18481
|
} catch {}
|
|
18328
|
-
const formattedText =
|
|
18482
|
+
const formattedText = formatPromptForCC(parsed, true, await resolveQuotedContentForMessage(parsed));
|
|
18329
18483
|
teammateBuffer.push(parsed.chatId, {
|
|
18330
18484
|
formattedText,
|
|
18331
18485
|
bufferedAt: Date.now(),
|
|
@@ -18377,6 +18531,7 @@ async function main() {
|
|
|
18377
18531
|
workspaceRoot
|
|
18378
18532
|
});
|
|
18379
18533
|
await ensureUserDirectory(userContext);
|
|
18534
|
+
const quotedContent = await resolveQuotedContentForMessage(parsed);
|
|
18380
18535
|
await dispatchToCC({
|
|
18381
18536
|
ctx: parsed,
|
|
18382
18537
|
account,
|
|
@@ -18386,7 +18541,8 @@ async function main() {
|
|
|
18386
18541
|
userContext,
|
|
18387
18542
|
placeholderCardId,
|
|
18388
18543
|
placeholderMessageId,
|
|
18389
|
-
messageStore
|
|
18544
|
+
messageStore,
|
|
18545
|
+
quotedContent
|
|
18390
18546
|
});
|
|
18391
18547
|
if (threadId) markBotInvolvedInThread(threadId);
|
|
18392
18548
|
} catch (err) {
|