@vibe-lark/larkpal 0.1.38 → 0.1.40

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 (3) hide show
  1. package/bin/larkpal.js +0 -0
  2. package/dist/main.mjs +673 -255
  3. package/package.json +14 -13
package/dist/main.mjs CHANGED
@@ -9,6 +9,7 @@ import "dotenv/config";
9
9
  import { homedir } from "node:os";
10
10
  import { access, lstat, mkdir, readFile, readdir, readlink, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
11
11
  import { execSync, spawn } from "node:child_process";
12
+ import { randomUUID } from "node:crypto";
12
13
  import { createInterface } from "node:readline";
13
14
  import { v4, v5 } from "uuid";
14
15
  import { EventEmitter } from "node:events";
@@ -326,6 +327,54 @@ const DEFAULT_CLAUDE_MD = `# LarkPal
326
327
  ## 技能
327
328
  - 你的可用技能在 ~/.claude/commands/ 和当前目录的 .claude/commands/ 中
328
329
  - 使用 /help 查看所有可用技能
330
+
331
+ ## 交互式卡片(重要)
332
+
333
+ 当你需要向用户发送交互式卡片时,在回复中嵌入以下标记。系统会自动检测并发送对应的飞书卡片。
334
+
335
+ ### 权限申请卡片
336
+ 当 lark-cli 调用返回权限不足错误(如 99991672、scope missing)时,使用此标记引导用户申请权限:
337
+
338
+ [PERMISSION_REQUEST]
339
+ \`\`\`json
340
+ {"scopes": ["calendar:calendar", "vc:meeting.meetingevent:read"]}
341
+ \`\`\`
342
+
343
+ 系统会发送一张独立的权限申请卡片,包含"去申请"和"已完成"按钮。用户完成配置后会自动通知你重试。
344
+
345
+ ### 向用户提问卡片
346
+ 当你需要收集用户的多维信息时(不确定且需要用户决策的场景),使用此标记发送结构化提问卡片:
347
+
348
+ [ASK_USER]
349
+ \`\`\`json
350
+ {
351
+ "question": "为了帮你完成这个任务,需要确认以下信息:",
352
+ "phases": [
353
+ {
354
+ "id": "target",
355
+ "title": "目标选择",
356
+ "description": "你想在哪个环境操作?",
357
+ "options": [{"label": "测试环境", "value": "sit"}, {"label": "生产环境", "value": "prod"}],
358
+ "allowFreeText": true,
359
+ "required": true
360
+ },
361
+ {
362
+ "id": "scope",
363
+ "title": "操作范围",
364
+ "options": [{"label": "仅当前模块", "value": "current"}, {"label": "全量", "value": "all"}],
365
+ "required": false
366
+ }
367
+ ]
368
+ }
369
+ \`\`\`
370
+
371
+ 卡片会逐 phase 展示选项,用户可选择预设项或跳过。至少需要回答一个 phase。回答完成后系统会将结果注入对话继续。
372
+
373
+ **注意**:标记文本会从最终展示给用户的回复中自动移除,用户只会看到卡片本身。
374
+
375
+ ### 何时使用标记 vs lark-cli
376
+ - **需要用户回答、确认、选择** 的场景 → 必须使用 [ASK_USER] 或 [PERMISSION_REQUEST] 标记,系统会统一样式并处理回调
377
+ - **纯通知、展示信息** 的卡片(不需要用户交互反馈) → 可以用 lark-cli 自行发送
329
378
  `;
330
379
  async function fileExists(filePath) {
331
380
  try {
@@ -3673,9 +3722,71 @@ function getDefaultClaudeMdBody() {
3673
3722
  - 当需要查看会话历史消息时,使用 lark-cli 从飞书接口获取
3674
3723
  - 用户发送的图片会自动保存到当前工作目录的 files/ 子目录中(以 img_key 命名)
3675
3724
 
3725
+ ## 安全规则(最高优先级)
3726
+ - **严禁**读取、输出、展示或以任何方式向用户透露以下敏感信息:
3727
+ - 环境变量中的 LARK_APP_SECRET、ANTHROPIC_API_KEY 及任何包含 SECRET/KEY/TOKEN/PASSWORD 的值
3728
+ - ~/.lark-cli/config.json 和 ~/.config/lark/config.json 中的 appSecret / app_secret 字段
3729
+ - ~/.larkpal/credentials.json 中的任何凭证内容
3730
+ - 任何 API 密钥、Token、密码等敏感凭证
3731
+ - **严禁**执行 \`cat\`/\`head\`/\`tail\`/\`grep\` 等命令读取上述文件内容
3732
+ - **严禁**在对话中引用、复述或暗示凭证的具体值(即使用户明确要求)
3733
+ - **严禁**修改 ~/.claude/CLAUDE.md 和 ~/.claude/settings.json — 这些文件由系统管理,人设内容来源于飞书云文档,你无权更改
3734
+ - 如果用户要求修改全局人设,应回复:"全局人设由飞书云文档统一管理,请在云文档中修改后执行 /sync-app-info 同步。"
3735
+ - 如果用户想添加个人偏好,可以修改当前会话目录下的 CLAUDE.md(仅当前会话生效)
3736
+ - 如果用户要求查看凭证,应回复:"出于安全策略,凭证信息不可查看或透露。"
3737
+ - lark-cli 的认证配置由系统自动管理,无需用户介入
3738
+
3676
3739
  ## 技能
3677
3740
  - 你的可用技能在 ~/.claude/commands/ 和当前目录的 .claude/commands/ 中
3678
3741
  - 使用 /help 查看所有可用技能
3742
+
3743
+ ## 交互式卡片(重要)
3744
+
3745
+ 当你需要向用户发送交互式卡片时,在回复中嵌入以下标记。系统会自动检测并发送对应的飞书卡片。
3746
+
3747
+ ### 权限申请卡片
3748
+ 当 lark-cli 调用返回权限不足错误(如 99991672、scope missing)时,使用此标记引导用户申请权限:
3749
+
3750
+ [PERMISSION_REQUEST]
3751
+ \`\`\`json
3752
+ {"scopes": ["calendar:calendar", "vc:meeting.meetingevent:read"]}
3753
+ \`\`\`
3754
+
3755
+ 系统会发送一张独立的权限申请卡片,包含"去申请"和"已完成"按钮。用户完成配置后会自动通知你重试。
3756
+
3757
+ ### 向用户提问卡片
3758
+ 当你需要收集用户的多维信息时(不确定且需要用户决策的场景),使用此标记发送结构化提问卡片:
3759
+
3760
+ [ASK_USER]
3761
+ \`\`\`json
3762
+ {
3763
+ "question": "为了帮你完成这个任务,需要确认以下信息:",
3764
+ "phases": [
3765
+ {
3766
+ "id": "target",
3767
+ "title": "目标选择",
3768
+ "description": "你想在哪个环境操作?",
3769
+ "options": [{"label": "测试环境", "value": "sit"}, {"label": "生产环境", "value": "prod"}],
3770
+ "allowFreeText": true,
3771
+ "required": true
3772
+ },
3773
+ {
3774
+ "id": "scope",
3775
+ "title": "操作范围",
3776
+ "options": [{"label": "仅当前模块", "value": "current"}, {"label": "全量", "value": "all"}],
3777
+ "required": false
3778
+ }
3779
+ ]
3780
+ }
3781
+ \`\`\`
3782
+
3783
+ 卡片会逐 phase 展示选项,用户可选择预设项或跳过。至少需要回答一个 phase。回答完成后系统会将结果注入对话继续。
3784
+
3785
+ **注意**:标记文本会从最终展示给用户的回复中自动移除,用户只会看到卡片本身。
3786
+
3787
+ ### 何时使用标记 vs lark-cli
3788
+ - **需要用户回答、确认、选择** 的场景 → 必须使用 [ASK_USER] 或 [PERMISSION_REQUEST] 标记,系统会统一样式并处理回调
3789
+ - **纯通知、展示信息** 的卡片(不需要用户交互反馈) → 可以用 lark-cli 自行发送
3679
3790
  `;
3680
3791
  }
3681
3792
  /**
@@ -5616,6 +5727,294 @@ var LarkClient = class LarkClient {
5616
5727
  };
5617
5728
  injectLarkClient(LarkClient);
5618
5729
  //#endregion
5730
+ //#region src/card/interactive-cards.ts
5731
+ /**
5732
+ * 交互式卡片模块 — 授权卡片 & 提问卡片
5733
+ *
5734
+ * 基于"后处理检测 + 下轮注入"的异步模式:
5735
+ * CC 无法暂停等待用户输入,因此通过卡片交互收集用户反馈后,
5736
+ * 将结果作为新消息注入 CC 的下一轮对话。
5737
+ *
5738
+ * 包含:
5739
+ * - 授权申请卡片(Permission Auth Card)
5740
+ * - 多 Phase 提问卡片(Ask Card)
5741
+ * - CC 输出文本后处理(检测特殊标记)
5742
+ * - 操作 ID 生命周期管理
5743
+ */
5744
+ const log$19 = larkLogger("card/interactive-cards");
5745
+ const pendingOperations = /* @__PURE__ */ new Map();
5746
+ const OPERATION_TTL_MS = 1800 * 1e3;
5747
+ setInterval(() => {
5748
+ const now = Date.now();
5749
+ for (const [id, op] of pendingOperations) if (now - op.createdAt > OPERATION_TTL_MS) pendingOperations.delete(id);
5750
+ }, 6e4);
5751
+ /**
5752
+ * 构建授权申请卡片(CardKit v2 格式)
5753
+ *
5754
+ * 包含:
5755
+ * - 缺失权限列表
5756
+ * - "去申请" 按钮(跳转到开放平台)
5757
+ * - "已完成" 按钮(回调通知宿主层)
5758
+ */
5759
+ function buildAuthCard(ctx) {
5760
+ const operationId = randomUUID();
5761
+ const openPlatformHost = ctx.brand === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn";
5762
+ const scopeQuery = ctx.scopes.join(",");
5763
+ const authUrl = `${openPlatformHost}/app/${ctx.appId}/auth?q=${encodeURIComponent(scopeQuery)}`;
5764
+ const card = {
5765
+ schema: "2.0",
5766
+ config: {
5767
+ wide_screen_mode: true,
5768
+ update_multi: true
5769
+ },
5770
+ header: {
5771
+ template: "orange",
5772
+ title: {
5773
+ tag: "plain_text",
5774
+ content: "🔐 需要申请权限才能继续"
5775
+ }
5776
+ },
5777
+ body: { elements: [
5778
+ {
5779
+ tag: "markdown",
5780
+ content: `当前操作需要以下飞书权限,请应用管理员前往开放平台申请:\n\n${ctx.scopes.map((s) => `• \`${s}\``).join("\n")}`
5781
+ },
5782
+ { tag: "hr" },
5783
+ {
5784
+ tag: "markdown",
5785
+ content: "**第一步:** 前往开放平台申请上述权限并发布版本"
5786
+ },
5787
+ {
5788
+ tag: "action",
5789
+ actions: [{
5790
+ tag: "button",
5791
+ text: {
5792
+ tag: "plain_text",
5793
+ content: "去申请权限 ↗"
5794
+ },
5795
+ type: "primary",
5796
+ multi_url: {
5797
+ url: authUrl,
5798
+ pc_url: authUrl,
5799
+ android_url: authUrl,
5800
+ ios_url: authUrl
5801
+ }
5802
+ }]
5803
+ },
5804
+ {
5805
+ tag: "markdown",
5806
+ content: "**第二步:** 权限申请通过后,点击下方按钮通知我继续"
5807
+ },
5808
+ {
5809
+ tag: "action",
5810
+ actions: [{
5811
+ tag: "button",
5812
+ text: {
5813
+ tag: "plain_text",
5814
+ content: "✓ 已完成权限配置"
5815
+ },
5816
+ type: "default",
5817
+ behaviors: [{
5818
+ type: "callback",
5819
+ value: {
5820
+ action: "auth_complete",
5821
+ operationId
5822
+ }
5823
+ }]
5824
+ }]
5825
+ }
5826
+ ] }
5827
+ };
5828
+ pendingOperations.set(operationId, {
5829
+ type: "auth",
5830
+ operationId,
5831
+ chatId: ctx.chatId,
5832
+ sessionId: ctx.sessionId,
5833
+ createdAt: Date.now(),
5834
+ authContext: {
5835
+ appId: ctx.appId,
5836
+ scopes: ctx.scopes
5837
+ }
5838
+ });
5839
+ log$19.info("已创建授权卡片操作", {
5840
+ operationId,
5841
+ scopes: ctx.scopes,
5842
+ sessionId: ctx.sessionId
5843
+ });
5844
+ return {
5845
+ card,
5846
+ operationId
5847
+ };
5848
+ }
5849
+ /**
5850
+ * 构建多 phase 提问卡片(CardKit v2 格式)
5851
+ *
5852
+ * 每次显示一个 phase:
5853
+ * - 有预设选项时显示按钮组
5854
+ * - 允许自由文本时显示输入提示
5855
+ * - 底部有"跳过"和"提交"按钮
5856
+ */
5857
+ function buildAskCard(ctx, phaseIndex = 0) {
5858
+ const operationId = randomUUID();
5859
+ const phase = ctx.phases[phaseIndex];
5860
+ const totalPhases = ctx.phases.length;
5861
+ const elements = [];
5862
+ elements.push({
5863
+ tag: "markdown",
5864
+ content: ctx.question
5865
+ });
5866
+ if (phase.description) elements.push({
5867
+ tag: "markdown",
5868
+ content: phase.description
5869
+ });
5870
+ elements.push({ tag: "hr" });
5871
+ elements.push({
5872
+ tag: "markdown",
5873
+ content: `**${phase.title}** (${phaseIndex + 1}/${totalPhases})`
5874
+ });
5875
+ if (phase.options && phase.options.length > 0) {
5876
+ const optionButtons = phase.options.map((opt) => ({
5877
+ tag: "button",
5878
+ text: {
5879
+ tag: "plain_text",
5880
+ content: opt.label
5881
+ },
5882
+ type: "default",
5883
+ behaviors: [{
5884
+ type: "callback",
5885
+ value: {
5886
+ action: "ask_select",
5887
+ operationId,
5888
+ phaseId: phase.id,
5889
+ value: opt.value
5890
+ }
5891
+ }]
5892
+ }));
5893
+ elements.push({
5894
+ tag: "action",
5895
+ actions: optionButtons
5896
+ });
5897
+ }
5898
+ if (phase.allowFreeText !== false) elements.push({
5899
+ tag: "markdown",
5900
+ content: "_💡 你也可以直接回复消息补充更多信息_"
5901
+ });
5902
+ const bottomActions = [];
5903
+ if (totalPhases > 1 && phaseIndex < totalPhases - 1) bottomActions.push({
5904
+ tag: "button",
5905
+ text: {
5906
+ tag: "plain_text",
5907
+ content: "跳过 →"
5908
+ },
5909
+ type: "default",
5910
+ behaviors: [{
5911
+ type: "callback",
5912
+ value: {
5913
+ action: "ask_skip",
5914
+ operationId,
5915
+ phaseId: phase.id
5916
+ }
5917
+ }]
5918
+ });
5919
+ bottomActions.push({
5920
+ tag: "button",
5921
+ text: {
5922
+ tag: "plain_text",
5923
+ content: "✓ 完成提交"
5924
+ },
5925
+ type: "primary",
5926
+ behaviors: [{
5927
+ type: "callback",
5928
+ value: {
5929
+ action: "ask_submit",
5930
+ operationId
5931
+ }
5932
+ }]
5933
+ });
5934
+ if (bottomActions.length > 0) {
5935
+ elements.push({ tag: "hr" });
5936
+ elements.push({
5937
+ tag: "action",
5938
+ actions: bottomActions
5939
+ });
5940
+ }
5941
+ const card = {
5942
+ schema: "2.0",
5943
+ config: {
5944
+ wide_screen_mode: true,
5945
+ update_multi: true
5946
+ },
5947
+ header: {
5948
+ template: "blue",
5949
+ title: {
5950
+ tag: "plain_text",
5951
+ content: "💬 需要补充信息"
5952
+ }
5953
+ },
5954
+ body: { elements }
5955
+ };
5956
+ pendingOperations.set(operationId, {
5957
+ type: "ask",
5958
+ operationId,
5959
+ chatId: ctx.chatId,
5960
+ sessionId: ctx.sessionId,
5961
+ createdAt: Date.now(),
5962
+ askContext: {
5963
+ phases: ctx.phases,
5964
+ currentPhase: phaseIndex,
5965
+ answers: {}
5966
+ }
5967
+ });
5968
+ log$19.info("已创建提问卡片操作", {
5969
+ operationId,
5970
+ phaseId: phase.id,
5971
+ phaseIndex,
5972
+ totalPhases,
5973
+ sessionId: ctx.sessionId
5974
+ });
5975
+ return {
5976
+ card,
5977
+ operationId
5978
+ };
5979
+ }
5980
+ /** CC 输出中的授权请求标记格式 */
5981
+ const AUTH_REQUEST_PATTERN = /\[PERMISSION_REQUEST\]\s*```json\s*(\{[\s\S]*?\})\s*```/;
5982
+ /** CC 输出中的提问请求标记格式 */
5983
+ const ASK_USER_PATTERN = /\[ASK_USER\]\s*```json\s*(\{[\s\S]*?\})\s*```/;
5984
+ /**
5985
+ * 从 CC 回复文本中检测是否包含授权请求标记
5986
+ */
5987
+ function detectAuthRequest(text) {
5988
+ const match = text.match(AUTH_REQUEST_PATTERN);
5989
+ if (!match) return null;
5990
+ try {
5991
+ const data = JSON.parse(match[1]);
5992
+ if (Array.isArray(data.scopes) && data.scopes.length > 0) return { scopes: data.scopes };
5993
+ } catch {}
5994
+ return null;
5995
+ }
5996
+ /**
5997
+ * 从 CC 回复文本中检测是否包含提问请求标记
5998
+ */
5999
+ function detectAskRequest(text) {
6000
+ const match = text.match(ASK_USER_PATTERN);
6001
+ if (!match) return null;
6002
+ try {
6003
+ const data = JSON.parse(match[1]);
6004
+ if (typeof data.question === "string" && Array.isArray(data.phases) && data.phases.length > 0) return {
6005
+ question: data.question,
6006
+ phases: data.phases
6007
+ };
6008
+ } catch {}
6009
+ return null;
6010
+ }
6011
+ /**
6012
+ * 从 CC 回复文本中移除标记部分(避免展示给用户)
6013
+ */
6014
+ function stripInteractiveMarkers(text) {
6015
+ return text.replace(AUTH_REQUEST_PATTERN, "").replace(ASK_USER_PATTERN, "").trim();
6016
+ }
6017
+ //#endregion
5619
6018
  //#region src/card/reasoning-utils.ts
5620
6019
  /**
5621
6020
  * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
@@ -5874,14 +6273,14 @@ function sortTraceValue(value) {
5874
6273
  }
5875
6274
  //#endregion
5876
6275
  //#region src/card/cc-stream-bridge.ts
5877
- const log$19 = larkLogger("card/cc-stream-bridge");
6276
+ const log$18 = larkLogger("card/cc-stream-bridge");
5878
6277
  const CC_INTERNAL_PLACEHOLDER = "No response requested.";
5879
6278
  /**
5880
6279
  * CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
5881
6280
  *
5882
6281
  * 内部维护文本累积状态,将增量事件转换为控制器需要的累积文本回调。
5883
6282
  */
5884
- var CCStreamBridge = class {
6283
+ var CCStreamBridge = class CCStreamBridge {
5885
6284
  /** 累积的文本输出(textDelta 逐步拼接) */
5886
6285
  accumulatedText = "";
5887
6286
  /** 累积的思考输出(thinkingDelta 逐步拼接) */
@@ -5899,24 +6298,34 @@ var CCStreamBridge = class {
5899
6298
  sessionKey: options?.sessionKey
5900
6299
  };
5901
6300
  if (this.options.sessionKey) startToolUseTraceRun(this.options.sessionKey);
5902
- log$19.info("CCStreamBridge 初始化", {
6301
+ log$18.info("CCStreamBridge 初始化", {
5903
6302
  autoCompleteOnTurnEnd: this.options.autoCompleteOnTurnEnd,
5904
6303
  sessionKey: this.options.sessionKey ?? "(none)"
5905
6304
  });
5906
6305
  }
5907
- /** 文本增量 累积后调用 controller.onPartialReply */
6306
+ /** 交互式标记前缀 用于流式过滤 */
6307
+ static INTERACTIVE_MARKER_PREFIXES = ["[ASK_USER]", "[PERMISSION_REQUEST]"];
6308
+ /** 文本增量 → 累积后调用 controller.onPartialReply(实时过滤交互式标记) */
5908
6309
  onTextDelta(text) {
5909
6310
  this.accumulatedText += text;
5910
- log$19.debug("textDelta 事件", {
6311
+ log$18.debug("textDelta 事件", {
5911
6312
  deltaLen: text.length,
5912
6313
  totalLen: this.accumulatedText.length
5913
6314
  });
5914
- this.controller.onPartialReply({ text: this.accumulatedText });
6315
+ let displayText = this.accumulatedText;
6316
+ for (const prefix of CCStreamBridge.INTERACTIVE_MARKER_PREFIXES) {
6317
+ const idx = displayText.indexOf(prefix);
6318
+ if (idx !== -1) {
6319
+ displayText = displayText.substring(0, idx).trimEnd();
6320
+ break;
6321
+ }
6322
+ }
6323
+ this.controller.onPartialReply({ text: displayText });
5915
6324
  }
5916
6325
  /** 思考增量 → 累积后调用 controller.onReasoningStream */
5917
6326
  onThinkingDelta(text) {
5918
6327
  this.accumulatedThinkingText += text;
5919
- log$19.debug("thinkingDelta 事件", {
6328
+ log$18.debug("thinkingDelta 事件", {
5920
6329
  deltaLen: text.length,
5921
6330
  totalLen: this.accumulatedThinkingText.length
5922
6331
  });
@@ -5928,7 +6337,7 @@ var CCStreamBridge = class {
5928
6337
  const displayName = getToolDisplayName(toolName);
5929
6338
  const toolParams = typeof _toolInput === "object" && _toolInput !== null ? _toolInput : void 0;
5930
6339
  const hasParams = toolParams && Object.keys(toolParams).length > 0;
5931
- log$19.info("toolUseStart 事件", {
6340
+ log$18.info("toolUseStart 事件", {
5932
6341
  toolName,
5933
6342
  displayName,
5934
6343
  activeToolsCount: this.activeTools.size,
@@ -5941,7 +6350,7 @@ var CCStreamBridge = class {
5941
6350
  toolName,
5942
6351
  toolParams
5943
6352
  })) {
5944
- log$19.info("toolUseStart: 更新已有步骤的 params", { toolName });
6353
+ log$18.info("toolUseStart: 更新已有步骤的 params", { toolName });
5945
6354
  this.controller.onToolStart({
5946
6355
  name: toolName,
5947
6356
  phase: "start"
@@ -5965,7 +6374,7 @@ var CCStreamBridge = class {
5965
6374
  const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
5966
6375
  const displayName = getToolDisplayName(toolName);
5967
6376
  this.activeTools.delete(toolUseId);
5968
- log$19.info("toolResult 事件", {
6377
+ log$18.info("toolResult 事件", {
5969
6378
  toolUseId,
5970
6379
  toolName,
5971
6380
  displayName,
@@ -5980,7 +6389,7 @@ var CCStreamBridge = class {
5980
6389
  }
5981
6390
  /** 工具执行进度 → 记录日志 */
5982
6391
  onToolProgress(toolName, elapsedSeconds) {
5983
- log$19.debug("toolProgress 事件", {
6392
+ log$18.debug("toolProgress 事件", {
5984
6393
  toolName,
5985
6394
  displayName: getToolDisplayName(toolName),
5986
6395
  elapsedSeconds
@@ -5988,7 +6397,7 @@ var CCStreamBridge = class {
5988
6397
  }
5989
6398
  /** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
5990
6399
  onTurnEnd(stopReason) {
5991
- log$19.info("turnEnd 事件", {
6400
+ log$18.info("turnEnd 事件", {
5992
6401
  stopReason,
5993
6402
  accumulatedTextLen: this.accumulatedText.length,
5994
6403
  accumulatedThinkingTextLen: this.accumulatedThinkingText.length
@@ -5996,7 +6405,7 @@ var CCStreamBridge = class {
5996
6405
  if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
5997
6406
  const trimmedText = this.accumulatedText.trim();
5998
6407
  if (trimmedText === CC_INTERNAL_PLACEHOLDER) {
5999
- log$19.info("检测到 CC 内部占位消息,静默丢弃", {
6408
+ log$18.info("检测到 CC 内部占位消息,静默丢弃", {
6000
6409
  text: trimmedText.slice(0, 50),
6001
6410
  sessionKey: this.options.sessionKey
6002
6411
  });
@@ -6004,16 +6413,26 @@ var CCStreamBridge = class {
6004
6413
  return;
6005
6414
  }
6006
6415
  if (trimmedText === "") {
6007
- log$19.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
6416
+ log$18.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
6008
6417
  return;
6009
6418
  }
6419
+ const strippedText = stripInteractiveMarkers(trimmedText);
6420
+ if (strippedText !== trimmedText) {
6421
+ log$18.info("turnEnd: 检测到交互式卡片标记,修剪卡片文本", {
6422
+ originalLen: trimmedText.length,
6423
+ strippedLen: strippedText.length,
6424
+ sessionKey: this.options.sessionKey
6425
+ });
6426
+ this.accumulatedText = strippedText;
6427
+ if (strippedText) this.controller.onPartialReply({ text: strippedText });
6428
+ }
6010
6429
  this.controller.markFullyComplete();
6011
6430
  this.controller.onIdle();
6012
6431
  }
6013
6432
  }
6014
6433
  /** 最终结果 → 兜底最终化 + 错误处理 */
6015
6434
  onResult(data) {
6016
- log$19.info("result 事件", {
6435
+ log$18.info("result 事件", {
6017
6436
  subtype: data.subtype,
6018
6437
  isError: data.isError,
6019
6438
  durationMs: data.durationMs,
@@ -6025,7 +6444,7 @@ var CCStreamBridge = class {
6025
6444
  const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
6026
6445
  const sessionId = this.options.sessionKey;
6027
6446
  if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
6028
- log$19.info("用户主动中断,按正常完成处理", {
6447
+ log$18.info("用户主动中断,按正常完成处理", {
6029
6448
  subtype: data.subtype,
6030
6449
  sessionId
6031
6450
  });
@@ -6034,14 +6453,14 @@ var CCStreamBridge = class {
6034
6453
  this.controller.onIdle();
6035
6454
  } else {
6036
6455
  const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
6037
- log$19.error("CC 执行返回错误", {
6456
+ log$18.error("CC 执行返回错误", {
6038
6457
  subtype: data.subtype,
6039
6458
  errorMessage: errorMessage.slice(0, 200)
6040
6459
  });
6041
6460
  this.controller.onError(new Error(errorMessage), { kind: "cc-execution" });
6042
6461
  }
6043
6462
  } else if (!this.accumulatedText.trim()) if (data.result) {
6044
- log$19.info("result 兜底:使用 result.result 作为最终回复", {
6463
+ log$18.info("result 兜底:使用 result.result 作为最终回复", {
6045
6464
  resultLen: data.result.length,
6046
6465
  sessionKey: this.options.sessionKey
6047
6466
  });
@@ -6049,7 +6468,7 @@ var CCStreamBridge = class {
6049
6468
  this.controller.markFullyComplete();
6050
6469
  this.controller.onIdle();
6051
6470
  } else {
6052
- log$19.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
6471
+ log$18.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
6053
6472
  this.controller.abortCard();
6054
6473
  }
6055
6474
  else {
@@ -6063,7 +6482,7 @@ var CCStreamBridge = class {
6063
6482
  * 内部复用上面的直接调用方法。
6064
6483
  */
6065
6484
  bindParser(parser) {
6066
- log$19.info("绑定 CCStreamParser 事件到卡片控制器");
6485
+ log$18.info("绑定 CCStreamParser 事件到卡片控制器");
6067
6486
  parser.on("textDelta", (text) => this.onTextDelta(text));
6068
6487
  parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
6069
6488
  parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
@@ -6071,7 +6490,7 @@ var CCStreamBridge = class {
6071
6490
  parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
6072
6491
  parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
6073
6492
  parser.on("result", (data) => this.onResult(data));
6074
- log$19.info("CCStreamParser 事件绑定完成");
6493
+ log$18.info("CCStreamParser 事件绑定完成");
6075
6494
  }
6076
6495
  };
6077
6496
  //#endregion
@@ -8087,7 +8506,7 @@ function resolveLarkSdk(cfg, accountId) {
8087
8506
  if (cached) return cached.sdk;
8088
8507
  return LarkClient.fromCfg(cfg, accountId).sdk;
8089
8508
  }
8090
- const log$18 = larkLogger("card/cardkit");
8509
+ const log$17 = larkLogger("card/cardkit");
8091
8510
  /**
8092
8511
  * 记录 CardKit API 响应日志,检测错误码并抛出异常。
8093
8512
  *
@@ -8097,13 +8516,13 @@ const log$18 = larkLogger("card/cardkit");
8097
8516
  function logCardKitResponse(params) {
8098
8517
  const { resp, api, context } = params;
8099
8518
  const { code, msg } = resp;
8100
- log$18.info(`cardkit ${api} response`, {
8519
+ log$17.info(`cardkit ${api} response`, {
8101
8520
  code,
8102
8521
  msg,
8103
8522
  context
8104
8523
  });
8105
8524
  if (code && code !== 0) {
8106
- log$18.warn(`cardkit ${api} FAILED`, {
8525
+ log$17.warn(`cardkit ${api} FAILED`, {
8107
8526
  code,
8108
8527
  msg,
8109
8528
  context,
@@ -8514,7 +8933,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
8514
8933
  * Feishu messages, uploading media to the Feishu IM storage, and
8515
8934
  * sending image / file messages to chats.
8516
8935
  */
8517
- const log$17 = larkLogger("outbound/media");
8936
+ const log$16 = larkLogger("outbound/media");
8518
8937
  /**
8519
8938
  * Upload an image to Feishu IM storage.
8520
8939
  *
@@ -8582,7 +9001,7 @@ async function validateRemoteUrl(raw) {
8582
9001
  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}"`);
8583
9002
  } catch (err) {
8584
9003
  if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
8585
- log$17.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
9004
+ log$16.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
8586
9005
  }
8587
9006
  }
8588
9007
  /**
@@ -8600,21 +9019,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
8600
9019
  if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
8601
9020
  else throw new Error(`[feishu-media] Local file access denied for "${filePath}": mediaLocalRoots is not configured. Configure mediaLocalRoots to explicitly allow local file access.`);
8602
9021
  const buf = fs.readFileSync(filePath);
8603
- log$17.debug(`local file read: "${filePath}", ${buf.length} bytes`);
9022
+ log$16.debug(`local file read: "${filePath}", ${buf.length} bytes`);
8604
9023
  return buf;
8605
9024
  }
8606
9025
  await validateRemoteUrl(raw);
8607
9026
  const FETCH_TIMEOUT_MS = 3e4;
8608
- log$17.info(`fetching remote media: ${raw}`);
9027
+ log$16.info(`fetching remote media: ${raw}`);
8609
9028
  const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
8610
9029
  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.`);
8611
9030
  const arrayBuffer = await response.arrayBuffer();
8612
- log$17.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
9031
+ log$16.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
8613
9032
  return Buffer.from(arrayBuffer);
8614
9033
  }
8615
9034
  //#endregion
8616
9035
  //#region src/card/image-resolver.ts
8617
- const log$16 = larkLogger("card/image-resolver");
9036
+ const log$15 = larkLogger("card/image-resolver");
8618
9037
  /** Matches complete markdown image syntax: `![alt](value)` */
8619
9038
  const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
8620
9039
  var ImageResolver = class {
@@ -8660,14 +9079,14 @@ var ImageResolver = class {
8660
9079
  async resolveImagesAwait(text, timeoutMs) {
8661
9080
  this.resolveImages(text);
8662
9081
  if (this.pending.size > 0) {
8663
- log$16.info("resolveImagesAwait: waiting for uploads", {
9082
+ log$15.info("resolveImagesAwait: waiting for uploads", {
8664
9083
  count: this.pending.size,
8665
9084
  timeoutMs
8666
9085
  });
8667
9086
  const allUploads = Promise.all(this.pending.values());
8668
9087
  const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
8669
9088
  await Promise.race([allUploads, timeout]);
8670
- if (this.pending.size > 0) log$16.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
9089
+ if (this.pending.size > 0) log$15.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
8671
9090
  }
8672
9091
  return this.resolveImages(text);
8673
9092
  }
@@ -8677,7 +9096,7 @@ var ImageResolver = class {
8677
9096
  }
8678
9097
  async doUpload(url) {
8679
9098
  try {
8680
- log$16.info("uploading image", { url });
9099
+ log$15.info("uploading image", { url });
8681
9100
  const buffer = await fetchRemoteImageBuffer(url);
8682
9101
  const { imageKey } = await uploadImageLark({
8683
9102
  cfg: this.cfg,
@@ -8685,7 +9104,7 @@ var ImageResolver = class {
8685
9104
  imageType: "message",
8686
9105
  accountId: this.accountId
8687
9106
  });
8688
- log$16.info("image uploaded", {
9107
+ log$15.info("image uploaded", {
8689
9108
  url,
8690
9109
  imageKey
8691
9110
  });
@@ -8694,7 +9113,7 @@ var ImageResolver = class {
8694
9113
  this.onImageResolved();
8695
9114
  return imageKey;
8696
9115
  } catch (err) {
8697
- log$16.warn("image upload failed", {
9116
+ log$15.warn("image upload failed", {
8698
9117
  url,
8699
9118
  error: String(err)
8700
9119
  });
@@ -8715,7 +9134,7 @@ var ImageResolver = class {
8715
9134
  * Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
8716
9135
  * logic previously scattered as closures in reply-dispatcher.ts.
8717
9136
  */
8718
- const log$15 = larkLogger("card/unavailable-guard");
9137
+ const log$14 = larkLogger("card/unavailable-guard");
8719
9138
  var UnavailableGuard = class {
8720
9139
  terminated = false;
8721
9140
  replyToMessageId;
@@ -8768,7 +9187,7 @@ var UnavailableGuard = class {
8768
9187
  this.terminated = true;
8769
9188
  this.onTerminate();
8770
9189
  const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
8771
- log$15.warn("reply pipeline terminated by unavailable message", {
9190
+ log$14.warn("reply pipeline terminated by unavailable message", {
8772
9191
  source,
8773
9192
  apiCode,
8774
9193
  messageId: affectedMessageId
@@ -8805,7 +9224,7 @@ function getActiveCard(sessionId) {
8805
9224
  * Delegates throttling to FlushController and message-unavailable
8806
9225
  * detection to UnavailableGuard.
8807
9226
  */
8808
- const log$14 = larkLogger("card/streaming");
9227
+ const log$13 = larkLogger("card/streaming");
8809
9228
  var StreamingCardController = class StreamingCardController {
8810
9229
  phase = "idle";
8811
9230
  cardKit = {
@@ -8893,7 +9312,7 @@ var StreamingCardController = class StreamingCardController {
8893
9312
  }
8894
9313
  }
8895
9314
  if (!entry) {
8896
- log$14.debug("footer metrics lookup: session entry missing", {
9315
+ log$13.debug("footer metrics lookup: session entry missing", {
8897
9316
  sessionKey: this.deps.sessionKey,
8898
9317
  candidateKeys,
8899
9318
  storePath,
@@ -8911,7 +9330,7 @@ var StreamingCardController = class StreamingCardController {
8911
9330
  contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
8912
9331
  model: typeof entry.model === "string" ? entry.model : void 0
8913
9332
  };
8914
- log$14.debug("footer metrics lookup: session entry found", {
9333
+ log$13.debug("footer metrics lookup: session entry found", {
8915
9334
  sessionKey: this.deps.sessionKey,
8916
9335
  matchedKey,
8917
9336
  storePath,
@@ -8936,7 +9355,7 @@ var StreamingCardController = class StreamingCardController {
8936
9355
  }
8937
9356
  }
8938
9357
  if (!entry) {
8939
- log$14.debug("footer metrics lookup: session entry missing", {
9358
+ log$13.debug("footer metrics lookup: session entry missing", {
8940
9359
  sessionKey: this.deps.sessionKey,
8941
9360
  candidateKeys,
8942
9361
  storePath,
@@ -8954,7 +9373,7 @@ var StreamingCardController = class StreamingCardController {
8954
9373
  contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
8955
9374
  model: typeof entry.model === "string" ? entry.model : void 0
8956
9375
  };
8957
- log$14.debug("footer metrics lookup: session entry found", {
9376
+ log$13.debug("footer metrics lookup: session entry found", {
8958
9377
  sessionKey: this.deps.sessionKey,
8959
9378
  matchedKey,
8960
9379
  storePath,
@@ -8962,7 +9381,7 @@ var StreamingCardController = class StreamingCardController {
8962
9381
  });
8963
9382
  return metrics;
8964
9383
  } catch (err) {
8965
- log$14.warn("footer metrics lookup failed", {
9384
+ log$13.warn("footer metrics lookup failed", {
8966
9385
  error: String(err),
8967
9386
  sessionKey: this.deps.sessionKey
8968
9387
  });
@@ -9069,7 +9488,7 @@ var StreamingCardController = class StreamingCardController {
9069
9488
  const from = this.phase;
9070
9489
  if (from === to) return false;
9071
9490
  if (!PHASE_TRANSITIONS[from].has(to)) {
9072
- log$14.warn("phase transition rejected", {
9491
+ log$13.warn("phase transition rejected", {
9073
9492
  from,
9074
9493
  to,
9075
9494
  source
@@ -9077,7 +9496,7 @@ var StreamingCardController = class StreamingCardController {
9077
9496
  return false;
9078
9497
  }
9079
9498
  this.phase = to;
9080
- log$14.info("phase transition", {
9499
+ log$13.info("phase transition", {
9081
9500
  from,
9082
9501
  to,
9083
9502
  source,
@@ -9197,7 +9616,7 @@ var StreamingCardController = class StreamingCardController {
9197
9616
  this.reasoning.dirty = true;
9198
9617
  }
9199
9618
  const text = split.answerText ?? stripReasoningTags(rawText);
9200
- log$14.debug("onPartialReply", { len: text.length });
9619
+ log$13.debug("onPartialReply", { len: text.length });
9201
9620
  if (!text) return;
9202
9621
  this.captureToolUseElapsed();
9203
9622
  if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
@@ -9209,7 +9628,7 @@ var StreamingCardController = class StreamingCardController {
9209
9628
  this.text.lastPartialText = text;
9210
9629
  this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
9211
9630
  if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
9212
- log$14.debug("onPartialReply: buffering NO_REPLY prefix");
9631
+ log$13.debug("onPartialReply: buffering NO_REPLY prefix");
9213
9632
  return;
9214
9633
  }
9215
9634
  await this.ensureCardCreated();
@@ -9219,7 +9638,7 @@ var StreamingCardController = class StreamingCardController {
9219
9638
  }
9220
9639
  async onError(err, info) {
9221
9640
  if (this.guard.terminate("onError", err)) return;
9222
- log$14.error(`${info.kind} reply failed`, { error: String(err) });
9641
+ log$13.error(`${info.kind} reply failed`, { error: String(err) });
9223
9642
  this.captureToolUseElapsed();
9224
9643
  this.finalizeCard("onError", "error");
9225
9644
  await this.flush.waitForFlush();
@@ -9275,7 +9694,7 @@ var StreamingCardController = class StreamingCardController {
9275
9694
  if (this.cardKit.cardMessageId) {
9276
9695
  const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
9277
9696
  const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
9278
- if (!this.text.completedText && !this.text.accumulatedText) log$14.warn("reply completed without visible text, using empty-reply fallback");
9697
+ if (!this.text.completedText && !this.text.accumulatedText) log$13.warn("reply completed without visible text, using empty-reply fallback");
9279
9698
  const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
9280
9699
  const idleToolUseDisplay = this.computeToolUseDisplay();
9281
9700
  const terminalContent = prepareTerminalCardContent({
@@ -9303,7 +9722,7 @@ var StreamingCardController = class StreamingCardController {
9303
9722
  const rewindSessionId = this.deps.sessionKey;
9304
9723
  const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
9305
9724
  const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
9306
- log$14.info("onIdle: rewind button conditions", {
9725
+ log$13.info("onIdle: rewind button conditions", {
9307
9726
  sessionId: rewindSessionId,
9308
9727
  messageId: rewindMessageId,
9309
9728
  hasFileChanges: rewindHasFileChanges,
@@ -9316,7 +9735,7 @@ var StreamingCardController = class StreamingCardController {
9316
9735
  if (idleEffectiveCardId) {
9317
9736
  const seqBeforeClose = this.cardKit.cardKitSequence;
9318
9737
  this.cardKit.cardKitSequence += 1;
9319
- log$14.info("onIdle: closing streaming mode", {
9738
+ log$13.info("onIdle: closing streaming mode", {
9320
9739
  attempt,
9321
9740
  seqBefore: seqBeforeClose,
9322
9741
  seqAfter: this.cardKit.cardKitSequence
@@ -9330,7 +9749,7 @@ var StreamingCardController = class StreamingCardController {
9330
9749
  });
9331
9750
  const seqBeforeUpdate = this.cardKit.cardKitSequence;
9332
9751
  this.cardKit.cardKitSequence += 1;
9333
- log$14.info("onIdle: updating final card", {
9752
+ log$13.info("onIdle: updating final card", {
9334
9753
  attempt,
9335
9754
  seqBefore: seqBeforeUpdate,
9336
9755
  seqAfter: this.cardKit.cardKitSequence
@@ -9348,33 +9767,33 @@ var StreamingCardController = class StreamingCardController {
9348
9767
  card: completeCard,
9349
9768
  accountId: this.deps.accountId
9350
9769
  });
9351
- log$14.info("reply completed, card finalized", {
9770
+ log$13.info("reply completed, card finalized", {
9352
9771
  elapsedMs: this.elapsed(),
9353
9772
  isCardKit: !!idleEffectiveCardId,
9354
9773
  attempt
9355
9774
  });
9356
9775
  break;
9357
9776
  } catch (retryErr) {
9358
- log$14.warn("final card update attempt failed", {
9777
+ log$13.warn("final card update attempt failed", {
9359
9778
  attempt,
9360
9779
  maxRetries: MAX_FINAL_UPDATE_RETRIES,
9361
9780
  error: String(retryErr)
9362
9781
  });
9363
9782
  if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
9364
- else log$14.error("final card update exhausted all retries, card may remain in streaming state", {
9783
+ else log$13.error("final card update exhausted all retries, card may remain in streaming state", {
9365
9784
  cardId: idleEffectiveCardId,
9366
9785
  messageId: this.cardKit.cardMessageId
9367
9786
  });
9368
9787
  }
9369
9788
  }
9370
9789
  } catch (err) {
9371
- log$14.error("final card update failed (outer)", { error: String(err) });
9790
+ log$13.error("final card update failed (outer)", { error: String(err) });
9372
9791
  } finally {
9373
9792
  clearToolUseTraceRun(this.deps.sessionKey);
9374
9793
  }
9375
9794
  }
9376
9795
  markFullyComplete() {
9377
- log$14.debug("markFullyComplete", {
9796
+ log$13.debug("markFullyComplete", {
9378
9797
  completedTextLen: this.text.completedText.length,
9379
9798
  accumulatedTextLen: this.text.accumulatedText.length
9380
9799
  });
@@ -9413,7 +9832,7 @@ var StreamingCardController = class StreamingCardController {
9413
9832
  footerMetrics
9414
9833
  });
9415
9834
  await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
9416
- log$14.info("abortCard completed", { effectiveCardId });
9835
+ log$13.info("abortCard completed", { effectiveCardId });
9417
9836
  } else if (this.cardKit.cardMessageId) {
9418
9837
  const abortCard = buildCardContent("complete", {
9419
9838
  text: terminalContent.text,
@@ -9434,10 +9853,10 @@ var StreamingCardController = class StreamingCardController {
9434
9853
  card: abortCard,
9435
9854
  accountId: this.deps.accountId
9436
9855
  });
9437
- log$14.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
9856
+ log$13.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
9438
9857
  }
9439
9858
  } catch (err) {
9440
- log$14.warn("abortCard failed", { error: String(err) });
9859
+ log$13.warn("abortCard failed", { error: String(err) });
9441
9860
  } finally {
9442
9861
  clearToolUseTraceRun(this.deps.sessionKey);
9443
9862
  }
@@ -9452,10 +9871,10 @@ var StreamingCardController = class StreamingCardController {
9452
9871
  async forceCloseStreaming() {
9453
9872
  const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
9454
9873
  if (!effectiveCardId && !this.cardKit.cardMessageId) {
9455
- log$14.warn("forceCloseStreaming: no card to close");
9874
+ log$13.warn("forceCloseStreaming: no card to close");
9456
9875
  return;
9457
9876
  }
9458
- log$14.info("forceCloseStreaming: 强制终态化卡片", {
9877
+ log$13.info("forceCloseStreaming: 强制终态化卡片", {
9459
9878
  cardId: effectiveCardId,
9460
9879
  messageId: this.cardKit.cardMessageId,
9461
9880
  phase: this.phase
@@ -9505,10 +9924,10 @@ var StreamingCardController = class StreamingCardController {
9505
9924
  card: completeCard,
9506
9925
  accountId: this.deps.accountId
9507
9926
  });
9508
- log$14.info("forceCloseStreaming: 卡片已强制终态化");
9927
+ log$13.info("forceCloseStreaming: 卡片已强制终态化");
9509
9928
  if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
9510
9929
  } catch (err) {
9511
- log$14.error("forceCloseStreaming failed", { error: String(err) });
9930
+ log$13.error("forceCloseStreaming failed", { error: String(err) });
9512
9931
  } finally {
9513
9932
  unregisterActiveCard(this.deps.sessionKey);
9514
9933
  clearToolUseTraceRun(this.deps.sessionKey);
@@ -9532,7 +9951,7 @@ var StreamingCardController = class StreamingCardController {
9532
9951
  this.disposeShutdownHook = null;
9533
9952
  return;
9534
9953
  }
9535
- log$14.info("reusing placeholder card", {
9954
+ log$13.info("reusing placeholder card", {
9536
9955
  cardId: this.deps.placeholderCardId,
9537
9956
  messageId: this.deps.placeholderMessageId
9538
9957
  });
@@ -9556,7 +9975,7 @@ var StreamingCardController = class StreamingCardController {
9556
9975
  accountId: this.deps.accountId
9557
9976
  });
9558
9977
  if (this.isStaleCreate(epoch)) {
9559
- log$14.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
9978
+ log$13.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
9560
9979
  epoch,
9561
9980
  phase: this.phase
9562
9981
  });
@@ -9567,7 +9986,7 @@ var StreamingCardController = class StreamingCardController {
9567
9986
  this.cardKit.originalCardKitCardId = cId;
9568
9987
  this.cardKit.cardKitSequence = 1;
9569
9988
  this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
9570
- log$14.info("created CardKit entity", {
9989
+ log$13.info("created CardKit entity", {
9571
9990
  cardId: cId,
9572
9991
  initialSequence: this.cardKit.cardKitSequence
9573
9992
  });
@@ -9580,7 +9999,7 @@ var StreamingCardController = class StreamingCardController {
9580
9999
  accountId: this.deps.accountId
9581
10000
  });
9582
10001
  if (this.isStaleCreate(epoch)) {
9583
- log$14.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
10002
+ log$13.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
9584
10003
  epoch,
9585
10004
  phase: this.phase
9586
10005
  });
@@ -9595,13 +10014,13 @@ var StreamingCardController = class StreamingCardController {
9595
10014
  this.disposeShutdownHook = null;
9596
10015
  return;
9597
10016
  }
9598
- log$14.info("sent CardKit card", { messageId: result.messageId });
10017
+ log$13.info("sent CardKit card", { messageId: result.messageId });
9599
10018
  } else throw new Error("card.create returned empty card_id");
9600
10019
  } catch (cardKitErr) {
9601
10020
  if (this.isStaleCreate(epoch)) return;
9602
10021
  if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
9603
10022
  const apiDetail = extractApiDetail(cardKitErr);
9604
- log$14.warn("CardKit flow failed, falling back to IM", { apiDetail });
10023
+ log$13.warn("CardKit flow failed, falling back to IM", { apiDetail });
9605
10024
  this.cardKit.cardKitCardId = null;
9606
10025
  this.cardKit.originalCardKitCardId = null;
9607
10026
  const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
@@ -9614,7 +10033,7 @@ var StreamingCardController = class StreamingCardController {
9614
10033
  accountId: this.deps.accountId
9615
10034
  });
9616
10035
  if (this.isStaleCreate(epoch)) {
9617
- log$14.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
10036
+ log$13.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
9618
10037
  epoch,
9619
10038
  phase: this.phase
9620
10039
  });
@@ -9623,12 +10042,12 @@ var StreamingCardController = class StreamingCardController {
9623
10042
  this.cardKit.cardMessageId = result.messageId;
9624
10043
  this.flush.setCardMessageReady(true);
9625
10044
  if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
9626
- log$14.info("sent fallback IM card", { messageId: result.messageId });
10045
+ log$13.info("sent fallback IM card", { messageId: result.messageId });
9627
10046
  }
9628
10047
  } catch (err) {
9629
10048
  if (this.isStaleCreate(epoch)) return;
9630
10049
  if (this.guard.terminate("ensureCardCreated.outer", err)) return;
9631
- log$14.warn("thinking card failed, falling back to static", { error: String(err) });
10050
+ log$13.warn("thinking card failed, falling back to static", { error: String(err) });
9632
10051
  this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
9633
10052
  }
9634
10053
  })();
@@ -9637,10 +10056,10 @@ var StreamingCardController = class StreamingCardController {
9637
10056
  async performFlush() {
9638
10057
  if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
9639
10058
  if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
9640
- log$14.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
10059
+ log$13.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
9641
10060
  return;
9642
10061
  }
9643
- log$14.debug("flushCardUpdate: enter", {
10062
+ log$13.debug("flushCardUpdate: enter", {
9644
10063
  seq: this.cardKit.cardKitSequence,
9645
10064
  isCardKit: !!this.cardKit.cardKitCardId
9646
10065
  });
@@ -9662,7 +10081,7 @@ var StreamingCardController = class StreamingCardController {
9662
10081
  reasoningText: this.reasoning.accumulatedReasoningText || void 0
9663
10082
  });
9664
10083
  this.cardKit.cardKitSequence += 1;
9665
- log$14.debug("flushCardUpdate: full card update (dirty)", {
10084
+ log$13.debug("flushCardUpdate: full card update (dirty)", {
9666
10085
  seq: this.cardKit.cardKitSequence,
9667
10086
  stepCount: display?.stepCount,
9668
10087
  hasReasoning: !!this.reasoning.accumulatedReasoningText
@@ -9678,7 +10097,7 @@ var StreamingCardController = class StreamingCardController {
9678
10097
  } else if (resolvedText !== this.text.lastFlushedText) {
9679
10098
  const prevSeq = this.cardKit.cardKitSequence;
9680
10099
  this.cardKit.cardKitSequence += 1;
9681
- log$14.debug("flushCardUpdate: answer seq bump", {
10100
+ log$13.debug("flushCardUpdate: answer seq bump", {
9682
10101
  seqBefore: prevSeq,
9683
10102
  seqAfter: this.cardKit.cardKitSequence
9684
10103
  });
@@ -9693,7 +10112,7 @@ var StreamingCardController = class StreamingCardController {
9693
10112
  this.text.lastFlushedText = resolvedText;
9694
10113
  }
9695
10114
  } else {
9696
- log$14.debug("flushCardUpdate: IM patch fallback");
10115
+ log$13.debug("flushCardUpdate: IM patch fallback");
9697
10116
  const flushDisplay = this.computeToolUseDisplay();
9698
10117
  const card = buildCardContent("streaming", {
9699
10118
  text: this.reasoning.isReasoningPhase ? "" : resolvedText,
@@ -9713,31 +10132,31 @@ var StreamingCardController = class StreamingCardController {
9713
10132
  if (this.guard.terminate("flushCardUpdate", err)) return;
9714
10133
  const apiCode = extractLarkApiCode(err);
9715
10134
  if (isCardRateLimitError(err)) {
9716
- log$14.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
10135
+ log$13.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
9717
10136
  return;
9718
10137
  }
9719
10138
  if (isCardTableLimitError(err)) {
9720
- log$14.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
10139
+ log$13.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
9721
10140
  this.cardKit.cardKitCardId = null;
9722
10141
  return;
9723
10142
  }
9724
10143
  if (apiCode === 300317 && this.cardKit.cardKitCardId) {
9725
10144
  const oldSeq = this.cardKit.cardKitSequence;
9726
10145
  this.cardKit.cardKitSequence += 100;
9727
- log$14.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
10146
+ log$13.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
9728
10147
  oldSeq,
9729
10148
  newSeq: this.cardKit.cardKitSequence
9730
10149
  });
9731
10150
  return;
9732
10151
  }
9733
10152
  const apiDetail = extractApiDetail(err);
9734
- log$14.error("card stream update failed", {
10153
+ log$13.error("card stream update failed", {
9735
10154
  apiCode,
9736
10155
  seq: this.cardKit.cardKitSequence,
9737
10156
  apiDetail
9738
10157
  });
9739
10158
  if (this.cardKit.cardKitCardId) {
9740
- log$14.warn("disabling CardKit streaming, falling back to im.message.patch");
10159
+ log$13.warn("disabling CardKit streaming, falling back to im.message.patch");
9741
10160
  this.cardKit.cardKitCardId = null;
9742
10161
  }
9743
10162
  }
@@ -9762,7 +10181,7 @@ var StreamingCardController = class StreamingCardController {
9762
10181
  if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
9763
10182
  try {
9764
10183
  const display = this.computeToolUseDisplay();
9765
- log$14.info("updateToolUseStatus", {
10184
+ log$13.info("updateToolUseStatus", {
9766
10185
  hasDisplay: !!display,
9767
10186
  stepCount: display?.stepCount,
9768
10187
  steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
@@ -9784,7 +10203,7 @@ var StreamingCardController = class StreamingCardController {
9784
10203
  accountId: this.deps.accountId
9785
10204
  });
9786
10205
  } catch (err) {
9787
- log$14.debug("updateToolUseStatus failed", { error: String(err) });
10206
+ log$13.debug("updateToolUseStatus failed", { error: String(err) });
9788
10207
  }
9789
10208
  }
9790
10209
  finalizeCard(source, reason) {
@@ -9796,7 +10215,7 @@ var StreamingCardController = class StreamingCardController {
9796
10215
  async closeStreamingAndUpdate(cardId, card, label) {
9797
10216
  const seqBeforeClose = this.cardKit.cardKitSequence;
9798
10217
  this.cardKit.cardKitSequence += 1;
9799
- log$14.info(`${label}: closing streaming mode`, {
10218
+ log$13.info(`${label}: closing streaming mode`, {
9800
10219
  seqBefore: seqBeforeClose,
9801
10220
  seqAfter: this.cardKit.cardKitSequence
9802
10221
  });
@@ -9809,7 +10228,7 @@ var StreamingCardController = class StreamingCardController {
9809
10228
  });
9810
10229
  const seqBeforeUpdate = this.cardKit.cardKitSequence;
9811
10230
  this.cardKit.cardKitSequence += 1;
9812
- log$14.info(`${label}: updating card`, {
10231
+ log$13.info(`${label}: updating card`, {
9813
10232
  seqBefore: seqBeforeUpdate,
9814
10233
  seqAfter: this.cardKit.cardKitSequence
9815
10234
  });
@@ -9843,7 +10262,7 @@ function extractApiDetail(err) {
9843
10262
  }
9844
10263
  //#endregion
9845
10264
  //#region src/messaging/format-for-cc.ts
9846
- const log$13 = larkLogger("messaging/format-for-cc");
10265
+ const log$12 = larkLogger("messaging/format-for-cc");
9847
10266
  function safeParseJSON(raw) {
9848
10267
  try {
9849
10268
  return JSON.parse(raw);
@@ -9899,7 +10318,7 @@ function extractPostText(rawContent) {
9899
10318
  */
9900
10319
  function formatContentByType(ctx) {
9901
10320
  const { contentType, content, resources } = ctx;
9902
- log$13.debug("formatContentByType 开始格式化", {
10321
+ log$12.debug("formatContentByType 开始格式化", {
9903
10322
  contentType,
9904
10323
  contentLength: content?.length ?? 0,
9905
10324
  resourceCount: resources?.length ?? 0
@@ -9941,7 +10360,7 @@ function formatContentByType(ctx) {
9941
10360
  case "todo":
9942
10361
  case "vote": return content;
9943
10362
  default:
9944
- log$13.warn("遇到不支持的消息类型", { contentType });
10363
+ log$12.warn("遇到不支持的消息类型", { contentType });
9945
10364
  return `[不支持的消息类型: ${contentType}]`;
9946
10365
  }
9947
10366
  }
@@ -9984,7 +10403,7 @@ function formatMessageTime(createTime) {
9984
10403
  * @returns 格式化后的纯文本字符串
9985
10404
  */
9986
10405
  function formatForCC(ctx, isGroup) {
9987
- log$13.info("formatForCC 开始处理", {
10406
+ log$12.info("formatForCC 开始处理", {
9988
10407
  messageId: ctx.messageId,
9989
10408
  contentType: ctx.contentType,
9990
10409
  chatType: ctx.chatType,
@@ -10000,7 +10419,7 @@ function formatForCC(ctx, isGroup) {
10000
10419
  if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
10001
10420
  if (ctx.threadId) parts.push("[话题回复]");
10002
10421
  const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
10003
- log$13.info("formatForCC 完成", {
10422
+ log$12.info("formatForCC 完成", {
10004
10423
  messageId: ctx.messageId,
10005
10424
  resultLength: result.length,
10006
10425
  hasTime: !!timeStr,
@@ -10010,7 +10429,7 @@ function formatForCC(ctx, isGroup) {
10010
10429
  }
10011
10430
  //#endregion
10012
10431
  //#region src/messaging/inbound/dispatch-cc.ts
10013
- const log$12 = larkLogger("inbound/dispatch-cc");
10432
+ const log$11 = larkLogger("inbound/dispatch-cc");
10014
10433
  async function dispatchToCC(params) {
10015
10434
  const { ctx, account, sessionRouter, processManager } = params;
10016
10435
  const chatId = ctx.chatId;
@@ -10020,7 +10439,7 @@ async function dispatchToCC(params) {
10020
10439
  const ccModel = params.model;
10021
10440
  const maxTurns = params.maxTurns;
10022
10441
  const maxBudgetUsd = params.maxBudgetUsd;
10023
- log$12.info("dispatchToCC 开始", {
10442
+ log$11.info("dispatchToCC 开始", {
10024
10443
  msgId,
10025
10444
  chatId,
10026
10445
  chatType,
@@ -10033,12 +10452,12 @@ async function dispatchToCC(params) {
10033
10452
  threadId,
10034
10453
  userId: ctx.senderId
10035
10454
  });
10036
- log$12.info("会话路由解析完成", {
10455
+ log$11.info("会话路由解析完成", {
10037
10456
  sessionId: route.sessionId,
10038
10457
  cwd: route.cwd,
10039
10458
  type: route.type
10040
10459
  });
10041
- if (params.userContext) log$12.info("用户上下文已注入", {
10460
+ if (params.userContext) log$11.info("用户上下文已注入", {
10042
10461
  userId: params.userContext.userId,
10043
10462
  isTenantIdentity: params.userContext.isTenantIdentity,
10044
10463
  credentialDir: params.userContext.credentialDir
@@ -10077,7 +10496,7 @@ async function dispatchToCC(params) {
10077
10496
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
10078
10497
  buffer = Buffer.concat(chunks);
10079
10498
  } else {
10080
- log$12.warn("图片资源下载返回未知格式,跳过", {
10499
+ log$11.warn("图片资源下载返回未知格式,跳过", {
10081
10500
  fileKey: imgRes.fileKey,
10082
10501
  responseType: typeof response
10083
10502
  });
@@ -10085,7 +10504,7 @@ async function dispatchToCC(params) {
10085
10504
  }
10086
10505
  if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
10087
10506
  } else {
10088
- log$12.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
10507
+ log$11.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
10089
10508
  continue;
10090
10509
  }
10091
10510
  const imgFileName = `${imgRes.fileKey}.png`;
@@ -10095,13 +10514,13 @@ async function dispatchToCC(params) {
10095
10514
  const { writeFile, mkdir } = await import("fs/promises");
10096
10515
  await mkdir(filesDir, { recursive: true });
10097
10516
  await writeFile(imgFilePath, buffer);
10098
- log$12.info("图片已保存到工作目录", {
10517
+ log$11.info("图片已保存到工作目录", {
10099
10518
  fileKey: imgRes.fileKey,
10100
10519
  path: imgFilePath,
10101
10520
  sizeBytes: buffer.length
10102
10521
  });
10103
10522
  } catch (saveErr) {
10104
- log$12.warn("图片保存到工作目录失败", {
10523
+ log$11.warn("图片保存到工作目录失败", {
10105
10524
  fileKey: imgRes.fileKey,
10106
10525
  path: imgFilePath,
10107
10526
  error: saveErr instanceof Error ? saveErr.message : String(saveErr)
@@ -10116,13 +10535,13 @@ async function dispatchToCC(params) {
10116
10535
  data: base64Data
10117
10536
  }
10118
10537
  });
10119
- log$12.info("图片资源下载成功", {
10538
+ log$11.info("图片资源下载成功", {
10120
10539
  fileKey: imgRes.fileKey,
10121
10540
  mediaType,
10122
10541
  sizeBytes: buffer.length
10123
10542
  });
10124
10543
  } catch (err) {
10125
- log$12.warn("图片资源下载失败,跳过", {
10544
+ log$11.warn("图片资源下载失败,跳过", {
10126
10545
  fileKey: imgRes.fileKey,
10127
10546
  error: err instanceof Error ? err.message : String(err)
10128
10547
  });
@@ -10132,7 +10551,7 @@ async function dispatchToCC(params) {
10132
10551
  type: "text",
10133
10552
  text: textPrompt
10134
10553
  }, ...imageBlocks];
10135
- log$12.info("已构建多模态 prompt", {
10554
+ log$11.info("已构建多模态 prompt", {
10136
10555
  textLength: textPrompt.length,
10137
10556
  imageCount: imageBlocks.length
10138
10557
  });
@@ -10140,12 +10559,12 @@ async function dispatchToCC(params) {
10140
10559
  const textWithoutImageRefs = textPrompt.replace(/!\[image\]\([^)]*\)/g, "").replace(/\[图片\]\s*/g, "").trim();
10141
10560
  if (textWithoutImageRefs.length > 0) {
10142
10561
  prompt = textPrompt.replace(/!\[image\]\([^)]*\)/g, "[图片加载失败]");
10143
- log$12.info("图片下载失败但保留文字内容继续调度", {
10562
+ log$11.info("图片下载失败但保留文字内容继续调度", {
10144
10563
  messageId: ctx.messageId,
10145
10564
  textLength: textWithoutImageRefs.length
10146
10565
  });
10147
10566
  } else {
10148
- log$12.warn("纯图片消息且图片全部下载失败,中止调度", {
10567
+ log$11.warn("纯图片消息且图片全部下载失败,中止调度", {
10149
10568
  messageId: ctx.messageId,
10150
10569
  expectedImages: imageResources.length
10151
10570
  });
@@ -10167,7 +10586,7 @@ async function dispatchToCC(params) {
10167
10586
  await fsMkdir(filesDir, { recursive: true });
10168
10587
  let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
10169
10588
  for (const res of attachmentResources) try {
10170
- log$12.info("开始下载非图片附件", {
10589
+ log$11.info("开始下载非图片附件", {
10171
10590
  type: res.type,
10172
10591
  fileKey: res.fileKey,
10173
10592
  fileName: res.fileName,
@@ -10181,7 +10600,7 @@ async function dispatchToCC(params) {
10181
10600
  },
10182
10601
  params: { type: "file" }
10183
10602
  });
10184
- log$12.info("非图片附件 API 响应已收到", {
10603
+ log$11.info("非图片附件 API 响应已收到", {
10185
10604
  type: res.type,
10186
10605
  fileKey: res.fileKey,
10187
10606
  responseType: typeof response,
@@ -10200,7 +10619,7 @@ async function dispatchToCC(params) {
10200
10619
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
10201
10620
  buffer = Buffer.concat(chunks);
10202
10621
  } else {
10203
- log$12.warn("非图片附件下载返回未知格式,跳过", {
10622
+ log$11.warn("非图片附件下载返回未知格式,跳过", {
10204
10623
  type: res.type,
10205
10624
  fileKey: res.fileKey,
10206
10625
  responseType: typeof response
@@ -10208,7 +10627,7 @@ async function dispatchToCC(params) {
10208
10627
  continue;
10209
10628
  }
10210
10629
  } else {
10211
- log$12.warn("非图片附件下载返回空,跳过", {
10630
+ log$11.warn("非图片附件下载返回空,跳过", {
10212
10631
  type: res.type,
10213
10632
  fileKey: res.fileKey
10214
10633
  });
@@ -10231,7 +10650,7 @@ async function dispatchToCC(params) {
10231
10650
  }
10232
10651
  const filePath = path.join(filesDir, savedFileName);
10233
10652
  await fsWriteFile(filePath, buffer);
10234
- log$12.info("非图片附件已保存到工作目录", {
10653
+ log$11.info("非图片附件已保存到工作目录", {
10235
10654
  type: res.type,
10236
10655
  fileKey: res.fileKey,
10237
10656
  savedFileName,
@@ -10265,7 +10684,7 @@ async function dispatchToCC(params) {
10265
10684
  }
10266
10685
  }
10267
10686
  } catch (err) {
10268
- log$12.warn("非图片附件下载失败,替换为降级描述", {
10687
+ log$11.warn("非图片附件下载失败,替换为降级描述", {
10269
10688
  type: res.type,
10270
10689
  fileKey: res.fileKey,
10271
10690
  error: err instanceof Error ? err.message : String(err)
@@ -10284,12 +10703,12 @@ async function dispatchToCC(params) {
10284
10703
  const textBlock = prompt.find((b) => b.type === "text");
10285
10704
  if (textBlock) textBlock.text = updatedTextPrompt;
10286
10705
  }
10287
- log$12.info("非图片附件处理完成", {
10706
+ log$11.info("非图片附件处理完成", {
10288
10707
  totalAttachments: attachmentResources.length,
10289
10708
  promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
10290
10709
  });
10291
10710
  }
10292
- log$12.info("消息格式化完成", {
10711
+ log$11.info("消息格式化完成", {
10293
10712
  promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
10294
10713
  isMultimodal: Array.isArray(prompt),
10295
10714
  isGroup
@@ -10321,13 +10740,13 @@ async function dispatchToCC(params) {
10321
10740
  };
10322
10741
  const cardController = new StreamingCardController(cardDeps);
10323
10742
  registerActiveCard(route.sessionId, cardController);
10324
- log$12.info("StreamingCardController 已创建", {
10743
+ log$11.info("StreamingCardController 已创建", {
10325
10744
  sessionId: route.sessionId,
10326
10745
  chatId,
10327
10746
  replyToMessageId: cardDeps.replyToMessageId
10328
10747
  });
10329
10748
  cardController.ensureCardCreated().catch((err) => {
10330
- log$12.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
10749
+ log$11.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
10331
10750
  });
10332
10751
  if (params.messageStore) {
10333
10752
  const userContent = typeof prompt === "string" ? prompt : textPrompt;
@@ -10338,7 +10757,7 @@ async function dispatchToCC(params) {
10338
10757
  channel: "feishu-bot",
10339
10758
  title: userContent.slice(0, 30) + (userContent.length > 30 ? "..." : "")
10340
10759
  }).catch((err) => {
10341
- log$12.warn("创建飞书端会话记录失败", { error: String(err) });
10760
+ log$11.warn("创建飞书端会话记录失败", { error: String(err) });
10342
10761
  });
10343
10762
  params.messageStore.appendMessage({
10344
10763
  sessionId: storeSessionId,
@@ -10347,18 +10766,18 @@ async function dispatchToCC(params) {
10347
10766
  channel: "feishu-bot",
10348
10767
  metadata: { feishuMessageId: msgId }
10349
10768
  }).catch((err) => {
10350
- log$12.warn("记录飞书用户消息到 store 失败", { error: String(err) });
10769
+ log$11.warn("记录飞书用户消息到 store 失败", { error: String(err) });
10351
10770
  });
10352
10771
  }
10353
10772
  const bridge = new CCStreamBridge(cardController, {
10354
10773
  autoCompleteOnTurnEnd: true,
10355
10774
  sessionKey: route.sessionId
10356
10775
  });
10357
- log$12.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
10776
+ log$11.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
10358
10777
  let feishuAccumText = "";
10359
10778
  const callbacks = {
10360
10779
  onTextDelta: (text) => {
10361
- log$12.debug("CC onTextDelta", {
10780
+ log$11.debug("CC onTextDelta", {
10362
10781
  sessionId: route.sessionId,
10363
10782
  deltaLen: text.length
10364
10783
  });
@@ -10366,28 +10785,28 @@ async function dispatchToCC(params) {
10366
10785
  feishuAccumText += text;
10367
10786
  },
10368
10787
  onThinkingDelta: (text) => {
10369
- log$12.debug("CC onThinkingDelta", {
10788
+ log$11.debug("CC onThinkingDelta", {
10370
10789
  sessionId: route.sessionId,
10371
10790
  deltaLen: text.length
10372
10791
  });
10373
10792
  bridge.onThinkingDelta(text);
10374
10793
  },
10375
10794
  onToolUseStart: (toolName, toolInput) => {
10376
- log$12.info("CC onToolUseStart", {
10795
+ log$11.info("CC onToolUseStart", {
10377
10796
  sessionId: route.sessionId,
10378
10797
  toolName
10379
10798
  });
10380
10799
  bridge.onToolUseStart(toolName, toolInput);
10381
10800
  },
10382
10801
  onToolResult: (toolUseId) => {
10383
- log$12.info("CC onToolResult", {
10802
+ log$11.info("CC onToolResult", {
10384
10803
  sessionId: route.sessionId,
10385
10804
  toolUseId
10386
10805
  });
10387
10806
  bridge.onToolResult(toolUseId);
10388
10807
  },
10389
10808
  onToolProgress: (toolName, elapsedSeconds) => {
10390
- log$12.debug("CC onToolProgress", {
10809
+ log$11.debug("CC onToolProgress", {
10391
10810
  sessionId: route.sessionId,
10392
10811
  toolName,
10393
10812
  elapsedSeconds
@@ -10395,14 +10814,14 @@ async function dispatchToCC(params) {
10395
10814
  bridge.onToolProgress(toolName, elapsedSeconds);
10396
10815
  },
10397
10816
  onTurnEnd: (stopReason) => {
10398
- log$12.info("CC onTurnEnd", {
10817
+ log$11.info("CC onTurnEnd", {
10399
10818
  sessionId: route.sessionId,
10400
10819
  stopReason
10401
10820
  });
10402
10821
  bridge.onTurnEnd(stopReason);
10403
10822
  },
10404
10823
  onResult: (result) => {
10405
- log$12.info("CC onResult", {
10824
+ log$11.info("CC onResult", {
10406
10825
  sessionId: route.sessionId,
10407
10826
  subtype: result.subtype,
10408
10827
  isError: result.isError,
@@ -10422,18 +10841,28 @@ async function dispatchToCC(params) {
10422
10841
  numTurns: result.numTurns
10423
10842
  }
10424
10843
  }).catch((err) => {
10425
- log$12.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
10844
+ log$11.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
10845
+ });
10846
+ if (feishuAccumText) handleInteractiveCardTriggers({
10847
+ text: feishuAccumText,
10848
+ chatId,
10849
+ sessionId: route.sessionId,
10850
+ appId: account.configured ? account.appId : "",
10851
+ brand: account.brand,
10852
+ account,
10853
+ replyInThread,
10854
+ threadId: ctx.threadId
10426
10855
  });
10427
10856
  },
10428
10857
  onError: (error) => {
10429
- log$12.error("CC 进程错误", {
10858
+ log$11.error("CC 进程错误", {
10430
10859
  sessionId: route.sessionId,
10431
10860
  error: error.message
10432
10861
  });
10433
10862
  cardController.onError(error, { kind: "cc-process" });
10434
10863
  }
10435
10864
  };
10436
- log$12.info("开始执行 CC 进程", {
10865
+ log$11.info("开始执行 CC 进程", {
10437
10866
  sessionId: route.sessionId,
10438
10867
  cwd: route.cwd,
10439
10868
  promptLength: prompt.length,
@@ -10450,10 +10879,10 @@ async function dispatchToCC(params) {
10450
10879
  maxBudgetUsd,
10451
10880
  userContext: params.userContext
10452
10881
  }, callbacks);
10453
- log$12.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
10882
+ log$11.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
10454
10883
  } catch (err) {
10455
10884
  const errorMessage = err instanceof Error ? err.message : String(err);
10456
- log$12.error("CC 进程 executePrompt 调用异常", {
10885
+ log$11.error("CC 进程 executePrompt 调用异常", {
10457
10886
  sessionId: route.sessionId,
10458
10887
  error: errorMessage
10459
10888
  });
@@ -10475,7 +10904,7 @@ async function dispatchToCC(params) {
10475
10904
  */
10476
10905
  async function dispatchTeammateEval(params) {
10477
10906
  const { chatId, prompt, account, sessionRouter, processManager } = params;
10478
- log$12.info("dispatchTeammateEval 开始", {
10907
+ log$11.info("dispatchTeammateEval 开始", {
10479
10908
  chatId,
10480
10909
  promptLength: prompt.length
10481
10910
  });
@@ -10488,7 +10917,7 @@ async function dispatchTeammateEval(params) {
10488
10917
  userName: void 0
10489
10918
  });
10490
10919
  if (processManager.isSessionBusy?.(route.sessionId)) {
10491
- log$12.info("dispatchTeammateEval 跳过:主 session 正在执行", {
10920
+ log$11.info("dispatchTeammateEval 跳过:主 session 正在执行", {
10492
10921
  chatId,
10493
10922
  sessionId: route.sessionId
10494
10923
  });
@@ -10521,7 +10950,7 @@ async function dispatchTeammateEval(params) {
10521
10950
  const confirmReply = () => {
10522
10951
  if (confirmed) return;
10523
10952
  confirmed = true;
10524
- log$12.info("teammate 确认回复,创建流式卡片", {
10953
+ log$11.info("teammate 确认回复,创建流式卡片", {
10525
10954
  chatId,
10526
10955
  thinkingLen: thinkingBuffer.length,
10527
10956
  textLen: fullTextBuffer.length
@@ -10578,7 +11007,7 @@ async function dispatchTeammateEval(params) {
10578
11007
  fullTextBuffer += text;
10579
11008
  if (fullTextBuffer.trimStart().startsWith(NO_REPLY_TOKEN)) {
10580
11009
  silenced = true;
10581
- log$12.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
11010
+ log$11.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
10582
11011
  chatId,
10583
11012
  bufferLen: fullTextBuffer.length
10584
11013
  });
@@ -10623,7 +11052,7 @@ async function dispatchTeammateEval(params) {
10623
11052
  onTurnEnd: (stopReason) => {
10624
11053
  finalStopReason = stopReason;
10625
11054
  if (stopReason === "tool_use") {
10626
- log$12.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
11055
+ log$11.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
10627
11056
  if (confirmed && bridge) bridge.onTurnEnd(stopReason);
10628
11057
  return;
10629
11058
  }
@@ -10631,7 +11060,7 @@ async function dispatchTeammateEval(params) {
10631
11060
  const trimmed = fullTextBuffer.trim();
10632
11061
  if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
10633
11062
  silenced = true;
10634
- log$12.info("teammate turnEnd 最终判断: 静默", {
11063
+ log$11.info("teammate turnEnd 最终判断: 静默", {
10635
11064
  chatId,
10636
11065
  trimmed: trimmed.slice(0, 60)
10637
11066
  });
@@ -10646,7 +11075,7 @@ async function dispatchTeammateEval(params) {
10646
11075
  resolve();
10647
11076
  },
10648
11077
  onError: (error) => {
10649
- log$12.error("teammate 评估 CC 进程错误", {
11078
+ log$11.error("teammate 评估 CC 进程错误", {
10650
11079
  chatId,
10651
11080
  error: error.message
10652
11081
  });
@@ -10659,14 +11088,14 @@ async function dispatchTeammateEval(params) {
10659
11088
  prompt,
10660
11089
  model: params.model
10661
11090
  }, callbacks).catch((err) => {
10662
- log$12.error("teammate executePrompt 异常", {
11091
+ log$11.error("teammate executePrompt 异常", {
10663
11092
  chatId,
10664
11093
  error: err instanceof Error ? err.message : String(err)
10665
11094
  });
10666
11095
  resolve();
10667
11096
  });
10668
11097
  });
10669
- log$12.info("dispatchTeammateEval 完成", {
11098
+ log$11.info("dispatchTeammateEval 完成", {
10670
11099
  chatId,
10671
11100
  confirmed,
10672
11101
  silenced,
@@ -10677,13 +11106,83 @@ async function dispatchTeammateEval(params) {
10677
11106
  return { replied: confirmed };
10678
11107
  } catch (err) {
10679
11108
  const errorMessage = err instanceof Error ? err.message : String(err);
10680
- log$12.error("dispatchTeammateEval 异常", {
11109
+ log$11.error("dispatchTeammateEval 异常", {
10681
11110
  chatId,
10682
11111
  error: errorMessage
10683
11112
  });
10684
11113
  return { replied: false };
10685
11114
  }
10686
11115
  }
11116
+ /**
11117
+ * 检测 CC 回复文本中的交互标记,并发送对应的独立卡片。
11118
+ *
11119
+ * 标记格式:
11120
+ * - [PERMISSION_REQUEST] ```json { "scopes": ["scope1", "scope2"] } ```
11121
+ * - [ASK_USER] ```json { "question": "...", "phases": [...] } ```
11122
+ */
11123
+ async function handleInteractiveCardTriggers(params) {
11124
+ const { text, chatId, sessionId, appId, brand, account, replyInThread, threadId } = params;
11125
+ try {
11126
+ const authReq = detectAuthRequest(text);
11127
+ if (authReq && appId) {
11128
+ log$11.info("检测到 CC 输出中的权限申请标记", {
11129
+ sessionId,
11130
+ scopes: authReq.scopes
11131
+ });
11132
+ const { card } = buildAuthCard({
11133
+ chatId,
11134
+ sessionId,
11135
+ appId,
11136
+ scopes: authReq.scopes,
11137
+ brand
11138
+ });
11139
+ await LarkClient.fromAccount(account).sdk.im.message.create({
11140
+ params: { receive_id_type: "chat_id" },
11141
+ data: {
11142
+ receive_id: chatId,
11143
+ msg_type: "interactive",
11144
+ content: JSON.stringify(card),
11145
+ ...replyInThread && threadId ? { reply_in_thread: true } : {}
11146
+ }
11147
+ });
11148
+ log$11.info("权限申请卡片已发送", {
11149
+ chatId,
11150
+ sessionId
11151
+ });
11152
+ }
11153
+ const askReq = detectAskRequest(text);
11154
+ if (askReq) {
11155
+ log$11.info("检测到 CC 输出中的提问标记", {
11156
+ sessionId,
11157
+ question: askReq.question.slice(0, 50)
11158
+ });
11159
+ const { card } = buildAskCard({
11160
+ chatId,
11161
+ sessionId,
11162
+ question: askReq.question,
11163
+ phases: askReq.phases
11164
+ });
11165
+ await LarkClient.fromAccount(account).sdk.im.message.create({
11166
+ params: { receive_id_type: "chat_id" },
11167
+ data: {
11168
+ receive_id: chatId,
11169
+ msg_type: "interactive",
11170
+ content: JSON.stringify(card),
11171
+ ...replyInThread && threadId ? { reply_in_thread: true } : {}
11172
+ }
11173
+ });
11174
+ log$11.info("提问卡片已发送", {
11175
+ chatId,
11176
+ sessionId
11177
+ });
11178
+ }
11179
+ } catch (err) {
11180
+ log$11.error("交互式卡片后处理失败", {
11181
+ sessionId,
11182
+ error: err instanceof Error ? err.message : String(err)
11183
+ });
11184
+ }
11185
+ }
10687
11186
  //#endregion
10688
11187
  //#region src/messaging/inbound/permission.ts
10689
11188
  /**
@@ -12189,7 +12688,7 @@ const convertLocation = (raw) => {
12189
12688
  * injected via callbacks in `ConvertContext`. Callers are responsible
12190
12689
  * for creating the appropriate callbacks (UAT / TAT / event push).
12191
12690
  */
12192
- const log$11 = larkLogger("converters/merge-forward");
12691
+ const log$10 = larkLogger("converters/merge-forward");
12193
12692
  /**
12194
12693
  * Recursively expand a merge_forward message.
12195
12694
  *
@@ -12204,7 +12703,7 @@ const log$11 = larkLogger("converters/merge-forward");
12204
12703
  const convertMergeForward = async (_raw, ctx) => {
12205
12704
  const { accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent } = ctx;
12206
12705
  if (!fetchSubMessages) {
12207
- log$11.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
12706
+ log$10.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
12208
12707
  messageId,
12209
12708
  accountId
12210
12709
  });
@@ -12222,23 +12721,23 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
12222
12721
  let items;
12223
12722
  try {
12224
12723
  items = await fetchSubMessages(messageId);
12225
- log$11.info("fetchSubMessages 成功", {
12724
+ log$10.info("fetchSubMessages 成功", {
12226
12725
  messageId,
12227
12726
  itemCount: items.length
12228
12727
  });
12229
12728
  } catch (error) {
12230
- log$11.error("fetch sub-messages failed", {
12729
+ log$10.error("fetch sub-messages failed", {
12231
12730
  messageId,
12232
12731
  error: error instanceof Error ? error.message : String(error)
12233
12732
  });
12234
12733
  return "<forwarded_messages/>";
12235
12734
  }
12236
12735
  if (items.length === 0) {
12237
- log$11.warn("fetchSubMessages 返回空 items", { messageId });
12736
+ log$10.warn("fetchSubMessages 返回空 items", { messageId });
12238
12737
  return "<forwarded_messages/>";
12239
12738
  }
12240
12739
  const childrenMap = buildChildrenMap(items, messageId);
12241
- log$11.info("buildChildrenMap 完成", {
12740
+ log$10.info("buildChildrenMap 完成", {
12242
12741
  messageId,
12243
12742
  parentKeys: [...childrenMap.keys()],
12244
12743
  childCounts: [...childrenMap.entries()].map(([k, v]) => `${k}:${v.length}`)
@@ -12247,7 +12746,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
12247
12746
  if (senderIds.length > 0 && batchResolveNames) try {
12248
12747
  await batchResolveNames(senderIds);
12249
12748
  } catch (err) {
12250
- log$11.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
12749
+ log$10.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
12251
12750
  }
12252
12751
  return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
12253
12752
  }
@@ -12327,7 +12826,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
12327
12826
  const indented = indentLines(content, " ");
12328
12827
  parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
12329
12828
  } catch (err) {
12330
- log$11.warn("failed to convert sub-message", {
12829
+ log$10.warn("failed to convert sub-message", {
12331
12830
  messageId: item.message_id,
12332
12831
  msgType: item.msg_type ?? "unknown",
12333
12832
  error: err instanceof Error ? err.message : String(err)
@@ -12541,7 +13040,7 @@ async function convertMessageContent(raw, messageType, ctx) {
12541
13040
  }
12542
13041
  //#endregion
12543
13042
  //#region src/messaging/inbound/parse-io.ts
12544
- const log$10 = larkLogger("inbound/parse-io");
13043
+ const log$9 = larkLogger("inbound/parse-io");
12545
13044
  /**
12546
13045
  * 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
12547
13046
  * 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
@@ -12561,7 +13060,7 @@ async function fetchCardContent(messageId, larkClient) {
12561
13060
  }
12562
13061
  }))?.data?.items?.[0]?.body?.content ?? void 0;
12563
13062
  } catch (err) {
12564
- log$10.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
13063
+ log$9.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
12565
13064
  return;
12566
13065
  }
12567
13066
  }
@@ -12576,7 +13075,7 @@ async function fetchCardContent(messageId, larkClient) {
12576
13075
  */
12577
13076
  function createFetchSubMessages(larkClient) {
12578
13077
  return async (msgId) => {
12579
- log$10.info("fetchSubMessages 请求", {
13078
+ log$9.info("fetchSubMessages 请求", {
12580
13079
  msgId,
12581
13080
  url: `/open-apis/im/v1/messages/${msgId}`
12582
13081
  });
@@ -12588,7 +13087,7 @@ function createFetchSubMessages(larkClient) {
12588
13087
  card_msg_content_type: "raw_card_content"
12589
13088
  }
12590
13089
  });
12591
- log$10.info("fetchSubMessages 响应", {
13090
+ log$9.info("fetchSubMessages 响应", {
12592
13091
  msgId,
12593
13092
  code: response?.code,
12594
13093
  msg: response?.msg,
@@ -12605,11 +13104,11 @@ function createFetchSubMessages(larkClient) {
12605
13104
  * the account and log function.
12606
13105
  */
12607
13106
  function createParseResolveNames(account) {
12608
- return createBatchResolveNames(account, (...args) => log$10.info(args.map(String).join(" ")));
13107
+ return createBatchResolveNames(account, (...args) => log$9.info(args.map(String).join(" ")));
12609
13108
  }
12610
13109
  //#endregion
12611
13110
  //#region src/messaging/inbound/parse.ts
12612
- const log$9 = larkLogger("inbound/parse");
13111
+ const log$8 = larkLogger("inbound/parse");
12613
13112
  /**
12614
13113
  * Parse a raw Feishu message event into a normalised MessageContext.
12615
13114
  *
@@ -12659,7 +13158,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
12659
13158
  const fullContent = await fetchCardContent(event.message.message_id, larkClient);
12660
13159
  if (fullContent) {
12661
13160
  effectiveContent = fullContent;
12662
- log$9.info("replaced interactive content with full v2 card data");
13161
+ log$8.info("replaced interactive content with full v2 card data");
12663
13162
  }
12664
13163
  }
12665
13164
  const convertCtx = {
@@ -12780,7 +13279,7 @@ var MessageDedup = class {
12780
13279
  };
12781
13280
  //#endregion
12782
13281
  //#region src/messaging/inbound/media-download.ts
12783
- const log$8 = larkLogger("inbound/media-download");
13282
+ const log$7 = larkLogger("inbound/media-download");
12784
13283
  /**
12785
13284
  * 从飞书 API 下载单个图片资源,返回 Buffer + mediaType
12786
13285
  */
@@ -12808,7 +13307,7 @@ async function downloadLarkImage(params) {
12808
13307
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
12809
13308
  buffer = Buffer.concat(chunks);
12810
13309
  } else {
12811
- log$8.warn("图片资源下载返回未知格式", {
13310
+ log$7.warn("图片资源下载返回未知格式", {
12812
13311
  fileKey,
12813
13312
  responseType: typeof response
12814
13313
  });
@@ -12816,10 +13315,10 @@ async function downloadLarkImage(params) {
12816
13315
  }
12817
13316
  if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
12818
13317
  } else {
12819
- log$8.warn("图片资源下载返回空", { fileKey });
13318
+ log$7.warn("图片资源下载返回空", { fileKey });
12820
13319
  return null;
12821
13320
  }
12822
- log$8.info("图片资源下载成功", {
13321
+ log$7.info("图片资源下载成功", {
12823
13322
  fileKey,
12824
13323
  mediaType,
12825
13324
  sizeBytes: buffer.length
@@ -12829,7 +13328,7 @@ async function downloadLarkImage(params) {
12829
13328
  mediaType
12830
13329
  };
12831
13330
  } catch (err) {
12832
- log$8.warn("图片资源下载失败", {
13331
+ log$7.warn("图片资源下载失败", {
12833
13332
  fileKey,
12834
13333
  error: err instanceof Error ? err.message : String(err)
12835
13334
  });
@@ -12864,12 +13363,12 @@ async function buildMultimodalPrompt(params) {
12864
13363
  await mkdir(saveDir, { recursive: true });
12865
13364
  const imgFilePath = path.join(saveDir, `${imgRes.fileKey}.png`);
12866
13365
  await writeFile(imgFilePath, buffer);
12867
- log$8.info("图片已保存到工作目录", {
13366
+ log$7.info("图片已保存到工作目录", {
12868
13367
  fileKey: imgRes.fileKey,
12869
13368
  path: imgFilePath
12870
13369
  });
12871
13370
  } catch (saveErr) {
12872
- log$8.warn("图片保存失败", {
13371
+ log$7.warn("图片保存失败", {
12873
13372
  fileKey: imgRes.fileKey,
12874
13373
  error: saveErr instanceof Error ? saveErr.message : String(saveErr)
12875
13374
  });
@@ -12884,7 +13383,7 @@ async function buildMultimodalPrompt(params) {
12884
13383
  });
12885
13384
  }
12886
13385
  if (imageBlocks.length > 0) {
12887
- log$8.info("已构建多模态 prompt", {
13386
+ log$7.info("已构建多模态 prompt", {
12888
13387
  textLength: textPrompt.length,
12889
13388
  imageCount: imageBlocks.length
12890
13389
  });
@@ -12907,7 +13406,7 @@ const TENANT_USER_ID = "tenant";
12907
13406
  * 在消息入口处构建 UserContext,通过参数逐层传递到
12908
13407
  * SessionRouter、ProcessManager、CC 进程环境。
12909
13408
  */
12910
- const log$7 = larkLogger("user/context");
13409
+ const log$6 = larkLogger("user/context");
12911
13410
  /**
12912
13411
  * 构建用户上下文
12913
13412
  *
@@ -12917,7 +13416,7 @@ const log$7 = larkLogger("user/context");
12917
13416
  function buildUserContext(params) {
12918
13417
  const { openId, userId, userName, chatType, workspaceRoot } = params;
12919
13418
  if (chatType === "group") {
12920
- log$7.info("群聊场景,使用 tenant 身份", {
13419
+ log$6.info("群聊场景,使用 tenant 身份", {
12921
13420
  openId,
12922
13421
  chatType
12923
13422
  });
@@ -12930,7 +13429,7 @@ function buildUserContext(params) {
12930
13429
  };
12931
13430
  }
12932
13431
  const effectiveUserId = userId || openId;
12933
- log$7.info("私聊场景,使用用户个人身份", {
13432
+ log$6.info("私聊场景,使用用户个人身份", {
12934
13433
  effectiveUserId,
12935
13434
  openId,
12936
13435
  userId,
@@ -12955,7 +13454,7 @@ function buildUserContext(params) {
12955
13454
  * ├── CLAUDE.md # 用户级人设(跨所有会话共享)
12956
13455
  * └── settings.json # 用户级偏好
12957
13456
  */
12958
- const log$6 = larkLogger("user/user-dir");
13457
+ const log$5 = larkLogger("user/user-dir");
12959
13458
  /**
12960
13459
  * 确保用户目录结构存在
12961
13460
  *
@@ -12964,7 +13463,7 @@ const log$6 = larkLogger("user/user-dir");
12964
13463
  */
12965
13464
  async function ensureUserDirectory(userCtx) {
12966
13465
  const { userId, credentialDir } = userCtx;
12967
- log$6.info("确保用户目录存在", {
13466
+ log$5.info("确保用户目录存在", {
12968
13467
  userId,
12969
13468
  credentialDir
12970
13469
  });
@@ -12972,7 +13471,7 @@ async function ensureUserDirectory(userCtx) {
12972
13471
  const credentialsPath = path.join(credentialDir, "credentials.json");
12973
13472
  if (!existsSync$1(credentialsPath)) {
12974
13473
  await writeFile$1(credentialsPath, JSON.stringify({}, null, 2), "utf-8");
12975
- log$6.info("创建默认 credentials.json", {
13474
+ log$5.info("创建默认 credentials.json", {
12976
13475
  userId,
12977
13476
  path: credentialsPath
12978
13477
  });
@@ -12980,7 +13479,7 @@ async function ensureUserDirectory(userCtx) {
12980
13479
  const mcpServersPath = path.join(credentialDir, "mcp-servers.json");
12981
13480
  if (!existsSync$1(mcpServersPath)) {
12982
13481
  await writeFile$1(mcpServersPath, JSON.stringify({}, null, 2), "utf-8");
12983
- log$6.info("创建默认 mcp-servers.json", {
13482
+ log$5.info("创建默认 mcp-servers.json", {
12984
13483
  userId,
12985
13484
  path: mcpServersPath
12986
13485
  });
@@ -12993,7 +13492,7 @@ async function ensureUserDirectory(userCtx) {
12993
13492
  userName: userCtx.userName
12994
13493
  };
12995
13494
  await writeFile$1(settingsPath, JSON.stringify(defaultSettings, null, 2), "utf-8");
12996
- log$6.info("创建默认 settings.json", {
13495
+ log$5.info("创建默认 settings.json", {
12997
13496
  userId,
12998
13497
  path: settingsPath
12999
13498
  });
@@ -13011,7 +13510,7 @@ async function ensureUserDirectory(userCtx) {
13011
13510
  `这是你的个人配置文件,跨所有会话共享。`,
13012
13511
  `你可以在这里记录用户偏好、常用指令等信息。`
13013
13512
  ].join("\n"), "utf-8");
13014
- log$6.info("创建用户级 CLAUDE.md", {
13513
+ log$5.info("创建用户级 CLAUDE.md", {
13015
13514
  userId,
13016
13515
  path: claudeMdPath
13017
13516
  });
@@ -13020,79 +13519,6 @@ async function ensureUserDirectory(userCtx) {
13020
13519
  }
13021
13520
  larkLogger("user/oauth-proxy");
13022
13521
  //#endregion
13023
- //#region src/gateway/plugins/loader.ts
13024
- const PLUGIN_REGISTRY = {};
13025
- const log$4 = larkLogger("gateway/plugin-loader");
13026
- /** 已加载的插件实例列表 */
13027
- let loadedPlugins = [];
13028
- /**
13029
- * 加载并注册所有已声明的插件
13030
- *
13031
- * @returns 是否有插件需要外部网络访问(影响网关监听地址)
13032
- */
13033
- async function loadPlugins(app, context) {
13034
- const pluginNames = (process.env.LARKPAL_PLUGINS || "").split(",").map((s) => s.trim()).filter(Boolean);
13035
- if (pluginNames.length === 0) {
13036
- log$4.info("未声明任何插件,跳过插件加载");
13037
- return { requiresExternalAccess: false };
13038
- }
13039
- log$4.info("开始加载插件", { plugins: pluginNames });
13040
- let needsExternal = false;
13041
- for (const name of pluginNames) {
13042
- const factoryLoader = PLUGIN_REGISTRY[name];
13043
- if (!factoryLoader) {
13044
- log$4.warn("未知插件,跳过", {
13045
- name,
13046
- available: Object.keys(PLUGIN_REGISTRY)
13047
- });
13048
- continue;
13049
- }
13050
- try {
13051
- const plugin = (await factoryLoader())();
13052
- plugin.register(app, context);
13053
- log$4.info("插件路由已注册", {
13054
- name: plugin.name,
13055
- version: plugin.version,
13056
- description: plugin.description,
13057
- requiresExternalAccess: plugin.requiresExternalAccess
13058
- });
13059
- if (plugin.init) {
13060
- await plugin.init();
13061
- log$4.info("插件初始化完成", { name: plugin.name });
13062
- }
13063
- loadedPlugins.push(plugin);
13064
- if (plugin.requiresExternalAccess) needsExternal = true;
13065
- } catch (err) {
13066
- log$4.error("插件加载失败", {
13067
- name,
13068
- error: err instanceof Error ? err.message : String(err)
13069
- });
13070
- }
13071
- }
13072
- log$4.info("插件加载完成", {
13073
- loaded: loadedPlugins.map((p) => p.name),
13074
- requiresExternalAccess: needsExternal
13075
- });
13076
- return { requiresExternalAccess: needsExternal };
13077
- }
13078
- /**
13079
- * 关闭所有已加载的插件
13080
- */
13081
- async function disposePlugins() {
13082
- for (const plugin of loadedPlugins) try {
13083
- if (plugin.dispose) {
13084
- await plugin.dispose();
13085
- log$4.info("插件已清理", { name: plugin.name });
13086
- }
13087
- } catch (err) {
13088
- log$4.warn("插件清理失败", {
13089
- name: plugin.name,
13090
- error: err instanceof Error ? err.message : String(err)
13091
- });
13092
- }
13093
- loadedPlugins = [];
13094
- }
13095
- //#endregion
13096
13522
  //#region src/messaging/inbound/reply-policy.ts
13097
13523
  const log$3 = larkLogger("messaging/reply-policy");
13098
13524
  /**
@@ -14001,8 +14427,7 @@ async function main() {
14001
14427
  await teammateConfig.load();
14002
14428
  logger.info("Teammate 配置加载完成", { globalDefault: teammateConfig.getData().globalDefault });
14003
14429
  const gatewayPort = 3e3;
14004
- const hasPlugins = !!process.env.LARKPAL_PLUGINS?.trim();
14005
- const gatewayHost = process.env.LARKPAL_GATEWAY_HOST || (hasPlugins ? "0.0.0.0" : "localhost");
14430
+ const gatewayHost = process.env.LARKPAL_GATEWAY_HOST || "localhost";
14006
14431
  const messageStore = new SessionMessageStore(workspaceRoot);
14007
14432
  const gateway = createGatewayServer({
14008
14433
  port: gatewayPort,
@@ -14015,15 +14440,10 @@ async function main() {
14015
14440
  },
14016
14441
  messageStore
14017
14442
  });
14018
- await loadPlugins(gateway.app, {
14019
- workspaceRoot,
14020
- externalBaseUrl: process.env.LARKPAL_EXTERNAL_URL
14021
- });
14022
14443
  await gateway.start();
14023
14444
  logger.info("网关 HTTP 服务启动完成", {
14024
14445
  host: gatewayHost,
14025
- port: gatewayPort,
14026
- hasPlugins
14446
+ port: gatewayPort
14027
14447
  });
14028
14448
  const messageDedup = new MessageDedup({
14029
14449
  ttlMs: 6e4,
@@ -14350,8 +14770,6 @@ async function main() {
14350
14770
  wsAbortController.abort();
14351
14771
  logger.info("WebSocket 连接已关闭");
14352
14772
  messageDedup.dispose();
14353
- await disposePlugins();
14354
- logger.info("网关插件已清理");
14355
14773
  await gateway.stop();
14356
14774
  logger.info("网关 HTTP 服务已停止");
14357
14775
  logger.info("LarkPal 已关闭");