@modelzen/feishu-codex-bridge 0.3.12-test.0 → 0.3.12-test.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -91,7 +91,10 @@ var paths = {
91
91
  inboundDir: join(appDir, "inbound"),
92
92
  /** daemon 内嵌 Web 控制台的发现文件 {port, token, pid}(0600,daemon 退出
93
93
  * 清理)——`web` 子命令据此直接打开 daemon 控制台而不是再起只读副本。 */
94
- webConsoleFile: join(appDir, "web-console.json")
94
+ webConsoleFile: join(appDir, "web-console.json"),
95
+ /** 稳定的 Web 控制台 token(0600,**不随进程退出清理**)——让重启 / 预览→daemon
96
+ * 切换后浏览器里那条带 token 的 URL 始终有效,不再 401。删此文件即轮换 token。 */
97
+ webTokenFile: join(appDir, "web-token")
95
98
  };
96
99
 
97
100
  // src/config/bots.ts
@@ -12167,7 +12170,7 @@ async function catalogEntryStatus(entry, defaultBackend) {
12167
12170
  }
12168
12171
  async function probeAllBackends() {
12169
12172
  return Promise.all(
12170
- backendIds().map(async (id) => {
12173
+ visibleCatalog().map((e) => e.id).map(async (id) => {
12171
12174
  const backend = createBackend(id);
12172
12175
  const probe = await backend.doctor({ force: true }).catch(() => void 0);
12173
12176
  return {
@@ -12195,6 +12198,7 @@ function isAlive(pid) {
12195
12198
  }
12196
12199
 
12197
12200
  // src/web/discovery.ts
12201
+ import { randomUUID as randomUUID6 } from "crypto";
12198
12202
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
12199
12203
  import { dirname as dirname12 } from "path";
12200
12204
  function publishWebConsole(rec, file = paths.webConsoleFile) {
@@ -12225,6 +12229,25 @@ function clearWebConsole(file = paths.webConsoleFile) {
12225
12229
  } catch {
12226
12230
  }
12227
12231
  }
12232
+ function stableWebConsoleToken(file = paths.webTokenFile) {
12233
+ try {
12234
+ const t = readFileSync5(file, "utf8").trim();
12235
+ if (t) return t;
12236
+ } catch {
12237
+ }
12238
+ const token = randomUUID6();
12239
+ try {
12240
+ mkdirSync2(dirname12(file), { recursive: true });
12241
+ try {
12242
+ unlinkSync(file);
12243
+ } catch {
12244
+ }
12245
+ writeFileSync2(file, `${token}
12246
+ `, { mode: 384 });
12247
+ } catch {
12248
+ }
12249
+ return token;
12250
+ }
12228
12251
  function isAlive2(pid) {
12229
12252
  try {
12230
12253
  process.kill(pid, 0);
@@ -12347,7 +12370,7 @@ async function doRestart(phase) {
12347
12370
 
12348
12371
  // src/web/server.ts
12349
12372
  import { createServer } from "http";
12350
- import { randomUUID as randomUUID6, timingSafeEqual } from "crypto";
12373
+ import { randomUUID as randomUUID7, timingSafeEqual } from "crypto";
12351
12374
  import { mkdirSync as mkdirSync3, watch } from "fs";
12352
12375
  import { open as open2, stat as stat5 } from "fs/promises";
12353
12376
  import { join as join19 } from "path";
@@ -14368,7 +14391,7 @@ ${UI_PURE_JS}
14368
14391
  box.textContent = '';
14369
14392
  box.className = 'note';
14370
14393
  if (!diag) return;
14371
- box.appendChild(el('div', null, '\u4E8B\u4EF6\u8BA2\u9605\uFF1A' + eventDiagText(diag.event)));
14394
+ box.appendChild(el('div', null, '\u2139\uFE0F \u4E8B\u4EF6\u8BA2\u9605\uFF1A\u8BF7\u81EA\u884C\u5230\u300C\u4E8B\u4EF6\u4E0E\u56DE\u8C03\u300D\u786E\u8BA4\u5DF2\u8BA2\u9605 im.message.receive_v1\uFF08\u957F\u8FDE\u63A5\uFF09\uFF1B\u6B64\u9879\u7CFB\u7EDF\u65E0\u6CD5\u53EF\u9760\u68C0\u6D4B\uFF0C\u4EC5\u4F5C\u63D0\u9192\u3002'));
14372
14395
  var title = el('div', null, '\u{1F9E0} \u540E\u7AEF\u73AF\u5883\uFF1A');
14373
14396
  title.style.marginTop = '6px';
14374
14397
  box.appendChild(title);
@@ -14553,9 +14576,12 @@ ${UI_PURE_JS}
14553
14576
  var wizEs = null; // \u626B\u7801 SSE EventSource
14554
14577
  var wizQrSessionId = null;
14555
14578
  var wizCountdown = null; // \u4E8C\u7EF4\u7801\u8FC7\u671F\u5012\u8BA1\u65F6
14579
+ var wizAutoEnabled = false; // \u65B0 bot \u662F\u5426\u5DF2\u81EA\u52A8\u52A0\u5165\u6D3B\u8DC3\u96C6\uFF08\u4E00\u6B21\u6027\uFF09
14580
+ var wizRestartPrompted = false; // \u5B8C\u6210\u6B65\u662F\u5426\u5DF2\u5F39\u8FC7\u300C\u91CD\u542F\u62C9\u8D77\u300D\u786E\u8BA4\uFF08\u4E00\u6B21\u6027\uFF09
14556
14581
 
14557
14582
  function openWizard() {
14558
14583
  wizStep = 1; wizBotId = null; wizSetup = null; wizManualOpen = false;
14584
+ wizAutoEnabled = false; wizRestartPrompted = false;
14559
14585
  stopWizPoll(); stopWizQr();
14560
14586
  $('wizMask').classList.add('open');
14561
14587
  renderWizard();
@@ -14773,12 +14799,21 @@ ${UI_PURE_JS}
14773
14799
  function startWizPoll() { stopWizPoll(); pollWizSetup(); wizPoll = setInterval(pollWizSetup, 5000); }
14774
14800
  function pollWizSetup() {
14775
14801
  if (!wizBotId) return;
14802
+ // \u65B0 bot \u9ED8\u8BA4\u5C31\u52A0\u5165\u6D3B\u8DC3\u96C6\u2014\u2014\u7528\u6237\u4E0D\u5FC5\u624B\u52A8\u70B9\u6309\u94AE / \u8DD1 bot use\u3002\u4E00\u6B21\u6027\uFF1B\u5931\u8D25\u4E0B\u5468\u671F\u91CD\u8BD5\u3002
14803
+ // \u771F\u6B63\u300C\u62C9\u8D77\u4E0A\u7EBF\u300D\u8981\u91CD\u542F Feishu Bridge\uFF0C\u90A3\u4E00\u6B65\u7559\u5230\u300C\u5B8C\u6210\u300D\u65F6\u5F39\u7A97\u786E\u8BA4\uFF08\u91CD\u542F\u4F1A\u77ED\u6682
14804
+ // \u5F71\u54CD\u5176\u5B83\u5728\u7EBF bot\uFF0C\u6240\u4EE5\u4E0D\u9759\u9ED8\u91CD\u542F\uFF09\u3002
14805
+ if (!wizAutoEnabled) {
14806
+ wizAutoEnabled = true;
14807
+ fetch('/api/bots/' + encodeURIComponent(wizBotId), {
14808
+ method: 'PATCH', headers: { 'Content-Type': 'application/json' },
14809
+ body: JSON.stringify({ enabled: true }),
14810
+ }).catch(function () { wizAutoEnabled = false; });
14811
+ }
14776
14812
  fetch('/api/bots/' + encodeURIComponent(wizBotId) + '/setup-status')
14777
14813
  .then(function (r) { return r.json(); })
14778
14814
  .then(function (s) {
14779
14815
  wizSetup = s;
14780
14816
  if (wizStep === 2) renderWizChecklist();
14781
- if (s.event && s.event.state === 'ok') stopWizPoll();
14782
14817
  })
14783
14818
  .catch(function () { /* \u4E0B\u4E2A\u5468\u671F\u91CD\u8BD5 */ });
14784
14819
  }
@@ -14805,7 +14840,7 @@ ${UI_PURE_JS}
14805
14840
  var s = wizSetup;
14806
14841
  if (!s || !s.credentials) {
14807
14842
  w.appendChild(checkItem('spin', '\u6B63\u5728\u68C0\u6D4B\u2026', '\u9996\u6B21\u62C9\u53D6\u7EA6 2~3 \u79D2'));
14808
- w.appendChild(wizChecklistActions(false));
14843
+ w.appendChild(wizChecklistActions());
14809
14844
  return;
14810
14845
  }
14811
14846
  w.appendChild(checkItem(
@@ -14833,81 +14868,58 @@ ${UI_PURE_JS}
14833
14868
  } else if (conn.running) {
14834
14869
  w.appendChild(checkItem('spin', 'bridge \u8FD0\u884C\u4E2D', '\u957F\u8FDE\u63A5' + (conn.connection ? '\uFF08' + conn.connection + '\uFF09' : '\u5EFA\u7ACB\u4E2D\u2026')));
14835
14870
  } else {
14836
- var cmd = el('div');
14837
- var act = el('div', 'actions');
14838
- var go1 = el('button', 'btn primary', '\u26A1 \u4E00\u952E\u542F\u7528\u5E76\u62C9\u8D77');
14839
- go1.onclick = function () { wizEnableAndLaunch(); };
14840
- act.appendChild(go1);
14841
- cmd.appendChild(act);
14842
- cmd.appendChild(el('div', 'note', '\u4E00\u952E\uFF1A\u628A\u5B83\u52A0\u5165\u6D3B\u8DC3\u96C6\u5E76\u81EA\u52A8'
14843
- + ((daemon && daemon.running) ? '\u91CD\u542F' : '\u542F\u52A8') + ' Feishu Bridge'
14844
- + ((daemon && daemon.running) ? '\uFF08\u7EA6\u6570\u79D2\uFF0C\u5176\u5B83\u5728\u7EBF\u673A\u5668\u4EBA\u4F1A\u77ED\u6682\u65AD\u8FDE\u540E\u81EA\u52A8\u91CD\u8FDE\uFF09' : '') + '\u3002'));
14845
- cmd.appendChild(el('div', 'note', '\u6216\u624B\u52A8\u5728\u7EC8\u7AEF\u6267\u884C\uFF1A'));
14846
- cmd.appendChild(copyRow('feishu-codex-bridge bot use ' + (wizBotId || '<appId>')));
14847
- cmd.appendChild(copyRow('feishu-codex-bridge start'));
14848
- w.appendChild(checkItem('\u{1F7E0}', '\u957F\u8FDE\u63A5\u672A\u5EFA\u7ACB', '\u9700\u8981 Feishu Bridge \u62C9\u8D77\u8FD9\u4E2A bot', cmd));
14849
- }
14850
- var ev = s.event || { state: 'unchecked' };
14851
- if (ev.state === 'ok') {
14852
- w.appendChild(checkItem('\u2705', '\u4E8B\u4EF6\u8BA2\u9605\u5DF2\u751F\u6548', eventDiagText(ev)));
14853
- } else {
14854
- var evExtra = el('div');
14855
- evExtra.appendChild(el('div', 'note', ev.state === 'unchecked'
14856
- ? '\uFF08\u7F3A application:app_version \u53EA\u8BFB scope \u6216\u7F51\u7EDC\u4E0D\u901A\u65F6\u65E0\u6CD5\u81EA\u52A8\u68C0\u6D4B\uFF1B\u6309\u4E0B\u65B9\u6DF1\u94FE\u624B\u52A8\u6838\u5BF9\u300C\u4E8B\u4EF6\u914D\u7F6E\u300D\u3002\uFF09'
14857
- : '\u53BB\u5F00\u53D1\u8005\u540E\u53F0\u300C\u4E8B\u4EF6\u4E0E\u56DE\u8C03\u300D\uFF1A\u4E8B\u4EF6\u914D\u7F6E\u6539\u300C\u957F\u8FDE\u63A5\u300D\u2192 \u6DFB\u52A0 im.message.receive_v1 \u2192 \u5E94\u7528\u53D1\u5E03\u91CC\u521B\u5EFA\u5E76\u53D1\u5E03\u7248\u672C\u3002'));
14858
- var ea = el('a', null, '\u6253\u5F00\u300C\u4E8B\u4EF6\u4E0E\u56DE\u8C03\u300D\u914D\u7F6E\u9875 \u2197');
14859
- ea.href = s.eventConfigUrl; ea.target = '_blank'; ea.rel = 'noopener';
14860
- evExtra.appendChild(ea);
14861
- evExtra.appendChild(el('div', 'note', '\u914D\u7F6E\u597D\u540E\u65E0\u9700\u624B\u52A8\u5237\u65B0\u2014\u2014\u672C\u9875\u6BCF 5 \u79D2\u81EA\u52A8\u590D\u68C0\uFF0C\u751F\u6548\u4F1A\u53D8 \u2705\u3002'));
14862
- w.appendChild(checkItem(ev.state === 'unchecked' ? '\u26A0\uFE0F' : 'spin',
14863
- ev.state === 'unchecked' ? '\u4E8B\u4EF6\u8BA2\u9605\u672A\u80FD\u81EA\u52A8\u68C0\u6D4B' : '\u4E8B\u4EF6\u8BA2\u9605\u5C1A\u672A\u751F\u6548',
14864
- eventDiagText(ev), evExtra));
14865
- }
14866
- w.appendChild(wizChecklistActions(ev.state === 'ok'));
14871
+ // \u65B0 bot \u5DF2\u81EA\u52A8\u52A0\u5165\u6D3B\u8DC3\u96C6\uFF08\u89C1 pollWizSetup\uFF09\uFF1B\u4E0D\u518D\u8BA9\u7528\u6237\u70B9\u6309\u94AE / \u590D\u5236\u547D\u4EE4\u3002\u771F\u6B63\u62C9\u8D77
14872
+ // \u4E0A\u7EBF\u9760\u6700\u540E\u4E00\u6B65\u300C\u5B8C\u6210\u300D\u65F6\u5F39\u7A97\u786E\u8BA4\u91CD\u542F Feishu Bridge\u3002
14873
+ w.appendChild(checkItem('spin', '\u5F85\u62C9\u8D77\u4E0A\u7EBF', '\u5DF2\u81EA\u52A8\u52A0\u5165\u6D3B\u8DC3\u96C6 \xB7 \u70B9\u300C\u4E0B\u4E00\u6B65\u300D\u5230\u5B8C\u6210\u9875\uFF0C\u786E\u8BA4\u91CD\u542F Feishu Bridge \u5373\u4E0A\u7EBF\u3002'));
14874
+ }
14875
+ // \u4E8B\u4EF6\u8BA2\u9605\uFF1A\u7CFB\u7EDF\u65E0\u6CD5\u53EF\u9760\u68C0\u6D4B\uFF08\u957F\u8FDE\u63A5\u8BA2\u9605\u5728\u5DF2\u53D1\u5E03\u7248\u672C\u91CC\u7684\u4F53\u73B0\u4E0D\u7A33\u5B9A\uFF09\uFF0C\u6545\u53EA\u505A\u63D0\u9192\u3001\u4E0D\u4E0B\u7ED3\u8BBA\u3001
14876
+ // \u4E0D\u963B\u585E\u300C\u4E0B\u4E00\u6B65\u300D\u3002\u7528\u6237\u81EA\u884C\u53BB\u540E\u53F0\u6838\u5BF9\u3002
14877
+ var evHint = el('div');
14878
+ var ea = el('a', null, '\u6253\u5F00\u300C\u4E8B\u4EF6\u4E0E\u56DE\u8C03\u300D\u914D\u7F6E\u9875 \u2197');
14879
+ ea.href = (s.eventConfigUrl || '#'); ea.target = '_blank'; ea.rel = 'noopener';
14880
+ evHint.appendChild(ea);
14881
+ w.appendChild(checkItem('\u2139\uFE0F', '\u4E8B\u4EF6\u8BA2\u9605\uFF08\u8BF7\u81EA\u884C\u786E\u8BA4\uFF09',
14882
+ '\u8BF7\u5230\u300C\u4E8B\u4EF6\u4E0E\u56DE\u8C03\u300D\u786E\u8BA4\u5DF2\u8BA2\u9605 im.message.receive_v1\uFF08\u957F\u8FDE\u63A5\u6A21\u5F0F\uFF09\u5E76\u5DF2\u53D1\u5E03\u7248\u672C\uFF0C\u5426\u5219 @\u673A\u5668\u4EBA\u4E0D\u4F1A\u6709\u53CD\u5E94\u3002\u6B64\u9879\u7CFB\u7EDF\u65E0\u6CD5\u53EF\u9760\u68C0\u6D4B\uFF0C\u4EC5\u4F5C\u63D0\u9192\u3002', evHint));
14883
+ w.appendChild(wizChecklistActions());
14867
14884
  }
14868
14885
 
14869
- function wizChecklistActions(eventOk) {
14886
+ function wizChecklistActions() {
14870
14887
  var actions = el('div', 'actions');
14871
14888
  var back = el('button', 'btn', '\u7A0D\u540E\u518D\u8BF4');
14872
14889
  back.onclick = closeWizard;
14873
14890
  actions.appendChild(back);
14874
14891
  actions.appendChild(el('div', 'grow'));
14875
- var next = el('button', 'btn' + (eventOk ? ' primary' : ''), eventOk ? '\u4E0B\u4E00\u6B65' : '\u4E8B\u4EF6\u751F\u6548\u540E\u518D\u7EE7\u7EED');
14876
- if (eventOk) { next.onclick = function () { wizStep = 3; stopWizPoll(); renderWizard(); }; }
14877
- else { next.className = 'btn disabled'; next.disabled = true; }
14892
+ var next = el('button', 'btn primary', '\u4E0B\u4E00\u6B65');
14893
+ next.onclick = function () { wizStep = 3; stopWizPoll(); renderWizard(); };
14878
14894
  actions.appendChild(next);
14879
14895
  return actions;
14880
14896
  }
14881
14897
 
14882
- // \u300C\u957F\u8FDE\u63A5\u672A\u5EFA\u7ACB\u300D\u4E00\u952E\uFF1A\u542F\u7528\uFF08\u52A0\u5165\u6D3B\u8DC3\u96C6\uFF09\u2192 daemon \u5728\u8DD1\u5C31\u786E\u8BA4\u91CD\u542F\u3001\u6CA1\u8DD1\u5C31\u76F4\u63A5\u542F\u52A8\uFF0C
14883
- // \u7701\u53BB\u7EC8\u7AEF bot use + start\u3002\u590D\u7528 confirmDialog / postAction\uFF0C\u4E0E\u4EEA\u8868\u76D8\u91CD\u542F\u540C\u4E00\u5957\u3002
14884
- function wizEnableAndLaunch() {
14885
- if (!wizBotId) { toast('\u274C \u7F3A\u5C11\u673A\u5668\u4EBA ID'); return; }
14886
- var who = (wizSetup && wizSetup.botName) || wizBotId;
14887
- fetch('/api/bots/' + encodeURIComponent(wizBotId), {
14888
- method: 'PATCH', headers: { 'Content-Type': 'application/json' },
14889
- body: JSON.stringify({ enabled: true }),
14890
- }).then(function (r) { return r.json().then(function (j) { return { status: r.status, body: j }; }); })
14891
- .then(function (resp) {
14892
- if (resp.status !== 200) { toast('\u274C ' + (resp.body.message || ('HTTP ' + resp.status))); return; }
14893
- toast('\u2705 \u5DF2\u52A0\u5165\u6D3B\u8DC3\u96C6');
14894
- if (daemon && daemon.running) {
14895
- confirmDialog({
14896
- title: '\u{1F504} \u7ACB\u5373\u91CD\u542F Feishu Bridge \u62C9\u8D77\u300C' + who + '\u300D\uFF1F',
14897
- lines: [
14898
- '\u5DF2\u52A0\u5165\u6D3B\u8DC3\u96C6\uFF0C\u91CD\u542F\u540E\u5373\u53EF\u4E0A\u7EBF\u3002',
14899
- '\u91CD\u542F\u7EA6\u6570\u79D2\uFF0C\u671F\u95F4\u6240\u6709\u5728\u7EBF\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u4F1A\u77ED\u6682\u65AD\u5F00\u5E76\u81EA\u52A8\u91CD\u8FDE\u3002',
14900
- ],
14901
- confirmLabel: '\u7ACB\u5373\u91CD\u542F',
14902
- onConfirm: function () { postAction('/api/daemon/restart', '\u91CD\u542F'); },
14903
- });
14904
- } else {
14905
- // daemon \u6CA1\u5728\u8DD1\uFF08\u53EA\u8BFB\u9884\u89C8\uFF09\u2192 \u542F\u52A8\u5B83\uFF08read-only preview \u6CE8\u5165\u4E86 startDaemon\uFF09\uFF1B
14906
- // \u8D77\u6765\u540E /api/console/live \u8F6E\u8BE2\u4F1A\u628A\u9875\u9762\u81EA\u52A8\u5E26\u53BB\u53EF\u5199\u63A7\u5236\u53F0\u3002
14907
- postAction('/api/daemon/start', '\u542F\u52A8');
14908
- }
14909
- })
14910
- .catch(function () { toast('\u274C \u8BF7\u6C42\u5931\u8D25'); });
14898
+ // \u5B8C\u6210\u6B65\uFF1A\u65B0 bot \u5DF2\u81EA\u52A8\u52A0\u5165\u6D3B\u8DC3\u96C6\uFF0C\u4F46\u8981\u91CD\u542F Feishu Bridge \u624D\u771F\u6B63\u62C9\u8D77\u4E0A\u7EBF\u3002\u5F39\u7A97\u786E\u8BA4
14899
+ // \uFF08\u91CD\u542F\u4F1A\u77ED\u6682\u5F71\u54CD\u5176\u5B83\u5728\u7EBF bot\uFF0C\u6240\u4EE5\u4E0D\u9759\u9ED8\u91CD\u542F\uFF09\uFF1Bdaemon \u6CA1\u5728\u8DD1\u5C31\u6539\u4E3A\u300C\u542F\u52A8\u300D\u3002\u4E00\u6B21\u6027\u3002
14900
+ function maybePromptRestartAtDone() {
14901
+ if (wizRestartPrompted) return;
14902
+ var conn = (wizSetup && wizSetup.connection) || {};
14903
+ if (conn.running && conn.connection === 'connected') return; // \u5DF2\u7ECF\u5728\u7EBF\uFF0C\u65E0\u9700\u91CD\u542F
14904
+ wizRestartPrompted = true;
14905
+ if (daemon && daemon.running) {
14906
+ confirmDialog({
14907
+ title: '\u{1F504} \u7ACB\u5373\u91CD\u542F Feishu Bridge \u8BA9\u65B0\u673A\u5668\u4EBA\u4E0A\u7EBF\uFF1F',
14908
+ lines: [
14909
+ '\u65B0\u673A\u5668\u4EBA\u5DF2\u52A0\u5165\u6D3B\u8DC3\u96C6\uFF0C\u91CD\u542F Feishu Bridge \u540E\u5373\u53EF\u4E0A\u7EBF\u3002',
14910
+ '\u91CD\u542F\u7EA6\u6570\u79D2\uFF0C\u671F\u95F4\u6240\u6709\u5728\u7EBF\u673A\u5668\u4EBA\u7684\u957F\u8FDE\u63A5\u4F1A\u77ED\u6682\u65AD\u5F00\u5E76\u81EA\u52A8\u91CD\u8FDE\u3002',
14911
+ ],
14912
+ confirmLabel: '\u7ACB\u5373\u91CD\u542F',
14913
+ onConfirm: function () { postAction('/api/daemon/restart', '\u91CD\u542F'); },
14914
+ });
14915
+ } else {
14916
+ confirmDialog({
14917
+ title: '\u{1F680} \u7ACB\u5373\u542F\u52A8 Feishu Bridge \u8BA9\u673A\u5668\u4EBA\u4E0A\u7EBF\uFF1F',
14918
+ lines: ['Feishu Bridge \u5F53\u524D\u672A\u5728\u8FD0\u884C\uFF0C\u542F\u52A8\u540E\u65B0\u673A\u5668\u4EBA\u5373\u53EF\u4E0A\u7EBF\u3002'],
14919
+ confirmLabel: '\u7ACB\u5373\u542F\u52A8',
14920
+ onConfirm: function () { postAction('/api/daemon/start', '\u542F\u52A8'); },
14921
+ });
14922
+ }
14911
14923
  }
14912
14924
 
14913
14925
  function copyRow(text) {
@@ -14930,7 +14942,9 @@ ${UI_PURE_JS}
14930
14942
  w.textContent = '';
14931
14943
  w.appendChild(el('h3', null, '\u{1F389} \u63A5\u5165\u5B8C\u6210'));
14932
14944
  w.appendChild(wizStepBar(3));
14933
- w.appendChild(el('div', 'note', '\u673A\u5668\u4EBA\u300C' + ((wizSetup && wizSetup.botName) || wizBotId || '') + '\u300D\u5DF2\u5C31\u7EEA\uFF0C\u4E8B\u4EF6\u8BA2\u9605\u5DF2\u751F\u6548\u3002'));
14945
+ w.appendChild(el('div', 'note', '\u673A\u5668\u4EBA\u300C' + ((wizSetup && wizSetup.botName) || wizBotId || '') + '\u300D\u5DF2\u52A0\u5165\u6D3B\u8DC3\u96C6\u3002'));
14946
+ // \u8FDB\u5165\u5B8C\u6210\u9875\u5373\u5F39\u7A97\u786E\u8BA4\u91CD\u542F Feishu Bridge \u628A\u65B0 bot \u62C9\u8D77\u4E0A\u7EBF\uFF08\u4E00\u6B21\u6027\uFF1B\u5DF2\u5728\u7EBF\u5219\u4E0D\u5F39\uFF09\u3002
14947
+ maybePromptRestartAtDone();
14934
14948
  var ul = el('div'); ul.style.margin = '12px 0';
14935
14949
  [
14936
14950
  '\u2460 \u5728\u98DE\u4E66\u91CC\u79C1\u804A\u8FD9\u4E2A\u673A\u5668\u4EBA\uFF0C\u70B9\u300C\u2795 \u65B0\u5EFA\u9879\u76EE\u300D\u628A\u4E00\u4E2A\u76EE\u5F55\u7ED1\u6210\u9879\u76EE\u7FA4\uFF1B',
@@ -15037,7 +15051,7 @@ var DEFAULT_WEB_PORT = 51847;
15037
15051
  var COOKIE_NAME = "fcb_console_token";
15038
15052
  var SSE_INITIAL_TAIL_BYTES = 16 * 1024;
15039
15053
  function createWebServer(opts) {
15040
- const token = opts.token ?? randomUUID6();
15054
+ const token = opts.token ?? randomUUID7();
15041
15055
  const html = opts.html ?? UI_HTML;
15042
15056
  const logDir = opts.logDir ?? join19(paths.appDir, "logs");
15043
15057
  const sseCleanups = /* @__PURE__ */ new Set();
@@ -15380,7 +15394,7 @@ function createWebServer(opts) {
15380
15394
  function handleRegisterQrStream(req, res) {
15381
15395
  qrSession?.abort.abort();
15382
15396
  const abort = new AbortController();
15383
- const session = { id: randomUUID6(), abort };
15397
+ const session = { id: randomUUID7(), abort };
15384
15398
  qrSession = session;
15385
15399
  res.writeHead(200, {
15386
15400
  "Content-Type": "text/event-stream",
@@ -15657,23 +15671,27 @@ async function readJsonBody(req) {
15657
15671
  }
15658
15672
 
15659
15673
  // src/web/mount.ts
15674
+ async function listenCanonical(web, attempts = 25, gapMs = 200) {
15675
+ for (let i = 0; i < attempts; i++) {
15676
+ try {
15677
+ return await web.listen(DEFAULT_WEB_PORT);
15678
+ } catch (err) {
15679
+ if (err.code !== "EADDRINUSE") throw err;
15680
+ if (i < attempts - 1) await new Promise((r) => setTimeout(r, gapMs));
15681
+ }
15682
+ }
15683
+ log.warn("web", "console-port-busy-fallback", { preferred: DEFAULT_WEB_PORT });
15684
+ return web.listen(0);
15685
+ }
15660
15686
  async function mountWebConsole(service) {
15661
- const web = createWebServer({ service });
15687
+ const web = createWebServer({ service, token: stableWebConsoleToken() });
15662
15688
  let port;
15663
15689
  let url;
15664
15690
  try {
15665
- ({ port, url } = await web.listen(DEFAULT_WEB_PORT));
15691
+ ({ port, url } = await listenCanonical(web));
15666
15692
  } catch (err) {
15667
- if (err.code !== "EADDRINUSE") {
15668
- log.fail("web", err, { phase: "console-listen" });
15669
- return void 0;
15670
- }
15671
- try {
15672
- ({ port, url } = await web.listen(0));
15673
- } catch (err2) {
15674
- log.fail("web", err2, { phase: "console-listen-fallback" });
15675
- return void 0;
15676
- }
15693
+ log.fail("web", err, { phase: "console-listen" });
15694
+ return void 0;
15677
15695
  }
15678
15696
  publishWebConsole({ port, token: web.token, pid: process.pid, startedAt: Date.now() });
15679
15697
  const exitCleanup = () => clearWebConsole();
@@ -16469,14 +16487,14 @@ async function runWeb(opts = {}) {
16469
16487
  console.log(" \xB7 \u4EC5\u672C\u673A\u53EF\u8BBF\u95EE\uFF08127.0.0.1\uFF09\uFF1BURL \u542B token\uFF0C\u8BF7\u52FF\u5916\u4F20/\u622A\u56FE\u3002");
16470
16488
  return;
16471
16489
  }
16472
- const port = opts.port ?? DEFAULT_WEB_PORT;
16490
+ const port = opts.port ?? 0;
16473
16491
  if (!Number.isInteger(port) || port < 0 || port > 65535) {
16474
16492
  console.error(`\u2717 \u65E0\u6548\u7AEF\u53E3\uFF1A${opts.port}`);
16475
16493
  process.exitCode = 1;
16476
16494
  return;
16477
16495
  }
16478
16496
  const service = createReadonlyAdminService({ startDaemon: () => spawnDaemonControl("start") });
16479
- const web = createWebServer({ service, liveConsole: () => readWebConsole() });
16497
+ const web = createWebServer({ service, token: stableWebConsoleToken(), liveConsole: () => readWebConsole() });
16480
16498
  let url;
16481
16499
  try {
16482
16500
  ({ url } = await web.listen(port));
package/dist/index.d.ts CHANGED
@@ -55,6 +55,9 @@ declare const paths: {
55
55
  /** daemon 内嵌 Web 控制台的发现文件 {port, token, pid}(0600,daemon 退出
56
56
  * 清理)——`web` 子命令据此直接打开 daemon 控制台而不是再起只读副本。 */
57
57
  webConsoleFile: string;
58
+ /** 稳定的 Web 控制台 token(0600,**不随进程退出清理**)——让重启 / 预览→daemon
59
+ * 切换后浏览器里那条带 token 的 URL 始终有效,不再 401。删此文件即轮换 token。 */
60
+ webTokenFile: string;
58
61
  };
59
62
 
60
63
  export { log, newTraceId, paths, withTrace };
package/dist/index.js CHANGED
@@ -61,7 +61,10 @@ var paths = {
61
61
  inboundDir: join(appDir, "inbound"),
62
62
  /** daemon 内嵌 Web 控制台的发现文件 {port, token, pid}(0600,daemon 退出
63
63
  * 清理)——`web` 子命令据此直接打开 daemon 控制台而不是再起只读副本。 */
64
- webConsoleFile: join(appDir, "web-console.json")
64
+ webConsoleFile: join(appDir, "web-console.json"),
65
+ /** 稳定的 Web 控制台 token(0600,**不随进程退出清理**)——让重启 / 预览→daemon
66
+ * 切换后浏览器里那条带 token 的 URL 始终有效,不再 401。删此文件即轮换 token。 */
67
+ webTokenFile: join(appDir, "web-token")
65
68
  };
66
69
 
67
70
  // src/core/logger.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.3.12-test.0",
3
+ "version": "0.3.12-test.1",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {