@modelzen/feishu-codex-bridge 0.4.2 → 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 +233 -75
  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;
@@ -3347,6 +3354,7 @@ var DM = {
3347
3354
  rmDo: "dm.rmDo",
3348
3355
  rmCancel: "dm.rmCancel",
3349
3356
  setTools: "dm.set.tools",
3357
+ setShowModel: "dm.set.showModel",
3350
3358
  setWatchdog: "dm.set.watchdog",
3351
3359
  // 假死超时「自定义…」:watchdogCustom 打开输入卡,watchdogCustomSubmit 保存任意秒数
3352
3360
  watchdogCustom: "dm.set.watchdog.custom",
@@ -3503,6 +3511,17 @@ function buildUpdateCard(state) {
3503
3511
  );
3504
3512
  }
3505
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
+ }
3506
3525
  function connLabel(state) {
3507
3526
  switch (state) {
3508
3527
  case "connected":
@@ -3809,38 +3828,78 @@ function optionRow(label, actionId, current, opts) {
3809
3828
  actions(opts.map((o) => button(o.label, { a: actionId, v: o.value }, o.value === current ? "primary" : "default")))
3810
3829
  ];
3811
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
+ }
3812
3841
  function buildSettingsCard(cfg) {
3813
3842
  const watchdogSec = cfg.preferences?.runIdleTimeoutSeconds ?? 120;
3814
3843
  return card(
3815
3844
  [
3816
- md("**\u5168\u5C40\u8BBE\u7F6E**\uFF08\u7BA1\u7406\u5458\uFF09"),
3817
- ...optionRow("\u{1F527} \u5DE5\u5177\u8C03\u7528", DM.setTools, getShowToolCalls(cfg) ? "on" : "off", [
3818
- { label: "\u663E\u793A", value: "on" },
3819
- { label: "\u9690\u85CF", value: "off" }
3820
- ]),
3821
- 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"),
3822
3871
  actions([
3823
3872
  ...[0, 120, 300].map(
3824
3873
  (v) => button(v === 0 ? "\u5173\u95ED" : `${v}\u79D2`, { a: DM.setWatchdog, v: String(v) }, v === watchdogSec ? "primary" : "default")
3825
3874
  ),
3826
3875
  button("\u81EA\u5B9A\u4E49\u2026", { a: DM.watchdogCustom })
3827
3876
  ]),
3828
- ...optionRow("\u{1F4E5} \u8FD0\u884C\u4E2D\u65B0\u6D88\u606F", DM.setPending, getPendingPolicy(cfg), [
3829
- { label: "\u5F15\u5BFC", value: "steer" },
3830
- { label: "\u6392\u961F", value: "queue" }
3831
- ]),
3832
- ...optionRow("\u26A1 \u5E76\u53D1\u4E0A\u9650", DM.setConcurrency, String(getMaxConcurrentRuns(cfg)), [
3833
- { label: "1", value: "1" },
3834
- { label: "5", value: "5" },
3835
- { label: "10", value: "10" },
3836
- { label: "20", value: "20" }
3837
- ]),
3838
- 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"),
3839
- 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
+ ),
3840
3899
  hr(),
3841
3900
  actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
3842
3901
  ],
3843
- { header: { title: "\u2699\uFE0F \u8BBE\u7F6E", template: "blue" } }
3902
+ { header: { title: "\u2699\uFE0F \u5168\u5C40\u8BBE\u7F6E", template: "blue" } }
3844
3903
  );
3845
3904
  }
3846
3905
  function buildWatchdogCustomCard(cfg) {
@@ -4904,24 +4963,6 @@ function buildRunCard(rc) {
4904
4963
  }
4905
4964
  function renderRunning(state, rc) {
4906
4965
  const elements = [];
4907
- if (rc.cardKey && rc.goalControls) {
4908
- if (rc.goalEnding) {
4909
- elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
4910
- elements.push(noteMd("_\u{1F3AF} \u76EE\u6807\u5DF2\u89E3\u9664\uFF0C\u672C\u8F6E\u8F93\u51FA\u5B8C\u6210\u540E\u505C\u6B62_"));
4911
- } else {
4912
- elements.push(
4913
- actions(
4914
- [
4915
- button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger"),
4916
- button("\u{1F3AF} \u7ED3\u675F\u76EE\u6807", { a: RC.endGoal, m: rc.cardKey }, "default")
4917
- ],
4918
- CONTROLS_EID
4919
- )
4920
- );
4921
- }
4922
- } else if (rc.cardKey && !rc.hideStop) {
4923
- elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")], CONTROLS_EID));
4924
- }
4925
4966
  const reasoning = reasoningContent(state);
4926
4967
  if (reasoning) elements.push(reasoningPanel(reasoning, state.reasoningActive));
4927
4968
  const showTools = rc.showTools !== false;
@@ -4937,9 +4978,30 @@ function renderRunning(state, rc) {
4937
4978
  if (tools.length > 0) elements.push(...renderToolGroup(tools, false));
4938
4979
  const answer = textParts.join("\n\n");
4939
4980
  if (answer) elements.push(mdStream(answer, ANSWER_EID));
4940
- 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);
4941
4985
  const gauge = gaugeEl(state);
4942
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
+ }
4943
5005
  return elements;
4944
5006
  }
4945
5007
  function renderTerminal(state, rc) {
@@ -4977,6 +5039,8 @@ function renderTerminal(state, rc) {
4977
5039
  }
4978
5040
  const gauge = gaugeEl(state);
4979
5041
  if (gauge) elements.push(gauge);
5042
+ const mEl = rc.modelOnTerminal ? modelEl(rc) : null;
5043
+ if (mEl) elements.push(mEl);
4980
5044
  return elements;
4981
5045
  }
4982
5046
  function errorAdvice(msg) {
@@ -5098,9 +5162,35 @@ function collapsedToolSummary(tools, finalized) {
5098
5162
  body: tools.map((t) => `- ${toolHeaderText(t)}`).join("\n")
5099
5163
  });
5100
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
+ }
5101
5168
  function footerStatus(status) {
5102
- 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";
5103
- 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
+ };
5104
5194
  }
5105
5195
  function summaryText(state) {
5106
5196
  if (state.terminal === "interrupted") return "\u5DF2\u4E2D\u65AD";
@@ -9034,6 +9124,35 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
9034
9124
  }
9035
9125
  return buildProjectListCard(projects, byChat);
9036
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
+ };
9037
9156
  dispatcher.on(DM.menu, ({ evt }) => {
9038
9157
  if (dmAdmin(evt.operator?.openId)) freshMenu(evt);
9039
9158
  }).on(DM.newProject, ({ evt }) => {
@@ -9098,41 +9217,15 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
9098
9217
  if (dmAdmin(evt.operator?.openId)) await patch(evt, buildSettingsCard(cfg));
9099
9218
  }).on(DM.doctor, async ({ evt }) => {
9100
9219
  if (!dmAdmin(evt.operator?.openId)) return;
9101
- const codexProbe = await backend.doctor({ force: true });
9102
- const app = cfg.accounts.app;
9103
- const secret = await getSecret(secretKeyForApp(app.id)).catch(() => void 0);
9104
- const scopeCheck = secret ? await validateAppCredentials(app.id, secret, app.tenant).catch(() => void 0) : void 0;
9105
- const missingScopes = scopeCheck?.missingScopes;
9106
- const missingJoinScopes = scopeCheck?.missingJoinScopes;
9107
- const info = {
9108
- codexOk: codexProbe.ok,
9109
- codexVer: codexProbe.version,
9110
- conn: channel.getConnectionStatus?.()?.state ?? "unknown",
9111
- bridgeVer: bridgeVersion(),
9112
- node: process.version,
9113
- platform: `${process.platform}-${process.arch}`,
9114
- logStdout: serviceStdoutPath(),
9115
- logStderr: serviceStderrPath(),
9116
- configFile: paths.configFile,
9117
- missingScopes,
9118
- // 缺失时预选缺失项(精准开通);查不到/全开通时预选全部必需 scope 供核对。
9119
- scopeGrantUrl: buildScopeGrantUrl(
9120
- app.id,
9121
- app.tenant,
9122
- missingScopes && missingScopes.length ? missingScopes : void 0
9123
- ),
9124
- missingJoinScopes,
9125
- // 「加入存量群」按钮恒预选这两项 opt-in scope(它们不在必需清单里)。
9126
- joinScopeGrantUrl: buildScopeGrantUrl(app.id, app.tenant, JOIN_GROUP_SCOPES)
9127
- };
9128
- await sendManagedCard(channel, evt.chatId, buildDoctorCard(info), evt.messageId).catch(
9220
+ await sendManagedCard(channel, evt.chatId, buildDoctorCard(await buildDoctorInfo()), evt.messageId).catch(
9129
9221
  (err) => log.fail("console", err, { cmd: "doctor" })
9130
9222
  );
9131
9223
  }).on(DM.reconnect, async ({ evt }) => {
9132
9224
  if (!dmAdmin(evt.operator?.openId)) return;
9133
9225
  const conn = channel.getConnectionStatus?.()?.state ?? "unknown";
9134
- await channel.send(evt.chatId, { markdown: `\u{1F504} \u957F\u8FDE\u63A5\u72B6\u6001\uFF1A**${conn}**
9135
- 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
+ );
9136
9229
  }).on(DM.update, ({ evt }) => {
9137
9230
  if (!dmAdmin(evt.operator?.openId)) return;
9138
9231
  void (async () => {
@@ -9239,6 +9332,10 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9239
9332
  });
9240
9333
  }).on(DM.setTools, ({ evt, value }) => {
9241
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
+ });
9242
9339
  }).on(DM.setWatchdog, ({ evt, value }) => {
9243
9340
  const n = Number(value.v);
9244
9341
  if (Number.isFinite(n)) applyPref(evt, (p) => p.runIdleTimeoutSeconds = n);
@@ -9553,6 +9650,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9553
9650
  firstRec = void 0;
9554
9651
  const turnModel = rec?.model ?? opts.model;
9555
9652
  const turnEffort = rec?.effort ?? opts.effort;
9653
+ const modelDisp = getModelDisplay(cfg);
9556
9654
  const run = opts.thread.runStreamed(turnInput, { model: turnModel, effort: turnEffort });
9557
9655
  const turnStartAt = Date.now();
9558
9656
  state.run = run;
@@ -9562,7 +9660,9 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9562
9660
  const rc = {
9563
9661
  rs: render.snapshot(),
9564
9662
  requesterOpenId: opts.requesterOpenId,
9565
- showTools: render.showTools
9663
+ showTools: render.showTools,
9664
+ // 模型显示档位:footnote 本轮 model·推理强度;always 档终态卡也保留。
9665
+ ...modelDisp !== "off" && turnModel ? { model: turnModel, effort: turnEffort, modelOnTerminal: modelDisp === "always" } : {}
9566
9666
  };
9567
9667
  const adoptThreadId = async (messageId) => {
9568
9668
  if (activeKey.startsWith("pending:")) {
@@ -9841,7 +9941,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9841
9941
  const startTurn = () => {
9842
9942
  const render = new RunRender();
9843
9943
  render.showTools = getShowToolCalls(cfg);
9844
- 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
+ };
9845
9952
  return { render, rc, stream: null, cardMsgId: null };
9846
9953
  };
9847
9954
  const ensureCard = async (ctx) => {
@@ -10208,9 +10315,60 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
10208
10315
  }).catch(() => void 0);
10209
10316
  return;
10210
10317
  }
10211
- await sendManagedCard(channel, op, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }), void 0, false, "open_id").catch(
10212
- (err) => log.fail("console", err, { cmd: "menu-card" })
10213
- );
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
+ }
10214
10372
  };
10215
10373
  const reaper = setInterval(() => {
10216
10374
  const now = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.4.2",
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": {