@vibe-lark/larkpal 0.1.58 → 0.1.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.mjs +978 -715
  2. 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$38 = larkLogger("preflight");
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$38.info("开始前置环境检查...");
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$38.info(` ${status(useCodex && codex ? codex : claude)}`);
86
- log$38.info(` ${status(larkCli)}`);
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$38.error(msg);
122
+ log$39.error(msg);
123
123
  throw new Error("前置环境检查未通过,缺少必要依赖");
124
124
  }
125
- log$38.info("前置环境检查通过 ✓");
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$37 = larkLogger("config/ensure-lark-cli");
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$37.info("环境变量凭证未设置,跳过 lark-cli 配置同步");
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$37.info("lark-cli 配置文件已存在,跳过同步", {
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$37.info("检测到环境变量凭证但 lark-cli 配置文件缺失,开始同步", {
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$37.info("已生成新版 lark-cli 配置文件", { path: NEW_CONFIG_PATH });
198
+ log$38.info("已生成新版 lark-cli 配置文件", { path: NEW_CONFIG_PATH });
199
199
  } catch (err) {
200
- log$37.warn("生成新版 lark-cli 配置文件失败", {
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$37.info("已生成旧版 lark-cli 配置文件", { path: LEGACY_CONFIG_PATH });
219
+ log$38.info("已生成旧版 lark-cli 配置文件", { path: LEGACY_CONFIG_PATH });
220
220
  } catch (err) {
221
- log$37.warn("生成旧版 lark-cli 配置文件失败", {
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$36 = larkLogger("user/credential-vault");
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$36.error("LARKPAL_ENCRYPTION_KEY 长度不正确,需要 32 字节", { actualLength: this.encryptionKey.length });
454
+ log$37.error("LARKPAL_ENCRYPTION_KEY 长度不正确,需要 32 字节", { actualLength: this.encryptionKey.length });
455
455
  this.encryptionKey = null;
456
- } else log$36.info("凭证加密已启用");
456
+ } else log$37.info("凭证加密已启用");
457
457
  } else {
458
458
  this.encryptionKey = null;
459
- log$36.warn("LARKPAL_ENCRYPTION_KEY 未设置,凭证将以明文存储(仅限开发模式)");
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$36.info("凭证文件不存在,使用空凭证", { path: this.credentialPath });
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$36.info("凭证文件加载完成", {
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$36.error("凭证文件加载失败", {
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$36.info("凭证文件保存完成", {
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$36.error("凭证文件保存失败", {
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$36.info("设置凭证", {
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$36.info("凭证已删除", { name });
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$36.warn("凭证解密失败,跳过", {
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$35 = larkLogger("user/mcp-merge");
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$35.info("全局 MCP 配置加载完成", { serverCount: Object.keys(globalConfig).length });
680
+ log$36.info("全局 MCP 配置加载完成", { serverCount: Object.keys(globalConfig).length });
681
681
  } catch (err) {
682
- log$35.warn("全局 MCP 配置文件解析失败", {
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$35.info("用户 MCP 配置加载完成", {
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$35.warn("用户 MCP 配置文件解析失败", {
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$35.info("MCP 配置合并完成", {
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$35.info("MCP 合并配置已写入临时文件", {
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$35.debug("MCP 临时配置文件已清理", { path: tmpPath });
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$34 = larkLogger("cc-runtime/stream-parser");
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$34.warn("NDJSON 解析失败,跳过该行", {
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$34.debug("跳过 subagent 内部事件", {
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$34.info("收到 system 消息", {
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$34.debug("收到未知消息类型,已忽略", { type: msg.type });
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$34.debug("content_block_start 事件", {
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$34.info("工具调用开始", {
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$34.info("轮次结束", { stopReason });
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$34.info("收到完整 assistant 消息", {
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$34.info("从 assistant 消息提取工具调用", {
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$34.debug("工具结果返回", { toolUseId: item.tool_use_id });
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$34.debug("工具执行进度", {
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$34.info("CC 执行完成", {
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$33 = larkLogger("cc-runtime/process-manager");
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$33.info("等待上一条消息处理完成", { sessionId });
1097
+ log$34.info("等待上一条消息处理完成", { sessionId });
1139
1098
  await prevLock;
1140
- log$33.info("[perf] 串行锁等待完成", {
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$33.info("向常驻进程发送消息", {
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$33.info("[perf] executePrompt 完成(热进程)", {
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$33.info("清理已终止的旧进程,准备重启", {
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$33.warn("停止旧的单次进程后重新启动为常驻模式", { sessionId });
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$33.info("[perf] 冷启动进程完成", {
1145
+ log$34.info("[perf] 冷启动进程完成", {
1199
1146
  sessionId,
1200
1147
  coldStartMs: Date.now() - tStart
1201
1148
  });
1202
1149
  await completionPromise;
1203
- log$33.info("[perf] executePrompt 完成(冷启动)", {
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$33.warn("ephemeral CC 进程清理失败", {
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$33.info("MCP 配置已合并并注入到 CC 进程", {
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$33.warn("MCP 配置合并失败,CC 进程将使用全局默认配置", {
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$33.info("LARKPAL_DISABLE_USER_AUTH 已启用,跳过飞书 user_access_token 注入", {
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$33.info("用户凭证已注入到 CC 进程环境", {
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$33.warn("用户凭证加载失败,CC 进程将使用默认环境", {
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$33.info("启动常驻 CC 进程", {
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$33.info("收到 control_response", {
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$33.warn("CC 进程 stderr", {
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$33.info("Session ID 已被占用,准备使用 --resume 模式重试", {
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$33.error("Session ID 重试失败", {
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$33.info("CC 常驻进程正常退出", {
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$33.error("CC 常驻进程异常退出", {
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$33.error("CC 常驻进程启动失败", {
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$33.info("CC 常驻进程已启动", {
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$33.info("[perf] 首 token (text)", {
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$33.info("[perf] 首 token (thinking)", {
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$33.info("[perf] turnEnd", {
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$33.info("[perf] result 事件", {
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$33.warn("流解析错误", {
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$33.error("无法发送消息:进程不存在或 stdin 不可用", { sessionId });
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$33.info("通过 stdin 发送用户消息", {
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$33.error("stdin 写入失败", {
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$33.info("发送控制请求", {
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$33.info("ensureProcessForRewind: 进程已在运行中,直接使用", { sessionId });
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$33.warn("ensureProcessForRewind: 无法获取会话 cwd", { sessionId });
1566
+ log$34.warn("ensureProcessForRewind: 无法获取会话 cwd", { sessionId });
1770
1567
  return;
1771
1568
  }
1772
1569
  if (existing) this.cleanup(sessionId);
1773
- log$33.info("ensureProcessForRewind: 启动临时进程用于 rewind", {
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$33.warn("ensureProcessForRewind: 进程启动失败", {
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$33.warn("尝试停止不存在的进程", { sessionId });
1603
+ log$34.warn("尝试停止不存在的进程", { sessionId });
1807
1604
  return;
1808
1605
  }
1809
1606
  if (proc.status === "stopped" || proc.status === "crashed") {
1810
- log$33.info("进程已经停止,直接清理", {
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$33.info("正在停止 CC 常驻进程", {
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$33.warn("CC 常驻进程未在规定时间内退出,强制终止", { sessionId });
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$33.info("正在停止所有 CC 常驻进程", { count: sessionIds.length });
1651
+ log$34.info("正在停止所有 CC 常驻进程", { count: sessionIds.length });
1855
1652
  await Promise.all(sessionIds.map((id) => this.stopProcess(id)));
1856
- log$33.info("所有 CC 常驻进程已停止");
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$33.info("CC 常驻进程空闲超时,自动停止", {
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$33.debug("已清理常驻进程状态", { sessionId });
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$33.debug("检查 CC session 文件", {
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$33.warn("检查 CC session 文件失败,默认为首次创建", { error: err instanceof Error ? err.message : String(err) });
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$32 = larkLogger("runtime/claude-code-adapter");
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$32.info("ClaudeCodeAdapter 初始化完成");
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$32.info("executePrompt via ClaudeCodeAdapter", {
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$32.info("stopProcess via ClaudeCodeAdapter", { sessionId });
1782
+ log$33.info("stopProcess via ClaudeCodeAdapter", { sessionId });
1989
1783
  await this.processManager.stopProcess(sessionId);
1990
1784
  }
1991
1785
  async stopAll() {
1992
- log$32.info("stopAll via ClaudeCodeAdapter");
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$31 = larkLogger("codex-runtime/app-server-client");
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$31.info("Codex app-server 进程已启动", {
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$31.warn("Codex app-server stderr", {
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$31.error("Codex app-server 进程错误", {
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$31.info("Codex app-server 进程退出", {
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$31.warn("Codex app-server JSONL 解析失败", {
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$31.warn("Codex app-server 返回无 id 错误响应", {
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$31.warn("Codex app-server 返回未知请求响应", {
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$30 = larkLogger("runtime/codex-app-server-adapter");
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$30.info("CodexAppServerAdapter 初始化完成", { statePath: this.statePath });
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$30.info("等待上一条 Codex 消息处理完成", { sessionId });
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$30.warn("ephemeral Codex app-server session 清理失败", {
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$30.warn("尝试停止不存在的 Codex app-server session", { sessionId });
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$30.info("发送 Codex turn/interrupt", {
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$30.warn("Codex thread/resume 失败,将新建 thread", {
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$30.debug("忽略 Codex app-server notification", {
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$30.debug("Codex MCP tool progress", {
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$30.warn("Codex app-server server request 默认拒绝", {
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$30.warn("Codex app-server v1 暂不支持 per-request llmConfig,已忽略", { sessionId: config.sessionId });
2708
- if (config.maxBudgetUsd != null) log$30.warn("Codex app-server v1 暂不支持 maxBudgetUsd,已忽略", { sessionId: config.sessionId });
2709
- if (config.maxTurns != null) log$30.warn("Codex app-server v1 暂不支持 maxTurns,已忽略", { sessionId: config.sessionId });
2710
- if (config.policy?.allowedTools || config.policy?.disallowedTools) log$30.warn("Codex app-server v1 暂不强制执行 tool allow/deny policy,已忽略", { sessionId: config.sessionId });
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$30.warn("Codex thread/unsubscribe 失败", {
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$30.warn("Codex thread state 加载失败,将从空状态继续", {
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$29 = larkLogger("runtime/larkpal-agent-factory");
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$29.info("systemPrompt 加载成功", {
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$29.warn("systemPrompt 文件不存在,使用默认", { path: AGENT_MD_PATH });
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$29.info("创建 LarkpalAgentAdapter", {
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$29.info("MCP 工具注册完成", {
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$29.error("MCP Server 连接失败,Agent 将无法使用远程工具", {
2782
+ log$30.error("MCP Server 连接失败,Agent 将无法使用远程工具", {
2985
2783
  serverUrl: mcpServerUrl,
2986
2784
  error: err?.message
2987
2785
  });
2988
2786
  }
2989
- else log$29.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
2787
+ else log$30.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
2990
2788
  return adapter;
2991
2789
  }
2992
2790
  //#endregion
@@ -3323,6 +3121,7 @@ function createChatAuthRouter() {
3323
3121
  //#endregion
3324
3122
  //#region src/gateway/agent-context.ts
3325
3123
  const SENSITIVE_KEY_PATTERN = /(authorization|cookie|token|api[-_]?key|secret|password|credential)/i;
3124
+ const SAFE_KEY_PATTERN = /^(file[-_]?token|drive[-_]?file[-_]?token|doc[-_]?token|sheet[-_]?token|wiki[-_]?token|node[-_]?token)$/i;
3326
3125
  const MAX_SUMMARY_STRING_LENGTH = 200;
3327
3126
  const PROTECTED_CONTEXT_HEADERS = new Set([
3328
3127
  "x-tenant-key",
@@ -3445,7 +3244,7 @@ function summarizeForAudit(value) {
3445
3244
  };
3446
3245
  if (typeof value === "object") {
3447
3246
  const result = {};
3448
- for (const [key, entry] of Object.entries(value)) if (SENSITIVE_KEY_PATTERN.test(key)) result[key] = "[redacted]";
3247
+ for (const [key, entry] of Object.entries(value)) if (SENSITIVE_KEY_PATTERN.test(key) && !SAFE_KEY_PATTERN.test(key)) result[key] = "[redacted]";
3449
3248
  else result[key] = summarizeForAudit(entry);
3450
3249
  return result;
3451
3250
  }
@@ -3551,7 +3350,8 @@ function buildExecutionPolicy(policy, limits) {
3551
3350
  maxTurns: limits.maxTurns ?? policy?.maxTurns,
3552
3351
  maxToolCalls: policy?.maxToolCalls,
3553
3352
  maxRuntimeMs: policy?.maxRuntimeMs,
3554
- maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd
3353
+ maxBudgetUsd: limits.maxBudgetUsd ?? policy?.maxBudgetUsd,
3354
+ turnPolicy: policy?.turnPolicy
3555
3355
  };
3556
3356
  }
3557
3357
  function buildUserContext$1(identity) {
@@ -3619,8 +3419,9 @@ function summarizeString(value) {
3619
3419
  * POST /api/chat/sessions — 创建新会话
3620
3420
  * DELETE /api/chat/sessions/:id — 删除会话
3621
3421
  */
3622
- const log$28 = larkLogger("chat/stream");
3422
+ const log$29 = larkLogger("chat/stream");
3623
3423
  const chatRunsBySession = /* @__PURE__ */ new Map();
3424
+ const DEFAULT_STALE_RUN_IDLE_MS = 1800 * 1e3;
3624
3425
  /** 统一写 SSE 格式数据到响应流 */
3625
3426
  function sendSSE(res, event, data) {
3626
3427
  const payload = typeof data === "string" ? data : JSON.stringify(data);
@@ -3630,7 +3431,7 @@ function isChatSessionRunning(sessionId, processManager) {
3630
3431
  return getActiveChatRun(sessionId) != null || (processManager.isSessionBusy?.(sessionId) ?? false);
3631
3432
  }
3632
3433
  function logChatAudit(event) {
3633
- log$28.info("[stream] Agent audit event", { ...event });
3434
+ log$29.info("[stream] Agent audit event", { ...event });
3634
3435
  }
3635
3436
  function prepareSSE(res) {
3636
3437
  res.setHeader("Content-Type", "text/event-stream");
@@ -3644,6 +3445,7 @@ function getActiveChatRun(sessionId) {
3644
3445
  return run?.status === "running" ? run : void 0;
3645
3446
  }
3646
3447
  function serializeChatRun(run) {
3448
+ const idleSeconds = Math.max(0, Math.floor((Date.now() - run.lastEventAt) / 1e3));
3647
3449
  return {
3648
3450
  runId: run.runId,
3649
3451
  sessionId: run.sessionId,
@@ -3655,9 +3457,24 @@ function serializeChatRun(run) {
3655
3457
  completedAt: run.completedAt,
3656
3458
  finalStatus: run.finalStatus,
3657
3459
  error: run.error,
3658
- textLength: run.text.length
3460
+ textLength: run.text.length,
3461
+ finalText: run.status === "running" ? void 0 : run.text || void 0,
3462
+ lastEventAt: run.lastEventAt,
3463
+ lastEventType: run.lastEventType,
3464
+ lastToolName: run.lastToolName,
3465
+ idleSeconds,
3466
+ adapterBusy: run.adapterBusy,
3467
+ staleReason: run.staleReason
3659
3468
  };
3660
3469
  }
3470
+ function getRuntimeEventDedupeKey(event) {
3471
+ const record = event;
3472
+ const eventId = record.eventId;
3473
+ if (typeof eventId === "string" && eventId.length > 0) return eventId;
3474
+ const runId = typeof record.runId === "string" ? record.runId : void 0;
3475
+ const seq = typeof record.seq === "number" || typeof record.seq === "string" ? String(record.seq) : void 0;
3476
+ return runId && seq ? `${runId}:${seq}` : void 0;
3477
+ }
3661
3478
  function isRunVisibleToUser(run, params) {
3662
3479
  return run.tenantKey === params.tenantKey && run.openId === params.openId;
3663
3480
  }
@@ -3665,7 +3482,7 @@ function addRunClient(req, res, run) {
3665
3482
  run.clients.add(res);
3666
3483
  req.on("close", () => {
3667
3484
  run.clients.delete(res);
3668
- log$28.info("[stream] 客户端断开连接", {
3485
+ log$29.info("[stream] 客户端断开连接", {
3669
3486
  sessionId: run.sessionId,
3670
3487
  runId: run.runId
3671
3488
  });
@@ -3674,12 +3491,37 @@ function addRunClient(req, res, run) {
3674
3491
  function sendRunStatus(res, run) {
3675
3492
  sendSSE(res, "run-status", serializeChatRun(run));
3676
3493
  }
3494
+ function getStaleRunIdleMs() {
3495
+ const raw = process.env.LARKPAL_CHAT_RUN_STALE_IDLE_MS;
3496
+ if (!raw) return DEFAULT_STALE_RUN_IDLE_MS;
3497
+ const parsed = Number.parseInt(raw, 10);
3498
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_STALE_RUN_IDLE_MS;
3499
+ }
3500
+ function recordRunEvent(run, eventType, params) {
3501
+ run.lastEventAt = Date.now();
3502
+ run.lastEventType = eventType;
3503
+ if (params?.toolName) run.lastToolName = params.toolName;
3504
+ }
3505
+ function extractRuntimeEventFinalText(event) {
3506
+ const direct = event.finalText;
3507
+ if (typeof direct === "string" && direct.trim()) return direct;
3508
+ const terminal = event.terminal;
3509
+ if (terminal && typeof terminal === "object" && !Array.isArray(terminal)) {
3510
+ const text = terminal.finalText;
3511
+ if (typeof text === "string" && text.trim()) return text;
3512
+ }
3513
+ const data = event.data;
3514
+ if (data && typeof data === "object" && !Array.isArray(data)) {
3515
+ const text = data.finalText;
3516
+ if (typeof text === "string" && text.trim()) return text;
3517
+ }
3518
+ }
3677
3519
  function broadcastRunEvent(run, event, data) {
3678
3520
  for (const client of Array.from(run.clients)) try {
3679
3521
  sendSSE(client, event, data);
3680
3522
  } catch (err) {
3681
3523
  run.clients.delete(client);
3682
- log$28.warn("[stream] SSE 写入失败,移除客户端", {
3524
+ log$29.warn("[stream] SSE 写入失败,移除客户端", {
3683
3525
  sessionId: run.sessionId,
3684
3526
  runId: run.runId,
3685
3527
  error: err instanceof Error ? err.message : String(err)
@@ -3699,7 +3541,7 @@ function queueRunMessage(run, messageStore, message, label) {
3699
3541
  run.persistQueue = run.persistQueue.catch(() => void 0).then(async () => {
3700
3542
  await messageStore.appendMessage(message);
3701
3543
  }).catch((err) => {
3702
- log$28.error(label, {
3544
+ log$29.error(label, {
3703
3545
  sessionId: run.sessionId,
3704
3546
  runId: run.runId,
3705
3547
  error: err instanceof Error ? err.message : String(err)
@@ -3707,6 +3549,71 @@ function queueRunMessage(run, messageStore, message, label) {
3707
3549
  });
3708
3550
  return run.persistQueue;
3709
3551
  }
3552
+ function appendRunStatusMessage(run, messageStore) {
3553
+ return queueRunMessage(run, messageStore, {
3554
+ sessionId: run.sessionId,
3555
+ role: "tool",
3556
+ content: JSON.stringify({
3557
+ runtimeEventType: "run-status",
3558
+ run: serializeChatRun(run)
3559
+ }),
3560
+ channel: "web",
3561
+ metadata: {
3562
+ runId: run.runId,
3563
+ requestId: run.requestId,
3564
+ traceId: run.traceId,
3565
+ scenarioId: run.scenarioId,
3566
+ runtimeEventType: "run-status",
3567
+ finalStatus: run.finalStatus,
3568
+ errorMessage: run.error,
3569
+ staleReason: run.staleReason
3570
+ }
3571
+ }, "[stream] 保存 run status 消息失败");
3572
+ }
3573
+ async function markRunTerminal(run, messageStore, status, params) {
3574
+ if (run.status !== "running") return false;
3575
+ run.status = status;
3576
+ run.completedAt = Date.now();
3577
+ run.finalStatus = params.finalStatus;
3578
+ run.error = params.error;
3579
+ run.staleReason = params.staleReason;
3580
+ recordRunEvent(run, `run-${status}`);
3581
+ broadcastRunStatus(run);
3582
+ await appendRunStatusMessage(run, messageStore);
3583
+ return true;
3584
+ }
3585
+ async function reconcileRunWithAdapter(run, messageStore, processManager) {
3586
+ if (run.status !== "running") return;
3587
+ const processInfo = processManager.getProcessInfo(run.sessionId);
3588
+ const adapterBusy = processManager.isSessionBusy?.(run.sessionId) ?? processInfo?.status === "running";
3589
+ run.adapterBusy = adapterBusy;
3590
+ const idleMs = Date.now() - run.lastEventAt;
3591
+ let staleReason;
3592
+ if (!adapterBusy) staleReason = processInfo ? `runtime process is ${processInfo.status}` : "runtime adapter reports session is not busy";
3593
+ else if (idleMs >= getStaleRunIdleMs()) staleReason = `run idle for ${Math.floor(idleMs / 1e3)}s`;
3594
+ if (!staleReason) return;
3595
+ const error = `Chat run became stale: ${staleReason}`;
3596
+ log$29.warn("[stream] activeRun 与 runtime 状态不一致,标记为 stale", {
3597
+ sessionId: run.sessionId,
3598
+ runId: run.runId,
3599
+ staleReason,
3600
+ idleMs,
3601
+ adapterBusy,
3602
+ processStatus: processInfo?.status
3603
+ });
3604
+ if (await markRunTerminal(run, messageStore, "failed", {
3605
+ finalStatus: "stale",
3606
+ error,
3607
+ staleReason
3608
+ })) {
3609
+ broadcastRunEvent(run, "error", {
3610
+ runId: run.runId,
3611
+ message: error,
3612
+ staleReason
3613
+ });
3614
+ endRunClients(run);
3615
+ }
3616
+ }
3710
3617
  function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
3711
3618
  return {
3712
3619
  runtimeEventType: event.type,
@@ -3764,7 +3671,7 @@ function createChatRouter(config) {
3764
3671
  addRunClient(req, res, activeRun);
3765
3672
  sendSSE(res, "session", { sessionId });
3766
3673
  sendRunStatus(res, activeRun);
3767
- log$28.info("[stream] 已重新连接到运行中的 run", {
3674
+ log$29.info("[stream] 已重新连接到运行中的 run", {
3768
3675
  sessionId,
3769
3676
  runId: activeRun.runId
3770
3677
  });
@@ -3777,7 +3684,7 @@ function createChatRouter(config) {
3777
3684
  const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
3778
3685
  const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
3779
3686
  const sourceArtifacts = getRunInputValue(body.sourceArtifacts, body.source_artifacts, body.options?.sourceArtifacts, body.options?.source_artifacts, body.options?.metadata?.sourceArtifacts, body.options?.metadata?.source_artifacts);
3780
- log$28.info("[stream] 收到流式对话请求", {
3687
+ log$29.info("[stream] 收到流式对话请求", {
3781
3688
  userId,
3782
3689
  sessionId,
3783
3690
  requestId,
@@ -3794,13 +3701,13 @@ function createChatRouter(config) {
3794
3701
  tenantKey,
3795
3702
  channel: "web"
3796
3703
  })).id;
3797
- log$28.info("[stream] 自动创建新会话", {
3704
+ log$29.info("[stream] 自动创建新会话", {
3798
3705
  userId: openId,
3799
3706
  sessionId
3800
3707
  });
3801
3708
  }
3802
3709
  if (isChatSessionRunning(sessionId, processManager)) {
3803
- log$28.warn("[stream] session 正在被非 Web Chat run 占用", {
3710
+ log$29.warn("[stream] session 正在被非 Web Chat run 占用", {
3804
3711
  tenantKey,
3805
3712
  userId,
3806
3713
  sessionId
@@ -3811,6 +3718,7 @@ function createChatRouter(config) {
3811
3718
  res.end();
3812
3719
  return;
3813
3720
  }
3721
+ const runtimePrompt = enrichPromptWithSourceArtifacts(body.prompt, sourceArtifacts);
3814
3722
  const runtimeConfig = buildAgentRuntimeConfig({
3815
3723
  requestId,
3816
3724
  traceId,
@@ -3818,7 +3726,7 @@ function createChatRouter(config) {
3818
3726
  conversationId: sessionId,
3819
3727
  entrypoint: "chat",
3820
3728
  scenarioId,
3821
- prompt: body.prompt,
3729
+ prompt: runtimePrompt,
3822
3730
  maxTurns: body.options?.maxTurns,
3823
3731
  maxBudgetUsd: body.options?.maxBudgetUsd,
3824
3732
  model: process.env.CLAUDE_MODEL || void 0,
@@ -3847,37 +3755,10 @@ function createChatRouter(config) {
3847
3755
  text: "",
3848
3756
  clients: /* @__PURE__ */ new Set(),
3849
3757
  runtimeConfig,
3850
- persistQueue: Promise.resolve()
3851
- };
3852
- const appendRunStatusMessage = () => {
3853
- return queueRunMessage(run, messageStore, {
3854
- sessionId,
3855
- role: "tool",
3856
- content: JSON.stringify({
3857
- runtimeEventType: "run-status",
3858
- run: serializeChatRun(run)
3859
- }),
3860
- channel: "web",
3861
- metadata: {
3862
- runId: run.runId,
3863
- requestId,
3864
- traceId,
3865
- scenarioId,
3866
- runtimeEventType: "run-status",
3867
- finalStatus: run.finalStatus,
3868
- errorMessage: run.error
3869
- }
3870
- }, "[stream] 保存 run status 消息失败");
3871
- };
3872
- const markRunTerminal = async (status, params) => {
3873
- if (run.status !== "running") return false;
3874
- run.status = status;
3875
- run.completedAt = Date.now();
3876
- run.finalStatus = params.finalStatus;
3877
- run.error = params.error;
3878
- broadcastRunStatus(run);
3879
- await appendRunStatusMessage();
3880
- return true;
3758
+ persistQueue: Promise.resolve(),
3759
+ seenRuntimeEventIds: /* @__PURE__ */ new Set(),
3760
+ lastEventAt: Date.now(),
3761
+ lastEventType: "run-started"
3881
3762
  };
3882
3763
  try {
3883
3764
  await messageStore.appendMessage({
@@ -3898,7 +3779,7 @@ function createChatRouter(config) {
3898
3779
  });
3899
3780
  } catch (err) {
3900
3781
  const errorMessage = err instanceof Error ? err.message : String(err);
3901
- log$28.error("[stream] 保存用户消息失败", {
3782
+ log$29.error("[stream] 保存用户消息失败", {
3902
3783
  sessionId,
3903
3784
  error: errorMessage
3904
3785
  });
@@ -3906,14 +3787,22 @@ function createChatRouter(config) {
3906
3787
  return;
3907
3788
  }
3908
3789
  chatRunsBySession.set(sessionId, run);
3909
- appendRunStatusMessage();
3790
+ appendRunStatusMessage(run, messageStore);
3910
3791
  prepareSSE(res);
3911
3792
  addRunClient(req, res, run);
3912
3793
  sendSSE(res, "session", { sessionId });
3913
3794
  sendRunStatus(res, run);
3795
+ const heartbeatTimer = setInterval(() => {
3796
+ if (run.status !== "running" || run.clients.size === 0) {
3797
+ clearInterval(heartbeatTimer);
3798
+ return;
3799
+ }
3800
+ broadcastRunEvent(run, "heartbeat", { ts: Date.now() });
3801
+ }, 15e3);
3914
3802
  const handleBlockedEvent = (event) => {
3915
3803
  const sanitized = sanitizeBlockedEvent(event);
3916
- log$28.warn("[stream] Agent blocked event", { ...sanitized });
3804
+ recordRunEvent(run, "blocked", { toolName: sanitized.name });
3805
+ log$29.warn("[stream] Agent blocked event", { ...sanitized });
3917
3806
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
3918
3807
  toolName: sanitized.name,
3919
3808
  inputSummary: sanitized.inputSummary,
@@ -3943,7 +3832,23 @@ function createChatRouter(config) {
3943
3832
  broadcastRunEvent(run, "blocked", { event: sanitized });
3944
3833
  };
3945
3834
  const handleRuntimeEvent = (event) => {
3835
+ const dedupeKey = getRuntimeEventDedupeKey(event);
3836
+ if (dedupeKey) {
3837
+ if (run.seenRuntimeEventIds.has(dedupeKey)) {
3838
+ log$29.debug("[stream] 跳过重复 runtime event", {
3839
+ sessionId,
3840
+ runId: run.runId,
3841
+ eventId: dedupeKey,
3842
+ eventType: event.type
3843
+ });
3844
+ return;
3845
+ }
3846
+ run.seenRuntimeEventIds.add(dedupeKey);
3847
+ }
3946
3848
  const eventSummary = summarizeForAudit(event);
3849
+ recordRunEvent(run, event.type, { toolName: event.capabilityId });
3850
+ const runtimeFinalText = extractRuntimeEventFinalText(event);
3851
+ if (runtimeFinalText && !run.text.trim()) run.text = runtimeFinalText;
3947
3852
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
3948
3853
  resultSummary: eventSummary,
3949
3854
  metadata: buildRuntimeEventAuditMetadata(event)
@@ -3968,13 +3873,16 @@ function createChatRouter(config) {
3968
3873
  };
3969
3874
  const callbacks = {
3970
3875
  onTextDelta: (text) => {
3876
+ recordRunEvent(run, "text-delta");
3971
3877
  run.text += text;
3972
3878
  broadcastRunEvent(run, "text-delta", { text });
3973
3879
  },
3974
3880
  onThinkingDelta: (text) => {
3881
+ recordRunEvent(run, "thinking-delta");
3975
3882
  broadcastRunEvent(run, "thinking-delta", { text });
3976
3883
  },
3977
3884
  onToolUseStart: (toolName, toolInput) => {
3885
+ recordRunEvent(run, "tool-use-start", { toolName });
3978
3886
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
3979
3887
  toolName,
3980
3888
  inputSummary: summarizeForAudit(toolInput)
@@ -4002,6 +3910,7 @@ function createChatRouter(config) {
4002
3910
  });
4003
3911
  },
4004
3912
  onToolResult: (toolUseId, result) => {
3913
+ recordRunEvent(run, "tool-result");
4005
3914
  const resultSummary = summarizeForAudit(result);
4006
3915
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_finished", {
4007
3916
  toolCallId: toolUseId,
@@ -4032,18 +3941,22 @@ function createChatRouter(config) {
4032
3941
  });
4033
3942
  },
4034
3943
  onToolProgress: (toolName, elapsedSeconds) => {
3944
+ recordRunEvent(run, "tool-progress", { toolName });
4035
3945
  broadcastRunEvent(run, "tool-progress", {
4036
3946
  toolName,
4037
3947
  elapsedSeconds
4038
3948
  });
4039
3949
  },
4040
3950
  onTurnEnd: (stopReason) => {
3951
+ recordRunEvent(run, "turn-end");
4041
3952
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
4042
3953
  broadcastRunEvent(run, "turn-end", { stopReason });
4043
3954
  },
4044
3955
  onResult: async (result) => {
4045
- const finalText = run.text || result.result || "";
4046
- await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
3956
+ recordRunEvent(run, "result");
3957
+ const finalText = run.text.trim() ? run.text : result.result || "";
3958
+ run.text = finalText;
3959
+ await markRunTerminal(run, messageStore, result.isError ? "failed" : "completed", { finalStatus: result.subtype });
4047
3960
  await queueRunMessage(run, messageStore, {
4048
3961
  sessionId,
4049
3962
  role: "assistant",
@@ -4070,8 +3983,9 @@ function createChatRouter(config) {
4070
3983
  totalCostUsd: result.totalCostUsd,
4071
3984
  numTurns: result.numTurns
4072
3985
  });
3986
+ clearInterval(heartbeatTimer);
4073
3987
  endRunClients(run);
4074
- log$28.info("[stream] 对话完成", {
3988
+ log$29.info("[stream] 对话完成", {
4075
3989
  sessionId,
4076
3990
  runId: run.runId,
4077
3991
  result: result.subtype,
@@ -4093,7 +4007,8 @@ function createChatRouter(config) {
4093
4007
  }));
4094
4008
  },
4095
4009
  onError: async (error) => {
4096
- log$28.error("[stream] Runtime 执行出错", {
4010
+ recordRunEvent(run, "error");
4011
+ log$29.error("[stream] Runtime 执行出错", {
4097
4012
  sessionId,
4098
4013
  runId: run.runId,
4099
4014
  error: error.message
@@ -4122,7 +4037,7 @@ function createChatRouter(config) {
4122
4037
  requestId
4123
4038
  }
4124
4039
  }));
4125
- await markRunTerminal("failed", {
4040
+ await markRunTerminal(run, messageStore, "failed", {
4126
4041
  finalStatus: "error",
4127
4042
  error: error.message
4128
4043
  });
@@ -4130,6 +4045,7 @@ function createChatRouter(config) {
4130
4045
  runId: run.runId,
4131
4046
  message: error.message
4132
4047
  });
4048
+ clearInterval(heartbeatTimer);
4133
4049
  endRunClients(run);
4134
4050
  },
4135
4051
  onAuditEvent: (event) => {
@@ -4140,6 +4056,7 @@ function createChatRouter(config) {
4140
4056
  });
4141
4057
  },
4142
4058
  onVerifierResult: (result) => {
4059
+ recordRunEvent(run, "verifier-result");
4143
4060
  const resultSummary = summarizeForAudit(result);
4144
4061
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "verifier_result", {
4145
4062
  resultSummary,
@@ -4179,7 +4096,7 @@ function createChatRouter(config) {
4179
4096
  scenarioId: runtimeConfig.runContext?.scenarioId,
4180
4097
  ...buildRunInputAuditMetadata(runtimeConfig)
4181
4098
  } }));
4182
- log$28.info("[stream] 开始执行 prompt", {
4099
+ log$29.info("[stream] 开始执行 prompt", {
4183
4100
  sessionId,
4184
4101
  runId: run.runId,
4185
4102
  cwd: runtimeConfig.cwd
@@ -4187,11 +4104,11 @@ function createChatRouter(config) {
4187
4104
  await processManager.executePrompt(runtimeConfig, callbacks);
4188
4105
  if (run.status === "running") {
4189
4106
  const errorMessage = "Runtime completed without a final result event";
4190
- log$28.error("[stream] executePrompt 未返回终态回调", {
4107
+ log$29.error("[stream] executePrompt 未返回终态回调", {
4191
4108
  sessionId,
4192
4109
  runId: run.runId
4193
4110
  });
4194
- await markRunTerminal("failed", {
4111
+ await markRunTerminal(run, messageStore, "failed", {
4195
4112
  finalStatus: "error",
4196
4113
  error: errorMessage
4197
4114
  });
@@ -4199,11 +4116,12 @@ function createChatRouter(config) {
4199
4116
  runId: run.runId,
4200
4117
  message: errorMessage
4201
4118
  });
4119
+ clearInterval(heartbeatTimer);
4202
4120
  endRunClients(run);
4203
4121
  }
4204
4122
  } catch (err) {
4205
4123
  const errorMessage = err instanceof Error ? err.message : String(err);
4206
- log$28.error("[stream] executePrompt 异常", {
4124
+ log$29.error("[stream] executePrompt 异常", {
4207
4125
  sessionId,
4208
4126
  runId: run.runId,
4209
4127
  error: errorMessage
@@ -4220,7 +4138,7 @@ function createChatRouter(config) {
4220
4138
  }
4221
4139
  const cursor = req.query.cursor;
4222
4140
  const limit = parseInt(req.query.limit, 10) || 50;
4223
- log$28.info("[history] 查询会话历史", {
4141
+ log$29.info("[history] 查询会话历史", {
4224
4142
  userId: chatUser.userId,
4225
4143
  sessionId,
4226
4144
  cursor,
@@ -4236,6 +4154,7 @@ function createChatRouter(config) {
4236
4154
  tenantKey: chatUser.tenantKey,
4237
4155
  openId: chatUser.openId
4238
4156
  }) ? run : void 0;
4157
+ if (visibleRun) await reconcileRunWithAdapter(visibleRun, messageStore, processManager);
4239
4158
  res.json({
4240
4159
  messages: result.messages,
4241
4160
  nextCursor: result.nextCursor,
@@ -4245,7 +4164,7 @@ function createChatRouter(config) {
4245
4164
  });
4246
4165
  } catch (err) {
4247
4166
  const errorMessage = err instanceof Error ? err.message : String(err);
4248
- log$28.error("[history] 查询历史消息失败", {
4167
+ log$29.error("[history] 查询历史消息失败", {
4249
4168
  sessionId,
4250
4169
  error: errorMessage
4251
4170
  });
@@ -4271,6 +4190,7 @@ function createChatRouter(config) {
4271
4190
  });
4272
4191
  return;
4273
4192
  }
4193
+ await reconcileRunWithAdapter(run, messageStore, processManager);
4274
4194
  const serialized = serializeChatRun(run);
4275
4195
  res.json({
4276
4196
  sessionId,
@@ -4280,13 +4200,13 @@ function createChatRouter(config) {
4280
4200
  });
4281
4201
  router.get("/api/chat/sessions", chatAuthMiddleware, async (_req, res) => {
4282
4202
  const chatUser = res.locals.chatUser;
4283
- log$28.info("[sessions] 列出用户会话", { userId: chatUser.userId });
4203
+ log$29.info("[sessions] 列出用户会话", { userId: chatUser.userId });
4284
4204
  try {
4285
4205
  const sessions = await messageStore.listSessions(chatUser.openId);
4286
4206
  res.json({ sessions });
4287
4207
  } catch (err) {
4288
4208
  const errorMessage = err instanceof Error ? err.message : String(err);
4289
- log$28.error("[sessions] 列出会话失败", {
4209
+ log$29.error("[sessions] 列出会话失败", {
4290
4210
  userId: chatUser.userId,
4291
4211
  error: errorMessage
4292
4212
  });
@@ -4296,7 +4216,7 @@ function createChatRouter(config) {
4296
4216
  router.post("/api/chat/sessions", chatAuthMiddleware, async (req, res) => {
4297
4217
  const chatUser = res.locals.chatUser;
4298
4218
  const body = req.body;
4299
- log$28.info("[sessions] 创建新会话", {
4219
+ log$29.info("[sessions] 创建新会话", {
4300
4220
  userId: chatUser.userId,
4301
4221
  title: body.title
4302
4222
  });
@@ -4311,7 +4231,7 @@ function createChatRouter(config) {
4311
4231
  res.json({ session });
4312
4232
  } catch (err) {
4313
4233
  const errorMessage = err instanceof Error ? err.message : String(err);
4314
- log$28.error("[sessions] 创建会话失败", {
4234
+ log$29.error("[sessions] 创建会话失败", {
4315
4235
  userId: chatUser.userId,
4316
4236
  error: errorMessage
4317
4237
  });
@@ -4321,7 +4241,7 @@ function createChatRouter(config) {
4321
4241
  router.delete("/api/chat/sessions/:id", chatAuthMiddleware, async (req, res) => {
4322
4242
  const chatUser = res.locals.chatUser;
4323
4243
  const sessionId = String(req.params.id);
4324
- log$28.info("[sessions] 删除会话", {
4244
+ log$29.info("[sessions] 删除会话", {
4325
4245
  userId: chatUser.userId,
4326
4246
  sessionId
4327
4247
  });
@@ -4331,7 +4251,7 @@ function createChatRouter(config) {
4331
4251
  res.json({ success: true });
4332
4252
  } catch (err) {
4333
4253
  const errorMessage = err instanceof Error ? err.message : String(err);
4334
- log$28.error("[sessions] 删除会话失败", {
4254
+ log$29.error("[sessions] 删除会话失败", {
4335
4255
  sessionId,
4336
4256
  error: errorMessage
4337
4257
  });
@@ -4340,6 +4260,34 @@ function createChatRouter(config) {
4340
4260
  });
4341
4261
  return router;
4342
4262
  }
4263
+ /**
4264
+ * 将 sourceArtifacts 中的文件信息(fileToken、fileName 等)注入到用户 prompt 中,
4265
+ * 使 LLM 能看到实际的 file token 值并直接用于下载。
4266
+ *
4267
+ * 如果没有 sourceArtifacts 或无法提取有效信息,返回原始 prompt。
4268
+ */
4269
+ function enrichPromptWithSourceArtifacts(prompt, sourceArtifacts) {
4270
+ if (!sourceArtifacts) return prompt;
4271
+ const lines = [];
4272
+ const extractInfo = (artifact) => {
4273
+ if (!artifact || typeof artifact !== "object") return;
4274
+ const record = artifact;
4275
+ const fileToken = record.fileToken ?? record.file_token ?? record.driveFileToken ?? record.drive_file_token;
4276
+ const fileName = record.fileName ?? record.file_name ?? record.name ?? record.title;
4277
+ const fileType = record.fileType ?? record.file_type ?? record.type ?? record.mimeType;
4278
+ const fileUrl = record.fileUrl ?? record.file_url ?? record.url ?? record.uri;
4279
+ if (fileToken || fileName || fileUrl) {
4280
+ if (fileName) lines.push(`- fileName: ${String(fileName)}`);
4281
+ if (fileToken) lines.push(`- fileToken: ${String(fileToken)}`);
4282
+ if (fileType) lines.push(`- fileType: ${String(fileType)}`);
4283
+ if (fileUrl) lines.push(`- fileUrl: ${String(fileUrl)}`);
4284
+ }
4285
+ };
4286
+ if (Array.isArray(sourceArtifacts)) for (const item of sourceArtifacts) extractInfo(item);
4287
+ else extractInfo(sourceArtifacts);
4288
+ if (lines.length === 0) return prompt;
4289
+ return `${prompt}\n\n[Attached Source File]\n${lines.join("\n")}`;
4290
+ }
4343
4291
  //#endregion
4344
4292
  //#region src/core/app-info-sync.ts
4345
4293
  /**
@@ -4357,7 +4305,7 @@ function createChatRouter(config) {
4357
4305
  * - GET /open-apis/application/v6/applications/me?lang=zh_cn(需要 application:application:self_manage 权限,返回完整信息)
4358
4306
  * 如果后者无权限则 fallback 到前者
4359
4307
  */
4360
- const log$27 = larkLogger("core/app-info-sync");
4308
+ const log$28 = larkLogger("core/app-info-sync");
4361
4309
  /**
4362
4310
  * 从飞书获取应用信息
4363
4311
  *
@@ -4368,7 +4316,7 @@ async function fetchAppInfo(credentials) {
4368
4316
  const { appId, appSecret } = credentials;
4369
4317
  const token = await getTenantAccessToken(appId, appSecret);
4370
4318
  if (!token) {
4371
- log$27.error("获取 tenant_access_token 失败,无法同步应用信息");
4319
+ log$28.error("获取 tenant_access_token 失败,无法同步应用信息");
4372
4320
  return null;
4373
4321
  }
4374
4322
  const fullInfo = await fetchFromApplicationApi(token);
@@ -4379,13 +4327,13 @@ async function fetchAppInfo(credentials) {
4379
4327
  async function fetchFromApplicationApi(token) {
4380
4328
  try {
4381
4329
  const data = await (await fetch("https://open.feishu.cn/open-apis/application/v6/applications/me?lang=zh_cn", { headers: { Authorization: `Bearer ${token}` } })).json();
4382
- log$27.info("application/v6 API 响应", {
4330
+ log$28.info("application/v6 API 响应", {
4383
4331
  code: data.code,
4384
4332
  msg: data.msg,
4385
4333
  hasApp: !!data.data?.app
4386
4334
  });
4387
4335
  if (data.code !== 0 || !data.data?.app) {
4388
- log$27.warn("application/v6 API 返回非零或无数据,将 fallback", {
4336
+ log$28.warn("application/v6 API 返回非零或无数据,将 fallback", {
4389
4337
  code: data.code,
4390
4338
  msg: data.msg
4391
4339
  });
@@ -4400,7 +4348,7 @@ async function fetchFromApplicationApi(token) {
4400
4348
  helpDocUrl: zhInfo?.help_use
4401
4349
  };
4402
4350
  } catch (err) {
4403
- log$27.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
4351
+ log$28.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
4404
4352
  return null;
4405
4353
  }
4406
4354
  }
@@ -4408,13 +4356,13 @@ async function fetchFromApplicationApi(token) {
4408
4356
  async function fetchFromBotApi(token) {
4409
4357
  try {
4410
4358
  const data = await (await fetch("https://open.feishu.cn/open-apis/bot/v3/info", { headers: { Authorization: `Bearer ${token}` } })).json();
4411
- log$27.info("bot/v3/info API 响应", {
4359
+ log$28.info("bot/v3/info API 响应", {
4412
4360
  code: data.code,
4413
4361
  msg: data.msg,
4414
4362
  hasBot: !!data.bot
4415
4363
  });
4416
4364
  if (data.code !== 0 || !data.bot) {
4417
- log$27.error("bot/v3/info API 失败", {
4365
+ log$28.error("bot/v3/info API 失败", {
4418
4366
  code: data.code,
4419
4367
  msg: data.msg
4420
4368
  });
@@ -4425,7 +4373,7 @@ async function fetchFromBotApi(token) {
4425
4373
  avatarUrl: data.bot.avatar_url
4426
4374
  };
4427
4375
  } catch (err) {
4428
- log$27.error("bot/v3/info API 请求异常", { error: err instanceof Error ? err.message : String(err) });
4376
+ log$28.error("bot/v3/info API 请求异常", { error: err instanceof Error ? err.message : String(err) });
4429
4377
  return null;
4430
4378
  }
4431
4379
  }
@@ -4441,7 +4389,7 @@ async function getTenantAccessToken(appId, appSecret) {
4441
4389
  })
4442
4390
  })).json();
4443
4391
  if (data.code !== 0 || !data.tenant_access_token) {
4444
- log$27.error("获取 tenant_access_token 失败", {
4392
+ log$28.error("获取 tenant_access_token 失败", {
4445
4393
  code: data.code,
4446
4394
  msg: data.msg
4447
4395
  });
@@ -4449,7 +4397,7 @@ async function getTenantAccessToken(appId, appSecret) {
4449
4397
  }
4450
4398
  return data.tenant_access_token;
4451
4399
  } catch (err) {
4452
- log$27.error("获取 tenant_access_token 异常", { error: err instanceof Error ? err.message : String(err) });
4400
+ log$28.error("获取 tenant_access_token 异常", { error: err instanceof Error ? err.message : String(err) });
4453
4401
  return null;
4454
4402
  }
4455
4403
  }
@@ -4487,10 +4435,10 @@ function parseDocTokenFromUrl(url) {
4487
4435
  async function fetchDocContent(docUrl, accessToken) {
4488
4436
  const parsed = parseDocTokenFromUrl(docUrl);
4489
4437
  if (!parsed) {
4490
- log$27.warn("无法从 URL 中解析文档 token", { url: docUrl });
4438
+ log$28.warn("无法从 URL 中解析文档 token", { url: docUrl });
4491
4439
  return null;
4492
4440
  }
4493
- log$27.info("开始读取人设文档", {
4441
+ log$28.info("开始读取人设文档", {
4494
4442
  url: docUrl,
4495
4443
  type: parsed.type,
4496
4444
  token: parsed.token
@@ -4498,18 +4446,18 @@ async function fetchDocContent(docUrl, accessToken) {
4498
4446
  let docToken = parsed.token;
4499
4447
  if (parsed.type === "wiki") {
4500
4448
  const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
4501
- if (!realToken) log$27.warn("wiki 节点解析失败,尝试直接使用 token 读取");
4449
+ if (!realToken) log$28.warn("wiki 节点解析失败,尝试直接使用 token 读取");
4502
4450
  else docToken = realToken;
4503
4451
  }
4504
4452
  try {
4505
4453
  const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
4506
- log$27.info("文档 raw_content API 响应", {
4454
+ log$28.info("文档 raw_content API 响应", {
4507
4455
  code: data.code,
4508
4456
  msg: data.msg,
4509
4457
  contentLength: data.data?.content?.length
4510
4458
  });
4511
4459
  if (data.code !== 0 || !data.data?.content) {
4512
- log$27.warn("读取文档内容失败", {
4460
+ log$28.warn("读取文档内容失败", {
4513
4461
  code: data.code,
4514
4462
  msg: data.msg,
4515
4463
  docToken
@@ -4518,7 +4466,7 @@ async function fetchDocContent(docUrl, accessToken) {
4518
4466
  }
4519
4467
  return data.data.content.trim();
4520
4468
  } catch (err) {
4521
- log$27.error("读取文档内容异常", {
4469
+ log$28.error("读取文档内容异常", {
4522
4470
  error: err instanceof Error ? err.message : String(err),
4523
4471
  docToken
4524
4472
  });
@@ -4529,7 +4477,7 @@ async function fetchDocContent(docUrl, accessToken) {
4529
4477
  async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
4530
4478
  try {
4531
4479
  const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
4532
- log$27.info("wiki get_node API 响应", {
4480
+ log$28.info("wiki get_node API 响应", {
4533
4481
  code: data.code,
4534
4482
  msg: data.msg,
4535
4483
  objType: data.data?.node?.obj_type
@@ -4537,7 +4485,7 @@ async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
4537
4485
  if (data.code !== 0 || !data.data?.node?.obj_token) return null;
4538
4486
  return data.data.node.obj_token;
4539
4487
  } catch (err) {
4540
- log$27.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
4488
+ log$28.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
4541
4489
  return null;
4542
4490
  }
4543
4491
  }
@@ -4559,7 +4507,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
4559
4507
  const infoBlock = buildAppInfoBlock(appInfo);
4560
4508
  if (!existsSync(claudeMdPath)) {
4561
4509
  await writeFile(claudeMdPath, infoBlock + "\n\n" + getDefaultClaudeMdBody(), "utf-8");
4562
- log$27.info("CLAUDE.md 已创建(含应用信息)", { appName: appInfo.appName });
4510
+ log$28.info("CLAUDE.md 已创建(含应用信息)", { appName: appInfo.appName });
4563
4511
  return;
4564
4512
  }
4565
4513
  let content = await readFile(claudeMdPath, "utf-8");
@@ -4571,7 +4519,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
4571
4519
  content = before + infoBlock + after;
4572
4520
  } else content = infoBlock + "\n\n" + content;
4573
4521
  await writeFile(claudeMdPath, content, "utf-8");
4574
- log$27.info("CLAUDE.md 应用信息已同步", {
4522
+ log$28.info("CLAUDE.md 应用信息已同步", {
4575
4523
  appName: appInfo.appName,
4576
4524
  hasDescription: !!appInfo.description,
4577
4525
  hasAvatar: !!appInfo.avatarUrl
@@ -4586,7 +4534,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
4586
4534
  async function syncPersonaDocToClaudeMd(personaContent) {
4587
4535
  const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
4588
4536
  if (!existsSync(claudeMdPath)) {
4589
- log$27.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
4537
+ log$28.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
4590
4538
  return;
4591
4539
  }
4592
4540
  let content = await readFile(claudeMdPath, "utf-8");
@@ -4607,7 +4555,7 @@ async function syncPersonaDocToClaudeMd(personaContent) {
4607
4555
  content = before + personaBlock + after;
4608
4556
  } else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
4609
4557
  await writeFile(claudeMdPath, content, "utf-8");
4610
- log$27.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
4558
+ log$28.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
4611
4559
  }
4612
4560
  /** 构建应用信息标记区块 */
4613
4561
  function buildAppInfoBlock(appInfo) {
@@ -4699,24 +4647,24 @@ function getDefaultClaudeMdBody() {
4699
4647
  * @returns 同步后的应用信息,如果失败返回 null
4700
4648
  */
4701
4649
  async function syncAppInfo(credentials) {
4702
- log$27.info("开始同步应用信息", { appId: credentials.appId });
4650
+ log$28.info("开始同步应用信息", { appId: credentials.appId });
4703
4651
  const appInfo = await fetchAppInfo(credentials);
4704
4652
  if (!appInfo) {
4705
- log$27.warn("获取应用信息失败,跳过同步");
4653
+ log$28.warn("获取应用信息失败,跳过同步");
4706
4654
  return null;
4707
4655
  }
4708
4656
  await syncAppInfoToClaudeMd(appInfo);
4709
4657
  if (appInfo.helpDocUrl) {
4710
- log$27.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
4658
+ log$28.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
4711
4659
  const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
4712
4660
  if (token) {
4713
4661
  const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
4714
4662
  if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
4715
- else log$27.warn("人设文档内容为空或读取失败,跳过同步");
4663
+ else log$28.warn("人设文档内容为空或读取失败,跳过同步");
4716
4664
  }
4717
4665
  }
4718
4666
  await installSyncSkill();
4719
- log$27.info("应用信息同步完成", {
4667
+ log$28.info("应用信息同步完成", {
4720
4668
  appName: appInfo.appName,
4721
4669
  description: appInfo.description?.substring(0, 50),
4722
4670
  hasPersonaDoc: !!appInfo.helpDocUrl
@@ -4734,7 +4682,7 @@ async function installSyncSkill() {
4734
4682
  if ((await readFile(SYNC_SKILL_PATH, "utf-8")).includes(`skill-version: ${SYNC_SKILL_VERSION}`)) return;
4735
4683
  }
4736
4684
  await writeFile(SYNC_SKILL_PATH, SYNC_SKILL_CONTENT, "utf-8");
4737
- log$27.info("sync-app-info 技能已安装", { path: SYNC_SKILL_PATH });
4685
+ log$28.info("sync-app-info 技能已安装", { path: SYNC_SKILL_PATH });
4738
4686
  }
4739
4687
  const SYNC_SKILL_CONTENT = `---
4740
4688
  skill-version: ${SYNC_SKILL_VERSION}
@@ -5730,7 +5678,7 @@ function createHooksRouter() {
5730
5678
  }
5731
5679
  //#endregion
5732
5680
  //#region src/memory/store.ts
5733
- const log$26 = larkLogger("memory/store");
5681
+ const log$27 = larkLogger("memory/store");
5734
5682
  const MEMORY_VERSION = 1;
5735
5683
  const MAX_TEAMMATE_RECENT = 3;
5736
5684
  const MAX_LONG_TERM = 100;
@@ -5756,7 +5704,7 @@ var MemoryStore = class MemoryStore {
5756
5704
  const raw = await readFile(path, "utf-8");
5757
5705
  return normalizeMemoryFile(JSON.parse(raw));
5758
5706
  } catch (err) {
5759
- log$26.warn("memory file load failed, using empty state", {
5707
+ log$27.warn("memory file load failed, using empty state", {
5760
5708
  cwd,
5761
5709
  path,
5762
5710
  error: err instanceof Error ? err.message : String(err)
@@ -6252,7 +6200,7 @@ function createMcpCallbackRouter() {
6252
6200
  * 提供 /api/scheduled-tasks 的 RESTful CRUD 接口。
6253
6201
  * 每个请求和响应都记录详细日志,便于后续错误排查。
6254
6202
  */
6255
- const log$25 = larkLogger("gateway/scheduler-handler");
6203
+ const log$26 = larkLogger("gateway/scheduler-handler");
6256
6204
  /**
6257
6205
  * 创建定时任务路由
6258
6206
  *
@@ -6263,16 +6211,16 @@ function createSchedulerRouter(taskManager) {
6263
6211
  const router = Router();
6264
6212
  router.get("/api/scheduled-tasks", (req, res) => {
6265
6213
  const requestId = req.headers["x-request-id"];
6266
- log$25.info("请求列出所有定时任务", { requestId });
6214
+ log$26.info("请求列出所有定时任务", { requestId });
6267
6215
  try {
6268
6216
  const tasks = taskManager.listTasks();
6269
- log$25.info("返回定时任务列表", {
6217
+ log$26.info("返回定时任务列表", {
6270
6218
  requestId,
6271
6219
  count: tasks.length
6272
6220
  });
6273
6221
  res.json({ tasks });
6274
6222
  } catch (err) {
6275
- log$25.error("列出定时任务失败", {
6223
+ log$26.error("列出定时任务失败", {
6276
6224
  requestId,
6277
6225
  error: String(err)
6278
6226
  });
@@ -6285,13 +6233,13 @@ function createSchedulerRouter(taskManager) {
6285
6233
  router.post("/api/scheduled-tasks", (req, res) => {
6286
6234
  const requestId = req.headers["x-request-id"];
6287
6235
  const body = req.body;
6288
- log$25.info("请求创建定时任务", {
6236
+ log$26.info("请求创建定时任务", {
6289
6237
  requestId,
6290
6238
  body: JSON.stringify(body)
6291
6239
  });
6292
6240
  const { name, cron: cronExpr, session_id, cwd, prompt, example_output, constraints } = body;
6293
6241
  if (!name || typeof name !== "string") {
6294
- log$25.warn("创建任务参数缺失: name", { requestId });
6242
+ log$26.warn("创建任务参数缺失: name", { requestId });
6295
6243
  res.status(400).json({
6296
6244
  error: "参数错误",
6297
6245
  detail: "name 为必填字符串"
@@ -6299,7 +6247,7 @@ function createSchedulerRouter(taskManager) {
6299
6247
  return;
6300
6248
  }
6301
6249
  if (!cronExpr || typeof cronExpr !== "string") {
6302
- log$25.warn("创建任务参数缺失: cron", { requestId });
6250
+ log$26.warn("创建任务参数缺失: cron", { requestId });
6303
6251
  res.status(400).json({
6304
6252
  error: "参数错误",
6305
6253
  detail: "cron 为必填字符串"
@@ -6307,7 +6255,7 @@ function createSchedulerRouter(taskManager) {
6307
6255
  return;
6308
6256
  }
6309
6257
  if (!session_id || typeof session_id !== "string") {
6310
- log$25.warn("创建任务参数缺失: session_id", { requestId });
6258
+ log$26.warn("创建任务参数缺失: session_id", { requestId });
6311
6259
  res.status(400).json({
6312
6260
  error: "参数错误",
6313
6261
  detail: "session_id 为必填字符串"
@@ -6315,7 +6263,7 @@ function createSchedulerRouter(taskManager) {
6315
6263
  return;
6316
6264
  }
6317
6265
  if (!cwd || typeof cwd !== "string") {
6318
- log$25.warn("创建任务参数缺失: cwd", { requestId });
6266
+ log$26.warn("创建任务参数缺失: cwd", { requestId });
6319
6267
  res.status(400).json({
6320
6268
  error: "参数错误",
6321
6269
  detail: "cwd 为必填字符串"
@@ -6323,7 +6271,7 @@ function createSchedulerRouter(taskManager) {
6323
6271
  return;
6324
6272
  }
6325
6273
  if (!prompt || typeof prompt !== "string") {
6326
- log$25.warn("创建任务参数缺失: prompt", { requestId });
6274
+ log$26.warn("创建任务参数缺失: prompt", { requestId });
6327
6275
  res.status(400).json({
6328
6276
  error: "参数错误",
6329
6277
  detail: "prompt 为必填字符串"
@@ -6331,7 +6279,7 @@ function createSchedulerRouter(taskManager) {
6331
6279
  return;
6332
6280
  }
6333
6281
  if (!cron.validate(cronExpr)) {
6334
- log$25.warn("cron 表达式不合法", {
6282
+ log$26.warn("cron 表达式不合法", {
6335
6283
  requestId,
6336
6284
  cron: cronExpr
6337
6285
  });
@@ -6352,7 +6300,7 @@ function createSchedulerRouter(taskManager) {
6352
6300
  constraints
6353
6301
  };
6354
6302
  const task = taskManager.createTask(params);
6355
- log$25.info("定时任务创建成功", {
6303
+ log$26.info("定时任务创建成功", {
6356
6304
  requestId,
6357
6305
  taskId: task.id,
6358
6306
  name: task.name,
@@ -6360,7 +6308,7 @@ function createSchedulerRouter(taskManager) {
6360
6308
  });
6361
6309
  res.status(201).json({ task });
6362
6310
  } catch (err) {
6363
- log$25.error("创建定时任务失败", {
6311
+ log$26.error("创建定时任务失败", {
6364
6312
  requestId,
6365
6313
  error: String(err)
6366
6314
  });
@@ -6374,13 +6322,13 @@ function createSchedulerRouter(taskManager) {
6374
6322
  const requestId = req.headers["x-request-id"];
6375
6323
  const id = String(req.params.id);
6376
6324
  const body = req.body;
6377
- log$25.info("请求更新定时任务", {
6325
+ log$26.info("请求更新定时任务", {
6378
6326
  requestId,
6379
6327
  taskId: id,
6380
6328
  body: JSON.stringify(body)
6381
6329
  });
6382
6330
  if (!taskManager.getTask(id)) {
6383
- log$25.warn("更新的任务不存在", {
6331
+ log$26.warn("更新的任务不存在", {
6384
6332
  requestId,
6385
6333
  taskId: id
6386
6334
  });
@@ -6392,7 +6340,7 @@ function createSchedulerRouter(taskManager) {
6392
6340
  }
6393
6341
  if (body.cron !== void 0) {
6394
6342
  if (typeof body.cron !== "string" || !cron.validate(body.cron)) {
6395
- log$25.warn("更新的 cron 表达式不合法", {
6343
+ log$26.warn("更新的 cron 表达式不合法", {
6396
6344
  requestId,
6397
6345
  taskId: id,
6398
6346
  cron: body.cron
@@ -6413,7 +6361,7 @@ function createSchedulerRouter(taskManager) {
6413
6361
  if (body.constraints !== void 0) updates.constraints = body.constraints;
6414
6362
  if (body.enabled !== void 0) updates.enabled = body.enabled;
6415
6363
  const task = taskManager.updateTask(id, updates);
6416
- log$25.info("定时任务更新成功", {
6364
+ log$26.info("定时任务更新成功", {
6417
6365
  requestId,
6418
6366
  taskId: task.id,
6419
6367
  name: task.name,
@@ -6421,7 +6369,7 @@ function createSchedulerRouter(taskManager) {
6421
6369
  });
6422
6370
  res.json({ task });
6423
6371
  } catch (err) {
6424
- log$25.error("更新定时任务失败", {
6372
+ log$26.error("更新定时任务失败", {
6425
6373
  requestId,
6426
6374
  taskId: id,
6427
6375
  error: String(err)
@@ -6435,12 +6383,12 @@ function createSchedulerRouter(taskManager) {
6435
6383
  router.delete("/api/scheduled-tasks/:id", (req, res) => {
6436
6384
  const requestId = req.headers["x-request-id"];
6437
6385
  const id = String(req.params.id);
6438
- log$25.info("请求删除定时任务", {
6386
+ log$26.info("请求删除定时任务", {
6439
6387
  requestId,
6440
6388
  taskId: id
6441
6389
  });
6442
6390
  if (!taskManager.getTask(id)) {
6443
- log$25.warn("删除的任务不存在", {
6391
+ log$26.warn("删除的任务不存在", {
6444
6392
  requestId,
6445
6393
  taskId: id
6446
6394
  });
@@ -6452,7 +6400,7 @@ function createSchedulerRouter(taskManager) {
6452
6400
  }
6453
6401
  try {
6454
6402
  const deleted = taskManager.deleteTask(id);
6455
- log$25.info("定时任务删除结果", {
6403
+ log$26.info("定时任务删除结果", {
6456
6404
  requestId,
6457
6405
  taskId: id,
6458
6406
  deleted
@@ -6462,7 +6410,7 @@ function createSchedulerRouter(taskManager) {
6462
6410
  deleted: true
6463
6411
  });
6464
6412
  } catch (err) {
6465
- log$25.error("删除定时任务失败", {
6413
+ log$26.error("删除定时任务失败", {
6466
6414
  requestId,
6467
6415
  taskId: id,
6468
6416
  error: String(err)
@@ -6712,6 +6660,7 @@ const PERSONA_FILE = join(homedir(), ".claude", "CLAUDE.md");
6712
6660
  function createStatusRouter(config) {
6713
6661
  const router = express.Router();
6714
6662
  const sessionsDir = getWorkspaceChatsRoot(config?.workspaceRoot ?? resolveWorkspaceRoot("/workspace"));
6663
+ const processManager = config?.processManager;
6715
6664
  router.get("/sessions", async (req, res) => {
6716
6665
  logger$2.info("list sessions request", {
6717
6666
  method: req.method,
@@ -6812,14 +6761,18 @@ function createStatusRouter(config) {
6812
6761
  try {
6813
6762
  const uptimeSeconds = process.uptime();
6814
6763
  const memory = process.memoryUsage();
6764
+ const processes = processManager?.getAllProcessInfo().map((processInfo) => {
6765
+ return serializeRuntimeProcess(processInfo, processManager);
6766
+ }) ?? [];
6815
6767
  const statusData = {
6816
6768
  uptime: uptimeSeconds,
6817
- processes: [],
6769
+ processes,
6818
6770
  memory
6819
6771
  };
6820
6772
  logger$2.info("get status response", {
6821
6773
  uptime: uptimeSeconds,
6822
- memoryRss: memory.rss
6774
+ memoryRss: memory.rss,
6775
+ processCount: processes.length
6823
6776
  });
6824
6777
  res.json(statusData);
6825
6778
  } catch (err) {
@@ -6863,6 +6816,14 @@ function createStatusRouter(config) {
6863
6816
  });
6864
6817
  return router;
6865
6818
  }
6819
+ function serializeRuntimeProcess(processInfo, processManager) {
6820
+ return {
6821
+ ...processInfo,
6822
+ startedAt: processInfo.startedAt.toISOString(),
6823
+ lastActiveAt: processInfo.lastActiveAt.toISOString(),
6824
+ busy: processManager.isSessionBusy?.(processInfo.sessionId) ?? processInfo.status === "running"
6825
+ };
6826
+ }
6866
6827
  //#endregion
6867
6828
  //#region src/gateway/server.ts
6868
6829
  const logger$1 = larkLogger("gateway");
@@ -6926,7 +6887,10 @@ function registerRoutes(app, processManager, scheduledTaskManager, appCredential
6926
6887
  app.put("/api/scheduled-tasks/:id", notImplemented);
6927
6888
  app.delete("/api/scheduled-tasks/:id", notImplemented);
6928
6889
  }
6929
- app.use("/api", createStatusRouter({ workspaceRoot }));
6890
+ app.use("/api", createStatusRouter({
6891
+ workspaceRoot,
6892
+ processManager
6893
+ }));
6930
6894
  app.use("/hooks", createHooksRouter());
6931
6895
  app.use("/api/mcp", createMcpCallbackRouter());
6932
6896
  app.use(createChatAuthRouter());
@@ -7019,7 +6983,7 @@ function createGatewayServer(config) {
7019
6983
  *
7020
6984
  * 持久化文件: /workspace/config/scheduled-tasks.json
7021
6985
  */
7022
- const log$24 = larkLogger("gateway/scheduler");
6986
+ const log$25 = larkLogger("gateway/scheduler");
7023
6987
  /** 持久化文件路径 */
7024
6988
  const PERSIST_PATH = "/workspace/config/scheduled-tasks.json";
7025
6989
  /**
@@ -7037,7 +7001,7 @@ var ScheduledTaskManager = class {
7037
7001
  processManager;
7038
7002
  constructor(processManager) {
7039
7003
  this.processManager = processManager;
7040
- log$24.info("ScheduledTaskManager 已创建");
7004
+ log$25.info("ScheduledTaskManager 已创建");
7041
7005
  }
7042
7006
  /**
7043
7007
  * 创建定时任务
@@ -7057,7 +7021,7 @@ var ScheduledTaskManager = class {
7057
7021
  enabled: true,
7058
7022
  created_at: (/* @__PURE__ */ new Date()).toISOString()
7059
7023
  };
7060
- log$24.info("创建定时任务", {
7024
+ log$25.info("创建定时任务", {
7061
7025
  taskId: task.id,
7062
7026
  name: task.name,
7063
7027
  cron: task.cron,
@@ -7093,7 +7057,7 @@ var ScheduledTaskManager = class {
7093
7057
  if (updates.example_output !== void 0) task.example_output = updates.example_output;
7094
7058
  if (updates.constraints !== void 0) task.constraints = updates.constraints;
7095
7059
  if (updates.enabled !== void 0) task.enabled = updates.enabled;
7096
- log$24.info("更新定时任务", {
7060
+ log$25.info("更新定时任务", {
7097
7061
  taskId: id,
7098
7062
  name: task.name,
7099
7063
  cronChanged: oldCron !== task.cron,
@@ -7115,10 +7079,10 @@ var ScheduledTaskManager = class {
7115
7079
  deleteTask(id) {
7116
7080
  const task = this.tasks.get(id);
7117
7081
  if (!task) {
7118
- log$24.warn("尝试删除不存在的任务", { taskId: id });
7082
+ log$25.warn("尝试删除不存在的任务", { taskId: id });
7119
7083
  return false;
7120
7084
  }
7121
- log$24.info("删除定时任务", {
7085
+ log$25.info("删除定时任务", {
7122
7086
  taskId: id,
7123
7087
  name: task.name
7124
7088
  });
@@ -7133,28 +7097,28 @@ var ScheduledTaskManager = class {
7133
7097
  * 启动时调用,读取 JSON 文件并为每个 enabled 的任务注册 cron。
7134
7098
  */
7135
7099
  async loadFromDisk() {
7136
- log$24.info("从磁盘加载定时任务", { path: PERSIST_PATH });
7100
+ log$25.info("从磁盘加载定时任务", { path: PERSIST_PATH });
7137
7101
  try {
7138
7102
  const content = await readFile(PERSIST_PATH, "utf-8");
7139
7103
  const data = JSON.parse(content);
7140
7104
  if (!Array.isArray(data)) {
7141
- log$24.warn("持久化文件格式异常,跳过加载", { path: PERSIST_PATH });
7105
+ log$25.warn("持久化文件格式异常,跳过加载", { path: PERSIST_PATH });
7142
7106
  return;
7143
7107
  }
7144
7108
  for (const task of data) {
7145
7109
  this.tasks.set(task.id, task);
7146
7110
  if (task.enabled) this.registerCronJob(task);
7147
7111
  }
7148
- log$24.info("定时任务加载完成", {
7112
+ log$25.info("定时任务加载完成", {
7149
7113
  total: data.length,
7150
7114
  enabled: data.filter((t) => t.enabled).length
7151
7115
  });
7152
7116
  } catch (err) {
7153
7117
  if (err.code === "ENOENT") {
7154
- log$24.info("持久化文件不存在,跳过加载(首次启动)", { path: PERSIST_PATH });
7118
+ log$25.info("持久化文件不存在,跳过加载(首次启动)", { path: PERSIST_PATH });
7155
7119
  return;
7156
7120
  }
7157
- log$24.error("加载定时任务失败", {
7121
+ log$25.error("加载定时任务失败", {
7158
7122
  path: PERSIST_PATH,
7159
7123
  error: String(err)
7160
7124
  });
@@ -7170,12 +7134,12 @@ var ScheduledTaskManager = class {
7170
7134
  try {
7171
7135
  await mkdir(dirname(PERSIST_PATH), { recursive: true });
7172
7136
  await writeFile(PERSIST_PATH, JSON.stringify(tasks, null, 2), "utf-8");
7173
- log$24.info("定时任务已持久化到磁盘", {
7137
+ log$25.info("定时任务已持久化到磁盘", {
7174
7138
  path: PERSIST_PATH,
7175
7139
  count: tasks.length
7176
7140
  });
7177
7141
  } catch (err) {
7178
- log$24.error("持久化定时任务失败", {
7142
+ log$25.error("持久化定时任务失败", {
7179
7143
  path: PERSIST_PATH,
7180
7144
  error: String(err)
7181
7145
  });
@@ -7188,13 +7152,13 @@ var ScheduledTaskManager = class {
7188
7152
  */
7189
7153
  registerCronJob(task) {
7190
7154
  this.unregisterCronJob(task.id);
7191
- log$24.info("注册 cron 调度", {
7155
+ log$25.info("注册 cron 调度", {
7192
7156
  taskId: task.id,
7193
7157
  name: task.name,
7194
7158
  cron: task.cron
7195
7159
  });
7196
7160
  const job = cron.schedule(task.cron, () => {
7197
- log$24.info("cron 触发任务执行", {
7161
+ log$25.info("cron 触发任务执行", {
7198
7162
  taskId: task.id,
7199
7163
  name: task.name
7200
7164
  });
@@ -7210,7 +7174,7 @@ var ScheduledTaskManager = class {
7210
7174
  if (job) {
7211
7175
  job.stop();
7212
7176
  this.cronJobs.delete(taskId);
7213
- log$24.info("已注销 cron 调度", { taskId });
7177
+ log$25.info("已注销 cron 调度", { taskId });
7214
7178
  }
7215
7179
  }
7216
7180
  /**
@@ -7220,10 +7184,10 @@ var ScheduledTaskManager = class {
7220
7184
  */
7221
7185
  stopAll() {
7222
7186
  const count = this.cronJobs.size;
7223
- log$24.info("停止所有 cron 调度", { count });
7187
+ log$25.info("停止所有 cron 调度", { count });
7224
7188
  for (const [taskId, job] of this.cronJobs) {
7225
7189
  job.stop();
7226
- log$24.debug("已停止 cron 调度", { taskId });
7190
+ log$25.debug("已停止 cron 调度", { taskId });
7227
7191
  }
7228
7192
  this.cronJobs.clear();
7229
7193
  }
@@ -7237,17 +7201,17 @@ var ScheduledTaskManager = class {
7237
7201
  async executeTask(taskId) {
7238
7202
  const task = this.tasks.get(taskId);
7239
7203
  if (!task) {
7240
- log$24.warn("任务执行时未找到任务", { taskId });
7204
+ log$25.warn("任务执行时未找到任务", { taskId });
7241
7205
  return;
7242
7206
  }
7243
7207
  if (!task.enabled) {
7244
- log$24.info("任务已禁用,跳过执行", {
7208
+ log$25.info("任务已禁用,跳过执行", {
7245
7209
  taskId,
7246
7210
  name: task.name
7247
7211
  });
7248
7212
  return;
7249
7213
  }
7250
- log$24.info("开始执行定时任务", {
7214
+ log$25.info("开始执行定时任务", {
7251
7215
  taskId: task.id,
7252
7216
  name: task.name,
7253
7217
  sessionId: task.session_id,
@@ -7268,7 +7232,7 @@ var ScheduledTaskManager = class {
7268
7232
  onResult: (result) => {
7269
7233
  const summary = result.result ?? resultText;
7270
7234
  task.last_result = summary.slice(0, 2e3);
7271
- log$24.info("定时任务执行完成", {
7235
+ log$25.info("定时任务执行完成", {
7272
7236
  taskId: task.id,
7273
7237
  name: task.name,
7274
7238
  subtype: result.subtype,
@@ -7281,7 +7245,7 @@ var ScheduledTaskManager = class {
7281
7245
  },
7282
7246
  onError: (error) => {
7283
7247
  task.last_result = `执行失败: ${error.message}`;
7284
- log$24.error("定时任务执行失败", {
7248
+ log$25.error("定时任务执行失败", {
7285
7249
  taskId: task.id,
7286
7250
  name: task.name,
7287
7251
  error: error.message
@@ -7293,7 +7257,7 @@ var ScheduledTaskManager = class {
7293
7257
  await this.processManager.executePrompt(config, callbacks);
7294
7258
  } catch (err) {
7295
7259
  task.last_result = `执行异常: ${String(err)}`;
7296
- log$24.error("定时任务执行异常", {
7260
+ log$25.error("定时任务执行异常", {
7297
7261
  taskId: task.id,
7298
7262
  name: task.name,
7299
7263
  error: String(err)
@@ -7322,17 +7286,18 @@ var ScheduledTaskManager = class {
7322
7286
  * - 会话元数据:{workspaceRoot}/.chat-history/sessions/{userId}.json
7323
7287
  * - 消息数据:{workspaceRoot}/.chat-history/messages/{sessionId}.json
7324
7288
  */
7325
- const log$23 = larkLogger("chat/message-store");
7289
+ const log$24 = larkLogger("chat/message-store");
7326
7290
  var SessionMessageStore = class {
7327
7291
  baseDir;
7328
7292
  sessionsDir;
7329
7293
  messagesDir;
7294
+ jsonCache = /* @__PURE__ */ new Map();
7330
7295
  constructor(workspaceRoot) {
7331
7296
  const root = workspaceRoot || resolveWorkspaceRoot();
7332
7297
  this.baseDir = getWorkspaceChatHistoryRoot(root);
7333
7298
  this.sessionsDir = join(this.baseDir, "sessions");
7334
7299
  this.messagesDir = join(this.baseDir, "messages");
7335
- log$23.info("初始化消息存储", { baseDir: this.baseDir });
7300
+ log$24.info("初始化消息存储", { baseDir: this.baseDir });
7336
7301
  }
7337
7302
  /**
7338
7303
  * 创建新会话
@@ -7345,7 +7310,7 @@ var SessionMessageStore = class {
7345
7310
  updatedAt: now,
7346
7311
  messageCount: 0
7347
7312
  };
7348
- log$23.info("创建会话", {
7313
+ log$24.info("创建会话", {
7349
7314
  sessionId: fullSession.id,
7350
7315
  userId: fullSession.userId
7351
7316
  });
@@ -7358,7 +7323,7 @@ var SessionMessageStore = class {
7358
7323
  * 获取指定会话
7359
7324
  */
7360
7325
  async getSession(sessionId) {
7361
- log$23.debug("获取会话", { sessionId });
7326
+ log$24.debug("获取会话", { sessionId });
7362
7327
  try {
7363
7328
  const files = await this.listSessionFiles();
7364
7329
  for (const file of files) {
@@ -7368,7 +7333,7 @@ var SessionMessageStore = class {
7368
7333
  if (found) return found;
7369
7334
  }
7370
7335
  } catch (err) {
7371
- log$23.error("获取会话失败", {
7336
+ log$24.error("获取会话失败", {
7372
7337
  sessionId,
7373
7338
  error: String(err)
7374
7339
  });
@@ -7379,14 +7344,14 @@ var SessionMessageStore = class {
7379
7344
  * 列出指定用户的所有会话
7380
7345
  */
7381
7346
  async listSessions(userId) {
7382
- log$23.debug("列出用户会话", { userId });
7347
+ log$24.debug("列出用户会话", { userId });
7383
7348
  return await this.readUserSessions(userId);
7384
7349
  }
7385
7350
  /**
7386
7351
  * 删除指定会话(同时删除消息文件)
7387
7352
  */
7388
7353
  async deleteSession(sessionId) {
7389
- log$23.info("删除会话", { sessionId });
7354
+ log$24.info("删除会话", { sessionId });
7390
7355
  try {
7391
7356
  const files = await this.listSessionFiles();
7392
7357
  for (const file of files) {
@@ -7401,9 +7366,9 @@ var SessionMessageStore = class {
7401
7366
  }
7402
7367
  const msgFile = this.getMessageFilePath(sessionId);
7403
7368
  await this.writeJsonFile(msgFile, []);
7404
- log$23.info("会话删除完成", { sessionId });
7369
+ log$24.info("会话删除完成", { sessionId });
7405
7370
  } catch (err) {
7406
- log$23.error("删除会话失败", {
7371
+ log$24.error("删除会话失败", {
7407
7372
  sessionId,
7408
7373
  error: String(err)
7409
7374
  });
@@ -7420,7 +7385,7 @@ var SessionMessageStore = class {
7420
7385
  id: v4(),
7421
7386
  timestamp: Date.now()
7422
7387
  };
7423
- log$23.info("追加消息", {
7388
+ log$24.info("追加消息", {
7424
7389
  messageId: fullMessage.id,
7425
7390
  sessionId: fullMessage.sessionId,
7426
7391
  role: fullMessage.role
@@ -7431,9 +7396,9 @@ var SessionMessageStore = class {
7431
7396
  messages.push(fullMessage);
7432
7397
  await this.writeJsonFile(msgFile, messages);
7433
7398
  await this.updateSessionMeta(fullMessage.sessionId, messages.length);
7434
- log$23.debug("消息追加完成", { messageId: fullMessage.id });
7399
+ log$24.debug("消息追加完成", { messageId: fullMessage.id });
7435
7400
  } catch (err) {
7436
- log$23.error("追加消息失败", {
7401
+ log$24.error("追加消息失败", {
7437
7402
  sessionId: fullMessage.sessionId,
7438
7403
  error: String(err)
7439
7404
  });
@@ -7448,7 +7413,7 @@ var SessionMessageStore = class {
7448
7413
  async getMessages(sessionId, options) {
7449
7414
  const limit = options?.limit ?? 50;
7450
7415
  const cursor = options?.cursor;
7451
- log$23.debug("获取消息列表", {
7416
+ log$24.debug("获取消息列表", {
7452
7417
  sessionId,
7453
7418
  cursor,
7454
7419
  limit
@@ -7467,7 +7432,7 @@ var SessionMessageStore = class {
7467
7432
  nextCursor: startIdx + limit < allMessages.length ? sliced[sliced.length - 1]?.id : void 0
7468
7433
  };
7469
7434
  } catch (err) {
7470
- log$23.error("获取消息列表失败", {
7435
+ log$24.error("获取消息列表失败", {
7471
7436
  sessionId,
7472
7437
  error: String(err)
7473
7438
  });
@@ -7478,12 +7443,12 @@ var SessionMessageStore = class {
7478
7443
  * 获取会话消息总数
7479
7444
  */
7480
7445
  async getMessageCount(sessionId) {
7481
- log$23.debug("获取消息数量", { sessionId });
7446
+ log$24.debug("获取消息数量", { sessionId });
7482
7447
  try {
7483
7448
  const msgFile = this.getMessageFilePath(sessionId);
7484
7449
  return (await this.readJsonFile(msgFile) || []).length;
7485
7450
  } catch (err) {
7486
- log$23.error("获取消息数量失败", {
7451
+ log$24.error("获取消息数量失败", {
7487
7452
  sessionId,
7488
7453
  error: String(err)
7489
7454
  });
@@ -7534,7 +7499,7 @@ var SessionMessageStore = class {
7534
7499
  }
7535
7500
  }
7536
7501
  } catch (err) {
7537
- log$23.warn("更新会话元数据失败", {
7502
+ log$24.warn("更新会话元数据失败", {
7538
7503
  sessionId,
7539
7504
  error: String(err)
7540
7505
  });
@@ -7549,7 +7514,7 @@ var SessionMessageStore = class {
7549
7514
  await mkdir(this.sessionsDir, { recursive: true });
7550
7515
  return (await readdir(this.sessionsDir)).filter((e) => e.endsWith(".json")).map((e) => join(this.sessionsDir, e));
7551
7516
  } catch (err) {
7552
- log$23.error("列出会话索引文件失败", { error: String(err) });
7517
+ log$24.error("列出会话索引文件失败", { error: String(err) });
7553
7518
  return [];
7554
7519
  }
7555
7520
  }
@@ -7559,13 +7524,19 @@ var SessionMessageStore = class {
7559
7524
  async readJsonFile(filePath) {
7560
7525
  try {
7561
7526
  const raw = await readFile(filePath, "utf-8");
7562
- return JSON.parse(raw);
7527
+ const parsed = JSON.parse(raw);
7528
+ this.jsonCache.set(filePath, parsed);
7529
+ return parsed;
7563
7530
  } catch (err) {
7564
7531
  if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
7565
- log$23.error("读取 JSON 文件失败", {
7532
+ log$24.error("读取 JSON 文件失败", {
7566
7533
  filePath,
7567
7534
  error: String(err)
7568
7535
  });
7536
+ if (this.jsonCache.has(filePath)) {
7537
+ log$24.warn("读取 JSON 文件失败,使用内存中的上一份完整快照", { filePath });
7538
+ return this.jsonCache.get(filePath);
7539
+ }
7569
7540
  return null;
7570
7541
  }
7571
7542
  }
@@ -7573,18 +7544,39 @@ var SessionMessageStore = class {
7573
7544
  * 通用 JSON 文件写入(自动创建目录)
7574
7545
  */
7575
7546
  async writeJsonFile(filePath, data) {
7547
+ const dir = dirname(filePath);
7548
+ const tmpPath = join(dir, `.${basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
7576
7549
  try {
7577
- const { dirname } = await import("node:path");
7578
- await mkdir(dirname(filePath), { recursive: true });
7579
- await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
7550
+ await mkdir(dir, { recursive: true });
7551
+ const handle = await open(tmpPath, "w");
7552
+ try {
7553
+ await handle.writeFile(JSON.stringify(data, null, 2), "utf-8");
7554
+ await handle.sync();
7555
+ } finally {
7556
+ await handle.close();
7557
+ }
7558
+ await rename(tmpPath, filePath);
7559
+ this.jsonCache.set(filePath, data);
7560
+ await this.fsyncDirectory(dir);
7580
7561
  } catch (err) {
7581
- log$23.error("写入 JSON 文件失败", {
7562
+ log$24.error("写入 JSON 文件失败", {
7582
7563
  filePath,
7583
7564
  error: String(err)
7584
7565
  });
7566
+ await unlink(tmpPath).catch(() => void 0);
7585
7567
  throw err;
7586
7568
  }
7587
7569
  }
7570
+ async fsyncDirectory(dir) {
7571
+ try {
7572
+ const handle = await open(dir, "r");
7573
+ try {
7574
+ await handle.sync();
7575
+ } finally {
7576
+ await handle.close();
7577
+ }
7578
+ } catch {}
7579
+ }
7588
7580
  };
7589
7581
  //#endregion
7590
7582
  //#region src/messaging/inbound/user-name-cache-store.ts
@@ -7859,7 +7851,7 @@ function getUserAgent() {
7859
7851
  * - `LarkClient.fromCredentials(credentials)` — ephemeral instance (not cached)
7860
7852
  * - `LarkClient.fromProvider(provider, opts)` — from ICredentialProvider (not cached)
7861
7853
  */
7862
- const log$21 = larkLogger("core/lark-client");
7854
+ const log$22 = larkLogger("core/lark-client");
7863
7855
  const GLOBAL_LARK_USER_AGENT_KEY = "LARK_USER_AGENT";
7864
7856
  function installGlobalUserAgent() {
7865
7857
  globalThis[GLOBAL_LARK_USER_AGENT_KEY] = getUserAgent();
@@ -7951,7 +7943,7 @@ var LarkClient = class LarkClient {
7951
7943
  const existing = cache.get(account.accountId);
7952
7944
  if (existing && existing.account.appId === account.appId && credentialsEqual(existing.account.appSecret, account.appSecret)) return existing;
7953
7945
  if (existing) {
7954
- log$21.info(`credentials changed, disposing stale instance`, { accountId: account.accountId });
7946
+ log$22.info(`credentials changed, disposing stale instance`, { accountId: account.accountId });
7955
7947
  existing.dispose();
7956
7948
  }
7957
7949
  const instance = new LarkClient(account);
@@ -7994,7 +7986,7 @@ var LarkClient = class LarkClient {
7994
7986
  static fromProvider(provider, opts) {
7995
7987
  const appId = provider.getAppId();
7996
7988
  const appSecret = provider.getAppSecret();
7997
- log$21.info("通过 ICredentialProvider 创建 LarkClient", {
7989
+ log$22.info("通过 ICredentialProvider 创建 LarkClient", {
7998
7990
  appId,
7999
7991
  accountId: opts?.accountId ?? "default",
8000
7992
  brand: opts?.brand ?? "feishu"
@@ -8030,7 +8022,7 @@ var LarkClient = class LarkClient {
8030
8022
  get sdk() {
8031
8023
  if (!this._sdk) {
8032
8024
  const { appId, appSecret } = this.requireCredentials();
8033
- log$21.info("创建 Lark SDK 客户端实例", {
8025
+ log$22.info("创建 Lark SDK 客户端实例", {
8034
8026
  accountId: this.accountId,
8035
8027
  appId,
8036
8028
  brand: this.account.brand
@@ -8045,7 +8037,7 @@ var LarkClient = class LarkClient {
8045
8037
  if (sdkAny.httpInstance?.interceptors) {
8046
8038
  const accountId = this.accountId;
8047
8039
  sdkAny.httpInstance.interceptors.request.use((req) => {
8048
- log$21.debug("飞书 API 请求", {
8040
+ log$22.debug("飞书 API 请求", {
8049
8041
  accountId,
8050
8042
  method: req.method,
8051
8043
  url: req.url,
@@ -8055,7 +8047,7 @@ var LarkClient = class LarkClient {
8055
8047
  return req;
8056
8048
  }, void 0, { synchronous: true });
8057
8049
  sdkAny.httpInstance.interceptors.response.use((res) => {
8058
- log$21.debug("飞书 API 响应", {
8050
+ log$22.debug("飞书 API 响应", {
8059
8051
  accountId,
8060
8052
  code: res?.code,
8061
8053
  msg: res?.msg,
@@ -8063,7 +8055,7 @@ var LarkClient = class LarkClient {
8063
8055
  });
8064
8056
  return res;
8065
8057
  }, (err) => {
8066
- log$21.error("飞书 API 请求失败", {
8058
+ log$22.error("飞书 API 请求失败", {
8067
8059
  accountId,
8068
8060
  error: err instanceof Error ? err.message : String(err),
8069
8061
  url: err?.config?.url
@@ -8150,7 +8142,7 @@ var LarkClient = class LarkClient {
8150
8142
  dispatcher.register(handlers);
8151
8143
  const { appId, appSecret } = this.requireCredentials();
8152
8144
  if (this._wsClient) {
8153
- log$21.warn(`closing previous WSClient before reconnect`, { accountId: this.accountId });
8145
+ log$22.warn(`closing previous WSClient before reconnect`, { accountId: this.accountId });
8154
8146
  try {
8155
8147
  this._wsClient.close({ force: true });
8156
8148
  } catch {}
@@ -8167,7 +8159,7 @@ var LarkClient = class LarkClient {
8167
8159
  wsClientAny.handleEventData = (data) => {
8168
8160
  const msgType = data.headers?.find?.((h) => h.key === "type")?.value;
8169
8161
  const eventType = data.headers?.find?.((h) => h.key === "event_type")?.value;
8170
- log$21.info("WS 收到原始事件", {
8162
+ log$22.info("WS 收到原始事件", {
8171
8163
  accountId: this.accountId,
8172
8164
  msgType,
8173
8165
  eventType,
@@ -8193,14 +8185,14 @@ var LarkClient = class LarkClient {
8193
8185
  /** Disconnect WebSocket but keep instance in cache. */
8194
8186
  disconnect() {
8195
8187
  if (this._wsClient) {
8196
- log$21.info(`disconnecting WebSocket`, { accountId: this.accountId });
8188
+ log$22.info(`disconnecting WebSocket`, { accountId: this.accountId });
8197
8189
  try {
8198
8190
  this._wsClient.close({ force: true });
8199
8191
  } catch {}
8200
8192
  }
8201
8193
  this._wsClient = null;
8202
8194
  if (this.messageDedup) {
8203
- log$21.info(`disposing message dedup`, {
8195
+ log$22.info(`disposing message dedup`, {
8204
8196
  accountId: this.accountId,
8205
8197
  size: this.messageDedup.size
8206
8198
  });
@@ -8506,7 +8498,7 @@ function sortTraceValue(value) {
8506
8498
  }
8507
8499
  //#endregion
8508
8500
  //#region src/card/cc-stream-bridge.ts
8509
- const log$20 = larkLogger("card/cc-stream-bridge");
8501
+ const log$21 = larkLogger("card/cc-stream-bridge");
8510
8502
  const CC_INTERNAL_PLACEHOLDER = "No response requested.";
8511
8503
  /**
8512
8504
  * CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
@@ -8531,7 +8523,7 @@ var CCStreamBridge = class {
8531
8523
  sessionKey: options?.sessionKey
8532
8524
  };
8533
8525
  if (this.options.sessionKey) startToolUseTraceRun(this.options.sessionKey);
8534
- log$20.info("CCStreamBridge 初始化", {
8526
+ log$21.info("CCStreamBridge 初始化", {
8535
8527
  autoCompleteOnTurnEnd: this.options.autoCompleteOnTurnEnd,
8536
8528
  sessionKey: this.options.sessionKey ?? "(none)"
8537
8529
  });
@@ -8539,7 +8531,7 @@ var CCStreamBridge = class {
8539
8531
  /** 文本增量 → 累积后调用 controller.onPartialReply */
8540
8532
  onTextDelta(text) {
8541
8533
  this.accumulatedText += text;
8542
- log$20.debug("textDelta 事件", {
8534
+ log$21.debug("textDelta 事件", {
8543
8535
  deltaLen: text.length,
8544
8536
  totalLen: this.accumulatedText.length
8545
8537
  });
@@ -8548,7 +8540,7 @@ var CCStreamBridge = class {
8548
8540
  /** 思考增量 → 累积后调用 controller.onReasoningStream */
8549
8541
  onThinkingDelta(text) {
8550
8542
  this.accumulatedThinkingText += text;
8551
- log$20.debug("thinkingDelta 事件", {
8543
+ log$21.debug("thinkingDelta 事件", {
8552
8544
  deltaLen: text.length,
8553
8545
  totalLen: this.accumulatedThinkingText.length
8554
8546
  });
@@ -8560,7 +8552,7 @@ var CCStreamBridge = class {
8560
8552
  const displayName = getToolDisplayName(toolName);
8561
8553
  const toolParams = typeof _toolInput === "object" && _toolInput != null ? _toolInput : void 0;
8562
8554
  const hasParams = toolParams && Object.keys(toolParams).length > 0;
8563
- log$20.info("toolUseStart 事件", {
8555
+ log$21.info("toolUseStart 事件", {
8564
8556
  toolName,
8565
8557
  displayName,
8566
8558
  activeToolsCount: this.activeTools.size,
@@ -8573,7 +8565,7 @@ var CCStreamBridge = class {
8573
8565
  toolName,
8574
8566
  toolParams
8575
8567
  })) {
8576
- log$20.info("toolUseStart: 更新已有步骤的 params", { toolName });
8568
+ log$21.info("toolUseStart: 更新已有步骤的 params", { toolName });
8577
8569
  this.controller.onToolStart({
8578
8570
  name: toolName,
8579
8571
  phase: "start"
@@ -8597,7 +8589,7 @@ var CCStreamBridge = class {
8597
8589
  const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
8598
8590
  const displayName = getToolDisplayName(toolName);
8599
8591
  this.activeTools.delete(toolUseId);
8600
- log$20.info("toolResult 事件", {
8592
+ log$21.info("toolResult 事件", {
8601
8593
  toolUseId,
8602
8594
  toolName,
8603
8595
  displayName,
@@ -8612,7 +8604,7 @@ var CCStreamBridge = class {
8612
8604
  }
8613
8605
  /** 工具执行进度 → 记录日志 */
8614
8606
  onToolProgress(toolName, elapsedSeconds) {
8615
- log$20.debug("toolProgress 事件", {
8607
+ log$21.debug("toolProgress 事件", {
8616
8608
  toolName,
8617
8609
  displayName: getToolDisplayName(toolName),
8618
8610
  elapsedSeconds
@@ -8620,7 +8612,7 @@ var CCStreamBridge = class {
8620
8612
  }
8621
8613
  /** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
8622
8614
  onTurnEnd(stopReason) {
8623
- log$20.info("turnEnd 事件", {
8615
+ log$21.info("turnEnd 事件", {
8624
8616
  stopReason,
8625
8617
  accumulatedTextLen: this.accumulatedText.length,
8626
8618
  accumulatedThinkingTextLen: this.accumulatedThinkingText.length
@@ -8628,7 +8620,7 @@ var CCStreamBridge = class {
8628
8620
  if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
8629
8621
  const trimmedText = this.accumulatedText.trim();
8630
8622
  if (trimmedText === CC_INTERNAL_PLACEHOLDER) {
8631
- log$20.info("检测到 CC 内部占位消息,静默丢弃", {
8623
+ log$21.info("检测到 CC 内部占位消息,静默丢弃", {
8632
8624
  text: trimmedText.slice(0, 50),
8633
8625
  sessionKey: this.options.sessionKey
8634
8626
  });
@@ -8636,7 +8628,7 @@ var CCStreamBridge = class {
8636
8628
  return;
8637
8629
  }
8638
8630
  if (trimmedText === "") {
8639
- log$20.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
8631
+ log$21.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
8640
8632
  return;
8641
8633
  }
8642
8634
  this.controller.markFullyComplete();
@@ -8645,7 +8637,7 @@ var CCStreamBridge = class {
8645
8637
  }
8646
8638
  /** 最终结果 → 兜底最终化 + 错误处理 */
8647
8639
  onResult(data) {
8648
- log$20.info("result 事件", {
8640
+ log$21.info("result 事件", {
8649
8641
  subtype: data.subtype,
8650
8642
  isError: data.isError,
8651
8643
  durationMs: data.durationMs,
@@ -8654,7 +8646,7 @@ var CCStreamBridge = class {
8654
8646
  resultLen: data.result?.length ?? 0
8655
8647
  });
8656
8648
  if (data.aborted) {
8657
- log$20.info("运行时报告用户主动中断,按正常完成处理", {
8649
+ log$21.info("运行时报告用户主动中断,按正常完成处理", {
8658
8650
  subtype: data.subtype,
8659
8651
  sessionId: this.options.sessionKey
8660
8652
  });
@@ -8665,7 +8657,7 @@ var CCStreamBridge = class {
8665
8657
  const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
8666
8658
  const sessionId = this.options.sessionKey;
8667
8659
  if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
8668
- log$20.info("用户主动中断,按正常完成处理", {
8660
+ log$21.info("用户主动中断,按正常完成处理", {
8669
8661
  subtype: data.subtype,
8670
8662
  sessionId
8671
8663
  });
@@ -8674,14 +8666,14 @@ var CCStreamBridge = class {
8674
8666
  this.controller.onIdle();
8675
8667
  } else {
8676
8668
  const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
8677
- log$20.error("CC 执行返回错误", {
8669
+ log$21.error("CC 执行返回错误", {
8678
8670
  subtype: data.subtype,
8679
8671
  errorMessage: errorMessage.slice(0, 200)
8680
8672
  });
8681
8673
  this.controller.onError(new Error(errorMessage), { kind: "cc-execution" });
8682
8674
  }
8683
8675
  } else if (!this.accumulatedText.trim()) if (data.result) {
8684
- log$20.info("result 兜底:使用 result.result 作为最终回复", {
8676
+ log$21.info("result 兜底:使用 result.result 作为最终回复", {
8685
8677
  resultLen: data.result.length,
8686
8678
  sessionKey: this.options.sessionKey
8687
8679
  });
@@ -8689,7 +8681,7 @@ var CCStreamBridge = class {
8689
8681
  this.controller.markFullyComplete();
8690
8682
  this.controller.onIdle();
8691
8683
  } else {
8692
- log$20.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
8684
+ log$21.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
8693
8685
  this.controller.abortCard();
8694
8686
  }
8695
8687
  else {
@@ -8703,7 +8695,7 @@ var CCStreamBridge = class {
8703
8695
  * 内部复用上面的直接调用方法。
8704
8696
  */
8705
8697
  bindParser(parser) {
8706
- log$20.info("绑定 CCStreamParser 事件到卡片控制器");
8698
+ log$21.info("绑定 CCStreamParser 事件到卡片控制器");
8707
8699
  parser.on("textDelta", (text) => this.onTextDelta(text));
8708
8700
  parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
8709
8701
  parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
@@ -8711,7 +8703,7 @@ var CCStreamBridge = class {
8711
8703
  parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
8712
8704
  parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
8713
8705
  parser.on("result", (data) => this.onResult(data));
8714
- log$20.info("CCStreamParser 事件绑定完成");
8706
+ log$21.info("CCStreamParser 事件绑定完成");
8715
8707
  }
8716
8708
  };
8717
8709
  //#endregion
@@ -10730,7 +10722,7 @@ function resolveLarkSdk(cfg, accountId) {
10730
10722
  if (cached) return cached.sdk;
10731
10723
  return LarkClient.fromCfg(cfg, accountId).sdk;
10732
10724
  }
10733
- const log$19 = larkLogger("card/cardkit");
10725
+ const log$20 = larkLogger("card/cardkit");
10734
10726
  /**
10735
10727
  * 记录 CardKit API 响应日志,检测错误码并抛出异常。
10736
10728
  *
@@ -10740,13 +10732,13 @@ const log$19 = larkLogger("card/cardkit");
10740
10732
  function logCardKitResponse(params) {
10741
10733
  const { resp, api, context } = params;
10742
10734
  const { code, msg } = resp;
10743
- log$19.info(`cardkit ${api} response`, {
10735
+ log$20.info(`cardkit ${api} response`, {
10744
10736
  code,
10745
10737
  msg,
10746
10738
  context
10747
10739
  });
10748
10740
  if (code && code !== 0) {
10749
- log$19.warn(`cardkit ${api} FAILED`, {
10741
+ log$20.warn(`cardkit ${api} FAILED`, {
10750
10742
  code,
10751
10743
  msg,
10752
10744
  context,
@@ -11157,7 +11149,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
11157
11149
  * Feishu messages, uploading media to the Feishu IM storage, and
11158
11150
  * sending image / file messages to chats.
11159
11151
  */
11160
- const log$18 = larkLogger("outbound/media");
11152
+ const log$19 = larkLogger("outbound/media");
11161
11153
  /**
11162
11154
  * Upload an image to Feishu IM storage.
11163
11155
  *
@@ -11225,7 +11217,7 @@ async function validateRemoteUrl(raw) {
11225
11217
  for (const addr of addresses) if (isPrivateIP(addr)) throw new Error(`[feishu-media] Domain "${hostname}" resolves to private/reserved IP "${addr}" (SSRF protection). URL: "${raw}"`);
11226
11218
  } catch (err) {
11227
11219
  if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
11228
- log$18.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
11220
+ log$19.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
11229
11221
  }
11230
11222
  }
11231
11223
  /**
@@ -11243,21 +11235,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
11243
11235
  if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
11244
11236
  else throw new Error(`[feishu-media] Local file access denied for "${filePath}": mediaLocalRoots is not configured. Configure mediaLocalRoots to explicitly allow local file access.`);
11245
11237
  const buf = fs.readFileSync(filePath);
11246
- log$18.debug(`local file read: "${filePath}", ${buf.length} bytes`);
11238
+ log$19.debug(`local file read: "${filePath}", ${buf.length} bytes`);
11247
11239
  return buf;
11248
11240
  }
11249
11241
  await validateRemoteUrl(raw);
11250
11242
  const FETCH_TIMEOUT_MS = 3e4;
11251
- log$18.info(`fetching remote media: ${raw}`);
11243
+ log$19.info(`fetching remote media: ${raw}`);
11252
11244
  const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
11253
11245
  if (!response.ok) throw new Error(`[feishu-media] Failed to fetch media from "${raw}": HTTP ${response.status} ${response.statusText}. Verify the URL is accessible and returns a valid media resource.`);
11254
11246
  const arrayBuffer = await response.arrayBuffer();
11255
- log$18.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
11247
+ log$19.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
11256
11248
  return Buffer.from(arrayBuffer);
11257
11249
  }
11258
11250
  //#endregion
11259
11251
  //#region src/card/image-resolver.ts
11260
- const log$17 = larkLogger("card/image-resolver");
11252
+ const log$18 = larkLogger("card/image-resolver");
11261
11253
  /** Matches complete markdown image syntax: `![alt](value)` */
11262
11254
  const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
11263
11255
  var ImageResolver = class {
@@ -11303,14 +11295,14 @@ var ImageResolver = class {
11303
11295
  async resolveImagesAwait(text, timeoutMs) {
11304
11296
  this.resolveImages(text);
11305
11297
  if (this.pending.size > 0) {
11306
- log$17.info("resolveImagesAwait: waiting for uploads", {
11298
+ log$18.info("resolveImagesAwait: waiting for uploads", {
11307
11299
  count: this.pending.size,
11308
11300
  timeoutMs
11309
11301
  });
11310
11302
  const allUploads = Promise.all(this.pending.values());
11311
11303
  const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
11312
11304
  await Promise.race([allUploads, timeout]);
11313
- if (this.pending.size > 0) log$17.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
11305
+ if (this.pending.size > 0) log$18.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
11314
11306
  }
11315
11307
  return this.resolveImages(text);
11316
11308
  }
@@ -11320,7 +11312,7 @@ var ImageResolver = class {
11320
11312
  }
11321
11313
  async doUpload(url) {
11322
11314
  try {
11323
- log$17.info("uploading image", { url });
11315
+ log$18.info("uploading image", { url });
11324
11316
  const buffer = await fetchRemoteImageBuffer(url);
11325
11317
  const { imageKey } = await uploadImageLark({
11326
11318
  cfg: this.cfg,
@@ -11328,7 +11320,7 @@ var ImageResolver = class {
11328
11320
  imageType: "message",
11329
11321
  accountId: this.accountId
11330
11322
  });
11331
- log$17.info("image uploaded", {
11323
+ log$18.info("image uploaded", {
11332
11324
  url,
11333
11325
  imageKey
11334
11326
  });
@@ -11337,7 +11329,7 @@ var ImageResolver = class {
11337
11329
  this.onImageResolved();
11338
11330
  return imageKey;
11339
11331
  } catch (err) {
11340
- log$17.warn("image upload failed", {
11332
+ log$18.warn("image upload failed", {
11341
11333
  url,
11342
11334
  error: String(err)
11343
11335
  });
@@ -11358,7 +11350,7 @@ var ImageResolver = class {
11358
11350
  * Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
11359
11351
  * logic previously scattered as closures in reply-dispatcher.ts.
11360
11352
  */
11361
- const log$16 = larkLogger("card/unavailable-guard");
11353
+ const log$17 = larkLogger("card/unavailable-guard");
11362
11354
  var UnavailableGuard = class {
11363
11355
  terminated = false;
11364
11356
  replyToMessageId;
@@ -11411,7 +11403,7 @@ var UnavailableGuard = class {
11411
11403
  this.terminated = true;
11412
11404
  this.onTerminate();
11413
11405
  const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
11414
- log$16.warn("reply pipeline terminated by unavailable message", {
11406
+ log$17.warn("reply pipeline terminated by unavailable message", {
11415
11407
  source,
11416
11408
  apiCode,
11417
11409
  messageId: affectedMessageId
@@ -11448,7 +11440,7 @@ function getActiveCard(sessionId) {
11448
11440
  * Delegates throttling to FlushController and message-unavailable
11449
11441
  * detection to UnavailableGuard.
11450
11442
  */
11451
- const log$15 = larkLogger("card/streaming");
11443
+ const log$16 = larkLogger("card/streaming");
11452
11444
  var StreamingCardController = class StreamingCardController {
11453
11445
  phase = "idle";
11454
11446
  cardKit = {
@@ -11536,7 +11528,7 @@ var StreamingCardController = class StreamingCardController {
11536
11528
  }
11537
11529
  }
11538
11530
  if (!entry) {
11539
- log$15.debug("footer metrics lookup: session entry missing", {
11531
+ log$16.debug("footer metrics lookup: session entry missing", {
11540
11532
  sessionKey: this.deps.sessionKey,
11541
11533
  candidateKeys,
11542
11534
  storePath,
@@ -11554,7 +11546,7 @@ var StreamingCardController = class StreamingCardController {
11554
11546
  contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
11555
11547
  model: typeof entry.model === "string" ? entry.model : void 0
11556
11548
  };
11557
- log$15.debug("footer metrics lookup: session entry found", {
11549
+ log$16.debug("footer metrics lookup: session entry found", {
11558
11550
  sessionKey: this.deps.sessionKey,
11559
11551
  matchedKey,
11560
11552
  storePath,
@@ -11579,7 +11571,7 @@ var StreamingCardController = class StreamingCardController {
11579
11571
  }
11580
11572
  }
11581
11573
  if (!entry) {
11582
- log$15.debug("footer metrics lookup: session entry missing", {
11574
+ log$16.debug("footer metrics lookup: session entry missing", {
11583
11575
  sessionKey: this.deps.sessionKey,
11584
11576
  candidateKeys,
11585
11577
  storePath,
@@ -11597,7 +11589,7 @@ var StreamingCardController = class StreamingCardController {
11597
11589
  contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
11598
11590
  model: typeof entry.model === "string" ? entry.model : void 0
11599
11591
  };
11600
- log$15.debug("footer metrics lookup: session entry found", {
11592
+ log$16.debug("footer metrics lookup: session entry found", {
11601
11593
  sessionKey: this.deps.sessionKey,
11602
11594
  matchedKey,
11603
11595
  storePath,
@@ -11605,7 +11597,7 @@ var StreamingCardController = class StreamingCardController {
11605
11597
  });
11606
11598
  return metrics;
11607
11599
  } catch (err) {
11608
- log$15.warn("footer metrics lookup failed", {
11600
+ log$16.warn("footer metrics lookup failed", {
11609
11601
  error: String(err),
11610
11602
  sessionKey: this.deps.sessionKey
11611
11603
  });
@@ -11712,7 +11704,7 @@ var StreamingCardController = class StreamingCardController {
11712
11704
  const from = this.phase;
11713
11705
  if (from === to) return false;
11714
11706
  if (!PHASE_TRANSITIONS[from].has(to)) {
11715
- log$15.warn("phase transition rejected", {
11707
+ log$16.warn("phase transition rejected", {
11716
11708
  from,
11717
11709
  to,
11718
11710
  source
@@ -11720,7 +11712,7 @@ var StreamingCardController = class StreamingCardController {
11720
11712
  return false;
11721
11713
  }
11722
11714
  this.phase = to;
11723
- log$15.info("phase transition", {
11715
+ log$16.info("phase transition", {
11724
11716
  from,
11725
11717
  to,
11726
11718
  source,
@@ -11840,7 +11832,7 @@ var StreamingCardController = class StreamingCardController {
11840
11832
  this.reasoning.dirty = true;
11841
11833
  }
11842
11834
  const text = split.answerText ?? stripReasoningTags(rawText);
11843
- log$15.debug("onPartialReply", { len: text.length });
11835
+ log$16.debug("onPartialReply", { len: text.length });
11844
11836
  if (!text) return;
11845
11837
  this.captureToolUseElapsed();
11846
11838
  if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
@@ -11852,7 +11844,7 @@ var StreamingCardController = class StreamingCardController {
11852
11844
  this.text.lastPartialText = text;
11853
11845
  this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
11854
11846
  if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
11855
- log$15.debug("onPartialReply: buffering NO_REPLY prefix");
11847
+ log$16.debug("onPartialReply: buffering NO_REPLY prefix");
11856
11848
  return;
11857
11849
  }
11858
11850
  await this.ensureCardCreated();
@@ -11862,7 +11854,7 @@ var StreamingCardController = class StreamingCardController {
11862
11854
  }
11863
11855
  async onError(err, info) {
11864
11856
  if (this.guard.terminate("onError", err)) return;
11865
- log$15.error(`${info.kind} reply failed`, { error: String(err) });
11857
+ log$16.error(`${info.kind} reply failed`, { error: String(err) });
11866
11858
  this.captureToolUseElapsed();
11867
11859
  this.finalizeCard("onError", "error");
11868
11860
  await this.flush.waitForFlush();
@@ -11918,7 +11910,7 @@ var StreamingCardController = class StreamingCardController {
11918
11910
  if (this.cardKit.cardMessageId) {
11919
11911
  const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
11920
11912
  const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
11921
- if (!this.text.completedText && !this.text.accumulatedText) log$15.warn("reply completed without visible text, using empty-reply fallback");
11913
+ if (!this.text.completedText && !this.text.accumulatedText) log$16.warn("reply completed without visible text, using empty-reply fallback");
11922
11914
  const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
11923
11915
  const idleToolUseDisplay = this.computeToolUseDisplay();
11924
11916
  const terminalContent = prepareTerminalCardContent({
@@ -11946,7 +11938,7 @@ var StreamingCardController = class StreamingCardController {
11946
11938
  const rewindSessionId = this.deps.sessionKey;
11947
11939
  const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
11948
11940
  const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
11949
- log$15.info("onIdle: rewind button conditions", {
11941
+ log$16.info("onIdle: rewind button conditions", {
11950
11942
  sessionId: rewindSessionId,
11951
11943
  messageId: rewindMessageId,
11952
11944
  hasFileChanges: rewindHasFileChanges,
@@ -11959,7 +11951,7 @@ var StreamingCardController = class StreamingCardController {
11959
11951
  if (idleEffectiveCardId) {
11960
11952
  const seqBeforeClose = this.cardKit.cardKitSequence;
11961
11953
  this.cardKit.cardKitSequence += 1;
11962
- log$15.info("onIdle: closing streaming mode", {
11954
+ log$16.info("onIdle: closing streaming mode", {
11963
11955
  attempt,
11964
11956
  seqBefore: seqBeforeClose,
11965
11957
  seqAfter: this.cardKit.cardKitSequence
@@ -11973,7 +11965,7 @@ var StreamingCardController = class StreamingCardController {
11973
11965
  });
11974
11966
  const seqBeforeUpdate = this.cardKit.cardKitSequence;
11975
11967
  this.cardKit.cardKitSequence += 1;
11976
- log$15.info("onIdle: updating final card", {
11968
+ log$16.info("onIdle: updating final card", {
11977
11969
  attempt,
11978
11970
  seqBefore: seqBeforeUpdate,
11979
11971
  seqAfter: this.cardKit.cardKitSequence
@@ -11991,33 +11983,33 @@ var StreamingCardController = class StreamingCardController {
11991
11983
  card: completeCard,
11992
11984
  accountId: this.deps.accountId
11993
11985
  });
11994
- log$15.info("reply completed, card finalized", {
11986
+ log$16.info("reply completed, card finalized", {
11995
11987
  elapsedMs: this.elapsed(),
11996
11988
  isCardKit: !!idleEffectiveCardId,
11997
11989
  attempt
11998
11990
  });
11999
11991
  break;
12000
11992
  } catch (retryErr) {
12001
- log$15.warn("final card update attempt failed", {
11993
+ log$16.warn("final card update attempt failed", {
12002
11994
  attempt,
12003
11995
  maxRetries: MAX_FINAL_UPDATE_RETRIES,
12004
11996
  error: String(retryErr)
12005
11997
  });
12006
11998
  if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
12007
- else log$15.error("final card update exhausted all retries, card may remain in streaming state", {
11999
+ else log$16.error("final card update exhausted all retries, card may remain in streaming state", {
12008
12000
  cardId: idleEffectiveCardId,
12009
12001
  messageId: this.cardKit.cardMessageId
12010
12002
  });
12011
12003
  }
12012
12004
  }
12013
12005
  } catch (err) {
12014
- log$15.error("final card update failed (outer)", { error: String(err) });
12006
+ log$16.error("final card update failed (outer)", { error: String(err) });
12015
12007
  } finally {
12016
12008
  clearToolUseTraceRun(this.deps.sessionKey);
12017
12009
  }
12018
12010
  }
12019
12011
  markFullyComplete() {
12020
- log$15.debug("markFullyComplete", {
12012
+ log$16.debug("markFullyComplete", {
12021
12013
  completedTextLen: this.text.completedText.length,
12022
12014
  accumulatedTextLen: this.text.accumulatedText.length
12023
12015
  });
@@ -12056,7 +12048,7 @@ var StreamingCardController = class StreamingCardController {
12056
12048
  footerMetrics
12057
12049
  });
12058
12050
  await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
12059
- log$15.info("abortCard completed", { effectiveCardId });
12051
+ log$16.info("abortCard completed", { effectiveCardId });
12060
12052
  } else if (this.cardKit.cardMessageId) {
12061
12053
  const abortCard = buildCardContent("complete", {
12062
12054
  text: terminalContent.text,
@@ -12077,10 +12069,10 @@ var StreamingCardController = class StreamingCardController {
12077
12069
  card: abortCard,
12078
12070
  accountId: this.deps.accountId
12079
12071
  });
12080
- log$15.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
12072
+ log$16.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
12081
12073
  }
12082
12074
  } catch (err) {
12083
- log$15.warn("abortCard failed", { error: String(err) });
12075
+ log$16.warn("abortCard failed", { error: String(err) });
12084
12076
  } finally {
12085
12077
  clearToolUseTraceRun(this.deps.sessionKey);
12086
12078
  }
@@ -12095,7 +12087,7 @@ var StreamingCardController = class StreamingCardController {
12095
12087
  if (effectiveCardId) {
12096
12088
  const seqBeforeClose = this.cardKit.cardKitSequence;
12097
12089
  this.cardKit.cardKitSequence += 1;
12098
- log$15.info("replaceWithClarificationCard: closing streaming mode", {
12090
+ log$16.info("replaceWithClarificationCard: closing streaming mode", {
12099
12091
  cardId: effectiveCardId,
12100
12092
  seqBefore: seqBeforeClose,
12101
12093
  seqAfter: this.cardKit.cardKitSequence
@@ -12116,7 +12108,7 @@ var StreamingCardController = class StreamingCardController {
12116
12108
  sequence: this.cardKit.cardKitSequence,
12117
12109
  accountId: this.deps.accountId
12118
12110
  });
12119
- log$15.info("replaceWithClarificationCard: existing card replaced", {
12111
+ log$16.info("replaceWithClarificationCard: existing card replaced", {
12120
12112
  cardId: effectiveCardId,
12121
12113
  seqBefore: seqBeforeUpdate,
12122
12114
  seqAfter: this.cardKit.cardKitSequence
@@ -12139,7 +12131,7 @@ var StreamingCardController = class StreamingCardController {
12139
12131
  this.cardKit.cardKitCardId = cardId;
12140
12132
  this.cardKit.originalCardKitCardId = cardId;
12141
12133
  this.cardKit.cardMessageId = result.messageId;
12142
- log$15.info("replaceWithClarificationCard: new clarification card sent", {
12134
+ log$16.info("replaceWithClarificationCard: new clarification card sent", {
12143
12135
  cardId,
12144
12136
  messageId: result.messageId
12145
12137
  });
@@ -12148,7 +12140,7 @@ var StreamingCardController = class StreamingCardController {
12148
12140
  clearToolUseTraceRun(this.deps.sessionKey);
12149
12141
  return true;
12150
12142
  } catch (err) {
12151
- log$15.error("replaceWithClarificationCard failed", { error: String(err) });
12143
+ log$16.error("replaceWithClarificationCard failed", { error: String(err) });
12152
12144
  return false;
12153
12145
  }
12154
12146
  }
@@ -12162,10 +12154,10 @@ var StreamingCardController = class StreamingCardController {
12162
12154
  async forceCloseStreaming() {
12163
12155
  const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
12164
12156
  if (!effectiveCardId && !this.cardKit.cardMessageId) {
12165
- log$15.warn("forceCloseStreaming: no card to close");
12157
+ log$16.warn("forceCloseStreaming: no card to close");
12166
12158
  return;
12167
12159
  }
12168
- log$15.info("forceCloseStreaming: 强制终态化卡片", {
12160
+ log$16.info("forceCloseStreaming: 强制终态化卡片", {
12169
12161
  cardId: effectiveCardId,
12170
12162
  messageId: this.cardKit.cardMessageId,
12171
12163
  phase: this.phase
@@ -12215,10 +12207,10 @@ var StreamingCardController = class StreamingCardController {
12215
12207
  card: completeCard,
12216
12208
  accountId: this.deps.accountId
12217
12209
  });
12218
- log$15.info("forceCloseStreaming: 卡片已强制终态化");
12210
+ log$16.info("forceCloseStreaming: 卡片已强制终态化");
12219
12211
  if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
12220
12212
  } catch (err) {
12221
- log$15.error("forceCloseStreaming failed", { error: String(err) });
12213
+ log$16.error("forceCloseStreaming failed", { error: String(err) });
12222
12214
  } finally {
12223
12215
  unregisterActiveCard(this.deps.sessionKey);
12224
12216
  clearToolUseTraceRun(this.deps.sessionKey);
@@ -12242,7 +12234,7 @@ var StreamingCardController = class StreamingCardController {
12242
12234
  this.disposeShutdownHook = null;
12243
12235
  return;
12244
12236
  }
12245
- log$15.info("reusing placeholder card", {
12237
+ log$16.info("reusing placeholder card", {
12246
12238
  cardId: this.deps.placeholderCardId,
12247
12239
  messageId: this.deps.placeholderMessageId
12248
12240
  });
@@ -12266,7 +12258,7 @@ var StreamingCardController = class StreamingCardController {
12266
12258
  accountId: this.deps.accountId
12267
12259
  });
12268
12260
  if (this.isStaleCreate(epoch)) {
12269
- log$15.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
12261
+ log$16.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
12270
12262
  epoch,
12271
12263
  phase: this.phase
12272
12264
  });
@@ -12277,7 +12269,7 @@ var StreamingCardController = class StreamingCardController {
12277
12269
  this.cardKit.originalCardKitCardId = cId;
12278
12270
  this.cardKit.cardKitSequence = 1;
12279
12271
  this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
12280
- log$15.info("created CardKit entity", {
12272
+ log$16.info("created CardKit entity", {
12281
12273
  cardId: cId,
12282
12274
  initialSequence: this.cardKit.cardKitSequence
12283
12275
  });
@@ -12290,7 +12282,7 @@ var StreamingCardController = class StreamingCardController {
12290
12282
  accountId: this.deps.accountId
12291
12283
  });
12292
12284
  if (this.isStaleCreate(epoch)) {
12293
- log$15.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
12285
+ log$16.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
12294
12286
  epoch,
12295
12287
  phase: this.phase
12296
12288
  });
@@ -12305,13 +12297,13 @@ var StreamingCardController = class StreamingCardController {
12305
12297
  this.disposeShutdownHook = null;
12306
12298
  return;
12307
12299
  }
12308
- log$15.info("sent CardKit card", { messageId: result.messageId });
12300
+ log$16.info("sent CardKit card", { messageId: result.messageId });
12309
12301
  } else throw new Error("card.create returned empty card_id");
12310
12302
  } catch (cardKitErr) {
12311
12303
  if (this.isStaleCreate(epoch)) return;
12312
12304
  if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
12313
12305
  const apiDetail = extractApiDetail(cardKitErr);
12314
- log$15.warn("CardKit flow failed, falling back to IM", { apiDetail });
12306
+ log$16.warn("CardKit flow failed, falling back to IM", { apiDetail });
12315
12307
  this.cardKit.cardKitCardId = null;
12316
12308
  this.cardKit.originalCardKitCardId = null;
12317
12309
  const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
@@ -12324,7 +12316,7 @@ var StreamingCardController = class StreamingCardController {
12324
12316
  accountId: this.deps.accountId
12325
12317
  });
12326
12318
  if (this.isStaleCreate(epoch)) {
12327
- log$15.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
12319
+ log$16.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
12328
12320
  epoch,
12329
12321
  phase: this.phase
12330
12322
  });
@@ -12333,12 +12325,12 @@ var StreamingCardController = class StreamingCardController {
12333
12325
  this.cardKit.cardMessageId = result.messageId;
12334
12326
  this.flush.setCardMessageReady(true);
12335
12327
  if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
12336
- log$15.info("sent fallback IM card", { messageId: result.messageId });
12328
+ log$16.info("sent fallback IM card", { messageId: result.messageId });
12337
12329
  }
12338
12330
  } catch (err) {
12339
12331
  if (this.isStaleCreate(epoch)) return;
12340
12332
  if (this.guard.terminate("ensureCardCreated.outer", err)) return;
12341
- log$15.warn("thinking card failed, falling back to static", { error: String(err) });
12333
+ log$16.warn("thinking card failed, falling back to static", { error: String(err) });
12342
12334
  this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
12343
12335
  }
12344
12336
  })();
@@ -12347,10 +12339,10 @@ var StreamingCardController = class StreamingCardController {
12347
12339
  async performFlush() {
12348
12340
  if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
12349
12341
  if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
12350
- log$15.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
12342
+ log$16.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
12351
12343
  return;
12352
12344
  }
12353
- log$15.debug("flushCardUpdate: enter", {
12345
+ log$16.debug("flushCardUpdate: enter", {
12354
12346
  seq: this.cardKit.cardKitSequence,
12355
12347
  isCardKit: !!this.cardKit.cardKitCardId
12356
12348
  });
@@ -12372,7 +12364,7 @@ var StreamingCardController = class StreamingCardController {
12372
12364
  reasoningText: this.reasoning.accumulatedReasoningText || void 0
12373
12365
  });
12374
12366
  this.cardKit.cardKitSequence += 1;
12375
- log$15.debug("flushCardUpdate: full card update (dirty)", {
12367
+ log$16.debug("flushCardUpdate: full card update (dirty)", {
12376
12368
  seq: this.cardKit.cardKitSequence,
12377
12369
  stepCount: display?.stepCount,
12378
12370
  hasReasoning: !!this.reasoning.accumulatedReasoningText
@@ -12388,7 +12380,7 @@ var StreamingCardController = class StreamingCardController {
12388
12380
  } else if (resolvedText !== this.text.lastFlushedText) {
12389
12381
  const prevSeq = this.cardKit.cardKitSequence;
12390
12382
  this.cardKit.cardKitSequence += 1;
12391
- log$15.debug("flushCardUpdate: answer seq bump", {
12383
+ log$16.debug("flushCardUpdate: answer seq bump", {
12392
12384
  seqBefore: prevSeq,
12393
12385
  seqAfter: this.cardKit.cardKitSequence
12394
12386
  });
@@ -12403,7 +12395,7 @@ var StreamingCardController = class StreamingCardController {
12403
12395
  this.text.lastFlushedText = resolvedText;
12404
12396
  }
12405
12397
  } else {
12406
- log$15.debug("flushCardUpdate: IM patch fallback");
12398
+ log$16.debug("flushCardUpdate: IM patch fallback");
12407
12399
  const flushDisplay = this.computeToolUseDisplay();
12408
12400
  const card = buildCardContent("streaming", {
12409
12401
  text: this.reasoning.isReasoningPhase ? "" : resolvedText,
@@ -12423,31 +12415,31 @@ var StreamingCardController = class StreamingCardController {
12423
12415
  if (this.guard.terminate("flushCardUpdate", err)) return;
12424
12416
  const apiCode = extractLarkApiCode(err);
12425
12417
  if (isCardRateLimitError(err)) {
12426
- log$15.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
12418
+ log$16.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
12427
12419
  return;
12428
12420
  }
12429
12421
  if (isCardTableLimitError(err)) {
12430
- log$15.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
12422
+ log$16.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
12431
12423
  this.cardKit.cardKitCardId = null;
12432
12424
  return;
12433
12425
  }
12434
12426
  if (apiCode === 300317 && this.cardKit.cardKitCardId) {
12435
12427
  const oldSeq = this.cardKit.cardKitSequence;
12436
12428
  this.cardKit.cardKitSequence += 100;
12437
- log$15.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
12429
+ log$16.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
12438
12430
  oldSeq,
12439
12431
  newSeq: this.cardKit.cardKitSequence
12440
12432
  });
12441
12433
  return;
12442
12434
  }
12443
12435
  const apiDetail = extractApiDetail(err);
12444
- log$15.error("card stream update failed", {
12436
+ log$16.error("card stream update failed", {
12445
12437
  apiCode,
12446
12438
  seq: this.cardKit.cardKitSequence,
12447
12439
  apiDetail
12448
12440
  });
12449
12441
  if (this.cardKit.cardKitCardId) {
12450
- log$15.warn("disabling CardKit streaming, falling back to im.message.patch");
12442
+ log$16.warn("disabling CardKit streaming, falling back to im.message.patch");
12451
12443
  this.cardKit.cardKitCardId = null;
12452
12444
  }
12453
12445
  }
@@ -12472,7 +12464,7 @@ var StreamingCardController = class StreamingCardController {
12472
12464
  if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
12473
12465
  try {
12474
12466
  const display = this.computeToolUseDisplay();
12475
- log$15.info("updateToolUseStatus", {
12467
+ log$16.info("updateToolUseStatus", {
12476
12468
  hasDisplay: !!display,
12477
12469
  stepCount: display?.stepCount,
12478
12470
  steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
@@ -12494,7 +12486,7 @@ var StreamingCardController = class StreamingCardController {
12494
12486
  accountId: this.deps.accountId
12495
12487
  });
12496
12488
  } catch (err) {
12497
- log$15.debug("updateToolUseStatus failed", { error: String(err) });
12489
+ log$16.debug("updateToolUseStatus failed", { error: String(err) });
12498
12490
  }
12499
12491
  }
12500
12492
  finalizeCard(source, reason) {
@@ -12505,7 +12497,7 @@ var StreamingCardController = class StreamingCardController {
12505
12497
  const from = this.phase;
12506
12498
  this.phase = "completed";
12507
12499
  this._terminalReason = "normal";
12508
- log$15.info("phase transition", {
12500
+ log$16.info("phase transition", {
12509
12501
  from,
12510
12502
  to: this.phase,
12511
12503
  source: "replaceWithClarificationCard",
@@ -12519,7 +12511,7 @@ var StreamingCardController = class StreamingCardController {
12519
12511
  async closeStreamingAndUpdate(cardId, card, label) {
12520
12512
  const seqBeforeClose = this.cardKit.cardKitSequence;
12521
12513
  this.cardKit.cardKitSequence += 1;
12522
- log$15.info(`${label}: closing streaming mode`, {
12514
+ log$16.info(`${label}: closing streaming mode`, {
12523
12515
  seqBefore: seqBeforeClose,
12524
12516
  seqAfter: this.cardKit.cardKitSequence
12525
12517
  });
@@ -12532,7 +12524,7 @@ var StreamingCardController = class StreamingCardController {
12532
12524
  });
12533
12525
  const seqBeforeUpdate = this.cardKit.cardKitSequence;
12534
12526
  this.cardKit.cardKitSequence += 1;
12535
- log$15.info(`${label}: updating card`, {
12527
+ log$16.info(`${label}: updating card`, {
12536
12528
  seqBefore: seqBeforeUpdate,
12537
12529
  seqAfter: this.cardKit.cardKitSequence
12538
12530
  });
@@ -12566,7 +12558,7 @@ function extractApiDetail(err) {
12566
12558
  }
12567
12559
  //#endregion
12568
12560
  //#region src/messaging/format-for-cc.ts
12569
- const log$14 = larkLogger("messaging/format-for-cc");
12561
+ const log$15 = larkLogger("messaging/format-for-cc");
12570
12562
  function safeParseJSON(raw) {
12571
12563
  try {
12572
12564
  return JSON.parse(raw);
@@ -12622,7 +12614,7 @@ function extractPostText(rawContent) {
12622
12614
  */
12623
12615
  function formatContentByType(ctx) {
12624
12616
  const { contentType, content, resources } = ctx;
12625
- log$14.debug("formatContentByType 开始格式化", {
12617
+ log$15.debug("formatContentByType 开始格式化", {
12626
12618
  contentType,
12627
12619
  contentLength: content?.length ?? 0,
12628
12620
  resourceCount: resources?.length ?? 0
@@ -12664,7 +12656,7 @@ function formatContentByType(ctx) {
12664
12656
  case "todo":
12665
12657
  case "vote": return content;
12666
12658
  default:
12667
- log$14.warn("遇到不支持的消息类型", { contentType });
12659
+ log$15.warn("遇到不支持的消息类型", { contentType });
12668
12660
  return `[不支持的消息类型: ${contentType}]`;
12669
12661
  }
12670
12662
  }
@@ -12707,7 +12699,7 @@ function formatMessageTime(createTime) {
12707
12699
  * @returns 格式化后的纯文本字符串
12708
12700
  */
12709
12701
  function formatForCC(ctx, isGroup) {
12710
- log$14.info("formatForCC 开始处理", {
12702
+ log$15.info("formatForCC 开始处理", {
12711
12703
  messageId: ctx.messageId,
12712
12704
  contentType: ctx.contentType,
12713
12705
  chatType: ctx.chatType,
@@ -12723,7 +12715,7 @@ function formatForCC(ctx, isGroup) {
12723
12715
  if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
12724
12716
  if (ctx.threadId) parts.push("[话题回复]");
12725
12717
  const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
12726
- log$14.info("formatForCC 完成", {
12718
+ log$15.info("formatForCC 完成", {
12727
12719
  messageId: ctx.messageId,
12728
12720
  resultLength: result.length,
12729
12721
  hasTime: !!timeStr,
@@ -12731,6 +12723,41 @@ function formatForCC(ctx, isGroup) {
12731
12723
  });
12732
12724
  return result;
12733
12725
  }
12726
+ function formatPromptMetadata(ctx) {
12727
+ const senderOpenId = ctx.rawSender.sender_id.open_id || ctx.senderId;
12728
+ const senderUserId = ctx.rawSender.sender_id.user_id;
12729
+ const fields = [
12730
+ `message_id=${ctx.messageId}`,
12731
+ `chat_id=${ctx.chatId}`,
12732
+ `chat_type=${ctx.chatType}`,
12733
+ `sender_open_id=${senderOpenId}`
12734
+ ];
12735
+ if (senderUserId) fields.push(`sender_user_id=${senderUserId}`);
12736
+ if (ctx.senderName) fields.push(`sender_name=${ctx.senderName}`);
12737
+ if (ctx.parentId) fields.push(`reply_to_message_id=${ctx.parentId}`);
12738
+ if (ctx.rootId) fields.push(`root_message_id=${ctx.rootId}`);
12739
+ if (ctx.threadId) fields.push(`thread_id=${ctx.threadId}`);
12740
+ const mentionedUsers = ctx.mentions.filter((mention) => !mention.isBot && mention.openId).map((mention) => `${mention.name || mention.openId}(open_id=${mention.openId})`);
12741
+ if (mentionedUsers.length > 0) fields.push(`mentioned_users=${mentionedUsers.join(", ")}`);
12742
+ return `[消息元数据] ${fields.join("; ")}`;
12743
+ }
12744
+ /**
12745
+ * Build the full text prompt sent to the agent runtime.
12746
+ *
12747
+ * Quoted/replied-to content is intentionally injected outside the parser:
12748
+ * resolving it requires Feishu API calls and can fail independently from
12749
+ * parsing the current event.
12750
+ */
12751
+ function formatPromptForCC(ctx, isGroup, quotedContent) {
12752
+ let currentMessage = formatForCC(ctx, isGroup);
12753
+ if (isGroup && ctx.threadId) currentMessage = `[群聊话题] ${currentMessage}`;
12754
+ else if (isGroup) currentMessage = `[群聊] ${currentMessage}`;
12755
+ else currentMessage = `[私聊] ${currentMessage}`;
12756
+ const quote = quotedContent?.trim();
12757
+ const metadata = formatPromptMetadata(ctx);
12758
+ if (!quote) return `${metadata}\n${currentMessage}`;
12759
+ return `${metadata}\n\n[引用/回复消息上下文]\n${quote}\n\n[当前消息]\n${currentMessage}`;
12760
+ }
12734
12761
  //#endregion
12735
12762
  //#region src/card/interactive-cards.ts
12736
12763
  /**
@@ -12746,7 +12773,7 @@ function formatForCC(ctx, isGroup) {
12746
12773
  * - CC 输出文本后处理(检测特殊标记)
12747
12774
  * - 操作 ID 生命周期管理
12748
12775
  */
12749
- const log$13 = larkLogger("card/interactive-cards");
12776
+ const log$14 = larkLogger("card/interactive-cards");
12750
12777
  const pendingOperations = /* @__PURE__ */ new Map();
12751
12778
  const OPERATION_TTL_MS = 1800 * 1e3;
12752
12779
  setInterval(() => {
@@ -12844,7 +12871,7 @@ function buildAuthCard(ctx) {
12844
12871
  scopes: ctx.scopes
12845
12872
  }
12846
12873
  });
12847
- log$13.info("已创建授权卡片操作", {
12874
+ log$14.info("已创建授权卡片操作", {
12848
12875
  operationId,
12849
12876
  scopes: ctx.scopes,
12850
12877
  sessionId: ctx.sessionId
@@ -12882,7 +12909,7 @@ function buildAskCard(ctx, phaseIndex = 0) {
12882
12909
  freeTextAnswers: {}
12883
12910
  }
12884
12911
  });
12885
- log$13.info("已创建提问卡片操作", {
12912
+ log$14.info("已创建提问卡片操作", {
12886
12913
  operationId,
12887
12914
  phaseId: phase?.id,
12888
12915
  phaseIndex,
@@ -13047,7 +13074,7 @@ function toggleAskSelection(operationId, phaseId, value) {
13047
13074
  else next = current[0] === value ? [] : [value];
13048
13075
  if (next.length > 0) op.askContext.answers[phaseId] = next;
13049
13076
  else delete op.askContext.answers[phaseId];
13050
- log$13.info("更新提问卡片勾选状态", {
13077
+ log$14.info("更新提问卡片勾选状态", {
13051
13078
  operationId,
13052
13079
  phaseId,
13053
13080
  selectedValues: next
@@ -13063,7 +13090,7 @@ function setAskFreeTextAnswer(operationId, phaseId, value) {
13063
13090
  const text = normalizeFreeText(value);
13064
13091
  if (text) op.askContext.freeTextAnswers[phaseId] = text;
13065
13092
  else delete op.askContext.freeTextAnswers[phaseId];
13066
- log$13.info("更新提问卡片自由文本", {
13093
+ log$14.info("更新提问卡片自由文本", {
13067
13094
  operationId,
13068
13095
  phaseId,
13069
13096
  hasText: text.length > 0
@@ -13307,7 +13334,7 @@ async function trySendClarificationFallback(params) {
13307
13334
  }
13308
13335
  //#endregion
13309
13336
  //#region src/messaging/inbound/dispatch-cc.ts
13310
- const log$12 = larkLogger("inbound/dispatch-cc");
13337
+ const log$13 = larkLogger("inbound/dispatch-cc");
13311
13338
  function buildFeishuRuntimeConfig(params) {
13312
13339
  const { ctx, account, route } = params;
13313
13340
  const senderUserId = ctx.rawSender.sender_id.user_id;
@@ -13364,7 +13391,7 @@ async function dispatchToCC(params) {
13364
13391
  const ccModel = params.model;
13365
13392
  const maxTurns = params.maxTurns;
13366
13393
  const maxBudgetUsd = params.maxBudgetUsd;
13367
- log$12.info("dispatchToCC 开始", {
13394
+ log$13.info("dispatchToCC 开始", {
13368
13395
  msgId,
13369
13396
  chatId,
13370
13397
  chatType,
@@ -13377,12 +13404,12 @@ async function dispatchToCC(params) {
13377
13404
  threadId,
13378
13405
  userId: ctx.senderId
13379
13406
  });
13380
- log$12.info("会话路由解析完成", {
13407
+ log$13.info("会话路由解析完成", {
13381
13408
  sessionId: route.sessionId,
13382
13409
  cwd: route.cwd,
13383
13410
  type: route.type
13384
13411
  });
13385
- if (params.userContext) log$12.info("用户上下文已注入", {
13412
+ if (params.userContext) log$13.info("用户上下文已注入", {
13386
13413
  userId: params.userContext.userId,
13387
13414
  isTenantIdentity: params.userContext.isTenantIdentity,
13388
13415
  credentialDir: params.userContext.credentialDir
@@ -13391,10 +13418,7 @@ async function dispatchToCC(params) {
13391
13418
  const isGroup = chatType === "group";
13392
13419
  const replyInThread = !!threadId;
13393
13420
  const replyToMessageId = threadId ? void 0 : ctx.messageId;
13394
- let textPrompt = formatForCC(ctx, isGroup);
13395
- if (isGroup && replyInThread) textPrompt = `[群聊话题] ${textPrompt}`;
13396
- else if (isGroup) textPrompt = `[群聊] ${textPrompt}`;
13397
- else textPrompt = `[私聊] ${textPrompt}`;
13421
+ const textPrompt = formatPromptForCC(ctx, isGroup, params.quotedContent);
13398
13422
  const imageResources = ctx.resources.filter((r) => r.type === "image");
13399
13423
  let prompt = textPrompt;
13400
13424
  if (imageResources.length > 0) {
@@ -13421,7 +13445,7 @@ async function dispatchToCC(params) {
13421
13445
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
13422
13446
  buffer = Buffer.concat(chunks);
13423
13447
  } else {
13424
- log$12.warn("图片资源下载返回未知格式,跳过", {
13448
+ log$13.warn("图片资源下载返回未知格式,跳过", {
13425
13449
  fileKey: imgRes.fileKey,
13426
13450
  responseType: typeof response
13427
13451
  });
@@ -13429,7 +13453,7 @@ async function dispatchToCC(params) {
13429
13453
  }
13430
13454
  if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
13431
13455
  } else {
13432
- log$12.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
13456
+ log$13.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
13433
13457
  continue;
13434
13458
  }
13435
13459
  const imgFileName = `${imgRes.fileKey}.png`;
@@ -13439,13 +13463,13 @@ async function dispatchToCC(params) {
13439
13463
  const { mkdir, writeFile } = await import("node:fs/promises");
13440
13464
  await mkdir(filesDir, { recursive: true });
13441
13465
  await writeFile(imgFilePath, buffer);
13442
- log$12.info("图片已保存到工作目录", {
13466
+ log$13.info("图片已保存到工作目录", {
13443
13467
  fileKey: imgRes.fileKey,
13444
13468
  path: imgFilePath,
13445
13469
  sizeBytes: buffer.length
13446
13470
  });
13447
13471
  } catch (saveErr) {
13448
- log$12.warn("图片保存到工作目录失败", {
13472
+ log$13.warn("图片保存到工作目录失败", {
13449
13473
  fileKey: imgRes.fileKey,
13450
13474
  path: imgFilePath,
13451
13475
  error: saveErr instanceof Error ? saveErr.message : String(saveErr)
@@ -13460,13 +13484,13 @@ async function dispatchToCC(params) {
13460
13484
  data: base64Data
13461
13485
  }
13462
13486
  });
13463
- log$12.info("图片资源下载成功", {
13487
+ log$13.info("图片资源下载成功", {
13464
13488
  fileKey: imgRes.fileKey,
13465
13489
  mediaType,
13466
13490
  sizeBytes: buffer.length
13467
13491
  });
13468
13492
  } catch (err) {
13469
- log$12.warn("图片资源下载失败,跳过", {
13493
+ log$13.warn("图片资源下载失败,跳过", {
13470
13494
  fileKey: imgRes.fileKey,
13471
13495
  error: err instanceof Error ? err.message : String(err)
13472
13496
  });
@@ -13476,7 +13500,7 @@ async function dispatchToCC(params) {
13476
13500
  type: "text",
13477
13501
  text: textPrompt
13478
13502
  }, ...imageBlocks];
13479
- log$12.info("已构建多模态 prompt", {
13503
+ log$13.info("已构建多模态 prompt", {
13480
13504
  textLength: textPrompt.length,
13481
13505
  imageCount: imageBlocks.length
13482
13506
  });
@@ -13484,12 +13508,12 @@ async function dispatchToCC(params) {
13484
13508
  const textWithoutImageRefs = textPrompt.replace(/!\[image\]\([^)]*\)/g, "").replace(/\[图片\]\s*/g, "").trim();
13485
13509
  if (textWithoutImageRefs.length > 0) {
13486
13510
  prompt = textPrompt.replace(/!\[image\]\([^)]*\)/g, "[图片加载失败]");
13487
- log$12.info("图片下载失败但保留文字内容继续调度", {
13511
+ log$13.info("图片下载失败但保留文字内容继续调度", {
13488
13512
  messageId: ctx.messageId,
13489
13513
  textLength: textWithoutImageRefs.length
13490
13514
  });
13491
13515
  } else {
13492
- log$12.warn("纯图片消息且图片全部下载失败,中止调度", {
13516
+ log$13.warn("纯图片消息且图片全部下载失败,中止调度", {
13493
13517
  messageId: ctx.messageId,
13494
13518
  expectedImages: imageResources.length
13495
13519
  });
@@ -13511,7 +13535,7 @@ async function dispatchToCC(params) {
13511
13535
  await fsMkdir(filesDir, { recursive: true });
13512
13536
  let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
13513
13537
  for (const res of attachmentResources) try {
13514
- log$12.info("开始下载非图片附件", {
13538
+ log$13.info("开始下载非图片附件", {
13515
13539
  type: res.type,
13516
13540
  fileKey: res.fileKey,
13517
13541
  fileName: res.fileName,
@@ -13525,7 +13549,7 @@ async function dispatchToCC(params) {
13525
13549
  },
13526
13550
  params: { type: "file" }
13527
13551
  });
13528
- log$12.info("非图片附件 API 响应已收到", {
13552
+ log$13.info("非图片附件 API 响应已收到", {
13529
13553
  type: res.type,
13530
13554
  fileKey: res.fileKey,
13531
13555
  responseType: typeof response,
@@ -13544,7 +13568,7 @@ async function dispatchToCC(params) {
13544
13568
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
13545
13569
  buffer = Buffer.concat(chunks);
13546
13570
  } else {
13547
- log$12.warn("非图片附件下载返回未知格式,跳过", {
13571
+ log$13.warn("非图片附件下载返回未知格式,跳过", {
13548
13572
  type: res.type,
13549
13573
  fileKey: res.fileKey,
13550
13574
  responseType: typeof response
@@ -13552,7 +13576,7 @@ async function dispatchToCC(params) {
13552
13576
  continue;
13553
13577
  }
13554
13578
  } else {
13555
- log$12.warn("非图片附件下载返回空,跳过", {
13579
+ log$13.warn("非图片附件下载返回空,跳过", {
13556
13580
  type: res.type,
13557
13581
  fileKey: res.fileKey
13558
13582
  });
@@ -13575,7 +13599,7 @@ async function dispatchToCC(params) {
13575
13599
  }
13576
13600
  const filePath = path.join(filesDir, savedFileName);
13577
13601
  await fsWriteFile(filePath, buffer);
13578
- log$12.info("非图片附件已保存到工作目录", {
13602
+ log$13.info("非图片附件已保存到工作目录", {
13579
13603
  type: res.type,
13580
13604
  fileKey: res.fileKey,
13581
13605
  savedFileName,
@@ -13609,7 +13633,7 @@ async function dispatchToCC(params) {
13609
13633
  }
13610
13634
  }
13611
13635
  } catch (err) {
13612
- log$12.warn("非图片附件下载失败,替换为降级描述", {
13636
+ log$13.warn("非图片附件下载失败,替换为降级描述", {
13613
13637
  type: res.type,
13614
13638
  fileKey: res.fileKey,
13615
13639
  error: err instanceof Error ? err.message : String(err)
@@ -13628,12 +13652,12 @@ async function dispatchToCC(params) {
13628
13652
  const textBlock = prompt.find((b) => b.type === "text");
13629
13653
  if (textBlock) textBlock.text = updatedTextPrompt;
13630
13654
  }
13631
- log$12.info("非图片附件处理完成", {
13655
+ log$13.info("非图片附件处理完成", {
13632
13656
  totalAttachments: attachmentResources.length,
13633
13657
  promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
13634
13658
  });
13635
13659
  }
13636
- log$12.info("消息格式化完成", {
13660
+ log$13.info("消息格式化完成", {
13637
13661
  promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
13638
13662
  isMultimodal: Array.isArray(prompt),
13639
13663
  isGroup
@@ -13665,19 +13689,19 @@ async function dispatchToCC(params) {
13665
13689
  };
13666
13690
  const cardController = new StreamingCardController(cardDeps);
13667
13691
  registerActiveCard(route.sessionId, cardController);
13668
- log$12.info("StreamingCardController 已创建", {
13692
+ log$13.info("StreamingCardController 已创建", {
13669
13693
  sessionId: route.sessionId,
13670
13694
  chatId,
13671
13695
  replyToMessageId: cardDeps.replyToMessageId
13672
13696
  });
13673
13697
  cardController.ensureCardCreated().catch((err) => {
13674
- log$12.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
13698
+ log$13.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
13675
13699
  });
13676
13700
  let promptForRuntime = prompt;
13677
13701
  try {
13678
13702
  promptForRuntime = await new MemoryContextBuilder().injectPrompt(route.cwd, prompt, promptToText(prompt));
13679
13703
  } catch (err) {
13680
- log$12.warn("memory context 注入失败,继续使用原始 prompt", {
13704
+ log$13.warn("memory context 注入失败,继续使用原始 prompt", {
13681
13705
  sessionId: route.sessionId,
13682
13706
  error: err instanceof Error ? err.message : String(err)
13683
13707
  });
@@ -13698,7 +13722,7 @@ async function dispatchToCC(params) {
13698
13722
  });
13699
13723
  } catch (err) {
13700
13724
  const errorMessage = err instanceof Error ? err.message : String(err);
13701
- log$12.error("飞书事件 run context 构造失败,终止调度", {
13725
+ log$13.error("飞书事件 run context 构造失败,终止调度", {
13702
13726
  sessionId: route.sessionId,
13703
13727
  messageId: msgId,
13704
13728
  error: errorMessage
@@ -13716,7 +13740,7 @@ async function dispatchToCC(params) {
13716
13740
  channel: "feishu-bot",
13717
13741
  title: userContent.slice(0, 30) + (userContent.length > 30 ? "..." : "")
13718
13742
  }).catch((err) => {
13719
- log$12.warn("创建飞书端会话记录失败", { error: String(err) });
13743
+ log$13.warn("创建飞书端会话记录失败", { error: String(err) });
13720
13744
  });
13721
13745
  params.messageStore.appendMessage({
13722
13746
  sessionId: storeSessionId,
@@ -13730,14 +13754,14 @@ async function dispatchToCC(params) {
13730
13754
  scenarioId: runtimeConfig.runContext?.scenarioId
13731
13755
  }
13732
13756
  }).catch((err) => {
13733
- log$12.warn("记录飞书用户消息到 store 失败", { error: String(err) });
13757
+ log$13.warn("记录飞书用户消息到 store 失败", { error: String(err) });
13734
13758
  });
13735
13759
  }
13736
13760
  const bridge = new CCStreamBridge(cardController, {
13737
13761
  autoCompleteOnTurnEnd: true,
13738
13762
  sessionKey: route.sessionId
13739
13763
  });
13740
- log$12.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
13764
+ log$13.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
13741
13765
  let feishuAccumText = "";
13742
13766
  let usedAskUserTool = false;
13743
13767
  let pendingEndTurn = null;
@@ -13759,12 +13783,12 @@ async function dispatchToCC(params) {
13759
13783
  ...metadata
13760
13784
  }
13761
13785
  }).catch((err) => {
13762
- log$12.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
13786
+ log$13.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
13763
13787
  });
13764
13788
  };
13765
13789
  const appendRuntimeEventMessage = (event) => {
13766
13790
  const eventSummary = summarizeForAudit(event);
13767
- log$12.info("CC onRuntimeEvent", {
13791
+ log$13.info("CC onRuntimeEvent", {
13768
13792
  sessionId: route.sessionId,
13769
13793
  type: event.type,
13770
13794
  traceId: runtimeConfig.runContext?.traceId,
@@ -13802,11 +13826,11 @@ async function dispatchToCC(params) {
13802
13826
  finalStatus: event.finalStatus
13803
13827
  }
13804
13828
  }).catch((err) => {
13805
- log$12.warn("记录飞书 runtime event 到 store 失败", { error: String(err) });
13829
+ log$13.warn("记录飞书 runtime event 到 store 失败", { error: String(err) });
13806
13830
  });
13807
13831
  };
13808
13832
  const handleResult = async (result) => {
13809
- log$12.info("CC onResult", {
13833
+ log$13.info("CC onResult", {
13810
13834
  sessionId: route.sessionId,
13811
13835
  subtype: result.subtype,
13812
13836
  isError: result.isError,
@@ -13834,13 +13858,13 @@ async function dispatchToCC(params) {
13834
13858
  })
13835
13859
  });
13836
13860
  } catch (err) {
13837
- log$12.warn("clarification gate: 兜底处理失败,回退到普通回复", {
13861
+ log$13.warn("clarification gate: 兜底处理失败,回退到普通回复", {
13838
13862
  sessionId: route.sessionId,
13839
13863
  error: err instanceof Error ? err.message : String(err)
13840
13864
  });
13841
13865
  }
13842
13866
  if (clarificationFallback.handled) {
13843
- log$12.info("clarification gate: 普通文本提问已转为提问卡片", {
13867
+ log$13.info("clarification gate: 普通文本提问已转为提问卡片", {
13844
13868
  sessionId: route.sessionId,
13845
13869
  operationId: clarificationFallback.operationId,
13846
13870
  reason: clarificationFallback.reason,
@@ -13848,7 +13872,7 @@ async function dispatchToCC(params) {
13848
13872
  });
13849
13873
  return;
13850
13874
  }
13851
- if (clarificationFallback.matched) log$12.warn("clarification gate: 提问卡片替换失败,回退到普通回复", {
13875
+ if (clarificationFallback.matched) log$13.warn("clarification gate: 提问卡片替换失败,回退到普通回复", {
13852
13876
  sessionId: route.sessionId,
13853
13877
  operationId: clarificationFallback.operationId
13854
13878
  });
@@ -13861,7 +13885,7 @@ async function dispatchToCC(params) {
13861
13885
  };
13862
13886
  const handleBlockedEvent = (event) => {
13863
13887
  const sanitized = sanitizeBlockedEvent(event);
13864
- log$12.warn("CC onBlockedEvent", {
13888
+ log$13.warn("CC onBlockedEvent", {
13865
13889
  ...sanitized,
13866
13890
  traceId: runtimeConfig.runContext?.traceId,
13867
13891
  scenarioId: runtimeConfig.runContext?.scenarioId
@@ -13869,7 +13893,7 @@ async function dispatchToCC(params) {
13869
13893
  };
13870
13894
  const callbacks = {
13871
13895
  onTextDelta: (text) => {
13872
- log$12.debug("CC onTextDelta", {
13896
+ log$13.debug("CC onTextDelta", {
13873
13897
  sessionId: route.sessionId,
13874
13898
  deltaLen: text.length
13875
13899
  });
@@ -13877,14 +13901,14 @@ async function dispatchToCC(params) {
13877
13901
  feishuAccumText += text;
13878
13902
  },
13879
13903
  onThinkingDelta: (text) => {
13880
- log$12.debug("CC onThinkingDelta", {
13904
+ log$13.debug("CC onThinkingDelta", {
13881
13905
  sessionId: route.sessionId,
13882
13906
  deltaLen: text.length
13883
13907
  });
13884
13908
  bridge.onThinkingDelta(text);
13885
13909
  },
13886
13910
  onToolUseStart: (toolName, toolInput) => {
13887
- log$12.info("CC onToolUseStart", {
13911
+ log$13.info("CC onToolUseStart", {
13888
13912
  sessionId: route.sessionId,
13889
13913
  toolName
13890
13914
  });
@@ -13892,7 +13916,7 @@ async function dispatchToCC(params) {
13892
13916
  bridge.onToolUseStart(toolName, toolInput);
13893
13917
  },
13894
13918
  onToolResult: (toolUseId, result) => {
13895
- log$12.info("CC onToolResult", {
13919
+ log$13.info("CC onToolResult", {
13896
13920
  sessionId: route.sessionId,
13897
13921
  toolUseId,
13898
13922
  traceId: runtimeConfig.runContext?.traceId,
@@ -13901,7 +13925,7 @@ async function dispatchToCC(params) {
13901
13925
  bridge.onToolResult(toolUseId);
13902
13926
  },
13903
13927
  onToolProgress: (toolName, elapsedSeconds) => {
13904
- log$12.debug("CC onToolProgress", {
13928
+ log$13.debug("CC onToolProgress", {
13905
13929
  sessionId: route.sessionId,
13906
13930
  toolName,
13907
13931
  elapsedSeconds
@@ -13909,7 +13933,7 @@ async function dispatchToCC(params) {
13909
13933
  bridge.onToolProgress(toolName, elapsedSeconds);
13910
13934
  },
13911
13935
  onTurnEnd: (stopReason) => {
13912
- log$12.info("CC onTurnEnd", {
13936
+ log$13.info("CC onTurnEnd", {
13913
13937
  sessionId: route.sessionId,
13914
13938
  stopReason
13915
13939
  });
@@ -13921,14 +13945,14 @@ async function dispatchToCC(params) {
13921
13945
  },
13922
13946
  onResult: (result) => {
13923
13947
  resultHandlingPromise = handleResult(result).catch((err) => {
13924
- log$12.error("CC onResult 收尾处理失败", {
13948
+ log$13.error("CC onResult 收尾处理失败", {
13925
13949
  sessionId: route.sessionId,
13926
13950
  error: err instanceof Error ? err.message : String(err)
13927
13951
  });
13928
13952
  });
13929
13953
  },
13930
13954
  onError: (error) => {
13931
- log$12.error("CC 进程错误", {
13955
+ log$13.error("CC 进程错误", {
13932
13956
  sessionId: route.sessionId,
13933
13957
  traceId: runtimeConfig.runContext?.traceId,
13934
13958
  error: error.message
@@ -13936,7 +13960,7 @@ async function dispatchToCC(params) {
13936
13960
  cardController.onError(error, { kind: "cc-process" });
13937
13961
  },
13938
13962
  onAuditEvent: (event) => {
13939
- log$12.info("CC onAuditEvent", {
13963
+ log$13.info("CC onAuditEvent", {
13940
13964
  ...event,
13941
13965
  inputSummary: summarizeForAudit(event.inputSummary),
13942
13966
  resultSummary: summarizeForAudit(event.resultSummary)
@@ -13947,7 +13971,7 @@ async function dispatchToCC(params) {
13947
13971
  onBlocked: handleBlockedEvent,
13948
13972
  onBlockedEvent: handleBlockedEvent
13949
13973
  };
13950
- log$12.info("开始执行 CC 进程", {
13974
+ log$13.info("开始执行 CC 进程", {
13951
13975
  sessionId: route.sessionId,
13952
13976
  cwd: route.cwd,
13953
13977
  promptLength: prompt.length,
@@ -13970,11 +13994,11 @@ async function dispatchToCC(params) {
13970
13994
  onMcpCallback.on(`callback:${route.sessionId}`, mcpCallbackListener);
13971
13995
  try {
13972
13996
  await processManager.executePrompt(runtimeConfig, callbacks);
13973
- log$12.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
13997
+ log$13.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
13974
13998
  if (resultHandlingPromise) await resultHandlingPromise;
13975
13999
  } catch (err) {
13976
14000
  const errorMessage = err instanceof Error ? err.message : String(err);
13977
- log$12.error("CC 进程 executePrompt 调用异常", {
14001
+ log$13.error("CC 进程 executePrompt 调用异常", {
13978
14002
  sessionId: route.sessionId,
13979
14003
  error: errorMessage
13980
14004
  });
@@ -13998,7 +14022,7 @@ async function dispatchToCC(params) {
13998
14022
  */
13999
14023
  async function dispatchTeammateEval(params) {
14000
14024
  const { chatId, prompt, account, sessionRouter, processManager } = params;
14001
- log$12.info("dispatchTeammateEval 开始", {
14025
+ log$13.info("dispatchTeammateEval 开始", {
14002
14026
  chatId,
14003
14027
  promptLength: prompt.length
14004
14028
  });
@@ -14011,7 +14035,7 @@ async function dispatchTeammateEval(params) {
14011
14035
  userName: void 0
14012
14036
  });
14013
14037
  if (processManager.isSessionBusy?.(route.sessionId)) {
14014
- log$12.info("dispatchTeammateEval 跳过:主 session 正在执行", {
14038
+ log$13.info("dispatchTeammateEval 跳过:主 session 正在执行", {
14015
14039
  chatId,
14016
14040
  sessionId: route.sessionId
14017
14041
  });
@@ -14074,7 +14098,7 @@ async function dispatchTeammateEval(params) {
14074
14098
  }
14075
14099
  });
14076
14100
  } catch (err) {
14077
- log$12.error("teammate run context 构造失败,跳过评估", {
14101
+ log$13.error("teammate run context 构造失败,跳过评估", {
14078
14102
  chatId,
14079
14103
  error: err instanceof Error ? err.message : String(err)
14080
14104
  });
@@ -14098,7 +14122,7 @@ async function dispatchTeammateEval(params) {
14098
14122
  sourceRefs: params.sourceRefs ?? (params.requestId ? [params.requestId] : [])
14099
14123
  });
14100
14124
  if (evalDecision.decision === "NO_REPLY") {
14101
- log$12.info("teammate evaluator 决定静默", {
14125
+ log$13.info("teammate evaluator 决定静默", {
14102
14126
  chatId,
14103
14127
  evalSessionId,
14104
14128
  summary: evalDecision.summary.slice(0, 120)
@@ -14132,7 +14156,7 @@ async function dispatchTeammateEval(params) {
14132
14156
  const confirmReply = () => {
14133
14157
  if (confirmed) return;
14134
14158
  confirmed = true;
14135
- log$12.info("teammate 确认回复,创建流式卡片", {
14159
+ log$13.info("teammate 确认回复,创建流式卡片", {
14136
14160
  chatId,
14137
14161
  thinkingLen: thinkingBuffer.length,
14138
14162
  textLen: fullTextBuffer.length
@@ -14189,7 +14213,7 @@ async function dispatchTeammateEval(params) {
14189
14213
  fullTextBuffer += text;
14190
14214
  if (fullTextBuffer.trimStart().startsWith(NO_REPLY_TOKEN)) {
14191
14215
  silenced = true;
14192
- log$12.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
14216
+ log$13.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
14193
14217
  chatId,
14194
14218
  bufferLen: fullTextBuffer.length
14195
14219
  });
@@ -14222,7 +14246,7 @@ async function dispatchTeammateEval(params) {
14222
14246
  toolName: currentToolName,
14223
14247
  toolCallId: toolUseId
14224
14248
  });
14225
- log$12.info("teammate onToolResult", {
14249
+ log$13.info("teammate onToolResult", {
14226
14250
  chatId,
14227
14251
  toolUseId,
14228
14252
  traceId: mainRuntimeConfig.runContext?.traceId,
@@ -14240,7 +14264,7 @@ async function dispatchTeammateEval(params) {
14240
14264
  onTurnEnd: (stopReason) => {
14241
14265
  finalStopReason = stopReason;
14242
14266
  if (stopReason === "tool_use") {
14243
- log$12.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
14267
+ log$13.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
14244
14268
  if (confirmed && bridge) bridge.onTurnEnd(stopReason);
14245
14269
  return;
14246
14270
  }
@@ -14248,7 +14272,7 @@ async function dispatchTeammateEval(params) {
14248
14272
  const trimmed = fullTextBuffer.trim();
14249
14273
  if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
14250
14274
  silenced = true;
14251
- log$12.info("teammate turnEnd 最终判断: 静默", {
14275
+ log$13.info("teammate turnEnd 最终判断: 静默", {
14252
14276
  chatId,
14253
14277
  trimmed: trimmed.slice(0, 60)
14254
14278
  });
@@ -14263,7 +14287,7 @@ async function dispatchTeammateEval(params) {
14263
14287
  resolve();
14264
14288
  },
14265
14289
  onError: (error) => {
14266
- log$12.error("teammate 评估 CC 进程错误", {
14290
+ log$13.error("teammate 评估 CC 进程错误", {
14267
14291
  chatId,
14268
14292
  traceId: mainRuntimeConfig.runContext?.traceId,
14269
14293
  error: error.message
@@ -14271,14 +14295,14 @@ async function dispatchTeammateEval(params) {
14271
14295
  resolve();
14272
14296
  },
14273
14297
  onAuditEvent: (event) => {
14274
- log$12.info("teammate onAuditEvent", {
14298
+ log$13.info("teammate onAuditEvent", {
14275
14299
  ...event,
14276
14300
  inputSummary: summarizeForAudit(event.inputSummary),
14277
14301
  resultSummary: summarizeForAudit(event.resultSummary)
14278
14302
  });
14279
14303
  },
14280
14304
  onRuntimeEvent: (event) => {
14281
- log$12.info("teammate onRuntimeEvent", {
14305
+ log$13.info("teammate onRuntimeEvent", {
14282
14306
  type: event.type,
14283
14307
  chatId,
14284
14308
  traceId: mainRuntimeConfig.runContext?.traceId,
@@ -14289,7 +14313,7 @@ async function dispatchTeammateEval(params) {
14289
14313
  });
14290
14314
  },
14291
14315
  onScenarioEvent: (event) => {
14292
- log$12.info("teammate onScenarioEvent", {
14316
+ log$13.info("teammate onScenarioEvent", {
14293
14317
  type: event.type,
14294
14318
  chatId,
14295
14319
  traceId: mainRuntimeConfig.runContext?.traceId,
@@ -14301,28 +14325,28 @@ async function dispatchTeammateEval(params) {
14301
14325
  },
14302
14326
  onBlocked: (event) => {
14303
14327
  const sanitized = sanitizeBlockedEvent(event);
14304
- log$12.warn("teammate onBlocked", {
14328
+ log$13.warn("teammate onBlocked", {
14305
14329
  ...sanitized,
14306
14330
  traceId: mainRuntimeConfig.runContext?.traceId
14307
14331
  });
14308
14332
  },
14309
14333
  onBlockedEvent: (event) => {
14310
14334
  const sanitized = sanitizeBlockedEvent(event);
14311
- log$12.warn("teammate onBlockedEvent", {
14335
+ log$13.warn("teammate onBlockedEvent", {
14312
14336
  ...sanitized,
14313
14337
  traceId: mainRuntimeConfig.runContext?.traceId
14314
14338
  });
14315
14339
  }
14316
14340
  };
14317
14341
  processManager.executePrompt(mainRuntimeConfig, callbacks).catch((err) => {
14318
- log$12.error("teammate executePrompt 异常", {
14342
+ log$13.error("teammate executePrompt 异常", {
14319
14343
  chatId,
14320
14344
  error: err instanceof Error ? err.message : String(err)
14321
14345
  });
14322
14346
  resolve();
14323
14347
  });
14324
14348
  });
14325
- log$12.info("dispatchTeammateEval 完成", {
14349
+ log$13.info("dispatchTeammateEval 完成", {
14326
14350
  chatId,
14327
14351
  confirmed,
14328
14352
  silenced,
@@ -14333,7 +14357,7 @@ async function dispatchTeammateEval(params) {
14333
14357
  return { replied: confirmed };
14334
14358
  } catch (err) {
14335
14359
  const errorMessage = err instanceof Error ? err.message : String(err);
14336
- log$12.error("dispatchTeammateEval 异常", {
14360
+ log$13.error("dispatchTeammateEval 异常", {
14337
14361
  chatId,
14338
14362
  error: errorMessage
14339
14363
  });
@@ -14357,7 +14381,7 @@ async function runTeammateDecisionEval(params) {
14357
14381
  resolve();
14358
14382
  },
14359
14383
  onError: (error) => {
14360
- log$12.error("teammate evaluator 执行错误", {
14384
+ log$13.error("teammate evaluator 执行错误", {
14361
14385
  chatId,
14362
14386
  sessionId: runtimeConfig.sessionId,
14363
14387
  error: error.message
@@ -14365,21 +14389,21 @@ async function runTeammateDecisionEval(params) {
14365
14389
  resolve();
14366
14390
  },
14367
14391
  onToolUseStart: (toolName, toolInput) => {
14368
- log$12.info("teammate evaluator toolUseStart", {
14392
+ log$13.info("teammate evaluator toolUseStart", {
14369
14393
  chatId,
14370
14394
  toolName,
14371
14395
  toolInput: summarizeForAudit(toolInput)
14372
14396
  });
14373
14397
  },
14374
14398
  onToolResult: (toolUseId, result) => {
14375
- log$12.info("teammate evaluator toolResult", {
14399
+ log$13.info("teammate evaluator toolResult", {
14376
14400
  chatId,
14377
14401
  toolUseId,
14378
14402
  result: summarizeForAudit(result)
14379
14403
  });
14380
14404
  }
14381
14405
  }).catch((err) => {
14382
- log$12.error("teammate evaluator executePrompt 异常", {
14406
+ log$13.error("teammate evaluator executePrompt 异常", {
14383
14407
  chatId,
14384
14408
  sessionId: runtimeConfig.sessionId,
14385
14409
  error: err instanceof Error ? err.message : String(err)
@@ -14394,7 +14418,7 @@ async function runTeammateDecisionEval(params) {
14394
14418
  const decision = trimmed !== "" && trimmed !== placeholderText && firstToken === "HANDOFF_TO_MAIN" ? "HANDOFF_TO_MAIN" : "NO_REPLY";
14395
14419
  const cleanedSummary = trimmed.replace(/^(HANDOFF_TO_MAIN|NO_REPLY)\b[\s::-]*/i, "").trim();
14396
14420
  const summary = malformed ? "NO_REPLY (malformed evaluator output)" : cleanedSummary.slice(0, 600) || `${decision} (thinking ${thinkingLength} chars)`;
14397
- log$12.info("teammate evaluator 完成", {
14421
+ log$13.info("teammate evaluator 完成", {
14398
14422
  chatId,
14399
14423
  sessionId: runtimeConfig.sessionId,
14400
14424
  decision,
@@ -14452,7 +14476,7 @@ async function handleMcpToolCallback(req, ctx) {
14452
14476
  try {
14453
14477
  if (req.tool === "ask_user") {
14454
14478
  const params = req.params;
14455
- log$12.info("[MCP] 收到 ask_user 回调,准备发送提问卡片", {
14479
+ log$13.info("[MCP] 收到 ask_user 回调,准备发送提问卡片", {
14456
14480
  requestId: req.requestId,
14457
14481
  sessionId: req.sessionId,
14458
14482
  question: params.question?.slice(0, 50)
@@ -14465,14 +14489,14 @@ async function handleMcpToolCallback(req, ctx) {
14465
14489
  });
14466
14490
  mcpOperationMap.set(operationId, req.requestId);
14467
14491
  await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
14468
- log$12.info("[MCP] 提问卡片已发送", {
14492
+ log$13.info("[MCP] 提问卡片已发送", {
14469
14493
  requestId: req.requestId,
14470
14494
  operationId,
14471
14495
  chatId
14472
14496
  });
14473
14497
  } else if (req.tool === "request_permission") {
14474
14498
  const params = req.params;
14475
- log$12.info("[MCP] 收到 request_permission 回调,准备发送授权卡片", {
14499
+ log$13.info("[MCP] 收到 request_permission 回调,准备发送授权卡片", {
14476
14500
  requestId: req.requestId,
14477
14501
  scopes: params.scopes
14478
14502
  });
@@ -14486,14 +14510,14 @@ async function handleMcpToolCallback(req, ctx) {
14486
14510
  });
14487
14511
  mcpOperationMap.set(operationId, req.requestId);
14488
14512
  await sendInteractiveCardViaCardKit(card, chatId, account, replyInThread, threadId);
14489
- log$12.info("[MCP] 授权卡片已发送", {
14513
+ log$13.info("[MCP] 授权卡片已发送", {
14490
14514
  requestId: req.requestId,
14491
14515
  operationId,
14492
14516
  chatId
14493
14517
  });
14494
14518
  }
14495
14519
  } catch (err) {
14496
- log$12.error("[MCP] 交互卡片发送失败", {
14520
+ log$13.error("[MCP] 交互卡片发送失败", {
14497
14521
  requestId: req.requestId,
14498
14522
  tool: req.tool,
14499
14523
  error: err instanceof Error ? err.message : String(err)
@@ -14518,7 +14542,7 @@ async function sendInteractiveCardViaCardKit(card, chatId, account, replyInThrea
14518
14542
  } });
14519
14543
  const cardId = createResp?.data?.card_id;
14520
14544
  if (!cardId) throw new Error(`CardKit card.create 失败: code=${createResp?.code}, msg=${createResp?.msg}`);
14521
- log$12.info("[MCP] CardKit 卡片实体已创建", { cardId });
14545
+ log$13.info("[MCP] CardKit 卡片实体已创建", { cardId });
14522
14546
  const contentPayload = JSON.stringify({
14523
14547
  type: "card",
14524
14548
  data: { card_id: cardId }
@@ -16048,7 +16072,7 @@ const convertLocation = (raw) => {
16048
16072
  * injected via callbacks in `ConvertContext`. Callers are responsible
16049
16073
  * for creating the appropriate callbacks (UAT / TAT / event push).
16050
16074
  */
16051
- const log$11 = larkLogger("converters/merge-forward");
16075
+ const log$12 = larkLogger("converters/merge-forward");
16052
16076
  /**
16053
16077
  * Recursively expand a merge_forward message.
16054
16078
  *
@@ -16063,7 +16087,7 @@ const log$11 = larkLogger("converters/merge-forward");
16063
16087
  const convertMergeForward = async (_raw, ctx) => {
16064
16088
  const { accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent } = ctx;
16065
16089
  if (!fetchSubMessages) {
16066
- log$11.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
16090
+ log$12.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
16067
16091
  messageId,
16068
16092
  accountId
16069
16093
  });
@@ -16081,23 +16105,23 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
16081
16105
  let items;
16082
16106
  try {
16083
16107
  items = await fetchSubMessages(messageId);
16084
- log$11.info("fetchSubMessages 成功", {
16108
+ log$12.info("fetchSubMessages 成功", {
16085
16109
  messageId,
16086
16110
  itemCount: items.length
16087
16111
  });
16088
16112
  } catch (error) {
16089
- log$11.error("fetch sub-messages failed", {
16113
+ log$12.error("fetch sub-messages failed", {
16090
16114
  messageId,
16091
16115
  error: error instanceof Error ? error.message : String(error)
16092
16116
  });
16093
16117
  return "<forwarded_messages/>";
16094
16118
  }
16095
16119
  if (items.length === 0) {
16096
- log$11.warn("fetchSubMessages 返回空 items", { messageId });
16120
+ log$12.warn("fetchSubMessages 返回空 items", { messageId });
16097
16121
  return "<forwarded_messages/>";
16098
16122
  }
16099
16123
  const childrenMap = buildChildrenMap(items, messageId);
16100
- log$11.info("buildChildrenMap 完成", {
16124
+ log$12.info("buildChildrenMap 完成", {
16101
16125
  messageId,
16102
16126
  parentKeys: [...childrenMap.keys()],
16103
16127
  childCounts: [...childrenMap.entries()].map(([k, v]) => `${k}:${v.length}`)
@@ -16106,7 +16130,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
16106
16130
  if (senderIds.length > 0 && batchResolveNames) try {
16107
16131
  await batchResolveNames(senderIds);
16108
16132
  } catch (err) {
16109
- log$11.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
16133
+ log$12.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
16110
16134
  }
16111
16135
  return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
16112
16136
  }
@@ -16186,7 +16210,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
16186
16210
  const indented = indentLines(content, " ");
16187
16211
  parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
16188
16212
  } catch (err) {
16189
- log$11.warn("failed to convert sub-message", {
16213
+ log$12.warn("failed to convert sub-message", {
16190
16214
  messageId: item.message_id,
16191
16215
  msgType: item.msg_type ?? "unknown",
16192
16216
  error: err instanceof Error ? err.message : String(err)
@@ -16400,7 +16424,7 @@ async function convertMessageContent(raw, messageType, ctx) {
16400
16424
  }
16401
16425
  //#endregion
16402
16426
  //#region src/messaging/inbound/parse-io.ts
16403
- const log$10 = larkLogger("inbound/parse-io");
16427
+ const log$11 = larkLogger("inbound/parse-io");
16404
16428
  /**
16405
16429
  * 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
16406
16430
  * 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
@@ -16420,7 +16444,7 @@ async function fetchCardContent(messageId, larkClient) {
16420
16444
  }
16421
16445
  }))?.data?.items?.[0]?.body?.content ?? void 0;
16422
16446
  } catch (err) {
16423
- log$10.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
16447
+ log$11.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
16424
16448
  return;
16425
16449
  }
16426
16450
  }
@@ -16435,7 +16459,7 @@ async function fetchCardContent(messageId, larkClient) {
16435
16459
  */
16436
16460
  function createFetchSubMessages(larkClient) {
16437
16461
  return async (msgId) => {
16438
- log$10.info("fetchSubMessages 请求", {
16462
+ log$11.info("fetchSubMessages 请求", {
16439
16463
  msgId,
16440
16464
  url: `/open-apis/im/v1/messages/${msgId}`
16441
16465
  });
@@ -16447,7 +16471,7 @@ function createFetchSubMessages(larkClient) {
16447
16471
  card_msg_content_type: "raw_card_content"
16448
16472
  }
16449
16473
  });
16450
- log$10.info("fetchSubMessages 响应", {
16474
+ log$11.info("fetchSubMessages 响应", {
16451
16475
  msgId,
16452
16476
  code: response?.code,
16453
16477
  msg: response?.msg,
@@ -16464,11 +16488,11 @@ function createFetchSubMessages(larkClient) {
16464
16488
  * the account and log function.
16465
16489
  */
16466
16490
  function createParseResolveNames(account) {
16467
- return createBatchResolveNames(account, (...args) => log$10.info(args.map(String).join(" ")));
16491
+ return createBatchResolveNames(account, (...args) => log$11.info(args.map(String).join(" ")));
16468
16492
  }
16469
16493
  //#endregion
16470
16494
  //#region src/messaging/inbound/parse.ts
16471
- const log$9 = larkLogger("inbound/parse");
16495
+ const log$10 = larkLogger("inbound/parse");
16472
16496
  /**
16473
16497
  * Parse a raw Feishu message event into a normalised MessageContext.
16474
16498
  *
@@ -16518,7 +16542,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
16518
16542
  const fullContent = await fetchCardContent(event.message.message_id, larkClient);
16519
16543
  if (fullContent) {
16520
16544
  effectiveContent = fullContent;
16521
- log$9.info("replaced interactive content with full v2 card data");
16545
+ log$10.info("replaced interactive content with full v2 card data");
16522
16546
  }
16523
16547
  }
16524
16548
  const convertCtx = {
@@ -16558,6 +16582,219 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
16558
16582
  };
16559
16583
  }
16560
16584
  //#endregion
16585
+ //#region src/messaging/shared/message-lookup.ts
16586
+ const log$9 = larkLogger("shared/message-lookup");
16587
+ /**
16588
+ * Retrieve a single message by its ID from the Feishu IM API.
16589
+ *
16590
+ * Returns a normalised {@link FeishuMessageInfo} object, or `null` if the
16591
+ * message cannot be found or the API returns an error.
16592
+ *
16593
+ * @param params.cfg - Plugin configuration with Feishu credentials.
16594
+ * @param params.messageId - The message ID to fetch.
16595
+ * @param params.accountId - Optional account identifier for multi-account setups.
16596
+ */
16597
+ async function getMessageFeishu(params) {
16598
+ const { cfg, messageId, accountId, expandForward } = params;
16599
+ const larkClient = LarkClient.fromCfg(cfg, accountId);
16600
+ const sdk = larkClient.sdk;
16601
+ try {
16602
+ const requestOpts = {
16603
+ method: "GET",
16604
+ url: `/open-apis/im/v1/messages/mget`,
16605
+ params: {
16606
+ message_ids: messageId,
16607
+ user_id_type: "open_id",
16608
+ card_msg_content_type: "raw_card_content"
16609
+ }
16610
+ };
16611
+ const items = (await sdk.request(requestOpts))?.data?.items;
16612
+ if (!items || items.length === 0) {
16613
+ log$9.info(`getMessageFeishu: no items returned for ${messageId}`);
16614
+ return null;
16615
+ }
16616
+ const expandCtx = expandForward ? {
16617
+ cfg,
16618
+ accountId,
16619
+ fetchSubMessages: async (msgId) => {
16620
+ const res = await larkClient.sdk.request({
16621
+ method: "GET",
16622
+ url: `/open-apis/im/v1/messages/${msgId}`,
16623
+ params: {
16624
+ user_id_type: "open_id",
16625
+ card_msg_content_type: "raw_card_content"
16626
+ }
16627
+ });
16628
+ if (res?.code !== 0) throw new Error(`API error: code=${res?.code} msg=${res?.msg}`);
16629
+ return res?.data?.items ?? [];
16630
+ },
16631
+ batchResolveNames: createBatchResolveNames(getLarkAccount(cfg, accountId), (...args) => log$9.info(args.map(String).join(" ")))
16632
+ } : void 0;
16633
+ return await parseMessageItem(items[0], messageId, expandCtx);
16634
+ } catch (error) {
16635
+ log$9.error(`get message failed (${messageId}): ${error instanceof Error ? error.message : String(error)}`);
16636
+ return null;
16637
+ }
16638
+ }
16639
+ /**
16640
+ * Parse a single message item from the Feishu IM API response into a
16641
+ * normalised {@link FeishuMessageInfo}.
16642
+ *
16643
+ * Content parsing is delegated to the shared converter system so that
16644
+ * every message-type mapping is defined in exactly one place.
16645
+ */
16646
+ async function parseMessageItem(msg, fallbackMessageId, expandCtx) {
16647
+ const msgType = msg.msg_type ?? "text";
16648
+ const rawContent = msg.body?.content ?? "{}";
16649
+ const messageId = msg.message_id ?? fallbackMessageId;
16650
+ const acctId = expandCtx?.accountId;
16651
+ const { content } = await convertMessageContent(rawContent, msgType, {
16652
+ ...buildConvertContextFromItem(msg, fallbackMessageId, acctId),
16653
+ cfg: expandCtx?.cfg,
16654
+ accountId: acctId,
16655
+ fetchSubMessages: expandCtx?.fetchSubMessages,
16656
+ batchResolveNames: expandCtx?.batchResolveNames
16657
+ });
16658
+ const senderId = msg.sender?.id ?? void 0;
16659
+ const senderType = msg.sender?.sender_type ?? void 0;
16660
+ const senderName = senderId && acctId ? getUserNameCache(acctId).get(senderId) : void 0;
16661
+ return {
16662
+ messageId,
16663
+ chatId: msg.chat_id ?? "",
16664
+ chatType: msg.chat_type ?? void 0,
16665
+ senderId,
16666
+ senderName,
16667
+ senderType,
16668
+ content,
16669
+ contentType: msgType,
16670
+ createTime: msg.create_time ? parseInt(String(msg.create_time), 10) : void 0,
16671
+ threadId: msg.thread_id || void 0
16672
+ };
16673
+ }
16674
+ //#endregion
16675
+ //#region src/messaging/inbound/enrich.ts
16676
+ /** 从飞书 IM API 获取指定消息的 parent_id 和 root_id 字段 */
16677
+ async function getMessageParentId(params) {
16678
+ const { messageId, account, accountScopedCfg, log } = params;
16679
+ try {
16680
+ const items = (await LarkClient.fromCfg(accountScopedCfg, account.accountId).sdk.request({
16681
+ method: "GET",
16682
+ url: `/open-apis/im/v1/messages/mget`,
16683
+ params: {
16684
+ message_ids: messageId,
16685
+ user_id_type: "open_id"
16686
+ }
16687
+ }))?.data?.items;
16688
+ if (!items || items.length === 0) {
16689
+ log(`feishu[${account.accountId}]: getMessageParentId: no items returned for ${messageId}`);
16690
+ return null;
16691
+ }
16692
+ const item = items[0];
16693
+ log(`feishu[${account.accountId}]: getMessageParentId(${messageId}): parent_id=${item.parent_id || "none"}, root_id=${item.root_id || "none"}`);
16694
+ return {
16695
+ parentId: item.parent_id || void 0,
16696
+ rootId: item.root_id || void 0
16697
+ };
16698
+ } catch (error) {
16699
+ log(`feishu[${account.accountId}]: getMessageParentId failed (${messageId}): ${error instanceof Error ? error.message : String(error)}`);
16700
+ return null;
16701
+ }
16702
+ }
16703
+ /** 将消息格式化为带缩进层级的回复链文本行 */
16704
+ function formatChainMessage(msg, depth) {
16705
+ const prefix = `[message_id=${msg.messageId}]`;
16706
+ const text = msg.senderName ? `${prefix} ${msg.senderName}: ${msg.content}` : `${prefix} ${msg.content}`;
16707
+ if (depth === 0) return text;
16708
+ return `${" ".repeat(depth)}↳ ${text}`;
16709
+ }
16710
+ /**
16711
+ * 递归解析回复链上下文,支持 rootId 追溯和 N 层 parentId 递归。
16712
+ *
16713
+ * 解析逻辑:
16714
+ * 1. 如果存在 rootId 且与 parentId 不同,先获取话题根消息作为链的起点
16715
+ * 2. 从 parentId 开始,递归向上追溯每条消息的 parentId,最多追溯 maxQuoteDepth 层
16716
+ * 3. 对结果去重后按时间顺序(最旧在前)输出完整的对话链上下文
16717
+ *
16718
+ * 输出格式:
16719
+ * ```
16720
+ * [回复链上下文]:
16721
+ * [message_id=xxx] senderName: content
16722
+ * ↳ [message_id=yyy] senderName2: content2
16723
+ * ↳ [message_id=zzz] senderName3: content3
16724
+ * ```
16725
+ */
16726
+ async function resolveQuotedContent(params) {
16727
+ const { ctx, accountScopedCfg, account, log, maxQuoteDepth = 3 } = params;
16728
+ if (!ctx.parentId) return void 0;
16729
+ log(`feishu[${account.accountId}]: resolveQuotedContent 开始, parentId=${ctx.parentId}, rootId=${ctx.rootId || "none"}, maxDepth=${maxQuoteDepth}`);
16730
+ const chainMessages = [];
16731
+ const visited = /* @__PURE__ */ new Set();
16732
+ let currentMsgId = ctx.parentId;
16733
+ let depth = 0;
16734
+ while (currentMsgId && depth < maxQuoteDepth) {
16735
+ if (visited.has(currentMsgId)) {
16736
+ log(`feishu[${account.accountId}]: 检测到循环引用, messageId=${currentMsgId}, 停止递归`);
16737
+ break;
16738
+ }
16739
+ visited.add(currentMsgId);
16740
+ try {
16741
+ const msg = await getMessageFeishu({
16742
+ cfg: accountScopedCfg,
16743
+ messageId: currentMsgId,
16744
+ accountId: account.accountId,
16745
+ expandForward: true
16746
+ });
16747
+ if (!msg) {
16748
+ log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层消息获取为空, messageId=${currentMsgId}, 停止递归`);
16749
+ break;
16750
+ }
16751
+ log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层: messageId=${currentMsgId}, content=${msg.content?.slice(0, 80)}`);
16752
+ chainMessages.push(msg);
16753
+ const parentInfo = await getMessageParentId({
16754
+ messageId: currentMsgId,
16755
+ account,
16756
+ accountScopedCfg,
16757
+ log
16758
+ });
16759
+ if (!parentInfo?.parentId) {
16760
+ log(`feishu[${account.accountId}]: 第 ${depth + 1} 层消息无 parentId, 到达链顶端`);
16761
+ break;
16762
+ }
16763
+ currentMsgId = parentInfo.parentId;
16764
+ depth++;
16765
+ } catch (err) {
16766
+ log(`feishu[${account.accountId}]: 回复链第 ${depth + 1} 层获取失败, messageId=${currentMsgId}: ${String(err)}, 停止递归`);
16767
+ break;
16768
+ }
16769
+ }
16770
+ if (depth >= maxQuoteDepth) log(`feishu[${account.accountId}]: 已达到最大递归深度 ${maxQuoteDepth}, 停止追溯`);
16771
+ if (ctx.rootId && ctx.rootId !== ctx.parentId && !visited.has(ctx.rootId)) try {
16772
+ const rootMsg = await getMessageFeishu({
16773
+ cfg: accountScopedCfg,
16774
+ messageId: ctx.rootId,
16775
+ accountId: account.accountId,
16776
+ expandForward: true
16777
+ });
16778
+ if (rootMsg) {
16779
+ log(`feishu[${account.accountId}]: 获取到话题根消息: rootId=${ctx.rootId}, content=${rootMsg.content?.slice(0, 80)}`);
16780
+ chainMessages.push(rootMsg);
16781
+ visited.add(ctx.rootId);
16782
+ } else log(`feishu[${account.accountId}]: 话题根消息获取为空, rootId=${ctx.rootId}`);
16783
+ } catch (err) {
16784
+ log(`feishu[${account.accountId}]: 话题根消息获取失败, rootId=${ctx.rootId}: ${String(err)}`);
16785
+ }
16786
+ if (chainMessages.length === 0) {
16787
+ log(`feishu[${account.accountId}]: 回复链解析完成, 未获取到任何消息`);
16788
+ return;
16789
+ }
16790
+ chainMessages.reverse();
16791
+ const lines = ["[回复链上下文]:"];
16792
+ for (let i = 0; i < chainMessages.length; i++) lines.push(formatChainMessage(chainMessages[i], i));
16793
+ const result = lines.join("\n");
16794
+ log(`feishu[${account.accountId}]: 回复链解析完成, 共 ${chainMessages.length} 条消息`);
16795
+ return result;
16796
+ }
16797
+ //#endregion
16561
16798
  //#region src/messaging/inbound/dedup.ts
16562
16799
  const DEFAULT_TTL_MS = 720 * 60 * 1e3;
16563
16800
  const DEFAULT_MAX_ENTRIES = 5e3;
@@ -18187,6 +18424,30 @@ async function main() {
18187
18424
  brand: "feishu",
18188
18425
  config: {}
18189
18426
  };
18427
+ const accountScopedCfg = { channels: { feishu: {
18428
+ ...account.config,
18429
+ appId: account.appId,
18430
+ appSecret: account.appSecret,
18431
+ domain: account.brand
18432
+ } } };
18433
+ const resolveQuotedContentForMessage = async (ctx) => {
18434
+ if (!ctx.parentId) return void 0;
18435
+ try {
18436
+ return await resolveQuotedContent({
18437
+ ctx,
18438
+ accountScopedCfg,
18439
+ account,
18440
+ log: (...args) => logger.info("resolveQuotedContent", { detail: args.map(String).join(" ") })
18441
+ });
18442
+ } catch (err) {
18443
+ logger.warn("引用消息解析失败,继续仅处理当前消息", {
18444
+ msgId: ctx.messageId,
18445
+ parentId: ctx.parentId,
18446
+ error: err instanceof Error ? err.message : String(err)
18447
+ });
18448
+ return;
18449
+ }
18450
+ };
18190
18451
  const larkClient = LarkClient.fromAccount(account);
18191
18452
  const probeResult = await larkClient.probe();
18192
18453
  if (probeResult.ok) logger.info("Bot 身份探测成功", {
@@ -18325,7 +18586,7 @@ async function main() {
18325
18586
  });
18326
18587
  if (nameResult.name) parsed.senderName = nameResult.name;
18327
18588
  } catch {}
18328
- const formattedText = formatForCC(parsed, true);
18589
+ const formattedText = formatPromptForCC(parsed, true, await resolveQuotedContentForMessage(parsed));
18329
18590
  teammateBuffer.push(parsed.chatId, {
18330
18591
  formattedText,
18331
18592
  bufferedAt: Date.now(),
@@ -18377,6 +18638,7 @@ async function main() {
18377
18638
  workspaceRoot
18378
18639
  });
18379
18640
  await ensureUserDirectory(userContext);
18641
+ const quotedContent = await resolveQuotedContentForMessage(parsed);
18380
18642
  await dispatchToCC({
18381
18643
  ctx: parsed,
18382
18644
  account,
@@ -18386,7 +18648,8 @@ async function main() {
18386
18648
  userContext,
18387
18649
  placeholderCardId,
18388
18650
  placeholderMessageId,
18389
- messageStore
18651
+ messageStore,
18652
+ quotedContent
18390
18653
  });
18391
18654
  if (threadId) markBotInvolvedInThread(threadId);
18392
18655
  } catch (err) {