@modelzen/feishu-codex-bridge 0.4.1 → 0.4.3

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 +378 -161
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -127,6 +127,13 @@ function secretKeyForApp(appId) {
127
127
  function getShowToolCalls(cfg) {
128
128
  return cfg.preferences?.showToolCalls !== false;
129
129
  }
130
+ function getModelDisplay(cfg) {
131
+ const v = cfg.preferences?.showModel;
132
+ if (v === "running" || v === "always" || v === "off") return v;
133
+ if (v === true) return "always";
134
+ if (v === false) return "off";
135
+ return "running";
136
+ }
130
137
  function getMaxConcurrentRuns(cfg) {
131
138
  const raw = cfg.preferences?.maxConcurrentRuns;
132
139
  if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 1) return 10;
@@ -2950,7 +2957,14 @@ function card(elements, opts = {}) {
2950
2957
  obj.header = {
2951
2958
  template: opts.header.template ?? "blue",
2952
2959
  title: { tag: "plain_text", content: opts.header.title },
2953
- ...opts.header.subtitle ? { subtitle: { tag: "plain_text", content: opts.header.subtitle } } : {}
2960
+ ...opts.header.subtitle ? { subtitle: { tag: "plain_text", content: opts.header.subtitle } } : {},
2961
+ ...opts.header.textTags?.length ? {
2962
+ text_tag_list: opts.header.textTags.map((t) => ({
2963
+ tag: "text_tag",
2964
+ text: { tag: "plain_text", content: t.text },
2965
+ color: t.color
2966
+ }))
2967
+ } : {}
2954
2968
  };
2955
2969
  }
2956
2970
  return obj;
@@ -3025,6 +3039,27 @@ function actions(items, elementId) {
3025
3039
  columns: items.map((it) => ({ tag: "column", width: "auto", elements: [it] }))
3026
3040
  };
3027
3041
  }
3042
+ function actionsFixed(items, width, elementId) {
3043
+ return {
3044
+ tag: "column_set",
3045
+ ...elementId ? { element_id: elementId } : {},
3046
+ flex_mode: "flow",
3047
+ horizontal_spacing: "8px",
3048
+ columns: items.map((it) => ({ tag: "column", width: "auto", elements: [{ ...it, width, size: "large" }] }))
3049
+ };
3050
+ }
3051
+ function splitRow(left, right, elementId) {
3052
+ return {
3053
+ tag: "column_set",
3054
+ ...elementId ? { element_id: elementId } : {},
3055
+ flex_mode: "none",
3056
+ horizontal_spacing: "medium",
3057
+ columns: [
3058
+ { tag: "column", width: "auto", vertical_align: "center", elements: [left] },
3059
+ { tag: "column", width: "weighted", weight: 1, vertical_align: "center", elements: [right] }
3060
+ ]
3061
+ };
3062
+ }
3028
3063
  function button(label, value, type = "default") {
3029
3064
  return {
3030
3065
  tag: "button",
@@ -3033,11 +3068,12 @@ function button(label, value, type = "default") {
3033
3068
  behaviors: [{ type: "callback", value }]
3034
3069
  };
3035
3070
  }
3036
- function linkButton(label, url, type = "default") {
3071
+ function linkButton(label, url, type = "default", size) {
3037
3072
  return {
3038
3073
  tag: "button",
3039
3074
  text: { tag: "plain_text", content: label },
3040
3075
  type,
3076
+ ...size ? { size } : {},
3041
3077
  behaviors: [{ type: "open_url", default_url: url }]
3042
3078
  };
3043
3079
  }
@@ -3295,6 +3331,8 @@ function openChatUrl(chatId) {
3295
3331
  return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
3296
3332
  }
3297
3333
  var REPO = "https://github.com/modelzen/feishu-codex-bridge";
3334
+ var MENU_BTN_W_TOP = "152px";
3335
+ var MENU_BTN_W_BOT = "112px";
3298
3336
  var DM = {
3299
3337
  menu: "dm.menu",
3300
3338
  newProject: "dm.newProject",
@@ -3316,6 +3354,7 @@ var DM = {
3316
3354
  rmDo: "dm.rmDo",
3317
3355
  rmCancel: "dm.rmCancel",
3318
3356
  setTools: "dm.set.tools",
3357
+ setShowModel: "dm.set.showModel",
3319
3358
  setWatchdog: "dm.set.watchdog",
3320
3359
  // 假死超时「自定义…」:watchdogCustom 打开输入卡,watchdogCustomSubmit 保存任意秒数
3321
3360
  watchdogCustom: "dm.set.watchdog.custom",
@@ -3351,24 +3390,47 @@ var GS = {
3351
3390
  function kindLabel(kind) {
3352
3391
  return kind === "single" ? "\u{1F4AC} \u5355\u4F1A\u8BDD\u7FA4" : "\u{1F465} \u591A\u8BDD\u9898\u7FA4";
3353
3392
  }
3354
- function buildDmMenuCard() {
3393
+ function buildDmMenuCard(opts = {}) {
3394
+ const { webConsoleUrl: webConsoleUrl2, version } = opts;
3355
3395
  return card(
3356
3396
  [
3357
3397
  md("\u79C1\u804A\u7528\u4E8E**\u5EFA\u9879\u76EE\u548C\u7BA1\u7406**\uFF1B\u5177\u4F53\u4EFB\u52A1\u8BF7\u5230\u9879\u76EE\u7FA4\u91CC @\u6211\u3002"),
3358
3398
  hr(),
3359
- actions([
3399
+ // 两行按钮固定宽度、左对齐;两行宽度配平后总长相等、右边缘对齐(见 MENU_BTN_W_*)。
3400
+ actionsFixed([
3360
3401
  button("\u2795 \u65B0\u5EFA\u9879\u76EE", { a: DM.newProject }, "primary"),
3361
3402
  button("\u{1F4C1} \u9879\u76EE\u5217\u8868", { a: DM.projects }),
3362
3403
  button("\u2699\uFE0F \u8BBE\u7F6E", { a: DM.settings })
3363
- ]),
3364
- actions([
3404
+ ], MENU_BTN_W_TOP),
3405
+ actionsFixed([
3365
3406
  button("\u{1F4CA} \u7528\u91CF", { a: DM.usage }),
3366
3407
  button("\u{1FA7A} \u8BCA\u65AD", { a: DM.doctor }),
3367
3408
  button("\u{1F504} \u91CD\u8FDE", { a: DM.reconnect }),
3368
- button("\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", { a: DM.update })
3369
- ])
3409
+ button("\u2B06\uFE0F \u66F4\u65B0", { a: DM.update })
3410
+ ], MENU_BTN_W_BOT),
3411
+ // 🌐 网页控制台:刻意低调——小号按钮靠左 + 右侧灰字说明;仅在 daemon 本机控制台
3412
+ // 在跑(webConsoleUrl 有值)时出现。按钮直接开 127.0.0.1 控制台——只有运行 bridge
3413
+ // 的这台机器打得开;其它电脑/手机点了会打不开(本机回环地址,飞书也分不出点击设备
3414
+ // 是不是本机),右侧文字已说明,按用户决定不另做处理。
3415
+ ...webConsoleUrl2 ? [
3416
+ hr(),
3417
+ splitRow(
3418
+ linkButton("\u{1F310} \u7F51\u9875\u63A7\u5236\u53F0", webConsoleUrl2, "default", "small"),
3419
+ note("\u4EC5\u5728**\u8FD0\u884C bridge \u7684\u8FD9\u53F0\u7535\u8111**\u4E0A\u80FD\u6253\u5F00\uFF08\u672C\u673A\u5730\u5740\uFF09\u3002")
3420
+ )
3421
+ ] : []
3370
3422
  ],
3371
- { header: { title: "\u{1F916} Codex Bridge \u7BA1\u7406\u53F0", template: "blue" } }
3423
+ {
3424
+ header: {
3425
+ title: "\u{1F916} Codex Bridge \u7BA1\u7406\u53F0",
3426
+ template: "blue",
3427
+ textTags: version ? [{ text: `v${version}`, color: "green" }] : void 0
3428
+ },
3429
+ // 这张卡可能内嵌带 token 的本机控制台直达链接(pc_url),且按钮全是管理员专属的
3430
+ // dm.* 回调——转发出去既无意义、又会把 token 链接一并带走。loopback 已使转发副本
3431
+ // 在他人设备上不可用,这里再加一道:禁止转发。
3432
+ forward: false
3433
+ }
3372
3434
  );
3373
3435
  }
3374
3436
  var backToMenu = () => actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]);
@@ -3449,6 +3511,17 @@ function buildUpdateCard(state) {
3449
3511
  );
3450
3512
  }
3451
3513
  }
3514
+ function buildReconnectCard(conn) {
3515
+ const template = conn === "connected" ? "green" : "orange";
3516
+ return card(
3517
+ [
3518
+ md(`\u957F\u8FDE\u63A5\u72B6\u6001\uFF1A**${conn}**`),
3519
+ note("SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u91CD\u8DD1 `feishu-codex-bridge run`\uFF08\u524D\u53F0\uFF09\u6216 `feishu-codex-bridge restart`\uFF08\u540E\u53F0\u5B88\u62A4\uFF09\u3002"),
3520
+ backToMenu()
3521
+ ],
3522
+ { header: { title: "\u{1F504} \u957F\u8FDE\u63A5", template } }
3523
+ );
3524
+ }
3452
3525
  function connLabel(state) {
3453
3526
  switch (state) {
3454
3527
  case "connected":
@@ -3755,38 +3828,78 @@ function optionRow(label, actionId, current, opts) {
3755
3828
  actions(opts.map((o) => button(o.label, { a: actionId, v: o.value }, o.value === current ? "primary" : "default")))
3756
3829
  ];
3757
3830
  }
3831
+ function settingSection(title) {
3832
+ return { tag: "markdown", content: `**${title}**`, text_size: "notation", text_color: "grey" };
3833
+ }
3834
+ function settingItem(name, desc, actionId, current, opts) {
3835
+ return [
3836
+ md(`**${name}**`),
3837
+ note(desc),
3838
+ actions(opts.map((o) => button(o.label, { a: actionId, v: o.value }, o.value === current ? "primary" : "default")))
3839
+ ];
3840
+ }
3758
3841
  function buildSettingsCard(cfg) {
3759
3842
  const watchdogSec = cfg.preferences?.runIdleTimeoutSeconds ?? 120;
3760
3843
  return card(
3761
3844
  [
3762
- md("**\u5168\u5C40\u8BBE\u7F6E**\uFF08\u7BA1\u7406\u5458\uFF09"),
3763
- ...optionRow("\u{1F527} \u5DE5\u5177\u8C03\u7528", DM.setTools, getShowToolCalls(cfg) ? "on" : "off", [
3764
- { label: "\u663E\u793A", value: "on" },
3765
- { label: "\u9690\u85CF", value: "off" }
3766
- ]),
3767
- md(`\u23F1 \u5047\u6B7B\u8D85\u65F6\uFF08\u5F53\u524D **${watchdogSec === 0 ? "\u5173\u95ED" : `${watchdogSec} \u79D2`}**\uFF09`),
3845
+ settingSection("\u{1F4E4} \u8F93\u51FA\u5C55\u793A"),
3846
+ ...settingItem(
3847
+ "\u{1F527} \u5DE5\u5177\u8C03\u7528",
3848
+ "\u8F93\u51FA\u65F6\u663E\u793A\u6267\u884C\u7684\u547D\u4EE4 / \u5DE5\u5177\u8C03\u7528\uFF1B\u5173\u6389\u53EA\u770B\u6700\u7EC8\u56DE\u7B54\u3002",
3849
+ DM.setTools,
3850
+ getShowToolCalls(cfg) ? "on" : "off",
3851
+ [
3852
+ { label: "\u663E\u793A", value: "on" },
3853
+ { label: "\u9690\u85CF", value: "off" }
3854
+ ]
3855
+ ),
3856
+ ...settingItem(
3857
+ "\u{1F9E0} \u6A21\u578B\u663E\u793A",
3858
+ "\u6BCF\u6761\u56DE\u590D\u53F3\u4E0B\u89D2\u663E\u793A\u300C\u6A21\u578B \xB7 \u63A8\u7406\u5F3A\u5EA6\u300D\u3002\u4EC5\u8F93\u51FA\u65F6\uFF1D\u53EA\u5728\u751F\u6210\u4E2D\u663E\u793A\uFF1B\u59CB\u7EC8\uFF1D\u751F\u6210\u5B8C\u540E\u5361\u7247\u4E5F\u4FDD\u7559\u3002",
3859
+ DM.setShowModel,
3860
+ getModelDisplay(cfg),
3861
+ [
3862
+ { label: "\u5173\u95ED", value: "off" },
3863
+ { label: "\u4EC5\u8F93\u51FA\u65F6", value: "running" },
3864
+ { label: "\u59CB\u7EC8", value: "always" }
3865
+ ]
3866
+ ),
3867
+ hr(),
3868
+ settingSection("\u23F1 \u8FD0\u884C\u63A7\u5236"),
3869
+ md(`**\u23F1 \u5047\u6B7B\u8D85\u65F6** \xB7 \u5F53\u524D **${watchdogSec === 0 ? "\u5173\u95ED" : `${watchdogSec} \u79D2`}**`),
3870
+ note("\u591A\u4E45\u6CA1\u6709\u4EFB\u4F55\u8F93\u51FA\u5C31\u81EA\u52A8\u7EC8\u6B62\u672C\u8F6E\uFF08\u9632\u5361\u6B7B\uFF09\u3002"),
3768
3871
  actions([
3769
3872
  ...[0, 120, 300].map(
3770
3873
  (v) => button(v === 0 ? "\u5173\u95ED" : `${v}\u79D2`, { a: DM.setWatchdog, v: String(v) }, v === watchdogSec ? "primary" : "default")
3771
3874
  ),
3772
3875
  button("\u81EA\u5B9A\u4E49\u2026", { a: DM.watchdogCustom })
3773
3876
  ]),
3774
- ...optionRow("\u{1F4E5} \u8FD0\u884C\u4E2D\u65B0\u6D88\u606F", DM.setPending, getPendingPolicy(cfg), [
3775
- { label: "\u5F15\u5BFC", value: "steer" },
3776
- { label: "\u6392\u961F", value: "queue" }
3777
- ]),
3778
- ...optionRow("\u26A1 \u5E76\u53D1\u4E0A\u9650", DM.setConcurrency, String(getMaxConcurrentRuns(cfg)), [
3779
- { label: "1", value: "1" },
3780
- { label: "5", value: "5" },
3781
- { label: "10", value: "10" },
3782
- { label: "20", value: "20" }
3783
- ]),
3784
- note("\u26A1 \u5E76\u53D1\u6C60\u4E3A**\u6240\u6709\u7FA4/\u8BDD\u9898\u5168\u5C40\u5171\u4EAB**\uFF1A\u6EE1\u4E86\u65B0\u4EFB\u52A1\u6309\u5148\u6765\u540E\u5230\u6392\u961F\uFF08\u6392\u961F\u5361\u53EF\u89C1\u3001\u53EF \u23F9 \u53D6\u6D88\uFF09\u3002"),
3785
- note("\u26A0\uFE0F \u5E76\u53D1\u4E0A\u9650 \u6539\u540E\u9700**\u91CD\u542F**\u751F\u6548\uFF1B\u5176\u4F59\u8BBE\u7F6E\uFF08\u542B\u5047\u6B7B\u8D85\u65F6\uFF09\u5373\u65F6\u751F\u6548\uFF0C\u6240\u6709\u7FA4\u7ACB\u5373\u5957\u7528\u3002"),
3877
+ ...settingItem(
3878
+ "\u{1F4E5} \u8FD0\u884C\u4E2D\u6765\u65B0\u6D88\u606F",
3879
+ "\u6B63\u5728\u8DD1\u65F6\u4F60\u53C8\u53D1\u6D88\u606F\uFF1A\u5F15\u5BFC\uFF1D\u63D2\u8FDB\u5F53\u524D\u8F6E\u7EA0\u504F\uFF1B\u6392\u961F\uFF1D\u7B49\u8FD9\u8F6E\u8DD1\u5B8C\u518D\u5904\u7406\u3002",
3880
+ DM.setPending,
3881
+ getPendingPolicy(cfg),
3882
+ [
3883
+ { label: "\u5F15\u5BFC", value: "steer" },
3884
+ { label: "\u6392\u961F", value: "queue" }
3885
+ ]
3886
+ ),
3887
+ ...settingItem(
3888
+ "\u26A1 \u5E76\u53D1\u4E0A\u9650",
3889
+ "\u6240\u6709\u7FA4 / \u8BDD\u9898\u5168\u5C40\u540C\u65F6\u6700\u591A\u8DD1\u51E0\u4E2A\uFF0C\u6EE1\u4E86\u6392\u961F\uFF08\u6392\u961F\u5361\u53EF \u23F9 \u53D6\u6D88\uFF09\u3002\u6539\u540E\u9700\u91CD\u542F\u751F\u6548\u3002",
3890
+ DM.setConcurrency,
3891
+ String(getMaxConcurrentRuns(cfg)),
3892
+ [
3893
+ { label: "1", value: "1" },
3894
+ { label: "5", value: "5" },
3895
+ { label: "10", value: "10" },
3896
+ { label: "20", value: "20" }
3897
+ ]
3898
+ ),
3786
3899
  hr(),
3787
3900
  actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
3788
3901
  ],
3789
- { header: { title: "\u2699\uFE0F \u8BBE\u7F6E", template: "blue" } }
3902
+ { header: { title: "\u2699\uFE0F \u5168\u5C40\u8BBE\u7F6E", template: "blue" } }
3790
3903
  );
3791
3904
  }
3792
3905
  function buildWatchdogCustomCard(cfg) {
@@ -4850,24 +4963,6 @@ function buildRunCard(rc) {
4850
4963
  }
4851
4964
  function renderRunning(state, rc) {
4852
4965
  const elements = [];
4853
- if (rc.cardKey && rc.goalControls) {
4854
- if (rc.goalEnding) {
4855
- elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
4856
- elements.push(noteMd("_\u{1F3AF} \u76EE\u6807\u5DF2\u89E3\u9664\uFF0C\u672C\u8F6E\u8F93\u51FA\u5B8C\u6210\u540E\u505C\u6B62_"));
4857
- } else {
4858
- elements.push(
4859
- actions(
4860
- [
4861
- button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger"),
4862
- button("\u{1F3AF} \u7ED3\u675F\u76EE\u6807", { a: RC.endGoal, m: rc.cardKey }, "default")
4863
- ],
4864
- CONTROLS_EID
4865
- )
4866
- );
4867
- }
4868
- } else if (rc.cardKey && !rc.hideStop) {
4869
- elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
4870
- }
4871
4966
  const reasoning = reasoningContent(state);
4872
4967
  if (reasoning) elements.push(reasoningPanel(reasoning, state.reasoningActive));
4873
4968
  const showTools = rc.showTools !== false;
@@ -4883,9 +4978,30 @@ function renderRunning(state, rc) {
4883
4978
  if (tools.length > 0) elements.push(...renderToolGroup(tools, false));
4884
4979
  const answer = textParts.join("\n\n");
4885
4980
  if (answer) elements.push(mdStream(answer, ANSWER_EID));
4886
- if (state.footer) elements.push(footerStatus(state.footer));
4981
+ const mEl = modelEl(rc);
4982
+ if (state.footer && mEl) elements.push(splitRow(footerStatus(state.footer), mEl));
4983
+ else if (state.footer) elements.push(footerStatus(state.footer));
4984
+ else if (mEl) elements.push(mEl);
4887
4985
  const gauge = gaugeEl(state);
4888
4986
  if (gauge) elements.push(gauge);
4987
+ if (rc.cardKey && rc.goalControls) {
4988
+ if (rc.goalEnding) {
4989
+ elements.push(noteMd("_\u{1F3AF} \u76EE\u6807\u5DF2\u89E3\u9664\uFF0C\u672C\u8F6E\u8F93\u51FA\u5B8C\u6210\u540E\u505C\u6B62_"));
4990
+ elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
4991
+ } else {
4992
+ elements.push(
4993
+ actions(
4994
+ [
4995
+ button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger"),
4996
+ button("\u{1F3AF} \u7ED3\u675F\u76EE\u6807", { a: RC.endGoal, m: rc.cardKey }, "default")
4997
+ ],
4998
+ CONTROLS_EID
4999
+ )
5000
+ );
5001
+ }
5002
+ } else if (rc.cardKey && !rc.hideStop) {
5003
+ elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
5004
+ }
4889
5005
  return elements;
4890
5006
  }
4891
5007
  function renderTerminal(state, rc) {
@@ -4923,6 +5039,8 @@ function renderTerminal(state, rc) {
4923
5039
  }
4924
5040
  const gauge = gaugeEl(state);
4925
5041
  if (gauge) elements.push(gauge);
5042
+ const mEl = rc.modelOnTerminal ? modelEl(rc) : null;
5043
+ if (mEl) elements.push(mEl);
4926
5044
  return elements;
4927
5045
  }
4928
5046
  function errorAdvice(msg) {
@@ -5044,9 +5162,35 @@ function collapsedToolSummary(tools, finalized) {
5044
5162
  body: tools.map((t) => `- ${toolHeaderText(t)}`).join("\n")
5045
5163
  });
5046
5164
  }
5165
+ function footerStatusText(status) {
5166
+ return status === "thinking" ? "\u{1F9E0} \u6B63\u5728\u601D\u8003" : status === "tool_running" ? "\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177" : status === "retrying" ? "\u26A0\uFE0F \u77AC\u65AD\uFF0C\u81EA\u52A8\u91CD\u8BD5\u4E2D\u2026" : "\u270D\uFE0F \u6B63\u5728\u8F93\u51FA";
5167
+ }
5047
5168
  function footerStatus(status) {
5048
- const text = status === "thinking" ? "\u{1F9E0} \u6B63\u5728\u601D\u8003" : status === "tool_running" ? "\u{1F9F0} \u6B63\u5728\u8C03\u7528\u5DE5\u5177" : status === "retrying" ? "\u26A0\uFE0F \u77AC\u65AD\uFF0C\u81EA\u52A8\u91CD\u8BD5\u4E2D\u2026" : "\u270D\uFE0F \u6B63\u5728\u8F93\u51FA";
5049
- return noteMd(text);
5169
+ return noteMd(footerStatusText(status));
5170
+ }
5171
+ var EFFORT_TIER = {
5172
+ none: { label: "\u65E0", color: "grey" },
5173
+ minimal: { label: "\u6781\u7B80", color: "grey" },
5174
+ low: { label: "\u4F4E", color: "yellow" },
5175
+ medium: { label: "\u4E2D", color: "green" },
5176
+ high: { label: "\u9AD8", color: "violet" },
5177
+ xhigh: { label: "\u6781\u9AD8", color: "purple" }
5178
+ };
5179
+ function modelEffortMd(model, effort) {
5180
+ if (!effort) return model;
5181
+ const t = EFFORT_TIER[effort];
5182
+ if (!t) return `${model} \xB7 ${effort}`;
5183
+ return `${model} \xB7 <font color='${t.color}'>${t.label}</font>`;
5184
+ }
5185
+ function modelEl(rc) {
5186
+ if (!rc.model) return null;
5187
+ return {
5188
+ tag: "markdown",
5189
+ content: modelEffortMd(rc.model, rc.effort),
5190
+ text_size: "notation",
5191
+ text_color: "grey",
5192
+ text_align: "right"
5193
+ };
5050
5194
  }
5051
5195
  function summaryText(state) {
5052
5196
  if (state.terminal === "interrupted") return "\u5DF2\u4E2D\u65AD";
@@ -6751,6 +6895,71 @@ function buildUsageShareCard(data, opts = {}) {
6751
6895
  });
6752
6896
  }
6753
6897
 
6898
+ // src/web/discovery.ts
6899
+ import { randomUUID as randomUUID3 } from "crypto";
6900
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
6901
+ import { dirname as dirname10 } from "path";
6902
+ function publishWebConsole(rec, file = paths.webConsoleFile) {
6903
+ mkdirSync2(dirname10(file), { recursive: true });
6904
+ try {
6905
+ unlinkSync(file);
6906
+ } catch {
6907
+ }
6908
+ writeFileSync2(file, `${JSON.stringify(rec, null, 2)}
6909
+ `, { mode: 384 });
6910
+ }
6911
+ function readWebConsole(file = paths.webConsoleFile) {
6912
+ try {
6913
+ const rec = JSON.parse(readFileSync5(file, "utf8"));
6914
+ if (typeof rec.port !== "number" || typeof rec.token !== "string" || rec.token === "" || typeof rec.pid !== "number") {
6915
+ return void 0;
6916
+ }
6917
+ if (!isAlive(rec.pid)) return void 0;
6918
+ return rec;
6919
+ } catch {
6920
+ return void 0;
6921
+ }
6922
+ }
6923
+ function webConsoleUrl(file = paths.webConsoleFile) {
6924
+ const rec = readWebConsole(file);
6925
+ if (!rec) return void 0;
6926
+ return `http://127.0.0.1:${rec.port}/?token=${encodeURIComponent(rec.token)}`;
6927
+ }
6928
+ function clearWebConsole(file = paths.webConsoleFile) {
6929
+ try {
6930
+ const rec = JSON.parse(readFileSync5(file, "utf8"));
6931
+ if (rec.pid === process.pid) unlinkSync(file);
6932
+ } catch {
6933
+ }
6934
+ }
6935
+ function stableWebConsoleToken(file = paths.webTokenFile) {
6936
+ try {
6937
+ const t = readFileSync5(file, "utf8").trim();
6938
+ if (t) return t;
6939
+ } catch {
6940
+ }
6941
+ const token = randomUUID3();
6942
+ try {
6943
+ mkdirSync2(dirname10(file), { recursive: true });
6944
+ try {
6945
+ unlinkSync(file);
6946
+ } catch {
6947
+ }
6948
+ writeFileSync2(file, `${token}
6949
+ `, { mode: 384 });
6950
+ } catch {
6951
+ }
6952
+ return token;
6953
+ }
6954
+ function isAlive(pid) {
6955
+ try {
6956
+ process.kill(pid, 0);
6957
+ return true;
6958
+ } catch (err) {
6959
+ return err.code === "EPERM";
6960
+ }
6961
+ }
6962
+
6754
6963
  // src/project/lifecycle.ts
6755
6964
  import { mkdir as mkdir10 } from "fs/promises";
6756
6965
  import { existsSync as existsSync9 } from "fs";
@@ -7019,8 +7228,8 @@ async function leaveChat(channel, chatId) {
7019
7228
 
7020
7229
  // src/bot/session-store.ts
7021
7230
  import { mkdir as mkdir11, readFile as readFile10, rename as rename5, writeFile as writeFile9 } from "fs/promises";
7022
- import { randomUUID as randomUUID3 } from "crypto";
7023
- import { dirname as dirname10 } from "path";
7231
+ import { randomUUID as randomUUID4 } from "crypto";
7232
+ import { dirname as dirname11 } from "path";
7024
7233
  var FILE_VERSION3 = 2;
7025
7234
  var LEGACY_V1_SESSION_FIELD = "codexThreadId";
7026
7235
  function migrate(raw) {
@@ -7056,8 +7265,8 @@ function withLock2(fn) {
7056
7265
  return run;
7057
7266
  }
7058
7267
  async function write2(sessions) {
7059
- await mkdir11(dirname10(paths.sessionsFile), { recursive: true });
7060
- const tmp = `${paths.sessionsFile}.tmp-${process.pid}-${randomUUID3()}`;
7268
+ await mkdir11(dirname11(paths.sessionsFile), { recursive: true });
7269
+ const tmp = `${paths.sessionsFile}.tmp-${process.pid}-${randomUUID4()}`;
7061
7270
  const body = { version: FILE_VERSION3, sessions };
7062
7271
  await writeFile9(tmp, `${JSON.stringify(body, null, 2)}
7063
7272
  `, "utf8");
@@ -7101,7 +7310,7 @@ async function handleDmConsole(channel, cfg, msg) {
7101
7310
  await channel.send(msg.chatId, { markdown: "\u26D4 \u4EC5\u7BA1\u7406\u5458\u53EF\u5728\u79C1\u804A\u91CC\u7BA1\u7406\u9879\u76EE\u3002" }, { replyTo: msg.messageId }).catch(() => void 0);
7102
7311
  return;
7103
7312
  }
7104
- await sendManagedCard(channel, msg.chatId, buildDmMenuCard(), msg.messageId).catch(
7313
+ await sendManagedCard(channel, msg.chatId, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }), msg.messageId).catch(
7105
7314
  (err) => log.fail("console", err, { cmd: "menu-send" })
7106
7315
  );
7107
7316
  });
@@ -8863,7 +9072,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
8863
9072
  patch(evt, buildSettingsCard(cfg));
8864
9073
  }
8865
9074
  const freshMenu = (evt) => {
8866
- patch(evt, buildDmMenuCard());
9075
+ patch(evt, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
8867
9076
  };
8868
9077
  const runUsage = (evt, force) => {
8869
9078
  if (!dmAdmin(evt.operator?.openId)) return;
@@ -8915,6 +9124,35 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
8915
9124
  }
8916
9125
  return buildProjectListCard(projects, byChat);
8917
9126
  };
9127
+ const buildDoctorInfo = async () => {
9128
+ const codexProbe = await backend.doctor({ force: true });
9129
+ const app = cfg.accounts.app;
9130
+ const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
9131
+ const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
9132
+ const missingScopes = scopeCheck?.missingScopes;
9133
+ const missingJoinScopes = scopeCheck?.missingJoinScopes;
9134
+ return {
9135
+ codexOk: codexProbe.ok,
9136
+ codexVer: codexProbe.version,
9137
+ conn: channel.getConnectionStatus?.()?.state ?? "unknown",
9138
+ bridgeVer: bridgeVersion(),
9139
+ node: process.version,
9140
+ platform: `${process.platform}-${process.arch}`,
9141
+ logStdout: serviceStdoutPath(),
9142
+ logStderr: serviceStderrPath(),
9143
+ configFile: paths.configFile,
9144
+ missingScopes,
9145
+ // 缺失时预选缺失项(精准开通);查不到/全开通时预选全部必需 scope 供核对。
9146
+ scopeGrantUrl: buildScopeGrantUrl(
9147
+ app.id,
9148
+ app.tenant,
9149
+ missingScopes && missingScopes.length ? missingScopes : void 0
9150
+ ),
9151
+ missingJoinScopes,
9152
+ // 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
9153
+ joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
9154
+ };
9155
+ };
8918
9156
  dispatcher.on(DM.menu, ({ evt }) => {
8919
9157
  if (dmAdmin(evt.operator?.openId)) freshMenu(evt);
8920
9158
  }).on(DM.newProject, ({ evt }) => {
@@ -8979,41 +9217,15 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
8979
9217
  if (dmAdmin(evt.operator?.openId)) await patch(evt, buildSettingsCard(cfg));
8980
9218
  }).on(DM.doctor, async ({ evt }) => {
8981
9219
  if (!dmAdmin(evt.operator?.openId)) return;
8982
- const codexProbe = await backend.doctor({ force: true });
8983
- const app = cfg.accounts.app;
8984
- const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
8985
- const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
8986
- const missingScopes = scopeCheck?.missingScopes;
8987
- const missingJoinScopes = scopeCheck?.missingJoinScopes;
8988
- const info = {
8989
- codexOk: codexProbe.ok,
8990
- codexVer: codexProbe.version,
8991
- conn: channel.getConnectionStatus?.()?.state ?? "unknown",
8992
- bridgeVer: bridgeVersion(),
8993
- node: process.version,
8994
- platform: `${process.platform}-${process.arch}`,
8995
- logStdout: serviceStdoutPath(),
8996
- logStderr: serviceStderrPath(),
8997
- configFile: paths.configFile,
8998
- missingScopes,
8999
- // 缺失时预选缺失项(精准开通);查不到/全开通时预选全部必需 scope 供核对。
9000
- scopeGrantUrl: buildScopeGrantUrl(
9001
- app.id,
9002
- app.tenant,
9003
- missingScopes && missingScopes.length ? missingScopes : void 0
9004
- ),
9005
- missingJoinScopes,
9006
- // 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
9007
- joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
9008
- };
9009
- await sendManagedCard(channel, evt.chatId, buildDoctorCard(info), evt.messageId).catch(
9220
+ await sendManagedCard(channel, evt.chatId, buildDoctorCard(await buildDoctorInfo()), evt.messageId).catch(
9010
9221
  (err) => log.fail("console", err, { cmd: "doctor" })
9011
9222
  );
9012
9223
  }).on(DM.reconnect, async ({ evt }) => {
9013
9224
  if (!dmAdmin(evt.operator?.openId)) return;
9014
9225
  const conn = channel.getConnectionStatus?.()?.state ?? "unknown";
9015
- await channel.send(evt.chatId, { markdown: `\u{1F504} \u957F\u8FDE\u63A5\u72B6\u6001\uFF1A**${conn}**
9016
- SDK \u4F1A\u81EA\u52A8\u91CD\u8FDE\uFF1B\u82E5\u957F\u671F\u65AD\u5F00\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u91CD\u8DD1 \`feishu-codex-bridge run\`\uFF08\u524D\u53F0\uFF09\u6216 \`feishu-codex-bridge restart\`\uFF08\u540E\u53F0\u5B88\u62A4\uFF09\u3002` }, { replyTo: evt.messageId }).catch(() => void 0);
9226
+ await sendManagedCard(channel, evt.chatId, buildReconnectCard(conn), evt.messageId).catch(
9227
+ (err) => log.fail("console", err, { cmd: "reconnect" })
9228
+ );
9017
9229
  }).on(DM.update, ({ evt }) => {
9018
9230
  if (!dmAdmin(evt.operator?.openId)) return;
9019
9231
  void (async () => {
@@ -9120,6 +9332,10 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9120
9332
  });
9121
9333
  }).on(DM.setTools, ({ evt, value }) => {
9122
9334
  applyPref(evt, (p) => p.showToolCalls = value.v === "on");
9335
+ }).on(DM.setShowModel, ({ evt, value }) => {
9336
+ applyPref(evt, (p) => {
9337
+ p.showModel = value.v === "running" ? "running" : value.v === "always" ? "always" : "off";
9338
+ });
9123
9339
  }).on(DM.setWatchdog, ({ evt, value }) => {
9124
9340
  const n = Number(value.v);
9125
9341
  if (Number.isFinite(n)) applyPref(evt, (p) => p.runIdleTimeoutSeconds = n);
@@ -9213,7 +9429,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9213
9429
  const name = typeof value.n === "string" ? value.n : "";
9214
9430
  patch(evt, async () => {
9215
9431
  const p = await getProjectByName(name);
9216
- if (!p) return buildDmMenuCard();
9432
+ if (!p) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9217
9433
  return buildAllowlistCard(p, await namesWithOperator(evt, p.allowedUsers ?? []));
9218
9434
  });
9219
9435
  }).on(DM.addAllowedForm, ({ evt, value }) => {
@@ -9245,7 +9461,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9245
9461
  patch(evt, async () => {
9246
9462
  await updateProject(name, (p) => ({ allowedUsers: (p.allowedUsers ?? []).filter((x) => x !== id) }));
9247
9463
  const fresh = await getProjectByName(name);
9248
- if (!fresh) return buildDmMenuCard();
9464
+ if (!fresh) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9249
9465
  return buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
9250
9466
  });
9251
9467
  }).on(DM.projectSettings, ({ evt, value }) => {
@@ -9253,14 +9469,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9253
9469
  const name = typeof value.n === "string" ? value.n : "";
9254
9470
  patch(evt, async () => {
9255
9471
  const p = await getProjectByName(name);
9256
- return p ? buildProjectSettingsCard(p, backendDisplayName(p.backend)) : buildDmMenuCard();
9472
+ return p ? buildProjectSettingsCard(p, backendDisplayName(p.backend)) : buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9257
9473
  });
9258
9474
  }).on(DM.projectTopics, ({ evt, value }) => {
9259
9475
  if (!dmAdmin(evt.operator?.openId)) return;
9260
9476
  const name = typeof value.n === "string" ? value.n : "";
9261
9477
  patch(evt, async () => {
9262
9478
  const p = await getProjectByName(name);
9263
- if (!p) return buildDmMenuCard();
9479
+ if (!p) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9264
9480
  const sessions2 = (await listSessions()).filter((s) => s.chatId === p.chatId);
9265
9481
  return buildProjectTopicsCard(p, sessions2);
9266
9482
  });
@@ -9270,7 +9486,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9270
9486
  const on = value.v === "on";
9271
9487
  patch(evt, async () => {
9272
9488
  const r = await performSetNoMention({ projectName: name, on });
9273
- if (!r.ok) return buildDmMenuCard();
9489
+ if (!r.ok) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9274
9490
  return buildProjectSettingsCard(r.project, backendDisplayName(r.project.backend));
9275
9491
  });
9276
9492
  }).on(DM.setAutoCompactDm, ({ evt, value }) => {
@@ -9279,7 +9495,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9279
9495
  const on = value.v === "on";
9280
9496
  patch(evt, async () => {
9281
9497
  const r = await performSetAutoCompact({ projectName: name, on, evictLiveSessionsForChat });
9282
- if (!r.ok) return buildDmMenuCard();
9498
+ if (!r.ok) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9283
9499
  log.info("console", "project-autocompact", { project: name, on });
9284
9500
  return buildProjectSettingsCard(r.project, backendDisplayName(r.project.backend));
9285
9501
  });
@@ -9288,7 +9504,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9288
9504
  const name = typeof value.n === "string" ? value.n : "";
9289
9505
  patch(evt, async () => {
9290
9506
  const p = await getProjectByName(name);
9291
- return p ? buildPermissionCard(p) : buildDmMenuCard();
9507
+ return p ? buildPermissionCard(p) : buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9292
9508
  });
9293
9509
  }).on(DM.permissionSubmit, ({ evt, value, formValue }) => {
9294
9510
  if (!dmAdmin(evt.operator?.openId)) return;
@@ -9434,6 +9650,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9434
9650
  firstRec = void 0;
9435
9651
  const turnModel = rec?.model ?? opts.model;
9436
9652
  const turnEffort = rec?.effort ?? opts.effort;
9653
+ const modelDisp = getModelDisplay(cfg);
9437
9654
  const run = opts.thread.runStreamed(turnInput, { model: turnModel, effort: turnEffort });
9438
9655
  const turnStartAt = Date.now();
9439
9656
  state.run = run;
@@ -9443,7 +9660,9 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9443
9660
  const rc = {
9444
9661
  rs: render.snapshot(),
9445
9662
  requesterOpenId: opts.requesterOpenId,
9446
- showTools: render.showTools
9663
+ showTools: render.showTools,
9664
+ // 模型显示档位:footnote 本轮 model·推理强度;always 档终态卡也保留。
9665
+ ...modelDisp !== "off" && turnModel ? { model: turnModel, effort: turnEffort, modelOnTerminal: modelDisp === "always" } : {}
9447
9666
  };
9448
9667
  const adoptThreadId = async (messageId) => {
9449
9668
  if (activeKey.startsWith("pending:")) {
@@ -9722,7 +9941,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9722
9941
  const startTurn = () => {
9723
9942
  const render = new RunRender();
9724
9943
  render.showTools = getShowToolCalls(cfg);
9725
- const rc = { rs: render.snapshot(), requesterOpenId: opts.requesterOpenId, showTools: render.showTools, goalControls: true };
9944
+ const goalModelDisp = getModelDisplay(cfg);
9945
+ const rc = {
9946
+ rs: render.snapshot(),
9947
+ requesterOpenId: opts.requesterOpenId,
9948
+ showTools: render.showTools,
9949
+ goalControls: true,
9950
+ ...goalModelDisp !== "off" && opts.model ? { model: opts.model, effort: opts.effort, modelOnTerminal: goalModelDisp === "always" } : {}
9951
+ };
9726
9952
  return { render, rc, stream: null, cardMsgId: null };
9727
9953
  };
9728
9954
  const ensureCard = async (ctx) => {
@@ -10089,9 +10315,60 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
10089
10315
  }).catch(() => void 0);
10090
10316
  return;
10091
10317
  }
10092
- await sendManagedCard(channel, op, buildDmMenuCard(), void 0, false, "open_id").catch(
10093
- (err) => log.fail("console", err, { cmd: "menu-card" })
10094
- );
10318
+ const sendDm = (c) => sendManagedCard(channel, op, c, void 0, false, "open_id");
10319
+ try {
10320
+ switch (evt.eventKey) {
10321
+ case DM.newProject:
10322
+ await sendDm(buildNewProjectFormCard({ backends: backendOptionsFor("full") }));
10323
+ break;
10324
+ case DM.projects:
10325
+ await sendDm(await renderProjectList());
10326
+ break;
10327
+ case DM.settings:
10328
+ await sendDm(buildSettingsCard(cfg));
10329
+ break;
10330
+ case DM.doctor:
10331
+ await sendDm(buildDoctorCard(await buildDoctorInfo()));
10332
+ break;
10333
+ case DM.reconnect:
10334
+ await sendDm(buildReconnectCard(channel.getConnectionStatus?.()?.state ?? "unknown"));
10335
+ break;
10336
+ case DM.usage: {
10337
+ const { messageId } = await sendDm(buildUsageCard({ phase: "loading" }));
10338
+ let state;
10339
+ try {
10340
+ state = { phase: "ready", data: await fetchUsageBundle(false) };
10341
+ } catch (e) {
10342
+ log.fail("console", e, { phase: "usage", via: "menu" });
10343
+ state = {
10344
+ phase: "error",
10345
+ kind: e instanceof UsageError ? e.kind : "transient",
10346
+ message: e instanceof Error ? e.message : String(e)
10347
+ };
10348
+ }
10349
+ const ok = await updateManagedCard(channel, messageId, buildUsageCard(state)).catch(() => false);
10350
+ if (!ok) await sendDm(buildUsageCard(state));
10351
+ break;
10352
+ }
10353
+ case DM.update: {
10354
+ const { messageId } = await sendDm(buildUpdateCard({ phase: "checking" }));
10355
+ const current = currentVersion();
10356
+ const latest = await latestVersion().catch(() => null);
10357
+ const hasUpdate = !!latest && isNewer(latest, current);
10358
+ log.info("console", "update-check", { current, latest, hasUpdate, via: "menu" });
10359
+ await updateManagedCard(
10360
+ channel,
10361
+ messageId,
10362
+ buildUpdateCard({ phase: "checked", current, latest, hasUpdate, dev: isDevSource() })
10363
+ ).catch((e) => log.fail("console", e, { phase: "update-check", via: "menu" }));
10364
+ break;
10365
+ }
10366
+ default:
10367
+ await sendDm(buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
10368
+ }
10369
+ } catch (err) {
10370
+ log.fail("console", err, { cmd: "menu-card", key: evt.eventKey });
10371
+ }
10095
10372
  };
10096
10373
  const reaper = setInterval(() => {
10097
10374
  const now = Date.now();
@@ -10355,7 +10632,7 @@ function withOwnerAdmin(base, ownerOpenId) {
10355
10632
 
10356
10633
  // src/admin/host.ts
10357
10634
  import { readdir as readdir4, stat as stat4 } from "fs/promises";
10358
- import { dirname as dirname11, join as join18, resolve as resolve7 } from "path";
10635
+ import { dirname as dirname12, join as join18, resolve as resolve7 } from "path";
10359
10636
  import { fileURLToPath as fileURLToPath5 } from "url";
10360
10637
  import { arch, platform as platform2, release } from "os";
10361
10638
  function toDaemonStatus(opts) {
@@ -10407,7 +10684,7 @@ async function collectHostDoctor(logsDir2 = join18(paths.appDir, "logs")) {
10407
10684
  };
10408
10685
  }
10409
10686
  function resolveCliBinPath3() {
10410
- const distDir = dirname11(fileURLToPath5(import.meta.url));
10687
+ const distDir = dirname12(fileURLToPath5(import.meta.url));
10411
10688
  return resolve7(distDir, "..", "bin", "feishu-codex-bridge.mjs");
10412
10689
  }
10413
10690
  function buildDaemonControlCommand(action, binPath = resolveCliBinPath3(), nodePath = process.execPath) {
@@ -10457,7 +10734,7 @@ function createAdminService(deps = {}) {
10457
10734
  try {
10458
10735
  const raw = await readFile11(botPaths(botId).processesFile, "utf8");
10459
10736
  const rec = JSON.parse(raw);
10460
- if (typeof rec.pid === "number" && isAlive(rec.pid)) {
10737
+ if (typeof rec.pid === "number" && isAlive2(rec.pid)) {
10461
10738
  return { running: true, pid: rec.pid, startedAt: rec.startedAt };
10462
10739
  }
10463
10740
  } catch {
@@ -10853,7 +11130,7 @@ async function probeAllBackends() {
10853
11130
  function createReadonlyAdminService(deps = {}) {
10854
11131
  return createAdminService({ startDaemon: deps.startDaemon, applyUpdate: deps.applyUpdate, readonlyPreview: true });
10855
11132
  }
10856
- function isAlive(pid) {
11133
+ function isAlive2(pid) {
10857
11134
  try {
10858
11135
  process.kill(pid, 0);
10859
11136
  return true;
@@ -10862,66 +11139,6 @@ function isAlive(pid) {
10862
11139
  }
10863
11140
  }
10864
11141
 
10865
- // src/web/discovery.ts
10866
- import { randomUUID as randomUUID4 } from "crypto";
10867
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
10868
- import { dirname as dirname12 } from "path";
10869
- function publishWebConsole(rec, file = paths.webConsoleFile) {
10870
- mkdirSync2(dirname12(file), { recursive: true });
10871
- try {
10872
- unlinkSync(file);
10873
- } catch {
10874
- }
10875
- writeFileSync2(file, `${JSON.stringify(rec, null, 2)}
10876
- `, { mode: 384 });
10877
- }
10878
- function readWebConsole(file = paths.webConsoleFile) {
10879
- try {
10880
- const rec = JSON.parse(readFileSync5(file, "utf8"));
10881
- if (typeof rec.port !== "number" || typeof rec.token !== "string" || rec.token === "" || typeof rec.pid !== "number") {
10882
- return void 0;
10883
- }
10884
- if (!isAlive2(rec.pid)) return void 0;
10885
- return rec;
10886
- } catch {
10887
- return void 0;
10888
- }
10889
- }
10890
- function clearWebConsole(file = paths.webConsoleFile) {
10891
- try {
10892
- const rec = JSON.parse(readFileSync5(file, "utf8"));
10893
- if (rec.pid === process.pid) unlinkSync(file);
10894
- } catch {
10895
- }
10896
- }
10897
- function stableWebConsoleToken(file = paths.webTokenFile) {
10898
- try {
10899
- const t = readFileSync5(file, "utf8").trim();
10900
- if (t) return t;
10901
- } catch {
10902
- }
10903
- const token = randomUUID4();
10904
- try {
10905
- mkdirSync2(dirname12(file), { recursive: true });
10906
- try {
10907
- unlinkSync(file);
10908
- } catch {
10909
- }
10910
- writeFileSync2(file, `${token}
10911
- `, { mode: 384 });
10912
- } catch {
10913
- }
10914
- return token;
10915
- }
10916
- function isAlive2(pid) {
10917
- try {
10918
- process.kill(pid, 0);
10919
- return true;
10920
- } catch (err) {
10921
- return err.code === "EPERM";
10922
- }
10923
- }
10924
-
10925
11142
  // src/cli/commands/daemon-control.ts
10926
11143
  function spawnDaemonControl(action) {
10927
11144
  const { command, args } = buildDaemonControlCommand(action);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {