@modelzen/feishu-codex-bridge 0.4.1 → 0.4.2

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 +146 -87
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2950,7 +2950,14 @@ function card(elements, opts = {}) {
2950
2950
  obj.header = {
2951
2951
  template: opts.header.template ?? "blue",
2952
2952
  title: { tag: "plain_text", content: opts.header.title },
2953
- ...opts.header.subtitle ? { subtitle: { tag: "plain_text", content: opts.header.subtitle } } : {}
2953
+ ...opts.header.subtitle ? { subtitle: { tag: "plain_text", content: opts.header.subtitle } } : {},
2954
+ ...opts.header.textTags?.length ? {
2955
+ text_tag_list: opts.header.textTags.map((t) => ({
2956
+ tag: "text_tag",
2957
+ text: { tag: "plain_text", content: t.text },
2958
+ color: t.color
2959
+ }))
2960
+ } : {}
2954
2961
  };
2955
2962
  }
2956
2963
  return obj;
@@ -3025,6 +3032,27 @@ function actions(items, elementId) {
3025
3032
  columns: items.map((it) => ({ tag: "column", width: "auto", elements: [it] }))
3026
3033
  };
3027
3034
  }
3035
+ function actionsFixed(items, width, elementId) {
3036
+ return {
3037
+ tag: "column_set",
3038
+ ...elementId ? { element_id: elementId } : {},
3039
+ flex_mode: "flow",
3040
+ horizontal_spacing: "8px",
3041
+ columns: items.map((it) => ({ tag: "column", width: "auto", elements: [{ ...it, width, size: "large" }] }))
3042
+ };
3043
+ }
3044
+ function splitRow(left, right, elementId) {
3045
+ return {
3046
+ tag: "column_set",
3047
+ ...elementId ? { element_id: elementId } : {},
3048
+ flex_mode: "none",
3049
+ horizontal_spacing: "medium",
3050
+ columns: [
3051
+ { tag: "column", width: "auto", vertical_align: "center", elements: [left] },
3052
+ { tag: "column", width: "weighted", weight: 1, vertical_align: "center", elements: [right] }
3053
+ ]
3054
+ };
3055
+ }
3028
3056
  function button(label, value, type = "default") {
3029
3057
  return {
3030
3058
  tag: "button",
@@ -3033,11 +3061,12 @@ function button(label, value, type = "default") {
3033
3061
  behaviors: [{ type: "callback", value }]
3034
3062
  };
3035
3063
  }
3036
- function linkButton(label, url, type = "default") {
3064
+ function linkButton(label, url, type = "default", size) {
3037
3065
  return {
3038
3066
  tag: "button",
3039
3067
  text: { tag: "plain_text", content: label },
3040
3068
  type,
3069
+ ...size ? { size } : {},
3041
3070
  behaviors: [{ type: "open_url", default_url: url }]
3042
3071
  };
3043
3072
  }
@@ -3295,6 +3324,8 @@ function openChatUrl(chatId) {
3295
3324
  return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
3296
3325
  }
3297
3326
  var REPO = "https://github.com/modelzen/feishu-codex-bridge";
3327
+ var MENU_BTN_W_TOP = "152px";
3328
+ var MENU_BTN_W_BOT = "112px";
3298
3329
  var DM = {
3299
3330
  menu: "dm.menu",
3300
3331
  newProject: "dm.newProject",
@@ -3351,24 +3382,47 @@ var GS = {
3351
3382
  function kindLabel(kind) {
3352
3383
  return kind === "single" ? "\u{1F4AC} \u5355\u4F1A\u8BDD\u7FA4" : "\u{1F465} \u591A\u8BDD\u9898\u7FA4";
3353
3384
  }
3354
- function buildDmMenuCard() {
3385
+ function buildDmMenuCard(opts = {}) {
3386
+ const { webConsoleUrl: webConsoleUrl2, version } = opts;
3355
3387
  return card(
3356
3388
  [
3357
3389
  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
3390
  hr(),
3359
- actions([
3391
+ // 两行按钮固定宽度、左对齐;两行宽度配平后总长相等、右边缘对齐(见 MENU_BTN_W_*)。
3392
+ actionsFixed([
3360
3393
  button("\u2795 \u65B0\u5EFA\u9879\u76EE", { a: DM.newProject }, "primary"),
3361
3394
  button("\u{1F4C1} \u9879\u76EE\u5217\u8868", { a: DM.projects }),
3362
3395
  button("\u2699\uFE0F \u8BBE\u7F6E", { a: DM.settings })
3363
- ]),
3364
- actions([
3396
+ ], MENU_BTN_W_TOP),
3397
+ actionsFixed([
3365
3398
  button("\u{1F4CA} \u7528\u91CF", { a: DM.usage }),
3366
3399
  button("\u{1FA7A} \u8BCA\u65AD", { a: DM.doctor }),
3367
3400
  button("\u{1F504} \u91CD\u8FDE", { a: DM.reconnect }),
3368
- button("\u2B06\uFE0F \u7248\u672C\u66F4\u65B0", { a: DM.update })
3369
- ])
3401
+ button("\u2B06\uFE0F \u66F4\u65B0", { a: DM.update })
3402
+ ], MENU_BTN_W_BOT),
3403
+ // 🌐 网页控制台:刻意低调——小号按钮靠左 + 右侧灰字说明;仅在 daemon 本机控制台
3404
+ // 在跑(webConsoleUrl 有值)时出现。按钮直接开 127.0.0.1 控制台——只有运行 bridge
3405
+ // 的这台机器打得开;其它电脑/手机点了会打不开(本机回环地址,飞书也分不出点击设备
3406
+ // 是不是本机),右侧文字已说明,按用户决定不另做处理。
3407
+ ...webConsoleUrl2 ? [
3408
+ hr(),
3409
+ splitRow(
3410
+ linkButton("\u{1F310} \u7F51\u9875\u63A7\u5236\u53F0", webConsoleUrl2, "default", "small"),
3411
+ note("\u4EC5\u5728**\u8FD0\u884C bridge \u7684\u8FD9\u53F0\u7535\u8111**\u4E0A\u80FD\u6253\u5F00\uFF08\u672C\u673A\u5730\u5740\uFF09\u3002")
3412
+ )
3413
+ ] : []
3370
3414
  ],
3371
- { header: { title: "\u{1F916} Codex Bridge \u7BA1\u7406\u53F0", template: "blue" } }
3415
+ {
3416
+ header: {
3417
+ title: "\u{1F916} Codex Bridge \u7BA1\u7406\u53F0",
3418
+ template: "blue",
3419
+ textTags: version ? [{ text: `v${version}`, color: "green" }] : void 0
3420
+ },
3421
+ // 这张卡可能内嵌带 token 的本机控制台直达链接(pc_url),且按钮全是管理员专属的
3422
+ // dm.* 回调——转发出去既无意义、又会把 token 链接一并带走。loopback 已使转发副本
3423
+ // 在他人设备上不可用,这里再加一道:禁止转发。
3424
+ forward: false
3425
+ }
3372
3426
  );
3373
3427
  }
3374
3428
  var backToMenu = () => actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]);
@@ -6751,6 +6805,71 @@ function buildUsageShareCard(data, opts = {}) {
6751
6805
  });
6752
6806
  }
6753
6807
 
6808
+ // src/web/discovery.ts
6809
+ import { randomUUID as randomUUID3 } from "crypto";
6810
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
6811
+ import { dirname as dirname10 } from "path";
6812
+ function publishWebConsole(rec, file = paths.webConsoleFile) {
6813
+ mkdirSync2(dirname10(file), { recursive: true });
6814
+ try {
6815
+ unlinkSync(file);
6816
+ } catch {
6817
+ }
6818
+ writeFileSync2(file, `${JSON.stringify(rec, null, 2)}
6819
+ `, { mode: 384 });
6820
+ }
6821
+ function readWebConsole(file = paths.webConsoleFile) {
6822
+ try {
6823
+ const rec = JSON.parse(readFileSync5(file, "utf8"));
6824
+ if (typeof rec.port !== "number" || typeof rec.token !== "string" || rec.token === "" || typeof rec.pid !== "number") {
6825
+ return void 0;
6826
+ }
6827
+ if (!isAlive(rec.pid)) return void 0;
6828
+ return rec;
6829
+ } catch {
6830
+ return void 0;
6831
+ }
6832
+ }
6833
+ function webConsoleUrl(file = paths.webConsoleFile) {
6834
+ const rec = readWebConsole(file);
6835
+ if (!rec) return void 0;
6836
+ return `http://127.0.0.1:${rec.port}/?token=${encodeURIComponent(rec.token)}`;
6837
+ }
6838
+ function clearWebConsole(file = paths.webConsoleFile) {
6839
+ try {
6840
+ const rec = JSON.parse(readFileSync5(file, "utf8"));
6841
+ if (rec.pid === process.pid) unlinkSync(file);
6842
+ } catch {
6843
+ }
6844
+ }
6845
+ function stableWebConsoleToken(file = paths.webTokenFile) {
6846
+ try {
6847
+ const t = readFileSync5(file, "utf8").trim();
6848
+ if (t) return t;
6849
+ } catch {
6850
+ }
6851
+ const token = randomUUID3();
6852
+ try {
6853
+ mkdirSync2(dirname10(file), { recursive: true });
6854
+ try {
6855
+ unlinkSync(file);
6856
+ } catch {
6857
+ }
6858
+ writeFileSync2(file, `${token}
6859
+ `, { mode: 384 });
6860
+ } catch {
6861
+ }
6862
+ return token;
6863
+ }
6864
+ function isAlive(pid) {
6865
+ try {
6866
+ process.kill(pid, 0);
6867
+ return true;
6868
+ } catch (err) {
6869
+ return err.code === "EPERM";
6870
+ }
6871
+ }
6872
+
6754
6873
  // src/project/lifecycle.ts
6755
6874
  import { mkdir as mkdir10 } from "fs/promises";
6756
6875
  import { existsSync as existsSync9 } from "fs";
@@ -7019,8 +7138,8 @@ async function leaveChat(channel, chatId) {
7019
7138
 
7020
7139
  // src/bot/session-store.ts
7021
7140
  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";
7141
+ import { randomUUID as randomUUID4 } from "crypto";
7142
+ import { dirname as dirname11 } from "path";
7024
7143
  var FILE_VERSION3 = 2;
7025
7144
  var LEGACY_V1_SESSION_FIELD = "codexThreadId";
7026
7145
  function migrate(raw) {
@@ -7056,8 +7175,8 @@ function withLock2(fn) {
7056
7175
  return run;
7057
7176
  }
7058
7177
  async function write2(sessions) {
7059
- await mkdir11(dirname10(paths.sessionsFile), { recursive: true });
7060
- const tmp = `${paths.sessionsFile}.tmp-${process.pid}-${randomUUID3()}`;
7178
+ await mkdir11(dirname11(paths.sessionsFile), { recursive: true });
7179
+ const tmp = `${paths.sessionsFile}.tmp-${process.pid}-${randomUUID4()}`;
7061
7180
  const body = { version: FILE_VERSION3, sessions };
7062
7181
  await writeFile9(tmp, `${JSON.stringify(body, null, 2)}
7063
7182
  `, "utf8");
@@ -7101,7 +7220,7 @@ async function handleDmConsole(channel, cfg, msg) {
7101
7220
  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
7221
  return;
7103
7222
  }
7104
- await sendManagedCard(channel, msg.chatId, buildDmMenuCard(), msg.messageId).catch(
7223
+ await sendManagedCard(channel, msg.chatId, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }), msg.messageId).catch(
7105
7224
  (err) => log.fail("console", err, { cmd: "menu-send" })
7106
7225
  );
7107
7226
  });
@@ -8863,7 +8982,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
8863
8982
  patch(evt, buildSettingsCard(cfg));
8864
8983
  }
8865
8984
  const freshMenu = (evt) => {
8866
- patch(evt, buildDmMenuCard());
8985
+ patch(evt, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
8867
8986
  };
8868
8987
  const runUsage = (evt, force) => {
8869
8988
  if (!dmAdmin(evt.operator?.openId)) return;
@@ -9213,7 +9332,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9213
9332
  const name = typeof value.n === "string" ? value.n : "";
9214
9333
  patch(evt, async () => {
9215
9334
  const p = await getProjectByName(name);
9216
- if (!p) return buildDmMenuCard();
9335
+ if (!p) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9217
9336
  return buildAllowlistCard(p, await namesWithOperator(evt, p.allowedUsers ?? []));
9218
9337
  });
9219
9338
  }).on(DM.addAllowedForm, ({ evt, value }) => {
@@ -9245,7 +9364,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9245
9364
  patch(evt, async () => {
9246
9365
  await updateProject(name, (p) => ({ allowedUsers: (p.allowedUsers ?? []).filter((x) => x !== id) }));
9247
9366
  const fresh = await getProjectByName(name);
9248
- if (!fresh) return buildDmMenuCard();
9367
+ if (!fresh) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9249
9368
  return buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
9250
9369
  });
9251
9370
  }).on(DM.projectSettings, ({ evt, value }) => {
@@ -9253,14 +9372,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9253
9372
  const name = typeof value.n === "string" ? value.n : "";
9254
9373
  patch(evt, async () => {
9255
9374
  const p = await getProjectByName(name);
9256
- return p ? buildProjectSettingsCard(p, backendDisplayName(p.backend)) : buildDmMenuCard();
9375
+ return p ? buildProjectSettingsCard(p, backendDisplayName(p.backend)) : buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9257
9376
  });
9258
9377
  }).on(DM.projectTopics, ({ evt, value }) => {
9259
9378
  if (!dmAdmin(evt.operator?.openId)) return;
9260
9379
  const name = typeof value.n === "string" ? value.n : "";
9261
9380
  patch(evt, async () => {
9262
9381
  const p = await getProjectByName(name);
9263
- if (!p) return buildDmMenuCard();
9382
+ if (!p) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9264
9383
  const sessions2 = (await listSessions()).filter((s) => s.chatId === p.chatId);
9265
9384
  return buildProjectTopicsCard(p, sessions2);
9266
9385
  });
@@ -9270,7 +9389,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9270
9389
  const on = value.v === "on";
9271
9390
  patch(evt, async () => {
9272
9391
  const r = await performSetNoMention({ projectName: name, on });
9273
- if (!r.ok) return buildDmMenuCard();
9392
+ if (!r.ok) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9274
9393
  return buildProjectSettingsCard(r.project, backendDisplayName(r.project.backend));
9275
9394
  });
9276
9395
  }).on(DM.setAutoCompactDm, ({ evt, value }) => {
@@ -9279,7 +9398,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9279
9398
  const on = value.v === "on";
9280
9399
  patch(evt, async () => {
9281
9400
  const r = await performSetAutoCompact({ projectName: name, on, evictLiveSessionsForChat });
9282
- if (!r.ok) return buildDmMenuCard();
9401
+ if (!r.ok) return buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9283
9402
  log.info("console", "project-autocompact", { project: name, on });
9284
9403
  return buildProjectSettingsCard(r.project, backendDisplayName(r.project.backend));
9285
9404
  });
@@ -9288,7 +9407,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
9288
9407
  const name = typeof value.n === "string" ? value.n : "";
9289
9408
  patch(evt, async () => {
9290
9409
  const p = await getProjectByName(name);
9291
- return p ? buildPermissionCard(p) : buildDmMenuCard();
9410
+ return p ? buildPermissionCard(p) : buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() });
9292
9411
  });
9293
9412
  }).on(DM.permissionSubmit, ({ evt, value, formValue }) => {
9294
9413
  if (!dmAdmin(evt.operator?.openId)) return;
@@ -10089,7 +10208,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
10089
10208
  }).catch(() => void 0);
10090
10209
  return;
10091
10210
  }
10092
- await sendManagedCard(channel, op, buildDmMenuCard(), void 0, false, "open_id").catch(
10211
+ await sendManagedCard(channel, op, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }), void 0, false, "open_id").catch(
10093
10212
  (err) => log.fail("console", err, { cmd: "menu-card" })
10094
10213
  );
10095
10214
  };
@@ -10355,7 +10474,7 @@ function withOwnerAdmin(base, ownerOpenId) {
10355
10474
 
10356
10475
  // src/admin/host.ts
10357
10476
  import { readdir as readdir4, stat as stat4 } from "fs/promises";
10358
- import { dirname as dirname11, join as join18, resolve as resolve7 } from "path";
10477
+ import { dirname as dirname12, join as join18, resolve as resolve7 } from "path";
10359
10478
  import { fileURLToPath as fileURLToPath5 } from "url";
10360
10479
  import { arch, platform as platform2, release } from "os";
10361
10480
  function toDaemonStatus(opts) {
@@ -10407,7 +10526,7 @@ async function collectHostDoctor(logsDir2 = join18(paths.appDir, "logs")) {
10407
10526
  };
10408
10527
  }
10409
10528
  function resolveCliBinPath3() {
10410
- const distDir = dirname11(fileURLToPath5(import.meta.url));
10529
+ const distDir = dirname12(fileURLToPath5(import.meta.url));
10411
10530
  return resolve7(distDir, "..", "bin", "feishu-codex-bridge.mjs");
10412
10531
  }
10413
10532
  function buildDaemonControlCommand(action, binPath = resolveCliBinPath3(), nodePath = process.execPath) {
@@ -10457,7 +10576,7 @@ function createAdminService(deps = {}) {
10457
10576
  try {
10458
10577
  const raw = await readFile11(botPaths(botId).processesFile, "utf8");
10459
10578
  const rec = JSON.parse(raw);
10460
- if (typeof rec.pid === "number" && isAlive(rec.pid)) {
10579
+ if (typeof rec.pid === "number" && isAlive2(rec.pid)) {
10461
10580
  return { running: true, pid: rec.pid, startedAt: rec.startedAt };
10462
10581
  }
10463
10582
  } catch {
@@ -10853,7 +10972,7 @@ async function probeAllBackends() {
10853
10972
  function createReadonlyAdminService(deps = {}) {
10854
10973
  return createAdminService({ startDaemon: deps.startDaemon, applyUpdate: deps.applyUpdate, readonlyPreview: true });
10855
10974
  }
10856
- function isAlive(pid) {
10975
+ function isAlive2(pid) {
10857
10976
  try {
10858
10977
  process.kill(pid, 0);
10859
10978
  return true;
@@ -10862,66 +10981,6 @@ function isAlive(pid) {
10862
10981
  }
10863
10982
  }
10864
10983
 
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
10984
  // src/cli/commands/daemon-control.ts
10926
10985
  function spawnDaemonControl(action) {
10927
10986
  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.2",
4
4
  "description": "Bridge Feishu/Lark messenger with local Codex via app-server (project=group, thread=session)",
5
5
  "type": "module",
6
6
  "bin": {