botmux 3.18.1 → 3.18.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 (42) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/bot-registry.d.ts +6 -0
  3. package/dist/bot-registry.d.ts.map +1 -1
  4. package/dist/bot-registry.js +25 -0
  5. package/dist/bot-registry.js.map +1 -1
  6. package/dist/core/worker-pool.d.ts.map +1 -1
  7. package/dist/core/worker-pool.js +17 -1
  8. package/dist/core/worker-pool.js.map +1 -1
  9. package/dist/daemon.d.ts.map +1 -1
  10. package/dist/daemon.js +6 -2
  11. package/dist/daemon.js.map +1 -1
  12. package/dist/dashboard.js +102 -7
  13. package/dist/dashboard.js.map +1 -1
  14. package/dist/i18n/en.d.ts.map +1 -1
  15. package/dist/i18n/en.js +6 -0
  16. package/dist/i18n/en.js.map +1 -1
  17. package/dist/i18n/zh.d.ts.map +1 -1
  18. package/dist/i18n/zh.js +6 -0
  19. package/dist/i18n/zh.js.map +1 -1
  20. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  21. package/dist/im/lark/event-dispatcher.js +10 -6
  22. package/dist/im/lark/event-dispatcher.js.map +1 -1
  23. package/dist/im/lark/issue-command-deps.d.ts.map +1 -1
  24. package/dist/im/lark/issue-command-deps.js +14 -5
  25. package/dist/im/lark/issue-command-deps.js.map +1 -1
  26. package/dist/im/lark/issue-command.d.ts +6 -3
  27. package/dist/im/lark/issue-command.d.ts.map +1 -1
  28. package/dist/im/lark/issue-command.js +23 -10
  29. package/dist/im/lark/issue-command.js.map +1 -1
  30. package/dist/im/lark/mention-mode-command.d.ts +2 -0
  31. package/dist/im/lark/mention-mode-command.d.ts.map +1 -0
  32. package/dist/im/lark/mention-mode-command.js +72 -0
  33. package/dist/im/lark/mention-mode-command.js.map +1 -0
  34. package/dist/services/chat-reply-mode-store.d.ts +21 -4
  35. package/dist/services/chat-reply-mode-store.d.ts.map +1 -1
  36. package/dist/services/chat-reply-mode-store.js +68 -1
  37. package/dist/services/chat-reply-mode-store.js.map +1 -1
  38. package/dist/services/vc-meeting-listener-output-protocol.d.ts +10 -0
  39. package/dist/services/vc-meeting-listener-output-protocol.d.ts.map +1 -1
  40. package/dist/services/vc-meeting-listener-output-protocol.js +12 -0
  41. package/dist/services/vc-meeting-listener-output-protocol.js.map +1 -1
  42. package/package.json +7 -7
package/dist/dashboard.js CHANGED
@@ -8,6 +8,7 @@ import { homedir } from 'node:os';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { createHmac, randomBytes } from 'node:crypto';
10
10
  import { logger } from './utils/logger.js';
11
+ import { isStandaloneBinary } from './core/self-spawn.js';
11
12
  import { gracefulProcessExitCode } from './pm2-graceful-exit.js';
12
13
  import { config, isWildcardBindHost } from './config.js';
13
14
  import { listenWithProbe } from './utils/listen-with-probe.js';
@@ -69,7 +70,7 @@ import { parseCloseResidual } from './core/close-residual.js';
69
70
  import { dashboardSecretPath } from './core/dashboard-secret.js';
70
71
  import { getGitRepoInfo } from './core/session-row-enrichment.js';
71
72
  import { deleteWhiteboard, listWhiteboards, readWhiteboard, whiteboardEnabled } from './services/whiteboard-store.js';
72
- import { isLocalDevInstall, botmuxVersionAt, botmuxCliEntryAt, botmuxInstallRoot } from './utils/install-info.js';
73
+ import { isLocalDevInstall, botmuxVersionAt, botmuxCliEntryAt, botmuxInstallRoot, bakedBinaryVersion } from './utils/install-info.js';
73
74
  import { checkNode, detectBotmuxInstalls, resolveCurrentVersion, resolveCurrentVersionAt } from './utils/install-diagnostics.js';
74
75
  import { fetchLatestVersion, fetchReleasesSince, fetchRollbackVersions, compareVersions, isCanonicalStableVersion, isNewerVersion, } from './core/update-check.js';
75
76
  import { GITHUB_REPO } from './core/restart-report.js';
@@ -1546,6 +1547,93 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
1546
1547
  const WEB_DIR = join(__dirname, 'dashboard-web');
1547
1548
  const DEV_RELOAD_MARKER = join(WEB_DIR, '.botmux-dashboard-dev');
1548
1549
  const DEV_RELOAD_VERSION = join(WEB_DIR, '.botmux-dashboard-reload');
1550
+ /**
1551
+ * Request-relative asset path → on-disk path to read, or null when we have no
1552
+ * such asset. The ONE place that knows whether the frontend lives on disk
1553
+ * (source/npm) or inside the compiled binary's virtual filesystem.
1554
+ *
1555
+ * WHY: `WEB_DIR` is derived from `__dirname`, which in the compiled single binary
1556
+ * is the virtual `/$bunfs/root` — a path that does not exist on disk (the
1557
+ * documented CLAUDE.md `__dirname` hazard). Every asset request therefore missed
1558
+ * and fell through to the catch-all 404: the login redirect landed on `/` and
1559
+ * returned `{"error":"not_found_yet","path":"/"}`, so the Dashboard was entirely
1560
+ * unreachable from a compiled binary while working fine from npm/source.
1561
+ *
1562
+ * In compiled mode the assets are embedded instead (see
1563
+ * scripts/generate-dashboard-embed.mjs) and Bun renames each one with a content
1564
+ * hash, so the request path cannot address them directly — the generated manifest
1565
+ * supplies the mapping. Extensions are preserved, so MIME lookup is unaffected.
1566
+ *
1567
+ * Path traversal is handled differently per mode, and both are closed:
1568
+ * • Embedded: the manifest is a build-time allowlist, so a traversal string
1569
+ * simply misses. The lookup still goes through `hasOwnProperty` and a string
1570
+ * check — a plain `map[rel]` answers `__proto__`/`constructor`/`toString` with
1571
+ * inherited objects and functions (MEASURED), and handing one of those onward
1572
+ * would only be stopped by a downstream throw. A security property should not
1573
+ * rest on that.
1574
+ * • On disk: the resolved path must stay inside WEB_DIR.
1575
+ *
1576
+ * Returns null (→ caller 404s) rather than throwing: an asset genuinely absent
1577
+ * from the bundle must stay a 404, exactly as a missing file on disk does.
1578
+ */
1579
+ function resolveWebAsset(rel) {
1580
+ const embedded = embeddedDashboardAssets();
1581
+ if (embedded) {
1582
+ if (!Object.prototype.hasOwnProperty.call(embedded, rel))
1583
+ return null;
1584
+ const fp = embedded[rel];
1585
+ return typeof fp === 'string' && fp.length > 0 ? fp : null;
1586
+ }
1587
+ // Source / npm install: real directory on disk. Keep the path-traversal guard
1588
+ // here — `rel` is derived from the request.
1589
+ const fp = resolve(WEB_DIR, rel);
1590
+ const relToRoot = relative(resolve(WEB_DIR), fp);
1591
+ if (relToRoot === '..' || relToRoot.startsWith('..\\') || relToRoot.startsWith('../') || isAbsolute(relToRoot))
1592
+ return null;
1593
+ return fp;
1594
+ }
1595
+ /**
1596
+ * The compiled binary's embedded-asset manifest, or null when running from
1597
+ * source/npm (where the frontend is a real directory on disk). Resolved once and
1598
+ * cached — including the null.
1599
+ *
1600
+ * HOW IT GETS HERE: the compiled build's plugin (scripts/bun-native-embed-plugin.mjs)
1601
+ * prepends a generated block of `import … with { type: 'file' }` statements to
1602
+ * this module and assigns the map to `globalThis.__BOTMUX_DASHBOARD_ASSETS__`.
1603
+ *
1604
+ * It has to arrive by build-time injection rather than an ordinary import:
1605
+ * • A static `import './dashboard-web-embedded.js'` would make `tsc` (and a
1606
+ * plain source checkout) demand a generated artifact that only exists after
1607
+ * `bun run dashboard:bundle`.
1608
+ * • A runtime `require()` of it does NOT work: Bun only embeds what it can trace
1609
+ * statically, and MEASURED, a `createRequire` require of the manifest bundled
1610
+ * 1 module and embedded zero assets — the binary would still 404 everything.
1611
+ */
1612
+ let embeddedAssetsCache;
1613
+ function embeddedDashboardAssets() {
1614
+ if (embeddedAssetsCache !== undefined)
1615
+ return embeddedAssetsCache;
1616
+ const injected = globalThis.__BOTMUX_DASHBOARD_ASSETS__;
1617
+ if (injected && typeof injected === 'object') {
1618
+ embeddedAssetsCache = injected;
1619
+ }
1620
+ else {
1621
+ embeddedAssetsCache = null;
1622
+ // A compiled binary with no injected manifest is a BUILD regression, not a
1623
+ // runtime condition to paper over. Say it loudly: the symptom is a 404 on
1624
+ // every page, which otherwise reads as a routing or auth bug.
1625
+ if (isStandaloneBinary()) {
1626
+ logger.error('[dashboard] compiled binary has no embedded frontend — every asset will 404');
1627
+ }
1628
+ }
1629
+ return embeddedAssetsCache;
1630
+ }
1631
+ /** Cache-busting stamp for embedded assets, whose `mtimeMs` is 0 (MEASURED).
1632
+ * The baked release version changes with every binary, so it invalidates a
1633
+ * browser's cached copy exactly when the binary changes. */
1634
+ function embeddedAssetStamp() {
1635
+ return (bakedBinaryVersion() ?? 'embedded').replace(/[^\w.-]/g, '');
1636
+ }
1549
1637
  const MIME = {
1550
1638
  '.html': 'text/html; charset=utf-8',
1551
1639
  '.js': 'application/javascript',
@@ -1610,12 +1698,15 @@ function injectDevReload(html) {
1610
1698
  }
1611
1699
  function serveStatic(req, res, pathname, options = {}) {
1612
1700
  const rel = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, '');
1613
- const fp = resolve(WEB_DIR, rel);
1614
- const webRoot = resolve(WEB_DIR);
1615
- const relToRoot = relative(webRoot, fp);
1616
- // Path-traversal guard: resolved path must stay inside WEB_DIR.
1617
- if (relToRoot === '..' || relToRoot.startsWith('..\\') || relToRoot.startsWith('../') || isAbsolute(relToRoot))
1701
+ const fp = resolveWebAsset(rel);
1702
+ if (!fp)
1618
1703
  return false;
1704
+ // Asset-identity decisions below key off the REQUEST path, not `fp`: in the
1705
+ // compiled binary `fp` is a content-hash-renamed file in the virtual fs
1706
+ // (`chunks/x.js` → `/$bunfs/root/x-<hash>.js`), so `relative(WEB_DIR, fp)`
1707
+ // would classify every embedded asset wrongly — no chunk would be immutable
1708
+ // and index.html would never get its CSRF shell injected.
1709
+ const relToRoot = rel;
1619
1710
  try {
1620
1711
  const st = statSync(fp);
1621
1712
  if (!st.isFile())
@@ -1624,7 +1715,11 @@ function serveStatic(req, res, pathname, options = {}) {
1624
1715
  // a deploy never serves new JS with old CSS. Lazy chunks are content-hashed
1625
1716
  // and can be cached immutably once the current app.js points at them.
1626
1717
  const immutableChunk = relToRoot.startsWith('chunks/') || relToRoot.startsWith('chunks\\');
1627
- const etag = `W/"${st.size.toString(16)}-${Math.floor(st.mtimeMs).toString(16)}"`;
1718
+ // `mtimeMs` is 0 for an embedded file (MEASURED), so the ETag would collapse
1719
+ // to size alone across a whole compiled release. Fold in the baked build
1720
+ // identity so a new binary always invalidates the browser's cache.
1721
+ const etagStamp = st.mtimeMs > 0 ? Math.floor(st.mtimeMs).toString(16) : embeddedAssetStamp();
1722
+ const etag = `W/"${st.size.toString(16)}-${etagStamp}"`;
1628
1723
  const isIndex = relToRoot === 'index.html';
1629
1724
  const devIndex = isIndex && dashboardDevReloadEnabled();
1630
1725
  // 注入 CSRF 票据的壳是每次请求现生成的:ETag 只反映磁盘文件,走 304 会让浏览