@modelzen/feishu-codex-bridge 0.3.0 → 0.3.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 +735 -100
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -78,6 +78,7 @@ import { dirname as dirname3, join as join2 } from "path";
78
78
 
79
79
  // src/config/store.ts
80
80
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
81
+ import { randomUUID } from "crypto";
81
82
  import { dirname as dirname2 } from "path";
82
83
 
83
84
  // src/config/schema.ts
@@ -114,18 +115,23 @@ function getRunIdleTimeoutMs(cfg) {
114
115
  const clamped = Math.min(Math.max(Math.floor(raw), 10), 1800);
115
116
  return clamped * 1e3;
116
117
  }
117
- function isUserAllowed(cfg, senderId) {
118
- const list = cfg.preferences?.access?.allowedUsers;
119
- if (!list || list.length === 0) return true;
120
- return list.includes(senderId);
121
- }
122
118
  function isChatAllowed(cfg, chatId) {
123
119
  const list = cfg.preferences?.access?.allowedChats;
124
120
  if (!list || list.length === 0) return true;
125
121
  return list.includes(chatId);
126
122
  }
123
+ function resolveOwner(cfg) {
124
+ const access = cfg.preferences?.access;
125
+ return access?.ownerOpenId ?? access?.admins?.[0];
126
+ }
127
127
  function isAdmin(cfg, senderId) {
128
- const list = cfg.preferences?.access?.admins;
128
+ if (!senderId) return false;
129
+ if (senderId === resolveOwner(cfg)) return true;
130
+ return Boolean(cfg.preferences?.access?.admins?.includes(senderId));
131
+ }
132
+ function isUserAllowedInProject(cfg, project, senderId) {
133
+ if (isAdmin(cfg, senderId)) return true;
134
+ const list = project?.allowedUsers;
129
135
  if (!list || list.length === 0) return true;
130
136
  return list.includes(senderId);
131
137
  }
@@ -174,13 +180,21 @@ exec ${sq(node)} ${sq(bridgeEntry)} secrets get "$@"
174
180
  await rename(tmp, wrapperPath);
175
181
  return wrapperPath;
176
182
  }
177
- async function saveConfig(cfg, path = paths.configFile) {
178
- await mkdir(dirname2(path), { recursive: true });
179
- const tmp = `${path}.tmp-${process.pid}`;
180
- await writeFile(tmp, `${JSON.stringify(cfg, null, 2)}
183
+ var saveChain = Promise.resolve();
184
+ function saveConfig(cfg, path = paths.configFile) {
185
+ const run = saveChain.then(async () => {
186
+ await mkdir(dirname2(path), { recursive: true });
187
+ const tmp = `${path}.tmp-${process.pid}-${randomUUID()}`;
188
+ await writeFile(tmp, `${JSON.stringify(cfg, null, 2)}
181
189
  `, "utf8");
182
- await chmod(tmp, 384);
183
- await rename(tmp, path);
190
+ await chmod(tmp, 384);
191
+ await rename(tmp, path);
192
+ });
193
+ saveChain = run.then(
194
+ () => void 0,
195
+ () => void 0
196
+ );
197
+ return run;
184
198
  }
185
199
 
186
200
  // src/config/bots.ts
@@ -228,6 +242,21 @@ function findBot(reg, nameOrAppId) {
228
242
  function currentBot(reg) {
229
243
  return reg.current ? reg.bots.find((b) => b.appId === reg.current) : void 0;
230
244
  }
245
+ function activeBots(reg) {
246
+ const configured = reg.bots.some((b) => b.active !== void 0);
247
+ if (configured) return reg.bots.filter((b) => b.active === true);
248
+ const cur = currentBot(reg);
249
+ return cur ? [cur] : [];
250
+ }
251
+ async function setActiveBots(appIds) {
252
+ const reg = await loadBots();
253
+ const want = new Set(appIds);
254
+ for (const b of reg.bots) b.active = want.has(b.appId);
255
+ const firstActive = reg.bots.find((b) => b.active);
256
+ if (firstActive) reg.current = firstActive.appId;
257
+ await saveBots(reg);
258
+ return reg;
259
+ }
231
260
  async function addBot(entry) {
232
261
  const reg = await loadBots();
233
262
  reg.bots = reg.bots.filter((b) => b.appId !== entry.appId);
@@ -236,11 +265,6 @@ async function addBot(entry) {
236
265
  await saveBots(reg);
237
266
  return reg;
238
267
  }
239
- async function setCurrent(appId) {
240
- const reg = await loadBots();
241
- reg.current = appId;
242
- await saveBots(reg);
243
- }
244
268
  async function removeBot(appId) {
245
269
  const reg = await loadBots();
246
270
  reg.bots = reg.bots.filter((b) => b.appId !== appId);
@@ -660,7 +684,7 @@ async function runRegistrationWizard() {
660
684
  accounts: { app: { id: result.client_id, secret: result.client_secret, tenant } }
661
685
  };
662
686
  if (operatorOpenId) {
663
- cfg.preferences = { access: { admins: [operatorOpenId] } };
687
+ cfg.preferences = { access: { ownerOpenId: operatorOpenId, admins: [operatorOpenId] } };
664
688
  console.log(` Admin: ${operatorOpenId} (\u4F60\u81EA\u5DF1\uFF0C\u5DF2\u81EA\u52A8\u52A0\u5165\u7BA1\u7406\u5458\u540D\u5355)`);
665
689
  } else {
666
690
  console.log(
@@ -717,7 +741,13 @@ var JOIN_GROUP_SCOPES = [
717
741
  "im:chat:readonly",
718
742
  "im:chat.members:write_only"
719
743
  ];
720
- var GRANT_SCOPES = [...REQUIRED_SCOPES, ...COMMENT_SCOPES, ...JOIN_GROUP_SCOPES];
744
+ var CONTACT_SCOPES = ["contact:user.base:readonly"];
745
+ var GRANT_SCOPES = [
746
+ ...REQUIRED_SCOPES,
747
+ ...COMMENT_SCOPES,
748
+ ...JOIN_GROUP_SCOPES,
749
+ ...CONTACT_SCOPES
750
+ ];
721
751
  var SCOPE_LABELS = {
722
752
  "im:message.group_at_msg:readonly": "\u63A5\u6536\u7FA4\u91CC @\u673A\u5668\u4EBA \u7684\u6D88\u606F",
723
753
  "im:message.group_msg": "\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\uFF08\u514D@\uFF09",
@@ -738,7 +768,8 @@ var SCOPE_LABELS = {
738
768
  "cardkit:card:write": "\u4EA4\u4E92\u6309\u94AE\u5361\u7247",
739
769
  "docs:document.comment:read": "\u8BFB\u53D6\u6587\u6863\u8BC4\u8BBA",
740
770
  "docs:document.comment:create": "\u53D1\u8868\u6587\u6863\u8BC4\u8BBA\u56DE\u590D",
741
- "wiki:wiki:readonly": "\u8BFB\u53D6\u77E5\u8BC6\u5E93\u8282\u70B9"
771
+ "wiki:wiki:readonly": "\u8BFB\u53D6\u77E5\u8BC6\u5E93\u8282\u70B9",
772
+ "contact:user.base:readonly": "\u8BFB\u53D6\u6210\u5458\u59D3\u540D\uFF08\u7BA1\u7406\u5458 / \u767D\u540D\u5355\u5C55\u793A\uFF09"
742
773
  };
743
774
  function labelScope(scope) {
744
775
  const label = SCOPE_LABELS[scope];
@@ -1026,8 +1057,14 @@ function ensureCodex() {
1026
1057
  async function ensureOnboarded(opts = {}) {
1027
1058
  if (!ensureCodex()) return null;
1028
1059
  const reg = await ensureRegistry();
1029
- const entry = currentBot(reg);
1060
+ const entry = opts.bot ? findBot(reg, opts.bot) : currentBot(reg);
1030
1061
  if (!entry) {
1062
+ if (opts.bot) {
1063
+ console.error(
1064
+ `\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\u300C${opts.bot}\u300D\u3002\u7528 \`feishu-codex-bridge bot list\` \u67E5\u770B\u5DF2\u6CE8\u518C\u7684\u673A\u5668\u4EBA\u3002`
1065
+ );
1066
+ return null;
1067
+ }
1031
1068
  if (!opts.allowCreate) {
1032
1069
  console.error("\u2717 \u5C1A\u672A\u914D\u7F6E\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u8BF7\u5148\u8FD0\u884C `feishu-codex-bridge bot init`\uFF08\u6216\u524D\u53F0 `run`\uFF09\u626B\u7801\u521B\u5EFA\u3002");
1033
1070
  return null;
@@ -2152,6 +2189,14 @@ function selectStatic(opts) {
2152
2189
  behaviors: [{ type: "callback", value: { a: opts.actionId } }]
2153
2190
  };
2154
2191
  }
2192
+ function selectMenu(opts) {
2193
+ return {
2194
+ tag: "select_static",
2195
+ name: opts.name,
2196
+ placeholder: { tag: "plain_text", content: opts.placeholder },
2197
+ options: opts.options.map((o) => ({ text: { tag: "plain_text", content: o.label }, value: o.value }))
2198
+ };
2199
+ }
2155
2200
 
2156
2201
  // src/card/command-cards.ts
2157
2202
  var MC = {
@@ -2259,19 +2304,13 @@ function pickerTime(unixSeconds) {
2259
2304
  function talkLine(noMention, tail) {
2260
2305
  return noMention ? `\xB7 \u76F4\u63A5\u53D1\u6D88\u606F\uFF08\u514D@\uFF09\u2192 ${tail}` : `\xB7 **@\u6211 + \u5185\u5BB9** \u2192 ${tail}\uFF08\u672C\u7FA4\u9ED8\u8BA4\u9700 @\uFF1B\`/settings\` \u53EF\u5F00\u542F\u514D@\uFF09`;
2261
2306
  }
2262
- function buildHelpCard(scope, noMention = true) {
2307
+ function buildHelpCard(scope, noMention = true, isAdmin2 = false) {
2263
2308
  const elements = [];
2264
2309
  if (scope === "single") {
2265
- elements.push(
2266
- md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"),
2267
- hr(),
2268
- md(
2269
- `${talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406")}
2270
- \xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
2271
- \xB7 \`/settings\` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09
2272
- \xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
2273
- )
2274
- );
2310
+ const lines = [talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"), "\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6"];
2311
+ if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
2312
+ lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
2313
+ elements.push(md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"), hr(), md(lines.join("\n")));
2275
2314
  } else if (scope === "topic") {
2276
2315
  elements.push(
2277
2316
  md("\u{1F9F5} **\u8BDD\u9898\u5185** \u2014 \u6BCF\u4E2A\u8BDD\u9898\u662F\u4E00\u4E2A\u72EC\u7ACB\u4F1A\u8BDD\u3002"),
@@ -2284,13 +2323,10 @@ function buildHelpCard(scope, noMention = true) {
2284
2323
  note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
2285
2324
  );
2286
2325
  } else {
2287
- elements.push(
2288
- md("\u{1F465} **\u4E3B\u7FA4\u533A** \u2014 @\u6211\u5F00\u8BDD\u9898\uFF0C\u6BCF\u4E2A\u8BDD\u9898\u662F\u72EC\u7ACB\u4F1A\u8BDD\u3002"),
2289
- hr(),
2290
- md(
2291
- "\xB7 **@\u6211 + \u5185\u5BB9** \u2192 \u5F00\u4E00\u4E2A\u65B0\u8BDD\u9898\u5E76\u5F00\u59CB\n\xB7 `/resume` \u2192 \u6062\u590D\u5386\u53F2\u4F1A\u8BDD\n\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09\n\xB7 `/model` \u2192 \u9700\u8981\u5728\u8BDD\u9898\u91CC\u7528\n\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361"
2292
- )
2293
- );
2326
+ const lines = ["\xB7 **@\u6211 + \u5185\u5BB9** \u2192 \u5F00\u4E00\u4E2A\u65B0\u8BDD\u9898\u5E76\u5F00\u59CB"];
2327
+ if (isAdmin2) lines.push("\xB7 `/resume` \u2192 \u6062\u590D\u5386\u53F2\u4F1A\u8BDD", "\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
2328
+ lines.push("\xB7 `/model` \u2192 \u9700\u8981\u5728\u8BDD\u9898\u91CC\u7528", "\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
2329
+ elements.push(md("\u{1F465} **\u4E3B\u7FA4\u533A** \u2014 @\u6211\u5F00\u8BDD\u9898\uFF0C\u6BCF\u4E2A\u8BDD\u9898\u662F\u72EC\u7ACB\u4F1A\u8BDD\u3002"), hr(), md(lines.join("\n")));
2294
2330
  }
2295
2331
  return card(elements, { header: { title: "\u{1F916} \u53EF\u7528\u547D\u4EE4", template: "blue" }, summary: "\u53EF\u7528\u547D\u4EE4" });
2296
2332
  }
@@ -3097,6 +3133,7 @@ async function uploadBuffer(channel, buffer) {
3097
3133
 
3098
3134
  // src/project/registry.ts
3099
3135
  import { mkdir as mkdir4, readFile as readFile6, rename as rename4, writeFile as writeFile4 } from "fs/promises";
3136
+ import { randomUUID as randomUUID2 } from "crypto";
3100
3137
  import { dirname as dirname5 } from "path";
3101
3138
  function defaultNoMention(p) {
3102
3139
  return !((p.origin ?? "created") === "joined" && (p.kind ?? "multi") === "single");
@@ -3112,9 +3149,18 @@ async function read() {
3112
3149
  throw err;
3113
3150
  }
3114
3151
  }
3152
+ var opChain = Promise.resolve();
3153
+ function withLock(fn) {
3154
+ const run = opChain.then(fn, fn);
3155
+ opChain = run.then(
3156
+ () => void 0,
3157
+ () => void 0
3158
+ );
3159
+ return run;
3160
+ }
3115
3161
  async function write(projects) {
3116
3162
  await mkdir4(dirname5(paths.projectsFile), { recursive: true });
3117
- const tmp = `${paths.projectsFile}.tmp-${process.pid}`;
3163
+ const tmp = `${paths.projectsFile}.tmp-${process.pid}-${randomUUID2()}`;
3118
3164
  const body = { version: FILE_VERSION2, projects };
3119
3165
  await writeFile4(tmp, `${JSON.stringify(body, null, 2)}
3120
3166
  `, "utf8");
@@ -3130,34 +3176,41 @@ async function getProjectByName(name) {
3130
3176
  return (await read()).find((p) => p.name === name);
3131
3177
  }
3132
3178
  async function addProject(p) {
3133
- const projects = await read();
3134
- if (projects.some((x) => x.name === p.name)) {
3135
- throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
3136
- }
3137
- if (p.chatId) {
3138
- const bound = projects.find((x) => x.chatId === p.chatId);
3139
- if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
3140
- }
3141
- projects.push(p);
3142
- await write(projects);
3179
+ return withLock(async () => {
3180
+ const projects = await read();
3181
+ if (projects.some((x) => x.name === p.name)) {
3182
+ throw new Error(`\u9879\u76EE\u540D\u300C${p.name}\u300D\u5DF2\u5B58\u5728`);
3183
+ }
3184
+ if (p.chatId) {
3185
+ const bound = projects.find((x) => x.chatId === p.chatId);
3186
+ if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
3187
+ }
3188
+ projects.push(p);
3189
+ await write(projects);
3190
+ });
3143
3191
  }
3144
3192
  async function updateProject(name, patch) {
3145
- const projects = await read();
3146
- const p = projects.find((x) => x.name === name);
3147
- if (!p) return;
3148
- const target = p;
3149
- for (const [k, v] of Object.entries(patch)) {
3150
- if (v !== void 0) target[k] = v;
3151
- }
3152
- await write(projects);
3193
+ return withLock(async () => {
3194
+ const projects = await read();
3195
+ const p = projects.find((x) => x.name === name);
3196
+ if (!p) return;
3197
+ const actual = typeof patch === "function" ? patch(p) : patch;
3198
+ const target = p;
3199
+ for (const [k, v] of Object.entries(actual)) {
3200
+ if (v !== void 0) target[k] = v;
3201
+ }
3202
+ await write(projects);
3203
+ });
3153
3204
  }
3154
3205
  async function removeProject(name) {
3155
- const projects = await read();
3156
- const idx = projects.findIndex((p) => p.name === name);
3157
- if (idx === -1) return void 0;
3158
- const [removed] = projects.splice(idx, 1);
3159
- await write(projects);
3160
- return removed;
3206
+ return withLock(async () => {
3207
+ const projects = await read();
3208
+ const idx = projects.findIndex((p) => p.name === name);
3209
+ if (idx === -1) return void 0;
3210
+ const [removed] = projects.splice(idx, 1);
3211
+ await write(projects);
3212
+ return removed;
3213
+ });
3161
3214
  }
3162
3215
 
3163
3216
  // src/card/dm-cards.ts
@@ -3182,7 +3235,19 @@ var DM = {
3182
3235
  setTools: "dm.set.tools",
3183
3236
  setWatchdog: "dm.set.watchdog",
3184
3237
  setPending: "dm.set.pending",
3185
- setConcurrency: "dm.set.concurrency"
3238
+ setConcurrency: "dm.set.concurrency",
3239
+ // 权限管理:全局 admins(settings 卡进入)+ 项目响应白名单(项目列表 / 建项目完成卡进入)
3240
+ admins: "dm.admins",
3241
+ addAdminForm: "dm.admin.addForm",
3242
+ addAdminSubmit: "dm.admin.addSubmit",
3243
+ rmAdmin: "dm.admin.rm",
3244
+ allowlist: "dm.allowlist",
3245
+ addAllowedForm: "dm.allow.addForm",
3246
+ addAllowedSubmit: "dm.allow.addSubmit",
3247
+ rmAllowed: "dm.allow.rm",
3248
+ // 项目设置容器(项目列表 / 建项目完成卡 进入),以后的项目级设置项往这里加
3249
+ projectSettings: "dm.projectSettings",
3250
+ setNoMentionDm: "dm.proj.noMention"
3186
3251
  };
3187
3252
  var GS = {
3188
3253
  setNoMention: "gs.noMention"
@@ -3448,7 +3513,13 @@ function buildNewProjectDoneCard(p) {
3448
3513
  note(`\u{1F4C2} \`${p.cwd}\` \xB7 ${kindLabel(p.kind)}`),
3449
3514
  md(p.chatId ? "\u{1F449} \u53BB\u7FA4\u91CC **@\u6211** \u5E72\u6D3B\u3002" : "\u53D1\u6211\u4EFB\u610F\u6D88\u606F\u53EF\u518D\u6B21\u6253\u5F00\u7BA1\u7406\u53F0\u3002")
3450
3515
  ];
3451
- if (p.chatId) elements.push(actions([linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId), "primary")]));
3516
+ if (p.chatId)
3517
+ elements.push(
3518
+ actions([
3519
+ linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId), "primary"),
3520
+ button("\u2699\uFE0F \u9879\u76EE\u8BBE\u7F6E", { a: DM.projectSettings, n: p.name })
3521
+ ])
3522
+ );
3452
3523
  return card(elements, { header: { title, template: "green" } });
3453
3524
  }
3454
3525
  function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
@@ -3479,6 +3550,7 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
3479
3550
  }
3480
3551
  const row = [];
3481
3552
  if (p.chatId) row.push(linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId)));
3553
+ row.push(button("\u2699\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: p.name }));
3482
3554
  row.push(button("\u{1F5D1} \u5220\u9664", { a: DM.rmConfirm, n: p.name }, "danger"));
3483
3555
  elements.push(actions(row));
3484
3556
  elements.push(hr());
@@ -3533,7 +3605,8 @@ function buildSettingsCard(cfg) {
3533
3605
  { label: "20", value: "20" }
3534
3606
  ]),
3535
3607
  note("\u26A0\uFE0F \u5047\u6B7B\u8D85\u65F6 / \u5E76\u53D1\u4E0A\u9650 \u6539\u540E\u9700**\u91CD\u542F**\u751F\u6548\uFF1B\u5DE5\u5177\u663E\u793A / \u8FD0\u884C\u4E2D\u65B0\u6D88\u606F \u5373\u65F6\u751F\u6548\u3002"),
3536
- actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
3608
+ hr(),
3609
+ actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
3537
3610
  ],
3538
3611
  { header: { title: "\u2699\uFE0F \u8BBE\u7F6E", template: "blue" } }
3539
3612
  );
@@ -3556,6 +3629,146 @@ function buildGroupSettingsCard(project) {
3556
3629
  { header: { title: "\u2699\uFE0F \u7FA4\u8BBE\u7F6E", template: "blue" } }
3557
3630
  );
3558
3631
  }
3632
+ function memberName(names, id) {
3633
+ return names.get(id) ?? `\u2026${id.slice(-6)}`;
3634
+ }
3635
+ function buildAdminsCard(cfg, names) {
3636
+ const owner = resolveOwner(cfg);
3637
+ const admins = cfg.preferences?.access?.admins ?? [];
3638
+ const elements = [md("**\u7BA1\u7406\u5458\u540D\u5355** \xB7 \u672C bot \u5168\u5C40\uFF08\u53EF\u79C1\u804A\u7BA1\u7406 / \u5EFA\u9879\u76EE / \u9500\u6BC1\u64CD\u4F5C\uFF09"), hr()];
3639
+ const seen = /* @__PURE__ */ new Set();
3640
+ if (owner) {
3641
+ seen.add(owner);
3642
+ elements.push(actions([md(`\u{1F451} **${memberName(names, owner)}** \xB7 Bot \u62E5\u6709\u8005\uFF08\u6CE8\u518C\u8005\uFF09`)]));
3643
+ }
3644
+ let extra = 0;
3645
+ for (const id of admins) {
3646
+ if (seen.has(id)) continue;
3647
+ seen.add(id);
3648
+ extra++;
3649
+ elements.push(actions([md(memberName(names, id)), button("\u{1F5D1} \u79FB\u9664", { a: DM.rmAdmin, u: id }, "danger")]));
3650
+ }
3651
+ if (extra === 0) elements.push(note("\u6682\u65E0\u989D\u5916\u7BA1\u7406\u5458\u3002"));
3652
+ elements.push(
3653
+ hr(),
3654
+ actions([button("\u2795 \u6DFB\u52A0\u7BA1\u7406\u5458", { a: DM.addAdminForm }, "primary"), button("\u2B05\uFE0F \u8BBE\u7F6E", { a: DM.settings })]),
3655
+ note("\u{1F451} Bot \u62E5\u6709\u8005\uFF08\u6CE8\u518C\u6B64 bot \u7684\u4EBA\uFF09\u6052\u4E3A\u7BA1\u7406\u5458\uFF0C\u4E0D\u53EF\u79FB\u9664\uFF1B\u540D\u5355\u4E3A\u7A7A\u65F6\u4EC5\u62E5\u6709\u8005\u53EF\u7BA1\u7406\u3002")
3656
+ );
3657
+ return card(elements, { header: { title: "\u{1F46E} \u7BA1\u7406\u5458", template: "blue" } });
3658
+ }
3659
+ function buildAddAdminCard(members) {
3660
+ const MAX = 50;
3661
+ const shown = members.slice(0, MAX);
3662
+ const formEls = [];
3663
+ if (shown.length > 0) {
3664
+ formEls.push(
3665
+ selectMenu({
3666
+ name: "pick",
3667
+ placeholder: "\u4ECE\u9879\u76EE\u7FA4\u6210\u5458\u9009\u62E9",
3668
+ options: shown.map((m) => ({ label: m.name, value: m.openId }))
3669
+ })
3670
+ );
3671
+ }
3672
+ formEls.push(
3673
+ input({
3674
+ name: "open_id",
3675
+ label: shown.length ? "\u6216\u76F4\u63A5\u8F93\u5165 open_id" : "\u8F93\u5165 open_id\uFF08\u672A\u8BFB\u53D6\u5230\u9879\u76EE\u7FA4\u6210\u5458\uFF09",
3676
+ placeholder: "ou_xxx"
3677
+ }),
3678
+ actions([submitButton("\u2705 \u786E\u8BA4\u6DFB\u52A0", { a: DM.addAdminSubmit }, "primary", "submit_admin")])
3679
+ );
3680
+ const tail = [];
3681
+ if (members.length > MAX) tail.push(note(`\u5019\u9009\u8F83\u591A\uFF0C\u4EC5\u5217\u524D ${MAX} \u4E2A\uFF1B\u5176\u4F59\u8BF7\u76F4\u63A5\u8F93\u5165 open_id\u3002`));
3682
+ return card(
3683
+ [
3684
+ md("**\u6DFB\u52A0\u7BA1\u7406\u5458** \xB7 \u4ECE\u9879\u76EE\u7FA4\u6210\u5458\u9009\uFF0C\u6216\u8F93\u5165 open_id"),
3685
+ form("add_admin", formEls),
3686
+ ...tail,
3687
+ actions([button("\u2B05\uFE0F \u53D6\u6D88", { a: DM.admins })])
3688
+ ],
3689
+ { header: { title: "\u2795 \u6DFB\u52A0\u7BA1\u7406\u5458", template: "blue" } }
3690
+ );
3691
+ }
3692
+ function buildProjectSettingsCard(project) {
3693
+ const kind = project.kind ?? "multi";
3694
+ const noMention = project.noMention ?? defaultNoMention(project);
3695
+ return card(
3696
+ [
3697
+ md(`**\u9879\u76EE\u8BBE\u7F6E** \xB7 ${project.name}`),
3698
+ note(`${kindLabel(kind)}${project.cwd ? ` \xB7 \u{1F4C2} \`${project.cwd}\`` : ""}`),
3699
+ hr(),
3700
+ md("\u270B \u514D@\uFF08\u4E0D\u7528 @ \u4E5F\u56DE\u590D\uFF09"),
3701
+ actions([
3702
+ button("\u5F00", { a: DM.setNoMentionDm, v: "on", n: project.name }, noMention ? "primary" : "default"),
3703
+ button("\u5173", { a: DM.setNoMentionDm, v: "off", n: project.name }, noMention ? "default" : "primary")
3704
+ ]),
3705
+ note(
3706
+ kind === "single" ? "\u5F00\u542F\u540E\uFF1A\u672C\u7FA4\u6240\u6709\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u4EA4\u7ED9\u6211\u5904\u7406\u3002" : "\u5F00\u542F\u540E\uFF1A\u8BDD\u9898\u5185\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u5904\u7406\uFF1B**\u5F00\u65B0\u8BDD\u9898\u4ECD\u9700 @\u6211**\u3002"
3707
+ ),
3708
+ hr(),
3709
+ actions([button("\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", { a: DM.allowlist, n: project.name }, "primary")]),
3710
+ note("\u8BBE\u7F6E\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex\uFF08\u7A7A = \u6240\u6709\u4EBA\uFF09\u3002"),
3711
+ hr(),
3712
+ actions([button("\u2B05\uFE0F \u9879\u76EE\u5217\u8868", { a: DM.projects })])
3713
+ ],
3714
+ { header: { title: "\u2699\uFE0F \u9879\u76EE\u8BBE\u7F6E", template: "blue" } }
3715
+ );
3716
+ }
3717
+ function buildAllowlistCard(project, names) {
3718
+ const list = project.allowedUsers ?? [];
3719
+ const elements = [md(`**\u54CD\u5E94\u767D\u540D\u5355** \xB7 ${project.name}`), note("\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex"), hr()];
3720
+ if (list.length === 0) {
3721
+ elements.push(note("\u5F53\u524D**\u6240\u6709\u4EBA**\u53EF\u7528\uFF08\u7BA1\u7406\u5458\u59CB\u7EC8\u53EF\u7528\uFF09\u3002"));
3722
+ } else {
3723
+ for (const id of list) {
3724
+ elements.push(
3725
+ actions([md(memberName(names, id)), button("\u{1F5D1} \u79FB\u9664", { a: DM.rmAllowed, u: id, n: project.name }, "danger")])
3726
+ );
3727
+ }
3728
+ }
3729
+ elements.push(
3730
+ hr(),
3731
+ actions([
3732
+ button("\u2795 \u6DFB\u52A0", { a: DM.addAllowedForm, n: project.name }, "primary"),
3733
+ button("\u2B05\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: project.name })
3734
+ ]),
3735
+ note("\u7BA1\u7406\u5458\u59CB\u7EC8\u53EF\u7528\uFF0C\u4E0D\u53D7\u6B64\u540D\u5355\u9650\u5236\uFF1B\u540D\u5355\u4E3A\u7A7A = \u6240\u6709\u4EBA\u53EF\u7528\u3002")
3736
+ );
3737
+ return card(elements, { header: { title: "\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", template: "blue" } });
3738
+ }
3739
+ function buildAddAllowedCard(projectName, members) {
3740
+ const MAX = 50;
3741
+ const shown = members.slice(0, MAX);
3742
+ const formEls = [];
3743
+ if (shown.length > 0) {
3744
+ formEls.push(
3745
+ selectMenu({
3746
+ name: "pick",
3747
+ placeholder: "\u4ECE\u7FA4\u6210\u5458\u9009\u62E9",
3748
+ options: shown.map((m) => ({ label: m.name, value: m.openId }))
3749
+ })
3750
+ );
3751
+ }
3752
+ formEls.push(
3753
+ input({
3754
+ name: "open_id",
3755
+ label: shown.length ? "\u6216\u76F4\u63A5\u8F93\u5165 open_id" : "\u8F93\u5165 open_id\uFF08\u672A\u8BFB\u53D6\u5230\u7FA4\u6210\u5458\uFF09",
3756
+ placeholder: "ou_xxx"
3757
+ }),
3758
+ actions([submitButton("\u2705 \u786E\u8BA4\u6DFB\u52A0", { a: DM.addAllowedSubmit, n: projectName }, "primary", "submit_allowed")])
3759
+ );
3760
+ const tail = [];
3761
+ if (members.length > MAX) tail.push(note(`\u7FA4\u6210\u5458\u8F83\u591A\uFF0C\u4EC5\u5217\u524D ${MAX} \u4E2A\uFF1B\u5176\u4F59\u8BF7\u76F4\u63A5\u8F93\u5165 open_id\u3002`));
3762
+ return card(
3763
+ [
3764
+ md(`**\u6DFB\u52A0\u53EF\u4F7F\u7528\u300C${projectName}\u300D\u7684\u4EBA**`),
3765
+ form("add_allowed", formEls),
3766
+ ...tail,
3767
+ actions([button("\u2B05\uFE0F \u53D6\u6D88", { a: DM.allowlist, n: projectName })])
3768
+ ],
3769
+ { header: { title: "\u2795 \u6DFB\u52A0\u767D\u540D\u5355\u6210\u5458", template: "blue" } }
3770
+ );
3771
+ }
3559
3772
 
3560
3773
  // src/service/update.ts
3561
3774
  import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
@@ -4829,6 +5042,59 @@ var Semaphore = class {
4829
5042
  };
4830
5043
 
4831
5044
  // src/bot/handle-message.ts
5045
+ async function resolveNames(channel, ids) {
5046
+ const uniq = [...new Set(ids.filter((x) => Boolean(x)))];
5047
+ const out = /* @__PURE__ */ new Map();
5048
+ if (uniq.length === 0) return out;
5049
+ try {
5050
+ const r = await channel.rawClient.contact.v3.user.batch({
5051
+ params: { user_ids: uniq, user_id_type: "open_id" }
5052
+ });
5053
+ for (const it of r.data?.items ?? []) {
5054
+ if (it.open_id && it.name) out.set(it.open_id, it.name);
5055
+ }
5056
+ } catch (err) {
5057
+ log.info("console", "resolve-names-fail", { n: uniq.length, err: String(err) });
5058
+ }
5059
+ return out;
5060
+ }
5061
+ async function fetchChatMembers(channel, chatId) {
5062
+ try {
5063
+ const r = await channel.rawClient.im.v1.chatMembers.get({
5064
+ path: { chat_id: chatId },
5065
+ params: { member_id_type: "open_id", page_size: 100 }
5066
+ });
5067
+ const out = [];
5068
+ for (const it of r.data?.items ?? []) {
5069
+ if (it.member_id) out.push({ openId: it.member_id, name: it.name || `\u2026${it.member_id.slice(-6)}` });
5070
+ }
5071
+ return out;
5072
+ } catch (err) {
5073
+ log.info("console", "fetch-members-fail", { chatId: chatId.slice(-6), err: String(err) });
5074
+ return [];
5075
+ }
5076
+ }
5077
+ async function fetchAllProjectMembers(channel) {
5078
+ const projects = await listProjects();
5079
+ const lists = await Promise.all(projects.filter((p) => p.chatId).map((p) => fetchChatMembers(channel, p.chatId)));
5080
+ const seen = /* @__PURE__ */ new Map();
5081
+ for (const members of lists) {
5082
+ for (const m of members) if (!seen.has(m.openId)) seen.set(m.openId, m.name);
5083
+ }
5084
+ return [...seen].map(([openId, name]) => ({ openId, name }));
5085
+ }
5086
+ function pickOpenId(formValue) {
5087
+ const raw = formValue?.pick;
5088
+ const cands = Array.isArray(raw) ? raw : [raw];
5089
+ for (const c of cands) {
5090
+ if (typeof c === "string" && c.startsWith("ou_")) return c;
5091
+ if (c && typeof c === "object") {
5092
+ const o = c;
5093
+ for (const v of [o.open_id, o.id, o.value]) if (typeof v === "string" && v.startsWith("ou_")) return v;
5094
+ }
5095
+ }
5096
+ return void 0;
5097
+ }
4832
5098
  function createOrchestrator(channel, cfg, fallbackCwd) {
4833
5099
  const backend = createBackend();
4834
5100
  const sessions = /* @__PURE__ */ new Map();
@@ -4906,7 +5172,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
4906
5172
  }
4907
5173
  const project = await getProjectByChatId(msg.chatId);
4908
5174
  if (!msg.mentionedBot && !(project && shouldRespondWithoutMention(project, msg))) return;
4909
- if (!isChatAllowed(cfg, msg.chatId) || !isUserAllowed(cfg, msg.senderId)) {
5175
+ if (!isChatAllowed(cfg, msg.chatId) || !isUserAllowedInProject(cfg, project, msg.senderId)) {
4910
5176
  log.info("intake", "reject", { reason: "not_allowed", chatId: msg.chatId.slice(-6) });
4911
5177
  return;
4912
5178
  }
@@ -4980,9 +5246,13 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
4980
5246
  if ((project.kind ?? "multi") === "single") return true;
4981
5247
  return Boolean(msg.threadId) || parseCommand(msg.content.trim()) !== null;
4982
5248
  }
5249
+ async function denyAdminCommand(msg, cmd) {
5250
+ await channel.send(msg.chatId, { markdown: `\u26A0\uFE0F \`/${cmd}\` \u4EC5 bot \u7BA1\u7406\u5458\u53EF\u7528\u3002` }, { replyTo: msg.messageId }).catch(() => void 0);
5251
+ log.info("intake", "cmd-denied", { cmd });
5252
+ }
4983
5253
  async function postGroupSettings(msg, project) {
4984
5254
  if (!isAdmin(cfg, msg.senderId)) {
4985
- await channel.send(msg.chatId, { markdown: "\u4EC5\u7BA1\u7406\u5458\u53EF\u6539\u7FA4\u8BBE\u7F6E\u3002" }, { replyTo: msg.messageId }).catch(() => void 0);
5255
+ await denyAdminCommand(msg, "settings");
4986
5256
  return;
4987
5257
  }
4988
5258
  if (!project) {
@@ -5134,6 +5404,10 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
5134
5404
  }).catch((err) => log.fail("intake", err));
5135
5405
  }
5136
5406
  async function postResumeCard(msg) {
5407
+ if (!isAdmin(cfg, msg.senderId)) {
5408
+ await denyAdminCommand(msg, "resume");
5409
+ return;
5410
+ }
5137
5411
  await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
5138
5412
  const project = await getProjectByChatId(msg.chatId);
5139
5413
  const cwd = project?.cwd ?? fallbackCwd;
@@ -5175,7 +5449,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
5175
5449
  async function postHelpCard(msg, scope, inThread = false, project) {
5176
5450
  const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
5177
5451
  await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
5178
- await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention), msg.messageId, inThread).catch(
5452
+ await sendManagedCard(channel, msg.chatId, buildHelpCard(scope, noMention, isAdmin(cfg, msg.senderId)), msg.messageId, inThread).catch(
5179
5453
  (err) => log.fail("card", err, { cmd: "help", scope })
5180
5454
  );
5181
5455
  log.info("card", "help", { scope });
@@ -5214,7 +5488,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
5214
5488
  return void 0;
5215
5489
  }
5216
5490
  const op = evt.operator?.openId ?? "";
5217
- if (op !== state.requesterOpenId || !isChatAllowed(cfg, state.chatId) || !isUserAllowed(cfg, op)) {
5491
+ if (op !== state.requesterOpenId || !isChatAllowed(cfg, state.chatId)) {
5218
5492
  log.info("card", "action-denied", { reason: "not-allowed" });
5219
5493
  return void 0;
5220
5494
  }
@@ -5250,7 +5524,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
5250
5524
  settleUpdate(evt.messageId, buildResumeLaunchingCard(state));
5251
5525
  void resumeFromCard(evt, state, codexThreadId);
5252
5526
  });
5253
- const runAllowed = (evt) => isChatAllowed(cfg, evt.chatId) && isUserAllowed(cfg, evt.operator?.openId ?? "");
5527
+ const runAllowed = (evt) => isChatAllowed(cfg, evt.chatId);
5254
5528
  const runOwnerOrAdmin = (evt, ownerOpenId) => {
5255
5529
  if (!runAllowed(evt)) return false;
5256
5530
  const op = evt.operator?.openId ?? "";
@@ -5265,6 +5539,15 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
5265
5539
  });
5266
5540
  const dmAdmin = (openId) => isAdmin(cfg, openId ?? "");
5267
5541
  const patch = (evt, c) => settleUpdate(evt.messageId, c, evt.chatId);
5542
+ const namesWithOperator = async (evt, ids) => {
5543
+ const m = await resolveNames(channel, ids);
5544
+ if (ids.some((id) => id && !m.has(id))) {
5545
+ for (const mem of await fetchAllProjectMembers(channel)) if (!m.has(mem.openId)) m.set(mem.openId, mem.name);
5546
+ }
5547
+ const op = evt.operator;
5548
+ if (op?.openId && op.name && !m.has(op.openId)) m.set(op.openId, op.name);
5549
+ return m;
5550
+ };
5268
5551
  function applyPref(evt, mut) {
5269
5552
  if (!dmAdmin(evt.operator?.openId)) return;
5270
5553
  const prefs = { ...cfg.preferences ?? {} };
@@ -5488,6 +5771,107 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
5488
5771
  }
5489
5772
  return buildGroupSettingsCard({ name: "\u672C\u7FA4", kind: "multi", noMention: on });
5490
5773
  });
5774
+ }).on(DM.admins, ({ evt }) => {
5775
+ if (!dmAdmin(evt.operator?.openId)) return;
5776
+ patch(
5777
+ evt,
5778
+ async () => buildAdminsCard(cfg, await namesWithOperator(evt, [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []]))
5779
+ );
5780
+ }).on(DM.addAdminForm, ({ evt }) => {
5781
+ if (!dmAdmin(evt.operator?.openId)) return;
5782
+ patch(evt, async () => {
5783
+ const all = await fetchAllProjectMembers(channel);
5784
+ const members = all.filter((m) => !isAdmin(cfg, m.openId));
5785
+ return buildAddAdminCard(members);
5786
+ });
5787
+ }).on(DM.addAdminSubmit, ({ evt, formValue }) => {
5788
+ if (!dmAdmin(evt.operator?.openId)) return;
5789
+ const manual = String(formValue?.open_id ?? "").trim();
5790
+ const id = manual.startsWith("ou_") ? manual : pickOpenId(formValue);
5791
+ log.info("console", "admin-add", { picked: id?.slice(-6) ?? null });
5792
+ void (async () => {
5793
+ if (id) {
5794
+ const access = { ...cfg.preferences?.access ?? {} };
5795
+ access.ownerOpenId ??= resolveOwner(cfg);
5796
+ access.admins = Array.from(/* @__PURE__ */ new Set([...access.admins ?? [], id]));
5797
+ cfg.preferences = { ...cfg.preferences ?? {}, access };
5798
+ await saveConfig(cfg).catch((e) => log.fail("console", e, { phase: "save-config" }));
5799
+ }
5800
+ const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
5801
+ const next = buildAdminsCard(cfg, await namesWithOperator(evt, ids));
5802
+ await sendManagedCard(channel, evt.chatId, next).catch((e) => log.fail("console", e, { phase: "admin-add-result" }));
5803
+ })();
5804
+ }).on(DM.rmAdmin, ({ evt, value }) => {
5805
+ if (!dmAdmin(evt.operator?.openId)) return;
5806
+ const id = typeof value.u === "string" ? value.u : "";
5807
+ patch(evt, async () => {
5808
+ if (id && id !== resolveOwner(cfg)) {
5809
+ const access = { ...cfg.preferences?.access ?? {} };
5810
+ access.ownerOpenId ??= resolveOwner(cfg);
5811
+ access.admins = (access.admins ?? []).filter((x) => x !== id);
5812
+ cfg.preferences = { ...cfg.preferences ?? {}, access };
5813
+ await saveConfig(cfg).catch((e) => log.fail("console", e, { phase: "save-config" }));
5814
+ }
5815
+ const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
5816
+ return buildAdminsCard(cfg, await namesWithOperator(evt, ids));
5817
+ });
5818
+ }).on(DM.allowlist, ({ evt, value }) => {
5819
+ if (!dmAdmin(evt.operator?.openId)) return;
5820
+ const name = typeof value.n === "string" ? value.n : "";
5821
+ patch(evt, async () => {
5822
+ const p = await getProjectByName(name);
5823
+ if (!p) return buildDmMenuCard();
5824
+ return buildAllowlistCard(p, await namesWithOperator(evt, p.allowedUsers ?? []));
5825
+ });
5826
+ }).on(DM.addAllowedForm, ({ evt, value }) => {
5827
+ if (!dmAdmin(evt.operator?.openId)) return;
5828
+ const name = typeof value.n === "string" ? value.n : "";
5829
+ if (!name) return;
5830
+ patch(evt, async () => {
5831
+ const p = await getProjectByName(name);
5832
+ const members = p?.chatId ? await fetchChatMembers(channel, p.chatId) : [];
5833
+ return buildAddAllowedCard(name, members);
5834
+ });
5835
+ }).on(DM.addAllowedSubmit, ({ evt, value, formValue }) => {
5836
+ if (!dmAdmin(evt.operator?.openId)) return;
5837
+ const name = typeof value.n === "string" ? value.n : "";
5838
+ const manual = String(formValue?.open_id ?? "").trim();
5839
+ const id = manual.startsWith("ou_") ? manual : pickOpenId(formValue);
5840
+ log.info("console", "allow-add", { project: name, picked: id?.slice(-6) ?? null });
5841
+ void (async () => {
5842
+ if (id) await updateProject(name, (p) => ({ allowedUsers: Array.from(/* @__PURE__ */ new Set([...p.allowedUsers ?? [], id])) }));
5843
+ const fresh = await getProjectByName(name);
5844
+ if (!fresh) return;
5845
+ const card2 = buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
5846
+ await sendManagedCard(channel, evt.chatId, card2).catch((e) => log.fail("console", e, { phase: "allow-add-result" }));
5847
+ })();
5848
+ }).on(DM.rmAllowed, ({ evt, value }) => {
5849
+ if (!dmAdmin(evt.operator?.openId)) return;
5850
+ const id = typeof value.u === "string" ? value.u : "";
5851
+ const name = typeof value.n === "string" ? value.n : "";
5852
+ patch(evt, async () => {
5853
+ await updateProject(name, (p) => ({ allowedUsers: (p.allowedUsers ?? []).filter((x) => x !== id) }));
5854
+ const fresh = await getProjectByName(name);
5855
+ if (!fresh) return buildDmMenuCard();
5856
+ return buildAllowlistCard(fresh, await namesWithOperator(evt, fresh.allowedUsers ?? []));
5857
+ });
5858
+ }).on(DM.projectSettings, ({ evt, value }) => {
5859
+ if (!dmAdmin(evt.operator?.openId)) return;
5860
+ const name = typeof value.n === "string" ? value.n : "";
5861
+ patch(evt, async () => {
5862
+ const p = await getProjectByName(name);
5863
+ return p ? buildProjectSettingsCard(p) : buildDmMenuCard();
5864
+ });
5865
+ }).on(DM.setNoMentionDm, ({ evt, value }) => {
5866
+ if (!dmAdmin(evt.operator?.openId)) return;
5867
+ const name = typeof value.n === "string" ? value.n : "";
5868
+ const on = value.v === "on";
5869
+ patch(evt, async () => {
5870
+ const p = await getProjectByName(name);
5871
+ if (!p) return buildDmMenuCard();
5872
+ await updateProject(name, { noMention: on });
5873
+ return buildProjectSettingsCard({ ...p, noMention: on });
5874
+ });
5491
5875
  });
5492
5876
  async function resumeFromCard(evt, state, codexThreadId) {
5493
5877
  try {
@@ -5737,8 +6121,6 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
5737
6121
  if (!evt.mentionedBot) return log.info("comment", "skip", { reason: "not-mentioned" });
5738
6122
  if (!SUPPORTED_FILE_TYPES.has(evt.fileType))
5739
6123
  return log.info("comment", "skip", { reason: "unsupported-fileType", fileType: evt.fileType });
5740
- if (!isUserAllowed(cfg, evt.operator.openId))
5741
- return log.info("comment", "skip", { reason: "not-allowed" });
5742
6124
  const resolved = await resolveComment(channel, evt);
5743
6125
  if (!resolved) return log.info("comment", "skip", { reason: "no-target-or-empty" });
5744
6126
  const { target, ctx } = resolved;
@@ -5956,6 +6338,94 @@ async function startBridge(opts) {
5956
6338
  return { channel, shutdown };
5957
6339
  }
5958
6340
 
6341
+ // src/bot/supervisor.ts
6342
+ var BACKOFF_MIN_MS = 1e3;
6343
+ var BACKOFF_MAX_MS = 3e4;
6344
+ var HEALTHY_UPTIME_MS = 6e4;
6345
+ var SHUTDOWN_GRACE_MS = 8e3;
6346
+ async function runSupervisor(bots) {
6347
+ const cliEntry = process.argv[1];
6348
+ if (!cliEntry) throw new Error("supervisor: \u65E0\u6CD5\u89E3\u6790 CLI \u5165\u53E3\uFF08process.argv[1] \u4E3A\u7A7A\uFF09");
6349
+ const childEnv = { ...process.env };
6350
+ delete childEnv[SERVICE_ENV_FLAG];
6351
+ let shuttingDown = false;
6352
+ const children = bots.map((bot2) => ({ bot: bot2, backoffMs: BACKOFF_MIN_MS, startedAt: 0 }));
6353
+ console.log(`
6354
+ \u6B63\u5728\u542F\u52A8 ${bots.length} \u4E2A\u673A\u5668\u4EBA\uFF08\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF09\uFF1A`);
6355
+ for (const b of bots) console.log(` \u2022 ${b.name} (${b.appId}) [${b.tenant}]`);
6356
+ console.log("Ctrl+C \u9000\u51FA\uFF08\u5173\u95ED\u5168\u90E8\uFF09\u3002\n");
6357
+ const prefixPipe = (name, src, dst) => {
6358
+ if (!src) return;
6359
+ let buf = "";
6360
+ src.setEncoding("utf8");
6361
+ src.on("data", (chunk) => {
6362
+ buf += chunk;
6363
+ let nl;
6364
+ while ((nl = buf.indexOf("\n")) >= 0) {
6365
+ const line = buf.slice(0, nl);
6366
+ buf = buf.slice(nl + 1);
6367
+ dst.write(`\x1B[2m[${name}]\x1B[0m ${line}
6368
+ `);
6369
+ }
6370
+ });
6371
+ src.on("end", () => {
6372
+ if (buf) dst.write(`\x1B[2m[${name}]\x1B[0m ${buf}
6373
+ `);
6374
+ });
6375
+ };
6376
+ const spawnChild = (c) => {
6377
+ c.startedAt = Date.now();
6378
+ const proc = spawnProcess(process.execPath, [cliEntry, "run", "--bot", c.bot.appId], {
6379
+ stdio: ["ignore", "pipe", "pipe"],
6380
+ env: childEnv
6381
+ });
6382
+ c.proc = proc;
6383
+ log.info("supervisor", "child-start", { bot: c.bot.name, appId: c.bot.appId, pid: proc.pid ?? null });
6384
+ prefixPipe(c.bot.name, proc.stdout, process.stdout);
6385
+ prefixPipe(c.bot.name, proc.stderr, process.stderr);
6386
+ proc.on("exit", (code, signal) => {
6387
+ c.proc = void 0;
6388
+ if (shuttingDown) return;
6389
+ const uptime = Date.now() - c.startedAt;
6390
+ if (uptime >= HEALTHY_UPTIME_MS) c.backoffMs = BACKOFF_MIN_MS;
6391
+ const wait = c.backoffMs;
6392
+ c.backoffMs = Math.min(c.backoffMs * 2, BACKOFF_MAX_MS);
6393
+ log.warn("supervisor", "child-exit", { bot: c.bot.name, code, signal, restartInMs: wait });
6394
+ console.error(
6395
+ `\x1B[2m[${c.bot.name}]\x1B[0m \u8FDB\u7A0B\u9000\u51FA\uFF08code=${code ?? signal ?? "?"}\uFF09\uFF0C${Math.round(wait / 1e3)}s \u540E\u91CD\u542F\u2026`
6396
+ );
6397
+ c.restartTimer = setTimeout(() => spawnChild(c), wait);
6398
+ });
6399
+ proc.on("error", (err) => {
6400
+ log.fail("supervisor", err, { bot: c.bot.name, phase: "spawn" });
6401
+ });
6402
+ };
6403
+ for (const c of children) spawnChild(c);
6404
+ await new Promise((resolve7) => {
6405
+ const stop = (sig) => {
6406
+ if (shuttingDown) return;
6407
+ shuttingDown = true;
6408
+ console.log(`
6409
+ \u6536\u5230 ${sig}\uFF0C\u6B63\u5728\u5173\u95ED\u5168\u90E8\u673A\u5668\u4EBA\u2026`);
6410
+ for (const c of children) {
6411
+ if (c.restartTimer) clearTimeout(c.restartTimer);
6412
+ c.proc?.kill("SIGTERM");
6413
+ }
6414
+ const deadline = Date.now() + SHUTDOWN_GRACE_MS;
6415
+ const poll = setInterval(() => {
6416
+ const alive = children.filter((c) => c.proc && !c.proc.killed);
6417
+ if (alive.length === 0 || Date.now() >= deadline) {
6418
+ clearInterval(poll);
6419
+ for (const c of alive) c.proc?.kill("SIGKILL");
6420
+ resolve7();
6421
+ }
6422
+ }, 200);
6423
+ };
6424
+ for (const sig of ["SIGINT", "SIGTERM"]) process.once(sig, () => stop(sig));
6425
+ });
6426
+ process.exit(0);
6427
+ }
6428
+
5959
6429
  // src/core/single-instance.ts
5960
6430
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
5961
6431
  import { dirname as dirname11 } from "path";
@@ -6003,8 +6473,20 @@ function acquireSingleInstanceLock(appId) {
6003
6473
  }
6004
6474
 
6005
6475
  // src/cli/commands/run.ts
6006
- async function runRun() {
6007
- const ready = await ensureOnboarded({ allowCreate: true });
6476
+ async function runRun(botName) {
6477
+ if (botName) {
6478
+ await runSingle(botName);
6479
+ return;
6480
+ }
6481
+ const active = activeBots(await loadBots());
6482
+ if (active.length > 1) {
6483
+ await runSupervisor(active);
6484
+ return;
6485
+ }
6486
+ await runSingle(active[0]?.name);
6487
+ }
6488
+ async function runSingle(botName) {
6489
+ const ready = await ensureOnboarded({ allowCreate: !botName, bot: botName });
6008
6490
  if (!ready) {
6009
6491
  process.exitCode = 1;
6010
6492
  return;
@@ -6047,14 +6529,37 @@ async function runRun() {
6047
6529
 
6048
6530
  // src/cli/commands/daemon.ts
6049
6531
  async function runStart() {
6050
- const ready = await ensureOnboarded({ allowCreate: true });
6051
- if (!ready) {
6052
- process.exitCode = 1;
6053
- return;
6054
- }
6055
- if (!await confirmReadyForDaemon(ready)) {
6056
- process.exitCode = 1;
6057
- return;
6532
+ const active = activeBots(await loadBots());
6533
+ if (active.length === 0) {
6534
+ const ready = await ensureOnboarded({ allowCreate: true });
6535
+ if (!ready) {
6536
+ process.exitCode = 1;
6537
+ return;
6538
+ }
6539
+ if (!await confirmReadyForDaemon(ready)) {
6540
+ process.exitCode = 1;
6541
+ return;
6542
+ }
6543
+ } else {
6544
+ if (active.length > 1) {
6545
+ console.log(`
6546
+ \u540E\u53F0\u670D\u52A1\u5C06\u6258\u7BA1 ${active.length} \u4E2A\u673A\u5668\u4EBA\uFF08supervisor \u591A\u8FDB\u7A0B\uFF0C\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF09\uFF1A`);
6547
+ for (const b of active) console.log(` \u2022 ${b.name} (${b.appId}) [${b.tenant}]`);
6548
+ console.log("");
6549
+ }
6550
+ for (const bot2 of active) {
6551
+ if (active.length > 1) console.log(`
6552
+ \u2500\u2500\u2500\u2500 \u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId}) \u2500\u2500\u2500\u2500`);
6553
+ const ready = await ensureOnboarded({ bot: bot2.appId });
6554
+ if (!ready) {
6555
+ process.exitCode = 1;
6556
+ return;
6557
+ }
6558
+ if (!await confirmReadyForDaemon(ready)) {
6559
+ process.exitCode = 1;
6560
+ return;
6561
+ }
6562
+ }
6058
6563
  }
6059
6564
  const status = await getServiceAdapter().install();
6060
6565
  console.log(installedNote());
@@ -6151,6 +6656,98 @@ async function runUpdate(opts = {}) {
6151
6656
 
6152
6657
  // src/cli/commands/bot.ts
6153
6658
  import { rm as rm5 } from "fs/promises";
6659
+
6660
+ // src/cli/checkbox.ts
6661
+ import { emitKeypressEvents } from "readline";
6662
+ var ESC = "\x1B";
6663
+ var HIDE_CURSOR = `${ESC}[?25l`;
6664
+ var SHOW_CURSOR = `${ESC}[?25h`;
6665
+ var ALT_SCREEN_ON = `${ESC}[?1049h`;
6666
+ var ALT_SCREEN_OFF = `${ESC}[?1049l`;
6667
+ var HOME_AND_CLEAR = `${ESC}[H${ESC}[2J`;
6668
+ async function checkboxSelect(title, items) {
6669
+ const input2 = process.stdin;
6670
+ const output = process.stdout;
6671
+ if (!input2.isTTY) throw new Error("checkboxSelect requires an interactive TTY");
6672
+ const checked = items.map((it) => Boolean(it.checked));
6673
+ let cursor = 0;
6674
+ const frame = () => {
6675
+ const lines = [title, ""];
6676
+ items.forEach((it, i) => {
6677
+ const box = checked[i] ? "\x1B[32m[x]\x1B[0m" : "[ ]";
6678
+ const pointer = i === cursor ? "\x1B[36m>\x1B[0m" : " ";
6679
+ const label = i === cursor ? `\x1B[1m${it.label}\x1B[0m` : it.label;
6680
+ const hint = it.hint ? ` \x1B[2m${it.hint}\x1B[0m` : "";
6681
+ lines.push(`${pointer} ${box} ${label}${hint}`);
6682
+ });
6683
+ const n = checked.filter(Boolean).length;
6684
+ lines.push("");
6685
+ lines.push(`\x1B[2m${n} \u4E2A\u5DF2\u52FE\u9009 \xB7 \u2191\u2193 \u79FB\u52A8 \xB7 \u7A7A\u683C\u52FE\u9009 \xB7 a \u5168\u9009 \xB7 \u56DE\u8F66\u786E\u8BA4 \xB7 q \u53D6\u6D88\x1B[0m`);
6686
+ return lines.join("\r\n");
6687
+ };
6688
+ const redraw = () => {
6689
+ output.write(HOME_AND_CLEAR + frame());
6690
+ };
6691
+ emitKeypressEvents(input2);
6692
+ const wasRaw = Boolean(input2.isRaw);
6693
+ let restored = false;
6694
+ const restore = () => {
6695
+ if (restored) return;
6696
+ restored = true;
6697
+ output.write(`${SHOW_CURSOR}${ALT_SCREEN_OFF}`);
6698
+ };
6699
+ input2.setRawMode?.(true);
6700
+ input2.resume();
6701
+ output.write(`${ALT_SCREEN_ON}${HIDE_CURSOR}`);
6702
+ process.once("exit", restore);
6703
+ redraw();
6704
+ return await new Promise((resolve7) => {
6705
+ const cleanup = () => {
6706
+ input2.off("keypress", onKey);
6707
+ input2.setRawMode?.(wasRaw);
6708
+ input2.pause();
6709
+ process.off("exit", restore);
6710
+ restore();
6711
+ };
6712
+ const onKey = (_str, key) => {
6713
+ const name = key?.name;
6714
+ if (key?.ctrl && name === "c" || name === "escape" || name === "q") {
6715
+ cleanup();
6716
+ resolve7(null);
6717
+ return;
6718
+ }
6719
+ if (name === "return" || name === "enter") {
6720
+ cleanup();
6721
+ resolve7(checked.flatMap((on, i) => on ? [i] : []));
6722
+ return;
6723
+ }
6724
+ if (name === "up" || name === "k") {
6725
+ cursor = (cursor - 1 + items.length) % items.length;
6726
+ redraw();
6727
+ return;
6728
+ }
6729
+ if (name === "down" || name === "j") {
6730
+ cursor = (cursor + 1) % items.length;
6731
+ redraw();
6732
+ return;
6733
+ }
6734
+ if (name === "space") {
6735
+ checked[cursor] = !checked[cursor];
6736
+ redraw();
6737
+ return;
6738
+ }
6739
+ if (name === "a") {
6740
+ const allOn = checked.every(Boolean);
6741
+ for (let i = 0; i < checked.length; i++) checked[i] = !allOn;
6742
+ redraw();
6743
+ return;
6744
+ }
6745
+ };
6746
+ input2.on("keypress", onKey);
6747
+ });
6748
+ }
6749
+
6750
+ // src/cli/commands/bot.ts
6154
6751
  async function runBotInit(name) {
6155
6752
  if (!ensureCodex()) {
6156
6753
  process.exitCode = 1;
@@ -6165,7 +6762,7 @@ async function runBotInit(name) {
6165
6762
  console.log(" 1) \u4E8B\u4EF6\u4E0E\u56DE\u8C03 \u2192 \u957F\u8FDE\u63A5 \u2192 \u8BA2\u9605\uFF1Aim.message.receive_v1 / card.action.trigger / application.bot.menu_v6");
6166
6763
  console.log(" \uFF08\u53EF\u9009\uFF09\u300C\u52A0\u8FDB\u5DF2\u6709\u7FA4\u300D\u529F\u80FD\u518D\u8BA2\u9605\uFF1Aim.chat.member.bot.added_v1 / im.chat.member.bot.deleted_v1");
6167
6764
  console.log(" 2) \u521B\u5EFA\u5E76\u53D1\u5E03\u5E94\u7528\u7248\u672C");
6168
- console.log("\n`bot list` \u67E5\u770B\u5168\u90E8\uFF1B`bot use <\u540D>` \u5207\u6362\u5F53\u524D\uFF1B`run` \u524D\u53F0\u8DD1 / `start` \u540E\u53F0\u5E38\u9A7B\u3002\n");
6765
+ console.log("\n`bot list` \u67E5\u770B\u5168\u90E8\uFF1B`bot use` \u52FE\u9009\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF1B`run` \u524D\u53F0\u8DD1 / `start` \u540E\u53F0\u5E38\u9A7B\u3002\n");
6169
6766
  }
6170
6767
  async function runBotList() {
6171
6768
  const reg = await loadBots();
@@ -6173,27 +6770,65 @@ async function runBotList() {
6173
6770
  console.log("\uFF08\u8FD8\u6CA1\u6709\u6CE8\u518C\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u8FD0\u884C `feishu-codex-bridge bot init` \u521B\u5EFA\u3002\uFF09");
6174
6771
  return;
6175
6772
  }
6773
+ const active = new Set(activeBots(reg).map((b) => b.appId));
6176
6774
  console.log("\n\u5DF2\u6CE8\u518C\u7684\u98DE\u4E66\u673A\u5668\u4EBA\uFF1A\n");
6177
6775
  for (const b of reg.bots) {
6178
- const cur = b.appId === reg.current ? "\u{1F449}" : " ";
6179
- console.log(`${cur} ${b.name.padEnd(16)} ${b.appId} [${b.tenant}]${b.botName ? ` ${b.botName}` : ""}`);
6776
+ const mark = active.has(b.appId) ? "\u2705" : "\u2B1C";
6777
+ console.log(`${mark} ${b.name.padEnd(16)} ${b.appId} [${b.tenant}]${b.botName ? ` ${b.botName}` : ""}`);
6180
6778
  }
6181
- console.log("\n\u{1F449} = \u5F53\u524D\u9009\u4E2D\uFF08run / start \u542F\u52A8\u7684\u5C31\u662F\u5B83\uFF09\u3002`bot use <\u540D>` \u5207\u6362\u3002\n");
6779
+ console.log("\n\x1B[2m`bot use` \u52FE\u9009\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF0C\u6216 `bot use <\u540D> [\u540D\u2026]` \u76F4\u63A5\u6307\u5B9A\u3002\x1B[0m\n");
6182
6780
  }
6183
- async function runBotUse(name) {
6781
+ async function runBotUse(names) {
6184
6782
  const reg = await loadBots();
6185
- const bot2 = findBot(reg, name);
6186
- if (!bot2) {
6187
- console.error(`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\u300C${name}\u300D\u3002\u5DF2\u6CE8\u518C\uFF1A${botNames(reg.bots)}`);
6783
+ if (reg.bots.length === 0) {
6784
+ console.error("\u2717 \u8FD8\u6CA1\u6709\u6CE8\u518C\u4EFB\u4F55\u98DE\u4E66\u673A\u5668\u4EBA\u3002\u5148 `feishu-codex-bridge bot init` \u521B\u5EFA\u3002");
6188
6785
  process.exitCode = 1;
6189
6786
  return;
6190
6787
  }
6191
- if (reg.current === bot2.appId) {
6192
- console.log(`\u300C${bot2.name}\u300D\u5DF2\u7ECF\u662F\u5F53\u524D\u673A\u5668\u4EBA\u3002`);
6788
+ let appIds;
6789
+ if (names.length > 0) {
6790
+ const resolved = [];
6791
+ const unknown = [];
6792
+ for (const n of names) {
6793
+ const b = findBot(reg, n);
6794
+ if (!b) unknown.push(n);
6795
+ else if (!resolved.includes(b.appId)) resolved.push(b.appId);
6796
+ }
6797
+ if (unknown.length) {
6798
+ console.error(`\u2717 \u627E\u4E0D\u5230\u673A\u5668\u4EBA\uFF1A${unknown.join(", ")}\u3002\u5DF2\u6CE8\u518C\uFF1A${botNames(reg.bots)}`);
6799
+ process.exitCode = 1;
6800
+ return;
6801
+ }
6802
+ appIds = resolved;
6803
+ } else {
6804
+ if (!process.stdin.isTTY) {
6805
+ const cur = activeBots(reg).map((b) => b.name).join(", ") || "\uFF08\u7A7A\uFF09";
6806
+ console.log(`\u5F53\u524D\u6D3B\u8DC3\u673A\u5668\u4EBA\uFF1A${cur}`);
6807
+ console.log("\uFF08\u975E\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\uFF0C\u65E0\u6CD5\u5F39\u52FE\u9009\u6846\u3002\u7528 `bot use <\u540D> [\u540D\u2026]` \u76F4\u63A5\u6307\u5B9A\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\u3002\uFF09");
6808
+ return;
6809
+ }
6810
+ const activeSet = new Set(activeBots(reg).map((b) => b.appId));
6811
+ const items = reg.bots.map((b) => ({
6812
+ label: b.name,
6813
+ hint: `${b.appId} [${b.tenant}]${b.botName ? ` ${b.botName}` : ""}`,
6814
+ checked: activeSet.has(b.appId)
6815
+ }));
6816
+ const picked = await checkboxSelect("\u9009\u62E9\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF08\u7A7A\u683C\u52FE\u9009\uFF0C\u56DE\u8F66\u786E\u8BA4\uFF09\uFF1A", items);
6817
+ if (picked === null) {
6818
+ console.log("\u5DF2\u53D6\u6D88\uFF0C\u672A\u6539\u52A8\u3002");
6819
+ return;
6820
+ }
6821
+ appIds = picked.map((i) => reg.bots[i]?.appId).filter((id) => Boolean(id));
6822
+ }
6823
+ await setActiveBots(appIds);
6824
+ const chosen = appIds.map((id) => reg.bots.find((b) => b.appId === id)?.name ?? id);
6825
+ if (chosen.length === 0) {
6826
+ console.log("\u2713 \u5DF2\u6E05\u7A7A\u6D3B\u8DC3\u673A\u5668\u4EBA\u2014\u2014`run` / `start` \u6682\u4E0D\u8FDE\u63A5\u4EFB\u4F55 bot\u3002`bot use` \u91CD\u65B0\u52FE\u9009\u3002");
6193
6827
  return;
6194
6828
  }
6195
- await setCurrent(bot2.appId);
6196
- console.log(`\u2713 \u5F53\u524D\u673A\u5668\u4EBA \u2192 \u300C${bot2.name}\u300D(${bot2.appId})\u3002\u524D\u53F0 \`run\` \u76F4\u63A5\u751F\u6548\uFF1B\u540E\u53F0\u8BF7 \`restart\`\u3002`);
6829
+ console.log(
6830
+ `\u2713 \u6D3B\u8DC3\u673A\u5668\u4EBA\uFF08${chosen.length} \u4E2A\uFF09\u2192 ${chosen.join(", ")}\u3002\u524D\u53F0\u91CD\u8DD1 \`run\` \u751F\u6548${chosen.length > 1 ? "\uFF08\u591A\u8FDB\u7A0B\u6258\u7BA1\uFF09" : ""}\uFF1B\u540E\u53F0\u8BF7 \`restart\`\u3002`
6831
+ );
6197
6832
  }
6198
6833
  async function runBotRm(name) {
6199
6834
  const reg = await loadBots();
@@ -6278,8 +6913,8 @@ function readStdin() {
6278
6913
  // src/cli/index.ts
6279
6914
  var program = new Command();
6280
6915
  program.name("feishu-codex-bridge").description("\u628A\u98DE\u4E66/Lark \u6865\u63A5\u5230\u672C\u673A Codex\uFF08\u9879\u76EE=\u7FA4, \u8BDD\u9898=\u4F1A\u8BDD\uFF09").version(bridgeVersion());
6281
- program.command("run").description("\u524D\u53F0\u542F\u52A8 bot\uFF08\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF1BCtrl+C \u4F18\u96C5\u9000\u51FA\uFF09").action(async () => {
6282
- await runRun();
6916
+ program.command("run").description("\u524D\u53F0\u542F\u52A8\u6D3B\u8DC3\u673A\u5668\u4EBA\uFF08\u591A\u4E2A\u5219\u5404\u81EA\u72EC\u7ACB\u8FDB\u7A0B\uFF1B\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF1BCtrl+C \u4F18\u96C5\u9000\u51FA\uFF09").option("--bot <name>", "\u53EA\u542F\u52A8\u6307\u5B9A\u7684\u4E00\u4E2A\u673A\u5668\u4EBA\uFF08\u540D\u5B57\u6216 appId\uFF09").action(async (options) => {
6917
+ await runRun(options.bot);
6283
6918
  });
6284
6919
  program.command("start").description("\u540E\u53F0 daemon \u542F\u52A8\uFF08\u88C5 launchd \u5F00\u673A\u81EA\u542F\uFF1B\u6CA1\u914D\u7F6E\u5219\u5148\u626B\u7801 init\uFF09").action(async () => {
6285
6920
  await runStart();
@@ -6306,8 +6941,8 @@ bot.command("init [name]").description("\u6CE8\u518C\u4E00\u4E2A\u98DE\u4E66\u67
6306
6941
  bot.command("list").description("\u5217\u51FA\u5DF2\u6CE8\u518C\u7684\u98DE\u4E66\u673A\u5668\u4EBA").action(async () => {
6307
6942
  await runBotList();
6308
6943
  });
6309
- bot.command("use <name>").description("\u9009\u62E9 run / start \u542F\u52A8\u65F6\u4F7F\u7528\u7684\u673A\u5668\u4EBA").action(async (name) => {
6310
- await runBotUse(name);
6944
+ bot.command("use [names...]").description("\u52FE\u9009/\u6307\u5B9A\u8981\u540C\u65F6\u8FDE\u63A5\u7684\u673A\u5668\u4EBA\uFF08\u591A\u9009\uFF09\uFF1B\u65E0\u53C2\u6570\u5F39\u4EA4\u4E92\u5F0F\u52FE\u9009\u6846").action(async (names) => {
6945
+ await runBotUse(names ?? []);
6311
6946
  });
6312
6947
  bot.command("rm <name>").description("\u79FB\u9664\u4E00\u4E2A\u673A\u5668\u4EBA\u914D\u7F6E").action(async (name) => {
6313
6948
  await runBotRm(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelzen/feishu-codex-bridge",
3
- "version": "0.3.0",
3
+ "version": "0.3.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": {