@modelzen/feishu-codex-bridge 0.6.4 → 0.6.6
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/README.md +32 -8
- package/dist/cli.js +280 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
|
|
5
5
|
[](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
|
|
6
6
|
[](https://github.com/modelzen/feishu-codex-bridge/blob/main/LICENSE)
|
|
7
|
+
[](https://bridge.vonvon.cc)
|
|
8
|
+
|
|
9
|
+
🌐 **官网:<https://bridge.vonvon.cc>**
|
|
7
10
|
|
|
8
11
|
> 把飞书 / Lark 桥接到你本机的 [Codex](https://github.com/openai/codex) 或 [Claude Code](https://www.anthropic.com/claude-code),在群里 @ 机器人就能让它在指定项目目录里干活,结果以流式 Markdown 卡片实时回到群里。
|
|
9
12
|
>
|
|
@@ -23,22 +26,42 @@
|
|
|
23
26
|
|
|
24
27
|
## ⚡ 安装
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
两步打开本机网页控制台。装好 Bridge、打开控制台,再在页面里启动后台服务、添加第一个 Bridge 机器人。
|
|
27
30
|
|
|
28
31
|
```bash
|
|
29
|
-
|
|
30
|
-
feishu-codex-bridge
|
|
32
|
+
# 1. 全局安装 Bridge
|
|
33
|
+
npm i -g @modelzen/feishu-codex-bridge
|
|
34
|
+
|
|
35
|
+
# 2. 打开本机网页控制台
|
|
36
|
+
# 启动服务、添加好机器人后,这条前台命令即可 Ctrl+C 关闭
|
|
37
|
+
feishu-codex-bridge web
|
|
31
38
|
```
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
不想自己敲?把下面这段发给 **Codex / Claude** 等 AI agent,让它替你装好并跑起来:
|
|
34
41
|
|
|
35
42
|
```text
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
帮我在这台电脑上安装并跑起来 feishu-codex-bridge:
|
|
44
|
+
|
|
45
|
+
1) 先 node -v 确认有 Node.js(≥18),没有就先装好;
|
|
46
|
+
2) 再 codex --version 确认已经安装了 codex CLI,没有就先装好;
|
|
47
|
+
3) 全局安装:
|
|
48
|
+
npm i -g @modelzen/feishu-codex-bridge
|
|
49
|
+
4) 前台运行,打开本机 Web 控制台:
|
|
50
|
+
feishu-codex-bridge web
|
|
51
|
+
它一启动会打印一个 http://127.0.0.1:xxxx/?token=... 的链接。
|
|
52
|
+
5) 把那个链接发给我;
|
|
53
|
+
6) 然后用文字告诉我接下来怎么做:
|
|
54
|
+
- 我在浏览器打开这个链接;
|
|
55
|
+
- 点页面上的「启动」按钮,把后台服务跑起来;
|
|
56
|
+
- 启动后页面会自动进入可写控制台,在里面扫码添加我的第一个飞书机器人;
|
|
57
|
+
- 成功创建机器人后,前台那条 feishu-codex-bridge web 就可以 Ctrl+C 关掉,不影响后台服务。
|
|
39
58
|
```
|
|
40
59
|
|
|
41
|
-
|
|
60
|
+
### 三分钟,看它入职你的飞书
|
|
61
|
+
|
|
62
|
+
从全局装包、打开本机网页控制台,到扫码建机器人、私聊报到,一整段带解说录屏。点击下面封面去 B 站观看高清版。
|
|
63
|
+
|
|
64
|
+
[](https://www.bilibili.com/video/BV1xP7V6fESb)
|
|
42
65
|
|
|
43
66
|
---
|
|
44
67
|
|
|
@@ -140,6 +163,7 @@ npm test # vitest
|
|
|
140
163
|
|
|
141
164
|
## 💬 文档 & 交流
|
|
142
165
|
|
|
166
|
+
- 🌐 **官网**:<https://bridge.vonvon.cc>
|
|
143
167
|
- 🎀 **图文介绍**:<https://my.feishu.cn/docx/AFKNdf4QaooL5OxSR8bc5H7vn7b> —— 配大量截图,讲清它在飞书里长什么样、怎么用。
|
|
144
168
|
- 📖 **命令手册**:<https://my.feishu.cn/wiki/PZ23wGr7JiKK5RkIG4rcZXzGn5g> —— 各场景可用命令速查。
|
|
145
169
|
- 🐛 **反馈 / 贡献**:<https://github.com/modelzen/feishu-codex-bridge/issues>
|
package/dist/cli.js
CHANGED
|
@@ -1106,7 +1106,7 @@ ${lines || "\uFF08\u65E0\uFF09"}`));
|
|
|
1106
1106
|
return card(elements, { forward: false });
|
|
1107
1107
|
}
|
|
1108
1108
|
function buildCliBridgeTaskCompletionCard(input2) {
|
|
1109
|
-
const verb = input2.replyDoneAt ? "\u2705 \u5DF2\
|
|
1109
|
+
const verb = input2.replyDoneAt ? "\u2705 \u5DF2\u6536\u5DE5" : input2.status === "failed" ? "\u274C \u4EFB\u52A1\u5931\u8D25" : pickCopy(COPY.completion, input2.id || input2.cwd);
|
|
1110
1110
|
const elements = [
|
|
1111
1111
|
titleEl(verb),
|
|
1112
1112
|
metaLine(input2.source, input2.cwd)
|
|
@@ -1123,7 +1123,7 @@ ${codeBlock(chunk, "text")}`));
|
|
|
1123
1123
|
}
|
|
1124
1124
|
if (input2.replyEnabled) {
|
|
1125
1125
|
const expiresAt = input2.replyExpiresAt ? `\uFF08\u6709\u6548\u671F\u81F3 ${new Date(input2.replyExpiresAt).toLocaleString("zh-CN")}\uFF09` : "";
|
|
1126
|
-
elements.push(actions([button("\
|
|
1126
|
+
elements.push(actions([button("\u2705 \u6536\u5DE5", { a: CLI.taskCompletionDone, id: input2.id }, "primary")]));
|
|
1127
1127
|
elements.push(note(pickCopy(COPY.footerReply, input2.id || input2.cwd) + expiresAt));
|
|
1128
1128
|
} else if (input2.replyDoneAt) {
|
|
1129
1129
|
elements.push(actions([disabledButton("\u2705 \u5DF2\u5B8C\u6210", "primary")]));
|
|
@@ -1190,8 +1190,8 @@ var init_cards2 = __esm({
|
|
|
1190
1190
|
"\u4EBA\u5728\u684C\u524D\u6211\u5C31\u95ED\u5634,\u4E00\u5207\u56DE\u5F52\u7EC8\u7AEF\u3002"
|
|
1191
1191
|
],
|
|
1192
1192
|
footerReply: [
|
|
1193
|
-
"\u{1F4AC} \u76F4\u63A5\u56DE\u590D\u8FD9\u6761\u6D88\u606F,\u5C31\u80FD\u63A5\u7740\u652F\u4F7F\u5B83\u5E72\u6D3B;\u6216\u70B9\u300C\
|
|
1194
|
-
"\u{1F4AC} \u56DE\u6211\u4E00\u53E5\u8BDD,\u5B83\u7ACB\u523B\u63A5\u7740\u8DD1;\u4E0D\u60F3\u7EE7\u7EED\u5C31\u70B9\u300C\
|
|
1193
|
+
"\u{1F4AC} \u76F4\u63A5\u56DE\u590D\u8FD9\u6761\u6D88\u606F,\u5C31\u80FD\u63A5\u7740\u652F\u4F7F\u5B83\u5E72\u6D3B;\u6216\u70B9\u300C\u6536\u5DE5\u300D\u8BA9\u5B83\u9000\u51FA\u3002",
|
|
1194
|
+
"\u{1F4AC} \u56DE\u6211\u4E00\u53E5\u8BDD,\u5B83\u7ACB\u523B\u63A5\u7740\u8DD1;\u4E0D\u60F3\u7EE7\u7EED\u5C31\u70B9\u300C\u6536\u5DE5\u300D\u3002"
|
|
1195
1195
|
]
|
|
1196
1196
|
};
|
|
1197
1197
|
}
|
|
@@ -3126,14 +3126,19 @@ function mapNotification(n, ctx) {
|
|
|
3126
3126
|
function mapItemStart(item, ctx) {
|
|
3127
3127
|
switch (item.type) {
|
|
3128
3128
|
case "commandExecution":
|
|
3129
|
-
return { type: "tool_use", itemId: item.id, title: item.command, detail: String(item.cwd) };
|
|
3129
|
+
return { type: "tool_use", itemId: item.id, title: item.command, detail: String(item.cwd), kind: "command" };
|
|
3130
3130
|
case "fileChange":
|
|
3131
|
-
return { type: "tool_use", itemId: item.id, title: fileChangeTitle(item.changes, ctx?.cwd) };
|
|
3131
|
+
return { type: "tool_use", itemId: item.id, title: fileChangeTitle(item.changes, ctx?.cwd), kind: "file" };
|
|
3132
3132
|
case "webSearch":
|
|
3133
|
-
return {
|
|
3133
|
+
return {
|
|
3134
|
+
type: "tool_use",
|
|
3135
|
+
itemId: item.id,
|
|
3136
|
+
title: item.query ? `\u8054\u7F51\u641C\u7D22\uFF1A${item.query}` : "\u8054\u7F51\u641C\u7D22",
|
|
3137
|
+
kind: "search"
|
|
3138
|
+
};
|
|
3134
3139
|
case "mcpToolCall":
|
|
3135
3140
|
case "dynamicToolCall":
|
|
3136
|
-
return { type: "tool_use", itemId: item.id, title: "\u5DE5\u5177\u8C03\u7528" };
|
|
3141
|
+
return { type: "tool_use", itemId: item.id, title: "\u5DE5\u5177\u8C03\u7528", kind: "tool" };
|
|
3137
3142
|
default:
|
|
3138
3143
|
return null;
|
|
3139
3144
|
}
|
|
@@ -3803,7 +3808,8 @@ function createTurnMapper(ctx = {}) {
|
|
|
3803
3808
|
type: "tool_use",
|
|
3804
3809
|
itemId: String(b.id ?? `tool${++blockSeq}`),
|
|
3805
3810
|
title: toolTitle(b.name ?? "\u5DE5\u5177", b.input ?? {}, ctx.cwd),
|
|
3806
|
-
detail: toolDetail(b.name ?? "", b.input ?? {})
|
|
3811
|
+
detail: toolDetail(b.name ?? "", b.input ?? {}),
|
|
3812
|
+
kind: toolKind(b.name ?? "")
|
|
3807
3813
|
});
|
|
3808
3814
|
}
|
|
3809
3815
|
}
|
|
@@ -3922,6 +3928,26 @@ function toolTitle(name, input2, cwd) {
|
|
|
3922
3928
|
return name || "\u5DE5\u5177\u8C03\u7528";
|
|
3923
3929
|
}
|
|
3924
3930
|
}
|
|
3931
|
+
function toolKind(name) {
|
|
3932
|
+
switch (name) {
|
|
3933
|
+
case "Bash":
|
|
3934
|
+
case "BashOutput":
|
|
3935
|
+
return "command";
|
|
3936
|
+
case "Read":
|
|
3937
|
+
case "Write":
|
|
3938
|
+
case "Edit":
|
|
3939
|
+
case "MultiEdit":
|
|
3940
|
+
case "NotebookEdit":
|
|
3941
|
+
return "file";
|
|
3942
|
+
case "Glob":
|
|
3943
|
+
case "Grep":
|
|
3944
|
+
case "WebFetch":
|
|
3945
|
+
case "WebSearch":
|
|
3946
|
+
return "search";
|
|
3947
|
+
default:
|
|
3948
|
+
return "tool";
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3925
3951
|
function toolDetail(name, input2) {
|
|
3926
3952
|
if (name === "Bash" && typeof input2.description === "string" && input2.description) {
|
|
3927
3953
|
return input2.description;
|
|
@@ -5512,7 +5538,11 @@ function buildResumeCard(state) {
|
|
|
5512
5538
|
if (state.threads.length === 0) {
|
|
5513
5539
|
elements.push(md("_\u8BE5\u76EE\u5F55\u4E0B\u8FD8\u6CA1\u6709\u5386\u53F2\u4F1A\u8BDD\u3002\u76F4\u63A5 @\u6211 \u5373\u53EF\u65B0\u5EFA\u3002_"));
|
|
5514
5540
|
} else {
|
|
5515
|
-
elements.push(
|
|
5541
|
+
elements.push(
|
|
5542
|
+
note(
|
|
5543
|
+
state.flat ? "\u70B9\u4E00\u6761\u5373\u5207\u56DE \u2014\u2014 \u5C31\u5730\u7EE7\u7EED\uFF0C\u4E0D\u53E6\u8D77\u8BDD\u9898\u3002" : "\u70B9\u4E00\u6761\u5373\u6062\u590D \u2014\u2014 \u5728\u65B0\u8BDD\u9898\u91CC\u6253\u5F00\u5386\u53F2\u3001\u53EF\u76F4\u63A5\u7EE7\u7EED\u3002"
|
|
5544
|
+
)
|
|
5545
|
+
);
|
|
5516
5546
|
for (const t of state.threads) {
|
|
5517
5547
|
const title = (t.name?.trim() || t.preview.trim() || "(\u65E0\u6458\u8981)").replace(/\s+/g, " ");
|
|
5518
5548
|
const label = `\u21A9\uFE0F ${pickerTime(t.updatedAt || t.createdAt)} \xB7 ${truncate(title, RESUME_TITLE_MAX)}`;
|
|
@@ -5525,7 +5555,19 @@ function buildResumeLaunchingCard(state) {
|
|
|
5525
5555
|
return card([md("\u23F3 \u6B63\u5728\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u2026"), note(metaNote(state))], { summary: "\u6062\u590D\u4E2D" });
|
|
5526
5556
|
}
|
|
5527
5557
|
function buildResumeDoneCard(state) {
|
|
5528
|
-
|
|
5558
|
+
const line = state.flat ? "\u21A9\uFE0F \u5DF2\u5207\u56DE \u2014\u2014 \u5C31\u5730\u7EE7\u7EED\uFF0C\u4E0A\u9762\u7684\u5386\u53F2\u6D88\u606F\u4FDD\u7559\u3002" : "\u2705 \u5DF2\u6062\u590D \u2014\u2014 \u5DF2\u5728\u4E0A\u65B9\u65B0\u8BDD\u9898\u6253\u5F00\uFF0C\u53EF\u76F4\u63A5\u7EE7\u7EED\u3002";
|
|
5559
|
+
return card([md(line), note(metaNote(state))], { summary: "\u5DF2\u6062\u590D" });
|
|
5560
|
+
}
|
|
5561
|
+
function buildClearedCard() {
|
|
5562
|
+
return card(
|
|
5563
|
+
[
|
|
5564
|
+
md("\u{1F9F9} **\u5DF2\u5F00\u542F\u5168\u65B0\u4F1A\u8BDD**"),
|
|
5565
|
+
md("\u2705 \u4E0A\u4E0B\u6587\u5DF2\u6E05\u7A7A \u2014\u2014 \u6211\u4ECE\u8FD9\u91CC\u91CD\u65B0\u5F00\u59CB\uFF0C\u4E0D\u518D\u53C2\u8003\u4E0A\u9762\u7684\u5BF9\u8BDD\u3002"),
|
|
5566
|
+
md("\u{1F4AC} \u98DE\u4E66\u804A\u5929\u8BB0\u5F55\u4E0D\u53D7\u5F71\u54CD \u2014\u2014 \u4E0A\u9762\u7684\u6D88\u606F\u90FD\u8FD8\u5728\uFF0C\u53EA\u662F\u6211\u4E0D\u518D\u8BFB\u53D6\u5B83\u4EEC\u3002"),
|
|
5567
|
+
md("\u{1F5C2}\uFE0F \u521A\u624D\u90A3\u6BB5\u5DF2\u5F52\u6863 \u2014\u2014 \u53D1 `/resume` \u968F\u65F6\u5207\u56DE\u7EE7\u7EED\u3002")
|
|
5568
|
+
],
|
|
5569
|
+
{ summary: "\u5DF2\u6E05\u7A7A\u4E0A\u4E0B\u6587" }
|
|
5570
|
+
);
|
|
5529
5571
|
}
|
|
5530
5572
|
function buildResumeErrorCard(state, message) {
|
|
5531
5573
|
return card([md(`\u274C \u6062\u590D\u5931\u8D25\uFF1A${truncate(message, 200)}`), note(metaNote(state))], { summary: "\u6062\u590D\u5931\u8D25" });
|
|
@@ -5582,6 +5624,8 @@ function buildHelpCard(scope, noMention = true, isAdmin2 = false, caps) {
|
|
|
5582
5624
|
if (showGoal) lines.push(goalLine);
|
|
5583
5625
|
lines.push("\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6", "\xB7 `/context` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4");
|
|
5584
5626
|
if (showCompact) lines.push(compactLine);
|
|
5627
|
+
if (isAdmin2) lines.push("\xB7 `/clear` \u2192 \u6E05\u7A7A\u4E0A\u4E0B\u6587\uFF0C\u5F00\u4E00\u6BB5\u5168\u65B0\u4F1A\u8BDD\uFF08\u98DE\u4E66\u6D88\u606F\u4FDD\u7559\uFF09");
|
|
5628
|
+
if (isAdmin2 && showResume) lines.push("\xB7 `/resume` \u2192 \u5207\u56DE\u5386\u53F2\u4F1A\u8BDD");
|
|
5585
5629
|
if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
5586
5630
|
lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
5587
5631
|
elements.push(md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"), hr(), md(lines.join("\n")));
|
|
@@ -5625,6 +5669,8 @@ function buildWelcomeCard(kind, docUrl, noMention = true, caps, agentName = "Cod
|
|
|
5625
5669
|
talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"),
|
|
5626
5670
|
...showGoal ? [goalLine] : [],
|
|
5627
5671
|
"\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6",
|
|
5672
|
+
"\xB7 `/clear` \u2192 \u6E05\u7A7A\u4E0A\u4E0B\u6587\uFF0C\u5F00\u65B0\u4F1A\u8BDD\uFF08\u7BA1\u7406\u5458\uFF0C\u98DE\u4E66\u6D88\u606F\u4FDD\u7559\uFF09",
|
|
5673
|
+
...showResume ? ["\xB7 `/resume` \u2192 \u5207\u56DE\u5386\u53F2\u4F1A\u8BDD\uFF08\u7BA1\u7406\u5458\uFF09"] : [],
|
|
5628
5674
|
"\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09",
|
|
5629
5675
|
"\xB7 `/help` \u2192 \u547D\u4EE4\u901F\u67E5\u5361"
|
|
5630
5676
|
].join("\n")
|
|
@@ -7007,6 +7053,7 @@ function reduce(state, evt) {
|
|
|
7007
7053
|
id: evt.itemId,
|
|
7008
7054
|
title: evt.title,
|
|
7009
7055
|
detail: evt.detail,
|
|
7056
|
+
kind: evt.kind,
|
|
7010
7057
|
status: "running"
|
|
7011
7058
|
};
|
|
7012
7059
|
return {
|
|
@@ -7349,25 +7396,61 @@ function renderCleanBody(bodyMarkdown, images) {
|
|
|
7349
7396
|
}
|
|
7350
7397
|
|
|
7351
7398
|
// src/card/tool-render.ts
|
|
7352
|
-
var HEADER_TITLE_MAX =
|
|
7399
|
+
var HEADER_TITLE_MAX = 120;
|
|
7353
7400
|
var OUTPUT_MAX = 1200;
|
|
7401
|
+
var CMD_BLOCK_MAX = 1e3;
|
|
7402
|
+
var SUMMARY_LINE_MAX = 400;
|
|
7354
7403
|
var BODY_TOTAL_MAX = 2500;
|
|
7404
|
+
function statusIcon(status) {
|
|
7405
|
+
return status === "done" ? "\u2705" : status === "error" ? "\u274C" : "\u23F3";
|
|
7406
|
+
}
|
|
7407
|
+
function kindGlyph(kind) {
|
|
7408
|
+
switch (kind) {
|
|
7409
|
+
case "command":
|
|
7410
|
+
return "\u{1F527}";
|
|
7411
|
+
case "file":
|
|
7412
|
+
return "\u{1F4C4}";
|
|
7413
|
+
case "search":
|
|
7414
|
+
return "\u{1F50D}";
|
|
7415
|
+
default:
|
|
7416
|
+
return "";
|
|
7417
|
+
}
|
|
7418
|
+
}
|
|
7419
|
+
function leadGlyphs(tool) {
|
|
7420
|
+
const glyph = kindGlyph(tool.kind);
|
|
7421
|
+
return glyph ? `${statusIcon(tool.status)} ${glyph}` : statusIcon(tool.status);
|
|
7422
|
+
}
|
|
7355
7423
|
function toolHeaderText(tool) {
|
|
7356
|
-
|
|
7357
|
-
|
|
7424
|
+
return `${leadGlyphs(tool)} **${escapeInline2(truncate3(tool.title, HEADER_TITLE_MAX))}**`;
|
|
7425
|
+
}
|
|
7426
|
+
function toolSummaryLine(tool) {
|
|
7427
|
+
return `- ${leadGlyphs(tool)} **${escapeInline2(truncate3(tool.title, SUMMARY_LINE_MAX))}**`;
|
|
7358
7428
|
}
|
|
7359
7429
|
function toolBodyMd(tool) {
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
const label = tool.status === "error" ? "Error" : "Output";
|
|
7364
|
-
const block = tool.output.startsWith("```") ? tool.output : bashBlock(tool.output);
|
|
7365
|
-
const body = `**${label}**
|
|
7366
|
-
${block}`;
|
|
7430
|
+
const lead = invocationMd(tool);
|
|
7431
|
+
const outputPart = tool.output ? outputBlock(tool) : tool.status === "running" ? "_\u8FD0\u884C\u4E2D\u2026_" : tool.kind === "search" ? "_\uFF08\u641C\u7D22\u7ED3\u679C\u5DF2\u7528\u4E8E\u4F5C\u7B54\uFF0C\u4E0D\u5355\u72EC\u56DE\u4F20\uFF09_" : "";
|
|
7432
|
+
const body = [lead, outputPart].filter(Boolean).join("\n\n");
|
|
7367
7433
|
if (body.length <= BODY_TOTAL_MAX) return body;
|
|
7368
7434
|
return `${body.slice(0, BODY_TOTAL_MAX)}\u2026
|
|
7369
7435
|
|
|
7370
|
-
_\uFF08\
|
|
7436
|
+
_\uFF08\u5185\u5BB9\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF09_`;
|
|
7437
|
+
}
|
|
7438
|
+
function invocationMd(tool) {
|
|
7439
|
+
if (tool.kind !== "command") return "";
|
|
7440
|
+
const cmd = tool.title.trim();
|
|
7441
|
+
if (!cmd) return "";
|
|
7442
|
+
const detail = tool.detail ? `
|
|
7443
|
+
\`${escapeInline2(tool.detail)}\`` : "";
|
|
7444
|
+
return `**\u547D\u4EE4**
|
|
7445
|
+
\`\`\`bash
|
|
7446
|
+
${truncate3(cmd, CMD_BLOCK_MAX)}
|
|
7447
|
+
\`\`\`${detail}`;
|
|
7448
|
+
}
|
|
7449
|
+
function outputBlock(tool) {
|
|
7450
|
+
const label = tool.status === "error" ? "Error" : "Output";
|
|
7451
|
+
const block = tool.output.startsWith("```") ? tool.output : bashBlock(tool.output);
|
|
7452
|
+
return `**${label}**
|
|
7453
|
+
${block}`;
|
|
7371
7454
|
}
|
|
7372
7455
|
function bashBlock(output) {
|
|
7373
7456
|
const note2 = output.length > OUTPUT_MAX ? `
|
|
@@ -7468,6 +7551,8 @@ var CONTROLS_EID = "controls";
|
|
|
7468
7551
|
var REASONING_MAX = 1500;
|
|
7469
7552
|
var COLLAPSE_TOOL_THRESHOLD = 3;
|
|
7470
7553
|
var PROCESS_BODY_BUDGET = 22e3;
|
|
7554
|
+
var PROCESS_COMPONENT_BUDGET = 120;
|
|
7555
|
+
var SUMMARY_BODY_MAX = 6e3;
|
|
7471
7556
|
function buildRunCard(rc) {
|
|
7472
7557
|
const state = rc.rs;
|
|
7473
7558
|
const running = state.terminal === "running";
|
|
@@ -7529,7 +7614,7 @@ function renderTerminal(state, rc) {
|
|
|
7529
7614
|
const toolCount = blocks.reduce((n, b) => b.kind === "tool" ? n + 1 : n, 0);
|
|
7530
7615
|
elements.push(
|
|
7531
7616
|
collapsiblePanelEl({
|
|
7532
|
-
title: processTitle(Boolean(reasoning), toolCount),
|
|
7617
|
+
title: processTitle(Boolean(reasoning), toolCount, state.terminal),
|
|
7533
7618
|
expanded: false,
|
|
7534
7619
|
border: "grey",
|
|
7535
7620
|
elements: processEls
|
|
@@ -7577,7 +7662,9 @@ function lastTextIndex(blocks) {
|
|
|
7577
7662
|
}
|
|
7578
7663
|
function buildProcessBody(reasoning, blocks) {
|
|
7579
7664
|
const rich = processElements(reasoning, blocks, false);
|
|
7580
|
-
if (estimateSize2(rich) <= PROCESS_BODY_BUDGET)
|
|
7665
|
+
if (estimateSize2(rich) <= PROCESS_BODY_BUDGET && estimateComponents(rich) <= PROCESS_COMPONENT_BUDGET) {
|
|
7666
|
+
return rich;
|
|
7667
|
+
}
|
|
7581
7668
|
return processElements(reasoning, blocks, true);
|
|
7582
7669
|
}
|
|
7583
7670
|
function processElements(reasoning, blocks, compactTools) {
|
|
@@ -7592,18 +7679,25 @@ function processElements(reasoning, blocks, compactTools) {
|
|
|
7592
7679
|
}
|
|
7593
7680
|
return out;
|
|
7594
7681
|
}
|
|
7595
|
-
function processTitle(hasReasoning, toolCount) {
|
|
7682
|
+
function processTitle(hasReasoning, toolCount, terminal) {
|
|
7683
|
+
const icon = terminal === "interrupted" ? "\u23F9" : terminal === "idle_timeout" ? "\u23F1" : terminal === "error" ? "\u26A0\uFE0F" : "\u2705";
|
|
7684
|
+
const noun = terminal === "interrupted" ? "\u4E2D\u65AD\u524D\u7684\u8FC7\u7A0B" : terminal === "idle_timeout" ? "\u8D85\u65F6\u524D\u7684\u8FC7\u7A0B" : terminal === "error" ? "\u51FA\u9519\u524D\u7684\u8FC7\u7A0B" : "\u672C\u8F6E\u8FC7\u7A0B";
|
|
7596
7685
|
const parts = [];
|
|
7597
7686
|
if (hasReasoning) parts.push("\u{1F9E0} \u601D\u8003");
|
|
7598
|
-
if (toolCount > 0) parts.push(`\u{1F9F0} ${toolCount} \u4E2A\u5DE5\u5177
|
|
7599
|
-
const detail = parts.length > 0 ?
|
|
7600
|
-
return
|
|
7687
|
+
if (toolCount > 0) parts.push(`\u{1F9F0} ${toolCount} \u4E2A\u5DE5\u5177`);
|
|
7688
|
+
const detail = parts.length > 0 ? ` \xB7 ${parts.join(" \xB7 ")}` : "";
|
|
7689
|
+
return `${icon} **${noun}**${detail}\uFF08\u70B9\u51FB\u5C55\u5F00\uFF09`;
|
|
7601
7690
|
}
|
|
7602
7691
|
function estimateSize2(els) {
|
|
7603
7692
|
let n = 0;
|
|
7604
7693
|
for (const el of els) n += JSON.stringify(el).length;
|
|
7605
7694
|
return n;
|
|
7606
7695
|
}
|
|
7696
|
+
function estimateComponents(els) {
|
|
7697
|
+
let n = 0;
|
|
7698
|
+
for (const el of els) n += el.tag === "collapsible_panel" ? 3 : 1;
|
|
7699
|
+
return n;
|
|
7700
|
+
}
|
|
7607
7701
|
function buildRunCardPlain(rc) {
|
|
7608
7702
|
return buildRunCard({ ...rc, cardKey: void 0 });
|
|
7609
7703
|
}
|
|
@@ -7638,11 +7732,11 @@ function* groupBlocks(blocks) {
|
|
|
7638
7732
|
}
|
|
7639
7733
|
function renderToolGroup(tools, finalized, compact = false) {
|
|
7640
7734
|
if (tools.length === 0) return [];
|
|
7641
|
-
if (compact) return [collapsedToolSummary(tools,
|
|
7735
|
+
if (compact) return [collapsedToolSummary(tools, finalized)];
|
|
7736
|
+
if (finalized) return tools.map((t) => toolPanel(t, false));
|
|
7642
7737
|
if (tools.length < COLLAPSE_TOOL_THRESHOLD) {
|
|
7643
7738
|
return tools.map((t) => toolPanel(t, false));
|
|
7644
7739
|
}
|
|
7645
|
-
if (finalized) return [collapsedToolSummary(tools, true)];
|
|
7646
7740
|
const prior = tools.slice(0, -1);
|
|
7647
7741
|
const latest = tools[tools.length - 1];
|
|
7648
7742
|
const out = [];
|
|
@@ -7652,7 +7746,7 @@ function renderToolGroup(tools, finalized, compact = false) {
|
|
|
7652
7746
|
}
|
|
7653
7747
|
function reasoningPanel(content, active) {
|
|
7654
7748
|
return collapsiblePanel({
|
|
7655
|
-
title: active ? "\u{1F9E0} **\u601D\u8003\
|
|
7749
|
+
title: active ? "\u{1F9E0} **\u6B63\u5728\u601D\u8003\u2026**" : "\u{1F9E0} **\u601D\u8003\u8FC7\u7A0B**\uFF08\u70B9\u51FB\u5C55\u5F00\uFF09",
|
|
7656
7750
|
expanded: active,
|
|
7657
7751
|
border: "grey",
|
|
7658
7752
|
body: truncate4(content, REASONING_MAX)
|
|
@@ -7668,11 +7762,24 @@ function toolPanel(tool, expanded) {
|
|
|
7668
7762
|
}
|
|
7669
7763
|
function collapsedToolSummary(tools, finalized) {
|
|
7670
7764
|
const suffix = finalized ? "\uFF08\u5DF2\u7ED3\u675F\uFF09" : "";
|
|
7765
|
+
const lines = tools.map(toolSummaryLine);
|
|
7766
|
+
let body = lines.join("\n");
|
|
7767
|
+
if (body.length > SUMMARY_BODY_MAX) {
|
|
7768
|
+
let kept = 0;
|
|
7769
|
+
let acc = 0;
|
|
7770
|
+
for (const line of lines) {
|
|
7771
|
+
if (acc + line.length + 1 > SUMMARY_BODY_MAX) break;
|
|
7772
|
+
acc += line.length + 1;
|
|
7773
|
+
kept++;
|
|
7774
|
+
}
|
|
7775
|
+
body = `${lines.slice(0, kept).join("\n")}
|
|
7776
|
+
- _\u2026\u8FD8\u6709 ${tools.length - kept} \u4E2A\u547D\u4EE4\u672A\u663E\u793A_`;
|
|
7777
|
+
}
|
|
7671
7778
|
return collapsiblePanel({
|
|
7672
|
-
title: `\
|
|
7779
|
+
title: `\u{1F9F0} **${tools.length} \u4E2A\u5DE5\u5177\u8C03\u7528${suffix}**`,
|
|
7673
7780
|
expanded: false,
|
|
7674
7781
|
border: "blue",
|
|
7675
|
-
body
|
|
7782
|
+
body
|
|
7676
7783
|
});
|
|
7677
7784
|
}
|
|
7678
7785
|
function footerStatusText(status) {
|
|
@@ -11282,6 +11389,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11282
11389
|
return;
|
|
11283
11390
|
}
|
|
11284
11391
|
const ts = turnSession(msg.chatId, project, msg.senderId);
|
|
11392
|
+
if (cmd === "resume") {
|
|
11393
|
+
postResumeCard(msg, ts.sessionKey);
|
|
11394
|
+
return;
|
|
11395
|
+
}
|
|
11396
|
+
if (cmd === "clear") {
|
|
11397
|
+
runClear(msg, ts.sessionKey, ts);
|
|
11398
|
+
return;
|
|
11399
|
+
}
|
|
11285
11400
|
if (cmd === "model") {
|
|
11286
11401
|
postModelCard(msg, ts.sessionKey, false);
|
|
11287
11402
|
return;
|
|
@@ -11315,6 +11430,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11315
11430
|
await channel.send(msg.chatId, { markdown: `\`/${cmd}\` \u8BF7\u5230\u4E3B\u7FA4\u533A\u4F7F\u7528\uFF08\u8BDD\u9898\u5916\u53D1\uFF09\u3002` }, { replyTo: msg.messageId, replyInThread: true }).catch(() => void 0);
|
|
11316
11431
|
return;
|
|
11317
11432
|
}
|
|
11433
|
+
if (cmd === "clear") {
|
|
11434
|
+
await channel.send(
|
|
11435
|
+
msg.chatId,
|
|
11436
|
+
{ markdown: "`/clear` \u4EC5\u5355\u4F1A\u8BDD\u7FA4\u53EF\u7528\u3002\u591A\u8BDD\u9898\u7FA4\u91CC\uFF0C\u56DE\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9 \u5373\u5F00\u65B0\u8BDD\u9898\uFF1B`/resume` \u53EF\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u3002" },
|
|
11437
|
+
{ replyTo: msg.messageId, replyInThread: true }
|
|
11438
|
+
).catch(() => void 0);
|
|
11439
|
+
return;
|
|
11440
|
+
}
|
|
11318
11441
|
const ts = turnSession(msg.threadId, project, msg.senderId);
|
|
11319
11442
|
if (cmd === "model") {
|
|
11320
11443
|
postModelCard(msg, ts.sessionKey, true);
|
|
@@ -11352,6 +11475,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11352
11475
|
await postGroupSettings(msg, project);
|
|
11353
11476
|
return;
|
|
11354
11477
|
}
|
|
11478
|
+
if (cmd === "clear") {
|
|
11479
|
+
await channel.send(
|
|
11480
|
+
msg.chatId,
|
|
11481
|
+
{ markdown: "`/clear` \u4EC5\u5355\u4F1A\u8BDD\u7FA4\u53EF\u7528\u3002\u591A\u8BDD\u9898\u7FA4\u91CC\uFF0C@\u6211 + \u5185\u5BB9 \u5373\u5F00\u65B0\u8BDD\u9898\uFF1B`/resume` \u53EF\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u3002" },
|
|
11482
|
+
{ replyTo: msg.messageId }
|
|
11483
|
+
).catch(() => void 0);
|
|
11484
|
+
return;
|
|
11485
|
+
}
|
|
11355
11486
|
if (cmd === "model" || cmd === "compact" || cmd === "context") {
|
|
11356
11487
|
await channel.send(msg.chatId, { markdown: `\`/${cmd}\` \u9700\u8981\u5728\u8BDD\u9898\u91CC\u4F7F\u7528\uFF08\u5148 @\u6211 \u5F00\u4E2A\u8BDD\u9898\uFF09\u3002` }, { replyTo: msg.messageId }).catch(() => void 0);
|
|
11357
11488
|
return;
|
|
@@ -11380,7 +11511,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11380
11511
|
function parseCommand(text) {
|
|
11381
11512
|
const m = /^\/(\w+)/.exec(text);
|
|
11382
11513
|
const name = m?.[1]?.toLowerCase();
|
|
11383
|
-
return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" ? name : null;
|
|
11514
|
+
return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" || name === "clear" ? name : null;
|
|
11384
11515
|
}
|
|
11385
11516
|
function shouldRespondWithoutMention(project, msg) {
|
|
11386
11517
|
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
@@ -11696,7 +11827,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11696
11827
|
);
|
|
11697
11828
|
}).catch((err) => log.fail("intake", err));
|
|
11698
11829
|
}
|
|
11699
|
-
function postResumeCard(msg) {
|
|
11830
|
+
function postResumeCard(msg, singleSessionKey) {
|
|
11700
11831
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
11701
11832
|
if (!isAdmin(cfg, msg.senderId)) {
|
|
11702
11833
|
await denyAdminCommand(msg, "resume");
|
|
@@ -11715,7 +11846,9 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11715
11846
|
projectName: project?.name,
|
|
11716
11847
|
backend: be.id,
|
|
11717
11848
|
threads,
|
|
11718
|
-
createdAt: Date.now()
|
|
11849
|
+
createdAt: Date.now(),
|
|
11850
|
+
// Single-session group: pick rebinds this key flat (no new topic).
|
|
11851
|
+
...singleSessionKey ? { flat: true, sessionKey: singleSessionKey } : {}
|
|
11719
11852
|
};
|
|
11720
11853
|
const res = await sendManagedCard(channel, msg.chatId, buildResumeCard(state), msg.messageId);
|
|
11721
11854
|
pruneResumePending();
|
|
@@ -11839,6 +11972,68 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11839
11972
|
}
|
|
11840
11973
|
});
|
|
11841
11974
|
}
|
|
11975
|
+
function runClear(msg, sessionKey, perm) {
|
|
11976
|
+
const reply = (markdown) => channel.send(msg.chatId, { markdown }, { replyTo: msg.messageId }).then(
|
|
11977
|
+
() => void 0,
|
|
11978
|
+
() => void 0
|
|
11979
|
+
);
|
|
11980
|
+
if (!isAdmin(cfg, msg.senderId)) {
|
|
11981
|
+
void denyAdminCommand(msg, "clear");
|
|
11982
|
+
return;
|
|
11983
|
+
}
|
|
11984
|
+
if (active.get(sessionKey)) {
|
|
11985
|
+
void reply("\u23F3 \u8FD9\u4E00\u8F6E\u8FD8\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D `/clear`\u3002");
|
|
11986
|
+
return;
|
|
11987
|
+
}
|
|
11988
|
+
const reserved = { queue: [], requesterOpenId: msg.senderId };
|
|
11989
|
+
active.set(sessionKey, reserved);
|
|
11990
|
+
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
11991
|
+
try {
|
|
11992
|
+
const [rec, project] = await Promise.all([getSession(sessionKey), getProjectByChatId(msg.chatId)]);
|
|
11993
|
+
const be = backendFor(rec?.backend ?? project?.backend);
|
|
11994
|
+
const cwd = rec?.cwd ?? project?.cwd ?? fallbackCwd;
|
|
11995
|
+
const fresh = await be.startThread({
|
|
11996
|
+
cwd,
|
|
11997
|
+
model: rec?.model,
|
|
11998
|
+
effort: rec?.effort,
|
|
11999
|
+
mode: perm.mode,
|
|
12000
|
+
network: perm.network,
|
|
12001
|
+
autoCompact: perm.autoCompact
|
|
12002
|
+
});
|
|
12003
|
+
const old = sessions.get(sessionKey);
|
|
12004
|
+
if (old && old !== fresh) {
|
|
12005
|
+
sessions.delete(sessionKey);
|
|
12006
|
+
void old.close().catch(() => void 0);
|
|
12007
|
+
}
|
|
12008
|
+
trackSession(sessionKey, fresh);
|
|
12009
|
+
lastUsage.delete(sessionKey);
|
|
12010
|
+
await upsertSession({
|
|
12011
|
+
threadId: sessionKey,
|
|
12012
|
+
chatId: msg.chatId,
|
|
12013
|
+
cwd,
|
|
12014
|
+
sessionId: fresh.sessionId,
|
|
12015
|
+
backend: be.id,
|
|
12016
|
+
model: rec?.model,
|
|
12017
|
+
effort: rec?.effort,
|
|
12018
|
+
summary: "(\u65B0\u4F1A\u8BDD)",
|
|
12019
|
+
createdAt: Date.now(),
|
|
12020
|
+
updatedAt: Date.now()
|
|
12021
|
+
});
|
|
12022
|
+
await sendManagedCard(channel, msg.chatId, buildClearedCard(), msg.messageId, false).catch(
|
|
12023
|
+
(err) => log.fail("card", err, { phase: "clear" })
|
|
12024
|
+
);
|
|
12025
|
+
log.info("intake", "clear", { sessionKey, sessionId: fresh.sessionId, backend: be.id });
|
|
12026
|
+
} catch (err) {
|
|
12027
|
+
log.fail("intake", err, { phase: "clear" });
|
|
12028
|
+
await reply(`\u274C ${err instanceof Error ? err.message : String(err)}`);
|
|
12029
|
+
} finally {
|
|
12030
|
+
if (active.get(sessionKey) === reserved) active.delete(sessionKey);
|
|
12031
|
+
if (reserved.queue.length > 0) {
|
|
12032
|
+
await reply(`\u26A0\uFE0F \u6E05\u7A7A\u671F\u95F4\u6536\u5230\u7684 ${reserved.queue.length} \u6761\u6D88\u606F\u672A\u8FDB\u5165\u4F1A\u8BDD\uFF0C\u8BF7\u91CD\u53D1\u3002`);
|
|
12033
|
+
}
|
|
12034
|
+
}
|
|
12035
|
+
});
|
|
12036
|
+
}
|
|
11842
12037
|
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
11843
12038
|
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
11844
12039
|
const caps = backendFor(project?.backend).capabilities;
|
|
@@ -12707,6 +12902,55 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
12707
12902
|
async function resumeFromCard(evt, state, sessionId, backendId) {
|
|
12708
12903
|
const be = backendFor(backendId);
|
|
12709
12904
|
try {
|
|
12905
|
+
if (state.flat && state.sessionKey) {
|
|
12906
|
+
const sessionKey = state.sessionKey;
|
|
12907
|
+
if (active.get(sessionKey)) {
|
|
12908
|
+
resumePending.delete(evt.messageId);
|
|
12909
|
+
settleUpdate(evt.messageId, buildResumeErrorCard(state, "\u5F53\u524D\u6709\u4EFB\u52A1\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D /resume"));
|
|
12910
|
+
return;
|
|
12911
|
+
}
|
|
12912
|
+
const reserved = { queue: [], requesterOpenId: state.requesterOpenId };
|
|
12913
|
+
active.set(sessionKey, reserved);
|
|
12914
|
+
try {
|
|
12915
|
+
const history2 = await be.readHistory(state.cwd, sessionId);
|
|
12916
|
+
resumePending.delete(evt.messageId);
|
|
12917
|
+
await withTrace({ chatId: state.chatId, msgId: state.originalMsgId }, async () => {
|
|
12918
|
+
const cardState = { cwd: state.cwd, projectName: state.projectName, history: history2 };
|
|
12919
|
+
await sendManagedCard(channel, state.chatId, buildHistoryCard(cardState), state.originalMsgId, false).catch(
|
|
12920
|
+
(err) => log.fail("card", err, { phase: "resume-history-single" })
|
|
12921
|
+
);
|
|
12922
|
+
const old = sessions.get(sessionKey);
|
|
12923
|
+
if (old) {
|
|
12924
|
+
sessions.delete(sessionKey);
|
|
12925
|
+
void old.close().catch(() => void 0);
|
|
12926
|
+
}
|
|
12927
|
+
lastUsage.delete(sessionKey);
|
|
12928
|
+
const now = Date.now();
|
|
12929
|
+
await upsertSession({
|
|
12930
|
+
threadId: sessionKey,
|
|
12931
|
+
chatId: state.chatId,
|
|
12932
|
+
cwd: state.cwd,
|
|
12933
|
+
sessionId,
|
|
12934
|
+
backend: be.id,
|
|
12935
|
+
summary: history2.name || history2.preview || "(\u6062\u590D\u4F1A\u8BDD)",
|
|
12936
|
+
createdAt: now,
|
|
12937
|
+
updatedAt: now
|
|
12938
|
+
});
|
|
12939
|
+
log.info("card", "resume-done-single", { sessionKey, sessionId, turns: history2.totalTurns });
|
|
12940
|
+
});
|
|
12941
|
+
settleUpdate(evt.messageId, buildResumeDoneCard(state));
|
|
12942
|
+
} finally {
|
|
12943
|
+
if (active.get(sessionKey) === reserved) active.delete(sessionKey);
|
|
12944
|
+
if (reserved.queue.length > 0) {
|
|
12945
|
+
await channel.send(
|
|
12946
|
+
state.chatId,
|
|
12947
|
+
{ markdown: `\u26A0\uFE0F \u5207\u56DE\u671F\u95F4\u6536\u5230\u7684 ${reserved.queue.length} \u6761\u6D88\u606F\u672A\u8FDB\u5165\u4F1A\u8BDD\uFF0C\u8BF7\u91CD\u53D1\u3002` },
|
|
12948
|
+
{ replyTo: state.originalMsgId }
|
|
12949
|
+
).catch(() => void 0);
|
|
12950
|
+
}
|
|
12951
|
+
}
|
|
12952
|
+
return;
|
|
12953
|
+
}
|
|
12710
12954
|
const history = await be.readHistory(state.cwd, sessionId);
|
|
12711
12955
|
resumePending.delete(evt.messageId);
|
|
12712
12956
|
let bound = false;
|