botmux 2.102.0 → 2.103.0

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 (45) hide show
  1. package/dist/cli.d.ts +9 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +141 -6
  4. package/dist/cli.js.map +1 -1
  5. package/dist/dashboard/bot-onboarding.d.ts +28 -0
  6. package/dist/dashboard/bot-onboarding.d.ts.map +1 -1
  7. package/dist/dashboard/bot-onboarding.js +20 -0
  8. package/dist/dashboard/bot-onboarding.js.map +1 -1
  9. package/dist/dashboard/feishu-login.d.ts +64 -0
  10. package/dist/dashboard/feishu-login.d.ts.map +1 -0
  11. package/dist/dashboard/feishu-login.js +99 -0
  12. package/dist/dashboard/feishu-login.js.map +1 -0
  13. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  14. package/dist/dashboard/web/bot-defaults.js +108 -0
  15. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  16. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  17. package/dist/dashboard/web/bot-onboarding.js +4 -1
  18. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  19. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  20. package/dist/dashboard/web/i18n.js +22 -2
  21. package/dist/dashboard/web/i18n.js.map +1 -1
  22. package/dist/dashboard-web/app.js +6 -6
  23. package/dist/dashboard-web/chunks/bot-defaults-page-ZVBKVYIG.js +380 -0
  24. package/dist/dashboard-web/chunks/{chunk-DWFMPIAQ.js → chunk-HC7ANVX7.js} +5 -5
  25. package/dist/dashboard-web/chunks/{chunk-NC77G2OB.js → chunk-NNXPEJ7S.js} +1 -1
  26. package/dist/dashboard-web/chunks/{connectors-BW6WO7P5.js → connectors-POZADN75.js} +1 -1
  27. package/dist/dashboard-web/chunks/{groups-page-UTXUVV2M.js → groups-page-UQFQ6DRE.js} +1 -1
  28. package/dist/dashboard-web/chunks/{insights-page-IW3KNMG4.js → insights-page-ATW2W5KP.js} +1 -1
  29. package/dist/dashboard-web/chunks/{overview-page-RO6R26AZ.js → overview-page-Z7OV54XU.js} +1 -1
  30. package/dist/dashboard-web/chunks/{roles-page-5DYKR2I5.js → roles-page-4BJVXK2E.js} +1 -1
  31. package/dist/dashboard-web/chunks/{schedules-WUZP3UPF.js → schedules-XLETZ2EP.js} +1 -1
  32. package/dist/dashboard-web/chunks/{sessions-page-GWZ4GKSG.js → sessions-page-RMMQUGXJ.js} +1 -1
  33. package/dist/dashboard-web/chunks/{settings-page-HJH6HQ7D.js → settings-page-PWKI4TWF.js} +1 -1
  34. package/dist/dashboard-web/chunks/{skills-page-ATVGKR5I.js → skills-page-SH7KUD45.js} +1 -1
  35. package/dist/dashboard-web/chunks/{team-federation-page-JVC73TTF.js → team-federation-page-JAJXFLXA.js} +1 -1
  36. package/dist/dashboard-web/chunks/{workflows-YCZTOJUS.js → workflows-FRJ2DJAO.js} +1 -1
  37. package/dist/dashboard-web/style.css +57 -0
  38. package/dist/dashboard.js +78 -2
  39. package/dist/dashboard.js.map +1 -1
  40. package/dist/utils/bot-routing.d.ts +26 -3
  41. package/dist/utils/bot-routing.d.ts.map +1 -1
  42. package/dist/utils/bot-routing.js +38 -4
  43. package/dist/utils/bot-routing.js.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/dashboard-web/chunks/bot-defaults-page-YAHNXFBC.js +0 -370
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ export type StartBotLiveResult = {
3
+ ok: true;
4
+ state: 'started' | 'already-online';
5
+ processName: string;
6
+ } | {
7
+ ok: false;
8
+ reason: 'not_found' | 'fleet_down' | 'pm2_error';
9
+ message: string;
10
+ };
2
11
  /**
3
12
  * runHook: hook 命令的纯业务逻辑,接受已解析的 payload/env/postAskFn,
4
13
  * 返回应写到 stdout 的字符串。通过依赖注入使单元测试无需真实 daemon/env。
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA+5KA;;;;;;;;;;;GAWG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACvC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,qBAAqB,EAAE,SAAS,CAAC,EAC9F,KAAK,EAAE,MAAM,EACb,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,2BAA2B,EAAE,UAAU,GAAG,IAAI,CAAC,GACzF,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8F7B"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAg7DA,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,SAAS,GAAG,gBAAgB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,YAAY,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAgnHrF;;;;;;;;;;;GAWG;AACH,wBAAsB,OAAO,CAC3B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EACvC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,qBAAqB,EAAE,SAAS,CAAC,EAC9F,KAAK,EAAE,MAAM,EACb,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,2BAA2B,EAAE,UAAU,GAAG,IAAI,CAAC,GACzF,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA8F7B"}
package/dist/cli.js CHANGED
@@ -48,7 +48,7 @@ import { callDashboard } from './cli/dashboard-endpoint.js';
48
48
  import { loadDashboardSecret } from './dashboard/auth.js';
49
49
  import { rejectLikelyWindowsStdinMojibake, decodeStdinBytes } from './cli/stdin-encoding.js';
50
50
  import { formatBotInfoEntriesForCli, formatChatBotsForCli, } from './cli/bots-list-output.js';
51
- import { buildFooterAddressing, hasKnownBotMention, knownBotOpenIdsFromCrossRef, orderedFooterRecipients, } from './utils/bot-routing.js';
51
+ import { buildFooterAddressing, hasKnownBotMention, knownBotOpenIdsFromCrossRef, orderedFooterRecipients, stripCodeSpans, } from './utils/bot-routing.js';
52
52
  import { isLocale, localeForBot, setDefaultLocale, SUPPORTED_LOCALES, t } from './i18n/index.js';
53
53
  import { chatAppLink, larkHosts, normalizeBrand, sdkDomain } from './im/lark/lark-hosts.js';
54
54
  import { mergeGlobalConfig, readGlobalConfig, setGlobalLocale, globalConfigPath } from './global-config.js';
@@ -1182,6 +1182,9 @@ async function cmdSetupScripted(argv) {
1182
1182
  await finishOpenPlatformSetup(bot.larkAppId, botBrand(bot));
1183
1183
  }
1184
1184
  const index = existing.length;
1185
+ // daemon 在跑就直接把新 bot 那一个进程拉起来,免整组 botmux restart。
1186
+ const live = ensureBotDaemonStarted(bot.larkAppId, { quiet: cmd.json });
1187
+ const next = live.ok ? 'live' : (live.reason === 'fleet_down' ? 'botmux start' : 'botmux restart');
1185
1188
  if (cmd.json) {
1186
1189
  console.log(JSON.stringify({
1187
1190
  ok: true,
@@ -1190,7 +1193,8 @@ async function cmdSetupScripted(argv) {
1190
1193
  botsFile: BOTS_JSON_FILE,
1191
1194
  envMigrated: migratedEnv || undefined,
1192
1195
  openPlatform: cmd.openPlatformAuto ? 'attempted' : 'skipped',
1193
- next: 'botmux restart',
1196
+ live,
1197
+ next,
1194
1198
  }, null, 2));
1195
1199
  }
1196
1200
  else {
@@ -1201,7 +1205,15 @@ async function cmdSetupScripted(argv) {
1201
1205
  if (!cmd.openPlatformAuto) {
1202
1206
  console.log(' 已跳过开放平台自动配置(权限导入/发版)。需要时加 --open-platform-auto(要扫码),或运行交互式 botmux setup。');
1203
1207
  }
1204
- console.log('下一步: botmux restart');
1208
+ if (live.ok) {
1209
+ console.log(`✅ 已自动上线(${live.processName}),无需重启其它机器人。`);
1210
+ }
1211
+ else if (live.reason === 'fleet_down') {
1212
+ console.log('下一步: botmux start(daemon 尚未运行)');
1213
+ }
1214
+ else {
1215
+ console.log(`⚠️ 自动上线失败(${live.message})。下一步: botmux restart`);
1216
+ }
1205
1217
  }
1206
1218
  return;
1207
1219
  }
@@ -1459,7 +1471,7 @@ async function cmdSetup() {
1459
1471
  console.log(`\n✅ 已添加机器人 ${newBot.larkAppId},共 ${bots.length + 1} 个`);
1460
1472
  console.log(` 配置文件: ${BOTS_JSON_FILE}`);
1461
1473
  await finishOpenPlatformSetup(newBot.larkAppId, botBrand(newBot));
1462
- console.log(`下一步: botmux restart\n`);
1474
+ printAddBotLiveHint(newBot.larkAppId);
1463
1475
  return;
1464
1476
  }
1465
1477
  }
@@ -1513,7 +1525,7 @@ async function cmdSetup() {
1513
1525
  console.log(` 配置文件: ${BOTS_JSON_FILE}`);
1514
1526
  console.log(` 旧配置已备份: ${ENV_FILE}.bak`);
1515
1527
  await finishOpenPlatformSetup(newBot.larkAppId, botBrand(newBot));
1516
- console.log(`下一步: botmux restart\n`);
1528
+ printAddBotLiveHint(newBot.larkAppId);
1517
1529
  }
1518
1530
  else {
1519
1531
  // --- Fresh install ---
@@ -1874,6 +1886,120 @@ async function cmdRestart() {
1874
1886
  }
1875
1887
  await printDashboardHintWithRetry();
1876
1888
  }
1889
+ /**
1890
+ * pm2 process list filtered to botmux entries (bot daemons + dashboard). Returns
1891
+ * `[]` when pm2 isn't running or has no botmux apps at all.
1892
+ */
1893
+ function listBotmuxPm2Apps() {
1894
+ try {
1895
+ const apps = JSON.parse(pm2Capture(['jlist']));
1896
+ return (Array.isArray(apps) ? apps : [])
1897
+ .filter(a => a && (a.name === PM2_NAME || String(a.name).startsWith(`${PM2_NAME}-`)))
1898
+ .map(a => ({ name: String(a.name), online: a?.pm2_env?.status === 'online' }));
1899
+ }
1900
+ catch {
1901
+ return [];
1902
+ }
1903
+ }
1904
+ /**
1905
+ * Bring a SINGLE bot's daemon online without touching any other bot's process.
1906
+ * The key to "add a bot without `botmux restart`": a new bot is always APPENDED
1907
+ * to bots.json (stable index), so the existing daemons (indices 0..N-1) keep
1908
+ * running unchanged — we only need to spawn the new bot's own process.
1909
+ *
1910
+ * We regenerate ecosystem.config.json (which now includes the new app at index
1911
+ * N) and run `pm2 start --only <processName>`, which starts exactly that one app
1912
+ * and leaves every already-online daemon untouched (unlike `botmux restart`,
1913
+ * which tears down the whole fleet). The new daemon runs its slice of
1914
+ * startDaemon() — registerBot + WSClient long-connection + descriptor publish —
1915
+ * so it starts receiving Feishu messages and the dashboard auto-discovers it via
1916
+ * its freshly-written descriptor.
1917
+ *
1918
+ * Idempotent: a no-op when the target is already online. When the whole fleet is
1919
+ * down (no botmux pm2 apps — the dashboard itself isn't running either), we do
1920
+ * NOT start a lone bot; that case belongs to `botmux start`, which brings up the
1921
+ * entire ecosystem (all bots + dashboard).
1922
+ */
1923
+ function ensureBotDaemonStarted(appId, opts = {}) {
1924
+ const bots = loadBotsJson();
1925
+ const index = bots.findIndex(b => b?.larkAppId === appId);
1926
+ if (index < 0) {
1927
+ return { ok: false, reason: 'not_found', message: `appId ${appId} 不在 bots.json 中` };
1928
+ }
1929
+ const processName = botProcessName(bots[index], index, PM2_NAME);
1930
+ const running = listBotmuxPm2Apps();
1931
+ if (running.length === 0) {
1932
+ return { ok: false, reason: 'fleet_down', message: 'daemon 未在运行,请先 botmux start' };
1933
+ }
1934
+ if (running.some(a => a.name === processName && a.online)) {
1935
+ return { ok: true, state: 'already-online', processName };
1936
+ }
1937
+ const cfg = ecosystemConfig();
1938
+ try {
1939
+ // `--only <name>` filters the ecosystem to just this app, so pm2 starts only
1940
+ // the new bot's daemon and never restarts the already-online ones.
1941
+ runPm2(['start', cfg, '--only', processName], !opts.quiet);
1942
+ }
1943
+ catch (e) {
1944
+ return { ok: false, reason: 'pm2_error', message: e instanceof Error ? e.message : String(e) };
1945
+ }
1946
+ return { ok: true, state: 'started', processName };
1947
+ }
1948
+ /**
1949
+ * `botmux start-bot <larkAppId>` — bring one freshly-added bot online without a
1950
+ * fleet-wide restart. Invoked by `botmux setup add` (inline) and by the
1951
+ * dashboard onboarding flow (spawned as a subprocess). `--json` for scripted
1952
+ * callers.
1953
+ */
1954
+ async function cmdStartBot(argv) {
1955
+ const wantsJson = argv.includes('--json');
1956
+ const appId = argv.find(a => !a.startsWith('-'));
1957
+ if (!appId) {
1958
+ const msg = '用法: botmux start-bot <larkAppId> —— 拉起单个新机器人的 daemon(不重启其它 bot)';
1959
+ if (wantsJson)
1960
+ console.log(JSON.stringify({ ok: false, reason: 'missing_app_id', message: msg }));
1961
+ else
1962
+ console.error(`❌ ${msg}`);
1963
+ process.exit(1);
1964
+ }
1965
+ ensureConfigDir();
1966
+ const r = ensureBotDaemonStarted(appId, { quiet: wantsJson });
1967
+ if (wantsJson) {
1968
+ console.log(JSON.stringify(r, null, 2));
1969
+ if (!r.ok)
1970
+ process.exitCode = 1;
1971
+ return;
1972
+ }
1973
+ if (r.ok) {
1974
+ if (r.state === 'already-online')
1975
+ console.log(`✅ ${r.processName} 已在运行,无需操作`);
1976
+ else
1977
+ console.log(`✅ 已拉起 ${r.processName}(未重启其它机器人)`);
1978
+ return;
1979
+ }
1980
+ if (r.reason === 'fleet_down') {
1981
+ console.error('ℹ️ daemon 未在运行。请用 `botmux start` 启动整个进程组。');
1982
+ }
1983
+ else {
1984
+ console.error(`❌ 拉起失败 (${r.reason}): ${r.message}`);
1985
+ }
1986
+ process.exit(1);
1987
+ }
1988
+ /** Print the post-add "next step" line for interactive setup: auto-start the new
1989
+ * bot's own daemon when the fleet is up (no fleet-wide restart), else fall back
1990
+ * to the botmux start / restart hint. */
1991
+ function printAddBotLiveHint(appId) {
1992
+ const live = ensureBotDaemonStarted(appId);
1993
+ if (live.ok) {
1994
+ console.log(`✅ 已自动上线(${live.processName}),无需重启其它机器人。\n`);
1995
+ }
1996
+ else if (live.reason === 'fleet_down') {
1997
+ console.log('下一步: botmux start(daemon 尚未运行)\n');
1998
+ }
1999
+ else {
2000
+ console.log(`⚠️ 自动上线失败(${live.message})。下一步: botmux restart\n`);
2001
+ }
2002
+ }
1877
2003
  /** Wraps `ensureDependencies()`. Fonts are nice-to-have (warn only). tmux is
1878
2004
  * required since PTY 退役: if it's GENUINELY ABSENT and a bot wants the tmux
1879
2005
  * backend (and the operator hasn't opted into BACKEND_TYPE=pty), we hard-fail
@@ -4501,6 +4627,12 @@ async function cmdSend(rest) {
4501
4627
  // bot(正是要避免的循环 @)。botEntries/crossRef 仍需加载供 footer 寻址用。
4502
4628
  if (!noMention) {
4503
4629
  const alreadyMentioned = new Set(mentions.map(m => m.open_id));
4630
+ // Scan a code-span-stripped copy so a bot name quoted inside backticks or a
4631
+ // fenced block (e.g. an example `botmux send --mention @Bot …` or an
4632
+ // explanatory `@Bot`) is not auto-injected as a real handoff — that spurious
4633
+ // <at> would wake a bot the model never meant to @. Explicit --mention still
4634
+ // works (it doesn't go through this prose scan).
4635
+ const textForBotScan = stripCodeSpans(text);
4504
4636
  // Sort by name length desc so longer names ("Claude分身") win over their
4505
4637
  // prefix ("Claude") when both could match — break-on-first-hit otherwise
4506
4638
  // routes "@Claude分身" to Claude.
@@ -4543,7 +4675,7 @@ async function cmdSend(rest) {
4543
4675
  // `@Claude2` doesn't match name "Claude" and `@Claude分身好的` doesn't
4544
4676
  // either-half-match.
4545
4677
  const re = new RegExp(`(?<![A-Za-z0-9_])@${escName}(?![\\p{L}\\p{N}_])`, 'iu');
4546
- if (!re.test(text))
4678
+ if (!re.test(textForBotScan))
4547
4679
  continue;
4548
4680
  // Lark open_id is per-app scoped. Use sender-scoped id from cross-ref
4549
4681
  // only — falling back to entry.botOpenId would feed Lark a wrong-scope
@@ -6102,6 +6234,9 @@ switch (command) {
6102
6234
  case 'start':
6103
6235
  await cmdStart();
6104
6236
  break;
6237
+ case 'start-bot':
6238
+ await cmdStartBot(process.argv.slice(3));
6239
+ break;
6105
6240
  case 'stop':
6106
6241
  cmdStop();
6107
6242
  break;