@vibe-lark/larkpal 0.1.38 → 0.1.39

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 +660 -252
  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,7 +6273,7 @@ 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
@@ -5899,7 +6298,7 @@ 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
  });
@@ -5907,7 +6306,7 @@ var CCStreamBridge = class {
5907
6306
  /** 文本增量 → 累积后调用 controller.onPartialReply */
5908
6307
  onTextDelta(text) {
5909
6308
  this.accumulatedText += text;
5910
- log$19.debug("textDelta 事件", {
6309
+ log$18.debug("textDelta 事件", {
5911
6310
  deltaLen: text.length,
5912
6311
  totalLen: this.accumulatedText.length
5913
6312
  });
@@ -5916,7 +6315,7 @@ var CCStreamBridge = class {
5916
6315
  /** 思考增量 → 累积后调用 controller.onReasoningStream */
5917
6316
  onThinkingDelta(text) {
5918
6317
  this.accumulatedThinkingText += text;
5919
- log$19.debug("thinkingDelta 事件", {
6318
+ log$18.debug("thinkingDelta 事件", {
5920
6319
  deltaLen: text.length,
5921
6320
  totalLen: this.accumulatedThinkingText.length
5922
6321
  });
@@ -5928,7 +6327,7 @@ var CCStreamBridge = class {
5928
6327
  const displayName = getToolDisplayName(toolName);
5929
6328
  const toolParams = typeof _toolInput === "object" && _toolInput !== null ? _toolInput : void 0;
5930
6329
  const hasParams = toolParams && Object.keys(toolParams).length > 0;
5931
- log$19.info("toolUseStart 事件", {
6330
+ log$18.info("toolUseStart 事件", {
5932
6331
  toolName,
5933
6332
  displayName,
5934
6333
  activeToolsCount: this.activeTools.size,
@@ -5941,7 +6340,7 @@ var CCStreamBridge = class {
5941
6340
  toolName,
5942
6341
  toolParams
5943
6342
  })) {
5944
- log$19.info("toolUseStart: 更新已有步骤的 params", { toolName });
6343
+ log$18.info("toolUseStart: 更新已有步骤的 params", { toolName });
5945
6344
  this.controller.onToolStart({
5946
6345
  name: toolName,
5947
6346
  phase: "start"
@@ -5965,7 +6364,7 @@ var CCStreamBridge = class {
5965
6364
  const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
5966
6365
  const displayName = getToolDisplayName(toolName);
5967
6366
  this.activeTools.delete(toolUseId);
5968
- log$19.info("toolResult 事件", {
6367
+ log$18.info("toolResult 事件", {
5969
6368
  toolUseId,
5970
6369
  toolName,
5971
6370
  displayName,
@@ -5980,7 +6379,7 @@ var CCStreamBridge = class {
5980
6379
  }
5981
6380
  /** 工具执行进度 → 记录日志 */
5982
6381
  onToolProgress(toolName, elapsedSeconds) {
5983
- log$19.debug("toolProgress 事件", {
6382
+ log$18.debug("toolProgress 事件", {
5984
6383
  toolName,
5985
6384
  displayName: getToolDisplayName(toolName),
5986
6385
  elapsedSeconds
@@ -5988,7 +6387,7 @@ var CCStreamBridge = class {
5988
6387
  }
5989
6388
  /** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
5990
6389
  onTurnEnd(stopReason) {
5991
- log$19.info("turnEnd 事件", {
6390
+ log$18.info("turnEnd 事件", {
5992
6391
  stopReason,
5993
6392
  accumulatedTextLen: this.accumulatedText.length,
5994
6393
  accumulatedThinkingTextLen: this.accumulatedThinkingText.length
@@ -5996,7 +6395,7 @@ var CCStreamBridge = class {
5996
6395
  if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
5997
6396
  const trimmedText = this.accumulatedText.trim();
5998
6397
  if (trimmedText === CC_INTERNAL_PLACEHOLDER) {
5999
- log$19.info("检测到 CC 内部占位消息,静默丢弃", {
6398
+ log$18.info("检测到 CC 内部占位消息,静默丢弃", {
6000
6399
  text: trimmedText.slice(0, 50),
6001
6400
  sessionKey: this.options.sessionKey
6002
6401
  });
@@ -6004,16 +6403,26 @@ var CCStreamBridge = class {
6004
6403
  return;
6005
6404
  }
6006
6405
  if (trimmedText === "") {
6007
- log$19.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
6406
+ log$18.info("turnEnd 时文本为空,延迟到 onResult 兜底处理", { sessionKey: this.options.sessionKey });
6008
6407
  return;
6009
6408
  }
6409
+ const strippedText = stripInteractiveMarkers(trimmedText);
6410
+ if (strippedText !== trimmedText) {
6411
+ log$18.info("turnEnd: 检测到交互式卡片标记,修剪卡片文本", {
6412
+ originalLen: trimmedText.length,
6413
+ strippedLen: strippedText.length,
6414
+ sessionKey: this.options.sessionKey
6415
+ });
6416
+ this.accumulatedText = strippedText;
6417
+ if (strippedText) this.controller.onPartialReply({ text: strippedText });
6418
+ }
6010
6419
  this.controller.markFullyComplete();
6011
6420
  this.controller.onIdle();
6012
6421
  }
6013
6422
  }
6014
6423
  /** 最终结果 → 兜底最终化 + 错误处理 */
6015
6424
  onResult(data) {
6016
- log$19.info("result 事件", {
6425
+ log$18.info("result 事件", {
6017
6426
  subtype: data.subtype,
6018
6427
  isError: data.isError,
6019
6428
  durationMs: data.durationMs,
@@ -6025,7 +6434,7 @@ var CCStreamBridge = class {
6025
6434
  const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
6026
6435
  const sessionId = this.options.sessionKey;
6027
6436
  if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
6028
- log$19.info("用户主动中断,按正常完成处理", {
6437
+ log$18.info("用户主动中断,按正常完成处理", {
6029
6438
  subtype: data.subtype,
6030
6439
  sessionId
6031
6440
  });
@@ -6034,14 +6443,14 @@ var CCStreamBridge = class {
6034
6443
  this.controller.onIdle();
6035
6444
  } else {
6036
6445
  const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
6037
- log$19.error("CC 执行返回错误", {
6446
+ log$18.error("CC 执行返回错误", {
6038
6447
  subtype: data.subtype,
6039
6448
  errorMessage: errorMessage.slice(0, 200)
6040
6449
  });
6041
6450
  this.controller.onError(new Error(errorMessage), { kind: "cc-execution" });
6042
6451
  }
6043
6452
  } else if (!this.accumulatedText.trim()) if (data.result) {
6044
- log$19.info("result 兜底:使用 result.result 作为最终回复", {
6453
+ log$18.info("result 兜底:使用 result.result 作为最终回复", {
6045
6454
  resultLen: data.result.length,
6046
6455
  sessionKey: this.options.sessionKey
6047
6456
  });
@@ -6049,7 +6458,7 @@ var CCStreamBridge = class {
6049
6458
  this.controller.markFullyComplete();
6050
6459
  this.controller.onIdle();
6051
6460
  } else {
6052
- log$19.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
6461
+ log$18.info("result 兜底:无文本且无 result,abort 卡片", { sessionKey: this.options.sessionKey });
6053
6462
  this.controller.abortCard();
6054
6463
  }
6055
6464
  else {
@@ -6063,7 +6472,7 @@ var CCStreamBridge = class {
6063
6472
  * 内部复用上面的直接调用方法。
6064
6473
  */
6065
6474
  bindParser(parser) {
6066
- log$19.info("绑定 CCStreamParser 事件到卡片控制器");
6475
+ log$18.info("绑定 CCStreamParser 事件到卡片控制器");
6067
6476
  parser.on("textDelta", (text) => this.onTextDelta(text));
6068
6477
  parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
6069
6478
  parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
@@ -6071,7 +6480,7 @@ var CCStreamBridge = class {
6071
6480
  parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
6072
6481
  parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
6073
6482
  parser.on("result", (data) => this.onResult(data));
6074
- log$19.info("CCStreamParser 事件绑定完成");
6483
+ log$18.info("CCStreamParser 事件绑定完成");
6075
6484
  }
6076
6485
  };
6077
6486
  //#endregion
@@ -8087,7 +8496,7 @@ function resolveLarkSdk(cfg, accountId) {
8087
8496
  if (cached) return cached.sdk;
8088
8497
  return LarkClient.fromCfg(cfg, accountId).sdk;
8089
8498
  }
8090
- const log$18 = larkLogger("card/cardkit");
8499
+ const log$17 = larkLogger("card/cardkit");
8091
8500
  /**
8092
8501
  * 记录 CardKit API 响应日志,检测错误码并抛出异常。
8093
8502
  *
@@ -8097,13 +8506,13 @@ const log$18 = larkLogger("card/cardkit");
8097
8506
  function logCardKitResponse(params) {
8098
8507
  const { resp, api, context } = params;
8099
8508
  const { code, msg } = resp;
8100
- log$18.info(`cardkit ${api} response`, {
8509
+ log$17.info(`cardkit ${api} response`, {
8101
8510
  code,
8102
8511
  msg,
8103
8512
  context
8104
8513
  });
8105
8514
  if (code && code !== 0) {
8106
- log$18.warn(`cardkit ${api} FAILED`, {
8515
+ log$17.warn(`cardkit ${api} FAILED`, {
8107
8516
  code,
8108
8517
  msg,
8109
8518
  context,
@@ -8514,7 +8923,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
8514
8923
  * Feishu messages, uploading media to the Feishu IM storage, and
8515
8924
  * sending image / file messages to chats.
8516
8925
  */
8517
- const log$17 = larkLogger("outbound/media");
8926
+ const log$16 = larkLogger("outbound/media");
8518
8927
  /**
8519
8928
  * Upload an image to Feishu IM storage.
8520
8929
  *
@@ -8582,7 +8991,7 @@ async function validateRemoteUrl(raw) {
8582
8991
  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
8992
  } catch (err) {
8584
8993
  if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
8585
- log$17.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
8994
+ log$16.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
8586
8995
  }
8587
8996
  }
8588
8997
  /**
@@ -8600,21 +9009,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
8600
9009
  if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
8601
9010
  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
9011
  const buf = fs.readFileSync(filePath);
8603
- log$17.debug(`local file read: "${filePath}", ${buf.length} bytes`);
9012
+ log$16.debug(`local file read: "${filePath}", ${buf.length} bytes`);
8604
9013
  return buf;
8605
9014
  }
8606
9015
  await validateRemoteUrl(raw);
8607
9016
  const FETCH_TIMEOUT_MS = 3e4;
8608
- log$17.info(`fetching remote media: ${raw}`);
9017
+ log$16.info(`fetching remote media: ${raw}`);
8609
9018
  const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
8610
9019
  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
9020
  const arrayBuffer = await response.arrayBuffer();
8612
- log$17.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
9021
+ log$16.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
8613
9022
  return Buffer.from(arrayBuffer);
8614
9023
  }
8615
9024
  //#endregion
8616
9025
  //#region src/card/image-resolver.ts
8617
- const log$16 = larkLogger("card/image-resolver");
9026
+ const log$15 = larkLogger("card/image-resolver");
8618
9027
  /** Matches complete markdown image syntax: `![alt](value)` */
8619
9028
  const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
8620
9029
  var ImageResolver = class {
@@ -8660,14 +9069,14 @@ var ImageResolver = class {
8660
9069
  async resolveImagesAwait(text, timeoutMs) {
8661
9070
  this.resolveImages(text);
8662
9071
  if (this.pending.size > 0) {
8663
- log$16.info("resolveImagesAwait: waiting for uploads", {
9072
+ log$15.info("resolveImagesAwait: waiting for uploads", {
8664
9073
  count: this.pending.size,
8665
9074
  timeoutMs
8666
9075
  });
8667
9076
  const allUploads = Promise.all(this.pending.values());
8668
9077
  const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
8669
9078
  await Promise.race([allUploads, timeout]);
8670
- if (this.pending.size > 0) log$16.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
9079
+ if (this.pending.size > 0) log$15.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
8671
9080
  }
8672
9081
  return this.resolveImages(text);
8673
9082
  }
@@ -8677,7 +9086,7 @@ var ImageResolver = class {
8677
9086
  }
8678
9087
  async doUpload(url) {
8679
9088
  try {
8680
- log$16.info("uploading image", { url });
9089
+ log$15.info("uploading image", { url });
8681
9090
  const buffer = await fetchRemoteImageBuffer(url);
8682
9091
  const { imageKey } = await uploadImageLark({
8683
9092
  cfg: this.cfg,
@@ -8685,7 +9094,7 @@ var ImageResolver = class {
8685
9094
  imageType: "message",
8686
9095
  accountId: this.accountId
8687
9096
  });
8688
- log$16.info("image uploaded", {
9097
+ log$15.info("image uploaded", {
8689
9098
  url,
8690
9099
  imageKey
8691
9100
  });
@@ -8694,7 +9103,7 @@ var ImageResolver = class {
8694
9103
  this.onImageResolved();
8695
9104
  return imageKey;
8696
9105
  } catch (err) {
8697
- log$16.warn("image upload failed", {
9106
+ log$15.warn("image upload failed", {
8698
9107
  url,
8699
9108
  error: String(err)
8700
9109
  });
@@ -8715,7 +9124,7 @@ var ImageResolver = class {
8715
9124
  * Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
8716
9125
  * logic previously scattered as closures in reply-dispatcher.ts.
8717
9126
  */
8718
- const log$15 = larkLogger("card/unavailable-guard");
9127
+ const log$14 = larkLogger("card/unavailable-guard");
8719
9128
  var UnavailableGuard = class {
8720
9129
  terminated = false;
8721
9130
  replyToMessageId;
@@ -8768,7 +9177,7 @@ var UnavailableGuard = class {
8768
9177
  this.terminated = true;
8769
9178
  this.onTerminate();
8770
9179
  const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
8771
- log$15.warn("reply pipeline terminated by unavailable message", {
9180
+ log$14.warn("reply pipeline terminated by unavailable message", {
8772
9181
  source,
8773
9182
  apiCode,
8774
9183
  messageId: affectedMessageId
@@ -8805,7 +9214,7 @@ function getActiveCard(sessionId) {
8805
9214
  * Delegates throttling to FlushController and message-unavailable
8806
9215
  * detection to UnavailableGuard.
8807
9216
  */
8808
- const log$14 = larkLogger("card/streaming");
9217
+ const log$13 = larkLogger("card/streaming");
8809
9218
  var StreamingCardController = class StreamingCardController {
8810
9219
  phase = "idle";
8811
9220
  cardKit = {
@@ -8893,7 +9302,7 @@ var StreamingCardController = class StreamingCardController {
8893
9302
  }
8894
9303
  }
8895
9304
  if (!entry) {
8896
- log$14.debug("footer metrics lookup: session entry missing", {
9305
+ log$13.debug("footer metrics lookup: session entry missing", {
8897
9306
  sessionKey: this.deps.sessionKey,
8898
9307
  candidateKeys,
8899
9308
  storePath,
@@ -8911,7 +9320,7 @@ var StreamingCardController = class StreamingCardController {
8911
9320
  contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
8912
9321
  model: typeof entry.model === "string" ? entry.model : void 0
8913
9322
  };
8914
- log$14.debug("footer metrics lookup: session entry found", {
9323
+ log$13.debug("footer metrics lookup: session entry found", {
8915
9324
  sessionKey: this.deps.sessionKey,
8916
9325
  matchedKey,
8917
9326
  storePath,
@@ -8936,7 +9345,7 @@ var StreamingCardController = class StreamingCardController {
8936
9345
  }
8937
9346
  }
8938
9347
  if (!entry) {
8939
- log$14.debug("footer metrics lookup: session entry missing", {
9348
+ log$13.debug("footer metrics lookup: session entry missing", {
8940
9349
  sessionKey: this.deps.sessionKey,
8941
9350
  candidateKeys,
8942
9351
  storePath,
@@ -8954,7 +9363,7 @@ var StreamingCardController = class StreamingCardController {
8954
9363
  contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
8955
9364
  model: typeof entry.model === "string" ? entry.model : void 0
8956
9365
  };
8957
- log$14.debug("footer metrics lookup: session entry found", {
9366
+ log$13.debug("footer metrics lookup: session entry found", {
8958
9367
  sessionKey: this.deps.sessionKey,
8959
9368
  matchedKey,
8960
9369
  storePath,
@@ -8962,7 +9371,7 @@ var StreamingCardController = class StreamingCardController {
8962
9371
  });
8963
9372
  return metrics;
8964
9373
  } catch (err) {
8965
- log$14.warn("footer metrics lookup failed", {
9374
+ log$13.warn("footer metrics lookup failed", {
8966
9375
  error: String(err),
8967
9376
  sessionKey: this.deps.sessionKey
8968
9377
  });
@@ -9069,7 +9478,7 @@ var StreamingCardController = class StreamingCardController {
9069
9478
  const from = this.phase;
9070
9479
  if (from === to) return false;
9071
9480
  if (!PHASE_TRANSITIONS[from].has(to)) {
9072
- log$14.warn("phase transition rejected", {
9481
+ log$13.warn("phase transition rejected", {
9073
9482
  from,
9074
9483
  to,
9075
9484
  source
@@ -9077,7 +9486,7 @@ var StreamingCardController = class StreamingCardController {
9077
9486
  return false;
9078
9487
  }
9079
9488
  this.phase = to;
9080
- log$14.info("phase transition", {
9489
+ log$13.info("phase transition", {
9081
9490
  from,
9082
9491
  to,
9083
9492
  source,
@@ -9197,7 +9606,7 @@ var StreamingCardController = class StreamingCardController {
9197
9606
  this.reasoning.dirty = true;
9198
9607
  }
9199
9608
  const text = split.answerText ?? stripReasoningTags(rawText);
9200
- log$14.debug("onPartialReply", { len: text.length });
9609
+ log$13.debug("onPartialReply", { len: text.length });
9201
9610
  if (!text) return;
9202
9611
  this.captureToolUseElapsed();
9203
9612
  if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
@@ -9209,7 +9618,7 @@ var StreamingCardController = class StreamingCardController {
9209
9618
  this.text.lastPartialText = text;
9210
9619
  this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
9211
9620
  if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
9212
- log$14.debug("onPartialReply: buffering NO_REPLY prefix");
9621
+ log$13.debug("onPartialReply: buffering NO_REPLY prefix");
9213
9622
  return;
9214
9623
  }
9215
9624
  await this.ensureCardCreated();
@@ -9219,7 +9628,7 @@ var StreamingCardController = class StreamingCardController {
9219
9628
  }
9220
9629
  async onError(err, info) {
9221
9630
  if (this.guard.terminate("onError", err)) return;
9222
- log$14.error(`${info.kind} reply failed`, { error: String(err) });
9631
+ log$13.error(`${info.kind} reply failed`, { error: String(err) });
9223
9632
  this.captureToolUseElapsed();
9224
9633
  this.finalizeCard("onError", "error");
9225
9634
  await this.flush.waitForFlush();
@@ -9275,7 +9684,7 @@ var StreamingCardController = class StreamingCardController {
9275
9684
  if (this.cardKit.cardMessageId) {
9276
9685
  const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
9277
9686
  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");
9687
+ if (!this.text.completedText && !this.text.accumulatedText) log$13.warn("reply completed without visible text, using empty-reply fallback");
9279
9688
  const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
9280
9689
  const idleToolUseDisplay = this.computeToolUseDisplay();
9281
9690
  const terminalContent = prepareTerminalCardContent({
@@ -9303,7 +9712,7 @@ var StreamingCardController = class StreamingCardController {
9303
9712
  const rewindSessionId = this.deps.sessionKey;
9304
9713
  const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
9305
9714
  const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
9306
- log$14.info("onIdle: rewind button conditions", {
9715
+ log$13.info("onIdle: rewind button conditions", {
9307
9716
  sessionId: rewindSessionId,
9308
9717
  messageId: rewindMessageId,
9309
9718
  hasFileChanges: rewindHasFileChanges,
@@ -9316,7 +9725,7 @@ var StreamingCardController = class StreamingCardController {
9316
9725
  if (idleEffectiveCardId) {
9317
9726
  const seqBeforeClose = this.cardKit.cardKitSequence;
9318
9727
  this.cardKit.cardKitSequence += 1;
9319
- log$14.info("onIdle: closing streaming mode", {
9728
+ log$13.info("onIdle: closing streaming mode", {
9320
9729
  attempt,
9321
9730
  seqBefore: seqBeforeClose,
9322
9731
  seqAfter: this.cardKit.cardKitSequence
@@ -9330,7 +9739,7 @@ var StreamingCardController = class StreamingCardController {
9330
9739
  });
9331
9740
  const seqBeforeUpdate = this.cardKit.cardKitSequence;
9332
9741
  this.cardKit.cardKitSequence += 1;
9333
- log$14.info("onIdle: updating final card", {
9742
+ log$13.info("onIdle: updating final card", {
9334
9743
  attempt,
9335
9744
  seqBefore: seqBeforeUpdate,
9336
9745
  seqAfter: this.cardKit.cardKitSequence
@@ -9348,33 +9757,33 @@ var StreamingCardController = class StreamingCardController {
9348
9757
  card: completeCard,
9349
9758
  accountId: this.deps.accountId
9350
9759
  });
9351
- log$14.info("reply completed, card finalized", {
9760
+ log$13.info("reply completed, card finalized", {
9352
9761
  elapsedMs: this.elapsed(),
9353
9762
  isCardKit: !!idleEffectiveCardId,
9354
9763
  attempt
9355
9764
  });
9356
9765
  break;
9357
9766
  } catch (retryErr) {
9358
- log$14.warn("final card update attempt failed", {
9767
+ log$13.warn("final card update attempt failed", {
9359
9768
  attempt,
9360
9769
  maxRetries: MAX_FINAL_UPDATE_RETRIES,
9361
9770
  error: String(retryErr)
9362
9771
  });
9363
9772
  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", {
9773
+ else log$13.error("final card update exhausted all retries, card may remain in streaming state", {
9365
9774
  cardId: idleEffectiveCardId,
9366
9775
  messageId: this.cardKit.cardMessageId
9367
9776
  });
9368
9777
  }
9369
9778
  }
9370
9779
  } catch (err) {
9371
- log$14.error("final card update failed (outer)", { error: String(err) });
9780
+ log$13.error("final card update failed (outer)", { error: String(err) });
9372
9781
  } finally {
9373
9782
  clearToolUseTraceRun(this.deps.sessionKey);
9374
9783
  }
9375
9784
  }
9376
9785
  markFullyComplete() {
9377
- log$14.debug("markFullyComplete", {
9786
+ log$13.debug("markFullyComplete", {
9378
9787
  completedTextLen: this.text.completedText.length,
9379
9788
  accumulatedTextLen: this.text.accumulatedText.length
9380
9789
  });
@@ -9413,7 +9822,7 @@ var StreamingCardController = class StreamingCardController {
9413
9822
  footerMetrics
9414
9823
  });
9415
9824
  await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
9416
- log$14.info("abortCard completed", { effectiveCardId });
9825
+ log$13.info("abortCard completed", { effectiveCardId });
9417
9826
  } else if (this.cardKit.cardMessageId) {
9418
9827
  const abortCard = buildCardContent("complete", {
9419
9828
  text: terminalContent.text,
@@ -9434,10 +9843,10 @@ var StreamingCardController = class StreamingCardController {
9434
9843
  card: abortCard,
9435
9844
  accountId: this.deps.accountId
9436
9845
  });
9437
- log$14.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
9846
+ log$13.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
9438
9847
  }
9439
9848
  } catch (err) {
9440
- log$14.warn("abortCard failed", { error: String(err) });
9849
+ log$13.warn("abortCard failed", { error: String(err) });
9441
9850
  } finally {
9442
9851
  clearToolUseTraceRun(this.deps.sessionKey);
9443
9852
  }
@@ -9452,10 +9861,10 @@ var StreamingCardController = class StreamingCardController {
9452
9861
  async forceCloseStreaming() {
9453
9862
  const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
9454
9863
  if (!effectiveCardId && !this.cardKit.cardMessageId) {
9455
- log$14.warn("forceCloseStreaming: no card to close");
9864
+ log$13.warn("forceCloseStreaming: no card to close");
9456
9865
  return;
9457
9866
  }
9458
- log$14.info("forceCloseStreaming: 强制终态化卡片", {
9867
+ log$13.info("forceCloseStreaming: 强制终态化卡片", {
9459
9868
  cardId: effectiveCardId,
9460
9869
  messageId: this.cardKit.cardMessageId,
9461
9870
  phase: this.phase
@@ -9505,10 +9914,10 @@ var StreamingCardController = class StreamingCardController {
9505
9914
  card: completeCard,
9506
9915
  accountId: this.deps.accountId
9507
9916
  });
9508
- log$14.info("forceCloseStreaming: 卡片已强制终态化");
9917
+ log$13.info("forceCloseStreaming: 卡片已强制终态化");
9509
9918
  if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
9510
9919
  } catch (err) {
9511
- log$14.error("forceCloseStreaming failed", { error: String(err) });
9920
+ log$13.error("forceCloseStreaming failed", { error: String(err) });
9512
9921
  } finally {
9513
9922
  unregisterActiveCard(this.deps.sessionKey);
9514
9923
  clearToolUseTraceRun(this.deps.sessionKey);
@@ -9532,7 +9941,7 @@ var StreamingCardController = class StreamingCardController {
9532
9941
  this.disposeShutdownHook = null;
9533
9942
  return;
9534
9943
  }
9535
- log$14.info("reusing placeholder card", {
9944
+ log$13.info("reusing placeholder card", {
9536
9945
  cardId: this.deps.placeholderCardId,
9537
9946
  messageId: this.deps.placeholderMessageId
9538
9947
  });
@@ -9556,7 +9965,7 @@ var StreamingCardController = class StreamingCardController {
9556
9965
  accountId: this.deps.accountId
9557
9966
  });
9558
9967
  if (this.isStaleCreate(epoch)) {
9559
- log$14.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
9968
+ log$13.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
9560
9969
  epoch,
9561
9970
  phase: this.phase
9562
9971
  });
@@ -9567,7 +9976,7 @@ var StreamingCardController = class StreamingCardController {
9567
9976
  this.cardKit.originalCardKitCardId = cId;
9568
9977
  this.cardKit.cardKitSequence = 1;
9569
9978
  this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
9570
- log$14.info("created CardKit entity", {
9979
+ log$13.info("created CardKit entity", {
9571
9980
  cardId: cId,
9572
9981
  initialSequence: this.cardKit.cardKitSequence
9573
9982
  });
@@ -9580,7 +9989,7 @@ var StreamingCardController = class StreamingCardController {
9580
9989
  accountId: this.deps.accountId
9581
9990
  });
9582
9991
  if (this.isStaleCreate(epoch)) {
9583
- log$14.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
9992
+ log$13.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
9584
9993
  epoch,
9585
9994
  phase: this.phase
9586
9995
  });
@@ -9595,13 +10004,13 @@ var StreamingCardController = class StreamingCardController {
9595
10004
  this.disposeShutdownHook = null;
9596
10005
  return;
9597
10006
  }
9598
- log$14.info("sent CardKit card", { messageId: result.messageId });
10007
+ log$13.info("sent CardKit card", { messageId: result.messageId });
9599
10008
  } else throw new Error("card.create returned empty card_id");
9600
10009
  } catch (cardKitErr) {
9601
10010
  if (this.isStaleCreate(epoch)) return;
9602
10011
  if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
9603
10012
  const apiDetail = extractApiDetail(cardKitErr);
9604
- log$14.warn("CardKit flow failed, falling back to IM", { apiDetail });
10013
+ log$13.warn("CardKit flow failed, falling back to IM", { apiDetail });
9605
10014
  this.cardKit.cardKitCardId = null;
9606
10015
  this.cardKit.originalCardKitCardId = null;
9607
10016
  const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
@@ -9614,7 +10023,7 @@ var StreamingCardController = class StreamingCardController {
9614
10023
  accountId: this.deps.accountId
9615
10024
  });
9616
10025
  if (this.isStaleCreate(epoch)) {
9617
- log$14.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
10026
+ log$13.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
9618
10027
  epoch,
9619
10028
  phase: this.phase
9620
10029
  });
@@ -9623,12 +10032,12 @@ var StreamingCardController = class StreamingCardController {
9623
10032
  this.cardKit.cardMessageId = result.messageId;
9624
10033
  this.flush.setCardMessageReady(true);
9625
10034
  if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
9626
- log$14.info("sent fallback IM card", { messageId: result.messageId });
10035
+ log$13.info("sent fallback IM card", { messageId: result.messageId });
9627
10036
  }
9628
10037
  } catch (err) {
9629
10038
  if (this.isStaleCreate(epoch)) return;
9630
10039
  if (this.guard.terminate("ensureCardCreated.outer", err)) return;
9631
- log$14.warn("thinking card failed, falling back to static", { error: String(err) });
10040
+ log$13.warn("thinking card failed, falling back to static", { error: String(err) });
9632
10041
  this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
9633
10042
  }
9634
10043
  })();
@@ -9637,10 +10046,10 @@ var StreamingCardController = class StreamingCardController {
9637
10046
  async performFlush() {
9638
10047
  if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
9639
10048
  if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
9640
- log$14.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
10049
+ log$13.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
9641
10050
  return;
9642
10051
  }
9643
- log$14.debug("flushCardUpdate: enter", {
10052
+ log$13.debug("flushCardUpdate: enter", {
9644
10053
  seq: this.cardKit.cardKitSequence,
9645
10054
  isCardKit: !!this.cardKit.cardKitCardId
9646
10055
  });
@@ -9662,7 +10071,7 @@ var StreamingCardController = class StreamingCardController {
9662
10071
  reasoningText: this.reasoning.accumulatedReasoningText || void 0
9663
10072
  });
9664
10073
  this.cardKit.cardKitSequence += 1;
9665
- log$14.debug("flushCardUpdate: full card update (dirty)", {
10074
+ log$13.debug("flushCardUpdate: full card update (dirty)", {
9666
10075
  seq: this.cardKit.cardKitSequence,
9667
10076
  stepCount: display?.stepCount,
9668
10077
  hasReasoning: !!this.reasoning.accumulatedReasoningText
@@ -9678,7 +10087,7 @@ var StreamingCardController = class StreamingCardController {
9678
10087
  } else if (resolvedText !== this.text.lastFlushedText) {
9679
10088
  const prevSeq = this.cardKit.cardKitSequence;
9680
10089
  this.cardKit.cardKitSequence += 1;
9681
- log$14.debug("flushCardUpdate: answer seq bump", {
10090
+ log$13.debug("flushCardUpdate: answer seq bump", {
9682
10091
  seqBefore: prevSeq,
9683
10092
  seqAfter: this.cardKit.cardKitSequence
9684
10093
  });
@@ -9693,7 +10102,7 @@ var StreamingCardController = class StreamingCardController {
9693
10102
  this.text.lastFlushedText = resolvedText;
9694
10103
  }
9695
10104
  } else {
9696
- log$14.debug("flushCardUpdate: IM patch fallback");
10105
+ log$13.debug("flushCardUpdate: IM patch fallback");
9697
10106
  const flushDisplay = this.computeToolUseDisplay();
9698
10107
  const card = buildCardContent("streaming", {
9699
10108
  text: this.reasoning.isReasoningPhase ? "" : resolvedText,
@@ -9713,31 +10122,31 @@ var StreamingCardController = class StreamingCardController {
9713
10122
  if (this.guard.terminate("flushCardUpdate", err)) return;
9714
10123
  const apiCode = extractLarkApiCode(err);
9715
10124
  if (isCardRateLimitError(err)) {
9716
- log$14.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
10125
+ log$13.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
9717
10126
  return;
9718
10127
  }
9719
10128
  if (isCardTableLimitError(err)) {
9720
- log$14.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
10129
+ log$13.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
9721
10130
  this.cardKit.cardKitCardId = null;
9722
10131
  return;
9723
10132
  }
9724
10133
  if (apiCode === 300317 && this.cardKit.cardKitCardId) {
9725
10134
  const oldSeq = this.cardKit.cardKitSequence;
9726
10135
  this.cardKit.cardKitSequence += 100;
9727
- log$14.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
10136
+ log$13.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
9728
10137
  oldSeq,
9729
10138
  newSeq: this.cardKit.cardKitSequence
9730
10139
  });
9731
10140
  return;
9732
10141
  }
9733
10142
  const apiDetail = extractApiDetail(err);
9734
- log$14.error("card stream update failed", {
10143
+ log$13.error("card stream update failed", {
9735
10144
  apiCode,
9736
10145
  seq: this.cardKit.cardKitSequence,
9737
10146
  apiDetail
9738
10147
  });
9739
10148
  if (this.cardKit.cardKitCardId) {
9740
- log$14.warn("disabling CardKit streaming, falling back to im.message.patch");
10149
+ log$13.warn("disabling CardKit streaming, falling back to im.message.patch");
9741
10150
  this.cardKit.cardKitCardId = null;
9742
10151
  }
9743
10152
  }
@@ -9762,7 +10171,7 @@ var StreamingCardController = class StreamingCardController {
9762
10171
  if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
9763
10172
  try {
9764
10173
  const display = this.computeToolUseDisplay();
9765
- log$14.info("updateToolUseStatus", {
10174
+ log$13.info("updateToolUseStatus", {
9766
10175
  hasDisplay: !!display,
9767
10176
  stepCount: display?.stepCount,
9768
10177
  steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
@@ -9784,7 +10193,7 @@ var StreamingCardController = class StreamingCardController {
9784
10193
  accountId: this.deps.accountId
9785
10194
  });
9786
10195
  } catch (err) {
9787
- log$14.debug("updateToolUseStatus failed", { error: String(err) });
10196
+ log$13.debug("updateToolUseStatus failed", { error: String(err) });
9788
10197
  }
9789
10198
  }
9790
10199
  finalizeCard(source, reason) {
@@ -9796,7 +10205,7 @@ var StreamingCardController = class StreamingCardController {
9796
10205
  async closeStreamingAndUpdate(cardId, card, label) {
9797
10206
  const seqBeforeClose = this.cardKit.cardKitSequence;
9798
10207
  this.cardKit.cardKitSequence += 1;
9799
- log$14.info(`${label}: closing streaming mode`, {
10208
+ log$13.info(`${label}: closing streaming mode`, {
9800
10209
  seqBefore: seqBeforeClose,
9801
10210
  seqAfter: this.cardKit.cardKitSequence
9802
10211
  });
@@ -9809,7 +10218,7 @@ var StreamingCardController = class StreamingCardController {
9809
10218
  });
9810
10219
  const seqBeforeUpdate = this.cardKit.cardKitSequence;
9811
10220
  this.cardKit.cardKitSequence += 1;
9812
- log$14.info(`${label}: updating card`, {
10221
+ log$13.info(`${label}: updating card`, {
9813
10222
  seqBefore: seqBeforeUpdate,
9814
10223
  seqAfter: this.cardKit.cardKitSequence
9815
10224
  });
@@ -9843,7 +10252,7 @@ function extractApiDetail(err) {
9843
10252
  }
9844
10253
  //#endregion
9845
10254
  //#region src/messaging/format-for-cc.ts
9846
- const log$13 = larkLogger("messaging/format-for-cc");
10255
+ const log$12 = larkLogger("messaging/format-for-cc");
9847
10256
  function safeParseJSON(raw) {
9848
10257
  try {
9849
10258
  return JSON.parse(raw);
@@ -9899,7 +10308,7 @@ function extractPostText(rawContent) {
9899
10308
  */
9900
10309
  function formatContentByType(ctx) {
9901
10310
  const { contentType, content, resources } = ctx;
9902
- log$13.debug("formatContentByType 开始格式化", {
10311
+ log$12.debug("formatContentByType 开始格式化", {
9903
10312
  contentType,
9904
10313
  contentLength: content?.length ?? 0,
9905
10314
  resourceCount: resources?.length ?? 0
@@ -9941,7 +10350,7 @@ function formatContentByType(ctx) {
9941
10350
  case "todo":
9942
10351
  case "vote": return content;
9943
10352
  default:
9944
- log$13.warn("遇到不支持的消息类型", { contentType });
10353
+ log$12.warn("遇到不支持的消息类型", { contentType });
9945
10354
  return `[不支持的消息类型: ${contentType}]`;
9946
10355
  }
9947
10356
  }
@@ -9984,7 +10393,7 @@ function formatMessageTime(createTime) {
9984
10393
  * @returns 格式化后的纯文本字符串
9985
10394
  */
9986
10395
  function formatForCC(ctx, isGroup) {
9987
- log$13.info("formatForCC 开始处理", {
10396
+ log$12.info("formatForCC 开始处理", {
9988
10397
  messageId: ctx.messageId,
9989
10398
  contentType: ctx.contentType,
9990
10399
  chatType: ctx.chatType,
@@ -10000,7 +10409,7 @@ function formatForCC(ctx, isGroup) {
10000
10409
  if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
10001
10410
  if (ctx.threadId) parts.push("[话题回复]");
10002
10411
  const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
10003
- log$13.info("formatForCC 完成", {
10412
+ log$12.info("formatForCC 完成", {
10004
10413
  messageId: ctx.messageId,
10005
10414
  resultLength: result.length,
10006
10415
  hasTime: !!timeStr,
@@ -10010,7 +10419,7 @@ function formatForCC(ctx, isGroup) {
10010
10419
  }
10011
10420
  //#endregion
10012
10421
  //#region src/messaging/inbound/dispatch-cc.ts
10013
- const log$12 = larkLogger("inbound/dispatch-cc");
10422
+ const log$11 = larkLogger("inbound/dispatch-cc");
10014
10423
  async function dispatchToCC(params) {
10015
10424
  const { ctx, account, sessionRouter, processManager } = params;
10016
10425
  const chatId = ctx.chatId;
@@ -10020,7 +10429,7 @@ async function dispatchToCC(params) {
10020
10429
  const ccModel = params.model;
10021
10430
  const maxTurns = params.maxTurns;
10022
10431
  const maxBudgetUsd = params.maxBudgetUsd;
10023
- log$12.info("dispatchToCC 开始", {
10432
+ log$11.info("dispatchToCC 开始", {
10024
10433
  msgId,
10025
10434
  chatId,
10026
10435
  chatType,
@@ -10033,12 +10442,12 @@ async function dispatchToCC(params) {
10033
10442
  threadId,
10034
10443
  userId: ctx.senderId
10035
10444
  });
10036
- log$12.info("会话路由解析完成", {
10445
+ log$11.info("会话路由解析完成", {
10037
10446
  sessionId: route.sessionId,
10038
10447
  cwd: route.cwd,
10039
10448
  type: route.type
10040
10449
  });
10041
- if (params.userContext) log$12.info("用户上下文已注入", {
10450
+ if (params.userContext) log$11.info("用户上下文已注入", {
10042
10451
  userId: params.userContext.userId,
10043
10452
  isTenantIdentity: params.userContext.isTenantIdentity,
10044
10453
  credentialDir: params.userContext.credentialDir
@@ -10077,7 +10486,7 @@ async function dispatchToCC(params) {
10077
10486
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
10078
10487
  buffer = Buffer.concat(chunks);
10079
10488
  } else {
10080
- log$12.warn("图片资源下载返回未知格式,跳过", {
10489
+ log$11.warn("图片资源下载返回未知格式,跳过", {
10081
10490
  fileKey: imgRes.fileKey,
10082
10491
  responseType: typeof response
10083
10492
  });
@@ -10085,7 +10494,7 @@ async function dispatchToCC(params) {
10085
10494
  }
10086
10495
  if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
10087
10496
  } else {
10088
- log$12.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
10497
+ log$11.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
10089
10498
  continue;
10090
10499
  }
10091
10500
  const imgFileName = `${imgRes.fileKey}.png`;
@@ -10095,13 +10504,13 @@ async function dispatchToCC(params) {
10095
10504
  const { writeFile, mkdir } = await import("fs/promises");
10096
10505
  await mkdir(filesDir, { recursive: true });
10097
10506
  await writeFile(imgFilePath, buffer);
10098
- log$12.info("图片已保存到工作目录", {
10507
+ log$11.info("图片已保存到工作目录", {
10099
10508
  fileKey: imgRes.fileKey,
10100
10509
  path: imgFilePath,
10101
10510
  sizeBytes: buffer.length
10102
10511
  });
10103
10512
  } catch (saveErr) {
10104
- log$12.warn("图片保存到工作目录失败", {
10513
+ log$11.warn("图片保存到工作目录失败", {
10105
10514
  fileKey: imgRes.fileKey,
10106
10515
  path: imgFilePath,
10107
10516
  error: saveErr instanceof Error ? saveErr.message : String(saveErr)
@@ -10116,13 +10525,13 @@ async function dispatchToCC(params) {
10116
10525
  data: base64Data
10117
10526
  }
10118
10527
  });
10119
- log$12.info("图片资源下载成功", {
10528
+ log$11.info("图片资源下载成功", {
10120
10529
  fileKey: imgRes.fileKey,
10121
10530
  mediaType,
10122
10531
  sizeBytes: buffer.length
10123
10532
  });
10124
10533
  } catch (err) {
10125
- log$12.warn("图片资源下载失败,跳过", {
10534
+ log$11.warn("图片资源下载失败,跳过", {
10126
10535
  fileKey: imgRes.fileKey,
10127
10536
  error: err instanceof Error ? err.message : String(err)
10128
10537
  });
@@ -10132,7 +10541,7 @@ async function dispatchToCC(params) {
10132
10541
  type: "text",
10133
10542
  text: textPrompt
10134
10543
  }, ...imageBlocks];
10135
- log$12.info("已构建多模态 prompt", {
10544
+ log$11.info("已构建多模态 prompt", {
10136
10545
  textLength: textPrompt.length,
10137
10546
  imageCount: imageBlocks.length
10138
10547
  });
@@ -10140,12 +10549,12 @@ async function dispatchToCC(params) {
10140
10549
  const textWithoutImageRefs = textPrompt.replace(/!\[image\]\([^)]*\)/g, "").replace(/\[图片\]\s*/g, "").trim();
10141
10550
  if (textWithoutImageRefs.length > 0) {
10142
10551
  prompt = textPrompt.replace(/!\[image\]\([^)]*\)/g, "[图片加载失败]");
10143
- log$12.info("图片下载失败但保留文字内容继续调度", {
10552
+ log$11.info("图片下载失败但保留文字内容继续调度", {
10144
10553
  messageId: ctx.messageId,
10145
10554
  textLength: textWithoutImageRefs.length
10146
10555
  });
10147
10556
  } else {
10148
- log$12.warn("纯图片消息且图片全部下载失败,中止调度", {
10557
+ log$11.warn("纯图片消息且图片全部下载失败,中止调度", {
10149
10558
  messageId: ctx.messageId,
10150
10559
  expectedImages: imageResources.length
10151
10560
  });
@@ -10167,7 +10576,7 @@ async function dispatchToCC(params) {
10167
10576
  await fsMkdir(filesDir, { recursive: true });
10168
10577
  let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
10169
10578
  for (const res of attachmentResources) try {
10170
- log$12.info("开始下载非图片附件", {
10579
+ log$11.info("开始下载非图片附件", {
10171
10580
  type: res.type,
10172
10581
  fileKey: res.fileKey,
10173
10582
  fileName: res.fileName,
@@ -10181,7 +10590,7 @@ async function dispatchToCC(params) {
10181
10590
  },
10182
10591
  params: { type: "file" }
10183
10592
  });
10184
- log$12.info("非图片附件 API 响应已收到", {
10593
+ log$11.info("非图片附件 API 响应已收到", {
10185
10594
  type: res.type,
10186
10595
  fileKey: res.fileKey,
10187
10596
  responseType: typeof response,
@@ -10200,7 +10609,7 @@ async function dispatchToCC(params) {
10200
10609
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
10201
10610
  buffer = Buffer.concat(chunks);
10202
10611
  } else {
10203
- log$12.warn("非图片附件下载返回未知格式,跳过", {
10612
+ log$11.warn("非图片附件下载返回未知格式,跳过", {
10204
10613
  type: res.type,
10205
10614
  fileKey: res.fileKey,
10206
10615
  responseType: typeof response
@@ -10208,7 +10617,7 @@ async function dispatchToCC(params) {
10208
10617
  continue;
10209
10618
  }
10210
10619
  } else {
10211
- log$12.warn("非图片附件下载返回空,跳过", {
10620
+ log$11.warn("非图片附件下载返回空,跳过", {
10212
10621
  type: res.type,
10213
10622
  fileKey: res.fileKey
10214
10623
  });
@@ -10231,7 +10640,7 @@ async function dispatchToCC(params) {
10231
10640
  }
10232
10641
  const filePath = path.join(filesDir, savedFileName);
10233
10642
  await fsWriteFile(filePath, buffer);
10234
- log$12.info("非图片附件已保存到工作目录", {
10643
+ log$11.info("非图片附件已保存到工作目录", {
10235
10644
  type: res.type,
10236
10645
  fileKey: res.fileKey,
10237
10646
  savedFileName,
@@ -10265,7 +10674,7 @@ async function dispatchToCC(params) {
10265
10674
  }
10266
10675
  }
10267
10676
  } catch (err) {
10268
- log$12.warn("非图片附件下载失败,替换为降级描述", {
10677
+ log$11.warn("非图片附件下载失败,替换为降级描述", {
10269
10678
  type: res.type,
10270
10679
  fileKey: res.fileKey,
10271
10680
  error: err instanceof Error ? err.message : String(err)
@@ -10284,12 +10693,12 @@ async function dispatchToCC(params) {
10284
10693
  const textBlock = prompt.find((b) => b.type === "text");
10285
10694
  if (textBlock) textBlock.text = updatedTextPrompt;
10286
10695
  }
10287
- log$12.info("非图片附件处理完成", {
10696
+ log$11.info("非图片附件处理完成", {
10288
10697
  totalAttachments: attachmentResources.length,
10289
10698
  promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
10290
10699
  });
10291
10700
  }
10292
- log$12.info("消息格式化完成", {
10701
+ log$11.info("消息格式化完成", {
10293
10702
  promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
10294
10703
  isMultimodal: Array.isArray(prompt),
10295
10704
  isGroup
@@ -10321,13 +10730,13 @@ async function dispatchToCC(params) {
10321
10730
  };
10322
10731
  const cardController = new StreamingCardController(cardDeps);
10323
10732
  registerActiveCard(route.sessionId, cardController);
10324
- log$12.info("StreamingCardController 已创建", {
10733
+ log$11.info("StreamingCardController 已创建", {
10325
10734
  sessionId: route.sessionId,
10326
10735
  chatId,
10327
10736
  replyToMessageId: cardDeps.replyToMessageId
10328
10737
  });
10329
10738
  cardController.ensureCardCreated().catch((err) => {
10330
- log$12.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
10739
+ log$11.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
10331
10740
  });
10332
10741
  if (params.messageStore) {
10333
10742
  const userContent = typeof prompt === "string" ? prompt : textPrompt;
@@ -10338,7 +10747,7 @@ async function dispatchToCC(params) {
10338
10747
  channel: "feishu-bot",
10339
10748
  title: userContent.slice(0, 30) + (userContent.length > 30 ? "..." : "")
10340
10749
  }).catch((err) => {
10341
- log$12.warn("创建飞书端会话记录失败", { error: String(err) });
10750
+ log$11.warn("创建飞书端会话记录失败", { error: String(err) });
10342
10751
  });
10343
10752
  params.messageStore.appendMessage({
10344
10753
  sessionId: storeSessionId,
@@ -10347,18 +10756,18 @@ async function dispatchToCC(params) {
10347
10756
  channel: "feishu-bot",
10348
10757
  metadata: { feishuMessageId: msgId }
10349
10758
  }).catch((err) => {
10350
- log$12.warn("记录飞书用户消息到 store 失败", { error: String(err) });
10759
+ log$11.warn("记录飞书用户消息到 store 失败", { error: String(err) });
10351
10760
  });
10352
10761
  }
10353
10762
  const bridge = new CCStreamBridge(cardController, {
10354
10763
  autoCompleteOnTurnEnd: true,
10355
10764
  sessionKey: route.sessionId
10356
10765
  });
10357
- log$12.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
10766
+ log$11.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
10358
10767
  let feishuAccumText = "";
10359
10768
  const callbacks = {
10360
10769
  onTextDelta: (text) => {
10361
- log$12.debug("CC onTextDelta", {
10770
+ log$11.debug("CC onTextDelta", {
10362
10771
  sessionId: route.sessionId,
10363
10772
  deltaLen: text.length
10364
10773
  });
@@ -10366,28 +10775,28 @@ async function dispatchToCC(params) {
10366
10775
  feishuAccumText += text;
10367
10776
  },
10368
10777
  onThinkingDelta: (text) => {
10369
- log$12.debug("CC onThinkingDelta", {
10778
+ log$11.debug("CC onThinkingDelta", {
10370
10779
  sessionId: route.sessionId,
10371
10780
  deltaLen: text.length
10372
10781
  });
10373
10782
  bridge.onThinkingDelta(text);
10374
10783
  },
10375
10784
  onToolUseStart: (toolName, toolInput) => {
10376
- log$12.info("CC onToolUseStart", {
10785
+ log$11.info("CC onToolUseStart", {
10377
10786
  sessionId: route.sessionId,
10378
10787
  toolName
10379
10788
  });
10380
10789
  bridge.onToolUseStart(toolName, toolInput);
10381
10790
  },
10382
10791
  onToolResult: (toolUseId) => {
10383
- log$12.info("CC onToolResult", {
10792
+ log$11.info("CC onToolResult", {
10384
10793
  sessionId: route.sessionId,
10385
10794
  toolUseId
10386
10795
  });
10387
10796
  bridge.onToolResult(toolUseId);
10388
10797
  },
10389
10798
  onToolProgress: (toolName, elapsedSeconds) => {
10390
- log$12.debug("CC onToolProgress", {
10799
+ log$11.debug("CC onToolProgress", {
10391
10800
  sessionId: route.sessionId,
10392
10801
  toolName,
10393
10802
  elapsedSeconds
@@ -10395,14 +10804,14 @@ async function dispatchToCC(params) {
10395
10804
  bridge.onToolProgress(toolName, elapsedSeconds);
10396
10805
  },
10397
10806
  onTurnEnd: (stopReason) => {
10398
- log$12.info("CC onTurnEnd", {
10807
+ log$11.info("CC onTurnEnd", {
10399
10808
  sessionId: route.sessionId,
10400
10809
  stopReason
10401
10810
  });
10402
10811
  bridge.onTurnEnd(stopReason);
10403
10812
  },
10404
10813
  onResult: (result) => {
10405
- log$12.info("CC onResult", {
10814
+ log$11.info("CC onResult", {
10406
10815
  sessionId: route.sessionId,
10407
10816
  subtype: result.subtype,
10408
10817
  isError: result.isError,
@@ -10422,18 +10831,28 @@ async function dispatchToCC(params) {
10422
10831
  numTurns: result.numTurns
10423
10832
  }
10424
10833
  }).catch((err) => {
10425
- log$12.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
10834
+ log$11.warn("记录飞书 assistant 消息到 store 失败", { error: String(err) });
10835
+ });
10836
+ if (feishuAccumText) handleInteractiveCardTriggers({
10837
+ text: feishuAccumText,
10838
+ chatId,
10839
+ sessionId: route.sessionId,
10840
+ appId: account.configured ? account.appId : "",
10841
+ brand: account.brand,
10842
+ account,
10843
+ replyInThread,
10844
+ threadId: ctx.threadId
10426
10845
  });
10427
10846
  },
10428
10847
  onError: (error) => {
10429
- log$12.error("CC 进程错误", {
10848
+ log$11.error("CC 进程错误", {
10430
10849
  sessionId: route.sessionId,
10431
10850
  error: error.message
10432
10851
  });
10433
10852
  cardController.onError(error, { kind: "cc-process" });
10434
10853
  }
10435
10854
  };
10436
- log$12.info("开始执行 CC 进程", {
10855
+ log$11.info("开始执行 CC 进程", {
10437
10856
  sessionId: route.sessionId,
10438
10857
  cwd: route.cwd,
10439
10858
  promptLength: prompt.length,
@@ -10450,10 +10869,10 @@ async function dispatchToCC(params) {
10450
10869
  maxBudgetUsd,
10451
10870
  userContext: params.userContext
10452
10871
  }, callbacks);
10453
- log$12.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
10872
+ log$11.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
10454
10873
  } catch (err) {
10455
10874
  const errorMessage = err instanceof Error ? err.message : String(err);
10456
- log$12.error("CC 进程 executePrompt 调用异常", {
10875
+ log$11.error("CC 进程 executePrompt 调用异常", {
10457
10876
  sessionId: route.sessionId,
10458
10877
  error: errorMessage
10459
10878
  });
@@ -10475,7 +10894,7 @@ async function dispatchToCC(params) {
10475
10894
  */
10476
10895
  async function dispatchTeammateEval(params) {
10477
10896
  const { chatId, prompt, account, sessionRouter, processManager } = params;
10478
- log$12.info("dispatchTeammateEval 开始", {
10897
+ log$11.info("dispatchTeammateEval 开始", {
10479
10898
  chatId,
10480
10899
  promptLength: prompt.length
10481
10900
  });
@@ -10488,7 +10907,7 @@ async function dispatchTeammateEval(params) {
10488
10907
  userName: void 0
10489
10908
  });
10490
10909
  if (processManager.isSessionBusy?.(route.sessionId)) {
10491
- log$12.info("dispatchTeammateEval 跳过:主 session 正在执行", {
10910
+ log$11.info("dispatchTeammateEval 跳过:主 session 正在执行", {
10492
10911
  chatId,
10493
10912
  sessionId: route.sessionId
10494
10913
  });
@@ -10521,7 +10940,7 @@ async function dispatchTeammateEval(params) {
10521
10940
  const confirmReply = () => {
10522
10941
  if (confirmed) return;
10523
10942
  confirmed = true;
10524
- log$12.info("teammate 确认回复,创建流式卡片", {
10943
+ log$11.info("teammate 确认回复,创建流式卡片", {
10525
10944
  chatId,
10526
10945
  thinkingLen: thinkingBuffer.length,
10527
10946
  textLen: fullTextBuffer.length
@@ -10578,7 +10997,7 @@ async function dispatchTeammateEval(params) {
10578
10997
  fullTextBuffer += text;
10579
10998
  if (fullTextBuffer.trimStart().startsWith(NO_REPLY_TOKEN)) {
10580
10999
  silenced = true;
10581
- log$12.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
11000
+ log$11.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
10582
11001
  chatId,
10583
11002
  bufferLen: fullTextBuffer.length
10584
11003
  });
@@ -10623,7 +11042,7 @@ async function dispatchTeammateEval(params) {
10623
11042
  onTurnEnd: (stopReason) => {
10624
11043
  finalStopReason = stopReason;
10625
11044
  if (stopReason === "tool_use") {
10626
- log$12.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
11045
+ log$11.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
10627
11046
  if (confirmed && bridge) bridge.onTurnEnd(stopReason);
10628
11047
  return;
10629
11048
  }
@@ -10631,7 +11050,7 @@ async function dispatchTeammateEval(params) {
10631
11050
  const trimmed = fullTextBuffer.trim();
10632
11051
  if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
10633
11052
  silenced = true;
10634
- log$12.info("teammate turnEnd 最终判断: 静默", {
11053
+ log$11.info("teammate turnEnd 最终判断: 静默", {
10635
11054
  chatId,
10636
11055
  trimmed: trimmed.slice(0, 60)
10637
11056
  });
@@ -10646,7 +11065,7 @@ async function dispatchTeammateEval(params) {
10646
11065
  resolve();
10647
11066
  },
10648
11067
  onError: (error) => {
10649
- log$12.error("teammate 评估 CC 进程错误", {
11068
+ log$11.error("teammate 评估 CC 进程错误", {
10650
11069
  chatId,
10651
11070
  error: error.message
10652
11071
  });
@@ -10659,14 +11078,14 @@ async function dispatchTeammateEval(params) {
10659
11078
  prompt,
10660
11079
  model: params.model
10661
11080
  }, callbacks).catch((err) => {
10662
- log$12.error("teammate executePrompt 异常", {
11081
+ log$11.error("teammate executePrompt 异常", {
10663
11082
  chatId,
10664
11083
  error: err instanceof Error ? err.message : String(err)
10665
11084
  });
10666
11085
  resolve();
10667
11086
  });
10668
11087
  });
10669
- log$12.info("dispatchTeammateEval 完成", {
11088
+ log$11.info("dispatchTeammateEval 完成", {
10670
11089
  chatId,
10671
11090
  confirmed,
10672
11091
  silenced,
@@ -10677,13 +11096,83 @@ async function dispatchTeammateEval(params) {
10677
11096
  return { replied: confirmed };
10678
11097
  } catch (err) {
10679
11098
  const errorMessage = err instanceof Error ? err.message : String(err);
10680
- log$12.error("dispatchTeammateEval 异常", {
11099
+ log$11.error("dispatchTeammateEval 异常", {
10681
11100
  chatId,
10682
11101
  error: errorMessage
10683
11102
  });
10684
11103
  return { replied: false };
10685
11104
  }
10686
11105
  }
11106
+ /**
11107
+ * 检测 CC 回复文本中的交互标记,并发送对应的独立卡片。
11108
+ *
11109
+ * 标记格式:
11110
+ * - [PERMISSION_REQUEST] ```json { "scopes": ["scope1", "scope2"] } ```
11111
+ * - [ASK_USER] ```json { "question": "...", "phases": [...] } ```
11112
+ */
11113
+ async function handleInteractiveCardTriggers(params) {
11114
+ const { text, chatId, sessionId, appId, brand, account, replyInThread, threadId } = params;
11115
+ try {
11116
+ const authReq = detectAuthRequest(text);
11117
+ if (authReq && appId) {
11118
+ log$11.info("检测到 CC 输出中的权限申请标记", {
11119
+ sessionId,
11120
+ scopes: authReq.scopes
11121
+ });
11122
+ const { card } = buildAuthCard({
11123
+ chatId,
11124
+ sessionId,
11125
+ appId,
11126
+ scopes: authReq.scopes,
11127
+ brand
11128
+ });
11129
+ await LarkClient.fromAccount(account).sdk.im.message.create({
11130
+ params: { receive_id_type: "chat_id" },
11131
+ data: {
11132
+ receive_id: chatId,
11133
+ msg_type: "interactive",
11134
+ content: JSON.stringify(card),
11135
+ ...replyInThread && threadId ? { reply_in_thread: true } : {}
11136
+ }
11137
+ });
11138
+ log$11.info("权限申请卡片已发送", {
11139
+ chatId,
11140
+ sessionId
11141
+ });
11142
+ }
11143
+ const askReq = detectAskRequest(text);
11144
+ if (askReq) {
11145
+ log$11.info("检测到 CC 输出中的提问标记", {
11146
+ sessionId,
11147
+ question: askReq.question.slice(0, 50)
11148
+ });
11149
+ const { card } = buildAskCard({
11150
+ chatId,
11151
+ sessionId,
11152
+ question: askReq.question,
11153
+ phases: askReq.phases
11154
+ });
11155
+ await LarkClient.fromAccount(account).sdk.im.message.create({
11156
+ params: { receive_id_type: "chat_id" },
11157
+ data: {
11158
+ receive_id: chatId,
11159
+ msg_type: "interactive",
11160
+ content: JSON.stringify(card),
11161
+ ...replyInThread && threadId ? { reply_in_thread: true } : {}
11162
+ }
11163
+ });
11164
+ log$11.info("提问卡片已发送", {
11165
+ chatId,
11166
+ sessionId
11167
+ });
11168
+ }
11169
+ } catch (err) {
11170
+ log$11.error("交互式卡片后处理失败", {
11171
+ sessionId,
11172
+ error: err instanceof Error ? err.message : String(err)
11173
+ });
11174
+ }
11175
+ }
10687
11176
  //#endregion
10688
11177
  //#region src/messaging/inbound/permission.ts
10689
11178
  /**
@@ -12189,7 +12678,7 @@ const convertLocation = (raw) => {
12189
12678
  * injected via callbacks in `ConvertContext`. Callers are responsible
12190
12679
  * for creating the appropriate callbacks (UAT / TAT / event push).
12191
12680
  */
12192
- const log$11 = larkLogger("converters/merge-forward");
12681
+ const log$10 = larkLogger("converters/merge-forward");
12193
12682
  /**
12194
12683
  * Recursively expand a merge_forward message.
12195
12684
  *
@@ -12204,7 +12693,7 @@ const log$11 = larkLogger("converters/merge-forward");
12204
12693
  const convertMergeForward = async (_raw, ctx) => {
12205
12694
  const { accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent } = ctx;
12206
12695
  if (!fetchSubMessages) {
12207
- log$11.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
12696
+ log$10.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
12208
12697
  messageId,
12209
12698
  accountId
12210
12699
  });
@@ -12222,23 +12711,23 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
12222
12711
  let items;
12223
12712
  try {
12224
12713
  items = await fetchSubMessages(messageId);
12225
- log$11.info("fetchSubMessages 成功", {
12714
+ log$10.info("fetchSubMessages 成功", {
12226
12715
  messageId,
12227
12716
  itemCount: items.length
12228
12717
  });
12229
12718
  } catch (error) {
12230
- log$11.error("fetch sub-messages failed", {
12719
+ log$10.error("fetch sub-messages failed", {
12231
12720
  messageId,
12232
12721
  error: error instanceof Error ? error.message : String(error)
12233
12722
  });
12234
12723
  return "<forwarded_messages/>";
12235
12724
  }
12236
12725
  if (items.length === 0) {
12237
- log$11.warn("fetchSubMessages 返回空 items", { messageId });
12726
+ log$10.warn("fetchSubMessages 返回空 items", { messageId });
12238
12727
  return "<forwarded_messages/>";
12239
12728
  }
12240
12729
  const childrenMap = buildChildrenMap(items, messageId);
12241
- log$11.info("buildChildrenMap 完成", {
12730
+ log$10.info("buildChildrenMap 完成", {
12242
12731
  messageId,
12243
12732
  parentKeys: [...childrenMap.keys()],
12244
12733
  childCounts: [...childrenMap.entries()].map(([k, v]) => `${k}:${v.length}`)
@@ -12247,7 +12736,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
12247
12736
  if (senderIds.length > 0 && batchResolveNames) try {
12248
12737
  await batchResolveNames(senderIds);
12249
12738
  } catch (err) {
12250
- log$11.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
12739
+ log$10.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
12251
12740
  }
12252
12741
  return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
12253
12742
  }
@@ -12327,7 +12816,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
12327
12816
  const indented = indentLines(content, " ");
12328
12817
  parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
12329
12818
  } catch (err) {
12330
- log$11.warn("failed to convert sub-message", {
12819
+ log$10.warn("failed to convert sub-message", {
12331
12820
  messageId: item.message_id,
12332
12821
  msgType: item.msg_type ?? "unknown",
12333
12822
  error: err instanceof Error ? err.message : String(err)
@@ -12541,7 +13030,7 @@ async function convertMessageContent(raw, messageType, ctx) {
12541
13030
  }
12542
13031
  //#endregion
12543
13032
  //#region src/messaging/inbound/parse-io.ts
12544
- const log$10 = larkLogger("inbound/parse-io");
13033
+ const log$9 = larkLogger("inbound/parse-io");
12545
13034
  /**
12546
13035
  * 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
12547
13036
  * 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
@@ -12561,7 +13050,7 @@ async function fetchCardContent(messageId, larkClient) {
12561
13050
  }
12562
13051
  }))?.data?.items?.[0]?.body?.content ?? void 0;
12563
13052
  } catch (err) {
12564
- log$10.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
13053
+ log$9.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
12565
13054
  return;
12566
13055
  }
12567
13056
  }
@@ -12576,7 +13065,7 @@ async function fetchCardContent(messageId, larkClient) {
12576
13065
  */
12577
13066
  function createFetchSubMessages(larkClient) {
12578
13067
  return async (msgId) => {
12579
- log$10.info("fetchSubMessages 请求", {
13068
+ log$9.info("fetchSubMessages 请求", {
12580
13069
  msgId,
12581
13070
  url: `/open-apis/im/v1/messages/${msgId}`
12582
13071
  });
@@ -12588,7 +13077,7 @@ function createFetchSubMessages(larkClient) {
12588
13077
  card_msg_content_type: "raw_card_content"
12589
13078
  }
12590
13079
  });
12591
- log$10.info("fetchSubMessages 响应", {
13080
+ log$9.info("fetchSubMessages 响应", {
12592
13081
  msgId,
12593
13082
  code: response?.code,
12594
13083
  msg: response?.msg,
@@ -12605,11 +13094,11 @@ function createFetchSubMessages(larkClient) {
12605
13094
  * the account and log function.
12606
13095
  */
12607
13096
  function createParseResolveNames(account) {
12608
- return createBatchResolveNames(account, (...args) => log$10.info(args.map(String).join(" ")));
13097
+ return createBatchResolveNames(account, (...args) => log$9.info(args.map(String).join(" ")));
12609
13098
  }
12610
13099
  //#endregion
12611
13100
  //#region src/messaging/inbound/parse.ts
12612
- const log$9 = larkLogger("inbound/parse");
13101
+ const log$8 = larkLogger("inbound/parse");
12613
13102
  /**
12614
13103
  * Parse a raw Feishu message event into a normalised MessageContext.
12615
13104
  *
@@ -12659,7 +13148,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
12659
13148
  const fullContent = await fetchCardContent(event.message.message_id, larkClient);
12660
13149
  if (fullContent) {
12661
13150
  effectiveContent = fullContent;
12662
- log$9.info("replaced interactive content with full v2 card data");
13151
+ log$8.info("replaced interactive content with full v2 card data");
12663
13152
  }
12664
13153
  }
12665
13154
  const convertCtx = {
@@ -12780,7 +13269,7 @@ var MessageDedup = class {
12780
13269
  };
12781
13270
  //#endregion
12782
13271
  //#region src/messaging/inbound/media-download.ts
12783
- const log$8 = larkLogger("inbound/media-download");
13272
+ const log$7 = larkLogger("inbound/media-download");
12784
13273
  /**
12785
13274
  * 从飞书 API 下载单个图片资源,返回 Buffer + mediaType
12786
13275
  */
@@ -12808,7 +13297,7 @@ async function downloadLarkImage(params) {
12808
13297
  for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
12809
13298
  buffer = Buffer.concat(chunks);
12810
13299
  } else {
12811
- log$8.warn("图片资源下载返回未知格式", {
13300
+ log$7.warn("图片资源下载返回未知格式", {
12812
13301
  fileKey,
12813
13302
  responseType: typeof response
12814
13303
  });
@@ -12816,10 +13305,10 @@ async function downloadLarkImage(params) {
12816
13305
  }
12817
13306
  if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
12818
13307
  } else {
12819
- log$8.warn("图片资源下载返回空", { fileKey });
13308
+ log$7.warn("图片资源下载返回空", { fileKey });
12820
13309
  return null;
12821
13310
  }
12822
- log$8.info("图片资源下载成功", {
13311
+ log$7.info("图片资源下载成功", {
12823
13312
  fileKey,
12824
13313
  mediaType,
12825
13314
  sizeBytes: buffer.length
@@ -12829,7 +13318,7 @@ async function downloadLarkImage(params) {
12829
13318
  mediaType
12830
13319
  };
12831
13320
  } catch (err) {
12832
- log$8.warn("图片资源下载失败", {
13321
+ log$7.warn("图片资源下载失败", {
12833
13322
  fileKey,
12834
13323
  error: err instanceof Error ? err.message : String(err)
12835
13324
  });
@@ -12864,12 +13353,12 @@ async function buildMultimodalPrompt(params) {
12864
13353
  await mkdir(saveDir, { recursive: true });
12865
13354
  const imgFilePath = path.join(saveDir, `${imgRes.fileKey}.png`);
12866
13355
  await writeFile(imgFilePath, buffer);
12867
- log$8.info("图片已保存到工作目录", {
13356
+ log$7.info("图片已保存到工作目录", {
12868
13357
  fileKey: imgRes.fileKey,
12869
13358
  path: imgFilePath
12870
13359
  });
12871
13360
  } catch (saveErr) {
12872
- log$8.warn("图片保存失败", {
13361
+ log$7.warn("图片保存失败", {
12873
13362
  fileKey: imgRes.fileKey,
12874
13363
  error: saveErr instanceof Error ? saveErr.message : String(saveErr)
12875
13364
  });
@@ -12884,7 +13373,7 @@ async function buildMultimodalPrompt(params) {
12884
13373
  });
12885
13374
  }
12886
13375
  if (imageBlocks.length > 0) {
12887
- log$8.info("已构建多模态 prompt", {
13376
+ log$7.info("已构建多模态 prompt", {
12888
13377
  textLength: textPrompt.length,
12889
13378
  imageCount: imageBlocks.length
12890
13379
  });
@@ -12907,7 +13396,7 @@ const TENANT_USER_ID = "tenant";
12907
13396
  * 在消息入口处构建 UserContext,通过参数逐层传递到
12908
13397
  * SessionRouter、ProcessManager、CC 进程环境。
12909
13398
  */
12910
- const log$7 = larkLogger("user/context");
13399
+ const log$6 = larkLogger("user/context");
12911
13400
  /**
12912
13401
  * 构建用户上下文
12913
13402
  *
@@ -12917,7 +13406,7 @@ const log$7 = larkLogger("user/context");
12917
13406
  function buildUserContext(params) {
12918
13407
  const { openId, userId, userName, chatType, workspaceRoot } = params;
12919
13408
  if (chatType === "group") {
12920
- log$7.info("群聊场景,使用 tenant 身份", {
13409
+ log$6.info("群聊场景,使用 tenant 身份", {
12921
13410
  openId,
12922
13411
  chatType
12923
13412
  });
@@ -12930,7 +13419,7 @@ function buildUserContext(params) {
12930
13419
  };
12931
13420
  }
12932
13421
  const effectiveUserId = userId || openId;
12933
- log$7.info("私聊场景,使用用户个人身份", {
13422
+ log$6.info("私聊场景,使用用户个人身份", {
12934
13423
  effectiveUserId,
12935
13424
  openId,
12936
13425
  userId,
@@ -12955,7 +13444,7 @@ function buildUserContext(params) {
12955
13444
  * ├── CLAUDE.md # 用户级人设(跨所有会话共享)
12956
13445
  * └── settings.json # 用户级偏好
12957
13446
  */
12958
- const log$6 = larkLogger("user/user-dir");
13447
+ const log$5 = larkLogger("user/user-dir");
12959
13448
  /**
12960
13449
  * 确保用户目录结构存在
12961
13450
  *
@@ -12964,7 +13453,7 @@ const log$6 = larkLogger("user/user-dir");
12964
13453
  */
12965
13454
  async function ensureUserDirectory(userCtx) {
12966
13455
  const { userId, credentialDir } = userCtx;
12967
- log$6.info("确保用户目录存在", {
13456
+ log$5.info("确保用户目录存在", {
12968
13457
  userId,
12969
13458
  credentialDir
12970
13459
  });
@@ -12972,7 +13461,7 @@ async function ensureUserDirectory(userCtx) {
12972
13461
  const credentialsPath = path.join(credentialDir, "credentials.json");
12973
13462
  if (!existsSync$1(credentialsPath)) {
12974
13463
  await writeFile$1(credentialsPath, JSON.stringify({}, null, 2), "utf-8");
12975
- log$6.info("创建默认 credentials.json", {
13464
+ log$5.info("创建默认 credentials.json", {
12976
13465
  userId,
12977
13466
  path: credentialsPath
12978
13467
  });
@@ -12980,7 +13469,7 @@ async function ensureUserDirectory(userCtx) {
12980
13469
  const mcpServersPath = path.join(credentialDir, "mcp-servers.json");
12981
13470
  if (!existsSync$1(mcpServersPath)) {
12982
13471
  await writeFile$1(mcpServersPath, JSON.stringify({}, null, 2), "utf-8");
12983
- log$6.info("创建默认 mcp-servers.json", {
13472
+ log$5.info("创建默认 mcp-servers.json", {
12984
13473
  userId,
12985
13474
  path: mcpServersPath
12986
13475
  });
@@ -12993,7 +13482,7 @@ async function ensureUserDirectory(userCtx) {
12993
13482
  userName: userCtx.userName
12994
13483
  };
12995
13484
  await writeFile$1(settingsPath, JSON.stringify(defaultSettings, null, 2), "utf-8");
12996
- log$6.info("创建默认 settings.json", {
13485
+ log$5.info("创建默认 settings.json", {
12997
13486
  userId,
12998
13487
  path: settingsPath
12999
13488
  });
@@ -13011,7 +13500,7 @@ async function ensureUserDirectory(userCtx) {
13011
13500
  `这是你的个人配置文件,跨所有会话共享。`,
13012
13501
  `你可以在这里记录用户偏好、常用指令等信息。`
13013
13502
  ].join("\n"), "utf-8");
13014
- log$6.info("创建用户级 CLAUDE.md", {
13503
+ log$5.info("创建用户级 CLAUDE.md", {
13015
13504
  userId,
13016
13505
  path: claudeMdPath
13017
13506
  });
@@ -13020,79 +13509,6 @@ async function ensureUserDirectory(userCtx) {
13020
13509
  }
13021
13510
  larkLogger("user/oauth-proxy");
13022
13511
  //#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
13512
  //#region src/messaging/inbound/reply-policy.ts
13097
13513
  const log$3 = larkLogger("messaging/reply-policy");
13098
13514
  /**
@@ -14001,8 +14417,7 @@ async function main() {
14001
14417
  await teammateConfig.load();
14002
14418
  logger.info("Teammate 配置加载完成", { globalDefault: teammateConfig.getData().globalDefault });
14003
14419
  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");
14420
+ const gatewayHost = process.env.LARKPAL_GATEWAY_HOST || "localhost";
14006
14421
  const messageStore = new SessionMessageStore(workspaceRoot);
14007
14422
  const gateway = createGatewayServer({
14008
14423
  port: gatewayPort,
@@ -14015,15 +14430,10 @@ async function main() {
14015
14430
  },
14016
14431
  messageStore
14017
14432
  });
14018
- await loadPlugins(gateway.app, {
14019
- workspaceRoot,
14020
- externalBaseUrl: process.env.LARKPAL_EXTERNAL_URL
14021
- });
14022
14433
  await gateway.start();
14023
14434
  logger.info("网关 HTTP 服务启动完成", {
14024
14435
  host: gatewayHost,
14025
- port: gatewayPort,
14026
- hasPlugins
14436
+ port: gatewayPort
14027
14437
  });
14028
14438
  const messageDedup = new MessageDedup({
14029
14439
  ttlMs: 6e4,
@@ -14350,8 +14760,6 @@ async function main() {
14350
14760
  wsAbortController.abort();
14351
14761
  logger.info("WebSocket 连接已关闭");
14352
14762
  messageDedup.dispose();
14353
- await disposePlugins();
14354
- logger.info("网关插件已清理");
14355
14763
  await gateway.stop();
14356
14764
  logger.info("网关 HTTP 服务已停止");
14357
14765
  logger.info("LarkPal 已关闭");