botmux 2.94.0 → 2.95.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 (134) hide show
  1. package/dist/adapters/backend/tmux-backend.d.ts +18 -2
  2. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  3. package/dist/adapters/backend/tmux-backend.js +45 -3
  4. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  5. package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
  6. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  7. package/dist/adapters/backend/types.d.ts +8 -0
  8. package/dist/adapters/backend/types.d.ts.map +1 -1
  9. package/dist/adapters/backend/types.js.map +1 -1
  10. package/dist/adapters/backend/zellij-backend.js +1 -1
  11. package/dist/adapters/backend/zellij-backend.js.map +1 -1
  12. package/dist/bot-registry.d.ts +40 -0
  13. package/dist/bot-registry.d.ts.map +1 -1
  14. package/dist/bot-registry.js +28 -0
  15. package/dist/bot-registry.js.map +1 -1
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +13 -0
  18. package/dist/cli.js.map +1 -1
  19. package/dist/core/command-handler.d.ts.map +1 -1
  20. package/dist/core/command-handler.js +28 -3
  21. package/dist/core/command-handler.js.map +1 -1
  22. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  23. package/dist/core/dashboard-ipc-server.js +89 -0
  24. package/dist/core/dashboard-ipc-server.js.map +1 -1
  25. package/dist/core/session-discovery.d.ts +9 -0
  26. package/dist/core/session-discovery.d.ts.map +1 -1
  27. package/dist/core/session-discovery.js +24 -0
  28. package/dist/core/session-discovery.js.map +1 -1
  29. package/dist/core/session-manager.d.ts.map +1 -1
  30. package/dist/core/session-manager.js +7 -6
  31. package/dist/core/session-manager.js.map +1 -1
  32. package/dist/core/terminal-url.d.ts.map +1 -1
  33. package/dist/core/terminal-url.js +15 -0
  34. package/dist/core/terminal-url.js.map +1 -1
  35. package/dist/core/trigger-session.js +2 -2
  36. package/dist/core/trigger-session.js.map +1 -1
  37. package/dist/core/worker-pool.d.ts.map +1 -1
  38. package/dist/core/worker-pool.js +1 -0
  39. package/dist/core/worker-pool.js.map +1 -1
  40. package/dist/daemon.d.ts.map +1 -1
  41. package/dist/daemon.js +14 -11
  42. package/dist/daemon.js.map +1 -1
  43. package/dist/dashboard/aggregator.d.ts +5 -0
  44. package/dist/dashboard/aggregator.d.ts.map +1 -1
  45. package/dist/dashboard/aggregator.js +7 -0
  46. package/dist/dashboard/aggregator.js.map +1 -1
  47. package/dist/dashboard/bot-payload.d.ts +1 -0
  48. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  49. package/dist/dashboard/bot-payload.js +1 -0
  50. package/dist/dashboard/bot-payload.js.map +1 -1
  51. package/dist/dashboard/connector-api.d.ts.map +1 -1
  52. package/dist/dashboard/connector-api.js +14 -3
  53. package/dist/dashboard/connector-api.js.map +1 -1
  54. package/dist/dashboard/settings-write-applier.d.ts +2 -1
  55. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  56. package/dist/dashboard/settings-write-applier.js +7 -0
  57. package/dist/dashboard/settings-write-applier.js.map +1 -1
  58. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  59. package/dist/dashboard/web/bot-defaults.js +105 -33
  60. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  61. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  62. package/dist/dashboard/web/i18n.js +30 -2
  63. package/dist/dashboard/web/i18n.js.map +1 -1
  64. package/dist/dashboard/web/settings.d.ts.map +1 -1
  65. package/dist/dashboard/web/settings.js +11 -0
  66. package/dist/dashboard/web/settings.js.map +1 -1
  67. package/dist/dashboard/web/whiteboards.d.ts.map +1 -1
  68. package/dist/dashboard/web/whiteboards.js +14 -5
  69. package/dist/dashboard/web/whiteboards.js.map +1 -1
  70. package/dist/dashboard-web/app.js +602 -580
  71. package/dist/dashboard-web/apple-touch-icon.png +0 -0
  72. package/dist/dashboard-web/favicon.png +0 -0
  73. package/dist/dashboard-web/index.html +2 -0
  74. package/dist/dashboard.js +163 -10
  75. package/dist/dashboard.js.map +1 -1
  76. package/dist/global-config.d.ts +10 -0
  77. package/dist/global-config.d.ts.map +1 -1
  78. package/dist/global-config.js +8 -0
  79. package/dist/global-config.js.map +1 -1
  80. package/dist/i18n/en.d.ts.map +1 -1
  81. package/dist/i18n/en.js +20 -0
  82. package/dist/i18n/en.js.map +1 -1
  83. package/dist/i18n/zh.d.ts.map +1 -1
  84. package/dist/i18n/zh.js +20 -0
  85. package/dist/i18n/zh.js.map +1 -1
  86. package/dist/im/lark/client.d.ts.map +1 -1
  87. package/dist/im/lark/client.js +3 -12
  88. package/dist/im/lark/client.js.map +1 -1
  89. package/dist/im/lark/event-dispatcher.d.ts +2 -1
  90. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  91. package/dist/im/lark/event-dispatcher.js +20 -6
  92. package/dist/im/lark/event-dispatcher.js.map +1 -1
  93. package/dist/im/lark/grant-command.js +4 -4
  94. package/dist/im/lark/grant-command.js.map +1 -1
  95. package/dist/im/lark/message-parser.d.ts +29 -1
  96. package/dist/im/lark/message-parser.d.ts.map +1 -1
  97. package/dist/im/lark/message-parser.js +35 -1
  98. package/dist/im/lark/message-parser.js.map +1 -1
  99. package/dist/platform/binding.d.ts +6 -0
  100. package/dist/platform/binding.d.ts.map +1 -1
  101. package/dist/platform/binding.js +17 -0
  102. package/dist/platform/binding.js.map +1 -1
  103. package/dist/platform/tunnel-client.d.ts +2 -0
  104. package/dist/platform/tunnel-client.d.ts.map +1 -1
  105. package/dist/platform/tunnel-client.js.map +1 -1
  106. package/dist/services/bot-config-store.d.ts.map +1 -1
  107. package/dist/services/bot-config-store.js +1 -0
  108. package/dist/services/bot-config-store.js.map +1 -1
  109. package/dist/services/oncall-store.d.ts +25 -0
  110. package/dist/services/oncall-store.d.ts.map +1 -1
  111. package/dist/services/oncall-store.js +56 -0
  112. package/dist/services/oncall-store.js.map +1 -1
  113. package/dist/services/transcript-resolver.d.ts.map +1 -1
  114. package/dist/services/transcript-resolver.js +21 -4
  115. package/dist/services/transcript-resolver.js.map +1 -1
  116. package/dist/setup/bot-config-editor.d.ts +7 -0
  117. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  118. package/dist/setup/bot-config-editor.js +33 -0
  119. package/dist/setup/bot-config-editor.js.map +1 -1
  120. package/dist/types.d.ts +1 -0
  121. package/dist/types.d.ts.map +1 -1
  122. package/dist/utils/terminal-favicon.d.ts +2 -0
  123. package/dist/utils/terminal-favicon.d.ts.map +1 -0
  124. package/dist/utils/terminal-favicon.js +5 -0
  125. package/dist/utils/terminal-favicon.js.map +1 -0
  126. package/dist/worker.js +142 -7
  127. package/dist/worker.js.map +1 -1
  128. package/dist/workflows/v3/bot-resolve.d.ts +1 -1
  129. package/dist/workflows/v3/bot-resolve.d.ts.map +1 -1
  130. package/dist/workflows/v3/bot-resolve.js +2 -1
  131. package/dist/workflows/v3/bot-resolve.js.map +1 -1
  132. package/dist/workflows/v3/cli-run.js +2 -2
  133. package/dist/workflows/v3/cli-run.js.map +1 -1
  134. package/package.json +2 -2
Binary file
@@ -4,6 +4,8 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>botmux dashboard</title>
7
+ <link rel="icon" type="image/png" href="/assets/favicon.png">
8
+ <link rel="apple-touch-icon" href="/assets/apple-touch-icon.png">
7
9
  <link rel="stylesheet" href="/assets/style.css">
8
10
  </head>
9
11
  <body>
package/dist/dashboard.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/dashboard.ts
2
- import { createServer, get as httpGet } from 'node:http';
3
- import { createServer as createTcpServer } from 'node:net';
2
+ import { createServer, get as httpGet, request as httpRequest } from 'node:http';
3
+ import { createServer as createTcpServer, connect as netConnect } from 'node:net';
4
4
  import { readFileSync, existsSync, mkdirSync, statSync, createReadStream, } from 'node:fs';
5
5
  import { atomicWriteFileSync } from './utils/atomic-write.js';
6
6
  import { join, dirname, extname, resolve, relative, isAbsolute } from 'node:path';
@@ -178,6 +178,7 @@ function resolveDashboardSettings() {
178
178
  maintenance: global.maintenance ?? {},
179
179
  localDevInstall: isLocalDevInstall(),
180
180
  whiteboard: { enabled: global.whiteboard?.enabled === true },
181
+ remoteAccess: global.remoteAccess === true,
181
182
  };
182
183
  }
183
184
  // Single shared deps object for `applySettingsWrite` — both the browser
@@ -833,6 +834,15 @@ async function dashboardSkillReferences(skillName) {
833
834
  bots: [...refs.values()].sort((a, b) => a.botName.localeCompare(b.botName)),
834
835
  };
835
836
  }
837
+ /** Extract the sessionId from a terminal path `/s/<sessionId>[/...]`. Returns
838
+ * the first path segment after `/s/` (stops at the next `/`; query/hash are
839
+ * already stripped by URL.pathname). undefined when there's no segment. */
840
+ function parseTerminalSessionId(pathname) {
841
+ if (!pathname.startsWith('/s/'))
842
+ return undefined;
843
+ const seg = pathname.slice(3).split('/')[0];
844
+ return seg || undefined;
845
+ }
836
846
  const server = createServer(async (req, res) => {
837
847
  try {
838
848
  const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
@@ -847,6 +857,35 @@ const server = createServer(async (req, res) => {
847
857
  res.writeHead(200, { 'content-type': 'text/plain' });
848
858
  return res.end(DASHBOARD_SELF_NONCE);
849
859
  }
860
+ // Web terminal reverse-proxy: `/s/<sessionId>/*` → the owning bot daemon's
861
+ // terminal proxy. The central platform only tunnels the dashboard port, so
862
+ // terminal links served under the machine subdomain
863
+ // (`https://m-<id>.<host>/s/<sessionId>`) land here. The dashboard is the
864
+ // aggregator process (it fronts many bot daemons, each with its own terminal
865
+ // proxy on proxyBasePort+idx), so we resolve the session's owning daemon's
866
+ // proxy port from the aggregator rows and forward there, streaming the
867
+ // response straight back. Mounted before the dashboard auth gate because the
868
+ // terminal proxy / worker enforces its own write gate (read-only without
869
+ // token / platform role).
870
+ if (url.pathname === '/s' || url.pathname.startsWith('/s/')) {
871
+ const sessionId = parseTerminalSessionId(url.pathname);
872
+ const tport = sessionId ? aggregator.terminalProxyPortOf(sessionId) : undefined;
873
+ if (!tport) {
874
+ res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
875
+ return res.end('session terminal not available');
876
+ }
877
+ const upstream = httpRequest({ host: '127.0.0.1', port: tport, method: req.method, path: req.url, headers: req.headers }, (up) => {
878
+ res.writeHead(up.statusCode ?? 502, up.headers);
879
+ up.pipe(res);
880
+ });
881
+ upstream.on('error', () => {
882
+ if (!res.headersSent)
883
+ res.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
884
+ res.end('terminal proxy error');
885
+ });
886
+ req.pipe(upstream);
887
+ return;
888
+ }
850
889
  if (await handleWebhookRoute(req, res, url, {
851
890
  proxyToDaemon,
852
891
  createLifecycleGroup: createLifecycleGroupForWebhook,
@@ -1091,7 +1130,14 @@ const server = createServer(async (req, res) => {
1091
1130
  // `lang` is the global UI locale (single source of truth shared with
1092
1131
  // `botmux lang` and the Feishu cards) — the web UI reads it as its
1093
1132
  // authoritative initial language when set.
1094
- return jsonRes(res, 200, { settings: dashboardSettings, lang: readGlobalConfig().lang ?? null, authed });
1133
+ // `bound` reflects central-platform binding; the Settings UI only shows the
1134
+ // 远程访问 toggle when bound (the central URLs are meaningless otherwise).
1135
+ return jsonRes(res, 200, {
1136
+ settings: dashboardSettings,
1137
+ lang: readGlobalConfig().lang ?? null,
1138
+ authed,
1139
+ bound: readPlatformBinding() !== null,
1140
+ });
1095
1141
  }
1096
1142
  if (req.method === 'PUT' && url.pathname === '/api/settings') {
1097
1143
  let parsed;
@@ -1881,6 +1927,25 @@ const server = createServer(async (req, res) => {
1881
1927
  res.end(await upstream.text());
1882
1928
  return;
1883
1929
  }
1930
+ // PUT /api/bots/:appId/working-dir-mode — proxy to that bot's daemon. Body
1931
+ // `{ mode: 'off'|'default'|'oncall', workingDir }` — sets the 3-way
1932
+ // mutually-exclusive default-dir mode (defaultWorkingDir vs defaultOncall).
1933
+ let mBotWdMode;
1934
+ if (req.method === 'PUT' && (mBotWdMode = url.pathname.match(/^\/api\/bots\/([^/]+)\/working-dir-mode$/))) {
1935
+ const appId = decodeURIComponent(mBotWdMode[1]);
1936
+ const chunks = [];
1937
+ for await (const c of req)
1938
+ chunks.push(c);
1939
+ const raw = Buffer.concat(chunks).toString('utf8') || '{}';
1940
+ const upstream = await proxyToDaemon(appId, `/api/bot-working-dir-mode`, {
1941
+ method: 'PUT',
1942
+ headers: { 'content-type': 'application/json' },
1943
+ body: raw,
1944
+ });
1945
+ res.writeHead(upstream.status, { 'content-type': 'application/json' });
1946
+ res.end(await upstream.text());
1947
+ return;
1948
+ }
1884
1949
  // PUT /api/bots/:appId/skills — proxy to that bot's daemon. Body accepts
1885
1950
  // `{ action:'attach'|'detach', name }` or `{ action:'set', policy|null }`.
1886
1951
  let mBotSkills;
@@ -1935,6 +2000,24 @@ const server = createServer(async (req, res) => {
1935
2000
  res.end(await upstream.text());
1936
2001
  return;
1937
2002
  }
2003
+ // PUT /api/bots/:appId/launch-shell — proxy to that bot's daemon. Body
2004
+ // `{ launchShell: string }` (shell name or absolute path; '' = clear → $SHELL).
2005
+ let mBotLaunchShell;
2006
+ if (req.method === 'PUT' && (mBotLaunchShell = url.pathname.match(/^\/api\/bots\/([^/]+)\/launch-shell$/))) {
2007
+ const appId = decodeURIComponent(mBotLaunchShell[1]);
2008
+ const chunks = [];
2009
+ for await (const c of req)
2010
+ chunks.push(c);
2011
+ const raw = Buffer.concat(chunks).toString('utf8') || '{}';
2012
+ const upstream = await proxyToDaemon(appId, `/api/bot-launch-shell`, {
2013
+ method: 'PUT',
2014
+ headers: { 'content-type': 'application/json' },
2015
+ body: raw,
2016
+ });
2017
+ res.writeHead(upstream.status, { 'content-type': 'application/json' });
2018
+ res.end(await upstream.text());
2019
+ return;
2020
+ }
1938
2021
  // PUT /api/bots/:appId/env — proxy to that bot's daemon. Body
1939
2022
  // `{ env: string }` (raw JSON text; '' = clear).
1940
2023
  let mBotEnv;
@@ -2307,6 +2390,59 @@ const server = createServer(async (req, res) => {
2307
2390
  jsonRes(res, 500, { error: String(err) });
2308
2391
  }
2309
2392
  });
2393
+ // Web terminal WebSocket reverse-proxy: bridge `/s/*` upgrade requests through to
2394
+ // the local terminal proxy (which in turn bridges to the session worker). Raw
2395
+ // socket-to-socket bridge: dial 127.0.0.1:<terminalProxyPort>, replay the upgrade
2396
+ // request line + headers verbatim, then pipe both directions. Mirrors how
2397
+ // terminal-proxy.ts bridges to the worker. Non-`/s/*` upgrades are dropped (the
2398
+ // dashboard SPA uses SSE, not WebSocket).
2399
+ server.on('upgrade', (req, clientSocket, head) => {
2400
+ try {
2401
+ const rawUrl = req.url ?? '/';
2402
+ if (!(rawUrl === '/s' || rawUrl.startsWith('/s/') || rawUrl.startsWith('/s?'))) {
2403
+ return clientSocket.destroy();
2404
+ }
2405
+ // Strip query/hash before extracting the sessionId path segment.
2406
+ const pathname = rawUrl.split(/[?#]/)[0];
2407
+ const sessionId = parseTerminalSessionId(pathname);
2408
+ const tport = sessionId ? aggregator.terminalProxyPortOf(sessionId) : undefined;
2409
+ if (!tport)
2410
+ return clientSocket.destroy();
2411
+ const upstream = netConnect(tport, '127.0.0.1', () => {
2412
+ // rawHeaders is a flat [k, v, k, v, ...] list — preserves casing/duplicates.
2413
+ const lines = [`${req.method} ${req.url} HTTP/1.1`];
2414
+ const rh = req.rawHeaders;
2415
+ for (let i = 0; i + 1 < rh.length; i += 2)
2416
+ lines.push(`${rh[i]}: ${rh[i + 1]}`);
2417
+ lines.push('', '');
2418
+ upstream.write(lines.join('\r\n'));
2419
+ if (head?.length)
2420
+ upstream.write(head);
2421
+ upstream.pipe(clientSocket);
2422
+ clientSocket.pipe(upstream);
2423
+ });
2424
+ const cleanup = () => {
2425
+ try {
2426
+ upstream.destroy();
2427
+ }
2428
+ catch { /* ignore */ }
2429
+ try {
2430
+ clientSocket.destroy();
2431
+ }
2432
+ catch { /* ignore */ }
2433
+ };
2434
+ upstream.on('error', cleanup);
2435
+ clientSocket.on('error', cleanup);
2436
+ upstream.on('close', () => clientSocket.destroy());
2437
+ clientSocket.on('close', () => upstream.destroy());
2438
+ }
2439
+ catch {
2440
+ try {
2441
+ clientSocket.destroy();
2442
+ }
2443
+ catch { /* ignore */ }
2444
+ }
2445
+ });
2310
2446
  // Probe upward on EADDRINUSE rather than crashing with an unhandled 'error':
2311
2447
  // a second botmux instance on this host (or a stray process) holding the
2312
2448
  // configured port would otherwise tear the dashboard process down on bind.
@@ -2396,14 +2532,31 @@ function readPlatformBotsInfo() {
2396
2532
  const entries = JSON.parse(readFileSync(fp, 'utf8'));
2397
2533
  if (!Array.isArray(entries))
2398
2534
  return [];
2535
+ // Merge per-bot team-visibility config (showInTeam) from bots.json by
2536
+ // larkAppId so the platform team page can hide bots. Default: showInTeam =
2537
+ // true (shown). bots.json may be unreadable from the dashboard process →
2538
+ // fall back to the default.
2539
+ const cfgByAppId = new Map();
2540
+ try {
2541
+ for (const cfg of loadBotConfigs()) {
2542
+ cfgByAppId.set(cfg.larkAppId, { showInTeam: cfg.showInTeam });
2543
+ }
2544
+ }
2545
+ catch {
2546
+ /* defaults below */
2547
+ }
2399
2548
  return entries
2400
- .map((e) => ({
2401
- appId: e.larkAppId || '',
2402
- openId: e.botOpenId ?? null,
2403
- name: e.botName || e.larkAppId || 'bot',
2404
- avatar: e.botAvatarUrl || undefined,
2405
- cli: e.cliId,
2406
- }))
2549
+ .map((e) => {
2550
+ const cfg = cfgByAppId.get(e.larkAppId || '');
2551
+ return {
2552
+ appId: e.larkAppId || '',
2553
+ openId: e.botOpenId ?? null,
2554
+ name: e.botName || e.larkAppId || 'bot',
2555
+ avatar: e.botAvatarUrl || undefined,
2556
+ cli: e.cliId,
2557
+ showInTeam: cfg?.showInTeam !== false, // default true
2558
+ };
2559
+ })
2407
2560
  .filter((b) => b.appId);
2408
2561
  }
2409
2562
  catch {