@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.
- package/bin/larkpal.js +0 -0
- package/dist/main.mjs +660 -252
- 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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
9026
|
+
const log$15 = larkLogger("card/image-resolver");
|
|
8618
9027
|
/** Matches complete markdown image syntax: `` */
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
9846
|
+
log$13.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
|
|
9438
9847
|
}
|
|
9439
9848
|
} catch (err) {
|
|
9440
|
-
log$
|
|
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$
|
|
9864
|
+
log$13.warn("forceCloseStreaming: no card to close");
|
|
9456
9865
|
return;
|
|
9457
9866
|
}
|
|
9458
|
-
log$
|
|
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$
|
|
9917
|
+
log$13.info("forceCloseStreaming: 卡片已强制终态化");
|
|
9509
9918
|
if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
|
|
9510
9919
|
} catch (err) {
|
|
9511
|
-
log$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
10049
|
+
log$13.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
|
|
9641
10050
|
return;
|
|
9642
10051
|
}
|
|
9643
|
-
log$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
10552
|
+
log$11.info("图片下载失败但保留文字内容继续调度", {
|
|
10144
10553
|
messageId: ctx.messageId,
|
|
10145
10554
|
textLength: textWithoutImageRefs.length
|
|
10146
10555
|
});
|
|
10147
10556
|
} else {
|
|
10148
|
-
log$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
10766
|
+
log$11.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
|
|
10358
10767
|
let feishuAccumText = "";
|
|
10359
10768
|
const callbacks = {
|
|
10360
10769
|
onTextDelta: (text) => {
|
|
10361
|
-
log$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
12714
|
+
log$10.info("fetchSubMessages 成功", {
|
|
12226
12715
|
messageId,
|
|
12227
12716
|
itemCount: items.length
|
|
12228
12717
|
});
|
|
12229
12718
|
} catch (error) {
|
|
12230
|
-
log$
|
|
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$
|
|
12726
|
+
log$10.warn("fetchSubMessages 返回空 items", { messageId });
|
|
12238
12727
|
return "<forwarded_messages/>";
|
|
12239
12728
|
}
|
|
12240
12729
|
const childrenMap = buildChildrenMap(items, messageId);
|
|
12241
|
-
log$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
13308
|
+
log$7.warn("图片资源下载返回空", { fileKey });
|
|
12820
13309
|
return null;
|
|
12821
13310
|
}
|
|
12822
|
-
log$
|
|
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$
|
|
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$
|
|
13356
|
+
log$7.info("图片已保存到工作目录", {
|
|
12868
13357
|
fileKey: imgRes.fileKey,
|
|
12869
13358
|
path: imgFilePath
|
|
12870
13359
|
});
|
|
12871
13360
|
} catch (saveErr) {
|
|
12872
|
-
log$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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 已关闭");
|