@modelzen/feishu-codex-bridge 0.6.5 → 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 +162 -5
- 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
|
@@ -5538,7 +5538,11 @@ function buildResumeCard(state) {
|
|
|
5538
5538
|
if (state.threads.length === 0) {
|
|
5539
5539
|
elements.push(md("_\u8BE5\u76EE\u5F55\u4E0B\u8FD8\u6CA1\u6709\u5386\u53F2\u4F1A\u8BDD\u3002\u76F4\u63A5 @\u6211 \u5373\u53EF\u65B0\u5EFA\u3002_"));
|
|
5540
5540
|
} else {
|
|
5541
|
-
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
|
+
);
|
|
5542
5546
|
for (const t of state.threads) {
|
|
5543
5547
|
const title = (t.name?.trim() || t.preview.trim() || "(\u65E0\u6458\u8981)").replace(/\s+/g, " ");
|
|
5544
5548
|
const label = `\u21A9\uFE0F ${pickerTime(t.updatedAt || t.createdAt)} \xB7 ${truncate(title, RESUME_TITLE_MAX)}`;
|
|
@@ -5551,7 +5555,19 @@ function buildResumeLaunchingCard(state) {
|
|
|
5551
5555
|
return card([md("\u23F3 \u6B63\u5728\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u2026"), note(metaNote(state))], { summary: "\u6062\u590D\u4E2D" });
|
|
5552
5556
|
}
|
|
5553
5557
|
function buildResumeDoneCard(state) {
|
|
5554
|
-
|
|
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
|
+
);
|
|
5555
5571
|
}
|
|
5556
5572
|
function buildResumeErrorCard(state, message) {
|
|
5557
5573
|
return card([md(`\u274C \u6062\u590D\u5931\u8D25\uFF1A${truncate(message, 200)}`), note(metaNote(state))], { summary: "\u6062\u590D\u5931\u8D25" });
|
|
@@ -5608,6 +5624,8 @@ function buildHelpCard(scope, noMention = true, isAdmin2 = false, caps) {
|
|
|
5608
5624
|
if (showGoal) lines.push(goalLine);
|
|
5609
5625
|
lines.push("\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6", "\xB7 `/context` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4");
|
|
5610
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");
|
|
5611
5629
|
if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
5612
5630
|
lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
5613
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")));
|
|
@@ -5651,6 +5669,8 @@ function buildWelcomeCard(kind, docUrl, noMention = true, caps, agentName = "Cod
|
|
|
5651
5669
|
talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"),
|
|
5652
5670
|
...showGoal ? [goalLine] : [],
|
|
5653
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"] : [],
|
|
5654
5674
|
"\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09",
|
|
5655
5675
|
"\xB7 `/help` \u2192 \u547D\u4EE4\u901F\u67E5\u5361"
|
|
5656
5676
|
].join("\n")
|
|
@@ -11369,6 +11389,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11369
11389
|
return;
|
|
11370
11390
|
}
|
|
11371
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
|
+
}
|
|
11372
11400
|
if (cmd === "model") {
|
|
11373
11401
|
postModelCard(msg, ts.sessionKey, false);
|
|
11374
11402
|
return;
|
|
@@ -11402,6 +11430,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11402
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);
|
|
11403
11431
|
return;
|
|
11404
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
|
+
}
|
|
11405
11441
|
const ts = turnSession(msg.threadId, project, msg.senderId);
|
|
11406
11442
|
if (cmd === "model") {
|
|
11407
11443
|
postModelCard(msg, ts.sessionKey, true);
|
|
@@ -11439,6 +11475,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11439
11475
|
await postGroupSettings(msg, project);
|
|
11440
11476
|
return;
|
|
11441
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
|
+
}
|
|
11442
11486
|
if (cmd === "model" || cmd === "compact" || cmd === "context") {
|
|
11443
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);
|
|
11444
11488
|
return;
|
|
@@ -11467,7 +11511,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11467
11511
|
function parseCommand(text) {
|
|
11468
11512
|
const m = /^\/(\w+)/.exec(text);
|
|
11469
11513
|
const name = m?.[1]?.toLowerCase();
|
|
11470
|
-
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;
|
|
11471
11515
|
}
|
|
11472
11516
|
function shouldRespondWithoutMention(project, msg) {
|
|
11473
11517
|
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
@@ -11783,7 +11827,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11783
11827
|
);
|
|
11784
11828
|
}).catch((err) => log.fail("intake", err));
|
|
11785
11829
|
}
|
|
11786
|
-
function postResumeCard(msg) {
|
|
11830
|
+
function postResumeCard(msg, singleSessionKey) {
|
|
11787
11831
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
11788
11832
|
if (!isAdmin(cfg, msg.senderId)) {
|
|
11789
11833
|
await denyAdminCommand(msg, "resume");
|
|
@@ -11802,7 +11846,9 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11802
11846
|
projectName: project?.name,
|
|
11803
11847
|
backend: be.id,
|
|
11804
11848
|
threads,
|
|
11805
|
-
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 } : {}
|
|
11806
11852
|
};
|
|
11807
11853
|
const res = await sendManagedCard(channel, msg.chatId, buildResumeCard(state), msg.messageId);
|
|
11808
11854
|
pruneResumePending();
|
|
@@ -11926,6 +11972,68 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11926
11972
|
}
|
|
11927
11973
|
});
|
|
11928
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
|
+
}
|
|
11929
12037
|
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
11930
12038
|
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
11931
12039
|
const caps = backendFor(project?.backend).capabilities;
|
|
@@ -12794,6 +12902,55 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
12794
12902
|
async function resumeFromCard(evt, state, sessionId, backendId) {
|
|
12795
12903
|
const be = backendFor(backendId);
|
|
12796
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
|
+
}
|
|
12797
12954
|
const history = await be.readHistory(state.cwd, sessionId);
|
|
12798
12955
|
resumePending.delete(evt.messageId);
|
|
12799
12956
|
let bound = false;
|