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