botmux 2.92.0 → 2.93.1

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 (58) hide show
  1. package/README.en.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/bot-registry.d.ts.map +1 -1
  4. package/dist/bot-registry.js +5 -4
  5. package/dist/bot-registry.js.map +1 -1
  6. package/dist/cli/dashboard-endpoint.d.ts +1 -1
  7. package/dist/cli/dashboard-endpoint.d.ts.map +1 -1
  8. package/dist/cli/dashboard-endpoint.js +3 -0
  9. package/dist/cli/dashboard-endpoint.js.map +1 -1
  10. package/dist/core/session-manager.js +2 -2
  11. package/dist/core/session-manager.js.map +1 -1
  12. package/dist/core/trigger-session.d.ts +10 -0
  13. package/dist/core/trigger-session.d.ts.map +1 -1
  14. package/dist/core/trigger-session.js +22 -2
  15. package/dist/core/trigger-session.js.map +1 -1
  16. package/dist/core/worker-pool.d.ts +6 -0
  17. package/dist/core/worker-pool.d.ts.map +1 -1
  18. package/dist/core/worker-pool.js +20 -6
  19. package/dist/core/worker-pool.js.map +1 -1
  20. package/dist/daemon.d.ts +2 -1
  21. package/dist/daemon.d.ts.map +1 -1
  22. package/dist/daemon.js +20 -5
  23. package/dist/daemon.js.map +1 -1
  24. package/dist/dashboard/federation-spoke-api.d.ts +31 -0
  25. package/dist/dashboard/federation-spoke-api.d.ts.map +1 -1
  26. package/dist/dashboard/federation-spoke-api.js +36 -14
  27. package/dist/dashboard/federation-spoke-api.js.map +1 -1
  28. package/dist/dashboard/web/i18n.js +2 -2
  29. package/dist/dashboard/web/i18n.js.map +1 -1
  30. package/dist/dashboard-web/app.js +2 -2
  31. package/dist/dashboard.js +55 -51
  32. package/dist/dashboard.js.map +1 -1
  33. package/dist/i18n/en.d.ts.map +1 -1
  34. package/dist/i18n/en.js +9 -6
  35. package/dist/i18n/en.js.map +1 -1
  36. package/dist/i18n/zh.d.ts.map +1 -1
  37. package/dist/i18n/zh.js +9 -6
  38. package/dist/i18n/zh.js.map +1 -1
  39. package/dist/im/lark/card-handler.d.ts.map +1 -1
  40. package/dist/im/lark/card-handler.js +24 -2
  41. package/dist/im/lark/card-handler.js.map +1 -1
  42. package/dist/im/lark/doc-comment.d.ts +20 -0
  43. package/dist/im/lark/doc-comment.d.ts.map +1 -1
  44. package/dist/im/lark/doc-comment.js +23 -0
  45. package/dist/im/lark/doc-comment.js.map +1 -1
  46. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  47. package/dist/im/lark/event-dispatcher.js +13 -9
  48. package/dist/im/lark/event-dispatcher.js.map +1 -1
  49. package/dist/platform/bind.d.ts.map +1 -1
  50. package/dist/platform/bind.js +17 -3
  51. package/dist/platform/bind.js.map +1 -1
  52. package/dist/services/group-creator.d.ts.map +1 -1
  53. package/dist/services/group-creator.js +18 -2
  54. package/dist/services/group-creator.js.map +1 -1
  55. package/dist/skills/definitions.js +1 -1
  56. package/dist/worker.js +69 -12
  57. package/dist/worker.js.map +1 -1
  58. package/package.json +1 -1
package/dist/dashboard.js CHANGED
@@ -21,7 +21,7 @@ import { handleConnectorApi } from './dashboard/connector-api.js';
21
21
  import { redactGroupsForPublic, redactSchedulesForPublic } from './dashboard/public-redact.js';
22
22
  import { handleWebhookRoute } from './dashboard/webhook-routes.js';
23
23
  import { handleFederationApi } from './dashboard/federation-api.js';
24
- import { handleFederationSpokeApi, syncAllMemberships } from './dashboard/federation-spoke-api.js';
24
+ import { handleFederationSpokeApi, syncAllMemberships, autoBindOwnerIfUnambiguous } from './dashboard/federation-spoke-api.js';
25
25
  import { getRunsDir } from './workflows/runs-dir.js';
26
26
  import { BotOnboardingManager } from './dashboard/bot-onboarding.js';
27
27
  import { CLI_SELECT_OPTIONS, resolveCliSelection, isTtadkWrapper, ttadkAcceptsModel, TTADK_DEFAULT_MODEL, TTADK_MODEL_SUGGESTIONS, } from './setup/cli-selection.js';
@@ -52,8 +52,7 @@ import { installDashboardSkill, parseDashboardSkillInstallRequest, parseInstallL
52
52
  import { botDefaultsPayload, botSummaryPayload } from './dashboard/bot-payload.js';
53
53
  import { isValidRoleProfileId } from './services/role-profile-store.js';
54
54
  import { mergeSafeInsightOverviews } from './services/insight/report.js';
55
- import { watch as fsWatch } from 'node:fs';
56
- import { readPlatformBinding, PLATFORM_BINDING_PATH } from './platform/binding.js';
55
+ import { readPlatformBinding } from './platform/binding.js';
57
56
  import { startPlatformTunnelClient } from './platform/tunnel-client.js';
58
57
  import { cleanupIdleSessions, parseIdleCleanupHours } from './dashboard/session-cleanup.js';
59
58
  const SECRET_PATH = join(homedir(), '.botmux', '.dashboard-secret');
@@ -898,6 +897,22 @@ const server = createServer(async (req, res) => {
898
897
  return jsonRes(res, 404, { error: 'no_active_token' });
899
898
  return jsonRes(res, 200, { url: dashboardUrlFor(activeToken) });
900
899
  }
900
+ // CLI 通知绑定变化(HMAC + loopback)——`botmux bind` 写完绑定后捅一下,立即重连平台,
901
+ // 无需重启 daemon,也不依赖 fs.watch。
902
+ if (req.method === 'POST' && url.pathname === '/__cli/reload-binding') {
903
+ const gate = verifyCliRequest(req, url.pathname);
904
+ if (!gate.ok)
905
+ return jsonRes(res, gate.status, gate.body);
906
+ try {
907
+ platformTunnel?.stop();
908
+ }
909
+ catch {
910
+ /* ignore */
911
+ }
912
+ platformTunnel = null;
913
+ startPlatformTunnelIfBound();
914
+ return jsonRes(res, 200, { ok: true });
915
+ }
901
916
  const presentedToken = authedToken(req, url);
902
917
  const dashboardSettings = resolveDashboardSettings();
903
918
  const decision = decideDashboardAuth({
@@ -2306,7 +2321,6 @@ listenWithProbe({
2306
2321
  }
2307
2322
  logger.info(`[dashboard] listening on ${config.dashboard.host}:${port}`);
2308
2323
  startPlatformTunnelIfBound();
2309
- watchPlatformBinding(); // bind 后无需重启 daemon,自动连接平台
2310
2324
  }).catch((err) => {
2311
2325
  logger.error(`[dashboard] could not bind near ${config.dashboard.host}:${config.dashboard.port} after probing — set BOTMUX_DASHBOARD_PORT to a free port. ${err.message}`);
2312
2326
  process.exit(1);
@@ -2321,14 +2335,46 @@ const federationSync = setInterval(() => {
2321
2335
  .catch(() => { });
2322
2336
  }, 2 * 60 * 1000);
2323
2337
  federationSync.unref();
2338
+ // 单候选自动绑定:standalone(未入团队)部署不必手动点面板「绑定」——用各机器人自己的
2339
+ // 凭证从 allowedUsers 解析出唯一负责人就自动认领,左上角飞书头像 / 拉群把发起人拉进群 /
2340
+ // 机器人归属随即生效。多候选(部署里配了多个人)仍保留手动选择。幂等:绑定后即刻 no-op。
2341
+ // 启动时按 0/5/15/60s 退避重试几次以覆盖 boot 时网络/凭证尚未就绪,之后交给手动按钮,
2342
+ // 不挂进永久心跳(避免对真·多候选/无 allowedUsers 的部署每 2 分钟空打飞书)。
2343
+ async function tryAutoBindOwner() {
2344
+ try {
2345
+ const r = await autoBindOwnerIfUnambiguous(config.session.dataDir, { fetcher: fetch, live: liveBots() });
2346
+ if (r.status === 'bound') {
2347
+ logger.info(`[identity] 已自动绑定本部署负责人:${r.owner?.name || r.owner?.unionId}(头像/拉群/归属即时生效)`);
2348
+ return 'done';
2349
+ }
2350
+ if (r.status === 'already_bound')
2351
+ return 'done';
2352
+ if (r.status === 'need_choice') {
2353
+ logger.info(`[identity] 检测到 ${r.candidates?.length ?? 0} 个候选负责人,请到面板「团队」手动选择绑定`);
2354
+ return 'done';
2355
+ }
2356
+ return 'retry'; // no_candidates:可能是网络/凭证未就绪的瞬时失败,退避后重试
2357
+ }
2358
+ catch (e) {
2359
+ logger.debug(`[identity] 自动绑定尝试失败(将退避重试):${e.message}`);
2360
+ return 'retry';
2361
+ }
2362
+ }
2363
+ void (async () => {
2364
+ for (const delayMs of [0, 5_000, 15_000, 60_000]) {
2365
+ if (delayMs)
2366
+ await new Promise((r) => setTimeout(r, delayMs));
2367
+ if ((await tryAutoBindOwner()) === 'done')
2368
+ return;
2369
+ }
2370
+ })();
2324
2371
  // 中心化平台隧道(已绑定才启动;每台机器一个,跑在 dashboard 进程里)
2325
2372
  let platformTunnel = null;
2326
- // 当前隧道对应的绑定身份(platformUrl|machineId|machineToken);用于判断 bind 变化是否需要重连
2327
- let platformBindingKey = null;
2328
2373
  function readBotmuxVersion() {
2374
+ // 与本地 dashboard「版本与更新」卡同源:源码 checkout 的 package.json 是占位的 0.0.0,
2375
+ // resolveCurrentVersion() 会用 git describe 推出真实版本(如 2.91.1),npm 安装则用 package.json。
2329
2376
  try {
2330
- const pkg = JSON.parse(readFileSync(join(dirname(__dirname), 'package.json'), 'utf8'));
2331
- return pkg.version || 'unknown';
2377
+ return resolveCurrentVersion();
2332
2378
  }
2333
2379
  catch {
2334
2380
  return 'unknown';
@@ -2360,10 +2406,8 @@ function readPlatformBotsInfo() {
2360
2406
  function startPlatformTunnelIfBound() {
2361
2407
  try {
2362
2408
  const binding = readPlatformBinding();
2363
- if (!binding) {
2364
- platformBindingKey = null;
2409
+ if (!binding)
2365
2410
  return;
2366
- }
2367
2411
  const version = readBotmuxVersion();
2368
2412
  platformTunnel = startPlatformTunnelClient({
2369
2413
  binding,
@@ -2373,52 +2417,12 @@ function startPlatformTunnelIfBound() {
2373
2417
  getBots: () => readPlatformBotsInfo(),
2374
2418
  log: (msg, extra) => logger.info(`[platform-tunnel] ${msg}${extra ? ' ' + JSON.stringify(extra) : ''}`),
2375
2419
  });
2376
- platformBindingKey = `${binding.platformUrl}|${binding.machineId}|${binding.machineToken}`;
2377
2420
  logger.info(`[platform-tunnel] 绑定到 ${binding.platformUrl},启动隧道`);
2378
2421
  }
2379
2422
  catch (e) {
2380
2423
  logger.warn(`[platform-tunnel] 启动失败: ${e.message}`);
2381
2424
  }
2382
2425
  }
2383
- /**
2384
- * 监听绑定文件变化:`botmux bind` 写入后无需重启 daemon,自动连接平台。
2385
- * 只在绑定「身份」(platformUrl/machineId/machineToken) 变化时重连——团队成员变化也会改写该文件
2386
- * (tunnel-client 自己写的),那种不重连,避免反复重启。
2387
- */
2388
- function watchPlatformBinding() {
2389
- let timer = null;
2390
- const onChange = () => {
2391
- if (timer)
2392
- clearTimeout(timer);
2393
- timer = setTimeout(() => {
2394
- const b = readPlatformBinding();
2395
- const key = b ? `${b.platformUrl}|${b.machineId}|${b.machineToken}` : null;
2396
- if (key === platformBindingKey)
2397
- return; // 没变(或仅团队变化)→ 不重连
2398
- logger.info('[platform-tunnel] 检测到绑定变化,重连平台');
2399
- try {
2400
- platformTunnel?.stop();
2401
- }
2402
- catch {
2403
- /* ignore */
2404
- }
2405
- platformTunnel = null;
2406
- startPlatformTunnelIfBound();
2407
- }, 800);
2408
- };
2409
- try {
2410
- // 监听所在目录而非文件本身——绑定走原子写(临时文件 + rename),直接 watch 文件会在 rename 后失效
2411
- const dir = dirname(PLATFORM_BINDING_PATH);
2412
- const base = PLATFORM_BINDING_PATH.slice(dir.length + 1);
2413
- fsWatch(dir, (_event, filename) => {
2414
- if (!filename || filename === base)
2415
- onChange();
2416
- });
2417
- }
2418
- catch (e) {
2419
- logger.warn(`[platform-tunnel] 绑定文件监听启动失败: ${e.message}`);
2420
- }
2421
- }
2422
2426
  // Graceful shutdown
2423
2427
  function shutdown() {
2424
2428
  for (const off of subs.values())