@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.
Files changed (3) hide show
  1. package/README.md +32 -8
  2. package/dist/cli.js +162 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,6 +4,9 @@
4
4
  [![total downloads](https://badgen.net/npm/dt/@modelzen/feishu-codex-bridge)](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
5
5
  [![downloads/month](https://badgen.net/npm/dm/@modelzen/feishu-codex-bridge)](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
6
6
  [![license](https://badgen.net/npm/license/@modelzen/feishu-codex-bridge)](https://github.com/modelzen/feishu-codex-bridge/blob/main/LICENSE)
7
+ [![官网](https://badgen.net/badge/%E5%AE%98%E7%BD%91/bridge.vonvon.cc/blue)](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
- npm i -g @modelzen/feishu-codex-bridge # 1. 装命令
30
- feishu-codex-bridge web # 2. 打开本机网页控制台
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
- **Codex / Claude** 替你装?把下面这段发给它:
40
+ 不想自己敲?把下面这段发给 **Codex / Claude** 等 AI agent,让它替你装好并跑起来:
34
41
 
35
42
  ```text
36
- 如果本地没有 feishu-codex-bridge 命令,就先用 npm i -g @modelzen/feishu-codex-bridge 安装。
37
- 然后运行 feishu-codex-bridge web 打开网页控制台,把输出的网址给我打开。
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
- > 前置:**Node ≥ 20**,外加一个登录好的后端 —— **Codex**(`npm i -g @openai/codex && codex login`)或 **Claude Code**(SDK 随桥内置、复用本机 `claude` 登录态,首次按需下载约 265MB)。打开网页后扫码加机器人、按 checklist 开权限 / 订阅事件,全程点点点,不用碰命令行。
60
+ ### 三分钟,看它入职你的飞书
61
+
62
+ 从全局装包、打开本机网页控制台,到扫码建机器人、私聊报到,一整段带解说录屏。点击下面封面去 B 站观看高清版。
63
+
64
+ [![3 分钟跑通:飞书里直接发任务,Codex 在你本机真干活](docs/assets/install-demo-cover.jpg)](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(note("\u70B9\u4E00\u6761\u5373\u6062\u590D \u2014\u2014 \u5728\u65B0\u8BDD\u9898\u91CC\u6253\u5F00\u5386\u53F2\u3001\u53EF\u76F4\u63A5\u7EE7\u7EED\u3002"));
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
- return card([md("\u2705 \u5DF2\u6062\u590D \u2014\u2014 \u5DF2\u5728\u4E0A\u65B9\u65B0\u8BDD\u9898\u6253\u5F00\uFF0C\u53EF\u76F4\u63A5\u7EE7\u7EED\u3002"), note(metaNote(state))], { summary: "\u5DF2\u6062\u590D" });
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {