@modelzen/feishu-codex-bridge 0.6.7-test.1 → 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 +273 -27
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5139,7 +5139,7 @@ function openUrl(url) {
5139
5139
  args = [url];
5140
5140
  }
5141
5141
  try {
5142
- const child = spawn(cmd, args, { stdio: "ignore", detached: true });
5142
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true, windowsHide: true });
5143
5143
  child.on("error", () => {
5144
5144
  });
5145
5145
  child.unref();
@@ -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() {
@@ -8811,16 +8853,16 @@ async function installWinStartup() {
8811
8853
  const reg = spawnSync2(
8812
8854
  "reg",
8813
8855
  ["add", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/t", "REG_SZ", "/d", `wscript.exe "${launcherVbsPath()}"`, "/f"],
8814
- { encoding: "utf8" }
8856
+ { encoding: "utf8", windowsHide: true }
8815
8857
  );
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() {
8823
- spawnSync2("reg", ["delete", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/f"], { stdio: "ignore" });
8865
+ spawnSync2("reg", ["delete", RUN_KEY_PATH, "/v", RUN_KEY_NAME, "/f"], { stdio: "ignore", windowsHide: true });
8824
8866
  killService();
8825
8867
  rmSync(servicePidFile(), { force: true });
8826
8868
  }
@@ -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 { 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 }`;
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");
@@ -8898,15 +8944,24 @@ function powershellPath() {
8898
8944
  function spawnTreeFreeRelauncher(reqPath) {
8899
8945
  const ps = powershellPath();
8900
8946
  if (ps) {
8901
- const errFd = openSync(serviceStderrPath(), "a");
8902
- const child = spawn3(
8947
+ const r = spawnSync2(
8903
8948
  ps,
8904
8949
  ["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-EncodedCommand", encodePowershellCommand(buildRelauncherPowershell(reqPath))],
8905
- { detached: true, windowsHide: true, stdio: ["ignore", errFd, errFd] }
8950
+ { encoding: "utf8", windowsHide: true, timeout: 2e4 }
8906
8951
  );
8907
- child.on("error", (e) => appendServiceErr("relaunch", `powershell spawn error: ${e.message}`));
8908
- child.unref();
8909
- return true;
8952
+ const out = [
8953
+ `exit=${r.status ?? "?"}`,
8954
+ r.error ? `err=${r.error.message}` : "",
8955
+ (r.stdout || "").replace(/\s+/g, " ").trim(),
8956
+ (r.stderr || "").replace(/\s+/g, " ").trim()
8957
+ ].filter(Boolean).join(" ");
8958
+ appendServiceErr("relaunch", `powershell submit: ${out.slice(0, 900)}`);
8959
+ const stdout = r.stdout || "";
8960
+ if (!r.error && r.status === 0 && (/cim rv=0\b/.test(stdout) || /schtasks .*run=0\b/.test(stdout))) {
8961
+ return true;
8962
+ }
8963
+ appendServiceErr("relaunch", "powershell did not confirm a launch; trying node-side schtasks fallback");
8964
+ return spawnSchtasksRelauncher(reqPath);
8910
8965
  }
8911
8966
  appendServiceErr("relaunch", "powershell.exe not found; trying schtasks fallback");
8912
8967
  return spawnSchtasksRelauncher(reqPath);
@@ -8974,7 +9029,12 @@ async function runWinRelaunch(deps = {}) {
8974
9029
  }
8975
9030
  await sleep(300);
8976
9031
  start({ appDir: appDir2, envPath: req.envPath, userProfile: req.userProfile });
8977
- 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})`);
8978
9038
  } finally {
8979
9039
  clearClaim(reqPath);
8980
9040
  if (process.platform === "win32") {
@@ -9021,7 +9081,8 @@ function clearServicePid() {
9021
9081
  }
9022
9082
  function isInstalled() {
9023
9083
  const r = spawnSync2("reg", ["query", RUN_KEY_PATH, "/v", RUN_KEY_NAME], {
9024
- stdio: ["ignore", "ignore", "ignore"]
9084
+ stdio: ["ignore", "ignore", "ignore"],
9085
+ windowsHide: true
9025
9086
  });
9026
9087
  return r.status === 0;
9027
9088
  }
@@ -9287,6 +9348,84 @@ async function installLatest(opts = {}) {
9287
9348
  });
9288
9349
  });
9289
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
+ }
9290
9429
  function daemonRunning() {
9291
9430
  return isServiceRunning();
9292
9431
  }
@@ -9876,7 +10015,7 @@ function buildUsageShareCard(data, opts = {}) {
9876
10015
  // src/web/discovery.ts
9877
10016
  init_paths();
9878
10017
  import { randomUUID as randomUUID4 } from "crypto";
9879
- 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";
9880
10019
  import { dirname as dirname11 } from "path";
9881
10020
  function publishWebConsole(rec, file = paths.webConsoleFile) {
9882
10021
  mkdirSync2(dirname11(file), { recursive: true });
@@ -9884,7 +10023,7 @@ function publishWebConsole(rec, file = paths.webConsoleFile) {
9884
10023
  unlinkSync(file);
9885
10024
  } catch {
9886
10025
  }
9887
- writeFileSync2(file, `${JSON.stringify(rec, null, 2)}
10026
+ writeFileSync3(file, `${JSON.stringify(rec, null, 2)}
9888
10027
  `, { mode: 384 });
9889
10028
  }
9890
10029
  function readWebConsole(file = paths.webConsoleFile) {
@@ -9924,7 +10063,7 @@ function stableWebConsoleToken(file = paths.webTokenFile) {
9924
10063
  unlinkSync(file);
9925
10064
  } catch {
9926
10065
  }
9927
- writeFileSync2(file, `${token}
10066
+ writeFileSync3(file, `${token}
9928
10067
  `, { mode: 384 });
9929
10068
  } catch {
9930
10069
  }
@@ -12629,6 +12768,24 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
12629
12768
  await sendManagedCard(channel, evt.chatId, buildReconnectCard(conn), evt.messageId).catch(
12630
12769
  (err) => log.fail("console", err, { cmd: "reconnect" })
12631
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
+ })();
12632
12789
  }).on(DM.update, ({ evt }) => {
12633
12790
  if (!dmAdmin(evt.operator?.openId)) return;
12634
12791
  void (async () => {
@@ -12651,10 +12808,24 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
12651
12808
  void (async () => {
12652
12809
  await new Promise((r) => setTimeout(r, CARD_SETTLE_MS));
12653
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
+ }
12654
12820
  await updateManagedCard(channel, evt.messageId, buildUpdateCard({ phase: "updating", from })).catch(
12655
12821
  () => void 0
12656
12822
  );
12657
- const res = await installLatest();
12823
+ let res;
12824
+ try {
12825
+ res = await installLatest();
12826
+ } finally {
12827
+ release2();
12828
+ }
12658
12829
  if (!res.ok) {
12659
12830
  log.info("console", "update-failed", { from });
12660
12831
  await updateManagedCard(
@@ -13959,6 +14130,9 @@ ${facts}`;
13959
14130
  case DM.reconnect:
13960
14131
  await sendDm(buildReconnectCard(channel.getConnectionStatus?.()?.state ?? "unknown"));
13961
14132
  break;
14133
+ case DM.restart:
14134
+ await sendDm(buildRestartConfirmCard(channel.getConnectionStatus?.()?.state ?? "unknown"));
14135
+ break;
13962
14136
  case DM.usage: {
13963
14137
  const { messageId } = await sendDm(buildUsageCard({ phase: "loading" }));
13964
14138
  let state;
@@ -14673,8 +14847,12 @@ function createAdminService(deps = {}) {
14673
14847
  dev: false
14674
14848
  }));
14675
14849
  },
14850
+ updateStatus() {
14851
+ return readUpdateStatus();
14852
+ },
14676
14853
  async applyUpdate() {
14677
14854
  if (!deps.applyUpdate) throw new NotWiredYetError("\u2B06\uFE0F \u5347\u7EA7");
14855
+ clearUpdateStatus();
14678
14856
  deps.applyUpdate();
14679
14857
  },
14680
14858
  async hostDoctor() {
@@ -14902,14 +15080,31 @@ async function doUpdate() {
14902
15080
  log.info("daemon-control", "update-skipped-dev", {});
14903
15081
  return;
14904
15082
  }
14905
- 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
+ }
14906
15097
  log.info("daemon-control", "update-installed", { ok: res.ok });
14907
15098
  appendServiceErr("daemon-control", `update-installed ok=${res.ok}`);
14908
15099
  if (!res.ok) {
14909
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() });
14910
15102
  return;
14911
15103
  }
15104
+ const to = currentVersion();
15105
+ writeUpdateStatus({ phase: "restarting", ok: true, from, to, at: Date.now() });
14912
15106
  await doRestart("update");
15107
+ writeUpdateStatus({ phase: "done", ok: true, from, to, at: Date.now() });
14913
15108
  }
14914
15109
  async function doRestart(phase) {
14915
15110
  if (!isServiceRunning()) {
@@ -15671,6 +15866,7 @@ ${UI_PURE_JS}
15671
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
15672
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
15673
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
15674
15870
  var drawerProject = null; // \u62BD\u5C49\u91CC\u6253\u5F00\u7684\u9879\u76EE\u540D
15675
15871
  var diagBotId = null; // diag \u5C5E\u4E8E\u54EA\u4E2A bot\uFF08\u9632\u4E32\u53F0\uFF09
15676
15872
  var bkDetailId = null; // \u540E\u7AEF Agent \u8BE6\u60C5\u89C6\u56FE\u5F53\u524D\u5C55\u5F00\u7684\u540E\u7AEF id\uFF08null=\u5217\u8868\uFF09
@@ -16580,10 +16776,13 @@ ${UI_PURE_JS}
16580
16776
  if (u.hasUpdate && u.latest) {
16581
16777
  box.appendChild(el('div', null, '\u{1F195} \u6709\u65B0\u7248 v' + u.latest + '\uFF08\u5F53\u524D v' + u.current + '\uFF09'));
16582
16778
  var row = el('div', 'statline');
16583
- var up = el('button', 'btn primary', '\u2B06\uFE0F \u66F4\u65B0\u5E76\u91CD\u542F \xB7 v' + u.latest);
16584
- 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); }; }
16585
16784
  row.appendChild(up);
16586
- 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'));
16587
16786
  box.appendChild(row);
16588
16787
  } else {
16589
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')));
@@ -16633,9 +16832,52 @@ ${UI_PURE_JS}
16633
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',
16634
16833
  ],
16635
16834
  confirmLabel: '\u786E\u8BA4\u66F4\u65B0',
16636
- onConfirm: function () { postAction('/api/update', '\u66F4\u65B0'); },
16835
+ onConfirm: function () { postUpdate(latest); },
16637
16836
  });
16638
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
+ }
16639
16881
  // 202 = \u5DF2\u53D1\u8D77\uFF08detached helper \u63A5\u7BA1\uFF09\uFF1B501 = \u53EA\u8BFB\u9884\u89C8\uFF08\u65E0 daemon\uFF09\u3002
16640
16882
  function postAction(path, label) {
16641
16883
  fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' })
@@ -17762,6 +18004,10 @@ function createWebServer(opts) {
17762
18004
  sendJson(res, 200, await opts.service.checkUpdate());
17763
18005
  return;
17764
18006
  }
18007
+ if (req.method === "GET" && pathName === "/api/update/status") {
18008
+ sendJson(res, 200, opts.service.updateStatus());
18009
+ return;
18010
+ }
17765
18011
  if (req.method === "POST" && pathName === "/api/update") {
17766
18012
  try {
17767
18013
  await opts.service.applyUpdate();
@@ -18475,7 +18721,7 @@ async function runSupervisor(bots) {
18475
18721
 
18476
18722
  // src/core/single-instance.ts
18477
18723
  init_paths();
18478
- 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";
18479
18725
  import { dirname as dirname15 } from "path";
18480
18726
  var CLAIM_ATTEMPTS = 5;
18481
18727
  var PID_REUSE_SLACK_MS = 15e3;
@@ -18526,7 +18772,7 @@ function acquireSingleInstanceLock(appId, file = paths.processesFile) {
18526
18772
  const record = { pid: process.pid, appId, startedAt: Date.now() };
18527
18773
  for (let attempt = 0; attempt < CLAIM_ATTEMPTS; attempt++) {
18528
18774
  try {
18529
- writeFileSync3(file, `${JSON.stringify(record)}
18775
+ writeFileSync4(file, `${JSON.stringify(record)}
18530
18776
  `, { flag: "wx" });
18531
18777
  const release2 = () => {
18532
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.1",
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": {