@vibe-lark/larkpal 0.1.37 → 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 +690 -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
|
|
@@ -10048,7 +10457,9 @@ async function dispatchToCC(params) {
|
|
|
10048
10457
|
const replyInThread = !!threadId;
|
|
10049
10458
|
const replyToMessageId = threadId ? void 0 : ctx.messageId;
|
|
10050
10459
|
let textPrompt = formatForCC(ctx, isGroup);
|
|
10051
|
-
if (isGroup) textPrompt = `[
|
|
10460
|
+
if (isGroup && replyInThread) textPrompt = `[群聊话题] ${textPrompt}`;
|
|
10461
|
+
else if (isGroup) textPrompt = `[群聊] ${textPrompt}`;
|
|
10462
|
+
else textPrompt = `[私聊] ${textPrompt}`;
|
|
10052
10463
|
const imageResources = ctx.resources.filter((r) => r.type === "image");
|
|
10053
10464
|
let prompt = textPrompt;
|
|
10054
10465
|
if (imageResources.length > 0) {
|
|
@@ -10075,7 +10486,7 @@ async function dispatchToCC(params) {
|
|
|
10075
10486
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
10076
10487
|
buffer = Buffer.concat(chunks);
|
|
10077
10488
|
} else {
|
|
10078
|
-
log$
|
|
10489
|
+
log$11.warn("图片资源下载返回未知格式,跳过", {
|
|
10079
10490
|
fileKey: imgRes.fileKey,
|
|
10080
10491
|
responseType: typeof response
|
|
10081
10492
|
});
|
|
@@ -10083,7 +10494,7 @@ async function dispatchToCC(params) {
|
|
|
10083
10494
|
}
|
|
10084
10495
|
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
10085
10496
|
} else {
|
|
10086
|
-
log$
|
|
10497
|
+
log$11.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
|
|
10087
10498
|
continue;
|
|
10088
10499
|
}
|
|
10089
10500
|
const imgFileName = `${imgRes.fileKey}.png`;
|
|
@@ -10093,13 +10504,13 @@ async function dispatchToCC(params) {
|
|
|
10093
10504
|
const { writeFile, mkdir } = await import("fs/promises");
|
|
10094
10505
|
await mkdir(filesDir, { recursive: true });
|
|
10095
10506
|
await writeFile(imgFilePath, buffer);
|
|
10096
|
-
log$
|
|
10507
|
+
log$11.info("图片已保存到工作目录", {
|
|
10097
10508
|
fileKey: imgRes.fileKey,
|
|
10098
10509
|
path: imgFilePath,
|
|
10099
10510
|
sizeBytes: buffer.length
|
|
10100
10511
|
});
|
|
10101
10512
|
} catch (saveErr) {
|
|
10102
|
-
log$
|
|
10513
|
+
log$11.warn("图片保存到工作目录失败", {
|
|
10103
10514
|
fileKey: imgRes.fileKey,
|
|
10104
10515
|
path: imgFilePath,
|
|
10105
10516
|
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
@@ -10114,13 +10525,13 @@ async function dispatchToCC(params) {
|
|
|
10114
10525
|
data: base64Data
|
|
10115
10526
|
}
|
|
10116
10527
|
});
|
|
10117
|
-
log$
|
|
10528
|
+
log$11.info("图片资源下载成功", {
|
|
10118
10529
|
fileKey: imgRes.fileKey,
|
|
10119
10530
|
mediaType,
|
|
10120
10531
|
sizeBytes: buffer.length
|
|
10121
10532
|
});
|
|
10122
10533
|
} catch (err) {
|
|
10123
|
-
log$
|
|
10534
|
+
log$11.warn("图片资源下载失败,跳过", {
|
|
10124
10535
|
fileKey: imgRes.fileKey,
|
|
10125
10536
|
error: err instanceof Error ? err.message : String(err)
|
|
10126
10537
|
});
|
|
@@ -10130,7 +10541,7 @@ async function dispatchToCC(params) {
|
|
|
10130
10541
|
type: "text",
|
|
10131
10542
|
text: textPrompt
|
|
10132
10543
|
}, ...imageBlocks];
|
|
10133
|
-
log$
|
|
10544
|
+
log$11.info("已构建多模态 prompt", {
|
|
10134
10545
|
textLength: textPrompt.length,
|
|
10135
10546
|
imageCount: imageBlocks.length
|
|
10136
10547
|
});
|
|
@@ -10138,12 +10549,12 @@ async function dispatchToCC(params) {
|
|
|
10138
10549
|
const textWithoutImageRefs = textPrompt.replace(/!\[image\]\([^)]*\)/g, "").replace(/\[图片\]\s*/g, "").trim();
|
|
10139
10550
|
if (textWithoutImageRefs.length > 0) {
|
|
10140
10551
|
prompt = textPrompt.replace(/!\[image\]\([^)]*\)/g, "[图片加载失败]");
|
|
10141
|
-
log$
|
|
10552
|
+
log$11.info("图片下载失败但保留文字内容继续调度", {
|
|
10142
10553
|
messageId: ctx.messageId,
|
|
10143
10554
|
textLength: textWithoutImageRefs.length
|
|
10144
10555
|
});
|
|
10145
10556
|
} else {
|
|
10146
|
-
log$
|
|
10557
|
+
log$11.warn("纯图片消息且图片全部下载失败,中止调度", {
|
|
10147
10558
|
messageId: ctx.messageId,
|
|
10148
10559
|
expectedImages: imageResources.length
|
|
10149
10560
|
});
|
|
@@ -10165,7 +10576,7 @@ async function dispatchToCC(params) {
|
|
|
10165
10576
|
await fsMkdir(filesDir, { recursive: true });
|
|
10166
10577
|
let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
|
|
10167
10578
|
for (const res of attachmentResources) try {
|
|
10168
|
-
log$
|
|
10579
|
+
log$11.info("开始下载非图片附件", {
|
|
10169
10580
|
type: res.type,
|
|
10170
10581
|
fileKey: res.fileKey,
|
|
10171
10582
|
fileName: res.fileName,
|
|
@@ -10179,7 +10590,7 @@ async function dispatchToCC(params) {
|
|
|
10179
10590
|
},
|
|
10180
10591
|
params: { type: "file" }
|
|
10181
10592
|
});
|
|
10182
|
-
log$
|
|
10593
|
+
log$11.info("非图片附件 API 响应已收到", {
|
|
10183
10594
|
type: res.type,
|
|
10184
10595
|
fileKey: res.fileKey,
|
|
10185
10596
|
responseType: typeof response,
|
|
@@ -10198,7 +10609,7 @@ async function dispatchToCC(params) {
|
|
|
10198
10609
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
10199
10610
|
buffer = Buffer.concat(chunks);
|
|
10200
10611
|
} else {
|
|
10201
|
-
log$
|
|
10612
|
+
log$11.warn("非图片附件下载返回未知格式,跳过", {
|
|
10202
10613
|
type: res.type,
|
|
10203
10614
|
fileKey: res.fileKey,
|
|
10204
10615
|
responseType: typeof response
|
|
@@ -10206,7 +10617,7 @@ async function dispatchToCC(params) {
|
|
|
10206
10617
|
continue;
|
|
10207
10618
|
}
|
|
10208
10619
|
} else {
|
|
10209
|
-
log$
|
|
10620
|
+
log$11.warn("非图片附件下载返回空,跳过", {
|
|
10210
10621
|
type: res.type,
|
|
10211
10622
|
fileKey: res.fileKey
|
|
10212
10623
|
});
|
|
@@ -10229,7 +10640,7 @@ async function dispatchToCC(params) {
|
|
|
10229
10640
|
}
|
|
10230
10641
|
const filePath = path.join(filesDir, savedFileName);
|
|
10231
10642
|
await fsWriteFile(filePath, buffer);
|
|
10232
|
-
log$
|
|
10643
|
+
log$11.info("非图片附件已保存到工作目录", {
|
|
10233
10644
|
type: res.type,
|
|
10234
10645
|
fileKey: res.fileKey,
|
|
10235
10646
|
savedFileName,
|
|
@@ -10263,7 +10674,7 @@ async function dispatchToCC(params) {
|
|
|
10263
10674
|
}
|
|
10264
10675
|
}
|
|
10265
10676
|
} catch (err) {
|
|
10266
|
-
log$
|
|
10677
|
+
log$11.warn("非图片附件下载失败,替换为降级描述", {
|
|
10267
10678
|
type: res.type,
|
|
10268
10679
|
fileKey: res.fileKey,
|
|
10269
10680
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -10282,12 +10693,12 @@ async function dispatchToCC(params) {
|
|
|
10282
10693
|
const textBlock = prompt.find((b) => b.type === "text");
|
|
10283
10694
|
if (textBlock) textBlock.text = updatedTextPrompt;
|
|
10284
10695
|
}
|
|
10285
|
-
log$
|
|
10696
|
+
log$11.info("非图片附件处理完成", {
|
|
10286
10697
|
totalAttachments: attachmentResources.length,
|
|
10287
10698
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
|
|
10288
10699
|
});
|
|
10289
10700
|
}
|
|
10290
|
-
log$
|
|
10701
|
+
log$11.info("消息格式化完成", {
|
|
10291
10702
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
|
|
10292
10703
|
isMultimodal: Array.isArray(prompt),
|
|
10293
10704
|
isGroup
|
|
@@ -10319,13 +10730,13 @@ async function dispatchToCC(params) {
|
|
|
10319
10730
|
};
|
|
10320
10731
|
const cardController = new StreamingCardController(cardDeps);
|
|
10321
10732
|
registerActiveCard(route.sessionId, cardController);
|
|
10322
|
-
log$
|
|
10733
|
+
log$11.info("StreamingCardController 已创建", {
|
|
10323
10734
|
sessionId: route.sessionId,
|
|
10324
10735
|
chatId,
|
|
10325
10736
|
replyToMessageId: cardDeps.replyToMessageId
|
|
10326
10737
|
});
|
|
10327
10738
|
cardController.ensureCardCreated().catch((err) => {
|
|
10328
|
-
log$
|
|
10739
|
+
log$11.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
|
|
10329
10740
|
});
|
|
10330
10741
|
if (params.messageStore) {
|
|
10331
10742
|
const userContent = typeof prompt === "string" ? prompt : textPrompt;
|
|
@@ -10336,7 +10747,7 @@ async function dispatchToCC(params) {
|
|
|
10336
10747
|
channel: "feishu-bot",
|
|
10337
10748
|
title: userContent.slice(0, 30) + (userContent.length > 30 ? "..." : "")
|
|
10338
10749
|
}).catch((err) => {
|
|
10339
|
-
log$
|
|
10750
|
+
log$11.warn("创建飞书端会话记录失败", { error: String(err) });
|
|
10340
10751
|
});
|
|
10341
10752
|
params.messageStore.appendMessage({
|
|
10342
10753
|
sessionId: storeSessionId,
|
|
@@ -10345,18 +10756,18 @@ async function dispatchToCC(params) {
|
|
|
10345
10756
|
channel: "feishu-bot",
|
|
10346
10757
|
metadata: { feishuMessageId: msgId }
|
|
10347
10758
|
}).catch((err) => {
|
|
10348
|
-
log$
|
|
10759
|
+
log$11.warn("记录飞书用户消息到 store 失败", { error: String(err) });
|
|
10349
10760
|
});
|
|
10350
10761
|
}
|
|
10351
10762
|
const bridge = new CCStreamBridge(cardController, {
|
|
10352
10763
|
autoCompleteOnTurnEnd: true,
|
|
10353
10764
|
sessionKey: route.sessionId
|
|
10354
10765
|
});
|
|
10355
|
-
log$
|
|
10766
|
+
log$11.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
|
|
10356
10767
|
let feishuAccumText = "";
|
|
10357
10768
|
const callbacks = {
|
|
10358
10769
|
onTextDelta: (text) => {
|
|
10359
|
-
log$
|
|
10770
|
+
log$11.debug("CC onTextDelta", {
|
|
10360
10771
|
sessionId: route.sessionId,
|
|
10361
10772
|
deltaLen: text.length
|
|
10362
10773
|
});
|
|
@@ -10364,28 +10775,28 @@ async function dispatchToCC(params) {
|
|
|
10364
10775
|
feishuAccumText += text;
|
|
10365
10776
|
},
|
|
10366
10777
|
onThinkingDelta: (text) => {
|
|
10367
|
-
log$
|
|
10778
|
+
log$11.debug("CC onThinkingDelta", {
|
|
10368
10779
|
sessionId: route.sessionId,
|
|
10369
10780
|
deltaLen: text.length
|
|
10370
10781
|
});
|
|
10371
10782
|
bridge.onThinkingDelta(text);
|
|
10372
10783
|
},
|
|
10373
10784
|
onToolUseStart: (toolName, toolInput) => {
|
|
10374
|
-
log$
|
|
10785
|
+
log$11.info("CC onToolUseStart", {
|
|
10375
10786
|
sessionId: route.sessionId,
|
|
10376
10787
|
toolName
|
|
10377
10788
|
});
|
|
10378
10789
|
bridge.onToolUseStart(toolName, toolInput);
|
|
10379
10790
|
},
|
|
10380
10791
|
onToolResult: (toolUseId) => {
|
|
10381
|
-
log$
|
|
10792
|
+
log$11.info("CC onToolResult", {
|
|
10382
10793
|
sessionId: route.sessionId,
|
|
10383
10794
|
toolUseId
|
|
10384
10795
|
});
|
|
10385
10796
|
bridge.onToolResult(toolUseId);
|
|
10386
10797
|
},
|
|
10387
10798
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
10388
|
-
log$
|
|
10799
|
+
log$11.debug("CC onToolProgress", {
|
|
10389
10800
|
sessionId: route.sessionId,
|
|
10390
10801
|
toolName,
|
|
10391
10802
|
elapsedSeconds
|
|
@@ -10393,14 +10804,14 @@ async function dispatchToCC(params) {
|
|
|
10393
10804
|
bridge.onToolProgress(toolName, elapsedSeconds);
|
|
10394
10805
|
},
|
|
10395
10806
|
onTurnEnd: (stopReason) => {
|
|
10396
|
-
log$
|
|
10807
|
+
log$11.info("CC onTurnEnd", {
|
|
10397
10808
|
sessionId: route.sessionId,
|
|
10398
10809
|
stopReason
|
|
10399
10810
|
});
|
|
10400
10811
|
bridge.onTurnEnd(stopReason);
|
|
10401
10812
|
},
|
|
10402
10813
|
onResult: (result) => {
|
|
10403
|
-
log$
|
|
10814
|
+
log$11.info("CC onResult", {
|
|
10404
10815
|
sessionId: route.sessionId,
|
|
10405
10816
|
subtype: result.subtype,
|
|
10406
10817
|
isError: result.isError,
|
|
@@ -10420,18 +10831,28 @@ async function dispatchToCC(params) {
|
|
|
10420
10831
|
numTurns: result.numTurns
|
|
10421
10832
|
}
|
|
10422
10833
|
}).catch((err) => {
|
|
10423
|
-
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
|
|
10424
10845
|
});
|
|
10425
10846
|
},
|
|
10426
10847
|
onError: (error) => {
|
|
10427
|
-
log$
|
|
10848
|
+
log$11.error("CC 进程错误", {
|
|
10428
10849
|
sessionId: route.sessionId,
|
|
10429
10850
|
error: error.message
|
|
10430
10851
|
});
|
|
10431
10852
|
cardController.onError(error, { kind: "cc-process" });
|
|
10432
10853
|
}
|
|
10433
10854
|
};
|
|
10434
|
-
log$
|
|
10855
|
+
log$11.info("开始执行 CC 进程", {
|
|
10435
10856
|
sessionId: route.sessionId,
|
|
10436
10857
|
cwd: route.cwd,
|
|
10437
10858
|
promptLength: prompt.length,
|
|
@@ -10448,10 +10869,10 @@ async function dispatchToCC(params) {
|
|
|
10448
10869
|
maxBudgetUsd,
|
|
10449
10870
|
userContext: params.userContext
|
|
10450
10871
|
}, callbacks);
|
|
10451
|
-
log$
|
|
10872
|
+
log$11.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
|
|
10452
10873
|
} catch (err) {
|
|
10453
10874
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
10454
|
-
log$
|
|
10875
|
+
log$11.error("CC 进程 executePrompt 调用异常", {
|
|
10455
10876
|
sessionId: route.sessionId,
|
|
10456
10877
|
error: errorMessage
|
|
10457
10878
|
});
|
|
@@ -10473,7 +10894,7 @@ async function dispatchToCC(params) {
|
|
|
10473
10894
|
*/
|
|
10474
10895
|
async function dispatchTeammateEval(params) {
|
|
10475
10896
|
const { chatId, prompt, account, sessionRouter, processManager } = params;
|
|
10476
|
-
log$
|
|
10897
|
+
log$11.info("dispatchTeammateEval 开始", {
|
|
10477
10898
|
chatId,
|
|
10478
10899
|
promptLength: prompt.length
|
|
10479
10900
|
});
|
|
@@ -10486,7 +10907,7 @@ async function dispatchTeammateEval(params) {
|
|
|
10486
10907
|
userName: void 0
|
|
10487
10908
|
});
|
|
10488
10909
|
if (processManager.isSessionBusy?.(route.sessionId)) {
|
|
10489
|
-
log$
|
|
10910
|
+
log$11.info("dispatchTeammateEval 跳过:主 session 正在执行", {
|
|
10490
10911
|
chatId,
|
|
10491
10912
|
sessionId: route.sessionId
|
|
10492
10913
|
});
|
|
@@ -10519,7 +10940,7 @@ async function dispatchTeammateEval(params) {
|
|
|
10519
10940
|
const confirmReply = () => {
|
|
10520
10941
|
if (confirmed) return;
|
|
10521
10942
|
confirmed = true;
|
|
10522
|
-
log$
|
|
10943
|
+
log$11.info("teammate 确认回复,创建流式卡片", {
|
|
10523
10944
|
chatId,
|
|
10524
10945
|
thinkingLen: thinkingBuffer.length,
|
|
10525
10946
|
textLen: fullTextBuffer.length
|
|
@@ -10576,7 +10997,7 @@ async function dispatchTeammateEval(params) {
|
|
|
10576
10997
|
fullTextBuffer += text;
|
|
10577
10998
|
if (fullTextBuffer.trimStart().startsWith(NO_REPLY_TOKEN)) {
|
|
10578
10999
|
silenced = true;
|
|
10579
|
-
log$
|
|
11000
|
+
log$11.info("teammate 前缀检测: 检测到 NO_REPLY 首输出,立即静默", {
|
|
10580
11001
|
chatId,
|
|
10581
11002
|
bufferLen: fullTextBuffer.length
|
|
10582
11003
|
});
|
|
@@ -10621,7 +11042,7 @@ async function dispatchTeammateEval(params) {
|
|
|
10621
11042
|
onTurnEnd: (stopReason) => {
|
|
10622
11043
|
finalStopReason = stopReason;
|
|
10623
11044
|
if (stopReason === "tool_use") {
|
|
10624
|
-
log$
|
|
11045
|
+
log$11.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
|
|
10625
11046
|
if (confirmed && bridge) bridge.onTurnEnd(stopReason);
|
|
10626
11047
|
return;
|
|
10627
11048
|
}
|
|
@@ -10629,7 +11050,7 @@ async function dispatchTeammateEval(params) {
|
|
|
10629
11050
|
const trimmed = fullTextBuffer.trim();
|
|
10630
11051
|
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
|
|
10631
11052
|
silenced = true;
|
|
10632
|
-
log$
|
|
11053
|
+
log$11.info("teammate turnEnd 最终判断: 静默", {
|
|
10633
11054
|
chatId,
|
|
10634
11055
|
trimmed: trimmed.slice(0, 60)
|
|
10635
11056
|
});
|
|
@@ -10644,7 +11065,7 @@ async function dispatchTeammateEval(params) {
|
|
|
10644
11065
|
resolve();
|
|
10645
11066
|
},
|
|
10646
11067
|
onError: (error) => {
|
|
10647
|
-
log$
|
|
11068
|
+
log$11.error("teammate 评估 CC 进程错误", {
|
|
10648
11069
|
chatId,
|
|
10649
11070
|
error: error.message
|
|
10650
11071
|
});
|
|
@@ -10657,14 +11078,14 @@ async function dispatchTeammateEval(params) {
|
|
|
10657
11078
|
prompt,
|
|
10658
11079
|
model: params.model
|
|
10659
11080
|
}, callbacks).catch((err) => {
|
|
10660
|
-
log$
|
|
11081
|
+
log$11.error("teammate executePrompt 异常", {
|
|
10661
11082
|
chatId,
|
|
10662
11083
|
error: err instanceof Error ? err.message : String(err)
|
|
10663
11084
|
});
|
|
10664
11085
|
resolve();
|
|
10665
11086
|
});
|
|
10666
11087
|
});
|
|
10667
|
-
log$
|
|
11088
|
+
log$11.info("dispatchTeammateEval 完成", {
|
|
10668
11089
|
chatId,
|
|
10669
11090
|
confirmed,
|
|
10670
11091
|
silenced,
|
|
@@ -10675,13 +11096,83 @@ async function dispatchTeammateEval(params) {
|
|
|
10675
11096
|
return { replied: confirmed };
|
|
10676
11097
|
} catch (err) {
|
|
10677
11098
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
10678
|
-
log$
|
|
11099
|
+
log$11.error("dispatchTeammateEval 异常", {
|
|
10679
11100
|
chatId,
|
|
10680
11101
|
error: errorMessage
|
|
10681
11102
|
});
|
|
10682
11103
|
return { replied: false };
|
|
10683
11104
|
}
|
|
10684
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
|
+
}
|
|
10685
11176
|
//#endregion
|
|
10686
11177
|
//#region src/messaging/inbound/permission.ts
|
|
10687
11178
|
/**
|
|
@@ -12187,7 +12678,7 @@ const convertLocation = (raw) => {
|
|
|
12187
12678
|
* injected via callbacks in `ConvertContext`. Callers are responsible
|
|
12188
12679
|
* for creating the appropriate callbacks (UAT / TAT / event push).
|
|
12189
12680
|
*/
|
|
12190
|
-
const log$
|
|
12681
|
+
const log$10 = larkLogger("converters/merge-forward");
|
|
12191
12682
|
/**
|
|
12192
12683
|
* Recursively expand a merge_forward message.
|
|
12193
12684
|
*
|
|
@@ -12201,10 +12692,16 @@ const log$11 = larkLogger("converters/merge-forward");
|
|
|
12201
12692
|
*/
|
|
12202
12693
|
const convertMergeForward = async (_raw, ctx) => {
|
|
12203
12694
|
const { accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent } = ctx;
|
|
12204
|
-
if (!fetchSubMessages)
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12695
|
+
if (!fetchSubMessages) {
|
|
12696
|
+
log$10.warn("fetchSubMessages 回调未注入,无法展开合并转发消息", {
|
|
12697
|
+
messageId,
|
|
12698
|
+
accountId
|
|
12699
|
+
});
|
|
12700
|
+
return {
|
|
12701
|
+
content: "<forwarded_messages/>",
|
|
12702
|
+
resources: []
|
|
12703
|
+
};
|
|
12704
|
+
}
|
|
12208
12705
|
return {
|
|
12209
12706
|
content: await expand(accountId, messageId, resolveUserName, batchResolveNames, fetchSubMessages, convertMessageContent),
|
|
12210
12707
|
resources: []
|
|
@@ -12214,20 +12711,32 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
12214
12711
|
let items;
|
|
12215
12712
|
try {
|
|
12216
12713
|
items = await fetchSubMessages(messageId);
|
|
12714
|
+
log$10.info("fetchSubMessages 成功", {
|
|
12715
|
+
messageId,
|
|
12716
|
+
itemCount: items.length
|
|
12717
|
+
});
|
|
12217
12718
|
} catch (error) {
|
|
12218
|
-
log$
|
|
12719
|
+
log$10.error("fetch sub-messages failed", {
|
|
12219
12720
|
messageId,
|
|
12220
12721
|
error: error instanceof Error ? error.message : String(error)
|
|
12221
12722
|
});
|
|
12222
12723
|
return "<forwarded_messages/>";
|
|
12223
12724
|
}
|
|
12224
|
-
if (items.length === 0)
|
|
12725
|
+
if (items.length === 0) {
|
|
12726
|
+
log$10.warn("fetchSubMessages 返回空 items", { messageId });
|
|
12727
|
+
return "<forwarded_messages/>";
|
|
12728
|
+
}
|
|
12225
12729
|
const childrenMap = buildChildrenMap(items, messageId);
|
|
12730
|
+
log$10.info("buildChildrenMap 完成", {
|
|
12731
|
+
messageId,
|
|
12732
|
+
parentKeys: [...childrenMap.keys()],
|
|
12733
|
+
childCounts: [...childrenMap.entries()].map(([k, v]) => `${k}:${v.length}`)
|
|
12734
|
+
});
|
|
12226
12735
|
const senderIds = collectSenderIds(items, messageId);
|
|
12227
12736
|
if (senderIds.length > 0 && batchResolveNames) try {
|
|
12228
12737
|
await batchResolveNames(senderIds);
|
|
12229
12738
|
} catch (err) {
|
|
12230
|
-
log$
|
|
12739
|
+
log$10.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
|
|
12231
12740
|
}
|
|
12232
12741
|
return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
|
|
12233
12742
|
}
|
|
@@ -12307,7 +12816,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
|
|
|
12307
12816
|
const indented = indentLines(content, " ");
|
|
12308
12817
|
parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
|
|
12309
12818
|
} catch (err) {
|
|
12310
|
-
log$
|
|
12819
|
+
log$10.warn("failed to convert sub-message", {
|
|
12311
12820
|
messageId: item.message_id,
|
|
12312
12821
|
msgType: item.msg_type ?? "unknown",
|
|
12313
12822
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -12521,7 +13030,7 @@ async function convertMessageContent(raw, messageType, ctx) {
|
|
|
12521
13030
|
}
|
|
12522
13031
|
//#endregion
|
|
12523
13032
|
//#region src/messaging/inbound/parse-io.ts
|
|
12524
|
-
const log$
|
|
13033
|
+
const log$9 = larkLogger("inbound/parse-io");
|
|
12525
13034
|
/**
|
|
12526
13035
|
* 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
|
|
12527
13036
|
* 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
|
|
@@ -12541,7 +13050,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
12541
13050
|
}
|
|
12542
13051
|
}))?.data?.items?.[0]?.body?.content ?? void 0;
|
|
12543
13052
|
} catch (err) {
|
|
12544
|
-
log$
|
|
13053
|
+
log$9.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12545
13054
|
return;
|
|
12546
13055
|
}
|
|
12547
13056
|
}
|
|
@@ -12556,6 +13065,10 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
12556
13065
|
*/
|
|
12557
13066
|
function createFetchSubMessages(larkClient) {
|
|
12558
13067
|
return async (msgId) => {
|
|
13068
|
+
log$9.info("fetchSubMessages 请求", {
|
|
13069
|
+
msgId,
|
|
13070
|
+
url: `/open-apis/im/v1/messages/${msgId}`
|
|
13071
|
+
});
|
|
12559
13072
|
const response = await larkClient.sdk.request({
|
|
12560
13073
|
method: "GET",
|
|
12561
13074
|
url: `/open-apis/im/v1/messages/${msgId}`,
|
|
@@ -12564,6 +13077,12 @@ function createFetchSubMessages(larkClient) {
|
|
|
12564
13077
|
card_msg_content_type: "raw_card_content"
|
|
12565
13078
|
}
|
|
12566
13079
|
});
|
|
13080
|
+
log$9.info("fetchSubMessages 响应", {
|
|
13081
|
+
msgId,
|
|
13082
|
+
code: response?.code,
|
|
13083
|
+
msg: response?.msg,
|
|
13084
|
+
itemCount: response?.data?.items?.length ?? 0
|
|
13085
|
+
});
|
|
12567
13086
|
if (response?.code !== 0) throw new Error(`API error: code=${response?.code} msg=${response?.msg}`);
|
|
12568
13087
|
return response?.data?.items ?? [];
|
|
12569
13088
|
};
|
|
@@ -12575,11 +13094,11 @@ function createFetchSubMessages(larkClient) {
|
|
|
12575
13094
|
* the account and log function.
|
|
12576
13095
|
*/
|
|
12577
13096
|
function createParseResolveNames(account) {
|
|
12578
|
-
return createBatchResolveNames(account, (...args) => log$
|
|
13097
|
+
return createBatchResolveNames(account, (...args) => log$9.info(args.map(String).join(" ")));
|
|
12579
13098
|
}
|
|
12580
13099
|
//#endregion
|
|
12581
13100
|
//#region src/messaging/inbound/parse.ts
|
|
12582
|
-
const log$
|
|
13101
|
+
const log$8 = larkLogger("inbound/parse");
|
|
12583
13102
|
/**
|
|
12584
13103
|
* Parse a raw Feishu message event into a normalised MessageContext.
|
|
12585
13104
|
*
|
|
@@ -12629,7 +13148,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
12629
13148
|
const fullContent = await fetchCardContent(event.message.message_id, larkClient);
|
|
12630
13149
|
if (fullContent) {
|
|
12631
13150
|
effectiveContent = fullContent;
|
|
12632
|
-
log$
|
|
13151
|
+
log$8.info("replaced interactive content with full v2 card data");
|
|
12633
13152
|
}
|
|
12634
13153
|
}
|
|
12635
13154
|
const convertCtx = {
|
|
@@ -12750,7 +13269,7 @@ var MessageDedup = class {
|
|
|
12750
13269
|
};
|
|
12751
13270
|
//#endregion
|
|
12752
13271
|
//#region src/messaging/inbound/media-download.ts
|
|
12753
|
-
const log$
|
|
13272
|
+
const log$7 = larkLogger("inbound/media-download");
|
|
12754
13273
|
/**
|
|
12755
13274
|
* 从飞书 API 下载单个图片资源,返回 Buffer + mediaType
|
|
12756
13275
|
*/
|
|
@@ -12778,7 +13297,7 @@ async function downloadLarkImage(params) {
|
|
|
12778
13297
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
12779
13298
|
buffer = Buffer.concat(chunks);
|
|
12780
13299
|
} else {
|
|
12781
|
-
log$
|
|
13300
|
+
log$7.warn("图片资源下载返回未知格式", {
|
|
12782
13301
|
fileKey,
|
|
12783
13302
|
responseType: typeof response
|
|
12784
13303
|
});
|
|
@@ -12786,10 +13305,10 @@ async function downloadLarkImage(params) {
|
|
|
12786
13305
|
}
|
|
12787
13306
|
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
12788
13307
|
} else {
|
|
12789
|
-
log$
|
|
13308
|
+
log$7.warn("图片资源下载返回空", { fileKey });
|
|
12790
13309
|
return null;
|
|
12791
13310
|
}
|
|
12792
|
-
log$
|
|
13311
|
+
log$7.info("图片资源下载成功", {
|
|
12793
13312
|
fileKey,
|
|
12794
13313
|
mediaType,
|
|
12795
13314
|
sizeBytes: buffer.length
|
|
@@ -12799,7 +13318,7 @@ async function downloadLarkImage(params) {
|
|
|
12799
13318
|
mediaType
|
|
12800
13319
|
};
|
|
12801
13320
|
} catch (err) {
|
|
12802
|
-
log$
|
|
13321
|
+
log$7.warn("图片资源下载失败", {
|
|
12803
13322
|
fileKey,
|
|
12804
13323
|
error: err instanceof Error ? err.message : String(err)
|
|
12805
13324
|
});
|
|
@@ -12834,12 +13353,12 @@ async function buildMultimodalPrompt(params) {
|
|
|
12834
13353
|
await mkdir(saveDir, { recursive: true });
|
|
12835
13354
|
const imgFilePath = path.join(saveDir, `${imgRes.fileKey}.png`);
|
|
12836
13355
|
await writeFile(imgFilePath, buffer);
|
|
12837
|
-
log$
|
|
13356
|
+
log$7.info("图片已保存到工作目录", {
|
|
12838
13357
|
fileKey: imgRes.fileKey,
|
|
12839
13358
|
path: imgFilePath
|
|
12840
13359
|
});
|
|
12841
13360
|
} catch (saveErr) {
|
|
12842
|
-
log$
|
|
13361
|
+
log$7.warn("图片保存失败", {
|
|
12843
13362
|
fileKey: imgRes.fileKey,
|
|
12844
13363
|
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
12845
13364
|
});
|
|
@@ -12854,7 +13373,7 @@ async function buildMultimodalPrompt(params) {
|
|
|
12854
13373
|
});
|
|
12855
13374
|
}
|
|
12856
13375
|
if (imageBlocks.length > 0) {
|
|
12857
|
-
log$
|
|
13376
|
+
log$7.info("已构建多模态 prompt", {
|
|
12858
13377
|
textLength: textPrompt.length,
|
|
12859
13378
|
imageCount: imageBlocks.length
|
|
12860
13379
|
});
|
|
@@ -12877,7 +13396,7 @@ const TENANT_USER_ID = "tenant";
|
|
|
12877
13396
|
* 在消息入口处构建 UserContext,通过参数逐层传递到
|
|
12878
13397
|
* SessionRouter、ProcessManager、CC 进程环境。
|
|
12879
13398
|
*/
|
|
12880
|
-
const log$
|
|
13399
|
+
const log$6 = larkLogger("user/context");
|
|
12881
13400
|
/**
|
|
12882
13401
|
* 构建用户上下文
|
|
12883
13402
|
*
|
|
@@ -12887,7 +13406,7 @@ const log$7 = larkLogger("user/context");
|
|
|
12887
13406
|
function buildUserContext(params) {
|
|
12888
13407
|
const { openId, userId, userName, chatType, workspaceRoot } = params;
|
|
12889
13408
|
if (chatType === "group") {
|
|
12890
|
-
log$
|
|
13409
|
+
log$6.info("群聊场景,使用 tenant 身份", {
|
|
12891
13410
|
openId,
|
|
12892
13411
|
chatType
|
|
12893
13412
|
});
|
|
@@ -12900,7 +13419,7 @@ function buildUserContext(params) {
|
|
|
12900
13419
|
};
|
|
12901
13420
|
}
|
|
12902
13421
|
const effectiveUserId = userId || openId;
|
|
12903
|
-
log$
|
|
13422
|
+
log$6.info("私聊场景,使用用户个人身份", {
|
|
12904
13423
|
effectiveUserId,
|
|
12905
13424
|
openId,
|
|
12906
13425
|
userId,
|
|
@@ -12925,7 +13444,7 @@ function buildUserContext(params) {
|
|
|
12925
13444
|
* ├── CLAUDE.md # 用户级人设(跨所有会话共享)
|
|
12926
13445
|
* └── settings.json # 用户级偏好
|
|
12927
13446
|
*/
|
|
12928
|
-
const log$
|
|
13447
|
+
const log$5 = larkLogger("user/user-dir");
|
|
12929
13448
|
/**
|
|
12930
13449
|
* 确保用户目录结构存在
|
|
12931
13450
|
*
|
|
@@ -12934,7 +13453,7 @@ const log$6 = larkLogger("user/user-dir");
|
|
|
12934
13453
|
*/
|
|
12935
13454
|
async function ensureUserDirectory(userCtx) {
|
|
12936
13455
|
const { userId, credentialDir } = userCtx;
|
|
12937
|
-
log$
|
|
13456
|
+
log$5.info("确保用户目录存在", {
|
|
12938
13457
|
userId,
|
|
12939
13458
|
credentialDir
|
|
12940
13459
|
});
|
|
@@ -12942,7 +13461,7 @@ async function ensureUserDirectory(userCtx) {
|
|
|
12942
13461
|
const credentialsPath = path.join(credentialDir, "credentials.json");
|
|
12943
13462
|
if (!existsSync$1(credentialsPath)) {
|
|
12944
13463
|
await writeFile$1(credentialsPath, JSON.stringify({}, null, 2), "utf-8");
|
|
12945
|
-
log$
|
|
13464
|
+
log$5.info("创建默认 credentials.json", {
|
|
12946
13465
|
userId,
|
|
12947
13466
|
path: credentialsPath
|
|
12948
13467
|
});
|
|
@@ -12950,7 +13469,7 @@ async function ensureUserDirectory(userCtx) {
|
|
|
12950
13469
|
const mcpServersPath = path.join(credentialDir, "mcp-servers.json");
|
|
12951
13470
|
if (!existsSync$1(mcpServersPath)) {
|
|
12952
13471
|
await writeFile$1(mcpServersPath, JSON.stringify({}, null, 2), "utf-8");
|
|
12953
|
-
log$
|
|
13472
|
+
log$5.info("创建默认 mcp-servers.json", {
|
|
12954
13473
|
userId,
|
|
12955
13474
|
path: mcpServersPath
|
|
12956
13475
|
});
|
|
@@ -12963,7 +13482,7 @@ async function ensureUserDirectory(userCtx) {
|
|
|
12963
13482
|
userName: userCtx.userName
|
|
12964
13483
|
};
|
|
12965
13484
|
await writeFile$1(settingsPath, JSON.stringify(defaultSettings, null, 2), "utf-8");
|
|
12966
|
-
log$
|
|
13485
|
+
log$5.info("创建默认 settings.json", {
|
|
12967
13486
|
userId,
|
|
12968
13487
|
path: settingsPath
|
|
12969
13488
|
});
|
|
@@ -12981,7 +13500,7 @@ async function ensureUserDirectory(userCtx) {
|
|
|
12981
13500
|
`这是你的个人配置文件,跨所有会话共享。`,
|
|
12982
13501
|
`你可以在这里记录用户偏好、常用指令等信息。`
|
|
12983
13502
|
].join("\n"), "utf-8");
|
|
12984
|
-
log$
|
|
13503
|
+
log$5.info("创建用户级 CLAUDE.md", {
|
|
12985
13504
|
userId,
|
|
12986
13505
|
path: claudeMdPath
|
|
12987
13506
|
});
|
|
@@ -12990,79 +13509,6 @@ async function ensureUserDirectory(userCtx) {
|
|
|
12990
13509
|
}
|
|
12991
13510
|
larkLogger("user/oauth-proxy");
|
|
12992
13511
|
//#endregion
|
|
12993
|
-
//#region src/gateway/plugins/loader.ts
|
|
12994
|
-
const PLUGIN_REGISTRY = {};
|
|
12995
|
-
const log$4 = larkLogger("gateway/plugin-loader");
|
|
12996
|
-
/** 已加载的插件实例列表 */
|
|
12997
|
-
let loadedPlugins = [];
|
|
12998
|
-
/**
|
|
12999
|
-
* 加载并注册所有已声明的插件
|
|
13000
|
-
*
|
|
13001
|
-
* @returns 是否有插件需要外部网络访问(影响网关监听地址)
|
|
13002
|
-
*/
|
|
13003
|
-
async function loadPlugins(app, context) {
|
|
13004
|
-
const pluginNames = (process.env.LARKPAL_PLUGINS || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
13005
|
-
if (pluginNames.length === 0) {
|
|
13006
|
-
log$4.info("未声明任何插件,跳过插件加载");
|
|
13007
|
-
return { requiresExternalAccess: false };
|
|
13008
|
-
}
|
|
13009
|
-
log$4.info("开始加载插件", { plugins: pluginNames });
|
|
13010
|
-
let needsExternal = false;
|
|
13011
|
-
for (const name of pluginNames) {
|
|
13012
|
-
const factoryLoader = PLUGIN_REGISTRY[name];
|
|
13013
|
-
if (!factoryLoader) {
|
|
13014
|
-
log$4.warn("未知插件,跳过", {
|
|
13015
|
-
name,
|
|
13016
|
-
available: Object.keys(PLUGIN_REGISTRY)
|
|
13017
|
-
});
|
|
13018
|
-
continue;
|
|
13019
|
-
}
|
|
13020
|
-
try {
|
|
13021
|
-
const plugin = (await factoryLoader())();
|
|
13022
|
-
plugin.register(app, context);
|
|
13023
|
-
log$4.info("插件路由已注册", {
|
|
13024
|
-
name: plugin.name,
|
|
13025
|
-
version: plugin.version,
|
|
13026
|
-
description: plugin.description,
|
|
13027
|
-
requiresExternalAccess: plugin.requiresExternalAccess
|
|
13028
|
-
});
|
|
13029
|
-
if (plugin.init) {
|
|
13030
|
-
await plugin.init();
|
|
13031
|
-
log$4.info("插件初始化完成", { name: plugin.name });
|
|
13032
|
-
}
|
|
13033
|
-
loadedPlugins.push(plugin);
|
|
13034
|
-
if (plugin.requiresExternalAccess) needsExternal = true;
|
|
13035
|
-
} catch (err) {
|
|
13036
|
-
log$4.error("插件加载失败", {
|
|
13037
|
-
name,
|
|
13038
|
-
error: err instanceof Error ? err.message : String(err)
|
|
13039
|
-
});
|
|
13040
|
-
}
|
|
13041
|
-
}
|
|
13042
|
-
log$4.info("插件加载完成", {
|
|
13043
|
-
loaded: loadedPlugins.map((p) => p.name),
|
|
13044
|
-
requiresExternalAccess: needsExternal
|
|
13045
|
-
});
|
|
13046
|
-
return { requiresExternalAccess: needsExternal };
|
|
13047
|
-
}
|
|
13048
|
-
/**
|
|
13049
|
-
* 关闭所有已加载的插件
|
|
13050
|
-
*/
|
|
13051
|
-
async function disposePlugins() {
|
|
13052
|
-
for (const plugin of loadedPlugins) try {
|
|
13053
|
-
if (plugin.dispose) {
|
|
13054
|
-
await plugin.dispose();
|
|
13055
|
-
log$4.info("插件已清理", { name: plugin.name });
|
|
13056
|
-
}
|
|
13057
|
-
} catch (err) {
|
|
13058
|
-
log$4.warn("插件清理失败", {
|
|
13059
|
-
name: plugin.name,
|
|
13060
|
-
error: err instanceof Error ? err.message : String(err)
|
|
13061
|
-
});
|
|
13062
|
-
}
|
|
13063
|
-
loadedPlugins = [];
|
|
13064
|
-
}
|
|
13065
|
-
//#endregion
|
|
13066
13512
|
//#region src/messaging/inbound/reply-policy.ts
|
|
13067
13513
|
const log$3 = larkLogger("messaging/reply-policy");
|
|
13068
13514
|
/**
|
|
@@ -13971,8 +14417,7 @@ async function main() {
|
|
|
13971
14417
|
await teammateConfig.load();
|
|
13972
14418
|
logger.info("Teammate 配置加载完成", { globalDefault: teammateConfig.getData().globalDefault });
|
|
13973
14419
|
const gatewayPort = 3e3;
|
|
13974
|
-
const
|
|
13975
|
-
const gatewayHost = process.env.LARKPAL_GATEWAY_HOST || (hasPlugins ? "0.0.0.0" : "localhost");
|
|
14420
|
+
const gatewayHost = process.env.LARKPAL_GATEWAY_HOST || "localhost";
|
|
13976
14421
|
const messageStore = new SessionMessageStore(workspaceRoot);
|
|
13977
14422
|
const gateway = createGatewayServer({
|
|
13978
14423
|
port: gatewayPort,
|
|
@@ -13985,15 +14430,10 @@ async function main() {
|
|
|
13985
14430
|
},
|
|
13986
14431
|
messageStore
|
|
13987
14432
|
});
|
|
13988
|
-
await loadPlugins(gateway.app, {
|
|
13989
|
-
workspaceRoot,
|
|
13990
|
-
externalBaseUrl: process.env.LARKPAL_EXTERNAL_URL
|
|
13991
|
-
});
|
|
13992
14433
|
await gateway.start();
|
|
13993
14434
|
logger.info("网关 HTTP 服务启动完成", {
|
|
13994
14435
|
host: gatewayHost,
|
|
13995
|
-
port: gatewayPort
|
|
13996
|
-
hasPlugins
|
|
14436
|
+
port: gatewayPort
|
|
13997
14437
|
});
|
|
13998
14438
|
const messageDedup = new MessageDedup({
|
|
13999
14439
|
ttlMs: 6e4,
|
|
@@ -14320,8 +14760,6 @@ async function main() {
|
|
|
14320
14760
|
wsAbortController.abort();
|
|
14321
14761
|
logger.info("WebSocket 连接已关闭");
|
|
14322
14762
|
messageDedup.dispose();
|
|
14323
|
-
await disposePlugins();
|
|
14324
|
-
logger.info("网关插件已清理");
|
|
14325
14763
|
await gateway.stop();
|
|
14326
14764
|
logger.info("网关 HTTP 服务已停止");
|
|
14327
14765
|
logger.info("LarkPal 已关闭");
|