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.
- package/dist/adapters/backend/tmux-backend.d.ts +18 -2
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +45 -3
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/backend/types.d.ts +8 -0
- package/dist/adapters/backend/types.d.ts.map +1 -1
- package/dist/adapters/backend/types.js.map +1 -1
- package/dist/adapters/backend/zellij-backend.js +1 -1
- package/dist/adapters/backend/zellij-backend.js.map +1 -1
- package/dist/bot-registry.d.ts +40 -0
- package/dist/bot-registry.d.ts.map +1 -1
- package/dist/bot-registry.js +28 -0
- package/dist/bot-registry.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +28 -3
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +89 -0
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/session-discovery.d.ts +9 -0
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +24 -0
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +7 -6
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/terminal-url.d.ts.map +1 -1
- package/dist/core/terminal-url.js +15 -0
- package/dist/core/terminal-url.js.map +1 -1
- package/dist/core/trigger-session.js +2 -2
- package/dist/core/trigger-session.js.map +1 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +1 -0
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +14 -11
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/aggregator.d.ts +5 -0
- package/dist/dashboard/aggregator.d.ts.map +1 -1
- package/dist/dashboard/aggregator.js +7 -0
- package/dist/dashboard/aggregator.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +1 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +1 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/connector-api.d.ts.map +1 -1
- package/dist/dashboard/connector-api.js +14 -3
- package/dist/dashboard/connector-api.js.map +1 -1
- package/dist/dashboard/settings-write-applier.d.ts +2 -1
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +7 -0
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +105 -33
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +30 -2
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/settings.d.ts.map +1 -1
- package/dist/dashboard/web/settings.js +11 -0
- package/dist/dashboard/web/settings.js.map +1 -1
- package/dist/dashboard/web/whiteboards.d.ts.map +1 -1
- package/dist/dashboard/web/whiteboards.js +14 -5
- package/dist/dashboard/web/whiteboards.js.map +1 -1
- package/dist/dashboard-web/app.js +602 -580
- package/dist/dashboard-web/apple-touch-icon.png +0 -0
- package/dist/dashboard-web/favicon.png +0 -0
- package/dist/dashboard-web/index.html +2 -0
- package/dist/dashboard.js +163 -10
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +10 -0
- package/dist/global-config.d.ts.map +1 -1
- package/dist/global-config.js +8 -0
- package/dist/global-config.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +20 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +20 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +3 -12
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +2 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +20 -6
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/grant-command.js +4 -4
- package/dist/im/lark/grant-command.js.map +1 -1
- package/dist/im/lark/message-parser.d.ts +29 -1
- package/dist/im/lark/message-parser.d.ts.map +1 -1
- package/dist/im/lark/message-parser.js +35 -1
- package/dist/im/lark/message-parser.js.map +1 -1
- package/dist/platform/binding.d.ts +6 -0
- package/dist/platform/binding.d.ts.map +1 -1
- package/dist/platform/binding.js +17 -0
- package/dist/platform/binding.js.map +1 -1
- package/dist/platform/tunnel-client.d.ts +2 -0
- package/dist/platform/tunnel-client.d.ts.map +1 -1
- package/dist/platform/tunnel-client.js.map +1 -1
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +1 -0
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/oncall-store.d.ts +25 -0
- package/dist/services/oncall-store.d.ts.map +1 -1
- package/dist/services/oncall-store.js +56 -0
- package/dist/services/oncall-store.js.map +1 -1
- package/dist/services/transcript-resolver.d.ts.map +1 -1
- package/dist/services/transcript-resolver.js +21 -4
- package/dist/services/transcript-resolver.js.map +1 -1
- package/dist/setup/bot-config-editor.d.ts +7 -0
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +33 -0
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/terminal-favicon.d.ts +2 -0
- package/dist/utils/terminal-favicon.d.ts.map +1 -0
- package/dist/utils/terminal-favicon.js +5 -0
- package/dist/utils/terminal-favicon.js.map +1 -0
- package/dist/worker.js +142 -7
- package/dist/worker.js.map +1 -1
- package/dist/workflows/v3/bot-resolve.d.ts +1 -1
- package/dist/workflows/v3/bot-resolve.d.ts.map +1 -1
- package/dist/workflows/v3/bot-resolve.js +2 -1
- package/dist/workflows/v3/bot-resolve.js.map +1 -1
- package/dist/workflows/v3/cli-run.js +2 -2
- package/dist/workflows/v3/cli-run.js.map +1 -1
- package/package.json +2 -2
|
Binary file
|
|
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
|
-
|
|
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
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
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 {
|