botmux 3.15.0 → 3.16.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 (780) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/.runtime-build-id +1 -1
  4. package/dist/adapters/backend/destroy-result.d.ts +159 -0
  5. package/dist/adapters/backend/destroy-result.d.ts.map +1 -0
  6. package/dist/adapters/backend/destroy-result.js +223 -0
  7. package/dist/adapters/backend/destroy-result.js.map +1 -0
  8. package/dist/adapters/backend/herdr-backend.d.ts +4 -0
  9. package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
  10. package/dist/adapters/backend/herdr-backend.js +23 -1
  11. package/dist/adapters/backend/herdr-backend.js.map +1 -1
  12. package/dist/adapters/backend/mojo-backend.d.ts +577 -0
  13. package/dist/adapters/backend/mojo-backend.d.ts.map +1 -0
  14. package/dist/adapters/backend/mojo-backend.js +2471 -0
  15. package/dist/adapters/backend/mojo-backend.js.map +1 -0
  16. package/dist/adapters/backend/mojo-budgets.d.ts +21 -0
  17. package/dist/adapters/backend/mojo-budgets.d.ts.map +1 -0
  18. package/dist/adapters/backend/mojo-budgets.js +22 -0
  19. package/dist/adapters/backend/mojo-budgets.js.map +1 -0
  20. package/dist/adapters/backend/mojo-isolated-workspace.d.ts +69 -0
  21. package/dist/adapters/backend/mojo-isolated-workspace.d.ts.map +1 -0
  22. package/dist/adapters/backend/mojo-isolated-workspace.js +213 -0
  23. package/dist/adapters/backend/mojo-isolated-workspace.js.map +1 -0
  24. package/dist/adapters/backend/mojo-process-tree.d.ts +292 -0
  25. package/dist/adapters/backend/mojo-process-tree.d.ts.map +1 -0
  26. package/dist/adapters/backend/mojo-process-tree.js +486 -0
  27. package/dist/adapters/backend/mojo-process-tree.js.map +1 -0
  28. package/dist/adapters/backend/mojo-types.d.ts +542 -0
  29. package/dist/adapters/backend/mojo-types.d.ts.map +1 -0
  30. package/dist/adapters/backend/mojo-types.js +682 -0
  31. package/dist/adapters/backend/mojo-types.js.map +1 -0
  32. package/dist/adapters/backend/reproduce-command.js +3 -3
  33. package/dist/adapters/backend/reproduce-command.js.map +1 -1
  34. package/dist/adapters/backend/riff-backend.js +1 -1
  35. package/dist/adapters/backend/riff-backend.js.map +1 -1
  36. package/dist/adapters/backend/sandbox.d.ts +44 -7
  37. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  38. package/dist/adapters/backend/sandbox.js +30 -7
  39. package/dist/adapters/backend/sandbox.js.map +1 -1
  40. package/dist/adapters/backend/session-backend-selector.d.ts +14 -1
  41. package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -1
  42. package/dist/adapters/backend/session-backend-selector.js +82 -10
  43. package/dist/adapters/backend/session-backend-selector.js.map +1 -1
  44. package/dist/adapters/backend/types.d.ts +101 -6
  45. package/dist/adapters/backend/types.d.ts.map +1 -1
  46. package/dist/adapters/backend/types.js.map +1 -1
  47. package/dist/adapters/backend/zmx-backend.d.ts +11 -0
  48. package/dist/adapters/backend/zmx-backend.d.ts.map +1 -1
  49. package/dist/adapters/backend/zmx-backend.js +16 -1
  50. package/dist/adapters/backend/zmx-backend.js.map +1 -1
  51. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  52. package/dist/adapters/cli/codex-app.js +2 -2
  53. package/dist/adapters/cli/codex-app.js.map +1 -1
  54. package/dist/adapters/cli/codex.d.ts.map +1 -1
  55. package/dist/adapters/cli/codex.js +7 -0
  56. package/dist/adapters/cli/codex.js.map +1 -1
  57. package/dist/adapters/cli/copilot.d.ts +3 -1
  58. package/dist/adapters/cli/copilot.d.ts.map +1 -1
  59. package/dist/adapters/cli/copilot.js +23 -5
  60. package/dist/adapters/cli/copilot.js.map +1 -1
  61. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  62. package/dist/adapters/cli/cursor.js +13 -3
  63. package/dist/adapters/cli/cursor.js.map +1 -1
  64. package/dist/adapters/cli/grok.d.ts.map +1 -1
  65. package/dist/adapters/cli/grok.js +24 -5
  66. package/dist/adapters/cli/grok.js.map +1 -1
  67. package/dist/adapters/cli/kimi.d.ts.map +1 -1
  68. package/dist/adapters/cli/kimi.js +19 -1
  69. package/dist/adapters/cli/kimi.js.map +1 -1
  70. package/dist/adapters/cli/mojo.d.ts +28 -0
  71. package/dist/adapters/cli/mojo.d.ts.map +1 -0
  72. package/dist/adapters/cli/mojo.js +58 -0
  73. package/dist/adapters/cli/mojo.js.map +1 -0
  74. package/dist/adapters/cli/pi.d.ts +6 -4
  75. package/dist/adapters/cli/pi.d.ts.map +1 -1
  76. package/dist/adapters/cli/pi.js +6 -4
  77. package/dist/adapters/cli/pi.js.map +1 -1
  78. package/dist/adapters/cli/read-isolation.d.ts +251 -1
  79. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  80. package/dist/adapters/cli/read-isolation.js +254 -1
  81. package/dist/adapters/cli/read-isolation.js.map +1 -1
  82. package/dist/adapters/cli/registry.d.ts +13 -1
  83. package/dist/adapters/cli/registry.d.ts.map +1 -1
  84. package/dist/adapters/cli/registry.js +19 -1
  85. package/dist/adapters/cli/registry.js.map +1 -1
  86. package/dist/adapters/cli/runner-input.d.ts +3 -3
  87. package/dist/adapters/cli/runner-input.d.ts.map +1 -1
  88. package/dist/adapters/cli/runner-input.js +4 -3
  89. package/dist/adapters/cli/runner-input.js.map +1 -1
  90. package/dist/adapters/cli/shared-hints.d.ts +3 -1
  91. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  92. package/dist/adapters/cli/shared-hints.js +22 -6
  93. package/dist/adapters/cli/shared-hints.js.map +1 -1
  94. package/dist/adapters/cli/types.d.ts +29 -4
  95. package/dist/adapters/cli/types.d.ts.map +1 -1
  96. package/dist/bot-registry.d.ts +20 -0
  97. package/dist/bot-registry.d.ts.map +1 -1
  98. package/dist/bot-registry.js +30 -4
  99. package/dist/bot-registry.js.map +1 -1
  100. package/dist/cli/arg-utils.d.ts +5 -0
  101. package/dist/cli/arg-utils.d.ts.map +1 -1
  102. package/dist/cli/arg-utils.js +7 -0
  103. package/dist/cli/arg-utils.js.map +1 -1
  104. package/dist/cli/bots-list-output.d.ts +130 -2
  105. package/dist/cli/bots-list-output.d.ts.map +1 -1
  106. package/dist/cli/bots-list-output.js +173 -27
  107. package/dist/cli/bots-list-output.js.map +1 -1
  108. package/dist/cli/daemon-lifecycle-env.d.ts +10 -3
  109. package/dist/cli/daemon-lifecycle-env.d.ts.map +1 -1
  110. package/dist/cli/daemon-lifecycle-env.js +38 -11
  111. package/dist/cli/daemon-lifecycle-env.js.map +1 -1
  112. package/dist/cli/dashboard-command.d.ts +15 -0
  113. package/dist/cli/dashboard-command.d.ts.map +1 -1
  114. package/dist/cli/dashboard-command.js +22 -0
  115. package/dist/cli/dashboard-command.js.map +1 -1
  116. package/dist/cli/log-tail.d.ts +54 -0
  117. package/dist/cli/log-tail.d.ts.map +1 -0
  118. package/dist/cli/log-tail.js +167 -0
  119. package/dist/cli/log-tail.js.map +1 -0
  120. package/dist/cli/pm2-env.d.ts +16 -0
  121. package/dist/cli/pm2-env.d.ts.map +1 -0
  122. package/dist/cli/pm2-env.js +63 -0
  123. package/dist/cli/pm2-env.js.map +1 -0
  124. package/dist/cli/pm2-fleet-lock.d.ts +26 -0
  125. package/dist/cli/pm2-fleet-lock.d.ts.map +1 -0
  126. package/dist/cli/pm2-fleet-lock.js +50 -0
  127. package/dist/cli/pm2-fleet-lock.js.map +1 -0
  128. package/dist/cli/pm2-god-retirement.d.ts +59 -0
  129. package/dist/cli/pm2-god-retirement.d.ts.map +1 -0
  130. package/dist/cli/pm2-god-retirement.js +95 -0
  131. package/dist/cli/pm2-god-retirement.js.map +1 -0
  132. package/dist/cli/session-list-wake.d.ts +41 -0
  133. package/dist/cli/session-list-wake.d.ts.map +1 -0
  134. package/dist/cli/session-list-wake.js +102 -0
  135. package/dist/cli/session-list-wake.js.map +1 -0
  136. package/dist/cli/workflow.d.ts.map +1 -1
  137. package/dist/cli/workflow.js +26 -0
  138. package/dist/cli/workflow.js.map +1 -1
  139. package/dist/cli.d.ts.map +1 -1
  140. package/dist/cli.js +933 -115
  141. package/dist/cli.js.map +1 -1
  142. package/dist/codex-app-runner.js +261 -49
  143. package/dist/codex-app-runner.js.map +1 -1
  144. package/dist/core/cli-runtime-update.d.ts +8 -0
  145. package/dist/core/cli-runtime-update.d.ts.map +1 -1
  146. package/dist/core/cli-runtime-update.js +127 -8
  147. package/dist/core/cli-runtime-update.js.map +1 -1
  148. package/dist/core/close-residual.d.ts +64 -0
  149. package/dist/core/close-residual.d.ts.map +1 -0
  150. package/dist/core/close-residual.js +93 -0
  151. package/dist/core/close-residual.js.map +1 -0
  152. package/dist/core/closed-session-card.d.ts.map +1 -1
  153. package/dist/core/closed-session-card.js +11 -3
  154. package/dist/core/closed-session-card.js.map +1 -1
  155. package/dist/core/command-handler.d.ts.map +1 -1
  156. package/dist/core/command-handler.js +129 -27
  157. package/dist/core/command-handler.js.map +1 -1
  158. package/dist/core/cost-calculator.d.ts +3 -1
  159. package/dist/core/cost-calculator.d.ts.map +1 -1
  160. package/dist/core/cost-calculator.js +230 -1
  161. package/dist/core/cost-calculator.js.map +1 -1
  162. package/dist/core/daemon-background-close.d.ts +19 -0
  163. package/dist/core/daemon-background-close.d.ts.map +1 -0
  164. package/dist/core/daemon-background-close.js +39 -0
  165. package/dist/core/daemon-background-close.js.map +1 -0
  166. package/dist/core/dashboard-command/overview.d.ts +4 -0
  167. package/dist/core/dashboard-command/overview.d.ts.map +1 -1
  168. package/dist/core/dashboard-command/overview.js +8 -1
  169. package/dist/core/dashboard-command/overview.js.map +1 -1
  170. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  171. package/dist/core/dashboard-ipc-server.js +373 -41
  172. package/dist/core/dashboard-ipc-server.js.map +1 -1
  173. package/dist/core/dashboard-rows.d.ts +4 -0
  174. package/dist/core/dashboard-rows.d.ts.map +1 -1
  175. package/dist/core/dashboard-rows.js +3 -0
  176. package/dist/core/dashboard-rows.js.map +1 -1
  177. package/dist/core/dashboard-url.d.ts +37 -0
  178. package/dist/core/dashboard-url.d.ts.map +1 -1
  179. package/dist/core/dashboard-url.js +71 -0
  180. package/dist/core/dashboard-url.js.map +1 -1
  181. package/dist/core/deferred-schedule-settlement.d.ts +15 -1
  182. package/dist/core/deferred-schedule-settlement.d.ts.map +1 -1
  183. package/dist/core/deferred-schedule-settlement.js +4 -1
  184. package/dist/core/deferred-schedule-settlement.js.map +1 -1
  185. package/dist/core/device-isolation-daemon.d.ts +91 -1
  186. package/dist/core/device-isolation-daemon.d.ts.map +1 -1
  187. package/dist/core/device-isolation-daemon.js +346 -2
  188. package/dist/core/device-isolation-daemon.js.map +1 -1
  189. package/dist/core/local-terminal-opener.d.ts.map +1 -1
  190. package/dist/core/local-terminal-opener.js +5 -1
  191. package/dist/core/local-terminal-opener.js.map +1 -1
  192. package/dist/core/loopback-target.d.ts +14 -0
  193. package/dist/core/loopback-target.d.ts.map +1 -0
  194. package/dist/core/loopback-target.js +20 -0
  195. package/dist/core/loopback-target.js.map +1 -0
  196. package/dist/core/maintenance.d.ts.map +1 -1
  197. package/dist/core/maintenance.js +16 -2
  198. package/dist/core/maintenance.js.map +1 -1
  199. package/dist/core/mojo-containment-command.d.ts +20 -0
  200. package/dist/core/mojo-containment-command.d.ts.map +1 -0
  201. package/dist/core/mojo-containment-command.js +266 -0
  202. package/dist/core/mojo-containment-command.js.map +1 -0
  203. package/dist/core/mojo-containment.d.ts +575 -0
  204. package/dist/core/mojo-containment.d.ts.map +1 -0
  205. package/dist/core/mojo-containment.js +1158 -0
  206. package/dist/core/mojo-containment.js.map +1 -0
  207. package/dist/core/mojo-launcher-env-quarantine.d.ts +30 -0
  208. package/dist/core/mojo-launcher-env-quarantine.d.ts.map +1 -0
  209. package/dist/core/mojo-launcher-env-quarantine.js +175 -0
  210. package/dist/core/mojo-launcher-env-quarantine.js.map +1 -0
  211. package/dist/core/mojo-session-identity.d.ts +3 -0
  212. package/dist/core/mojo-session-identity.d.ts.map +1 -0
  213. package/dist/core/mojo-session-identity.js +126 -0
  214. package/dist/core/mojo-session-identity.js.map +1 -0
  215. package/dist/core/persistent-backend.d.ts +32 -8
  216. package/dist/core/persistent-backend.d.ts.map +1 -1
  217. package/dist/core/persistent-backend.js +55 -17
  218. package/dist/core/persistent-backend.js.map +1 -1
  219. package/dist/core/plugins/mcp/gateway.d.ts +14 -1
  220. package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
  221. package/dist/core/plugins/mcp/gateway.js +70 -3
  222. package/dist/core/plugins/mcp/gateway.js.map +1 -1
  223. package/dist/core/plugins/mcp/host.d.ts +2 -0
  224. package/dist/core/plugins/mcp/host.d.ts.map +1 -1
  225. package/dist/core/plugins/mcp/host.js +2 -0
  226. package/dist/core/plugins/mcp/host.js.map +1 -1
  227. package/dist/core/plugins/pm2.d.ts.map +1 -1
  228. package/dist/core/plugins/pm2.js +16 -3
  229. package/dist/core/plugins/pm2.js.map +1 -1
  230. package/dist/core/plugins/service-manager.d.ts +9 -0
  231. package/dist/core/plugins/service-manager.d.ts.map +1 -1
  232. package/dist/core/plugins/service-manager.js +33 -16
  233. package/dist/core/plugins/service-manager.js.map +1 -1
  234. package/dist/core/preview-port-owner.d.ts +109 -0
  235. package/dist/core/preview-port-owner.d.ts.map +1 -0
  236. package/dist/core/preview-port-owner.js +320 -0
  237. package/dist/core/preview-port-owner.js.map +1 -0
  238. package/dist/core/remote-cli-ids.d.ts +5 -0
  239. package/dist/core/remote-cli-ids.d.ts.map +1 -0
  240. package/dist/core/remote-cli-ids.js +24 -0
  241. package/dist/core/remote-cli-ids.js.map +1 -0
  242. package/dist/core/{riff-shutdown-detach.d.ts → remote-shutdown-detach.d.ts} +40 -39
  243. package/dist/core/remote-shutdown-detach.d.ts.map +1 -0
  244. package/dist/core/{riff-shutdown-detach.js → remote-shutdown-detach.js} +85 -83
  245. package/dist/core/remote-shutdown-detach.js.map +1 -0
  246. package/dist/core/{riff-worker-shutdown-readiness.d.ts → remote-worker-shutdown-readiness.d.ts} +5 -4
  247. package/dist/core/remote-worker-shutdown-readiness.d.ts.map +1 -0
  248. package/dist/core/{riff-worker-shutdown-readiness.js → remote-worker-shutdown-readiness.js} +4 -3
  249. package/dist/core/remote-worker-shutdown-readiness.js.map +1 -0
  250. package/dist/core/reply-target.d.ts +16 -1
  251. package/dist/core/reply-target.d.ts.map +1 -1
  252. package/dist/core/reply-target.js +61 -0
  253. package/dist/core/reply-target.js.map +1 -1
  254. package/dist/core/session-group-birth.d.ts +26 -0
  255. package/dist/core/session-group-birth.d.ts.map +1 -1
  256. package/dist/core/session-group-birth.js +81 -4
  257. package/dist/core/session-group-birth.js.map +1 -1
  258. package/dist/core/session-manager.d.ts +15 -2
  259. package/dist/core/session-manager.d.ts.map +1 -1
  260. package/dist/core/session-manager.js +259 -5
  261. package/dist/core/session-manager.js.map +1 -1
  262. package/dist/core/session-model.d.ts +46 -0
  263. package/dist/core/session-model.d.ts.map +1 -0
  264. package/dist/core/session-model.js +42 -0
  265. package/dist/core/session-model.js.map +1 -0
  266. package/dist/core/session-owner-reminder.d.ts +63 -0
  267. package/dist/core/session-owner-reminder.d.ts.map +1 -0
  268. package/dist/core/session-owner-reminder.js +196 -0
  269. package/dist/core/session-owner-reminder.js.map +1 -0
  270. package/dist/core/session-preview-registry.d.ts +53 -0
  271. package/dist/core/session-preview-registry.d.ts.map +1 -0
  272. package/dist/core/session-preview-registry.js +82 -0
  273. package/dist/core/session-preview-registry.js.map +1 -0
  274. package/dist/core/session-preview.d.ts +114 -0
  275. package/dist/core/session-preview.d.ts.map +1 -0
  276. package/dist/core/session-preview.js +184 -0
  277. package/dist/core/session-preview.js.map +1 -0
  278. package/dist/core/session-rename.d.ts.map +1 -1
  279. package/dist/core/session-rename.js +5 -3
  280. package/dist/core/session-rename.js.map +1 -1
  281. package/dist/core/session-wake-deadline.d.ts +5 -0
  282. package/dist/core/session-wake-deadline.d.ts.map +1 -0
  283. package/dist/core/session-wake-deadline.js +13 -0
  284. package/dist/core/session-wake-deadline.js.map +1 -0
  285. package/dist/core/shutdown-budgets.d.ts +7 -6
  286. package/dist/core/shutdown-budgets.d.ts.map +1 -1
  287. package/dist/core/shutdown-budgets.js +11 -10
  288. package/dist/core/shutdown-budgets.js.map +1 -1
  289. package/dist/core/terminal-control-grant.d.ts +74 -0
  290. package/dist/core/terminal-control-grant.d.ts.map +1 -0
  291. package/dist/core/terminal-control-grant.js +183 -0
  292. package/dist/core/terminal-control-grant.js.map +1 -0
  293. package/dist/core/terminal-proxy.d.ts.map +1 -1
  294. package/dist/core/terminal-proxy.js +4 -5
  295. package/dist/core/terminal-proxy.js.map +1 -1
  296. package/dist/core/terminal-write-auth.d.ts +4 -11
  297. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  298. package/dist/core/terminal-write-auth.js +19 -16
  299. package/dist/core/terminal-write-auth.js.map +1 -1
  300. package/dist/core/trigger-session.d.ts.map +1 -1
  301. package/dist/core/trigger-session.js +36 -29
  302. package/dist/core/trigger-session.js.map +1 -1
  303. package/dist/core/types.d.ts +88 -10
  304. package/dist/core/types.d.ts.map +1 -1
  305. package/dist/core/types.js +9 -7
  306. package/dist/core/types.js.map +1 -1
  307. package/dist/core/workbench-link.d.ts +81 -0
  308. package/dist/core/workbench-link.d.ts.map +1 -0
  309. package/dist/core/workbench-link.js +109 -0
  310. package/dist/core/workbench-link.js.map +1 -0
  311. package/dist/core/worker-pool.d.ts +312 -11
  312. package/dist/core/worker-pool.d.ts.map +1 -1
  313. package/dist/core/worker-pool.js +2494 -209
  314. package/dist/core/worker-pool.js.map +1 -1
  315. package/dist/daemon.d.ts +6 -0
  316. package/dist/daemon.d.ts.map +1 -1
  317. package/dist/daemon.js +443 -106
  318. package/dist/daemon.js.map +1 -1
  319. package/dist/dashboard/aggregator.d.ts +33 -3
  320. package/dist/dashboard/aggregator.d.ts.map +1 -1
  321. package/dist/dashboard/aggregator.js +93 -17
  322. package/dist/dashboard/aggregator.js.map +1 -1
  323. package/dist/dashboard/auth-session-connections.d.ts +59 -0
  324. package/dist/dashboard/auth-session-connections.d.ts.map +1 -0
  325. package/dist/dashboard/auth-session-connections.js +109 -0
  326. package/dist/dashboard/auth-session-connections.js.map +1 -0
  327. package/dist/dashboard/auth.d.ts +99 -1
  328. package/dist/dashboard/auth.d.ts.map +1 -1
  329. package/dist/dashboard/auth.js +176 -64
  330. package/dist/dashboard/auth.js.map +1 -1
  331. package/dist/dashboard/bot-payload.d.ts +1 -0
  332. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  333. package/dist/dashboard/bot-payload.js +3 -0
  334. package/dist/dashboard/bot-payload.js.map +1 -1
  335. package/dist/dashboard/control-audit.d.ts +112 -0
  336. package/dist/dashboard/control-audit.d.ts.map +1 -0
  337. package/dist/dashboard/control-audit.js +195 -0
  338. package/dist/dashboard/control-audit.js.map +1 -0
  339. package/dist/dashboard/control-csrf.d.ts +123 -0
  340. package/dist/dashboard/control-csrf.d.ts.map +1 -0
  341. package/dist/dashboard/control-csrf.js +392 -0
  342. package/dist/dashboard/control-csrf.js.map +1 -0
  343. package/dist/dashboard/daemon-reconcile.d.ts +26 -0
  344. package/dist/dashboard/daemon-reconcile.d.ts.map +1 -0
  345. package/dist/dashboard/daemon-reconcile.js +46 -0
  346. package/dist/dashboard/daemon-reconcile.js.map +1 -0
  347. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  348. package/dist/dashboard/debug-terminal.js +18 -4
  349. package/dist/dashboard/debug-terminal.js.map +1 -1
  350. package/dist/dashboard/events-sse.d.ts +60 -0
  351. package/dist/dashboard/events-sse.d.ts.map +1 -0
  352. package/dist/dashboard/events-sse.js +63 -0
  353. package/dist/dashboard/events-sse.js.map +1 -0
  354. package/dist/dashboard/h5-auth.d.ts +256 -0
  355. package/dist/dashboard/h5-auth.d.ts.map +1 -0
  356. package/dist/dashboard/h5-auth.js +825 -0
  357. package/dist/dashboard/h5-auth.js.map +1 -0
  358. package/dist/dashboard/managed-spawn.d.ts +36 -0
  359. package/dist/dashboard/managed-spawn.d.ts.map +1 -0
  360. package/dist/dashboard/managed-spawn.js +169 -0
  361. package/dist/dashboard/managed-spawn.js.map +1 -0
  362. package/dist/dashboard/preview-content-capability.d.ts +41 -0
  363. package/dist/dashboard/preview-content-capability.d.ts.map +1 -0
  364. package/dist/dashboard/preview-content-capability.js +127 -0
  365. package/dist/dashboard/preview-content-capability.js.map +1 -0
  366. package/dist/dashboard/preview-contract.d.ts +85 -0
  367. package/dist/dashboard/preview-contract.d.ts.map +1 -0
  368. package/dist/dashboard/preview-contract.js +198 -0
  369. package/dist/dashboard/preview-contract.js.map +1 -0
  370. package/dist/dashboard/preview-guard-page.d.ts +52 -0
  371. package/dist/dashboard/preview-guard-page.d.ts.map +1 -0
  372. package/dist/dashboard/preview-guard-page.js +162 -0
  373. package/dist/dashboard/preview-guard-page.js.map +1 -0
  374. package/dist/dashboard/preview-interaction.d.ts +57 -0
  375. package/dist/dashboard/preview-interaction.d.ts.map +1 -0
  376. package/dist/dashboard/preview-interaction.js +198 -0
  377. package/dist/dashboard/preview-interaction.js.map +1 -0
  378. package/dist/dashboard/preview-proxy.d.ts +113 -0
  379. package/dist/dashboard/preview-proxy.d.ts.map +1 -0
  380. package/dist/dashboard/preview-proxy.js +543 -0
  381. package/dist/dashboard/preview-proxy.js.map +1 -0
  382. package/dist/dashboard/public-redact.d.ts +23 -0
  383. package/dist/dashboard/public-redact.d.ts.map +1 -1
  384. package/dist/dashboard/public-redact.js +90 -21
  385. package/dist/dashboard/public-redact.js.map +1 -1
  386. package/dist/dashboard/request-identity.d.ts +77 -0
  387. package/dist/dashboard/request-identity.d.ts.map +1 -0
  388. package/dist/dashboard/request-identity.js +72 -0
  389. package/dist/dashboard/request-identity.js.map +1 -0
  390. package/dist/dashboard/session-cleanup.d.ts +5 -0
  391. package/dist/dashboard/session-cleanup.d.ts.map +1 -1
  392. package/dist/dashboard/session-cleanup.js +4 -0
  393. package/dist/dashboard/session-cleanup.js.map +1 -1
  394. package/dist/dashboard/settings-write-applier.d.ts +5 -1
  395. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  396. package/dist/dashboard/settings-write-applier.js +14 -0
  397. package/dist/dashboard/settings-write-applier.js.map +1 -1
  398. package/dist/dashboard/standing-link.d.ts +76 -0
  399. package/dist/dashboard/standing-link.d.ts.map +1 -0
  400. package/dist/dashboard/standing-link.js +111 -0
  401. package/dist/dashboard/standing-link.js.map +1 -0
  402. package/dist/dashboard/terminal-control.d.ts +105 -0
  403. package/dist/dashboard/terminal-control.d.ts.map +1 -0
  404. package/dist/dashboard/terminal-control.js +296 -0
  405. package/dist/dashboard/terminal-control.js.map +1 -0
  406. package/dist/dashboard/terminal-front-proxy.d.ts +55 -0
  407. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -0
  408. package/dist/dashboard/terminal-front-proxy.js +397 -0
  409. package/dist/dashboard/terminal-front-proxy.js.map +1 -0
  410. package/dist/dashboard/terminal-view-capability.d.ts +97 -0
  411. package/dist/dashboard/terminal-view-capability.d.ts.map +1 -0
  412. package/dist/dashboard/terminal-view-capability.js +145 -0
  413. package/dist/dashboard/terminal-view-capability.js.map +1 -0
  414. package/dist/dashboard/web/agent-workbench-api.d.ts +59 -0
  415. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -0
  416. package/dist/dashboard/web/agent-workbench-api.js +227 -0
  417. package/dist/dashboard/web/agent-workbench-api.js.map +1 -0
  418. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts +59 -0
  419. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts.map +1 -0
  420. package/dist/dashboard/web/agent-workbench-appearance-menu.js +195 -0
  421. package/dist/dashboard/web/agent-workbench-appearance-menu.js.map +1 -0
  422. package/dist/dashboard/web/agent-workbench-appearance.d.ts +222 -0
  423. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -0
  424. package/dist/dashboard/web/agent-workbench-appearance.js +499 -0
  425. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -0
  426. package/dist/dashboard/web/agent-workbench-capabilities.d.ts +13 -0
  427. package/dist/dashboard/web/agent-workbench-capabilities.d.ts.map +1 -0
  428. package/dist/dashboard/web/agent-workbench-capabilities.js +24 -0
  429. package/dist/dashboard/web/agent-workbench-capabilities.js.map +1 -0
  430. package/dist/dashboard/web/agent-workbench-chat.d.ts +90 -0
  431. package/dist/dashboard/web/agent-workbench-chat.d.ts.map +1 -0
  432. package/dist/dashboard/web/agent-workbench-chat.js +168 -0
  433. package/dist/dashboard/web/agent-workbench-chat.js.map +1 -0
  434. package/dist/dashboard/web/agent-workbench-dock-page.d.ts +3 -0
  435. package/dist/dashboard/web/agent-workbench-dock-page.d.ts.map +1 -0
  436. package/dist/dashboard/web/agent-workbench-dock-page.js +23 -0
  437. package/dist/dashboard/web/agent-workbench-dock-page.js.map +1 -0
  438. package/dist/dashboard/web/agent-workbench-dock-view.d.ts +22 -0
  439. package/dist/dashboard/web/agent-workbench-dock-view.d.ts.map +1 -0
  440. package/dist/dashboard/web/agent-workbench-dock-view.js +76 -0
  441. package/dist/dashboard/web/agent-workbench-dock-view.js.map +1 -0
  442. package/dist/dashboard/web/agent-workbench-model.d.ts +196 -0
  443. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -0
  444. package/dist/dashboard/web/agent-workbench-model.js +529 -0
  445. package/dist/dashboard/web/agent-workbench-model.js.map +1 -0
  446. package/dist/dashboard/web/agent-workbench-page.d.ts +3 -0
  447. package/dist/dashboard/web/agent-workbench-page.d.ts.map +1 -0
  448. package/dist/dashboard/web/agent-workbench-page.js +27 -0
  449. package/dist/dashboard/web/agent-workbench-page.js.map +1 -0
  450. package/dist/dashboard/web/agent-workbench-panes.d.ts +49 -0
  451. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -0
  452. package/dist/dashboard/web/agent-workbench-panes.js +468 -0
  453. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -0
  454. package/dist/dashboard/web/agent-workbench-session-list.d.ts +42 -0
  455. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -0
  456. package/dist/dashboard/web/agent-workbench-session-list.js +353 -0
  457. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -0
  458. package/dist/dashboard/web/agent-workbench-storage.d.ts +49 -0
  459. package/dist/dashboard/web/agent-workbench-storage.d.ts.map +1 -0
  460. package/dist/dashboard/web/agent-workbench-storage.js +197 -0
  461. package/dist/dashboard/web/agent-workbench-storage.js.map +1 -0
  462. package/dist/dashboard/web/agent-workbench-touch.d.ts +46 -0
  463. package/dist/dashboard/web/agent-workbench-touch.d.ts.map +1 -0
  464. package/dist/dashboard/web/agent-workbench-touch.js +96 -0
  465. package/dist/dashboard/web/agent-workbench-touch.js.map +1 -0
  466. package/dist/dashboard/web/agent-workbench-view.d.ts +34 -0
  467. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -0
  468. package/dist/dashboard/web/agent-workbench-view.js +439 -0
  469. package/dist/dashboard/web/agent-workbench-view.js.map +1 -0
  470. package/dist/dashboard/web/app.d.ts.map +1 -1
  471. package/dist/dashboard/web/app.js +63 -2
  472. package/dist/dashboard/web/app.js.map +1 -1
  473. package/dist/dashboard/web/bot-defaults-page.d.ts +11 -0
  474. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  475. package/dist/dashboard/web/bot-defaults-page.js +267 -36
  476. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  477. package/dist/dashboard/web/bot-defaults.d.ts +6 -0
  478. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  479. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  480. package/dist/dashboard/web/control-csrf.d.ts +4 -0
  481. package/dist/dashboard/web/control-csrf.d.ts.map +1 -0
  482. package/dist/dashboard/web/control-csrf.js +26 -0
  483. package/dist/dashboard/web/control-csrf.js.map +1 -0
  484. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  485. package/dist/dashboard/web/dashboard-routes.js +2 -0
  486. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  487. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  488. package/dist/dashboard/web/groups-page.js +11 -2
  489. package/dist/dashboard/web/groups-page.js.map +1 -1
  490. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  491. package/dist/dashboard/web/i18n.js +132 -6
  492. package/dist/dashboard/web/i18n.js.map +1 -1
  493. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  494. package/dist/dashboard/web/overview-page.js +3 -2
  495. package/dist/dashboard/web/overview-page.js.map +1 -1
  496. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  497. package/dist/dashboard/web/sessions-page.js +41 -1
  498. package/dist/dashboard/web/sessions-page.js.map +1 -1
  499. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  500. package/dist/dashboard/web/sessions.js +5 -1
  501. package/dist/dashboard/web/sessions.js.map +1 -1
  502. package/dist/dashboard/web/settings-page.d.ts +3 -0
  503. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  504. package/dist/dashboard/web/settings-page.js +56 -10
  505. package/dist/dashboard/web/settings-page.js.map +1 -1
  506. package/dist/dashboard/web/store.d.ts +27 -2
  507. package/dist/dashboard/web/store.d.ts.map +1 -1
  508. package/dist/dashboard/web/store.js +43 -4
  509. package/dist/dashboard/web/store.js.map +1 -1
  510. package/dist/dashboard/web/ui.d.ts +3 -0
  511. package/dist/dashboard/web/ui.d.ts.map +1 -1
  512. package/dist/dashboard/web/ui.js +11 -0
  513. package/dist/dashboard/web/ui.js.map +1 -1
  514. package/dist/dashboard/web/update-action.d.ts +12 -0
  515. package/dist/dashboard/web/update-action.d.ts.map +1 -1
  516. package/dist/dashboard/web/update-action.js +11 -0
  517. package/dist/dashboard/web/update-action.js.map +1 -1
  518. package/dist/dashboard/workbench-doctor.d.ts +114 -0
  519. package/dist/dashboard/workbench-doctor.d.ts.map +1 -0
  520. package/dist/dashboard/workbench-doctor.js +620 -0
  521. package/dist/dashboard/workbench-doctor.js.map +1 -0
  522. package/dist/dashboard/workbench-ticket.d.ts +165 -0
  523. package/dist/dashboard/workbench-ticket.d.ts.map +1 -0
  524. package/dist/dashboard/workbench-ticket.js +495 -0
  525. package/dist/dashboard/workbench-ticket.js.map +1 -0
  526. package/dist/dashboard-web/app.js +1 -1
  527. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +1 -0
  528. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +1 -0
  529. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +10 -0
  530. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +2 -0
  531. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +1 -0
  532. package/dist/dashboard-web/chunks/{chunk-Z7B3E3US.js → chunk-2W6SCN3M.js} +1 -1
  533. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +1 -0
  534. package/dist/dashboard-web/chunks/{chunk-VMWNR3SV.js → chunk-F6KNPF4R.js} +11 -9
  535. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +1 -0
  536. package/dist/dashboard-web/chunks/chunk-G5ZS2YUS.js +1 -0
  537. package/dist/dashboard-web/chunks/chunk-KTQ7B6ZR.js +1 -0
  538. package/dist/dashboard-web/chunks/{chunk-Z7DO42GJ.js → chunk-NACP7BMR.js} +1 -1
  539. package/dist/dashboard-web/chunks/{connectors-page-CPH6VVFY.js → connectors-page-VOISXXII.js} +1 -1
  540. package/dist/dashboard-web/chunks/{feedback-page-J2FKQ3LK.js → feedback-page-RN6HRZMS.js} +1 -1
  541. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +8 -0
  542. package/dist/dashboard-web/chunks/{insights-page-A2LWTKJD.js → insights-page-HJFDYR5N.js} +3 -3
  543. package/dist/dashboard-web/chunks/{monitor-room-2YR752E3.js → monitor-room-V7PCP47V.js} +1 -1
  544. package/dist/dashboard-web/chunks/{monitoring-page-BGYSKZGM.js → monitoring-page-NKEKVQAP.js} +1 -1
  545. package/dist/dashboard-web/chunks/{office-page-ZZHTEAE4.js → office-page-GVJF73XW.js} +1 -1
  546. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +1 -0
  547. package/dist/dashboard-web/chunks/{plugin-page-VEGTCEAS.js → plugin-page-J3OF5IY4.js} +1 -1
  548. package/dist/dashboard-web/chunks/{roles-page-YUC4YLJG.js → roles-page-WQIRSOLZ.js} +1 -1
  549. package/dist/dashboard-web/chunks/{schedules-page-2L2NKTDJ.js → schedules-page-J2DEY2X6.js} +1 -1
  550. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +8 -0
  551. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +2 -0
  552. package/dist/dashboard-web/chunks/{skills-page-DQPBBI3T.js → skills-page-HGIFR3IU.js} +1 -1
  553. package/dist/dashboard-web/chunks/{team-federation-page-LQKLDV4K.js → team-federation-page-MR5ZPL4B.js} +1 -1
  554. package/dist/dashboard-web/chunks/{v3-page-RVKQIMAP.js → v3-page-GBR5JWPE.js} +2 -2
  555. package/dist/dashboard-web/chunks/{whiteboards-page-S5K55HRZ.js → whiteboards-page-QAZM4BN3.js} +1 -1
  556. package/dist/dashboard-web/index.html +8 -1
  557. package/dist/dashboard-web/style.css +2006 -10
  558. package/dist/dashboard.js +1006 -263
  559. package/dist/dashboard.js.map +1 -1
  560. package/dist/global-config.d.ts +30 -0
  561. package/dist/global-config.d.ts.map +1 -1
  562. package/dist/global-config.js +33 -0
  563. package/dist/global-config.js.map +1 -1
  564. package/dist/i18n/en.d.ts.map +1 -1
  565. package/dist/i18n/en.js +31 -1
  566. package/dist/i18n/en.js.map +1 -1
  567. package/dist/i18n/zh.d.ts.map +1 -1
  568. package/dist/i18n/zh.js +28 -1
  569. package/dist/i18n/zh.js.map +1 -1
  570. package/dist/im/lark/card-builder.d.ts +1 -1
  571. package/dist/im/lark/card-builder.d.ts.map +1 -1
  572. package/dist/im/lark/card-builder.js +22 -6
  573. package/dist/im/lark/card-builder.js.map +1 -1
  574. package/dist/im/lark/card-handler.d.ts.map +1 -1
  575. package/dist/im/lark/card-handler.js +118 -24
  576. package/dist/im/lark/card-handler.js.map +1 -1
  577. package/dist/im/lark/client.d.ts +15 -0
  578. package/dist/im/lark/client.d.ts.map +1 -1
  579. package/dist/im/lark/client.js +30 -0
  580. package/dist/im/lark/client.js.map +1 -1
  581. package/dist/im/lark/event-dispatcher.d.ts +48 -0
  582. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  583. package/dist/im/lark/event-dispatcher.js +133 -22
  584. package/dist/im/lark/event-dispatcher.js.map +1 -1
  585. package/dist/im/lark/identity-cache.d.ts +14 -6
  586. package/dist/im/lark/identity-cache.d.ts.map +1 -1
  587. package/dist/im/lark/identity-cache.js +69 -26
  588. package/dist/im/lark/identity-cache.js.map +1 -1
  589. package/dist/im/lark/lark-hosts.d.ts +13 -0
  590. package/dist/im/lark/lark-hosts.d.ts.map +1 -1
  591. package/dist/im/lark/lark-hosts.js +16 -0
  592. package/dist/im/lark/lark-hosts.js.map +1 -1
  593. package/dist/im/lark/overview-card.d.ts +22 -0
  594. package/dist/im/lark/overview-card.d.ts.map +1 -1
  595. package/dist/im/lark/overview-card.js +53 -3
  596. package/dist/im/lark/overview-card.js.map +1 -1
  597. package/dist/im/lark/sessions-card.d.ts.map +1 -1
  598. package/dist/im/lark/sessions-card.js +26 -3
  599. package/dist/im/lark/sessions-card.js.map +1 -1
  600. package/dist/im/lark/workflow-slash-command.d.ts +6 -0
  601. package/dist/im/lark/workflow-slash-command.d.ts.map +1 -1
  602. package/dist/im/lark/workflow-slash-command.js +7 -0
  603. package/dist/im/lark/workflow-slash-command.js.map +1 -1
  604. package/dist/index-daemon.js +27 -4
  605. package/dist/index-daemon.js.map +1 -1
  606. package/dist/index-dashboard.d.ts +3 -0
  607. package/dist/index-dashboard.d.ts.map +1 -0
  608. package/dist/index-dashboard.js +94 -0
  609. package/dist/index-dashboard.js.map +1 -0
  610. package/dist/platform/team-agents-client.d.ts +199 -0
  611. package/dist/platform/team-agents-client.d.ts.map +1 -0
  612. package/dist/platform/team-agents-client.js +263 -0
  613. package/dist/platform/team-agents-client.js.map +1 -0
  614. package/dist/platform/tunnel-client.d.ts +6 -0
  615. package/dist/platform/tunnel-client.d.ts.map +1 -1
  616. package/dist/platform/tunnel-client.js.map +1 -1
  617. package/dist/services/async-trigger-state.d.ts +7 -3
  618. package/dist/services/async-trigger-state.d.ts.map +1 -1
  619. package/dist/services/async-trigger-state.js +28 -0
  620. package/dist/services/async-trigger-state.js.map +1 -1
  621. package/dist/services/async-trigger-store.d.ts +13 -4
  622. package/dist/services/async-trigger-store.d.ts.map +1 -1
  623. package/dist/services/async-trigger-store.js +55 -8
  624. package/dist/services/async-trigger-store.js.map +1 -1
  625. package/dist/services/bot-config-store.d.ts +1 -11
  626. package/dist/services/bot-config-store.d.ts.map +1 -1
  627. package/dist/services/bot-config-store.js +27 -3
  628. package/dist/services/bot-config-store.js.map +1 -1
  629. package/dist/services/bot-profile-store.d.ts +17 -2
  630. package/dist/services/bot-profile-store.d.ts.map +1 -1
  631. package/dist/services/bot-profile-store.js +93 -12
  632. package/dist/services/bot-profile-store.js.map +1 -1
  633. package/dist/services/bridge-turn-queue.d.ts +10 -1
  634. package/dist/services/bridge-turn-queue.d.ts.map +1 -1
  635. package/dist/services/bridge-turn-queue.js +29 -9
  636. package/dist/services/bridge-turn-queue.js.map +1 -1
  637. package/dist/services/claude-transcript.d.ts +19 -0
  638. package/dist/services/claude-transcript.d.ts.map +1 -1
  639. package/dist/services/claude-transcript.js +57 -5
  640. package/dist/services/claude-transcript.js.map +1 -1
  641. package/dist/services/codex-app-threads.d.ts +2 -0
  642. package/dist/services/codex-app-threads.d.ts.map +1 -1
  643. package/dist/services/codex-app-threads.js +1 -1
  644. package/dist/services/codex-app-threads.js.map +1 -1
  645. package/dist/services/codex-reasoning-effort.d.ts +8 -0
  646. package/dist/services/codex-reasoning-effort.d.ts.map +1 -1
  647. package/dist/services/codex-reasoning-effort.js +24 -0
  648. package/dist/services/codex-reasoning-effort.js.map +1 -1
  649. package/dist/services/cursor-resume-policy.d.ts.map +1 -1
  650. package/dist/services/cursor-resume-policy.js +15 -3
  651. package/dist/services/cursor-resume-policy.js.map +1 -1
  652. package/dist/services/open-platform-rename.d.ts.map +1 -1
  653. package/dist/services/open-platform-rename.js +3 -2
  654. package/dist/services/open-platform-rename.js.map +1 -1
  655. package/dist/services/ordinary-turn-recovery.d.ts +76 -0
  656. package/dist/services/ordinary-turn-recovery.d.ts.map +1 -0
  657. package/dist/services/ordinary-turn-recovery.js +349 -0
  658. package/dist/services/ordinary-turn-recovery.js.map +1 -0
  659. package/dist/services/resume-fresh-policy.d.ts +17 -0
  660. package/dist/services/resume-fresh-policy.d.ts.map +1 -0
  661. package/dist/services/resume-fresh-policy.js +24 -0
  662. package/dist/services/resume-fresh-policy.js.map +1 -0
  663. package/dist/services/session-group-title.d.ts +1 -0
  664. package/dist/services/session-group-title.d.ts.map +1 -1
  665. package/dist/services/session-group-title.js +16 -1
  666. package/dist/services/session-group-title.js.map +1 -1
  667. package/dist/services/session-groups-store.d.ts +30 -0
  668. package/dist/services/session-groups-store.d.ts.map +1 -1
  669. package/dist/services/session-groups-store.js +26 -4
  670. package/dist/services/session-groups-store.js.map +1 -1
  671. package/dist/services/session-owner-notification.d.ts +9 -0
  672. package/dist/services/session-owner-notification.d.ts.map +1 -0
  673. package/dist/services/session-owner-notification.js +10 -0
  674. package/dist/services/session-owner-notification.js.map +1 -0
  675. package/dist/services/session-owner-reminder-config-store.d.ts +9 -0
  676. package/dist/services/session-owner-reminder-config-store.d.ts.map +1 -0
  677. package/dist/services/session-owner-reminder-config-store.js +24 -0
  678. package/dist/services/session-owner-reminder-config-store.js.map +1 -0
  679. package/dist/services/session-owner-reminder-store.d.ts +5 -0
  680. package/dist/services/session-owner-reminder-store.d.ts.map +1 -0
  681. package/dist/services/session-owner-reminder-store.js +54 -0
  682. package/dist/services/session-owner-reminder-store.js.map +1 -0
  683. package/dist/services/session-store.d.ts +98 -21
  684. package/dist/services/session-store.d.ts.map +1 -1
  685. package/dist/services/session-store.js +406 -48
  686. package/dist/services/session-store.js.map +1 -1
  687. package/dist/services/structured-bridge-clis.d.ts +9 -5
  688. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  689. package/dist/services/structured-bridge-clis.js +9 -4
  690. package/dist/services/structured-bridge-clis.js.map +1 -1
  691. package/dist/services/transcript-resolver.d.ts +1 -1
  692. package/dist/services/transcript-resolver.d.ts.map +1 -1
  693. package/dist/services/transcript-resolver.js +6 -1
  694. package/dist/services/transcript-resolver.js.map +1 -1
  695. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  696. package/dist/setup/bot-config-editor.js +7 -2
  697. package/dist/setup/bot-config-editor.js.map +1 -1
  698. package/dist/setup/ensure-herdr-integrations.d.ts +13 -0
  699. package/dist/setup/ensure-herdr-integrations.d.ts.map +1 -1
  700. package/dist/setup/ensure-herdr-integrations.js +21 -3
  701. package/dist/setup/ensure-herdr-integrations.js.map +1 -1
  702. package/dist/setup/open-platform-automation.d.ts +1 -0
  703. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  704. package/dist/setup/open-platform-automation.js +107 -7
  705. package/dist/setup/open-platform-automation.js.map +1 -1
  706. package/dist/skills/definitions.d.ts +17 -0
  707. package/dist/skills/definitions.d.ts.map +1 -1
  708. package/dist/skills/definitions.js +78 -8
  709. package/dist/skills/definitions.js.map +1 -1
  710. package/dist/skills/injection-mode.d.ts +24 -3
  711. package/dist/skills/injection-mode.d.ts.map +1 -1
  712. package/dist/skills/injection-mode.js +64 -15
  713. package/dist/skills/injection-mode.js.map +1 -1
  714. package/dist/skills/installer.d.ts +18 -0
  715. package/dist/skills/installer.d.ts.map +1 -1
  716. package/dist/skills/installer.js +45 -1
  717. package/dist/skills/installer.js.map +1 -1
  718. package/dist/types.d.ts +221 -10
  719. package/dist/types.d.ts.map +1 -1
  720. package/dist/utils/child-env.d.ts +92 -2
  721. package/dist/utils/child-env.d.ts.map +1 -1
  722. package/dist/utils/child-env.js +195 -1
  723. package/dist/utils/child-env.js.map +1 -1
  724. package/dist/utils/cli-usage-limit.d.ts +25 -14
  725. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  726. package/dist/utils/cli-usage-limit.js +25 -15
  727. package/dist/utils/cli-usage-limit.js.map +1 -1
  728. package/dist/utils/daemon-rejection-guard.d.ts +35 -0
  729. package/dist/utils/daemon-rejection-guard.d.ts.map +1 -0
  730. package/dist/utils/daemon-rejection-guard.js +101 -0
  731. package/dist/utils/daemon-rejection-guard.js.map +1 -0
  732. package/dist/utils/dashboard-env.d.ts +53 -0
  733. package/dist/utils/dashboard-env.d.ts.map +1 -0
  734. package/dist/utils/dashboard-env.js +116 -0
  735. package/dist/utils/dashboard-env.js.map +1 -0
  736. package/dist/utils/global-install.d.ts.map +1 -1
  737. package/dist/utils/global-install.js +61 -5
  738. package/dist/utils/global-install.js.map +1 -1
  739. package/dist/utils/install-diagnostics.d.ts +14 -7
  740. package/dist/utils/install-diagnostics.d.ts.map +1 -1
  741. package/dist/utils/install-diagnostics.js +20 -11
  742. package/dist/utils/install-diagnostics.js.map +1 -1
  743. package/dist/utils/local-dev-update.d.ts +132 -0
  744. package/dist/utils/local-dev-update.d.ts.map +1 -0
  745. package/dist/utils/local-dev-update.js +176 -0
  746. package/dist/utils/local-dev-update.js.map +1 -0
  747. package/dist/utils/pending-input-queue.d.ts +12 -1
  748. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  749. package/dist/utils/pending-input-queue.js.map +1 -1
  750. package/dist/worker.js +1284 -225
  751. package/dist/worker.js.map +1 -1
  752. package/dist/workflows/definition.d.ts +68 -68
  753. package/dist/workflows/events/payloads.d.ts +14 -14
  754. package/dist/workflows/events/schema.d.ts +312 -312
  755. package/dist/workflows/migration/v2-run-archive-schema.d.ts +40 -40
  756. package/dist/workflows/v3/cli-run.d.ts.map +1 -1
  757. package/dist/workflows/v3/cli-run.js +7 -0
  758. package/dist/workflows/v3/cli-run.js.map +1 -1
  759. package/dist/workflows/v3/goal-cli.d.ts +3 -0
  760. package/dist/workflows/v3/goal-cli.d.ts.map +1 -1
  761. package/dist/workflows/v3/goal-cli.js +11 -0
  762. package/dist/workflows/v3/goal-cli.js.map +1 -1
  763. package/dist/workflows/v3/run-envelope.d.ts +144 -144
  764. package/package.json +3 -2
  765. package/dist/cli/pm2-god-admission.d.ts +0 -8
  766. package/dist/cli/pm2-god-admission.d.ts.map +0 -1
  767. package/dist/cli/pm2-god-admission.js +0 -25
  768. package/dist/cli/pm2-god-admission.js.map +0 -1
  769. package/dist/core/riff-shutdown-detach.d.ts.map +0 -1
  770. package/dist/core/riff-shutdown-detach.js.map +0 -1
  771. package/dist/core/riff-worker-shutdown-readiness.d.ts.map +0 -1
  772. package/dist/core/riff-worker-shutdown-readiness.js.map +0 -1
  773. package/dist/dashboard-web/chunks/bot-defaults-page-65SMZ4QY.js +0 -10
  774. package/dist/dashboard-web/chunks/chunk-G5XGBCM6.js +0 -1
  775. package/dist/dashboard-web/chunks/chunk-IKN6WURK.js +0 -1
  776. package/dist/dashboard-web/chunks/chunk-WLIVBPJC.js +0 -1
  777. package/dist/dashboard-web/chunks/groups-page-VDOOLS4M.js +0 -8
  778. package/dist/dashboard-web/chunks/overview-page-7NYPKQSB.js +0 -1
  779. package/dist/dashboard-web/chunks/sessions-page-DYZZMKFL.js +0 -8
  780. package/dist/dashboard-web/chunks/settings-page-LIULUSMH.js +0 -2
@@ -0,0 +1,1158 @@
1
+ /**
2
+ * A containment handle: the thing that still identifies a mojo turn's process
3
+ * subtree AFTER the backend object that spawned it is gone.
4
+ *
5
+ * Why this module exists
6
+ * ---------------------
7
+ * mojo-process-tree.ts can enumerate a subtree, but only while someone still
8
+ * remembers two facts: the root pid, and the env nonce that was injected into it.
9
+ * Both used to live in per-instance MojoBackend fields (`lastTurnPid`, a
10
+ * `readonly treeNonce` freshly randomised in the field initialiser). Every worker
11
+ * generation therefore got a BRAND NEW nonce and a null root pid, which means a
12
+ * subtree left behind by the previous generation became permanently
13
+ * unenumerable — and `terminateChildProven()` reads "no root pid" as "no subtree
14
+ * exists" and returns true. A credentialed survivor thus turned into a
15
+ * successful close, the row was published `closed`, and the device-isolation
16
+ * blocker vanished (mergePersistedDeviceIsolationSessions filters closed rows).
17
+ *
18
+ * So the handle has to outlive the backend, the worker generation and the daemon
19
+ * process. That is what this module provides, plus the ONE rule that makes it
20
+ * safe:
21
+ *
22
+ * a handle may only be released when quiescence is PROVEN.
23
+ *
24
+ * Two strengths of handle
25
+ * -----------------------
26
+ * STRONG (`cgroup`) — a per-session cgroup v2 directory. Membership survives
27
+ * setsid and is the right tool to KILL a whole turn subtree (cgroup.kill) and
28
+ * to ENUMERATE it. But `cgroup.procs` being empty is NOT a boundary proof:
29
+ * mojo runs at the daemon's UID, and cgroup v2 lets a same-UID process migrate
30
+ * ITSELF out of the leaf (to the parent slice or a sibling) within the
31
+ * delegation domain — a move the leaf-down read cannot see. So a cgroup handle
32
+ * is treated like a weak one for RELEASE purposes (emptiness stops signalling
33
+ * and lets the row close, but the blocker is retained); only a per-session UID
34
+ * or namespace the process cannot leave would make emptiness a real proof.
35
+ *
36
+ * WEAK (`tree-identity`) — a persisted (rootPid, bootId, startTime, nonce)
37
+ * record for hosts with no usable cgroup v2 (this includes cgroup-v1-only
38
+ * hosts and Darwin). It is EVIDENCE THAT A TREE MAY STILL EXIST, never proof
39
+ * that one is gone. `bootId` + `startTime` exist so that pid REUSE cannot be
40
+ * mistaken for the original tree (and, in the one genuinely provable
41
+ * direction, so that a reboot can be recognised as having killed it).
42
+ *
43
+ * Fail-closed, in the same direction as the rest of the isolation path
44
+ * -------------------------------------------------------------------
45
+ * Unreadable state, an unsupported platform, a timeout, or any "cannot tell"
46
+ * answer resolves to NOT PROVEN, so the caller keeps the blocker. This matches
47
+ * the scanner's fail-closed contract (a read error fails the whole scan) and the
48
+ * destroy contract's `local-unproven` staying fenced. `mojo-launcher-env-quarantine`
49
+ * deliberately shipped with no clearing API because "proving that needs
50
+ * trustworthy termination of the whole mojo process group ... which does not
51
+ * exist yet"; `proveContainmentQuiescent` is that missing mechanism, which is why
52
+ * releasing is allowed here and only against a proven verdict.
53
+ */
54
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, unlinkSync, writeFileSync } from 'node:fs';
55
+ import { randomBytes } from 'node:crypto';
56
+ import { dirname, join } from 'node:path';
57
+ import { config } from '../config.js';
58
+ import { logger } from '../utils/logger.js';
59
+ import { withFileLockSync } from '../utils/file-lock.js';
60
+ const FILE_NAME = 'mojo-containment-handles.json';
61
+ /** Where per-session cgroups are created when the host supports cgroup v2. */
62
+ const CGROUP_ROOT = '/sys/fs/cgroup';
63
+ const CGROUP_SLICE = 'botmux.slice';
64
+ /**
65
+ * Every pid enrolled ANYWHERE in a cgroup subtree — the leaf plus every
66
+ * descendant cgroup, read recursively.
67
+ *
68
+ * A single-level `cgroup.procs` read is not a containment proof: a same-uid
69
+ * process can `mkdir` a child cgroup under a leaf it owns and migrate itself
70
+ * into it, after which the leaf's own `cgroup.procs` reads empty while the
71
+ * process runs on in the nested group. Reading the whole subtree closes that
72
+ * escape. `{ok:false}` on any unreadable level is the fail-closed signal —
73
+ * "cannot enumerate" must never collapse into "nobody there".
74
+ *
75
+ * NOTE ON SCOPE: this proves quiescence of the subtree we OWN. It does not, and
76
+ * cannot, cover a same-uid adversary that migrates a process OUT to a sibling or
77
+ * ancestor cgroup — cgroups are not a security boundary against a peer uid, and
78
+ * only per-session uid isolation would close that. `prepareContainmentBoundary`
79
+ * nests the boundary under the daemon's own (delegated) cgroup so a non-root
80
+ * delegated daemon at least blocks UP-migration; the residual sibling case is
81
+ * held by device-isolation staying fail-closed (the blocker is dropped only on
82
+ * a proof, never inferred).
83
+ */
84
+ function cgroupSubtreePids(dir) {
85
+ let pids;
86
+ try {
87
+ pids = readFileSync(`${dir}/cgroup.procs`, 'utf8')
88
+ .split('\n').map(l => Number(l.trim())).filter(n => Number.isInteger(n) && n > 0);
89
+ }
90
+ catch (err) {
91
+ // ENOENT at a subtree root means that cgroup is genuinely gone.
92
+ if (err.code === 'ENOENT')
93
+ return { ok: true, pids: [] };
94
+ return { ok: false, reason: `cannot read ${dir}/cgroup.procs (${err.code ?? 'unknown'})` };
95
+ }
96
+ let entries;
97
+ try {
98
+ entries = readdirSync(dir, { withFileTypes: true });
99
+ }
100
+ catch (err) {
101
+ if (err.code === 'ENOENT')
102
+ return { ok: true, pids };
103
+ return { ok: false, reason: `cannot enumerate ${dir} (${err.code ?? 'unknown'})` };
104
+ }
105
+ for (const entry of entries) {
106
+ if (!entry.isDirectory())
107
+ continue;
108
+ const child = cgroupSubtreePids(join(dir, entry.name));
109
+ if (!child.ok)
110
+ return child;
111
+ pids.push(...child.pids);
112
+ }
113
+ return { ok: true, pids };
114
+ }
115
+ /**
116
+ * The cgroup the daemon itself lives in (from /proc/self/cgroup), used as the
117
+ * PARENT of per-session boundaries.
118
+ *
119
+ * Nesting under the daemon's own cgroup keeps the per-session boundaries tidy
120
+ * and co-located with the daemon. It does NOT make the cgroup a security
121
+ * boundary: within a single delegation domain a same-UID process can still
122
+ * migrate itself between cgroups (up to a writable ancestor, or into a sibling),
123
+ * which is exactly why cgroup emptiness is no longer a release proof (see
124
+ * containmentReleaseDecision). Falls back to the flat `botmux.slice` at the root
125
+ * when /proc/self/cgroup is unreadable or names the root — the caller's `mkdir`
126
+ * still degrades to a weak handle if neither works.
127
+ */
128
+ function resolveSliceParent(cgroupRoot, procRoot = '/proc') {
129
+ try {
130
+ const rel = readFileSync(`${procRoot}/self/cgroup`, 'utf8')
131
+ .split('\n').find(l => l.startsWith('0::'))?.slice(3).trim();
132
+ if (rel && rel !== '/' && rel.startsWith('/')) {
133
+ const candidate = join(cgroupRoot, rel, CGROUP_SLICE);
134
+ // The daemon's cgroup dir must exist and be a real cgroup for nesting
135
+ // to mean anything; otherwise fall back.
136
+ if (existsSync(join(cgroupRoot, rel, 'cgroup.procs')))
137
+ return candidate;
138
+ }
139
+ }
140
+ catch { /* fall through to the flat root slice */ }
141
+ return join(cgroupRoot, CGROUP_SLICE);
142
+ }
143
+ /** Thrown instead of degrading to an empty (fail-open) handle store. */
144
+ export class MojoContainmentUnavailableError extends Error {
145
+ cause;
146
+ constructor(message, cause) {
147
+ super(message);
148
+ this.cause = cause;
149
+ this.name = 'MojoContainmentUnavailableError';
150
+ }
151
+ }
152
+ // ── host facts ───────────────────────────────────────────────────────────────
153
+ /**
154
+ * Boot identity of the running kernel.
155
+ *
156
+ * Returns null when it cannot be read (non-Linux, or a locked-down /proc).
157
+ * Callers must treat null as "cannot mint a trustworthy weak handle", NOT as a
158
+ * blank value to store: a handle whose bootId is empty would compare equal
159
+ * across reboots and across hosts, resurrecting exactly the pid-reuse confusion
160
+ * the field exists to prevent.
161
+ */
162
+ export function readBootId(opts = {}) {
163
+ const procRoot = opts.procRoot ?? '/proc';
164
+ try {
165
+ const id = readFileSync(`${procRoot}/sys/kernel/random/boot_id`, 'utf8').trim();
166
+ return id.length > 0 ? id : null;
167
+ }
168
+ catch {
169
+ return null;
170
+ }
171
+ }
172
+ /**
173
+ * `/proc/<pid>/stat` field 22 (starttime, in clock ticks since boot).
174
+ *
175
+ * null means the pid is not currently live (or is unreadable), which is why the
176
+ * caller may never read null as "the tree is gone": the ROOT exiting says
177
+ * nothing about a descendant that called setsid().
178
+ *
179
+ * Field indexing has the same comm hazard as the scanner: field 2 is
180
+ * parenthesised and may contain spaces or ')', so the split starts after the LAST
181
+ * ')'. From there, index 0 is state (field 3), hence starttime (field 22) sits at
182
+ * index 19.
183
+ */
184
+ export function readProcStartTime(pid, opts = {}) {
185
+ const procRoot = opts.procRoot ?? '/proc';
186
+ let text;
187
+ try {
188
+ text = readFileSync(`${procRoot}/${pid}/stat`, 'utf8');
189
+ }
190
+ catch {
191
+ return null;
192
+ }
193
+ const close = text.lastIndexOf(')');
194
+ if (close < 0)
195
+ return null;
196
+ const rest = text.slice(close + 1).trim().split(/\s+/);
197
+ const started = Number(rest[19]);
198
+ return Number.isInteger(started) ? started : null;
199
+ }
200
+ /**
201
+ * Is a usable cgroup v2 hierarchy mounted?
202
+ *
203
+ * `cgroup.controllers` exists only on the v2 unified hierarchy, so its presence
204
+ * is the cheap discriminator against a v1-only host (where per-session
205
+ * containment via this module is not available).
206
+ */
207
+ export function cgroupV2Available(opts = {}) {
208
+ const root = opts.cgroupRoot ?? CGROUP_ROOT;
209
+ try {
210
+ readFileSync(`${root}/cgroup.controllers`, 'utf8');
211
+ return true;
212
+ }
213
+ catch {
214
+ return false;
215
+ }
216
+ }
217
+ export function readProcLiveness(pid, opts = {}) {
218
+ const procRoot = opts.procRoot ?? '/proc';
219
+ let text;
220
+ try {
221
+ text = readFileSync(`${procRoot}/${pid}/stat`, 'utf8');
222
+ }
223
+ catch (err) {
224
+ return err.code === 'ENOENT' ? 'gone' : 'unreadable';
225
+ }
226
+ // Same comm hazard as everywhere else: field 2 is parenthesised and may
227
+ // contain spaces or ')', so cut at the LAST ')'. After that, index 0 is
228
+ // field 3 (state).
229
+ const close = text.lastIndexOf(')');
230
+ if (close < 0)
231
+ return 'unreadable';
232
+ const state = text.slice(close + 1).trim().split(/\s+/)[0];
233
+ if (!state)
234
+ return 'unreadable';
235
+ return state === 'Z' ? 'zombie' : 'running';
236
+ }
237
+ /**
238
+ * Create the cgroup directory for a turn about to be spawned.
239
+ *
240
+ * Returns null when the host cannot support a strong boundary (no cgroup v2, or
241
+ * the slice is not writable) — the caller falls back to the weak post-spawn
242
+ * handle, which never claims boundary proof.
243
+ */
244
+ export function prepareContainmentBoundary(input, opts = {}) {
245
+ const cgroupRoot = opts.cgroupRoot ?? CGROUP_ROOT;
246
+ if (!cgroupV2Available({ cgroupRoot }))
247
+ return null;
248
+ // Nest under the daemon's own (delegated) cgroup so a non-root delegated
249
+ // daemon's boundaries have a root-owned parent — blocking a same-uid child
250
+ // from migrating UP and out. Falls back to the flat root slice.
251
+ const sliceParent = resolveSliceParent(cgroupRoot, opts.procRoot);
252
+ // A stable, collision-free name: two generations of the same session must
253
+ // not share a directory, or releasing one would release the other's proof.
254
+ const dir = join(sliceParent, `mojo-${sanitizeForPath(input.sessionId)}-g${input.generation}-${randomBytes(4).toString('hex')}`);
255
+ // Two attempts: transient resource errno (system-wide file-table pressure —
256
+ // ENFILE/EMFILE — or an interrupted syscall) must not silently cost this turn
257
+ // its STRONG boundary when the very next open would succeed. Anything
258
+ // persistent (undelegated slice, read-only /sys) fails both attempts
259
+ // identically and degrades below.
260
+ let lastErr;
261
+ for (let attempt = 0; attempt < 2; attempt++) {
262
+ try {
263
+ mkdirSync(dir, { recursive: true });
264
+ // Enrolment is the CHILD's job (pre-exec), but it must be possible at
265
+ // all: an unwritable cgroup.procs would make every spawn die on the shim
266
+ // handshake. Probe writability now — an empty write is a no-op for the
267
+ // kernel — so an undelegated slice degrades to the weak handle here
268
+ // instead of failing every turn later.
269
+ writeFileSync(`${dir}/cgroup.procs`, '');
270
+ return { sessionId: input.sessionId, generation: input.generation, cgroupPath: dir, nonce: input.nonce };
271
+ }
272
+ catch (err) {
273
+ lastErr = err;
274
+ try {
275
+ rmdirSync(dir);
276
+ }
277
+ catch { /* best-effort */ }
278
+ }
279
+ }
280
+ // Delegation not granted / read-only /sys / persistent resource exhaustion —
281
+ // the caller falls back to the weak handle rather than pretending containment
282
+ // was established. Loudly: this downgrades the session's containment evidence
283
+ // for the whole turn (its close can only ever be `closed_with_residual`), and
284
+ // a silent downgrade here is indistinguishable from "host has no cgroup v2"
285
+ // when someone later asks why a strong-capable host produced weak handles.
286
+ logger.warn(`[mojo] session ${input.sessionId}: could not prepare a strong cgroup boundary under `
287
+ + `${sliceParent} (${lastErr?.code ?? String(lastErr)}); `
288
+ + 'this turn degrades to the weak tree-identity handle');
289
+ return null;
290
+ }
291
+ /** Mint the strong handle for a boundary the child has enrolled itself into. */
292
+ export function strongHandleFromPreparedBoundary(prepared, opts = {}) {
293
+ const bootId = readBootId({ procRoot: opts.procRoot });
294
+ return {
295
+ kind: 'cgroup',
296
+ sessionId: prepared.sessionId,
297
+ generation: prepared.generation,
298
+ cgroupPath: prepared.cgroupPath,
299
+ nonce: prepared.nonce,
300
+ // Stamp the boot id so a later reboot can prove the tree gone (see the
301
+ // cgroup branch of proveContainmentQuiescent). Omitted only if /proc is
302
+ // unreadable, in which case the handle behaves like a legacy one.
303
+ ...(bootId !== null ? { bootId } : {}),
304
+ };
305
+ }
306
+ /**
307
+ * Kill every process currently enrolled in a prepared boundary (cgroup.kill),
308
+ * then remove the directory if that emptied it.
309
+ *
310
+ * For the caller who spawned a child through the enrolment shim and then FAILED
311
+ * to record the handle durably: the subtree must not be left running behind a
312
+ * blocker nobody recorded. Returns true only when the boundary is provably
313
+ * empty (or already gone) afterwards — false means the caller must keep its
314
+ * own fence up.
315
+ */
316
+ export async function killPreparedBoundary(prepared) {
317
+ // The ONLY accepted proof of emptiness is a successful rmdir: the kernel
318
+ // refuses to remove a cgroup with members, and once removed no late enroller
319
+ // can enter (its `cgroup.procs` write fails, so the shim exits without
320
+ // exec'ing). Reading `cgroup.procs` and seeing it empty is NOT enough — a
321
+ // shim mid-handshake could enrol between the read and the rmdir, and a
322
+ // freshly SIGKILLed member lingers as a listed zombie until reaped.
323
+ //
324
+ // The attempts are SPACED (~50ms): `cgroup.kill` SIGKILLs the members, but a
325
+ // setsid descendant that reparents to init lingers as a listed zombie until
326
+ // init reaps it, so a zero-backoff loop would usually exhaust its tries
327
+ // against a corpse and latch the wedge. Descendant cgroups are killed too —
328
+ // cgroup.kill is recursive, but a nested cgroup dir must still be rmdir'd
329
+ // before the parent, which the depth-first sweep below handles.
330
+ for (let attempt = 0; attempt < 5; attempt++) {
331
+ if (attempt > 0)
332
+ await new Promise(r => { setTimeout(r, 50).unref?.(); });
333
+ try {
334
+ writeFileSync(`${prepared.cgroupPath}/cgroup.kill`, '1\n');
335
+ }
336
+ catch (err) {
337
+ if (err.code === 'ENOENT')
338
+ return true;
339
+ // Unwritable kill knob (or a synthetic boundary in tests): fall
340
+ // through — the rmdir below is still the authoritative proof.
341
+ }
342
+ if (rmdirCgroupTree(prepared.cgroupPath))
343
+ return true;
344
+ // EBUSY / ENOTEMPTY: members still live (or racing in) — kill again.
345
+ }
346
+ return false;
347
+ }
348
+ /**
349
+ * Depth-first rmdir of a cgroup subtree: child cgroups must be removed before
350
+ * their parent. Returns true when the whole tree is gone.
351
+ *
352
+ * On a SYNTHETIC (plain-directory) boundary the knob writes leave `cgroup.procs`
353
+ * / `cgroup.kill` files behind, which a plain rmdir refuses; unlinking them is a
354
+ * no-op on real cgroupfs (EPERM, swallowed) and restores rmdir's meaning in
355
+ * tests. Only the two known knob names are unlinked — never arbitrary content —
356
+ * so a real member file can't be spirited away.
357
+ */
358
+ function rmdirCgroupTree(dir) {
359
+ let entries;
360
+ try {
361
+ entries = readdirSync(dir, { withFileTypes: true });
362
+ }
363
+ catch (err) {
364
+ return err.code === 'ENOENT';
365
+ }
366
+ for (const entry of entries) {
367
+ if (entry.isDirectory() && !rmdirCgroupTree(join(dir, entry.name)))
368
+ return false;
369
+ }
370
+ for (const knob of ['cgroup.procs', 'cgroup.kill']) {
371
+ try {
372
+ unlinkSync(`${dir}/${knob}`);
373
+ }
374
+ catch { /* cgroupfs refuses; fine */ }
375
+ }
376
+ try {
377
+ rmdirSync(dir);
378
+ return true;
379
+ }
380
+ catch (err) {
381
+ return err.code === 'ENOENT';
382
+ }
383
+ }
384
+ /**
385
+ * Mint the strongest POST-SPAWN handle this host can describe a turn with.
386
+ *
387
+ * Deliberately NEVER a cgroup handle: migrating an already-running root into a
388
+ * cgroup does not capture descendants it forked before the write, so a strong
389
+ * handle minted here would claim a boundary that provably has a hole (the P0
390
+ * this module was rewritten for). Strong handles exist only via
391
+ * prepareContainmentBoundary + the pre-exec enrolment shim. This function is
392
+ * the fallback for hosts (or spawn paths) where that was not possible, and the
393
+ * handles it mints never carry boundary proof.
394
+ */
395
+ export function acquireContainmentHandle(input, opts = {}) {
396
+ const bootId = readBootId({ procRoot: opts.procRoot });
397
+ const startTime = bootId === null
398
+ ? null
399
+ : readProcStartTime(input.rootPid, { procRoot: opts.procRoot });
400
+ if (bootId === null || startTime === null) {
401
+ // NOT null. Returning null here meant the caller recorded nothing and
402
+ // `hasUnprovenContainment()` answered false, so the blocker was dropped on
403
+ // precisely the hosts that can never prove anything (see
404
+ // UnprovableContainmentHandle).
405
+ return {
406
+ kind: 'unprovable',
407
+ sessionId: input.sessionId,
408
+ generation: input.generation,
409
+ nonce: input.nonce,
410
+ platform: opts.platform ?? process.platform,
411
+ reason: bootId === null
412
+ ? 'no cgroup v2 delegation and no readable boot id'
413
+ : `no cgroup v2 delegation and pid ${input.rootPid} has no readable starttime`,
414
+ };
415
+ }
416
+ return {
417
+ kind: 'tree-identity',
418
+ sessionId: input.sessionId,
419
+ generation: input.generation,
420
+ rootPid: input.rootPid,
421
+ bootId,
422
+ startTime,
423
+ nonce: input.nonce,
424
+ };
425
+ }
426
+ function sanitizeForPath(value) {
427
+ return value.replace(/[^A-Za-z0-9_.-]/g, '_');
428
+ }
429
+ /**
430
+ * Can we prove that nothing this handle owns is still executing?
431
+ *
432
+ * STRONG handle: `cgroup.procs` is authoritative. An absent directory also counts
433
+ * as proof, because the kernel refuses `rmdir` on a non-empty cgroup and this
434
+ * module only removes one after a proven verdict — so "gone" can only mean
435
+ * "was empty when it went". Any OTHER read error is a non-proof.
436
+ *
437
+ * WEAK handle:
438
+ * - a bootId mismatch is genuine, cheap proof: the recorded tree cannot have
439
+ * survived the reboot that changed the id;
440
+ * - otherwise the only available evidence is a /proc scan, which the caller
441
+ * must supply. No scan, or a failed scan, or any surviving pid → not proven.
442
+ * - the root pid being gone is explicitly NOT accepted on its own: a descendant
443
+ * that called setsid() outlives its parent, which is the whole reason the
444
+ * scanner unions three signals.
445
+ *
446
+ * A clean weak verdict is the best this host can do, not an unforgeable
447
+ * boundary — see the trust-domain note in mojo-process-tree. Callers that need
448
+ * certainty need a strong handle.
449
+ */
450
+ export function proveContainmentQuiescent(handle, opts = {}) {
451
+ if (handle.kind === 'cgroup') {
452
+ // A REBOOT is the one thing that provably kills the whole tree, including
453
+ // the same-UID sibling/parent migrant that cgroup emptiness cannot see.
454
+ // Check it FIRST: after a reboot the cgroup directory is gone, so the
455
+ // subtree read below would report `cgroup-empty` — which is NOT a boundary
456
+ // proof (round-8) and would leave the handle unreleasable forever. A
457
+ // changed boot id turns that into the real `boot-id-changed` proof, so a
458
+ // reboot actually clears a cgroup handle (round-9 P2-1). A handle minted
459
+ // before this field existed has no bootId and simply falls through.
460
+ if (handle.bootId) {
461
+ const currentBootId = readBootId({ procRoot: opts.procRoot });
462
+ if (currentBootId !== null && currentBootId !== handle.bootId) {
463
+ return { proven: true, handle, evidence: 'boot-id-changed' };
464
+ }
465
+ }
466
+ // Read the WHOLE subtree, not just the leaf: a same-uid process can mkdir
467
+ // a child cgroup and migrate into it, which a single-level read would miss
468
+ // (it would see the leaf empty and call the tree gone).
469
+ const subtree = cgroupSubtreePids(handle.cgroupPath);
470
+ if (!subtree.ok) {
471
+ return { proven: false, handle, reason: subtree.reason };
472
+ }
473
+ const pids = subtree.pids;
474
+ if (pids.length === 0)
475
+ return { proven: true, handle, evidence: 'cgroup-empty' };
476
+ // A non-empty cgroup is NOT automatically a live tree. `cgroup.procs`
477
+ // keeps listing a zombie until its parent reaps it, and a zombie executes
478
+ // nothing and cannot use the injected credential. Treating it as alive
479
+ // would wedge the session permanently: the close is already allowed to
480
+ // succeed (the scanner reaches the same verdict), yet the blocker could
481
+ // never clear and `rmdir` on the cgroup would fail forever, leaving an
482
+ // unexplainable stuck blocker and a leaked cgroup directory.
483
+ //
484
+ // Fail-closed is preserved in the direction that matters: only state 'Z'
485
+ // is discounted, an ENOENT means the pid genuinely left, and a state we
486
+ // cannot read counts as EXECUTING.
487
+ const executing = [];
488
+ const zombies = [];
489
+ for (const pid of pids) {
490
+ const liveness = readProcLiveness(pid, { procRoot: opts.procRoot });
491
+ if (liveness === 'gone')
492
+ continue; // raced us; not a member
493
+ if (liveness === 'zombie') {
494
+ zombies.push(pid);
495
+ continue;
496
+ }
497
+ executing.push(pid); // running OR unreadable
498
+ }
499
+ if (executing.length > 0) {
500
+ return { proven: false, handle, reason: 'cgroup still has executing members', residualPids: executing };
501
+ }
502
+ // Zombie-only (or everything raced away): nothing can execute, so this is
503
+ // genuine quiescence. Reported in the reason so an operator can see why a
504
+ // still-populated cgroup was accepted.
505
+ return zombies.length > 0
506
+ ? {
507
+ proven: true,
508
+ handle,
509
+ reason: `zombie-only cgroup members (${zombies.join(',')})`,
510
+ evidence: 'cgroup-zombie-only',
511
+ }
512
+ : { proven: true, handle, evidence: 'cgroup-empty' };
513
+ }
514
+ if (handle.kind === 'unprovable') {
515
+ // By construction there is no evidence that could settle this. Returning
516
+ // proven:false unconditionally is what keeps the blocker, and because
517
+ // releaseContainmentHandle only accepts a proven verdict, such a handle can
518
+ // never be released — which is the intended terminal state.
519
+ return {
520
+ proven: false,
521
+ handle,
522
+ reason: `containment is unprovable on ${handle.platform}: ${handle.reason}`,
523
+ };
524
+ }
525
+ const currentBootId = readBootId({ procRoot: opts.procRoot });
526
+ if (currentBootId === null) {
527
+ // We cannot even establish which boot we are on, so we cannot rule out
528
+ // that this pid is the original tree. Fail closed.
529
+ return { proven: false, handle, reason: 'boot id unreadable; cannot age out the recorded tree' };
530
+ }
531
+ if (currentBootId !== handle.bootId) {
532
+ return { proven: true, handle, evidence: 'boot-id-changed' };
533
+ }
534
+ if (!opts.scan) {
535
+ return {
536
+ proven: false,
537
+ handle,
538
+ reason: 'weak containment handle cannot prove quiescence without a subtree scan',
539
+ };
540
+ }
541
+ const evidence = opts.scan(handle);
542
+ if (!evidence.scanned) {
543
+ return {
544
+ proven: false,
545
+ handle,
546
+ reason: `subtree scan failed: ${evidence.reason ?? 'unknown'}`,
547
+ };
548
+ }
549
+ if (evidence.pids.length > 0) {
550
+ return {
551
+ proven: false,
552
+ handle,
553
+ reason: 'subtree still has live members',
554
+ residualPids: [...evidence.pids],
555
+ };
556
+ }
557
+ // Clean scan. Labelled `scan-clean` on purpose, NOT a boundary proof: this is
558
+ // exactly the shape the reviewer reproduced with a setsid + scrubbed-environ
559
+ // + reparented descendant, which stays alive while the scan reports nothing.
560
+ return { proven: true, handle, evidence: 'scan-clean' };
561
+ }
562
+ /**
563
+ * Is the recorded root pid still the ORIGINAL process?
564
+ *
565
+ * Used before signalling: a weak handle names a pid, and pids are reused. Sending
566
+ * SIGKILL to a recycled pid would kill an unrelated process, so a caller must
567
+ * confirm identity first. False therefore means "do not signal this pid", NOT
568
+ * "the tree is gone".
569
+ */
570
+ export function weakHandleRootStillOriginal(handle, opts = {}) {
571
+ const bootId = readBootId({ procRoot: opts.procRoot });
572
+ if (bootId === null || bootId !== handle.bootId)
573
+ return false;
574
+ const startTime = readProcStartTime(handle.rootPid, { procRoot: opts.procRoot });
575
+ return startTime !== null && startTime === handle.startTime;
576
+ }
577
+ function filePath(dataDir) {
578
+ return join(dataDir ?? config.session.dataDir, FILE_NAME);
579
+ }
580
+ /** Stable identity of a handle, so union/removal cannot double-count or mis-hit. */
581
+ export function containmentHandleKey(handle) {
582
+ if (handle.kind === 'cgroup')
583
+ return `cgroup:${handle.cgroupPath}`;
584
+ if (handle.kind === 'unprovable')
585
+ return `unprovable:${handle.sessionId}:${handle.generation}`;
586
+ return `tree:${handle.bootId}:${handle.rootPid}:${handle.startTime}`;
587
+ }
588
+ function parseHandle(value, path, sessionId) {
589
+ const bad = (why) => {
590
+ throw new MojoContainmentUnavailableError(`mojo containment store at ${path} has ${why} for ${sessionId}; `
591
+ + 'refusing to treat the session as contained');
592
+ };
593
+ if (!value || typeof value !== 'object')
594
+ return bad('a non-object handle');
595
+ const h = value;
596
+ const common = typeof h.sessionId === 'string' && h.sessionId.length > 0
597
+ && typeof h.generation === 'number' && Number.isInteger(h.generation)
598
+ && typeof h.nonce === 'string' && h.nonce.length > 0;
599
+ if (!common)
600
+ return bad('a handle with missing common fields');
601
+ if (h.kind === 'cgroup') {
602
+ if (typeof h.cgroupPath !== 'string' || h.cgroupPath.length === 0)
603
+ return bad('a cgroup handle with no path');
604
+ // bootId is OPTIONAL (legacy handles predate it), but if present it must be
605
+ // a non-empty string — a blank one would compare equal across reboots and
606
+ // resurrect the very pid/boot confusion the field exists to prevent.
607
+ if (h.bootId !== undefined && (typeof h.bootId !== 'string' || h.bootId.length === 0)) {
608
+ return bad('a cgroup handle with a malformed bootId');
609
+ }
610
+ return h;
611
+ }
612
+ if (h.kind === 'unprovable') {
613
+ if (typeof h.platform !== 'string' || h.platform.length === 0
614
+ || typeof h.reason !== 'string' || h.reason.length === 0) {
615
+ return bad('a malformed unprovable handle');
616
+ }
617
+ return h;
618
+ }
619
+ if (h.kind === 'tree-identity') {
620
+ const ok = typeof h.rootPid === 'number' && Number.isInteger(h.rootPid) && h.rootPid > 0
621
+ && typeof h.bootId === 'string' && h.bootId.length > 0
622
+ && typeof h.startTime === 'number' && Number.isInteger(h.startTime);
623
+ if (!ok)
624
+ return bad('a malformed tree-identity handle');
625
+ return h;
626
+ }
627
+ return bad(`an unknown handle kind ${JSON.stringify(h.kind)}`);
628
+ }
629
+ /**
630
+ * Read the store. No caching: a cached snapshot would hide another daemon's
631
+ * writes and could lose a recorded, still-unproven tree.
632
+ *
633
+ * A genuinely absent file is an empty store; anything else (EACCES, corrupt JSON,
634
+ * unknown version, malformed entry) THROWS, because "cannot read" must never
635
+ * become "nothing is contained" — that is the fail-open direction, and it is
636
+ * trivially arranged by the same-user process being policed.
637
+ */
638
+ function readStrict(dataDir) {
639
+ const path = filePath(dataDir);
640
+ let raw;
641
+ try {
642
+ raw = readFileSync(path, 'utf8');
643
+ }
644
+ catch (err) {
645
+ if (err.code === 'ENOENT') {
646
+ return { version: 1, sessions: {} };
647
+ }
648
+ throw new MojoContainmentUnavailableError(`cannot read mojo containment store at ${path} `
649
+ + `(${err.code ?? 'unknown'}); `
650
+ + 'refusing to treat sessions as contained', err);
651
+ }
652
+ let parsed;
653
+ try {
654
+ parsed = JSON.parse(raw);
655
+ }
656
+ catch (err) {
657
+ throw new MojoContainmentUnavailableError(`mojo containment store at ${path} is corrupt; refusing to treat sessions as contained`, err);
658
+ }
659
+ const version = parsed?.version;
660
+ if (version !== 1) {
661
+ throw new MojoContainmentUnavailableError(`mojo containment store at ${path} has unsupported version ${JSON.stringify(version)}`);
662
+ }
663
+ const sessionsRaw = parsed?.sessions;
664
+ if (!parsed || typeof parsed !== 'object' || !sessionsRaw || typeof sessionsRaw !== 'object') {
665
+ throw new MojoContainmentUnavailableError(`mojo containment store at ${path} has an unexpected shape; refusing to treat sessions as contained`);
666
+ }
667
+ const sessions = {};
668
+ for (const [sessionId, list] of Object.entries(sessionsRaw)) {
669
+ if (!Array.isArray(list)) {
670
+ throw new MojoContainmentUnavailableError(`mojo containment store at ${path} has a non-array entry for ${sessionId}`);
671
+ }
672
+ // Rejecting the whole file rather than filtering: silently dropping one
673
+ // junk element would EMPTY that session's containment and unblock it.
674
+ sessions[sessionId] = list.map(item => parseHandle(item, path, sessionId));
675
+ }
676
+ return { version: 1, sessions };
677
+ }
678
+ /** Atomic replace via a UNIQUE temp file — a shared `.tmp` races between daemons. */
679
+ function writeStrict(data, dataDir) {
680
+ const path = filePath(dataDir);
681
+ const tmp = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
682
+ try {
683
+ mkdirSync(dirname(path), { recursive: true });
684
+ writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, { mode: 0o600 });
685
+ renameSync(tmp, path);
686
+ }
687
+ catch (err) {
688
+ try {
689
+ if (existsSync(tmp))
690
+ unlinkSync(tmp);
691
+ }
692
+ catch { /* best-effort cleanup */ }
693
+ throw new MojoContainmentUnavailableError(`cannot persist mojo containment store at ${path}; an unproven subtree would be lost`, err);
694
+ }
695
+ }
696
+ /**
697
+ * Record a handle as OWNED AND UNPROVEN.
698
+ *
699
+ * Call this at spawn time, before the child can do anything: a crash between
700
+ * spawn and record is exactly the window that used to lose the tree entirely.
701
+ * Monotonic union by handle identity, so re-recording is idempotent and a later
702
+ * call can never retract an earlier one.
703
+ *
704
+ * THROWS on any read/write failure — the caller must not proceed believing the
705
+ * tree was recorded.
706
+ */
707
+ export function recordContainmentHandle(handle, dataDir) {
708
+ const path = filePath(dataDir);
709
+ mkdirSync(dirname(path), { recursive: true });
710
+ withFileLockSync(path, () => {
711
+ const data = readStrict(dataDir);
712
+ const before = data.sessions[handle.sessionId] ?? [];
713
+ const key = containmentHandleKey(handle);
714
+ if (before.some(h => containmentHandleKey(h) === key))
715
+ return;
716
+ data.sessions[handle.sessionId] = [...before, handle];
717
+ writeStrict(data, dataDir);
718
+ });
719
+ }
720
+ /**
721
+ * Handles this session still owns. Empty ONLY when nothing is outstanding.
722
+ *
723
+ * THROWS when the store cannot be read, so isolation callers fail closed instead
724
+ * of reading an error as "clean".
725
+ */
726
+ export function containmentHandles(sessionId, dataDir) {
727
+ return readStrict(dataDir).sessions[sessionId] ?? [];
728
+ }
729
+ /**
730
+ * Session ids with an outstanding handle, INCLUDING sessions whose row is gone.
731
+ *
732
+ * The residual path needs this: an explicit `/close` deletes the row, so without
733
+ * it the inventory would lose every trace of an unproven credentialed subtree.
734
+ */
735
+ export function containmentSessionIds(dataDir) {
736
+ return Object.keys(readStrict(dataDir).sessions);
737
+ }
738
+ /**
739
+ * Does this session still have a tree we cannot prove is gone?
740
+ *
741
+ * This is the predicate the device-isolation blocker hangs off. It THROWS on an
742
+ * unreadable store rather than answering false.
743
+ */
744
+ export function hasUnprovenContainment(sessionId, dataDir) {
745
+ return containmentHandles(sessionId, dataDir).length > 0;
746
+ }
747
+ /**
748
+ * Release every durable handle a REBOOT has provably killed, and report what
749
+ * stayed. Run once per daemon boot, BEFORE the device-isolation activation
750
+ * inventory is built.
751
+ *
752
+ * Why this exists (round-11 P1-1): `proveContainmentQuiescent` gained a
753
+ * boot-id-changed branch for cgroup handles, but its only production callers are
754
+ * live-worker teardown and workerless close — NEITHER runs for a session that is
755
+ * already `closed_with_residual` and exists only as a durable handle. So after a
756
+ * host reboot (which truly kills the whole tree, migrant included) that handle
757
+ * lingered forever and device isolation kept synthesising an `activation_blocked`
758
+ * from it — the new bootId proof had no consumer. This is that consumer: it
759
+ * enumerates the store and RELEASES only handles whose verdict is
760
+ * `boot-id-changed`. A same-boot handle, a legacy handle with no bootId, an
761
+ * unprovable handle, or a still-live cgroup are all RETAINED.
762
+ *
763
+ * Fail-CLOSED throughout: an unreadable store, an unreadable /proc, or a handle
764
+ * whose proof throws leaves that handle in place. Releasing on a "cannot tell"
765
+ * is exactly the unblock this whole module refuses.
766
+ */
767
+ export function reconcileContainmentHandlesOnBoot(opts = {}) {
768
+ let ids;
769
+ try {
770
+ ids = containmentSessionIds(opts.dataDir);
771
+ }
772
+ catch (err) {
773
+ logger.error(`[mojo] boot reconciliation skipped: containment store unreadable (${String(err)}); `
774
+ + 'every blocker is retained (fail-closed)');
775
+ return { released: 0, retained: 0, storeUnreadable: true };
776
+ }
777
+ let released = 0;
778
+ let retained = 0;
779
+ for (const sessionId of ids) {
780
+ let handles;
781
+ try {
782
+ handles = containmentHandles(sessionId, opts.dataDir);
783
+ }
784
+ catch {
785
+ retained++;
786
+ continue;
787
+ }
788
+ for (const handle of handles) {
789
+ let verdict;
790
+ try {
791
+ // No scan callback: we only ever act on `boot-id-changed`, which is
792
+ // decided from the stamped boot id alone. A weak scan-clean or a
793
+ // cgroup-empty verdict is deliberately NOT released here.
794
+ verdict = proveContainmentQuiescent(handle, opts.procRoot ? { procRoot: opts.procRoot } : {});
795
+ }
796
+ catch {
797
+ retained++;
798
+ continue;
799
+ }
800
+ if (verdict.proven && verdict.evidence === 'boot-id-changed') {
801
+ try {
802
+ releaseContainmentHandle(verdict, opts.dataDir);
803
+ released++;
804
+ }
805
+ catch {
806
+ retained++;
807
+ }
808
+ }
809
+ else {
810
+ retained++;
811
+ }
812
+ }
813
+ }
814
+ if (released > 0) {
815
+ logger.info(`[mojo] boot reconciliation: released ${released} containment handle(s) a reboot proved gone, `
816
+ + `retained ${retained}`);
817
+ }
818
+ return { released, retained, storeUnreadable: false };
819
+ }
820
+ /**
821
+ * Is this verdict strong enough to FORGET the tree, and how much stays behind?
822
+ *
823
+ * This is the production gate on `boundaryProof`. It exists because a
824
+ * `proven: true` verdict is NOT one thing: on a cgroup host it is kernel state,
825
+ * on a plain Linux host it can be nothing more than "the scan saw nobody", and
826
+ * those two must not share a code path.
827
+ *
828
+ * Truth table, all of it pinned by unit tests:
829
+ *
830
+ * verdict evidence boundaryProof release residual
831
+ * ---------------- -------------------- ------------- ------- --------
832
+ * proven, weak boot-id-changed true yes null
833
+ * proven, cgroup boot-id-changed true yes null
834
+ * proven, cgroup cgroup-empty FALSE NO deviceIsolation
835
+ * proven, cgroup cgroup-zombie-only FALSE NO deviceIsolation
836
+ * proven, weak scan-clean FALSE NO deviceIsolation
837
+ * not proven n/a false NO deviceIsolation
838
+ *
839
+ * Only a reboot (boot-id-changed) authorises forgetting a tree — for BOTH handle
840
+ * kinds since the strong handle gained a stamped bootId (a reboot kills the
841
+ * whole tree, sibling-migrant included, and resets cgroupfs). A cgroup being
842
+ * empty is NOT a boundary proof — a same-UID process can migrate itself out of
843
+ * the leaf, invisible to the leaf-down read — so an emptiness verdict behaves
844
+ * exactly like a weak scan-clean: it lets the caller stop re-signalling
845
+ * (`signalsStopped: true`) and lets the SESSION close, but authorises nothing
846
+ * else. The handle stays in the durable store, so `hasUnprovenContainment` keeps
847
+ * answering true and the device-isolation blocker survives the close.
848
+ */
849
+ export function containmentReleaseDecision(verdict) {
850
+ const handle = verdict.handle;
851
+ if (!verdict.proven) {
852
+ return {
853
+ boundaryProof: false,
854
+ releaseAuthorised: false,
855
+ evidence: 'not-proven',
856
+ residual: {
857
+ deviceIsolation: true,
858
+ pids: verdict.residualPids ? [...verdict.residualPids] : undefined,
859
+ reason: verdict.reason,
860
+ },
861
+ signalsStopped: false,
862
+ };
863
+ }
864
+ // An absent evidence field is filled in from the handle kind, never upgraded
865
+ // past it: a weak handle defaults to `scan-clean`, the weakest option.
866
+ const evidence = verdict.evidence
867
+ ?? (handle.kind === 'cgroup' ? 'cgroup-empty' : 'scan-clean');
868
+ // EXACTLY ONE unforgeable fact authorises forgetting a tree: a boot id change.
869
+ // A reboot provably killed every process; nothing else does.
870
+ //
871
+ // A cgroup being empty is DELIBERATELY NOT on this list, reversing an earlier
872
+ // revision. mojo runs at the daemon's UID, and cgroup v2 lets a same-UID
873
+ // process migrate ITSELF between cgroups within the delegation domain — up to
874
+ // the parent slice, or sideways into a sibling. The close proof recurses the
875
+ // leaf DOWNWARD, so a process that migrated OUT is invisible and the leaf reads
876
+ // empty. Treating that as proof cleared the device-isolation blocker while a
877
+ // credentialed process ran on — and "device-isolation fails closed" cannot
878
+ // back it up, because the blocker is the very thing the false proof clears
879
+ // (circular). Until a per-session UID or namespace makes the cgroup a boundary
880
+ // the process cannot leave, a cgroup handle is only a KILL + DIAGNOSTIC tool:
881
+ // like a weak scan-clean, it may stop signalling and let the row close, but the
882
+ // close carries a residual and the blocker is RETAINED. `cgroup.kill` + reboot
883
+ // remain the only things that genuinely remove the tree.
884
+ const boundaryProof = evidence === 'boot-id-changed';
885
+ if (boundaryProof) {
886
+ return {
887
+ boundaryProof: true,
888
+ releaseAuthorised: true,
889
+ evidence,
890
+ residual: null,
891
+ signalsStopped: true,
892
+ };
893
+ }
894
+ const reason = evidence === 'cgroup-empty' || evidence === 'cgroup-zombie-only'
895
+ ? `${evidence} is a KILL/diagnostic signal, not a boundary proof: a same-UID process can `
896
+ + 'migrate itself out of the cgroup (to the parent slice or a sibling), which a leaf-down '
897
+ + 'read cannot see. The blocker is retained until a reboot or an operator revoke.'
898
+ : `${evidence} is a diagnostic signal, not a boundary proof: a descendant that `
899
+ + 'calls setsid, scrubs its environ and reparents to init is invisible to the scan';
900
+ return {
901
+ boundaryProof: false,
902
+ releaseAuthorised: false,
903
+ evidence,
904
+ residual: { deviceIsolation: true, reason },
905
+ signalsStopped: true,
906
+ };
907
+ }
908
+ /**
909
+ * Release a handle — the ONLY removal path, and it demands the proof.
910
+ *
911
+ * Taking the verdict (rather than a boolean, or nothing at all) is deliberate: it
912
+ * makes "clear the blocker without proving quiescence" unrepresentable at the type
913
+ * level, which is the invariant this whole module exists to enforce. A caller
914
+ * holding a `proven: false` verdict has no way to spend it here.
915
+ *
916
+ * The ONE evidence that authorises release is `boot-id-changed`, for either
917
+ * handle kind (a strong handle carries a stamped bootId since round 10).
918
+ * Emptiness verdicts (cgroup-empty / cgroup-zombie-only / scan-clean) resolve to
919
+ * `releaseAuthorised: false` and return below with the handle and the
920
+ * device-isolation blocker retained — cgroup emptiness is not a boundary proof
921
+ * (a same-UID process can migrate out of the leaf; see
922
+ * containmentReleaseDecision). No cgroup directory is reclaimed on the release
923
+ * path: `boot-id-changed` means the host rebooted and cgroupfs came back empty,
924
+ * so there is nothing to remove. Within a boot the tree is killed via
925
+ * `cgroup.kill` during teardown and the handle waits for reboot or operator
926
+ * revoke.
927
+ */
928
+ export function releaseContainmentHandle(verdict, dataDir) {
929
+ if (!verdict.proven) {
930
+ throw new MojoContainmentUnavailableError(`refusing to release containment for session ${verdict.handle.sessionId}: `
931
+ + `quiescence was not proven (${verdict.reason})`);
932
+ }
933
+ const handle = verdict.handle;
934
+ const decision = containmentReleaseDecision(verdict);
935
+ if (!decision.releaseAuthorised) {
936
+ // Proven, but only diagnostically (a weak scan-clean, or ANY cgroup verdict
937
+ // now). Do NOT throw: the caller is allowed to stop signalling and to close
938
+ // the session. Just refuse to forget the tree, which is what keeps the
939
+ // device-isolation blocker after the close.
940
+ logger.warn(`[mojo] session ${handle.sessionId}: containment looks quiescent by `
941
+ + `${decision.evidence} but that is not a boundary proof; keeping the handle and the `
942
+ + 'device-isolation residual. Signals stopped, blocker retained.');
943
+ return decision;
944
+ }
945
+ // Only a boot-id-changed proof reaches here (either handle kind). No cgroup
946
+ // directory needs reclaiming: the reboot that changed the boot id also reset
947
+ // cgroupfs, so a strong handle's directory is already gone.
948
+ const path = filePath(dataDir);
949
+ mkdirSync(dirname(path), { recursive: true });
950
+ withFileLockSync(path, () => {
951
+ const data = readStrict(dataDir);
952
+ const before = data.sessions[handle.sessionId] ?? [];
953
+ const key = containmentHandleKey(handle);
954
+ const after = before.filter(h => containmentHandleKey(h) !== key);
955
+ if (after.length === before.length)
956
+ return;
957
+ if (after.length === 0)
958
+ delete data.sessions[handle.sessionId];
959
+ else
960
+ data.sessions[handle.sessionId] = after;
961
+ writeStrict(data, dataDir);
962
+ });
963
+ return decision;
964
+ }
965
+ /**
966
+ * OPERATOR OVERRIDE: drop handles without quiescence proof.
967
+ *
968
+ * releaseContainmentHandle is deliberately unreachable without a proven verdict,
969
+ * and on a non-cgroup host a weak handle's scan-clean is never a boundary proof
970
+ * — so after one mojo session runs and closes there, its handle (and with it the
971
+ * whole-machine device-isolation `activation_blocked` 409) persists FOREVER,
972
+ * with no operational path out short of hand-editing the ledger JSON. An
973
+ * `unprovable` handle is like that by design. Both are correct fail-closed
974
+ * defaults and both still need an explicit, auditable exit.
975
+ *
976
+ * This is that exit, and only for a human operator (the `botmux mojo-containment
977
+ * revoke` command): it removes the named handles while logging exactly what was
978
+ * dropped, so the decision that "these trees are acceptable to forget" is a
979
+ * recorded human judgement, never something the runtime can reach on its own.
980
+ * Nothing in daemon/worker code may call this.
981
+ */
982
+ export function revokeContainmentHandles(sessionId, opts = {}) {
983
+ const path = filePath(opts.dataDir);
984
+ mkdirSync(dirname(path), { recursive: true });
985
+ let removed = [];
986
+ let remaining = [];
987
+ withFileLockSync(path, () => {
988
+ const data = readStrict(opts.dataDir);
989
+ const before = data.sessions[sessionId] ?? [];
990
+ removed = opts.handleKey === undefined
991
+ ? before
992
+ : before.filter(h => containmentHandleKey(h) === opts.handleKey);
993
+ remaining = opts.handleKey === undefined
994
+ ? []
995
+ : before.filter(h => containmentHandleKey(h) !== opts.handleKey);
996
+ if (removed.length === 0)
997
+ return;
998
+ if (remaining.length === 0)
999
+ delete data.sessions[sessionId];
1000
+ else
1001
+ data.sessions[sessionId] = remaining;
1002
+ writeStrict(data, opts.dataDir);
1003
+ });
1004
+ for (const handle of removed) {
1005
+ logger.warn(`[mojo] OPERATOR REVOCATION: containment handle ${containmentHandleKey(handle)} for `
1006
+ + `session ${sessionId} was dropped WITHOUT quiescence proof. Any surviving subtree `
1007
+ + 'of that turn is no longer tracked; its device-isolation blocker is gone.'
1008
+ + (opts.auditNote ? ` [${opts.auditNote}]` : ''));
1009
+ }
1010
+ // The caller (the operator CLI) is responsible for cgroup.kill + reclaiming the
1011
+ // directory of any removed cgroup handle, and for LOGGING that kill's result.
1012
+ // A cgroup handle IS released by proof — but only on `boot-id-changed`, i.e. a
1013
+ // reboot, which already wiped cgroupfs, so there is nothing to reclaim on that
1014
+ // path. Revoke is therefore the only place a cgroup handle is dropped while its
1015
+ // directory may still exist (a same-boot operator override), so it is where the
1016
+ // reclaim lives. Doing it here fire-and-forget would drop the blocker before a
1017
+ // possibly-live tree was actually killed, invisibly; the CLI awaits it instead.
1018
+ return { removed, remaining };
1019
+ }
1020
+ /**
1021
+ * Executing (non-zombie) pids anywhere in a cgroup handle's subtree, for the
1022
+ * operator revoke safety gate. `unreadable` is the fail-closed signal — the gate
1023
+ * must surface it rather than read "cannot enumerate" as "nobody there".
1024
+ */
1025
+ export function cgroupHandleLiveMembers(handle, opts = {}) {
1026
+ const subtree = cgroupSubtreePids(handle.cgroupPath);
1027
+ if (!subtree.ok)
1028
+ return { live: [], unreadable: true };
1029
+ const live = subtree.pids.filter(pid => readProcLiveness(pid, opts) !== 'zombie'
1030
+ && readProcLiveness(pid, opts) !== 'gone');
1031
+ return { live, unreadable: false };
1032
+ }
1033
+ /**
1034
+ * Hand every outstanding handle of a session to a new worker generation.
1035
+ *
1036
+ * Inheritance is a UNION and it is unconditional: replacement does not prove
1037
+ * anything about the old tree, so the new generation becomes responsible for
1038
+ * proving it later. The generation stamp is refreshed for logging while the
1039
+ * IDENTITY fields (cgroup path, pid/boot/starttime, nonce) are preserved
1040
+ * verbatim — rewriting those would invent a handle that proves nothing about the
1041
+ * tree actually left behind.
1042
+ *
1043
+ * Nothing is removed here, so a crash mid-inheritance is safe: the handles are
1044
+ * still recorded under the same session id.
1045
+ */
1046
+ export function inheritContainmentHandles(sessionId, nextGeneration, dataDir) {
1047
+ const path = filePath(dataDir);
1048
+ mkdirSync(dirname(path), { recursive: true });
1049
+ let inherited = [];
1050
+ withFileLockSync(path, () => {
1051
+ const data = readStrict(dataDir);
1052
+ const before = data.sessions[sessionId] ?? [];
1053
+ if (before.length === 0)
1054
+ return;
1055
+ inherited = before.map(h => ({ ...h, generation: nextGeneration }));
1056
+ data.sessions[sessionId] = inherited;
1057
+ writeStrict(data, dataDir);
1058
+ });
1059
+ return inherited;
1060
+ }
1061
+ // ── the ONLY source of boundaryProof: true ───────────────────────────────────
1062
+ /**
1063
+ * Map a containment verdict onto ProcTree's `TurnQuiescence`.
1064
+ *
1065
+ * This function is the single place in the codebase allowed to mint
1066
+ * `{ kind: 'contained-proven', boundaryProof: true }`, and it does so for exactly
1067
+ * ONE evidence — `boot-id-changed`. That is the whole point of the contracts
1068
+ * meeting here:
1069
+ *
1070
+ * - `quiescenceFromScan()` can never produce `boundaryProof: true`; a clean
1071
+ * /proc scan is a diagnostic signal, because a descendant that both setsids
1072
+ * AND scrubs its own environ evades enumeration entirely.
1073
+ * - An empty (or zombie-only) `cgroup.procs` is ALSO only a diagnostic signal,
1074
+ * NOT a boundary proof: mojo runs at the daemon's UID and cgroup v2 lets a
1075
+ * same-UID process migrate itself out of the leaf, invisible to the leaf-down
1076
+ * read. cgroup emptiness is good for stopping signals and for killing
1077
+ * (cgroup.kill), never for dropping device isolation.
1078
+ * - `boot-id-changed` is the only unforgeable release: a reboot kills the whole
1079
+ * tree, migrant included. It applies to a weak handle and to a cgroup handle
1080
+ * once its bootId is stamped.
1081
+ *
1082
+ * So a STRONG (cgroup) proven-empty verdict maps to `diagnostic-clean`, exactly
1083
+ * like a weak `scan-clean`, and `containmentReleaseDecision` refuses to authorise
1084
+ * removal for either — the handle stays in the durable store and the blocker
1085
+ * survives the close. The gate lives in `containmentReleaseDecision`, a real
1086
+ * production consumer.
1087
+ */
1088
+ /**
1089
+ * The ONE place a `boundaryProof: true` TurnQuiescence is constructed.
1090
+ *
1091
+ * Round 4 claimed this property in a comment while three separate sites minted
1092
+ * the value, which is why the claim was rejected as unverifiable. It is now
1093
+ * structural: `boundaryProof: true` is minted here and in the release-decision
1094
+ * gate (`containmentReleaseDecision`), and nowhere else — `git grep -n
1095
+ * "boundaryProof: true" -- src/` returns those two sites plus type definitions.
1096
+ * It is exported for the backend, which decides WHETHER a proven boundary applies
1097
+ * but must not decide what one looks like.
1098
+ */
1099
+ export function containedProvenQuiescence() {
1100
+ return { kind: 'contained-proven', boundaryProof: true };
1101
+ }
1102
+ export function containmentQuiescence(verdict) {
1103
+ if (!verdict.proven) {
1104
+ if (verdict.handle.kind === 'unprovable') {
1105
+ return { kind: 'unsupported-platform', boundaryProof: false, platform: verdict.handle.platform };
1106
+ }
1107
+ return verdict.residualPids && verdict.residualPids.length > 0
1108
+ ? { kind: 'alive', boundaryProof: false, pids: [...verdict.residualPids] }
1109
+ : { kind: 'unscannable', boundaryProof: false, reason: verdict.reason };
1110
+ }
1111
+ if (containmentReleaseDecision(verdict).boundaryProof) {
1112
+ return containedProvenQuiescence();
1113
+ }
1114
+ // No `unprovable` case here on purpose. proveContainmentQuiescent NEVER returns
1115
+ // proven:true for that kind, so a branch for it would be unreachable — an
1116
+ // unfalsifiable claim that no mutation can kill. A hand-constructed impossible
1117
+ // verdict falls through to the line below, which is still boundaryProof:false,
1118
+ // so deleting the branch costs no safety.
1119
+ //
1120
+ // Proven as far as a weak handle can prove anything — diagnostic only.
1121
+ return { kind: 'diagnostic-clean', boundaryProof: false };
1122
+ }
1123
+ /**
1124
+ * Strongest quiescence statement available for a session, across ALL of its
1125
+ * outstanding handles.
1126
+ *
1127
+ * Semantics are intentionally pessimistic, because a session is only as contained
1128
+ * as its WEAKEST outstanding tree:
1129
+ * - any handle that is not proven → that handle's non-proof is the answer
1130
+ * - no handles at all → `diagnostic-clean`; nothing is recorded,
1131
+ * but "nothing recorded" is not a kernel-level boundary proof either
1132
+ * - every handle proven, at least one only diagnostically → `diagnostic-clean`
1133
+ * - every handle proven WITH a boundary proof → `contained-proven`
1134
+ *
1135
+ * A store that cannot be read THROWS (via `containmentHandles`), so an
1136
+ * unreadable store can never present itself as a clean session.
1137
+ */
1138
+ export function sessionContainmentQuiescence(sessionId, prove, dataDir) {
1139
+ const handles = containmentHandles(sessionId, dataDir);
1140
+ if (handles.length === 0)
1141
+ return { kind: 'diagnostic-clean', boundaryProof: false };
1142
+ // Same authority as the single-handle path on purpose: a session is only as
1143
+ // contained as its weakest tree, and "weakest" is decided by boundary proof,
1144
+ // not by handle kind, so a weak handle aged out by a reboot counts while a
1145
+ // weak handle that merely scanned clean does not.
1146
+ let allProvenBoundary = true;
1147
+ for (const handle of handles) {
1148
+ const verdict = prove(handle);
1149
+ if (!verdict.proven)
1150
+ return containmentQuiescence(verdict);
1151
+ if (!containmentReleaseDecision(verdict).boundaryProof)
1152
+ allProvenBoundary = false;
1153
+ }
1154
+ return allProvenBoundary
1155
+ ? containedProvenQuiescence()
1156
+ : { kind: 'diagnostic-clean', boundaryProof: false };
1157
+ }
1158
+ //# sourceMappingURL=mojo-containment.js.map