@modelzen/feishu-codex-bridge 0.6.5 → 0.6.7-test.0

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 +372 -22
  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")
@@ -8489,7 +8509,7 @@ function withCodexHooksFeature(text) {
8489
8509
  }
8490
8510
 
8491
8511
  // src/service/update.ts
8492
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
8512
+ import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
8493
8513
  import { dirname as dirname9, join as join15, resolve as resolve6 } from "path";
8494
8514
  import { fileURLToPath as fileURLToPath4 } from "url";
8495
8515
 
@@ -8503,7 +8523,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
8503
8523
 
8504
8524
  // src/service/common.ts
8505
8525
  init_paths();
8506
- import { createReadStream, statSync as statSync2 } from "fs";
8526
+ import { appendFileSync, createReadStream, statSync as statSync2 } from "fs";
8507
8527
  import { appendFile, mkdir as mkdir7, readFile as readFile9 } from "fs/promises";
8508
8528
  import { dirname as dirname6, join as join11, resolve as resolve4 } from "path";
8509
8529
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -8518,6 +8538,13 @@ async function ensureLogFiles() {
8518
8538
  await appendFile(serviceStdoutPath(), "");
8519
8539
  await appendFile(serviceStderrPath(), "");
8520
8540
  }
8541
+ function appendServiceErr(tag, line) {
8542
+ try {
8543
+ appendFileSync(serviceStderrPath(), `[${(/* @__PURE__ */ new Date()).toISOString()}] [${tag}] ${line}
8544
+ `);
8545
+ } catch {
8546
+ }
8547
+ }
8521
8548
  function resolveCliBinPath() {
8522
8549
  const distDir = dirname6(fileURLToPath2(import.meta.url));
8523
8550
  return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
@@ -8727,9 +8754,10 @@ function launchctlError(command, result) {
8727
8754
  // src/service/win-startup.ts
8728
8755
  init_paths();
8729
8756
  import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
8730
- import { openSync, readFileSync as readFileSync3, rmSync, writeFileSync } from "fs";
8757
+ import { existsSync as existsSync7, openSync, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync } from "fs";
8731
8758
  import { mkdir as mkdir9, writeFile as writeFile8 } from "fs/promises";
8732
8759
  import { join as join13 } from "path";
8760
+ var RELAUNCH_TASK_NAME = "feishu-codex-bridge-relaunch";
8733
8761
  var RUN_KEY_PATH = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
8734
8762
  var RUN_KEY_NAME = "feishu-codex-bridge";
8735
8763
  var SERVICE_ENV_FLAG = "FEISHU_CODEX_BRIDGE_SERVICE";
@@ -8759,14 +8787,16 @@ function buildLauncherVbs() {
8759
8787
  ""
8760
8788
  ].join("\r\n");
8761
8789
  }
8762
- function startNow() {
8790
+ function startNow(envPath) {
8763
8791
  const out = openSync(serviceStdoutPath(), "a");
8764
8792
  const err = openSync(serviceStderrPath(), "a");
8793
+ const overrides = { [SERVICE_ENV_FLAG]: "1" };
8794
+ if (envPath) overrides.PATH = envPath;
8765
8795
  const child = spawn3(process.execPath, [resolveCliBinPath(), "run"], {
8766
8796
  detached: true,
8767
8797
  windowsHide: true,
8768
8798
  stdio: ["ignore", out, err],
8769
- env: mergeProcessEnv(process.env, { [SERVICE_ENV_FLAG]: "1" })
8799
+ env: mergeProcessEnv(process.env, overrides)
8770
8800
  });
8771
8801
  if (child.pid) writeFileSync(servicePidFile(), String(child.pid), "utf8");
8772
8802
  child.unref();
@@ -8797,10 +8827,150 @@ async function restartWinStartup() {
8797
8827
  throw new Error("\u767B\u5F55\u81EA\u542F\u672A\u5B89\u88C5\uFF08\u5148\u8FD0\u884C `feishu-codex-bridge start`\uFF09\u3002");
8798
8828
  }
8799
8829
  await ensureLogFiles();
8800
- killService();
8801
- startNow();
8830
+ const oldPid = readServicePid();
8831
+ if (oldPid === null || !pidAlive(oldPid)) {
8832
+ startNow();
8833
+ return statusWinStartup();
8834
+ }
8835
+ writeRelaunchRequest({ oldPid, envPath: process.env.PATH ?? "", nonce: `${process.pid}-${Date.now()}` });
8836
+ if (!spawnTreeFreeRelauncher()) {
8837
+ clearRelaunchRequest();
8838
+ appendServiceErr("relaunch", `no tree-free backend (powershell/schtasks); kept old pid=${oldPid} running, did NOT restart`);
8839
+ throw new Error("\u65E0\u6CD5\u62C9\u8D77\u300C\u6811\u5916\u300D\u91CD\u542F\u8FDB\u7A0B\uFF08powershell \u4E0E schtasks \u5747\u4E0D\u53EF\u7528\uFF09\uFF1B\u5DF2\u4FDD\u7559\u65E7\u670D\u52A1\u7EE7\u7EED\u8FD0\u884C\uFF0C\u672A\u91CD\u542F\u3002");
8840
+ }
8841
+ appendServiceErr("relaunch", `submitted async relaunch for oldPid=${oldPid}`);
8802
8842
  return statusWinStartup();
8803
8843
  }
8844
+ function relaunchRequestFile() {
8845
+ return join13(paths.appDir, "relaunch.json");
8846
+ }
8847
+ function writeRelaunchRequest(req) {
8848
+ writeFileSync(relaunchRequestFile(), JSON.stringify(req), "utf8");
8849
+ }
8850
+ function clearRelaunchRequest() {
8851
+ rmSync(relaunchRequestFile(), { force: true });
8852
+ }
8853
+ function relaunchClaimFile() {
8854
+ return `${relaunchRequestFile()}.claim.${process.pid}`;
8855
+ }
8856
+ function claimRelaunchRequest() {
8857
+ const claim = relaunchClaimFile();
8858
+ try {
8859
+ renameSync(relaunchRequestFile(), claim);
8860
+ } catch {
8861
+ return null;
8862
+ }
8863
+ try {
8864
+ const r = JSON.parse(readFileSync3(claim, "utf8"));
8865
+ if (typeof r.oldPid === "number" && Number.isFinite(r.oldPid)) {
8866
+ return {
8867
+ oldPid: r.oldPid,
8868
+ envPath: typeof r.envPath === "string" ? r.envPath : "",
8869
+ nonce: String(r.nonce ?? "")
8870
+ };
8871
+ }
8872
+ } catch {
8873
+ }
8874
+ return null;
8875
+ }
8876
+ function clearRelaunchClaim() {
8877
+ rmSync(relaunchClaimFile(), { force: true });
8878
+ }
8879
+ function buildRelauncherPowershell(binPath = resolveCliBinPath(), nodePath = process.execPath) {
8880
+ const inner = `"${nodePath}" "${binPath}" __win-relaunch`;
8881
+ const lit = `'${inner.replace(/'/g, "''")}'`;
8882
+ return `$c=${lit}; try { Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine=$c } -ErrorAction Stop | Out-Null } catch { schtasks /create /tn ${RELAUNCH_TASK_NAME} /tr $c /sc ONCE /st 00:00 /f | Out-Null; schtasks /run /tn ${RELAUNCH_TASK_NAME} | Out-Null }`;
8883
+ }
8884
+ function powershellPath() {
8885
+ const root = process.env.SystemRoot || process.env.windir || "C:\\Windows";
8886
+ const p = join13(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
8887
+ return existsSync7(p) ? p : null;
8888
+ }
8889
+ function spawnTreeFreeRelauncher() {
8890
+ const ps = powershellPath();
8891
+ if (ps) {
8892
+ const errFd = openSync(serviceStderrPath(), "a");
8893
+ const child = spawn3(
8894
+ ps,
8895
+ ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", buildRelauncherPowershell()],
8896
+ { detached: true, windowsHide: true, stdio: ["ignore", errFd, errFd] }
8897
+ );
8898
+ child.on("error", (e) => appendServiceErr("relaunch", `powershell spawn error: ${e.message}`));
8899
+ child.unref();
8900
+ return true;
8901
+ }
8902
+ appendServiceErr("relaunch", "powershell.exe not found; trying schtasks fallback");
8903
+ return spawnSchtasksRelauncher();
8904
+ }
8905
+ function spawnSchtasksRelauncher() {
8906
+ const tr = `"${process.execPath}" "${resolveCliBinPath()}" __win-relaunch`;
8907
+ const create = spawnSync2(
8908
+ "schtasks",
8909
+ ["/create", "/tn", RELAUNCH_TASK_NAME, "/tr", tr, "/sc", "ONCE", "/st", "00:00", "/f"],
8910
+ { encoding: "utf8", windowsHide: true }
8911
+ );
8912
+ if (create.status !== 0) {
8913
+ appendServiceErr("relaunch", `schtasks /create failed exit=${create.status ?? "?"} ${(create.stderr || "").trim()}`);
8914
+ return false;
8915
+ }
8916
+ const run = spawnSync2("schtasks", ["/run", "/tn", RELAUNCH_TASK_NAME], { encoding: "utf8", windowsHide: true });
8917
+ if (run.status !== 0) {
8918
+ appendServiceErr("relaunch", `schtasks /run failed exit=${run.status ?? "?"} ${(run.stderr || "").trim()}`);
8919
+ return false;
8920
+ }
8921
+ return true;
8922
+ }
8923
+ function defaultTaskkill(pid) {
8924
+ const r = spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
8925
+ return { status: r.status, stderr: (r.stderr || "").toString() };
8926
+ }
8927
+ async function runWinRelaunch(deps = {}) {
8928
+ const claim = deps.claimRequest ?? claimRelaunchRequest;
8929
+ const clearClaim = deps.clearRequest ?? clearRelaunchClaim;
8930
+ const alive = deps.pidAlive ?? pidAlive;
8931
+ const kill = deps.taskkill ?? defaultTaskkill;
8932
+ const start = deps.start ?? startNow;
8933
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
8934
+ const graceMs = deps.graceMs ?? 15e3;
8935
+ const pollMs = deps.pollMs ?? 200;
8936
+ const now = deps.now ?? (() => Date.now());
8937
+ const ensureLogs = deps.ensureLogs ?? ensureLogFiles;
8938
+ const logLine = deps.logLine ?? ((line) => appendServiceErr("relaunch", line));
8939
+ await ensureLogs();
8940
+ const req = claim();
8941
+ if (!req) {
8942
+ logLine("no relaunch request to claim (already handled by another relauncher, or none)");
8943
+ return;
8944
+ }
8945
+ try {
8946
+ logLine(`start oldPid=${req.oldPid} self=${process.pid}`);
8947
+ if (alive(req.oldPid)) {
8948
+ const r = kill(req.oldPid);
8949
+ logLine(`taskkill oldPid=${req.oldPid} exit=${r.status ?? "?"} ${r.stderr.trim()}`.trim());
8950
+ }
8951
+ const deadline = now() + graceMs;
8952
+ while (now() < deadline && alive(req.oldPid)) {
8953
+ await sleep(pollMs);
8954
+ }
8955
+ if (alive(req.oldPid)) {
8956
+ logLine(
8957
+ `ABORT: oldPid=${req.oldPid} still alive after ${graceMs}ms (elevated / access denied?); not starting new to avoid double instance`
8958
+ );
8959
+ return;
8960
+ }
8961
+ await sleep(300);
8962
+ start(req.envPath);
8963
+ logLine(`relaunched; new service.pid=${readServicePid() ?? "?"}`);
8964
+ } finally {
8965
+ clearClaim();
8966
+ if (process.platform === "win32") {
8967
+ try {
8968
+ spawnSync2("schtasks", ["/delete", "/tn", RELAUNCH_TASK_NAME, "/f"], { stdio: "ignore", windowsHide: true });
8969
+ } catch {
8970
+ }
8971
+ }
8972
+ }
8973
+ }
8804
8974
  function statusWinStartup() {
8805
8975
  const installed = isInstalled();
8806
8976
  const pid = readServicePid();
@@ -8860,12 +9030,15 @@ function pidAlive(pid) {
8860
9030
  function killService() {
8861
9031
  const pid = readServicePid();
8862
9032
  if (pid === null || !pidAlive(pid)) return;
8863
- spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
9033
+ const r = spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
9034
+ if (r.status !== 0) {
9035
+ appendServiceErr("kill", `taskkill pid=${pid} exit=${r.status ?? "?"} ${(r.stderr || "").toString().trim()}`);
9036
+ }
8864
9037
  }
8865
9038
 
8866
9039
  // src/service/systemd.ts
8867
9040
  import { spawnSync as spawnSync3 } from "child_process";
8868
- import { existsSync as existsSync7 } from "fs";
9041
+ import { existsSync as existsSync8 } from "fs";
8869
9042
  import { mkdir as mkdir10, rm as rm4, writeFile as writeFile9 } from "fs/promises";
8870
9043
  import { homedir as homedir5 } from "os";
8871
9044
  import { dirname as dirname8, join as join14 } from "path";
@@ -8969,7 +9142,7 @@ function statusSystemd() {
8969
9142
  };
8970
9143
  }
8971
9144
  function unitExists() {
8972
- return existsSync7(systemdUnitPath());
9145
+ return existsSync8(systemdUnitPath());
8973
9146
  }
8974
9147
  function systemdActive() {
8975
9148
  const r = spawnSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT_NAME], {
@@ -9044,7 +9217,7 @@ function packageName() {
9044
9217
  return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
9045
9218
  }
9046
9219
  function isDevSource() {
9047
- return existsSync8(join15(pkgRoot(), ".git"));
9220
+ return existsSync9(join15(pkgRoot(), ".git"));
9048
9221
  }
9049
9222
  function isNewer(a, b) {
9050
9223
  const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -9759,7 +9932,7 @@ init_paths();
9759
9932
  init_paths();
9760
9933
  init_logger();
9761
9934
  import { mkdir as mkdir11 } from "fs/promises";
9762
- import { existsSync as existsSync9 } from "fs";
9935
+ import { existsSync as existsSync10 } from "fs";
9763
9936
  import { isAbsolute as isAbsolute2, join as join17, resolve as resolve7 } from "path";
9764
9937
 
9765
9938
  // src/project/announcement.ts
@@ -9952,7 +10125,7 @@ function assertBackendUsable(backend, mode) {
9952
10125
  async function resolveCwd(name, existingPath) {
9953
10126
  if (existingPath) {
9954
10127
  const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve7(existingPath);
9955
- if (!existsSync9(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
10128
+ if (!existsSync10(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
9956
10129
  return { cwd: cwd2, blank: false };
9957
10130
  }
9958
10131
  const cwd = join17(paths.projectsRootDir, name);
@@ -11369,6 +11542,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
11369
11542
  return;
11370
11543
  }
11371
11544
  const ts = turnSession(msg.chatId, project, msg.senderId);
11545
+ if (cmd === "resume") {
11546
+ postResumeCard(msg, ts.sessionKey);
11547
+ return;
11548
+ }
11549
+ if (cmd === "clear") {
11550
+ runClear(msg, ts.sessionKey, ts);
11551
+ return;
11552
+ }
11372
11553
  if (cmd === "model") {
11373
11554
  postModelCard(msg, ts.sessionKey, false);
11374
11555
  return;
@@ -11402,6 +11583,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
11402
11583
  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
11584
  return;
11404
11585
  }
11586
+ if (cmd === "clear") {
11587
+ await channel.send(
11588
+ msg.chatId,
11589
+ { 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" },
11590
+ { replyTo: msg.messageId, replyInThread: true }
11591
+ ).catch(() => void 0);
11592
+ return;
11593
+ }
11405
11594
  const ts = turnSession(msg.threadId, project, msg.senderId);
11406
11595
  if (cmd === "model") {
11407
11596
  postModelCard(msg, ts.sessionKey, true);
@@ -11439,6 +11628,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
11439
11628
  await postGroupSettings(msg, project);
11440
11629
  return;
11441
11630
  }
11631
+ if (cmd === "clear") {
11632
+ await channel.send(
11633
+ msg.chatId,
11634
+ { 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" },
11635
+ { replyTo: msg.messageId }
11636
+ ).catch(() => void 0);
11637
+ return;
11638
+ }
11442
11639
  if (cmd === "model" || cmd === "compact" || cmd === "context") {
11443
11640
  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
11641
  return;
@@ -11467,7 +11664,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
11467
11664
  function parseCommand(text) {
11468
11665
  const m = /^\/(\w+)/.exec(text);
11469
11666
  const name = m?.[1]?.toLowerCase();
11470
- return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" ? name : null;
11667
+ return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" || name === "clear" ? name : null;
11471
11668
  }
11472
11669
  function shouldRespondWithoutMention(project, msg) {
11473
11670
  if (!(project.noMention ?? defaultNoMention(project))) return false;
@@ -11783,7 +11980,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
11783
11980
  );
11784
11981
  }).catch((err) => log.fail("intake", err));
11785
11982
  }
11786
- function postResumeCard(msg) {
11983
+ function postResumeCard(msg, singleSessionKey) {
11787
11984
  void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
11788
11985
  if (!isAdmin(cfg, msg.senderId)) {
11789
11986
  await denyAdminCommand(msg, "resume");
@@ -11802,7 +11999,9 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
11802
11999
  projectName: project?.name,
11803
12000
  backend: be.id,
11804
12001
  threads,
11805
- createdAt: Date.now()
12002
+ createdAt: Date.now(),
12003
+ // Single-session group: pick rebinds this key flat (no new topic).
12004
+ ...singleSessionKey ? { flat: true, sessionKey: singleSessionKey } : {}
11806
12005
  };
11807
12006
  const res = await sendManagedCard(channel, msg.chatId, buildResumeCard(state), msg.messageId);
11808
12007
  pruneResumePending();
@@ -11926,6 +12125,68 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
11926
12125
  }
11927
12126
  });
11928
12127
  }
12128
+ function runClear(msg, sessionKey, perm) {
12129
+ const reply = (markdown) => channel.send(msg.chatId, { markdown }, { replyTo: msg.messageId }).then(
12130
+ () => void 0,
12131
+ () => void 0
12132
+ );
12133
+ if (!isAdmin(cfg, msg.senderId)) {
12134
+ void denyAdminCommand(msg, "clear");
12135
+ return;
12136
+ }
12137
+ if (active.get(sessionKey)) {
12138
+ void reply("\u23F3 \u8FD9\u4E00\u8F6E\u8FD8\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D `/clear`\u3002");
12139
+ return;
12140
+ }
12141
+ const reserved = { queue: [], requesterOpenId: msg.senderId };
12142
+ active.set(sessionKey, reserved);
12143
+ void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
12144
+ try {
12145
+ const [rec, project] = await Promise.all([getSession(sessionKey), getProjectByChatId(msg.chatId)]);
12146
+ const be = backendFor(rec?.backend ?? project?.backend);
12147
+ const cwd = rec?.cwd ?? project?.cwd ?? fallbackCwd;
12148
+ const fresh = await be.startThread({
12149
+ cwd,
12150
+ model: rec?.model,
12151
+ effort: rec?.effort,
12152
+ mode: perm.mode,
12153
+ network: perm.network,
12154
+ autoCompact: perm.autoCompact
12155
+ });
12156
+ const old = sessions.get(sessionKey);
12157
+ if (old && old !== fresh) {
12158
+ sessions.delete(sessionKey);
12159
+ void old.close().catch(() => void 0);
12160
+ }
12161
+ trackSession(sessionKey, fresh);
12162
+ lastUsage.delete(sessionKey);
12163
+ await upsertSession({
12164
+ threadId: sessionKey,
12165
+ chatId: msg.chatId,
12166
+ cwd,
12167
+ sessionId: fresh.sessionId,
12168
+ backend: be.id,
12169
+ model: rec?.model,
12170
+ effort: rec?.effort,
12171
+ summary: "(\u65B0\u4F1A\u8BDD)",
12172
+ createdAt: Date.now(),
12173
+ updatedAt: Date.now()
12174
+ });
12175
+ await sendManagedCard(channel, msg.chatId, buildClearedCard(), msg.messageId, false).catch(
12176
+ (err) => log.fail("card", err, { phase: "clear" })
12177
+ );
12178
+ log.info("intake", "clear", { sessionKey, sessionId: fresh.sessionId, backend: be.id });
12179
+ } catch (err) {
12180
+ log.fail("intake", err, { phase: "clear" });
12181
+ await reply(`\u274C ${err instanceof Error ? err.message : String(err)}`);
12182
+ } finally {
12183
+ if (active.get(sessionKey) === reserved) active.delete(sessionKey);
12184
+ if (reserved.queue.length > 0) {
12185
+ 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`);
12186
+ }
12187
+ }
12188
+ });
12189
+ }
11929
12190
  async function postHelpCard(msg, scope, inThread = false, project) {
11930
12191
  const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
11931
12192
  const caps = backendFor(project?.backend).capabilities;
@@ -12794,6 +13055,55 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
12794
13055
  async function resumeFromCard(evt, state, sessionId, backendId) {
12795
13056
  const be = backendFor(backendId);
12796
13057
  try {
13058
+ if (state.flat && state.sessionKey) {
13059
+ const sessionKey = state.sessionKey;
13060
+ if (active.get(sessionKey)) {
13061
+ resumePending.delete(evt.messageId);
13062
+ settleUpdate(evt.messageId, buildResumeErrorCard(state, "\u5F53\u524D\u6709\u4EFB\u52A1\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D /resume"));
13063
+ return;
13064
+ }
13065
+ const reserved = { queue: [], requesterOpenId: state.requesterOpenId };
13066
+ active.set(sessionKey, reserved);
13067
+ try {
13068
+ const history2 = await be.readHistory(state.cwd, sessionId);
13069
+ resumePending.delete(evt.messageId);
13070
+ await withTrace({ chatId: state.chatId, msgId: state.originalMsgId }, async () => {
13071
+ const cardState = { cwd: state.cwd, projectName: state.projectName, history: history2 };
13072
+ await sendManagedCard(channel, state.chatId, buildHistoryCard(cardState), state.originalMsgId, false).catch(
13073
+ (err) => log.fail("card", err, { phase: "resume-history-single" })
13074
+ );
13075
+ const old = sessions.get(sessionKey);
13076
+ if (old) {
13077
+ sessions.delete(sessionKey);
13078
+ void old.close().catch(() => void 0);
13079
+ }
13080
+ lastUsage.delete(sessionKey);
13081
+ const now = Date.now();
13082
+ await upsertSession({
13083
+ threadId: sessionKey,
13084
+ chatId: state.chatId,
13085
+ cwd: state.cwd,
13086
+ sessionId,
13087
+ backend: be.id,
13088
+ summary: history2.name || history2.preview || "(\u6062\u590D\u4F1A\u8BDD)",
13089
+ createdAt: now,
13090
+ updatedAt: now
13091
+ });
13092
+ log.info("card", "resume-done-single", { sessionKey, sessionId, turns: history2.totalTurns });
13093
+ });
13094
+ settleUpdate(evt.messageId, buildResumeDoneCard(state));
13095
+ } finally {
13096
+ if (active.get(sessionKey) === reserved) active.delete(sessionKey);
13097
+ if (reserved.queue.length > 0) {
13098
+ await channel.send(
13099
+ state.chatId,
13100
+ { 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` },
13101
+ { replyTo: state.originalMsgId }
13102
+ ).catch(() => void 0);
13103
+ }
13104
+ }
13105
+ return;
13106
+ }
12797
13107
  const history = await be.readHistory(state.cwd, sessionId);
12798
13108
  resumePending.delete(evt.messageId);
12799
13109
  let bound = false;
@@ -14580,16 +14890,22 @@ async function doUpdate() {
14580
14890
  }
14581
14891
  const res = await installLatest();
14582
14892
  log.info("daemon-control", "update-installed", { ok: res.ok });
14583
- if (!res.ok) return;
14893
+ appendServiceErr("daemon-control", `update-installed ok=${res.ok}`);
14894
+ if (!res.ok) {
14895
+ appendServiceErr("daemon-control", "install failed \u2014 keeping old version, not restarting");
14896
+ return;
14897
+ }
14584
14898
  await doRestart("update");
14585
14899
  }
14586
14900
  async function doRestart(phase) {
14587
14901
  if (!isServiceRunning()) {
14588
14902
  log.info("daemon-control", "restart-skipped-no-service", { phase });
14903
+ appendServiceErr("daemon-control", `restart-skipped-no-service (phase=${phase}); service.pid \u672A\u6307\u5411\u5728\u8DD1\u7684 daemon`);
14589
14904
  return;
14590
14905
  }
14591
14906
  await getServiceAdapter().restart();
14592
14907
  log.info("daemon-control", "restart-issued", { phase });
14908
+ appendServiceErr("daemon-control", `restart-issued (phase=${phase})`);
14593
14909
  }
14594
14910
 
14595
14911
  // src/web/mount.ts
@@ -15338,6 +15654,9 @@ ${UI_PURE_JS}
15338
15654
  var daemon = null; // /api/daemon
15339
15655
  var diag = null; // /api/diagnosis \u7ED3\u679C\uFF08\u5F53\u524D bot \u7684\u63A2\u6D4B\uFF1B\u5207 Tab \u5FC5\u6E05\uFF09
15340
15656
  var catalog = null; // /api/backends \u7F13\u5B58\uFF08\u540E\u7AEF\u7BA1\u7406\u5361 + \u9879\u76EE picker \u590D\u7528\uFF09
15657
+ var lastUpdate = null; // /api/update/check \u7ED3\u679C\u7F13\u5B58\uFF1A\u603B\u89C8 5s \u91CD\u6E32\u4E0D\u91CD\u67E5\uFF08\u6BCF\u6B21\u67E5\u90FD spawn npm view\uFF09
15658
+ var updateCheckedAt = 0; // \u4E0A\u6B21\u68C0\u67E5\u65F6\u95F4\u6233\uFF1B\u62FF\u5230\u7ED3\u679C\u8D85 30min\u3001\u8F6F\u5931\u8D25\u8D85 ~2min \u624D\u968F\u91CD\u6E32\u81EA\u52A8\u590D\u67E5
15659
+ var updateChecking = false;// in-flight \u95F8\uFF1Anpm view \u53EF\u80FD\u6570\u79D2~20s\uFF0C\u671F\u95F4 5s \u91CD\u6E32\u4E0D\u53E0\u53D1\u7B2C\u4E8C\u4E2A\u8BF7\u6C42
15341
15660
  var drawerProject = null; // \u62BD\u5C49\u91CC\u6253\u5F00\u7684\u9879\u76EE\u540D
15342
15661
  var diagBotId = null; // diag \u5C5E\u4E8E\u54EA\u4E2A bot\uFF08\u9632\u4E32\u53F0\uFF09
15343
15662
  var bkDetailId = null; // \u540E\u7AEF Agent \u8BE6\u60C5\u89C6\u56FE\u5F53\u524D\u5C55\u5F00\u7684\u540E\u7AEF id\uFF08null=\u5217\u8868\uFF09
@@ -15536,6 +15855,14 @@ ${UI_PURE_JS}
15536
15855
  fetch('/api/daemon').then(function (r) { return r.json(); })
15537
15856
  .then(function (d) {
15538
15857
  daemon = d;
15858
+ // \u8FD0\u884C\u4E2D\u7684\u5B9E\u9645\u7248\u672C\u4E00\u65E6\u8DDF\u7248\u672C\u68C0\u67E5\u7F13\u5B58\u91CC\u7684 current \u5BF9\u4E0D\u4E0A\uFF08\u591A\u534A\u662F\u521A\u66F4\u65B0+\u91CD\u542F\u5B8C\u6210\uFF09\uFF0C
15859
+ // \u5C31\u4F5C\u5E9F\u7F13\u5B58\u5E76\u5F53\u573A\u8865\u67E5\u4E00\u6B21\uFF08\u82E5\u5728\u603B\u89C8\u9875\uFF09\uFF0C\u628A\u300C\u6709\u65B0\u7248\u300D\u7ACB\u523B\u7FFB\u6210\u300C\u5DF2\u662F\u6700\u65B0\u7248\u300D\u2014\u2014\u4E0D\u7B49
15860
+ // \u4E0B\u4E00\u8F6E renderRoute\uFF0C\u6D88\u9664 loadState/loadDaemon \u540C\u5468\u671F\u7ADE\u6001\u5BFC\u81F4\u7684 ~10s \u5EF6\u8FDF\u4E0E\u6A2A\u5E45\u6B8B\u7559\u3002
15861
+ // \u53EA\u5728\u7248\u672C\u771F\u53D8\u65F6\u624D\u67E5\uFF08\u7A33\u6001 d.version===current\uFF09\uFF0C\u65E2\u7559\u4F4F\u66F4\u65B0\u540E\u7684\u5373\u65F6\u53CD\u9988\uFF0C\u53C8\u4E0D\u56DE\u5230 5s \u7A7A\u8F6C\u3002
15862
+ if (lastUpdate && d.version && d.version !== lastUpdate.current) {
15863
+ lastUpdate = null; updateCheckedAt = 0;
15864
+ if (parseRoute(location.hash).tab === 'overview') { var ub = $('updateBody'); if (ub) loadUpdate(ub); }
15865
+ }
15539
15866
  if (parseRoute(location.hash).tab === 'overview') { var b = $('daemonBody'); if (b) renderDaemon(b); }
15540
15867
  renderSidebarFoot();
15541
15868
  renderGlobalSummary();
@@ -15849,7 +16176,14 @@ ${UI_PURE_JS}
15849
16176
  daemonCard.appendChild(updateBody);
15850
16177
  root.appendChild(daemonCard);
15851
16178
  renderDaemon(daemonBody);
15852
- loadUpdate(updateBody);
16179
+ // \u7248\u672C\u68C0\u67E5\u4F1A\u5728\u670D\u52A1\u7AEF spawn\u300Cnpm view\u300D\uFF0C\u4E0D\u80FD\u8DDF\u7740 5s \u91CD\u6E32\u6BCF\u6B21\u90FD\u8DD1\uFF1A\u6709\u7F13\u5B58\u5C31\u76F4\u63A5\u6E32\uFF0C
16180
+ // \u53EA\u5728\u9996\u6B21\u6216\u7F13\u5B58\u8FC7\u671F\uFF0830min\uFF09\u65F6\u624D\u771F\u67E5\u4E00\u6B21\u3002\u624B\u52A8\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u6309\u94AE\u4ECD\u5F3A\u5236\u5237\u65B0\u3002
16181
+ if (lastUpdate) {
16182
+ renderUpdate(updateBody, lastUpdate);
16183
+ if (Date.now() - updateCheckedAt > 30 * 60 * 1000) loadUpdate(updateBody);
16184
+ } else {
16185
+ loadUpdate(updateBody);
16186
+ }
15853
16187
 
15854
16188
  // \u{1F916} \u673A\u5668\u4EBA\uFF08\u5728 Feishu Bridge \u5361\u4E0B\u9762\uFF0C\u5404\u81EA\u5360\u6EE1\u5BBD\u5EA6\uFF09
15855
16189
  var botsCard = el('div', 'card');
@@ -16208,9 +16542,19 @@ ${UI_PURE_JS}
16208
16542
  }
16209
16543
 
16210
16544
  function loadUpdate(box) {
16545
+ if (updateChecking) return; // \u5728\u9014\u5C31\u4E0D\u53E0\u53D1\uFF1A\u6162\u68C0\u67E5\uFF08npm view \u6700\u957F 20s\uFF09\u671F\u95F4 5s \u91CD\u6E32\u4E0D\u91CD\u590D spawn
16546
+ updateChecking = true;
16547
+ updateCheckedAt = Date.now(); // \u4E50\u89C2\u5360\u4F4D\uFF1Ain-flight \u671F\u95F4 30min \u8FB9\u754C\u4E0D\u518D\u88AB\u53CD\u590D\u5224\u771F
16211
16548
  fetch('/api/update/check').then(function (r) { return r.json(); })
16212
- .then(function (u) { renderUpdate(box, u); })
16213
- .catch(function () { box.textContent = '\u26A0\uFE0F \u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF08\u7F51\u7EDC\u6216 npm registry\uFF09'; });
16549
+ .then(function (u) {
16550
+ lastUpdate = u;
16551
+ // \u8F6F\u5931\u8D25\uFF1A\u975E dev \u5374\u6CA1\u62FF\u5230 latest\uFF08\u591A\u534A registry \u4E0D\u53EF\u8FBE/\u8D85\u65F6\uFF0C\u670D\u52A1\u7AEF\u4ECD\u56DE 200\uFF09\uFF0C\u53EA\u7F13\u5B58 ~2min
16552
+ // \u4FBF\u81EA\u52A8\u590D\u67E5\uFF0C\u522B\u628A\u300C\u67E5\u8BE2\u5931\u8D25\u300D\u9489\u4F4F 30min\uFF1B\u62FF\u5230 latest \u6216 dev \u6A21\u5F0F\u624D\u6309\u5B8C\u6574 30min TTL\u3002
16553
+ updateCheckedAt = (u && (u.dev || u.latest)) ? Date.now() : Date.now() - 28 * 60 * 1000;
16554
+ renderUpdate(box, u);
16555
+ })
16556
+ .catch(function () { box.textContent = '\u26A0\uFE0F \u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF08\u7F51\u7EDC\u6216 npm registry\uFF09'; })
16557
+ .then(function () { updateChecking = false; }); // finally\uFF1A\u65E0\u8BBA\u6210\u8D25\u90FD\u677E\u95F8
16214
16558
  }
16215
16559
  function renderUpdate(box, u) {
16216
16560
  box.textContent = '';
@@ -17984,6 +18328,7 @@ async function runSupervisor(bots) {
17984
18328
  const supervisorStartedAt = Date.now();
17985
18329
  const cliEntry = process.argv[1];
17986
18330
  if (!cliEntry) throw new Error("supervisor: \u65E0\u6CD5\u89E3\u6790 CLI \u5165\u53E3\uFF08process.argv[1] \u4E3A\u7A7A\uFF09");
18331
+ recordServicePid();
17987
18332
  const childEnv = { ...process.env };
17988
18333
  delete childEnv[SERVICE_ENV_FLAG];
17989
18334
  let shuttingDown = false;
@@ -18250,6 +18595,7 @@ async function runOnboardingConsole() {
18250
18595
  releaseLock = acquireSingleInstanceLock("__onboarding__");
18251
18596
  } catch (err) {
18252
18597
  if (err instanceof BridgeAlreadyRunningError) {
18598
+ clearServicePid();
18253
18599
  console.error(`\u2717 ${err.message}`);
18254
18600
  process.exitCode = 1;
18255
18601
  return;
@@ -18318,6 +18664,7 @@ async function runSingle(botName) {
18318
18664
  releaseLock = acquireSingleInstanceLock(cfg.accounts.app.id);
18319
18665
  } catch (err) {
18320
18666
  if (err instanceof BridgeAlreadyRunningError) {
18667
+ clearServicePid();
18321
18668
  console.error(`\u2717 ${err.message}`);
18322
18669
  log.info("run", "already-running", { pid: err.pid });
18323
18670
  process.exitCode = 1;
@@ -18873,6 +19220,9 @@ program.command("web").description("\u672C\u673A Web \u63A7\u5236\u53F0\uFF08\u5
18873
19220
  program.command("__daemon-control <action>", { hidden: true }).action(async (action) => {
18874
19221
  await runDaemonControl(action);
18875
19222
  });
19223
+ program.command("__win-relaunch", { hidden: true }).action(async () => {
19224
+ await runWinRelaunch();
19225
+ });
18876
19226
  var bot = program.command("bot").description("\u98DE\u4E66\u673A\u5668\u4EBA\u7BA1\u7406\uFF08\u591A\u673A\u5668\u4EBA\uFF09");
18877
19227
  bot.command("init [name]").description("\u6CE8\u518C\u4E00\u4E2A\u98DE\u4E66\u673A\u5668\u4EBA\u5E76\u6388\u6743\uFF08\u53EF\u9009\u77ED\u540D\uFF09").action(async (name) => {
18878
19228
  await runBotInit(name);
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.7-test.0",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {