botmux 3.1.0 → 3.2.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 (204) hide show
  1. package/dist/adapters/backend/sandbox.d.ts +23 -0
  2. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  3. package/dist/adapters/backend/sandbox.js +69 -4
  4. package/dist/adapters/backend/sandbox.js.map +1 -1
  5. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  6. package/dist/adapters/backend/tmux-backend.js +11 -1
  7. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  8. package/dist/adapters/cli/read-isolation.d.ts +1 -1
  9. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  10. package/dist/adapters/cli/read-isolation.js +15 -7
  11. package/dist/adapters/cli/read-isolation.js.map +1 -1
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +117 -25
  14. package/dist/cli.js.map +1 -1
  15. package/dist/core/command-handler.d.ts.map +1 -1
  16. package/dist/core/command-handler.js +154 -90
  17. package/dist/core/command-handler.js.map +1 -1
  18. package/dist/core/dashboard-ipc-server.d.ts +1 -0
  19. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  20. package/dist/core/dashboard-ipc-server.js +132 -0
  21. package/dist/core/dashboard-ipc-server.js.map +1 -1
  22. package/dist/core/maintenance.d.ts +23 -1
  23. package/dist/core/maintenance.d.ts.map +1 -1
  24. package/dist/core/maintenance.js +63 -25
  25. package/dist/core/maintenance.js.map +1 -1
  26. package/dist/core/plugins/cli-generation.d.ts.map +1 -1
  27. package/dist/core/plugins/cli-generation.js +7 -0
  28. package/dist/core/plugins/cli-generation.js.map +1 -1
  29. package/dist/core/plugins/convention-scanner.d.ts +3 -3
  30. package/dist/core/plugins/convention-scanner.d.ts.map +1 -1
  31. package/dist/core/plugins/convention-scanner.js.map +1 -1
  32. package/dist/core/plugins/install.d.ts.map +1 -1
  33. package/dist/core/plugins/install.js +84 -26
  34. package/dist/core/plugins/install.js.map +1 -1
  35. package/dist/core/plugins/mcp/environment.d.ts +12 -0
  36. package/dist/core/plugins/mcp/environment.d.ts.map +1 -0
  37. package/dist/core/plugins/mcp/environment.js +17 -0
  38. package/dist/core/plugins/mcp/environment.js.map +1 -0
  39. package/dist/core/plugins/mcp/gateway-installer.d.ts.map +1 -1
  40. package/dist/core/plugins/mcp/gateway-installer.js +9 -3
  41. package/dist/core/plugins/mcp/gateway-installer.js.map +1 -1
  42. package/dist/core/plugins/mcp/gateway.d.ts +13 -1
  43. package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
  44. package/dist/core/plugins/mcp/gateway.js +143 -23
  45. package/dist/core/plugins/mcp/gateway.js.map +1 -1
  46. package/dist/core/plugins/mcp/host.d.ts +18 -0
  47. package/dist/core/plugins/mcp/host.d.ts.map +1 -0
  48. package/dist/core/plugins/mcp/host.js +126 -0
  49. package/dist/core/plugins/mcp/host.js.map +1 -0
  50. package/dist/core/plugins/mcp/private-store.d.ts +15 -0
  51. package/dist/core/plugins/mcp/private-store.d.ts.map +1 -0
  52. package/dist/core/plugins/mcp/private-store.js +162 -0
  53. package/dist/core/plugins/mcp/private-store.js.map +1 -0
  54. package/dist/core/plugins/mcp/session-runtime.d.ts +25 -0
  55. package/dist/core/plugins/mcp/session-runtime.d.ts.map +1 -0
  56. package/dist/core/plugins/mcp/session-runtime.js +76 -0
  57. package/dist/core/plugins/mcp/session-runtime.js.map +1 -0
  58. package/dist/core/plugins/mcp/socket-auth.d.ts +9 -0
  59. package/dist/core/plugins/mcp/socket-auth.d.ts.map +1 -0
  60. package/dist/core/plugins/mcp/socket-auth.js +94 -0
  61. package/dist/core/plugins/mcp/socket-auth.js.map +1 -0
  62. package/dist/core/plugins/paths.d.ts +3 -0
  63. package/dist/core/plugins/paths.d.ts.map +1 -1
  64. package/dist/core/plugins/paths.js +9 -0
  65. package/dist/core/plugins/paths.js.map +1 -1
  66. package/dist/core/plugins/pm2.d.ts.map +1 -1
  67. package/dist/core/plugins/pm2.js +3 -1
  68. package/dist/core/plugins/pm2.js.map +1 -1
  69. package/dist/core/plugins/runtime.d.ts +2 -0
  70. package/dist/core/plugins/runtime.d.ts.map +1 -1
  71. package/dist/core/plugins/runtime.js.map +1 -1
  72. package/dist/core/plugins/service-manager.d.ts +22 -0
  73. package/dist/core/plugins/service-manager.d.ts.map +1 -1
  74. package/dist/core/plugins/service-manager.js +189 -52
  75. package/dist/core/plugins/service-manager.js.map +1 -1
  76. package/dist/core/plugins/types.d.ts +15 -1
  77. package/dist/core/plugins/types.d.ts.map +1 -1
  78. package/dist/core/restart-report.d.ts +1 -1
  79. package/dist/core/restart-report.d.ts.map +1 -1
  80. package/dist/core/restart-report.js +5 -3
  81. package/dist/core/restart-report.js.map +1 -1
  82. package/dist/core/scheduler.d.ts +47 -0
  83. package/dist/core/scheduler.d.ts.map +1 -1
  84. package/dist/core/scheduler.js +117 -0
  85. package/dist/core/scheduler.js.map +1 -1
  86. package/dist/core/session-manager.d.ts +7 -0
  87. package/dist/core/session-manager.d.ts.map +1 -1
  88. package/dist/core/session-manager.js +130 -33
  89. package/dist/core/session-manager.js.map +1 -1
  90. package/dist/core/session-marker.d.ts.map +1 -1
  91. package/dist/core/session-marker.js +5 -2
  92. package/dist/core/session-marker.js.map +1 -1
  93. package/dist/core/silent-schedule-turns.d.ts +5 -0
  94. package/dist/core/silent-schedule-turns.d.ts.map +1 -0
  95. package/dist/core/silent-schedule-turns.js +47 -0
  96. package/dist/core/silent-schedule-turns.js.map +1 -0
  97. package/dist/core/types.d.ts +54 -0
  98. package/dist/core/types.d.ts.map +1 -1
  99. package/dist/core/types.js +55 -0
  100. package/dist/core/types.js.map +1 -1
  101. package/dist/core/update-check.d.ts +16 -0
  102. package/dist/core/update-check.d.ts.map +1 -1
  103. package/dist/core/update-check.js +52 -0
  104. package/dist/core/update-check.js.map +1 -1
  105. package/dist/core/worker-pool.d.ts.map +1 -1
  106. package/dist/core/worker-pool.js +40 -31
  107. package/dist/core/worker-pool.js.map +1 -1
  108. package/dist/daemon.d.ts.map +1 -1
  109. package/dist/daemon.js +90 -9
  110. package/dist/daemon.js.map +1 -1
  111. package/dist/dashboard/schedule-card-model.d.ts +3 -0
  112. package/dist/dashboard/schedule-card-model.d.ts.map +1 -1
  113. package/dist/dashboard/schedule-card-model.js +5 -0
  114. package/dist/dashboard/schedule-card-model.js.map +1 -1
  115. package/dist/dashboard/web/app.d.ts.map +1 -1
  116. package/dist/dashboard/web/app.js +295 -6
  117. package/dist/dashboard/web/app.js.map +1 -1
  118. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  119. package/dist/dashboard/web/i18n.js +92 -0
  120. package/dist/dashboard/web/i18n.js.map +1 -1
  121. package/dist/dashboard/web/plugin-page.d.ts.map +1 -1
  122. package/dist/dashboard/web/plugin-page.js +1 -3
  123. package/dist/dashboard/web/plugin-page.js.map +1 -1
  124. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  125. package/dist/dashboard/web/schedules-page.js +111 -4
  126. package/dist/dashboard/web/schedules-page.js.map +1 -1
  127. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  128. package/dist/dashboard/web/settings-page.js +5 -1
  129. package/dist/dashboard/web/settings-page.js.map +1 -1
  130. package/dist/dashboard/web/update-action.d.ts +16 -0
  131. package/dist/dashboard/web/update-action.d.ts.map +1 -0
  132. package/dist/dashboard/web/update-action.js +64 -0
  133. package/dist/dashboard/web/update-action.js.map +1 -0
  134. package/dist/dashboard-web/app.js +1 -1
  135. package/dist/dashboard-web/chunks/{bot-defaults-page-KLDCZNLP.js → bot-defaults-page-4PM4T6QQ.js} +1 -1
  136. package/dist/dashboard-web/chunks/{chunk-FMJNOATE.js → chunk-3PMN55Z6.js} +1 -1
  137. package/dist/dashboard-web/chunks/{chunk-MLNI5BKE.js → chunk-AIDN2WZB.js} +1 -1
  138. package/dist/dashboard-web/chunks/{chunk-EZAW2EPM.js → chunk-IKCZ677K.js} +1 -1
  139. package/dist/dashboard-web/chunks/{chunk-KEAOTUL3.js → chunk-RXVA6XW3.js} +7 -7
  140. package/dist/dashboard-web/chunks/{connectors-page-O32LFAL5.js → connectors-page-2MKF4YTN.js} +1 -1
  141. package/dist/dashboard-web/chunks/{groups-page-PSAFIZ6M.js → groups-page-TAU2UIVS.js} +1 -1
  142. package/dist/dashboard-web/chunks/{insights-page-V23LLATX.js → insights-page-PJ2XVM6T.js} +1 -1
  143. package/dist/dashboard-web/chunks/{monitor-room-7X5AKYOF.js → monitor-room-D6445HJO.js} +1 -1
  144. package/dist/dashboard-web/chunks/{monitoring-page-CBA3CQYL.js → monitoring-page-GUHMCKSO.js} +1 -1
  145. package/dist/dashboard-web/chunks/{office-page-RL6QSEM7.js → office-page-6K6U2MS5.js} +1 -1
  146. package/dist/dashboard-web/chunks/{overview-page-XQIPO6KH.js → overview-page-USDYU7U5.js} +1 -1
  147. package/dist/dashboard-web/chunks/plugin-page-JXC5F5YV.js +1 -0
  148. package/dist/dashboard-web/chunks/{roles-page-ACIWV7QS.js → roles-page-O4OUA7ZH.js} +1 -1
  149. package/dist/dashboard-web/chunks/schedules-page-LNIBXJSZ.js +1 -0
  150. package/dist/dashboard-web/chunks/{sessions-page-2WDWHSP4.js → sessions-page-B6U6TCFF.js} +1 -1
  151. package/dist/dashboard-web/chunks/{settings-page-6VX4OUGG.js → settings-page-PXLRFF7I.js} +2 -2
  152. package/dist/dashboard-web/chunks/{skills-page-WXE3UYLT.js → skills-page-M5KYNXBA.js} +1 -1
  153. package/dist/dashboard-web/chunks/{team-federation-page-PYTF4FLM.js → team-federation-page-GT7UFQMN.js} +1 -1
  154. package/dist/dashboard-web/chunks/{v3-page-G6CD6O3I.js → v3-page-UYZIRJ2C.js} +1 -1
  155. package/dist/dashboard-web/chunks/{whiteboards-page-6SG33GP4.js → whiteboards-page-WAR77DQE.js} +1 -1
  156. package/dist/dashboard-web/style.css +697 -0
  157. package/dist/dashboard.js +404 -29
  158. package/dist/dashboard.js.map +1 -1
  159. package/dist/i18n/en.d.ts.map +1 -1
  160. package/dist/i18n/en.js +5 -0
  161. package/dist/i18n/en.js.map +1 -1
  162. package/dist/i18n/zh.d.ts.map +1 -1
  163. package/dist/i18n/zh.js +5 -0
  164. package/dist/i18n/zh.js.map +1 -1
  165. package/dist/im/lark/card-handler.d.ts +2 -0
  166. package/dist/im/lark/card-handler.d.ts.map +1 -1
  167. package/dist/im/lark/card-handler.js +219 -139
  168. package/dist/im/lark/card-handler.js.map +1 -1
  169. package/dist/im/lark/schedules-card.d.ts.map +1 -1
  170. package/dist/im/lark/schedules-card.js +2 -0
  171. package/dist/im/lark/schedules-card.js.map +1 -1
  172. package/dist/services/plugin-registry-store.d.ts.map +1 -1
  173. package/dist/services/plugin-registry-store.js +100 -18
  174. package/dist/services/plugin-registry-store.js.map +1 -1
  175. package/dist/services/restart-intent-store.d.ts +17 -2
  176. package/dist/services/restart-intent-store.d.ts.map +1 -1
  177. package/dist/services/restart-intent-store.js +110 -0
  178. package/dist/services/restart-intent-store.js.map +1 -1
  179. package/dist/services/schedule-store.d.ts +3 -1
  180. package/dist/services/schedule-store.d.ts.map +1 -1
  181. package/dist/services/schedule-store.js +5 -0
  182. package/dist/services/schedule-store.js.map +1 -1
  183. package/dist/skills/definitions.d.ts.map +1 -1
  184. package/dist/skills/definitions.js +3 -2
  185. package/dist/skills/definitions.js.map +1 -1
  186. package/dist/types.d.ts +18 -0
  187. package/dist/types.d.ts.map +1 -1
  188. package/dist/utils/child-env.d.ts +9 -1
  189. package/dist/utils/child-env.d.ts.map +1 -1
  190. package/dist/utils/child-env.js +24 -0
  191. package/dist/utils/child-env.js.map +1 -1
  192. package/dist/utils/global-install.d.ts +2 -0
  193. package/dist/utils/global-install.d.ts.map +1 -1
  194. package/dist/utils/global-install.js +14 -0
  195. package/dist/utils/global-install.js.map +1 -1
  196. package/dist/worker.js +135 -18
  197. package/dist/worker.js.map +1 -1
  198. package/dist/workflows/hostExecutors/botmux-schedule.d.ts +3 -0
  199. package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -1
  200. package/dist/workflows/hostExecutors/botmux-schedule.js +9 -0
  201. package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -1
  202. package/package.json +1 -1
  203. package/dist/dashboard-web/chunks/plugin-page-7FW2LPJH.js +0 -1
  204. package/dist/dashboard-web/chunks/schedules-page-KJDUKOJV.js +0 -1
package/dist/worker.js CHANGED
@@ -15,8 +15,8 @@
15
15
  import { randomBytes } from 'node:crypto';
16
16
  import { mkdirSync, writeFileSync, unlinkSync, existsSync, statSync, readdirSync, readlinkSync, readFileSync, realpathSync, copyFileSync, watch as fsWatch, createWriteStream, openSync, closeSync, fstatSync, constants as fsConstants } from 'node:fs';
17
17
  import { atomicWriteFileSync } from './utils/atomic-write.js';
18
- import { join, basename } from 'node:path';
19
- import { homedir } from 'node:os';
18
+ import { join, basename, dirname } from 'node:path';
19
+ import { homedir, tmpdir } from 'node:os';
20
20
  import { spawnSync } from 'node:child_process';
21
21
  import { evaluateReadIsolationGate, buildSeatbeltProfile, isolatedPaneReattachSafe, sendCredFilePath, botHomePath, buildV2DenyPaths, buildV2DenyRegexes, buildV2CarveOuts, buildReadIsolationProtectedWriteRules, buildCliExecutableReadCarveOuts, buildWriteSandboxRules, buildLinuxReadIsolationMasks, isolationPaneMarkerContent, } from './adapters/cli/read-isolation.js';
22
22
  import { killPersistentSession } from './core/persistent-backend.js';
@@ -35,7 +35,10 @@ import { shouldRunStartupCommandsOnSpawn, shouldDeferInitialPromptForStartup } f
35
35
  import { sanitizePerBotEnv } from './core/per-bot-env.js';
36
36
  import { evaluateVcMeetingManagedSend, } from './services/vc-meeting-send-policy.js';
37
37
  import { TurnTerminalDeduper } from './services/turn-terminal-deduper.js';
38
- import { ensureGatewayEntry } from './core/plugins/mcp/gateway-installer.js';
38
+ import { defaultGatewayEntry, ensureGatewayEntry } from './core/plugins/mcp/gateway-installer.js';
39
+ import { sessionMcpGatewayPathRegex, startSessionMcpGatewayHost, } from './core/plugins/mcp/host.js';
40
+ import { MCP_GATEWAY_REQUIRED_ENV, MCP_GATEWAY_SOCKET_ENV, } from './core/plugins/mcp/environment.js';
41
+ import { readSessionMcpRuntimeManifest, sessionMcpRuntimeHostOnlyPaths, } from './core/plugins/mcp/session-runtime.js';
39
42
  import { prepareCliPluginGeneration } from './core/plugins/cli-generation.js';
40
43
  import { loadBotConfigs } from './bot-registry.js';
41
44
  import { readGlobalConfig } from './global-config.js';
@@ -59,7 +62,6 @@ import { drainCursorTranscript, findCursorChatIdByPid, findCursorTranscriptByCha
59
62
  import { shouldObserveCursorChatId, shouldPersistObservedCursorChatId } from './services/cursor-resume-policy.js';
60
63
  import { extractKiroSessionIdFromOutput } from './services/kiro-session.js';
61
64
  import { baselineJsonlCursor } from './services/jsonl-cursor.js';
62
- import { dirname } from 'node:path';
63
65
  import { createServer as createHttpServer } from 'node:http';
64
66
  import { WebSocketServer, WebSocket } from 'ws';
65
67
  import { listenWebTerminalWithFallback } from './utils/web-terminal-listen.js';
@@ -121,6 +123,7 @@ let sandboxRelayOutbox = null;
121
123
  let sandboxRelayCapability = null;
122
124
  let readIsolationOriginCapabilityFile = null;
123
125
  let sandboxTeardownDone = false; // guards the exit-time best-effort teardown from double-running / running on suspend-for-resume
126
+ let sessionMcpGatewayHost = null;
124
127
  /** Counts consecutive in-worker restart cycles (see case 'restart'). Used by
125
128
  * the SECONDARY guard so an adapter whose checkResumeTargetExists misses
126
129
  * (returns undefined) or whose resume target vanishes between the check and
@@ -136,6 +139,15 @@ let tmuxRestartTimer = null;
136
139
  let resumeFallbackNotified = false;
137
140
  /** Skill catalog to attach to the first user turn after a prompt-less CLI restart. */
138
141
  let deferredPluginSkillCatalog = null;
142
+ function stopSessionMcpGatewayHost() {
143
+ const host = sessionMcpGatewayHost;
144
+ sessionMcpGatewayHost = null;
145
+ if (!host)
146
+ return;
147
+ void host.close().catch((error) => {
148
+ log(`[mcp-gateway] host shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
149
+ });
150
+ }
139
151
  function refreshCliPluginGeneration(cfg, adapter) {
140
152
  let bot = {
141
153
  larkAppId: cfg.larkAppId,
@@ -749,6 +761,15 @@ async function deliverRawInput(msg) {
749
761
  const targetBackend = backend;
750
762
  if (!targetBackend)
751
763
  return;
764
+ // A passthrough is still an accepted input boundary. Rotate (or revoke) the
765
+ // marker immediately before the literal command reaches the CLI so it can
766
+ // never inherit a previous human turn. System-generated raw commands omit
767
+ // turnId and therefore publish an explicitly unattributed marker.
768
+ currentBotmuxTurnId = msg.turnId;
769
+ currentBotmuxDispatchAttempt = undefined;
770
+ currentVcMeetingImTurnOrigin = undefined;
771
+ writeCliPidMarker();
772
+ publishSandboxRelayCapability();
752
773
  let sent = false;
753
774
  try {
754
775
  await sendRawCommandLineSerially(targetBackend, msg.content);
@@ -766,7 +787,7 @@ async function deliverRawInput(msg) {
766
787
  // Enter lands. sendToPty also observes commandLineWritesPending, so another
767
788
  // raw command's text -> Enter window cannot be interrupted by the follow-up.
768
789
  if (sent && msg.followUpContent) {
769
- sendToPty(msg.followUpContent, undefined, {
790
+ sendToPty(msg.followUpContent, msg.followUpTurnId, {
770
791
  codexAppInput: msg.followUpCodexAppInput,
771
792
  });
772
793
  log(`Enqueued follow-up after raw input (${msg.followUpContent.length} chars)`);
@@ -3421,7 +3442,12 @@ function startScreenAnalyzer() {
3421
3442
  },
3422
3443
  onTuiPromptResolved: (selectedText) => {
3423
3444
  tuiPromptBlocking = false;
3424
- send({ type: 'tui_prompt_resolved', selectedText });
3445
+ send({
3446
+ type: 'tui_prompt_resolved',
3447
+ selectedText,
3448
+ turnId: currentBotmuxTurnId,
3449
+ dispatchAttempt: currentBotmuxDispatchAttempt,
3450
+ });
3425
3451
  // Flush any messages that were queued during the prompt
3426
3452
  flushPending();
3427
3453
  },
@@ -3570,7 +3596,13 @@ async function captureAndUpload() {
3570
3596
  let status = isPromptReady ? 'idle' : 'working';
3571
3597
  if (screenAnalyzer?.isAnalyzing)
3572
3598
  status = 'analyzing';
3573
- send({ type: 'screenshot_uploaded', imageKey, ...usageLimitTracker.classify(usageLimitContent, status) });
3599
+ send({
3600
+ type: 'screenshot_uploaded',
3601
+ imageKey,
3602
+ ...usageLimitTracker.classify(usageLimitContent, status),
3603
+ turnId: currentBotmuxTurnId,
3604
+ dispatchAttempt: currentBotmuxDispatchAttempt,
3605
+ });
3574
3606
  }
3575
3607
  function applyDisplayMode(mode) {
3576
3608
  displayMode = mode;
@@ -4183,7 +4215,12 @@ function persistCliSessionId(cliSessionId) {
4183
4215
  return;
4184
4216
  if (lastInitConfig)
4185
4217
  lastInitConfig.cliSessionId = cliSessionId;
4186
- send({ type: 'cli_session_id', cliSessionId });
4218
+ send({
4219
+ type: 'cli_session_id',
4220
+ cliSessionId,
4221
+ turnId: currentBotmuxTurnId,
4222
+ dispatchAttempt: currentBotmuxDispatchAttempt,
4223
+ });
4187
4224
  try {
4188
4225
  const session = sessionStore.getSession(sessionId);
4189
4226
  if (!session || session.cliSessionId === cliSessionId)
@@ -4753,7 +4790,12 @@ function sendToPty(content, turnId, opts = {}) {
4753
4790
  tuiPromptBlocking = false;
4754
4791
  screenAnalyzer?.notifySelection('lark-input');
4755
4792
  // Tear down the prompt card so the user doesn't see stale options.
4756
- send({ type: 'tui_prompt_resolved', selectedText: 'user-override' });
4793
+ send({
4794
+ type: 'tui_prompt_resolved',
4795
+ selectedText: 'user-override',
4796
+ turnId: currentBotmuxTurnId,
4797
+ dispatchAttempt: currentBotmuxDispatchAttempt,
4798
+ });
4757
4799
  }
4758
4800
  // See flushPending: type-ahead adapters flush even while the CLI is busy.
4759
4801
  // Claude attributes `attachment(queued_command)` identically to `role:user`;
@@ -5146,7 +5188,7 @@ function scheduleBusyPatternIdleProbe(source) {
5146
5188
  busyPatternIdleProbeTimer = setTimeout(tick, IDLE_PROBE_INTERVAL_MS);
5147
5189
  busyPatternIdleProbeTimer.unref?.();
5148
5190
  }
5149
- function spawnCli(cfg) {
5191
+ async function spawnCli(cfg) {
5150
5192
  clearSessionRenameInFlight();
5151
5193
  // (startupCommands one-shot is re-armed below, AFTER the reattach-vs-fresh
5152
5194
  // prediction — only a genuinely fresh CLI process replays them; see
@@ -5201,7 +5243,12 @@ function spawnCli(cfg) {
5201
5243
  setupAdoptIdleDetection(cfg, 'herdr');
5202
5244
  backend.onData(onPtyData);
5203
5245
  backend.onAccessUrl?.((url) => {
5204
- send({ type: 'riff_access_url', accessUrl: url });
5246
+ send({
5247
+ type: 'riff_access_url',
5248
+ accessUrl: url,
5249
+ turnId: currentBotmuxTurnId,
5250
+ dispatchAttempt: currentBotmuxDispatchAttempt,
5251
+ });
5205
5252
  });
5206
5253
  backend.onExit((code, signal) => {
5207
5254
  log(`Adopted herdr stream ended (code: ${code}, signal: ${signal})`);
@@ -5254,7 +5301,12 @@ function spawnCli(cfg) {
5254
5301
  setupAdoptInputAdapter(cfg);
5255
5302
  backend.onData(onPtyData);
5256
5303
  backend.onAccessUrl?.((url) => {
5257
- send({ type: 'riff_access_url', accessUrl: url });
5304
+ send({
5305
+ type: 'riff_access_url',
5306
+ accessUrl: url,
5307
+ turnId: currentBotmuxTurnId,
5308
+ dispatchAttempt: currentBotmuxDispatchAttempt,
5309
+ });
5258
5310
  });
5259
5311
  backend.onExit((code, signal) => {
5260
5312
  log(`Adopted pipe-pane stream ended (code: ${code}, signal: ${signal})`);
@@ -5597,6 +5649,21 @@ function spawnCli(cfg) {
5597
5649
  }
5598
5650
  }
5599
5651
  }
5652
+ let mcpRuntimeManifest = readSessionMcpRuntimeManifest(cfg.sessionId, config.session.dataDir);
5653
+ if (cliAdapter.mcpGateway && mcpRuntimeManifest?.entries.length && persistentSessionName && effectiveBackendType !== 'pty') {
5654
+ const paneLive = effectiveBackendType === 'tmux'
5655
+ ? TmuxBackend.hasSession(persistentSessionName)
5656
+ : effectiveBackendType === 'zellij'
5657
+ ? ZellijBackend.hasSession(persistentSessionName)
5658
+ : HerdrBackend.hasSession(persistentSessionName);
5659
+ if (paneLive) {
5660
+ // The trusted Gateway host belongs to the worker and cannot survive a
5661
+ // worker/daemon replacement. Cold-resume the CLI so its MCP client gets a
5662
+ // fresh relay socket instead of reattaching to a dead connection.
5663
+ log(`[mcp-gateway] persistent pane ${cfg.sessionId} has plugin MCP state — cold-resuming with a fresh host`);
5664
+ killPersistentSession(effectiveBackendType, persistentSessionName);
5665
+ }
5666
+ }
5600
5667
  const willReattachPersistent = persistentSessionName
5601
5668
  ? effectiveBackendType === 'tmux'
5602
5669
  ? TmuxBackend.hasSession(persistentSessionName)
@@ -5607,8 +5674,19 @@ function spawnCli(cfg) {
5607
5674
  // The plugin set is stable only for the lifetime of one real CLI process.
5608
5675
  // A warm worker reattach keeps the existing Gateway and catalog untouched;
5609
5676
  // every fresh/resumed CLI spawn atomically refreshes both from current Bot config.
5610
- if (!willReattachPersistent)
5677
+ if (!willReattachPersistent) {
5611
5678
  refreshCliPluginGeneration(cfg, cliAdapter);
5679
+ mcpRuntimeManifest = readSessionMcpRuntimeManifest(cfg.sessionId, config.session.dataDir);
5680
+ stopSessionMcpGatewayHost();
5681
+ if (cliAdapter.mcpGateway && mcpRuntimeManifest?.entries.length) {
5682
+ sessionMcpGatewayHost = await startSessionMcpGatewayHost({
5683
+ sessionId: cfg.sessionId,
5684
+ dataDir: config.session.dataDir,
5685
+ onError: error => log(`[mcp-gateway] host error: ${error.message}`),
5686
+ });
5687
+ log(`[mcp-gateway] trusted host listening for ${mcpRuntimeManifest.entries.length} plugin server(s)`);
5688
+ }
5689
+ }
5612
5690
  // Re-arm the startup-commands one-shot ONLY for a genuinely fresh CLI process.
5613
5691
  // A reattach to a LIVE persistent pane (daemon-restart recovery) is the SAME
5614
5692
  // CLI with /effort etc. already applied — replaying would re-type them (and
@@ -5890,6 +5968,14 @@ function spawnCli(cfg) {
5890
5968
  // namespaced BOTMUX_LARK_APP_ID injected below; the worker keeps its own
5891
5969
  // bare creds (forkWorker) for lark-upload. See utils/child-env.ts.
5892
5970
  const childEnv = redactChildEnv(process.env);
5971
+ if (sessionMcpGatewayHost) {
5972
+ childEnv[MCP_GATEWAY_SOCKET_ENV] = sessionMcpGatewayHost.socketPath;
5973
+ childEnv[MCP_GATEWAY_REQUIRED_ENV] = '1';
5974
+ }
5975
+ else {
5976
+ delete childEnv[MCP_GATEWAY_SOCKET_ENV];
5977
+ delete childEnv[MCP_GATEWAY_REQUIRED_ENV];
5978
+ }
5893
5979
  // Put the daemon-written wrapper dir (~/.botmux/bin/botmux = THIS build) ahead of any
5894
5980
  // stale npm-global botmux in PATH, so the agent's `botmux` is always this build. Matters
5895
5981
  // most under read isolation: only this build has the send-cred reader — a shadowing stale
@@ -6032,6 +6118,24 @@ function spawnCli(cfg) {
6032
6118
  sessionDataDirs: [readIsolationCtx.sessionDataDir],
6033
6119
  });
6034
6120
  }
6121
+ if (mcpRuntimeManifest) {
6122
+ finalDenyPaths.push(...sessionMcpRuntimeHostOnlyPaths(mcpRuntimeManifest, config.session.dataDir).map(canonical));
6123
+ }
6124
+ // Every isolated macOS CLI is denied access to all same-UID Gateway socket
6125
+ // directories. A session with its own Gateway gets one narrow, later READ
6126
+ // carve-out for its token; writes stay denied so the CLI cannot unlink or
6127
+ // replace even its own worker-managed socket. Sessions without plugin MCP
6128
+ // state get no read carve-out at all.
6129
+ const gatewaySocketRoot = canonical(sessionMcpGatewayHost ? dirname(sessionMcpGatewayHost.socketDir) : tmpdir());
6130
+ const gatewaySocketRegex = sessionMcpGatewayPathRegex(gatewaySocketRoot);
6131
+ denyRegexes.push(gatewaySocketRegex);
6132
+ if (sessionMcpGatewayHost)
6133
+ allowPaths.push(canonical(sessionMcpGatewayHost.socketDir));
6134
+ protectedWrites = {
6135
+ denyWritePaths: protectedWrites?.denyWritePaths ?? [],
6136
+ denyWriteRegexes: [...(protectedWrites?.denyWriteRegexes ?? []), gatewaySocketRegex],
6137
+ denyWriteLiterals: protectedWrites?.denyWriteLiterals ?? [],
6138
+ };
6035
6139
  // WRITE rules — the macOS file-sandbox (Linux-bwrap twin). Realpath the writable
6036
6140
  // zones + crown-jewel re-denies for the same symlink-safety reason as reads.
6037
6141
  const writeRules = writeSandboxRules
@@ -6218,7 +6322,12 @@ function spawnCli(cfg) {
6218
6322
  ? [sendCredFilePath(dataDir, cfg.larkAppId)]
6219
6323
  : [],
6220
6324
  extraExecPaths: cliAdapter.sandboxExtraExecPaths?.(),
6221
- readonlyRoots: [...(cfg.skillReadonlyRoots ?? []), ...(readIsoLinuxMasks?.ownReadOnlyPaths ?? [])],
6325
+ readonlyRoots: [
6326
+ ...(cfg.skillReadonlyRoots ?? []),
6327
+ ...(readIsoLinuxMasks?.ownReadOnlyPaths ?? []),
6328
+ ],
6329
+ mcpGatewaySocketPath: sessionMcpGatewayHost?.socketPath,
6330
+ trustedBotmuxCommandPaths: [defaultGatewayEntry().command],
6222
6331
  userReadonlyPaths: cfg.sandboxReadonlyPaths ?? [],
6223
6332
  net: cfg.sandboxNetwork !== false,
6224
6333
  });
@@ -6633,7 +6742,12 @@ function spawnCli(cfg) {
6633
6742
  backend.onData(onPtyData);
6634
6743
  const observedBackend = backend;
6635
6744
  backend.onAccessUrl?.((url) => {
6636
- send({ type: 'riff_access_url', accessUrl: url });
6745
+ send({
6746
+ type: 'riff_access_url',
6747
+ accessUrl: url,
6748
+ turnId: currentBotmuxTurnId,
6749
+ dispatchAttempt: currentBotmuxDispatchAttempt,
6750
+ });
6637
6751
  });
6638
6752
  // Remote-task turn boundary (riff): flushPending() marks the session busy on
6639
6753
  // every write and riff has no PTY output, so the idle detector never re-arms
@@ -6663,6 +6777,7 @@ function spawnCli(cfg) {
6663
6777
  log(`Ignored stale backend exit (code: ${code}, signal: ${signal})`);
6664
6778
  return;
6665
6779
  }
6780
+ stopSessionMcpGatewayHost();
6666
6781
  const exitedTurnId = currentBotmuxTurnId;
6667
6782
  const exitedDispatchAttempt = currentBotmuxDispatchAttempt;
6668
6783
  // Fail closed as soon as this CLI generation ends. The Node worker may
@@ -6786,6 +6901,7 @@ function spawnCli(cfg) {
6786
6901
  }
6787
6902
  }
6788
6903
  function killCli(opts = {}) {
6904
+ stopSessionMcpGatewayHost();
6789
6905
  destroyCrashDiagnosticTerminal('killCli');
6790
6906
  idleDetector?.dispose();
6791
6907
  idleDetector = null;
@@ -6916,7 +7032,7 @@ async function restartCliProcess(reason, opts = {}) {
6916
7032
  startScreenUpdates();
6917
7033
  startScreenAnalyzer();
6918
7034
  try {
6919
- spawnCli({ ...lastInitConfig, resume: true, prompt: '' });
7035
+ await spawnCli({ ...lastInitConfig, resume: true, prompt: '' });
6920
7036
  }
6921
7037
  catch (err) {
6922
7038
  cliRestartInProgress = false;
@@ -8181,7 +8297,7 @@ process.on('message', async (raw) => {
8181
8297
  port = await startWebServer(config.web.workerHost, msg.webPort);
8182
8298
  log('Workflow worker mode: web terminal enabled; skipping screen updates and screen analyzer');
8183
8299
  }
8184
- spawnCli(msg);
8300
+ await spawnCli(msg);
8185
8301
  // Queue the initial prompt — flushed when CLI shows idle.
8186
8302
  // Adapters with passesInitialPromptViaArgs (e.g. Gemini -i) bake the
8187
8303
  // prompt into CLI args, so we normally skip queuing to avoid double-send.
@@ -8270,7 +8386,7 @@ process.on('message', async (raw) => {
8270
8386
  startScreenUpdates();
8271
8387
  startScreenAnalyzer();
8272
8388
  try {
8273
- spawnCli({ ...lastInitConfig, resume: true, prompt: '' });
8389
+ await spawnCli({ ...lastInitConfig, resume: true, prompt: '' });
8274
8390
  }
8275
8391
  catch (err) {
8276
8392
  // Pass the message's own attempt (not the stale currentBotmux* from a
@@ -8657,6 +8773,7 @@ process.on('message', async (raw) => {
8657
8773
  });
8658
8774
  // ─── Cleanup ─────────────────────────────────────────────────────────────────
8659
8775
  function cleanup() {
8776
+ stopSessionMcpGatewayHost();
8660
8777
  if (tmuxRestartTimer) {
8661
8778
  clearTimeout(tmuxRestartTimer);
8662
8779
  tmuxRestartTimer = null;