@modelzen/feishu-codex-bridge 0.6.7-test.2 → 0.6.7-test.4

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 (2) hide show
  1. package/dist/cli.js +253 -17
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5716,6 +5716,8 @@ var DM = {
5716
5716
  coffeeSettings: "dm.coffee.settings",
5717
5717
  doctor: "dm.doctor",
5718
5718
  reconnect: "dm.reconnect",
5719
+ restart: "dm.restart",
5720
+ restartDo: "dm.restart.do",
5719
5721
  update: "dm.update",
5720
5722
  updateDo: "dm.update.do",
5721
5723
  // 📊 Codex 用量(限额 + 个人资料统计 + 热力图);share 打开内容选择卡,
@@ -5793,7 +5795,7 @@ function buildDmMenuCard(opts = {}) {
5793
5795
  actionsFixed([
5794
5796
  button("\u{1F4CA} \u7528\u91CF", { a: DM.usage }),
5795
5797
  button("\u{1FA7A} \u8BCA\u65AD", { a: DM.doctor }),
5796
- button("\u{1F504} \u91CD\u8FDE", { a: DM.reconnect }),
5798
+ button("\u{1F501} \u91CD\u542F", { a: DM.restart }),
5797
5799
  button("\u2B06\uFE0F \u66F4\u65B0", { a: DM.update })
5798
5800
  ], MENU_BTN_W_BOT),
5799
5801
  // 🌐 网页控制台:刻意低调——小号按钮靠左 + 右侧灰字说明;仅在 daemon 本机控制台
@@ -5910,6 +5912,35 @@ function buildReconnectCard(conn) {
5910
5912
  { header: { title: "\u{1F504} \u957F\u8FDE\u63A5", template } }
5911
5913
  );
5912
5914
  }
5915
+ function buildRestartConfirmCard(conn) {
5916
+ return card(
5917
+ [
5918
+ md(`\u957F\u8FDE\u63A5\u72B6\u6001\uFF1A**${conn}**`),
5919
+ note("\u91CD\u542F\u4F1A**\u65AD\u5F00\u5F53\u524D\u6240\u6709\u4F1A\u8BDD**\u5E76\u91CD\u65B0\u62C9\u8D77\u540E\u53F0\u670D\u52A1\uFF08\u7EA6\u6570\u79D2\uFF0C\u5176\u95F4\u673A\u5668\u4EBA\u77ED\u6682\u79BB\u7EBF\uFF09\uFF0C\u5B8C\u6210\u540E\u81EA\u52A8\u6062\u590D\u3002\u4EC5\u5728\u957F\u671F\u65AD\u8FDE\u6216\u5F02\u5E38\u65F6\u624D\u9700\u8981\u3002"),
5920
+ actions([
5921
+ button("\u{1F501} \u786E\u8BA4\u91CD\u542F", { a: DM.restartDo }, "danger"),
5922
+ button("\u53D6\u6D88", { a: DM.menu })
5923
+ ])
5924
+ ],
5925
+ { header: { title: "\u{1F501} \u91CD\u542F\u540E\u53F0\u670D\u52A1", template: "orange" } }
5926
+ );
5927
+ }
5928
+ function buildRestartingCard(mode = "restarting") {
5929
+ if (mode === "foreground") {
5930
+ return card(
5931
+ [
5932
+ md("\u2139\uFE0F \u5F53\u524D\u4E3A**\u524D\u53F0\u8FD0\u884C**\uFF08\u975E\u540E\u53F0\u5B88\u62A4\u670D\u52A1\uFF09\u3002"),
5933
+ note("\u6B64\u6309\u94AE\u53EA\u91CD\u542F\u7531 `feishu-codex-bridge start` \u5B89\u88C5\u7684\u540E\u53F0\u670D\u52A1\u3002\u524D\u53F0\u8FD0\u884C\u8BF7\u5728\u5176\u7EC8\u7AEF\u91CC Ctrl+C \u540E\u91CD\u8DD1 `feishu-codex-bridge run`\u3002"),
5934
+ backToMenu()
5935
+ ],
5936
+ { header: { title: "\u{1F501} \u91CD\u542F\u540E\u53F0\u670D\u52A1", template: "orange" } }
5937
+ );
5938
+ }
5939
+ return card(
5940
+ [md("\u{1F501} \u6B63\u5728\u91CD\u542F\u540E\u53F0\u670D\u52A1\u2026"), note("\u673A\u5668\u4EBA\u5C06\u77ED\u6682\u79BB\u7EBF\u6570\u79D2\u540E\u81EA\u52A8\u6062\u590D\uFF1B\u672C\u5361\u4E0D\u518D\u66F4\u65B0\u3002")],
5941
+ { header: { title: "\u{1F501} \u91CD\u542F\u540E\u53F0\u670D\u52A1", template: "orange" } }
5942
+ );
5943
+ }
5913
5944
  function connLabel(state) {
5914
5945
  switch (state) {
5915
5946
  case "connected":
@@ -8509,7 +8540,8 @@ function withCodexHooksFeature(text) {
8509
8540
  }
8510
8541
 
8511
8542
  // src/service/update.ts
8512
- import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
8543
+ init_paths();
8544
+ import { closeSync, existsSync as existsSync9, openSync as openSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2, writeSync } from "fs";
8513
8545
  import { dirname as dirname10, join as join15, resolve as resolve6 } from "path";
8514
8546
  import { fileURLToPath as fileURLToPath4 } from "url";
8515
8547
 
@@ -8800,7 +8832,17 @@ function startNow(opts = {}) {
8800
8832
  stdio: ["ignore", out, err],
8801
8833
  env: mergeProcessEnv(process.env, overrides)
8802
8834
  });
8803
- if (child.pid) writeFileSync(join13(dir, "service.pid"), String(child.pid), "utf8");
8835
+ if (child.pid) {
8836
+ const pidPath = join13(dir, "service.pid");
8837
+ let cur = null;
8838
+ try {
8839
+ const n = Number.parseInt(readFileSync3(pidPath, "utf8").trim(), 10);
8840
+ cur = Number.isFinite(n) ? n : null;
8841
+ } catch {
8842
+ cur = null;
8843
+ }
8844
+ if (cur === null || !pidAlive(cur)) writeFileSync(pidPath, String(child.pid), "utf8");
8845
+ }
8804
8846
  child.unref();
8805
8847
  }
8806
8848
  async function installWinStartup() {
@@ -8816,7 +8858,7 @@ async function installWinStartup() {
8816
8858
  if (reg.status !== 0) {
8817
8859
  throw new Error(`\u5199\u5165\u767B\u5F55\u81EA\u542F\u6CE8\u518C\u8868\u9879\u5931\u8D25\uFF08exit ${reg.status ?? "unknown"}\uFF09\uFF1A${(reg.stderr || reg.stdout || "").trim()}`);
8818
8860
  }
8819
- startNow();
8861
+ if (!winStartupRunning()) startNow();
8820
8862
  return statusWinStartup();
8821
8863
  }
8822
8864
  async function uninstallWinStartup() {
@@ -8885,7 +8927,11 @@ function clearRelaunchClaim(reqPath) {
8885
8927
  function buildRelauncherPowershell(reqPath, binPath = resolveCliBinPath(), nodePath = process.execPath) {
8886
8928
  const inner = `"${nodePath}" "${binPath}" __win-relaunch "${reqPath}"`;
8887
8929
  const lit = `'${inner.replace(/'/g, "''")}'`;
8888
- return `$c=${lit}; try { $r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine=$c } -ErrorAction Stop; Write-Output ('cim rv=' + $r.ReturnValue + ' pid=' + $r.ProcessId) } catch { Write-Output ('cim-error: ' + $_.Exception.Message); schtasks /create /tn ${RELAUNCH_TASK_NAME} /tr $c /sc ONCE /st 00:00 /f | Out-Null; $cr=$LASTEXITCODE; schtasks /run /tn ${RELAUNCH_TASK_NAME} | Out-Null; Write-Output ('schtasks create=' + $cr + ' run=' + $LASTEXITCODE) }`;
8930
+ return (
8931
+ // Silence the progress stream: with stderr redirected PowerShell serializes
8932
+ // progress records as noisy #< CLIXML blobs into our captured output.
8933
+ `$ProgressPreference='SilentlyContinue'; $c=${lit}; try { $r = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine=$c } -ErrorAction Stop; Write-Output ('cim rv=' + $r.ReturnValue + ' pid=' + $r.ProcessId) } catch { Write-Output ('cim-error: ' + $_.Exception.Message); schtasks /create /tn ${RELAUNCH_TASK_NAME} /tr $c /sc ONCE /st 00:00 /f | Out-Null; $cr=$LASTEXITCODE; schtasks /run /tn ${RELAUNCH_TASK_NAME} | Out-Null; Write-Output ('schtasks create=' + $cr + ' run=' + $LASTEXITCODE) }`
8934
+ );
8889
8935
  }
8890
8936
  function encodePowershellCommand(script) {
8891
8937
  return Buffer.from(script, "utf16le").toString("base64");
@@ -8983,7 +9029,12 @@ async function runWinRelaunch(deps = {}) {
8983
9029
  }
8984
9030
  await sleep(300);
8985
9031
  start({ appDir: appDir2, envPath: req.envPath, userProfile: req.userProfile });
8986
- logLine(`relaunched; new daemon started (appDir=${appDir2})`);
9032
+ let newPid = "?";
9033
+ try {
9034
+ newPid = readFileSync3(join13(appDir2, "service.pid"), "utf8").trim() || "?";
9035
+ } catch {
9036
+ }
9037
+ logLine(`relaunched; new daemon pid=${newPid} (appDir=${appDir2})`);
8987
9038
  } finally {
8988
9039
  clearClaim(reqPath);
8989
9040
  if (process.platform === "win32") {
@@ -9297,6 +9348,84 @@ async function installLatest(opts = {}) {
9297
9348
  });
9298
9349
  });
9299
9350
  }
9351
+ var UPDATE_LOCK_STALE_MS = 30 * 60 * 1e3;
9352
+ function updateLockFile() {
9353
+ return join15(paths.appDir, "update.lock");
9354
+ }
9355
+ function livePid(pid) {
9356
+ try {
9357
+ process.kill(pid, 0);
9358
+ return true;
9359
+ } catch (err) {
9360
+ return err.code === "EPERM";
9361
+ }
9362
+ }
9363
+ function isUpdateLockStale(file) {
9364
+ try {
9365
+ const rec = JSON.parse(readFileSync4(file, "utf8"));
9366
+ if (typeof rec.pid !== "number") return true;
9367
+ if (!livePid(rec.pid)) return true;
9368
+ return typeof rec.at === "number" && Date.now() - rec.at > UPDATE_LOCK_STALE_MS;
9369
+ } catch {
9370
+ return true;
9371
+ }
9372
+ }
9373
+ function acquireUpdateLock() {
9374
+ const file = updateLockFile();
9375
+ for (let attempt = 0; attempt < 3; attempt++) {
9376
+ try {
9377
+ const fd = openSync2(file, "wx");
9378
+ writeSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
9379
+ closeSync(fd);
9380
+ return releaseUpdateLock(file);
9381
+ } catch (err) {
9382
+ const code = err.code;
9383
+ if (code !== "EEXIST") return null;
9384
+ if (!isUpdateLockStale(file)) return null;
9385
+ try {
9386
+ rmSync2(file, { force: true });
9387
+ } catch {
9388
+ }
9389
+ }
9390
+ }
9391
+ return null;
9392
+ }
9393
+ function releaseUpdateLock(file) {
9394
+ return () => {
9395
+ try {
9396
+ const rec = JSON.parse(readFileSync4(file, "utf8"));
9397
+ if (rec.pid !== process.pid) return;
9398
+ } catch {
9399
+ }
9400
+ try {
9401
+ rmSync2(file, { force: true });
9402
+ } catch {
9403
+ }
9404
+ };
9405
+ }
9406
+ function updateStatusFile() {
9407
+ return join15(paths.appDir, "update-status.json");
9408
+ }
9409
+ function writeUpdateStatus(status) {
9410
+ try {
9411
+ writeFileSync2(updateStatusFile(), JSON.stringify(status), "utf8");
9412
+ } catch {
9413
+ }
9414
+ }
9415
+ function readUpdateStatus() {
9416
+ try {
9417
+ const s = JSON.parse(readFileSync4(updateStatusFile(), "utf8"));
9418
+ return s && typeof s.phase === "string" && typeof s.at === "number" ? s : null;
9419
+ } catch {
9420
+ return null;
9421
+ }
9422
+ }
9423
+ function clearUpdateStatus() {
9424
+ try {
9425
+ rmSync2(updateStatusFile(), { force: true });
9426
+ } catch {
9427
+ }
9428
+ }
9300
9429
  function daemonRunning() {
9301
9430
  return isServiceRunning();
9302
9431
  }
@@ -9886,7 +10015,7 @@ function buildUsageShareCard(data, opts = {}) {
9886
10015
  // src/web/discovery.ts
9887
10016
  init_paths();
9888
10017
  import { randomUUID as randomUUID4 } from "crypto";
9889
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
10018
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
9890
10019
  import { dirname as dirname11 } from "path";
9891
10020
  function publishWebConsole(rec, file = paths.webConsoleFile) {
9892
10021
  mkdirSync2(dirname11(file), { recursive: true });
@@ -9894,7 +10023,7 @@ function publishWebConsole(rec, file = paths.webConsoleFile) {
9894
10023
  unlinkSync(file);
9895
10024
  } catch {
9896
10025
  }
9897
- writeFileSync2(file, `${JSON.stringify(rec, null, 2)}
10026
+ writeFileSync3(file, `${JSON.stringify(rec, null, 2)}
9898
10027
  `, { mode: 384 });
9899
10028
  }
9900
10029
  function readWebConsole(file = paths.webConsoleFile) {
@@ -9934,7 +10063,7 @@ function stableWebConsoleToken(file = paths.webTokenFile) {
9934
10063
  unlinkSync(file);
9935
10064
  } catch {
9936
10065
  }
9937
- writeFileSync2(file, `${token}
10066
+ writeFileSync3(file, `${token}
9938
10067
  `, { mode: 384 });
9939
10068
  } catch {
9940
10069
  }
@@ -12639,6 +12768,24 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
12639
12768
  await sendManagedCard(channel, evt.chatId, buildReconnectCard(conn), evt.messageId).catch(
12640
12769
  (err) => log.fail("console", err, { cmd: "reconnect" })
12641
12770
  );
12771
+ }).on(DM.restart, async ({ evt }) => {
12772
+ if (!dmAdmin(evt.operator?.openId)) return;
12773
+ const conn = channel.getConnectionStatus?.()?.state ?? "unknown";
12774
+ await sendManagedCard(channel, evt.chatId, buildRestartConfirmCard(conn), evt.messageId).catch(
12775
+ (err) => log.fail("console", err, { cmd: "restart" })
12776
+ );
12777
+ }).on(DM.restartDo, ({ evt }) => {
12778
+ if (!dmAdmin(evt.operator?.openId)) return;
12779
+ void (async () => {
12780
+ await new Promise((r) => setTimeout(r, CARD_SETTLE_MS));
12781
+ if (!daemonRunning()) {
12782
+ await updateManagedCard(channel, evt.messageId, buildRestartingCard("foreground")).catch(() => void 0);
12783
+ return;
12784
+ }
12785
+ await updateManagedCard(channel, evt.messageId, buildRestartingCard("restarting")).catch(() => void 0);
12786
+ await new Promise((r) => setTimeout(r, 800));
12787
+ await restartDaemon().catch((e) => log.fail("console", e, { phase: "restart" }));
12788
+ })();
12642
12789
  }).on(DM.update, ({ evt }) => {
12643
12790
  if (!dmAdmin(evt.operator?.openId)) return;
12644
12791
  void (async () => {
@@ -12661,10 +12808,24 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
12661
12808
  void (async () => {
12662
12809
  await new Promise((r) => setTimeout(r, CARD_SETTLE_MS));
12663
12810
  const from = currentVersion();
12811
+ const release2 = acquireUpdateLock();
12812
+ if (!release2) {
12813
+ await updateManagedCard(
12814
+ channel,
12815
+ evt.messageId,
12816
+ buildUpdateCard({ phase: "error", from, message: "\u5DF2\u6709\u4E00\u4E2A\u66F4\u65B0\u6B63\u5728\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7A0D\u5019\u518D\u8BD5\u3002" })
12817
+ ).catch(() => void 0);
12818
+ return;
12819
+ }
12664
12820
  await updateManagedCard(channel, evt.messageId, buildUpdateCard({ phase: "updating", from })).catch(
12665
12821
  () => void 0
12666
12822
  );
12667
- const res = await installLatest();
12823
+ let res;
12824
+ try {
12825
+ res = await installLatest();
12826
+ } finally {
12827
+ release2();
12828
+ }
12668
12829
  if (!res.ok) {
12669
12830
  log.info("console", "update-failed", { from });
12670
12831
  await updateManagedCard(
@@ -13969,6 +14130,9 @@ ${facts}`;
13969
14130
  case DM.reconnect:
13970
14131
  await sendDm(buildReconnectCard(channel.getConnectionStatus?.()?.state ?? "unknown"));
13971
14132
  break;
14133
+ case DM.restart:
14134
+ await sendDm(buildRestartConfirmCard(channel.getConnectionStatus?.()?.state ?? "unknown"));
14135
+ break;
13972
14136
  case DM.usage: {
13973
14137
  const { messageId } = await sendDm(buildUsageCard({ phase: "loading" }));
13974
14138
  let state;
@@ -14683,8 +14847,12 @@ function createAdminService(deps = {}) {
14683
14847
  dev: false
14684
14848
  }));
14685
14849
  },
14850
+ updateStatus() {
14851
+ return readUpdateStatus();
14852
+ },
14686
14853
  async applyUpdate() {
14687
14854
  if (!deps.applyUpdate) throw new NotWiredYetError("\u2B06\uFE0F \u5347\u7EA7");
14855
+ clearUpdateStatus();
14688
14856
  deps.applyUpdate();
14689
14857
  },
14690
14858
  async hostDoctor() {
@@ -14912,14 +15080,31 @@ async function doUpdate() {
14912
15080
  log.info("daemon-control", "update-skipped-dev", {});
14913
15081
  return;
14914
15082
  }
14915
- const res = await installLatest();
15083
+ const release2 = acquireUpdateLock();
15084
+ if (!release2) {
15085
+ log.info("daemon-control", "update-skipped-in-progress", {});
15086
+ appendServiceErr("daemon-control", "update already in progress (lock held by another trigger); skipped");
15087
+ return;
15088
+ }
15089
+ const from = currentVersion();
15090
+ writeUpdateStatus({ phase: "installing", from, at: Date.now() });
15091
+ let res;
15092
+ try {
15093
+ res = await installLatest();
15094
+ } finally {
15095
+ release2();
15096
+ }
14916
15097
  log.info("daemon-control", "update-installed", { ok: res.ok });
14917
15098
  appendServiceErr("daemon-control", `update-installed ok=${res.ok}`);
14918
15099
  if (!res.ok) {
14919
15100
  appendServiceErr("daemon-control", "install failed \u2014 keeping old version, not restarting");
15101
+ writeUpdateStatus({ phase: "error", ok: false, from, message: `\u5B89\u88C5\u5931\u8D25\uFF1A${res.message}`.slice(0, 300), at: Date.now() });
14920
15102
  return;
14921
15103
  }
15104
+ const to = currentVersion();
15105
+ writeUpdateStatus({ phase: "restarting", ok: true, from, to, at: Date.now() });
14922
15106
  await doRestart("update");
15107
+ writeUpdateStatus({ phase: "done", ok: true, from, to, at: Date.now() });
14923
15108
  }
14924
15109
  async function doRestart(phase) {
14925
15110
  if (!isServiceRunning()) {
@@ -15681,6 +15866,7 @@ ${UI_PURE_JS}
15681
15866
  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
15682
15867
  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
15683
15868
  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
15869
+ var updateInFlight = false; // \u300C\u5347\u7EA7\u5E76\u91CD\u542F\u300Din-flight\uFF1A\u7981\u7528\u6309\u94AE\u9632\u91CD\u590D\u70B9\u51FB\uFF08\u5E76\u53D1\u89E6\u53D1\u4F1A\u7ED5\u8FC7\u670D\u52A1\u7AEF\u66F4\u65B0\u9501\u7684\u7A84\u7A97\uFF09\uFF0C\u5E76\u9A71\u52A8\u8F6E\u8BE2\u7ED3\u679C
15684
15870
  var drawerProject = null; // \u62BD\u5C49\u91CC\u6253\u5F00\u7684\u9879\u76EE\u540D
15685
15871
  var diagBotId = null; // diag \u5C5E\u4E8E\u54EA\u4E2A bot\uFF08\u9632\u4E32\u53F0\uFF09
15686
15872
  var bkDetailId = null; // \u540E\u7AEF Agent \u8BE6\u60C5\u89C6\u56FE\u5F53\u524D\u5C55\u5F00\u7684\u540E\u7AEF id\uFF08null=\u5217\u8868\uFF09
@@ -16590,10 +16776,13 @@ ${UI_PURE_JS}
16590
16776
  if (u.hasUpdate && u.latest) {
16591
16777
  box.appendChild(el('div', null, '\u{1F195} \u6709\u65B0\u7248 v' + u.latest + '\uFF08\u5F53\u524D v' + u.current + '\uFF09'));
16592
16778
  var row = el('div', 'statline');
16593
- var up = el('button', 'btn primary', '\u2B06\uFE0F \u66F4\u65B0\u5E76\u91CD\u542F \xB7 v' + u.latest);
16594
- up.onclick = function () { askUpdate(u.latest); };
16779
+ // updateInFlight \u671F\u95F4\u663E\u793A\u7981\u7528\u7684\u300C\u66F4\u65B0\u4E2D\u2026\u300D\u2014\u2014\u5373\u4F7F\u5361\u7247\u6BCF 5s \u91CD\u6E32\uFF0C\u6309\u94AE\u4E5F\u4FDD\u6301\u7981\u7528\uFF0C
16780
+ // \u6321\u4F4F\u91CD\u590D\u63D0\u4EA4\uFF08\u91CD\u590D\u89E6\u53D1\u662F\u5E76\u53D1\u7ED5\u8FC7\u670D\u52A1\u7AEF\u66F4\u65B0\u9501\u7684\u5165\u53E3\uFF09\u3002
16781
+ var up = el('button', 'btn primary', updateInFlight ? '\u23F3 \u66F4\u65B0\u4E2D\u2026' : ('\u2B06\uFE0F \u66F4\u65B0\u5E76\u91CD\u542F \xB7 v' + u.latest));
16782
+ if (updateInFlight) { up.disabled = true; }
16783
+ else { up.onclick = function () { askUpdate(u.latest); }; }
16595
16784
  row.appendChild(up);
16596
- row.appendChild(el('span', 'note', '\u9ED8\u8BA4\u53EA\u68C0\u6D4B\u4E0D\u81EA\u52A8\u5347\u7EA7\uFF1B\u70B9\u4E0A\u65B9\u6309\u94AE\u88C5\u6700\u65B0\u7248\u5E76\u81EA\u52A8\u91CD\u542F\u3002'));
16785
+ row.appendChild(el('span', 'note', updateInFlight ? '\u5B89\u88C5\u4E2D\uFF0C\u5B8C\u6210\u540E\u4F1A\u81EA\u52A8\u91CD\u542F\u2026' : '\u9ED8\u8BA4\u53EA\u68C0\u6D4B\u4E0D\u81EA\u52A8\u5347\u7EA7\uFF1B\u70B9\u4E0A\u65B9\u6309\u94AE\u88C5\u6700\u65B0\u7248\u5E76\u81EA\u52A8\u91CD\u542F\u3002'));
16597
16786
  box.appendChild(row);
16598
16787
  } else {
16599
16788
  box.appendChild(el('div', 'note', '\u2705 \u5DF2\u662F\u6700\u65B0\u7248 v' + u.current + (u.latest ? '' : '\uFF08\u6700\u65B0\u7248\u67E5\u8BE2\u5931\u8D25\uFF0C\u53EF\u7A0D\u540E\u91CD\u8BD5\uFF09')));
@@ -16643,9 +16832,52 @@ ${UI_PURE_JS}
16643
16832
  '\u91CD\u542F\u671F\u95F4\u6240\u6709\u7FA4\u77ED\u6682\u65E0\u54CD\u5E94\uFF1B\u6B63\u5728\u8FDB\u884C\u7684\u4F1A\u8BDD\u4F1A\u88AB\u4F18\u96C5\u5173\u95ED\u3002',
16644
16833
  ],
16645
16834
  confirmLabel: '\u786E\u8BA4\u66F4\u65B0',
16646
- onConfirm: function () { postAction('/api/update', '\u66F4\u65B0'); },
16835
+ onConfirm: function () { postUpdate(latest); },
16647
16836
  });
16648
16837
  }
16838
+ // \u5347\u7EA7\uFF1APOST \u540E\u8F6E\u8BE2 /api/update/status\uFF0C\u5931\u8D25\u4F1A\u660E\u786E\u62A5\u51FA\u6765\uFF08\u5426\u5219 detached helper \u7684\u5931\u8D25\u5BF9
16839
+ // \u7F51\u9875\u7AEF\u5B8C\u5168\u9759\u9ED8\u3001\u7528\u6237\u53EA\u770B\u5230\u300C\u5DF2\u53D1\u8D77\u300D\uFF09\uFF1B\u6210\u529F\u4F1A\u91CD\u542F daemon \u2192 \u8FDE\u63A5\u77ED\u6682\u65AD\u5F00\uFF0C\u7531\u7248\u672C\u53D8\u66F4\u6D41
16840
+ // \u63A5\u7BA1\u663E\u793A\u300C\u5DF2\u662F\u6700\u65B0\u7248\u300D\u3002updateInFlight \u517C\u4F5C\u6309\u94AE in-flight \u95F8\uFF0C\u6321\u4F4F\u91CD\u590D\u70B9\u51FB\u3002
16841
+ function postUpdate(latest) {
16842
+ if (updateInFlight) { toast('\u23F3 \u5347\u7EA7\u5DF2\u5728\u8FDB\u884C\u4E2D\u2026'); return; }
16843
+ updateInFlight = true;
16844
+ var box = $('updateBody'); if (box && lastUpdate) renderUpdate(box, lastUpdate); // \u7ACB\u523B\u628A\u6309\u94AE\u5207\u6210\u7981\u7528\u7684\u300C\u66F4\u65B0\u4E2D\u2026\u300D
16845
+ fetch('/api/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
16846
+ .then(function (r) { return r.json().then(function (j) { return { status: r.status, body: j }; }); })
16847
+ .then(function (resp) {
16848
+ if (resp.status !== 202) {
16849
+ updateInFlight = false;
16850
+ toast((resp.status === 501 ? '\u23F3 ' : '\u274C ') + (resp.body.message || ('HTTP ' + resp.status)));
16851
+ var b = $('updateBody'); if (b) loadUpdate(b);
16852
+ return;
16853
+ }
16854
+ toast('\u2705 ' + (resp.body.message || '\u5347\u7EA7\u5DF2\u53D1\u8D77'));
16855
+ pollUpdateStatus(0, latest);
16856
+ })
16857
+ .catch(function () { updateInFlight = false; toast('\u274C \u8BF7\u6C42\u5931\u8D25'); });
16858
+ }
16859
+ function pollUpdateStatus(tries, latest) {
16860
+ if (tries > 60) { updateInFlight = false; toast('\u23F3 \u5B89\u88C5\u4ECD\u5728\u540E\u53F0\u8FDB\u884C\uFF0C\u7A0D\u540E\u5237\u65B0\u9875\u9762\u67E5\u770B'); return; } // ~90s \u515C\u5E95\uFF1A\u522B\u65E0\u9650\u8F6E\u8BE2
16861
+ fetch('/api/update/status').then(function (r) { return r.json(); })
16862
+ .then(function (s) {
16863
+ if (!s || s.phase === 'installing' || s.phase === 'restarting') {
16864
+ setTimeout(function () { pollUpdateStatus(tries + 1, latest); }, 1500);
16865
+ return;
16866
+ }
16867
+ updateInFlight = false;
16868
+ if (s.phase === 'error') {
16869
+ toast('\u274C \u5347\u7EA7\u5931\u8D25\uFF1A' + (s.message || '\u672A\u77E5\u9519\u8BEF'));
16870
+ var box = $('updateBody'); if (box) loadUpdate(box); // \u6062\u590D\u53EF\u70B9\u7684\u66F4\u65B0\u5361\u4F9B\u91CD\u8BD5
16871
+ } else if (s.phase === 'done') {
16872
+ toast('\u2705 \u5DF2\u66F4\u65B0\u5230 v' + (s.to || latest || '') + '\uFF0C\u6B63\u5728\u91CD\u542F\u2026');
16873
+ lastUpdate = null; updateCheckedAt = 0; // \u4F5C\u5E9F\u7F13\u5B58\uFF1A\u91CD\u542F\u540E\u7248\u672C\u53D8\u66F4\u6D41\u4F1A\u7FFB\u6210\u300C\u5DF2\u662F\u6700\u65B0\u7248\u300D
16874
+ }
16875
+ })
16876
+ .catch(function () {
16877
+ // \u62FF\u4E0D\u5230\u72B6\u6001\uFF1A\u591A\u534A daemon \u6B63\u5728\u91CD\u542F\uFF08\u6210\u529F\u8DEF\u5F84\uFF09\u2192 \u4EA4\u7ED9\u91CD\u8FDE/\u7248\u672C\u53D8\u66F4\u6D41\u6536\u5C3E\u3002
16878
+ updateInFlight = false;
16879
+ });
16880
+ }
16649
16881
  // 202 = \u5DF2\u53D1\u8D77\uFF08detached helper \u63A5\u7BA1\uFF09\uFF1B501 = \u53EA\u8BFB\u9884\u89C8\uFF08\u65E0 daemon\uFF09\u3002
16650
16882
  function postAction(path, label) {
16651
16883
  fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
@@ -17772,6 +18004,10 @@ function createWebServer(opts) {
17772
18004
  sendJson(res, 200, await opts.service.checkUpdate());
17773
18005
  return;
17774
18006
  }
18007
+ if (req.method === "GET" && pathName === "/api/update/status") {
18008
+ sendJson(res, 200, opts.service.updateStatus());
18009
+ return;
18010
+ }
17775
18011
  if (req.method === "POST" && pathName === "/api/update") {
17776
18012
  try {
17777
18013
  await opts.service.applyUpdate();
@@ -18485,7 +18721,7 @@ async function runSupervisor(bots) {
18485
18721
 
18486
18722
  // src/core/single-instance.ts
18487
18723
  init_paths();
18488
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
18724
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
18489
18725
  import { dirname as dirname15 } from "path";
18490
18726
  var CLAIM_ATTEMPTS = 5;
18491
18727
  var PID_REUSE_SLACK_MS = 15e3;
@@ -18536,7 +18772,7 @@ function acquireSingleInstanceLock(appId, file = paths.processesFile) {
18536
18772
  const record = { pid: process.pid, appId, startedAt: Date.now() };
18537
18773
  for (let attempt = 0; attempt < CLAIM_ATTEMPTS; attempt++) {
18538
18774
  try {
18539
- writeFileSync3(file, `${JSON.stringify(record)}
18775
+ writeFileSync4(file, `${JSON.stringify(record)}
18540
18776
  `, { flag: "wx" });
18541
18777
  const release2 = () => {
18542
18778
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.6.7-test.2",
3
+ "version": "0.6.7-test.4",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {