botmux 3.14.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 (812) 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/tmux-pipe-backend.d.ts +30 -0
  45. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  46. package/dist/adapters/backend/tmux-pipe-backend.js +60 -0
  47. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  48. package/dist/adapters/backend/types.d.ts +108 -6
  49. package/dist/adapters/backend/types.d.ts.map +1 -1
  50. package/dist/adapters/backend/types.js.map +1 -1
  51. package/dist/adapters/backend/zmx-backend.d.ts +11 -0
  52. package/dist/adapters/backend/zmx-backend.d.ts.map +1 -1
  53. package/dist/adapters/backend/zmx-backend.js +16 -1
  54. package/dist/adapters/backend/zmx-backend.js.map +1 -1
  55. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  56. package/dist/adapters/cli/codex-app.js +2 -2
  57. package/dist/adapters/cli/codex-app.js.map +1 -1
  58. package/dist/adapters/cli/codex.d.ts.map +1 -1
  59. package/dist/adapters/cli/codex.js +7 -0
  60. package/dist/adapters/cli/codex.js.map +1 -1
  61. package/dist/adapters/cli/copilot.d.ts +3 -1
  62. package/dist/adapters/cli/copilot.d.ts.map +1 -1
  63. package/dist/adapters/cli/copilot.js +23 -5
  64. package/dist/adapters/cli/copilot.js.map +1 -1
  65. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  66. package/dist/adapters/cli/cursor.js +26 -4
  67. package/dist/adapters/cli/cursor.js.map +1 -1
  68. package/dist/adapters/cli/dsh.d.ts.map +1 -1
  69. package/dist/adapters/cli/dsh.js +5 -1
  70. package/dist/adapters/cli/dsh.js.map +1 -1
  71. package/dist/adapters/cli/grok.d.ts.map +1 -1
  72. package/dist/adapters/cli/grok.js +24 -5
  73. package/dist/adapters/cli/grok.js.map +1 -1
  74. package/dist/adapters/cli/kimi.d.ts.map +1 -1
  75. package/dist/adapters/cli/kimi.js +19 -1
  76. package/dist/adapters/cli/kimi.js.map +1 -1
  77. package/dist/adapters/cli/mojo.d.ts +28 -0
  78. package/dist/adapters/cli/mojo.d.ts.map +1 -0
  79. package/dist/adapters/cli/mojo.js +58 -0
  80. package/dist/adapters/cli/mojo.js.map +1 -0
  81. package/dist/adapters/cli/pi.d.ts +20 -5
  82. package/dist/adapters/cli/pi.d.ts.map +1 -1
  83. package/dist/adapters/cli/pi.js +27 -5
  84. package/dist/adapters/cli/pi.js.map +1 -1
  85. package/dist/adapters/cli/read-isolation.d.ts +251 -1
  86. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  87. package/dist/adapters/cli/read-isolation.js +254 -1
  88. package/dist/adapters/cli/read-isolation.js.map +1 -1
  89. package/dist/adapters/cli/registry.d.ts +13 -1
  90. package/dist/adapters/cli/registry.d.ts.map +1 -1
  91. package/dist/adapters/cli/registry.js +19 -1
  92. package/dist/adapters/cli/registry.js.map +1 -1
  93. package/dist/adapters/cli/runner-input.d.ts +3 -3
  94. package/dist/adapters/cli/runner-input.d.ts.map +1 -1
  95. package/dist/adapters/cli/runner-input.js +4 -3
  96. package/dist/adapters/cli/runner-input.js.map +1 -1
  97. package/dist/adapters/cli/shared-hints.d.ts +3 -1
  98. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  99. package/dist/adapters/cli/shared-hints.js +51 -30
  100. package/dist/adapters/cli/shared-hints.js.map +1 -1
  101. package/dist/adapters/cli/types.d.ts +33 -4
  102. package/dist/adapters/cli/types.d.ts.map +1 -1
  103. package/dist/bot-registry.d.ts +42 -0
  104. package/dist/bot-registry.d.ts.map +1 -1
  105. package/dist/bot-registry.js +54 -4
  106. package/dist/bot-registry.js.map +1 -1
  107. package/dist/cli/arg-utils.d.ts +5 -0
  108. package/dist/cli/arg-utils.d.ts.map +1 -1
  109. package/dist/cli/arg-utils.js +7 -0
  110. package/dist/cli/arg-utils.js.map +1 -1
  111. package/dist/cli/bots-list-output.d.ts +130 -2
  112. package/dist/cli/bots-list-output.d.ts.map +1 -1
  113. package/dist/cli/bots-list-output.js +173 -27
  114. package/dist/cli/bots-list-output.js.map +1 -1
  115. package/dist/cli/daemon-lifecycle-env.d.ts +10 -3
  116. package/dist/cli/daemon-lifecycle-env.d.ts.map +1 -1
  117. package/dist/cli/daemon-lifecycle-env.js +38 -11
  118. package/dist/cli/daemon-lifecycle-env.js.map +1 -1
  119. package/dist/cli/dashboard-command.d.ts +15 -0
  120. package/dist/cli/dashboard-command.d.ts.map +1 -1
  121. package/dist/cli/dashboard-command.js +22 -0
  122. package/dist/cli/dashboard-command.js.map +1 -1
  123. package/dist/cli/log-tail.d.ts +54 -0
  124. package/dist/cli/log-tail.d.ts.map +1 -0
  125. package/dist/cli/log-tail.js +167 -0
  126. package/dist/cli/log-tail.js.map +1 -0
  127. package/dist/cli/pm2-env.d.ts +16 -0
  128. package/dist/cli/pm2-env.d.ts.map +1 -0
  129. package/dist/cli/pm2-env.js +63 -0
  130. package/dist/cli/pm2-env.js.map +1 -0
  131. package/dist/cli/pm2-fleet-lock.d.ts +26 -0
  132. package/dist/cli/pm2-fleet-lock.d.ts.map +1 -0
  133. package/dist/cli/pm2-fleet-lock.js +50 -0
  134. package/dist/cli/pm2-fleet-lock.js.map +1 -0
  135. package/dist/cli/pm2-god-retirement.d.ts +59 -0
  136. package/dist/cli/pm2-god-retirement.d.ts.map +1 -0
  137. package/dist/cli/pm2-god-retirement.js +95 -0
  138. package/dist/cli/pm2-god-retirement.js.map +1 -0
  139. package/dist/cli/session-list-wake.d.ts +41 -0
  140. package/dist/cli/session-list-wake.d.ts.map +1 -0
  141. package/dist/cli/session-list-wake.js +102 -0
  142. package/dist/cli/session-list-wake.js.map +1 -0
  143. package/dist/cli/workflow.d.ts.map +1 -1
  144. package/dist/cli/workflow.js +26 -0
  145. package/dist/cli/workflow.js.map +1 -1
  146. package/dist/cli.d.ts.map +1 -1
  147. package/dist/cli.js +941 -118
  148. package/dist/cli.js.map +1 -1
  149. package/dist/codex-app-runner.js +261 -49
  150. package/dist/codex-app-runner.js.map +1 -1
  151. package/dist/core/cli-runtime-update.d.ts +8 -0
  152. package/dist/core/cli-runtime-update.d.ts.map +1 -1
  153. package/dist/core/cli-runtime-update.js +127 -8
  154. package/dist/core/cli-runtime-update.js.map +1 -1
  155. package/dist/core/close-residual.d.ts +64 -0
  156. package/dist/core/close-residual.d.ts.map +1 -0
  157. package/dist/core/close-residual.js +93 -0
  158. package/dist/core/close-residual.js.map +1 -0
  159. package/dist/core/closed-session-card.d.ts.map +1 -1
  160. package/dist/core/closed-session-card.js +11 -3
  161. package/dist/core/closed-session-card.js.map +1 -1
  162. package/dist/core/command-discovery.d.ts.map +1 -1
  163. package/dist/core/command-discovery.js +4 -1
  164. package/dist/core/command-discovery.js.map +1 -1
  165. package/dist/core/command-handler.d.ts.map +1 -1
  166. package/dist/core/command-handler.js +129 -27
  167. package/dist/core/command-handler.js.map +1 -1
  168. package/dist/core/cost-calculator.d.ts +3 -1
  169. package/dist/core/cost-calculator.d.ts.map +1 -1
  170. package/dist/core/cost-calculator.js +230 -1
  171. package/dist/core/cost-calculator.js.map +1 -1
  172. package/dist/core/daemon-background-close.d.ts +19 -0
  173. package/dist/core/daemon-background-close.d.ts.map +1 -0
  174. package/dist/core/daemon-background-close.js +39 -0
  175. package/dist/core/daemon-background-close.js.map +1 -0
  176. package/dist/core/dashboard-command/overview.d.ts +4 -0
  177. package/dist/core/dashboard-command/overview.d.ts.map +1 -1
  178. package/dist/core/dashboard-command/overview.js +8 -1
  179. package/dist/core/dashboard-command/overview.js.map +1 -1
  180. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  181. package/dist/core/dashboard-ipc-server.js +415 -42
  182. package/dist/core/dashboard-ipc-server.js.map +1 -1
  183. package/dist/core/dashboard-rows.d.ts +4 -0
  184. package/dist/core/dashboard-rows.d.ts.map +1 -1
  185. package/dist/core/dashboard-rows.js +3 -0
  186. package/dist/core/dashboard-rows.js.map +1 -1
  187. package/dist/core/dashboard-url.d.ts +37 -0
  188. package/dist/core/dashboard-url.d.ts.map +1 -1
  189. package/dist/core/dashboard-url.js +71 -0
  190. package/dist/core/dashboard-url.js.map +1 -1
  191. package/dist/core/deferred-schedule-settlement.d.ts +15 -1
  192. package/dist/core/deferred-schedule-settlement.d.ts.map +1 -1
  193. package/dist/core/deferred-schedule-settlement.js +4 -1
  194. package/dist/core/deferred-schedule-settlement.js.map +1 -1
  195. package/dist/core/device-isolation-daemon.d.ts +91 -1
  196. package/dist/core/device-isolation-daemon.d.ts.map +1 -1
  197. package/dist/core/device-isolation-daemon.js +346 -2
  198. package/dist/core/device-isolation-daemon.js.map +1 -1
  199. package/dist/core/local-terminal-opener.d.ts.map +1 -1
  200. package/dist/core/local-terminal-opener.js +5 -1
  201. package/dist/core/local-terminal-opener.js.map +1 -1
  202. package/dist/core/loopback-target.d.ts +14 -0
  203. package/dist/core/loopback-target.d.ts.map +1 -0
  204. package/dist/core/loopback-target.js +20 -0
  205. package/dist/core/loopback-target.js.map +1 -0
  206. package/dist/core/maintenance.d.ts.map +1 -1
  207. package/dist/core/maintenance.js +16 -2
  208. package/dist/core/maintenance.js.map +1 -1
  209. package/dist/core/mojo-containment-command.d.ts +20 -0
  210. package/dist/core/mojo-containment-command.d.ts.map +1 -0
  211. package/dist/core/mojo-containment-command.js +266 -0
  212. package/dist/core/mojo-containment-command.js.map +1 -0
  213. package/dist/core/mojo-containment.d.ts +575 -0
  214. package/dist/core/mojo-containment.d.ts.map +1 -0
  215. package/dist/core/mojo-containment.js +1158 -0
  216. package/dist/core/mojo-containment.js.map +1 -0
  217. package/dist/core/mojo-launcher-env-quarantine.d.ts +30 -0
  218. package/dist/core/mojo-launcher-env-quarantine.d.ts.map +1 -0
  219. package/dist/core/mojo-launcher-env-quarantine.js +175 -0
  220. package/dist/core/mojo-launcher-env-quarantine.js.map +1 -0
  221. package/dist/core/mojo-session-identity.d.ts +3 -0
  222. package/dist/core/mojo-session-identity.d.ts.map +1 -0
  223. package/dist/core/mojo-session-identity.js +126 -0
  224. package/dist/core/mojo-session-identity.js.map +1 -0
  225. package/dist/core/persistent-backend.d.ts +32 -8
  226. package/dist/core/persistent-backend.d.ts.map +1 -1
  227. package/dist/core/persistent-backend.js +55 -17
  228. package/dist/core/persistent-backend.js.map +1 -1
  229. package/dist/core/plugins/mcp/gateway.d.ts +14 -1
  230. package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
  231. package/dist/core/plugins/mcp/gateway.js +70 -3
  232. package/dist/core/plugins/mcp/gateway.js.map +1 -1
  233. package/dist/core/plugins/mcp/host.d.ts +2 -0
  234. package/dist/core/plugins/mcp/host.d.ts.map +1 -1
  235. package/dist/core/plugins/mcp/host.js +2 -0
  236. package/dist/core/plugins/mcp/host.js.map +1 -1
  237. package/dist/core/plugins/pm2.d.ts.map +1 -1
  238. package/dist/core/plugins/pm2.js +16 -3
  239. package/dist/core/plugins/pm2.js.map +1 -1
  240. package/dist/core/plugins/service-manager.d.ts +9 -0
  241. package/dist/core/plugins/service-manager.d.ts.map +1 -1
  242. package/dist/core/plugins/service-manager.js +33 -16
  243. package/dist/core/plugins/service-manager.js.map +1 -1
  244. package/dist/core/preview-port-owner.d.ts +109 -0
  245. package/dist/core/preview-port-owner.d.ts.map +1 -0
  246. package/dist/core/preview-port-owner.js +320 -0
  247. package/dist/core/preview-port-owner.js.map +1 -0
  248. package/dist/core/remote-cli-ids.d.ts +5 -0
  249. package/dist/core/remote-cli-ids.d.ts.map +1 -0
  250. package/dist/core/remote-cli-ids.js +24 -0
  251. package/dist/core/remote-cli-ids.js.map +1 -0
  252. package/dist/core/{riff-shutdown-detach.d.ts → remote-shutdown-detach.d.ts} +40 -39
  253. package/dist/core/remote-shutdown-detach.d.ts.map +1 -0
  254. package/dist/core/{riff-shutdown-detach.js → remote-shutdown-detach.js} +85 -83
  255. package/dist/core/remote-shutdown-detach.js.map +1 -0
  256. package/dist/core/{riff-worker-shutdown-readiness.d.ts → remote-worker-shutdown-readiness.d.ts} +5 -4
  257. package/dist/core/remote-worker-shutdown-readiness.d.ts.map +1 -0
  258. package/dist/core/{riff-worker-shutdown-readiness.js → remote-worker-shutdown-readiness.js} +4 -3
  259. package/dist/core/remote-worker-shutdown-readiness.js.map +1 -0
  260. package/dist/core/reply-target.d.ts +16 -1
  261. package/dist/core/reply-target.d.ts.map +1 -1
  262. package/dist/core/reply-target.js +61 -0
  263. package/dist/core/reply-target.js.map +1 -1
  264. package/dist/core/session-group-birth.d.ts +26 -0
  265. package/dist/core/session-group-birth.d.ts.map +1 -1
  266. package/dist/core/session-group-birth.js +81 -4
  267. package/dist/core/session-group-birth.js.map +1 -1
  268. package/dist/core/session-manager.d.ts +15 -2
  269. package/dist/core/session-manager.d.ts.map +1 -1
  270. package/dist/core/session-manager.js +259 -5
  271. package/dist/core/session-manager.js.map +1 -1
  272. package/dist/core/session-model.d.ts +46 -0
  273. package/dist/core/session-model.d.ts.map +1 -0
  274. package/dist/core/session-model.js +42 -0
  275. package/dist/core/session-model.js.map +1 -0
  276. package/dist/core/session-owner-reminder.d.ts +63 -0
  277. package/dist/core/session-owner-reminder.d.ts.map +1 -0
  278. package/dist/core/session-owner-reminder.js +196 -0
  279. package/dist/core/session-owner-reminder.js.map +1 -0
  280. package/dist/core/session-preview-registry.d.ts +53 -0
  281. package/dist/core/session-preview-registry.d.ts.map +1 -0
  282. package/dist/core/session-preview-registry.js +82 -0
  283. package/dist/core/session-preview-registry.js.map +1 -0
  284. package/dist/core/session-preview.d.ts +114 -0
  285. package/dist/core/session-preview.d.ts.map +1 -0
  286. package/dist/core/session-preview.js +184 -0
  287. package/dist/core/session-preview.js.map +1 -0
  288. package/dist/core/session-rename.d.ts.map +1 -1
  289. package/dist/core/session-rename.js +5 -3
  290. package/dist/core/session-rename.js.map +1 -1
  291. package/dist/core/session-wake-deadline.d.ts +5 -0
  292. package/dist/core/session-wake-deadline.d.ts.map +1 -0
  293. package/dist/core/session-wake-deadline.js +13 -0
  294. package/dist/core/session-wake-deadline.js.map +1 -0
  295. package/dist/core/shutdown-budgets.d.ts +7 -6
  296. package/dist/core/shutdown-budgets.d.ts.map +1 -1
  297. package/dist/core/shutdown-budgets.js +11 -10
  298. package/dist/core/shutdown-budgets.js.map +1 -1
  299. package/dist/core/skills/frontmatter.d.ts.map +1 -1
  300. package/dist/core/skills/frontmatter.js +6 -1
  301. package/dist/core/skills/frontmatter.js.map +1 -1
  302. package/dist/core/terminal-control-grant.d.ts +74 -0
  303. package/dist/core/terminal-control-grant.d.ts.map +1 -0
  304. package/dist/core/terminal-control-grant.js +183 -0
  305. package/dist/core/terminal-control-grant.js.map +1 -0
  306. package/dist/core/terminal-proxy.d.ts.map +1 -1
  307. package/dist/core/terminal-proxy.js +4 -5
  308. package/dist/core/terminal-proxy.js.map +1 -1
  309. package/dist/core/terminal-write-auth.d.ts +4 -11
  310. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  311. package/dist/core/terminal-write-auth.js +19 -16
  312. package/dist/core/terminal-write-auth.js.map +1 -1
  313. package/dist/core/trigger-session.d.ts.map +1 -1
  314. package/dist/core/trigger-session.js +36 -29
  315. package/dist/core/trigger-session.js.map +1 -1
  316. package/dist/core/types.d.ts +88 -10
  317. package/dist/core/types.d.ts.map +1 -1
  318. package/dist/core/types.js +9 -7
  319. package/dist/core/types.js.map +1 -1
  320. package/dist/core/workbench-link.d.ts +81 -0
  321. package/dist/core/workbench-link.d.ts.map +1 -0
  322. package/dist/core/workbench-link.js +109 -0
  323. package/dist/core/workbench-link.js.map +1 -0
  324. package/dist/core/worker-pool.d.ts +312 -11
  325. package/dist/core/worker-pool.d.ts.map +1 -1
  326. package/dist/core/worker-pool.js +2515 -217
  327. package/dist/core/worker-pool.js.map +1 -1
  328. package/dist/daemon.d.ts +6 -0
  329. package/dist/daemon.d.ts.map +1 -1
  330. package/dist/daemon.js +447 -116
  331. package/dist/daemon.js.map +1 -1
  332. package/dist/dashboard/aggregator.d.ts +33 -3
  333. package/dist/dashboard/aggregator.d.ts.map +1 -1
  334. package/dist/dashboard/aggregator.js +93 -17
  335. package/dist/dashboard/aggregator.js.map +1 -1
  336. package/dist/dashboard/auth-session-connections.d.ts +59 -0
  337. package/dist/dashboard/auth-session-connections.d.ts.map +1 -0
  338. package/dist/dashboard/auth-session-connections.js +109 -0
  339. package/dist/dashboard/auth-session-connections.js.map +1 -0
  340. package/dist/dashboard/auth.d.ts +99 -1
  341. package/dist/dashboard/auth.d.ts.map +1 -1
  342. package/dist/dashboard/auth.js +176 -64
  343. package/dist/dashboard/auth.js.map +1 -1
  344. package/dist/dashboard/bot-payload.d.ts +5 -0
  345. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  346. package/dist/dashboard/bot-payload.js +4 -0
  347. package/dist/dashboard/bot-payload.js.map +1 -1
  348. package/dist/dashboard/control-audit.d.ts +112 -0
  349. package/dist/dashboard/control-audit.d.ts.map +1 -0
  350. package/dist/dashboard/control-audit.js +195 -0
  351. package/dist/dashboard/control-audit.js.map +1 -0
  352. package/dist/dashboard/control-csrf.d.ts +123 -0
  353. package/dist/dashboard/control-csrf.d.ts.map +1 -0
  354. package/dist/dashboard/control-csrf.js +392 -0
  355. package/dist/dashboard/control-csrf.js.map +1 -0
  356. package/dist/dashboard/daemon-reconcile.d.ts +26 -0
  357. package/dist/dashboard/daemon-reconcile.d.ts.map +1 -0
  358. package/dist/dashboard/daemon-reconcile.js +46 -0
  359. package/dist/dashboard/daemon-reconcile.js.map +1 -0
  360. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  361. package/dist/dashboard/debug-terminal.js +18 -4
  362. package/dist/dashboard/debug-terminal.js.map +1 -1
  363. package/dist/dashboard/events-sse.d.ts +60 -0
  364. package/dist/dashboard/events-sse.d.ts.map +1 -0
  365. package/dist/dashboard/events-sse.js +63 -0
  366. package/dist/dashboard/events-sse.js.map +1 -0
  367. package/dist/dashboard/h5-auth.d.ts +256 -0
  368. package/dist/dashboard/h5-auth.d.ts.map +1 -0
  369. package/dist/dashboard/h5-auth.js +825 -0
  370. package/dist/dashboard/h5-auth.js.map +1 -0
  371. package/dist/dashboard/managed-spawn.d.ts +36 -0
  372. package/dist/dashboard/managed-spawn.d.ts.map +1 -0
  373. package/dist/dashboard/managed-spawn.js +169 -0
  374. package/dist/dashboard/managed-spawn.js.map +1 -0
  375. package/dist/dashboard/preview-content-capability.d.ts +41 -0
  376. package/dist/dashboard/preview-content-capability.d.ts.map +1 -0
  377. package/dist/dashboard/preview-content-capability.js +127 -0
  378. package/dist/dashboard/preview-content-capability.js.map +1 -0
  379. package/dist/dashboard/preview-contract.d.ts +85 -0
  380. package/dist/dashboard/preview-contract.d.ts.map +1 -0
  381. package/dist/dashboard/preview-contract.js +198 -0
  382. package/dist/dashboard/preview-contract.js.map +1 -0
  383. package/dist/dashboard/preview-guard-page.d.ts +52 -0
  384. package/dist/dashboard/preview-guard-page.d.ts.map +1 -0
  385. package/dist/dashboard/preview-guard-page.js +162 -0
  386. package/dist/dashboard/preview-guard-page.js.map +1 -0
  387. package/dist/dashboard/preview-interaction.d.ts +57 -0
  388. package/dist/dashboard/preview-interaction.d.ts.map +1 -0
  389. package/dist/dashboard/preview-interaction.js +198 -0
  390. package/dist/dashboard/preview-interaction.js.map +1 -0
  391. package/dist/dashboard/preview-proxy.d.ts +113 -0
  392. package/dist/dashboard/preview-proxy.d.ts.map +1 -0
  393. package/dist/dashboard/preview-proxy.js +543 -0
  394. package/dist/dashboard/preview-proxy.js.map +1 -0
  395. package/dist/dashboard/public-redact.d.ts +23 -0
  396. package/dist/dashboard/public-redact.d.ts.map +1 -1
  397. package/dist/dashboard/public-redact.js +90 -21
  398. package/dist/dashboard/public-redact.js.map +1 -1
  399. package/dist/dashboard/request-identity.d.ts +77 -0
  400. package/dist/dashboard/request-identity.d.ts.map +1 -0
  401. package/dist/dashboard/request-identity.js +72 -0
  402. package/dist/dashboard/request-identity.js.map +1 -0
  403. package/dist/dashboard/session-cleanup.d.ts +5 -0
  404. package/dist/dashboard/session-cleanup.d.ts.map +1 -1
  405. package/dist/dashboard/session-cleanup.js +4 -0
  406. package/dist/dashboard/session-cleanup.js.map +1 -1
  407. package/dist/dashboard/settings-write-applier.d.ts +5 -1
  408. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  409. package/dist/dashboard/settings-write-applier.js +14 -0
  410. package/dist/dashboard/settings-write-applier.js.map +1 -1
  411. package/dist/dashboard/standing-link.d.ts +76 -0
  412. package/dist/dashboard/standing-link.d.ts.map +1 -0
  413. package/dist/dashboard/standing-link.js +111 -0
  414. package/dist/dashboard/standing-link.js.map +1 -0
  415. package/dist/dashboard/terminal-control.d.ts +105 -0
  416. package/dist/dashboard/terminal-control.d.ts.map +1 -0
  417. package/dist/dashboard/terminal-control.js +296 -0
  418. package/dist/dashboard/terminal-control.js.map +1 -0
  419. package/dist/dashboard/terminal-front-proxy.d.ts +55 -0
  420. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -0
  421. package/dist/dashboard/terminal-front-proxy.js +397 -0
  422. package/dist/dashboard/terminal-front-proxy.js.map +1 -0
  423. package/dist/dashboard/terminal-view-capability.d.ts +97 -0
  424. package/dist/dashboard/terminal-view-capability.d.ts.map +1 -0
  425. package/dist/dashboard/terminal-view-capability.js +145 -0
  426. package/dist/dashboard/terminal-view-capability.js.map +1 -0
  427. package/dist/dashboard/web/agent-workbench-api.d.ts +59 -0
  428. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -0
  429. package/dist/dashboard/web/agent-workbench-api.js +227 -0
  430. package/dist/dashboard/web/agent-workbench-api.js.map +1 -0
  431. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts +59 -0
  432. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts.map +1 -0
  433. package/dist/dashboard/web/agent-workbench-appearance-menu.js +195 -0
  434. package/dist/dashboard/web/agent-workbench-appearance-menu.js.map +1 -0
  435. package/dist/dashboard/web/agent-workbench-appearance.d.ts +222 -0
  436. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -0
  437. package/dist/dashboard/web/agent-workbench-appearance.js +499 -0
  438. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -0
  439. package/dist/dashboard/web/agent-workbench-capabilities.d.ts +13 -0
  440. package/dist/dashboard/web/agent-workbench-capabilities.d.ts.map +1 -0
  441. package/dist/dashboard/web/agent-workbench-capabilities.js +24 -0
  442. package/dist/dashboard/web/agent-workbench-capabilities.js.map +1 -0
  443. package/dist/dashboard/web/agent-workbench-chat.d.ts +90 -0
  444. package/dist/dashboard/web/agent-workbench-chat.d.ts.map +1 -0
  445. package/dist/dashboard/web/agent-workbench-chat.js +168 -0
  446. package/dist/dashboard/web/agent-workbench-chat.js.map +1 -0
  447. package/dist/dashboard/web/agent-workbench-dock-page.d.ts +3 -0
  448. package/dist/dashboard/web/agent-workbench-dock-page.d.ts.map +1 -0
  449. package/dist/dashboard/web/agent-workbench-dock-page.js +23 -0
  450. package/dist/dashboard/web/agent-workbench-dock-page.js.map +1 -0
  451. package/dist/dashboard/web/agent-workbench-dock-view.d.ts +22 -0
  452. package/dist/dashboard/web/agent-workbench-dock-view.d.ts.map +1 -0
  453. package/dist/dashboard/web/agent-workbench-dock-view.js +76 -0
  454. package/dist/dashboard/web/agent-workbench-dock-view.js.map +1 -0
  455. package/dist/dashboard/web/agent-workbench-model.d.ts +196 -0
  456. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -0
  457. package/dist/dashboard/web/agent-workbench-model.js +529 -0
  458. package/dist/dashboard/web/agent-workbench-model.js.map +1 -0
  459. package/dist/dashboard/web/agent-workbench-page.d.ts +3 -0
  460. package/dist/dashboard/web/agent-workbench-page.d.ts.map +1 -0
  461. package/dist/dashboard/web/agent-workbench-page.js +27 -0
  462. package/dist/dashboard/web/agent-workbench-page.js.map +1 -0
  463. package/dist/dashboard/web/agent-workbench-panes.d.ts +49 -0
  464. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -0
  465. package/dist/dashboard/web/agent-workbench-panes.js +468 -0
  466. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -0
  467. package/dist/dashboard/web/agent-workbench-session-list.d.ts +42 -0
  468. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -0
  469. package/dist/dashboard/web/agent-workbench-session-list.js +353 -0
  470. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -0
  471. package/dist/dashboard/web/agent-workbench-storage.d.ts +49 -0
  472. package/dist/dashboard/web/agent-workbench-storage.d.ts.map +1 -0
  473. package/dist/dashboard/web/agent-workbench-storage.js +197 -0
  474. package/dist/dashboard/web/agent-workbench-storage.js.map +1 -0
  475. package/dist/dashboard/web/agent-workbench-touch.d.ts +46 -0
  476. package/dist/dashboard/web/agent-workbench-touch.d.ts.map +1 -0
  477. package/dist/dashboard/web/agent-workbench-touch.js +96 -0
  478. package/dist/dashboard/web/agent-workbench-touch.js.map +1 -0
  479. package/dist/dashboard/web/agent-workbench-view.d.ts +34 -0
  480. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -0
  481. package/dist/dashboard/web/agent-workbench-view.js +439 -0
  482. package/dist/dashboard/web/agent-workbench-view.js.map +1 -0
  483. package/dist/dashboard/web/app.d.ts.map +1 -1
  484. package/dist/dashboard/web/app.js +63 -2
  485. package/dist/dashboard/web/app.js.map +1 -1
  486. package/dist/dashboard/web/bot-defaults-page.d.ts +18 -0
  487. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  488. package/dist/dashboard/web/bot-defaults-page.js +362 -36
  489. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  490. package/dist/dashboard/web/bot-defaults.d.ts +8 -0
  491. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  492. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  493. package/dist/dashboard/web/control-csrf.d.ts +4 -0
  494. package/dist/dashboard/web/control-csrf.d.ts.map +1 -0
  495. package/dist/dashboard/web/control-csrf.js +26 -0
  496. package/dist/dashboard/web/control-csrf.js.map +1 -0
  497. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  498. package/dist/dashboard/web/dashboard-routes.js +2 -0
  499. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  500. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  501. package/dist/dashboard/web/groups-page.js +11 -2
  502. package/dist/dashboard/web/groups-page.js.map +1 -1
  503. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  504. package/dist/dashboard/web/i18n.js +140 -6
  505. package/dist/dashboard/web/i18n.js.map +1 -1
  506. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  507. package/dist/dashboard/web/overview-page.js +3 -2
  508. package/dist/dashboard/web/overview-page.js.map +1 -1
  509. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  510. package/dist/dashboard/web/sessions-page.js +41 -1
  511. package/dist/dashboard/web/sessions-page.js.map +1 -1
  512. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  513. package/dist/dashboard/web/sessions.js +5 -1
  514. package/dist/dashboard/web/sessions.js.map +1 -1
  515. package/dist/dashboard/web/settings-page.d.ts +3 -0
  516. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  517. package/dist/dashboard/web/settings-page.js +56 -10
  518. package/dist/dashboard/web/settings-page.js.map +1 -1
  519. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  520. package/dist/dashboard/web/skills-page.js +4 -2
  521. package/dist/dashboard/web/skills-page.js.map +1 -1
  522. package/dist/dashboard/web/store.d.ts +27 -2
  523. package/dist/dashboard/web/store.d.ts.map +1 -1
  524. package/dist/dashboard/web/store.js +43 -4
  525. package/dist/dashboard/web/store.js.map +1 -1
  526. package/dist/dashboard/web/ui.d.ts +3 -0
  527. package/dist/dashboard/web/ui.d.ts.map +1 -1
  528. package/dist/dashboard/web/ui.js +11 -0
  529. package/dist/dashboard/web/ui.js.map +1 -1
  530. package/dist/dashboard/web/update-action.d.ts +12 -0
  531. package/dist/dashboard/web/update-action.d.ts.map +1 -1
  532. package/dist/dashboard/web/update-action.js +11 -0
  533. package/dist/dashboard/web/update-action.js.map +1 -1
  534. package/dist/dashboard/workbench-doctor.d.ts +114 -0
  535. package/dist/dashboard/workbench-doctor.d.ts.map +1 -0
  536. package/dist/dashboard/workbench-doctor.js +620 -0
  537. package/dist/dashboard/workbench-doctor.js.map +1 -0
  538. package/dist/dashboard/workbench-ticket.d.ts +165 -0
  539. package/dist/dashboard/workbench-ticket.d.ts.map +1 -0
  540. package/dist/dashboard/workbench-ticket.js +495 -0
  541. package/dist/dashboard/workbench-ticket.js.map +1 -0
  542. package/dist/dashboard-web/app.js +1 -1
  543. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +1 -0
  544. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +1 -0
  545. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +10 -0
  546. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +2 -0
  547. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +1 -0
  548. package/dist/dashboard-web/chunks/{chunk-6O7KXFD2.js → chunk-2W6SCN3M.js} +1 -1
  549. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +1 -0
  550. package/dist/dashboard-web/chunks/{chunk-Q2MT4VWH.js → chunk-F6KNPF4R.js} +11 -9
  551. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +1 -0
  552. package/dist/dashboard-web/chunks/chunk-G5ZS2YUS.js +1 -0
  553. package/dist/dashboard-web/chunks/chunk-KTQ7B6ZR.js +1 -0
  554. package/dist/dashboard-web/chunks/{chunk-U3ZFSO7E.js → chunk-NACP7BMR.js} +1 -1
  555. package/dist/dashboard-web/chunks/{connectors-page-2KZO5KSJ.js → connectors-page-VOISXXII.js} +1 -1
  556. package/dist/dashboard-web/chunks/{feedback-page-CLVTOLLO.js → feedback-page-RN6HRZMS.js} +1 -1
  557. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +8 -0
  558. package/dist/dashboard-web/chunks/{insights-page-4RPU2CRU.js → insights-page-HJFDYR5N.js} +3 -3
  559. package/dist/dashboard-web/chunks/{monitor-room-OTKQ2CXK.js → monitor-room-V7PCP47V.js} +1 -1
  560. package/dist/dashboard-web/chunks/{monitoring-page-IJPUB6JT.js → monitoring-page-NKEKVQAP.js} +1 -1
  561. package/dist/dashboard-web/chunks/{office-page-QSR3J44Y.js → office-page-GVJF73XW.js} +1 -1
  562. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +1 -0
  563. package/dist/dashboard-web/chunks/{plugin-page-VEGTCEAS.js → plugin-page-J3OF5IY4.js} +1 -1
  564. package/dist/dashboard-web/chunks/{roles-page-6NWJMOB5.js → roles-page-WQIRSOLZ.js} +1 -1
  565. package/dist/dashboard-web/chunks/{schedules-page-6I3LNXD4.js → schedules-page-J2DEY2X6.js} +1 -1
  566. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +8 -0
  567. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +2 -0
  568. package/dist/dashboard-web/chunks/{skills-page-M2Q6L5CB.js → skills-page-HGIFR3IU.js} +2 -2
  569. package/dist/dashboard-web/chunks/{team-federation-page-7I7FDE3B.js → team-federation-page-MR5ZPL4B.js} +1 -1
  570. package/dist/dashboard-web/chunks/{v3-page-HQXRL65L.js → v3-page-GBR5JWPE.js} +2 -2
  571. package/dist/dashboard-web/chunks/{whiteboards-page-LQKC4TAZ.js → whiteboards-page-QAZM4BN3.js} +1 -1
  572. package/dist/dashboard-web/index.html +8 -1
  573. package/dist/dashboard-web/style.css +2006 -10
  574. package/dist/dashboard.js +1017 -264
  575. package/dist/dashboard.js.map +1 -1
  576. package/dist/dsh-runner.js +8 -1
  577. package/dist/dsh-runner.js.map +1 -1
  578. package/dist/global-config.d.ts +30 -0
  579. package/dist/global-config.d.ts.map +1 -1
  580. package/dist/global-config.js +33 -0
  581. package/dist/global-config.js.map +1 -1
  582. package/dist/i18n/en.d.ts.map +1 -1
  583. package/dist/i18n/en.js +46 -27
  584. package/dist/i18n/en.js.map +1 -1
  585. package/dist/i18n/zh.d.ts.map +1 -1
  586. package/dist/i18n/zh.js +43 -27
  587. package/dist/i18n/zh.js.map +1 -1
  588. package/dist/im/lark/card-builder.d.ts +1 -1
  589. package/dist/im/lark/card-builder.d.ts.map +1 -1
  590. package/dist/im/lark/card-builder.js +22 -6
  591. package/dist/im/lark/card-builder.js.map +1 -1
  592. package/dist/im/lark/card-handler.d.ts.map +1 -1
  593. package/dist/im/lark/card-handler.js +118 -24
  594. package/dist/im/lark/card-handler.js.map +1 -1
  595. package/dist/im/lark/client.d.ts +15 -0
  596. package/dist/im/lark/client.d.ts.map +1 -1
  597. package/dist/im/lark/client.js +30 -0
  598. package/dist/im/lark/client.js.map +1 -1
  599. package/dist/im/lark/event-dispatcher.d.ts +48 -0
  600. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  601. package/dist/im/lark/event-dispatcher.js +138 -41
  602. package/dist/im/lark/event-dispatcher.js.map +1 -1
  603. package/dist/im/lark/identity-cache.d.ts +14 -6
  604. package/dist/im/lark/identity-cache.d.ts.map +1 -1
  605. package/dist/im/lark/identity-cache.js +69 -26
  606. package/dist/im/lark/identity-cache.js.map +1 -1
  607. package/dist/im/lark/lark-hosts.d.ts +13 -0
  608. package/dist/im/lark/lark-hosts.d.ts.map +1 -1
  609. package/dist/im/lark/lark-hosts.js +16 -0
  610. package/dist/im/lark/lark-hosts.js.map +1 -1
  611. package/dist/im/lark/overview-card.d.ts +22 -0
  612. package/dist/im/lark/overview-card.d.ts.map +1 -1
  613. package/dist/im/lark/overview-card.js +53 -3
  614. package/dist/im/lark/overview-card.js.map +1 -1
  615. package/dist/im/lark/sessions-card.d.ts.map +1 -1
  616. package/dist/im/lark/sessions-card.js +26 -3
  617. package/dist/im/lark/sessions-card.js.map +1 -1
  618. package/dist/im/lark/workflow-slash-command.d.ts +6 -0
  619. package/dist/im/lark/workflow-slash-command.d.ts.map +1 -1
  620. package/dist/im/lark/workflow-slash-command.js +7 -0
  621. package/dist/im/lark/workflow-slash-command.js.map +1 -1
  622. package/dist/index-daemon.js +27 -4
  623. package/dist/index-daemon.js.map +1 -1
  624. package/dist/index-dashboard.d.ts +3 -0
  625. package/dist/index-dashboard.d.ts.map +1 -0
  626. package/dist/index-dashboard.js +94 -0
  627. package/dist/index-dashboard.js.map +1 -0
  628. package/dist/platform/team-agents-client.d.ts +199 -0
  629. package/dist/platform/team-agents-client.d.ts.map +1 -0
  630. package/dist/platform/team-agents-client.js +263 -0
  631. package/dist/platform/team-agents-client.js.map +1 -0
  632. package/dist/platform/tunnel-client.d.ts +6 -0
  633. package/dist/platform/tunnel-client.d.ts.map +1 -1
  634. package/dist/platform/tunnel-client.js.map +1 -1
  635. package/dist/services/async-trigger-state.d.ts +7 -3
  636. package/dist/services/async-trigger-state.d.ts.map +1 -1
  637. package/dist/services/async-trigger-state.js +28 -0
  638. package/dist/services/async-trigger-state.js.map +1 -1
  639. package/dist/services/async-trigger-store.d.ts +13 -4
  640. package/dist/services/async-trigger-store.d.ts.map +1 -1
  641. package/dist/services/async-trigger-store.js +55 -8
  642. package/dist/services/async-trigger-store.js.map +1 -1
  643. package/dist/services/bot-config-store.d.ts +1 -11
  644. package/dist/services/bot-config-store.d.ts.map +1 -1
  645. package/dist/services/bot-config-store.js +27 -3
  646. package/dist/services/bot-config-store.js.map +1 -1
  647. package/dist/services/bot-profile-store.d.ts +17 -2
  648. package/dist/services/bot-profile-store.d.ts.map +1 -1
  649. package/dist/services/bot-profile-store.js +93 -12
  650. package/dist/services/bot-profile-store.js.map +1 -1
  651. package/dist/services/bridge-fallback-gate.d.ts +15 -7
  652. package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
  653. package/dist/services/bridge-fallback-gate.js +43 -16
  654. package/dist/services/bridge-fallback-gate.js.map +1 -1
  655. package/dist/services/bridge-turn-queue.d.ts +10 -1
  656. package/dist/services/bridge-turn-queue.d.ts.map +1 -1
  657. package/dist/services/bridge-turn-queue.js +29 -9
  658. package/dist/services/bridge-turn-queue.js.map +1 -1
  659. package/dist/services/claude-transcript.d.ts +19 -0
  660. package/dist/services/claude-transcript.d.ts.map +1 -1
  661. package/dist/services/claude-transcript.js +57 -5
  662. package/dist/services/claude-transcript.js.map +1 -1
  663. package/dist/services/codex-app-threads.d.ts +2 -0
  664. package/dist/services/codex-app-threads.d.ts.map +1 -1
  665. package/dist/services/codex-app-threads.js +1 -1
  666. package/dist/services/codex-app-threads.js.map +1 -1
  667. package/dist/services/codex-reasoning-effort.d.ts +8 -0
  668. package/dist/services/codex-reasoning-effort.d.ts.map +1 -1
  669. package/dist/services/codex-reasoning-effort.js +24 -0
  670. package/dist/services/codex-reasoning-effort.js.map +1 -1
  671. package/dist/services/cursor-resume-policy.d.ts.map +1 -1
  672. package/dist/services/cursor-resume-policy.js +15 -3
  673. package/dist/services/cursor-resume-policy.js.map +1 -1
  674. package/dist/services/open-platform-rename.d.ts.map +1 -1
  675. package/dist/services/open-platform-rename.js +3 -2
  676. package/dist/services/open-platform-rename.js.map +1 -1
  677. package/dist/services/ordinary-turn-recovery.d.ts +76 -0
  678. package/dist/services/ordinary-turn-recovery.d.ts.map +1 -0
  679. package/dist/services/ordinary-turn-recovery.js +349 -0
  680. package/dist/services/ordinary-turn-recovery.js.map +1 -0
  681. package/dist/services/peer-cross-ref-store.d.ts +8 -0
  682. package/dist/services/peer-cross-ref-store.d.ts.map +1 -0
  683. package/dist/services/peer-cross-ref-store.js +95 -0
  684. package/dist/services/peer-cross-ref-store.js.map +1 -0
  685. package/dist/services/pi-transcript.d.ts.map +1 -1
  686. package/dist/services/pi-transcript.js +10 -6
  687. package/dist/services/pi-transcript.js.map +1 -1
  688. package/dist/services/resume-fresh-policy.d.ts +17 -0
  689. package/dist/services/resume-fresh-policy.d.ts.map +1 -0
  690. package/dist/services/resume-fresh-policy.js +24 -0
  691. package/dist/services/resume-fresh-policy.js.map +1 -0
  692. package/dist/services/session-group-title.d.ts +1 -0
  693. package/dist/services/session-group-title.d.ts.map +1 -1
  694. package/dist/services/session-group-title.js +16 -1
  695. package/dist/services/session-group-title.js.map +1 -1
  696. package/dist/services/session-groups-store.d.ts +30 -0
  697. package/dist/services/session-groups-store.d.ts.map +1 -1
  698. package/dist/services/session-groups-store.js +26 -4
  699. package/dist/services/session-groups-store.js.map +1 -1
  700. package/dist/services/session-owner-notification.d.ts +9 -0
  701. package/dist/services/session-owner-notification.d.ts.map +1 -0
  702. package/dist/services/session-owner-notification.js +10 -0
  703. package/dist/services/session-owner-notification.js.map +1 -0
  704. package/dist/services/session-owner-reminder-config-store.d.ts +9 -0
  705. package/dist/services/session-owner-reminder-config-store.d.ts.map +1 -0
  706. package/dist/services/session-owner-reminder-config-store.js +24 -0
  707. package/dist/services/session-owner-reminder-config-store.js.map +1 -0
  708. package/dist/services/session-owner-reminder-store.d.ts +5 -0
  709. package/dist/services/session-owner-reminder-store.d.ts.map +1 -0
  710. package/dist/services/session-owner-reminder-store.js +54 -0
  711. package/dist/services/session-owner-reminder-store.js.map +1 -0
  712. package/dist/services/session-store.d.ts +98 -21
  713. package/dist/services/session-store.d.ts.map +1 -1
  714. package/dist/services/session-store.js +406 -48
  715. package/dist/services/session-store.js.map +1 -1
  716. package/dist/services/structured-bridge-clis.d.ts +15 -2
  717. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  718. package/dist/services/structured-bridge-clis.js +16 -1
  719. package/dist/services/structured-bridge-clis.js.map +1 -1
  720. package/dist/services/transcript-resolver.d.ts +1 -1
  721. package/dist/services/transcript-resolver.d.ts.map +1 -1
  722. package/dist/services/transcript-resolver.js +6 -1
  723. package/dist/services/transcript-resolver.js.map +1 -1
  724. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  725. package/dist/setup/bot-config-editor.js +7 -2
  726. package/dist/setup/bot-config-editor.js.map +1 -1
  727. package/dist/setup/ensure-herdr-integrations.d.ts +13 -0
  728. package/dist/setup/ensure-herdr-integrations.d.ts.map +1 -1
  729. package/dist/setup/ensure-herdr-integrations.js +21 -3
  730. package/dist/setup/ensure-herdr-integrations.js.map +1 -1
  731. package/dist/setup/open-platform-automation.d.ts +1 -0
  732. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  733. package/dist/setup/open-platform-automation.js +107 -7
  734. package/dist/setup/open-platform-automation.js.map +1 -1
  735. package/dist/skills/definitions.d.ts +17 -0
  736. package/dist/skills/definitions.d.ts.map +1 -1
  737. package/dist/skills/definitions.js +78 -8
  738. package/dist/skills/definitions.js.map +1 -1
  739. package/dist/skills/injection-mode.d.ts +24 -3
  740. package/dist/skills/injection-mode.d.ts.map +1 -1
  741. package/dist/skills/injection-mode.js +64 -15
  742. package/dist/skills/injection-mode.js.map +1 -1
  743. package/dist/skills/installer.d.ts +18 -0
  744. package/dist/skills/installer.d.ts.map +1 -1
  745. package/dist/skills/installer.js +45 -1
  746. package/dist/skills/installer.js.map +1 -1
  747. package/dist/types.d.ts +222 -10
  748. package/dist/types.d.ts.map +1 -1
  749. package/dist/utils/bot-routing.d.ts +1 -1
  750. package/dist/utils/bot-routing.d.ts.map +1 -1
  751. package/dist/utils/bot-routing.js.map +1 -1
  752. package/dist/utils/child-env.d.ts +92 -2
  753. package/dist/utils/child-env.d.ts.map +1 -1
  754. package/dist/utils/child-env.js +195 -1
  755. package/dist/utils/child-env.js.map +1 -1
  756. package/dist/utils/cli-usage-limit.d.ts +25 -14
  757. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  758. package/dist/utils/cli-usage-limit.js +25 -15
  759. package/dist/utils/cli-usage-limit.js.map +1 -1
  760. package/dist/utils/daemon-rejection-guard.d.ts +35 -0
  761. package/dist/utils/daemon-rejection-guard.d.ts.map +1 -0
  762. package/dist/utils/daemon-rejection-guard.js +101 -0
  763. package/dist/utils/daemon-rejection-guard.js.map +1 -0
  764. package/dist/utils/dashboard-env.d.ts +53 -0
  765. package/dist/utils/dashboard-env.d.ts.map +1 -0
  766. package/dist/utils/dashboard-env.js +116 -0
  767. package/dist/utils/dashboard-env.js.map +1 -0
  768. package/dist/utils/global-install.d.ts.map +1 -1
  769. package/dist/utils/global-install.js +61 -5
  770. package/dist/utils/global-install.js.map +1 -1
  771. package/dist/utils/install-diagnostics.d.ts +14 -7
  772. package/dist/utils/install-diagnostics.d.ts.map +1 -1
  773. package/dist/utils/install-diagnostics.js +20 -11
  774. package/dist/utils/install-diagnostics.js.map +1 -1
  775. package/dist/utils/local-dev-update.d.ts +132 -0
  776. package/dist/utils/local-dev-update.d.ts.map +1 -0
  777. package/dist/utils/local-dev-update.js +176 -0
  778. package/dist/utils/local-dev-update.js.map +1 -0
  779. package/dist/utils/pending-input-queue.d.ts +12 -1
  780. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  781. package/dist/utils/pending-input-queue.js.map +1 -1
  782. package/dist/worker.js +1517 -236
  783. package/dist/worker.js.map +1 -1
  784. package/dist/workflows/definition.d.ts +68 -68
  785. package/dist/workflows/events/payloads.d.ts +14 -14
  786. package/dist/workflows/events/schema.d.ts +312 -312
  787. package/dist/workflows/migration/v2-run-archive-schema.d.ts +40 -40
  788. package/dist/workflows/v3/cli-run.d.ts.map +1 -1
  789. package/dist/workflows/v3/cli-run.js +7 -0
  790. package/dist/workflows/v3/cli-run.js.map +1 -1
  791. package/dist/workflows/v3/goal-cli.d.ts +3 -0
  792. package/dist/workflows/v3/goal-cli.d.ts.map +1 -1
  793. package/dist/workflows/v3/goal-cli.js +11 -0
  794. package/dist/workflows/v3/goal-cli.js.map +1 -1
  795. package/dist/workflows/v3/run-envelope.d.ts +144 -144
  796. package/package.json +7 -2
  797. package/dist/cli/pm2-god-admission.d.ts +0 -8
  798. package/dist/cli/pm2-god-admission.d.ts.map +0 -1
  799. package/dist/cli/pm2-god-admission.js +0 -25
  800. package/dist/cli/pm2-god-admission.js.map +0 -1
  801. package/dist/core/riff-shutdown-detach.d.ts.map +0 -1
  802. package/dist/core/riff-shutdown-detach.js.map +0 -1
  803. package/dist/core/riff-worker-shutdown-readiness.d.ts.map +0 -1
  804. package/dist/core/riff-worker-shutdown-readiness.js.map +0 -1
  805. package/dist/dashboard-web/chunks/bot-defaults-page-4RENHE7J.js +0 -10
  806. package/dist/dashboard-web/chunks/chunk-2WQDS65D.js +0 -1
  807. package/dist/dashboard-web/chunks/chunk-G5XGBCM6.js +0 -1
  808. package/dist/dashboard-web/chunks/chunk-WLIVBPJC.js +0 -1
  809. package/dist/dashboard-web/chunks/groups-page-HLASRTSY.js +0 -8
  810. package/dist/dashboard-web/chunks/overview-page-6OK63V4V.js +0 -1
  811. package/dist/dashboard-web/chunks/sessions-page-B2TXGJDO.js +0 -8
  812. package/dist/dashboard-web/chunks/settings-page-6KJADSC6.js +0 -2
package/dist/cli.js CHANGED
@@ -9,22 +9,24 @@
9
9
  * botmux start — start daemon and auto plugin services
10
10
  * botmux stop [--with-plugin] — stop daemon (optionally stop auto plugin services)
11
11
  * botmux restart [--include-pm2] [--with-plugin] — restart daemon, then ensure auto plugin services;
12
- * --include-pm2 is a zero-live-God admission fence, not authority to signal an existing PM2 God
12
+ * --include-pm2 additionally retires the PM2 God after the fleet is verified retired
13
+ * (socket-addressed `pm2 kill`, never a PID signal) so the whole tree restarts from a fresh env
13
14
  * botmux restart --bootstrap-shutdown-protocol --yes — operator-approved one-time retirement
14
15
  * of a pre-protocol fleet after independently confirming all Session/Riff work is idle
15
16
  * botmux logs [--lines] — view daemon logs
16
17
  * botmux status — show daemon status
17
- * botmux upgrade|update — upgrade to latest version
18
+ * botmux upgrade|update — upgrade to latest version (本地 checkout 则 git pull --ff-only + rebuild + restart)
18
19
  * botmux device enroll|status|logout — manage the host desktop device credential
19
20
  * botmux list — interactive session picker (TUI), attach to managed tmux/ZMX sessions
20
21
  * botmux list --plain — plain table output (for piping / scripts)
22
+ * botmux preview <port> — register this session's loopback Web preview
21
23
  * botmux delete <id> — close a session by ID prefix
22
24
  * botmux delete all — close all active sessions
23
25
  * botmux autostart enable|disable|status — manage boot-time autostart (launchd / user systemd / Windows Task Scheduler)
24
26
  * botmux whiteboard status|enable|disable|current|list|read|update|write — local project whiteboard
25
27
  */
26
28
  import { execSync, execFileSync, spawnSync, spawn } from 'node:child_process';
27
- import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, renameSync, readdirSync, readlinkSync, symlinkSync, appendFileSync, statSync, unlinkSync, rmSync, realpathSync } from 'node:fs';
29
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, renameSync, readdirSync, readlinkSync, symlinkSync, appendFileSync, statSync, unlinkSync, rmSync, realpathSync, chmodSync } from 'node:fs';
28
30
  import { underReadIsolation, sendCredFilePath } from './adapters/cli/read-isolation.js';
29
31
  import { atomicWriteFileSync } from './utils/atomic-write.js';
30
32
  import { join, dirname, basename, resolve } from 'node:path';
@@ -34,6 +36,7 @@ import { createInterface } from 'node:readline';
34
36
  import { createRequire } from 'node:module';
35
37
  import { randomBytes, randomUUID } from 'node:crypto';
36
38
  import { validateWorkingDir } from './core/working-dir.js';
39
+ import { closeResidualClause, parseCloseResidual } from './core/close-residual.js';
37
40
  import { findAncestorSessionContext as findLiveAncestorSessionContext, resolveSessionContext, } from './core/session-marker.js';
38
41
  import { resolveBotmuxDataDir } from './core/data-dir.js';
39
42
  import { dashboardSecretPath } from './core/dashboard-secret.js';
@@ -55,13 +58,15 @@ import { validateCodexAppManagedSendOrigin, } from './utils/codex-app-dispatch-l
55
58
  import { hasProtectedSessionMutationOwnership } from './core/session-mutation-guard.js';
56
59
  import { logger } from './utils/logger.js';
57
60
  import { withFileLock, withFileLockSync } from './utils/file-lock.js';
58
- import { scrubClaudeSessionMarkerEnv, scrubSessionCliHomeEnv, scrubWorkflowWorkerEnv } from './utils/child-env.js';
61
+ import { pm2CallerEnv } from './cli/pm2-env.js';
59
62
  import { scheduleTimeZone } from './utils/timezone.js';
60
63
  import { expandHomePath, invalidWorkingDirs } from './utils/working-dir.js';
61
- import { firstPositional } from './cli/arg-utils.js';
64
+ import { firstPositional, hasFlagOrEq } from './cli/arg-utils.js';
62
65
  import { isColdResumeDormant, isRealManagedSession, sessionListDisposition } from './cli/session-list-liveness.js';
63
66
  import { computeSessionPickerLayout, } from './cli/session-picker-layout.js';
64
67
  import { computeSessionPickerScrollWindow } from './cli/session-picker-viewport.js';
68
+ import { canWakeDormantBackendForAttach, wakeDormantBackendForAttach, } from './cli/session-list-wake.js';
69
+ import { SESSION_WAKE_DEADLINE_HEADER } from './core/session-wake-deadline.js';
65
70
  import { terminalCellWidth } from './cli/terminal-width.js';
66
71
  import { attachFrozenManagedZmxSession, freezeManagedZmxAttachTarget, } from './cli/zmx-managed-attach.js';
67
72
  import { readSupervisorProcessStartIdentity } from './core/process-start-identity.js';
@@ -81,13 +86,17 @@ import { assertPm2DaemonShutdownCapabilitiesIn } from './cli/pm2-shutdown-capabi
81
86
  import { evaluateRestartShutdownPreflight } from './cli/restart-shutdown-preflight.js';
82
87
  import { recordAndNotifyRestartBootstrapFailure, restartFailurePathIn, } from './cli/restart-failure-notification.js';
83
88
  import { resolveRestartFailureOwner } from './cli/restart-failure-owner.js';
84
- import { assertIncludePm2RestartAdmission } from './cli/pm2-god-admission.js';
89
+ import { assertNoReplacementPm2God, assertPm2RegistryQuiescentForGodRetirement, retireSoleLivePm2God, } from './cli/pm2-god-retirement.js';
90
+ import { pm2FleetMutationLockTarget, withPm2FleetMutationLock } from './cli/pm2-fleet-lock.js';
91
+ import { LogFileFollower } from './cli/log-tail.js';
85
92
  import { requestAttestedDaemonShutdown, requestAttestedDaemonShutdownBatch, } from './cli/supervisor-shutdown-client.js';
86
93
  import { assertDaemonPm2GracefulExitPolicy, assertConfiguredPm2FleetReady, assertExactAttestedDaemonSet, classifyStartBotFleetAdmission, normalizeRawPm2StopExitCodes, reconcileLatePm2StartPublication, runBoundedPm2StartTransaction, } from './cli/pm2-start-transaction.js';
87
94
  import { callDashboard } from './cli/dashboard-endpoint.js';
88
- import { DASHBOARD_COMMAND_USAGE, executeDashboardCommand, formatDashboardFallbackFailure, } from './cli/dashboard-command.js';
89
- import { globalInstallUpdateLockTargetIn, installLatestBotmuxSync } from './core/maintenance.js';
95
+ import { DASHBOARD_COMMAND_USAGE, executeDashboardCommand, formatDashboardFallbackFailure, formatDashboardSuccessLines, } from './cli/dashboard-command.js';
96
+ import { globalInstallUpdateLockTarget, globalInstallUpdateLockTargetIn, installLatestBotmuxSync } from './core/maintenance.js';
90
97
  import { formatGlobalInstallCommand, resolveGlobalInstallPlan, UnsupportedGlobalInstallError, } from './utils/global-install.js';
98
+ import { isLocalDevInstall, botmuxCliEntryAt } from './utils/install-info.js';
99
+ import { resolveLocalDevCheckoutDir, isGitWorktree, gitPorcelainStatus, localDevUpdateSteps, describeSpawnFailure, } from './utils/local-dev-update.js';
91
100
  import { loadDashboardSecret } from './dashboard/auth.js';
92
101
  import { postWorkflowDaemonMutation, } from './workflows/v3/daemon-ipc-client.js';
93
102
  import { postWorkflowSessionRunMutation, readWorkflowSessionRelayContext, } from './workflows/v3/session-relay-client.js';
@@ -98,14 +107,14 @@ import { isRetryableAskHttpStatus } from './core/ask-types.js';
98
107
  import { hasManagedOriginIsolationMarker, managedOriginDataRootProbeAccess, managedOriginIsolationSentinelAccess, managedOriginLegacyIsolationProbeAccess, readManagedOriginRootLocator, readManagedOriginCapability, } from './core/managed-origin-capability.js';
99
108
  import { attestManagedOrigin, } from './core/managed-origin-attestation.js';
100
109
  import { rejectLikelyWindowsStdinMojibake, decodeStdinBytes } from './cli/stdin-encoding.js';
101
- import { formatBotInfoEntriesForCli, formatChatBotsForCli, } from './cli/bots-list-output.js';
110
+ import { collaborationHelp, formatBotInfoEntriesForCli, formatChatBotsForCli, } from './cli/bots-list-output.js';
102
111
  import { ensureBotChatGrantMatrix, requestExactChatGrant } from './cli/exact-chat-grant-client.js';
103
112
  import { buildFooterAddressing, hasKnownBotMention, knownBotOpenIdsFromCrossRef, orderedFooterRecipients, stripCodeSpans, } from './utils/bot-routing.js';
104
113
  import { isLocale, localeForBot, setDefaultLocale, SUPPORTED_LOCALES, t } from './i18n/index.js';
105
114
  import { chatAppLink, larkHosts, normalizeBrand } from './im/lark/lark-hosts.js';
106
115
  import { mergeDashboardConfig, mergeGlobalConfig, readGlobalConfig, setGlobalLocale, globalConfigPath } from './global-config.js';
107
116
  import { createWhiteboard, ensureDefaultWhiteboard, getWhiteboard, listWhiteboards, readWhiteboard, whiteboardEnabled, whiteboardPath, } from './services/whiteboard-store.js';
108
- import { buildBridgeSendMarkerContent, buildBridgeSendPreviewText, } from './services/bridge-fallback-gate.js';
117
+ import { buildBridgeSendMarkerContent, buildBridgeSendPreviewText, stripTrailingOaiMemoryCitation, } from './services/bridge-fallback-gate.js';
109
118
  import { bindRestartLeaseTo, commitRestartIntentAttemptTo, consumeRestartIntentTo, removeRestartIntentAttemptTo, writeRestartAttemptIntentTo, } from './services/restart-intent-store.js';
110
119
  import { loadAllSessionsSnapshot, mutateSessionRowOffline } from './services/session-store.js';
111
120
  import { evaluateVcMeetingManagedSend, isTrustedVcMeetingHostRelayParent, resolveVcMeetingImTurnOrigin, } from './services/vc-meeting-send-policy.js';
@@ -151,8 +160,12 @@ const PM2_NAME = 'botmux';
151
160
  * when those external pm2 installations get moved or removed.
152
161
  */
153
162
  const PM2_HOME = join(CONFIG_DIR, 'pm2');
154
- const PM2_FLEET_MUTATION_LOCK_TARGET = join(CONFIG_DIR, 'pm2-fleet-mutation');
163
+ // Shared with plugin service-manager (src/cli/pm2-fleet-lock.ts): one lock
164
+ // serializes every botmux-internal mutation of the shared PM2_HOME.
165
+ const PM2_FLEET_MUTATION_LOCK_TARGET = pm2FleetMutationLockTarget();
155
166
  const PM2_START_COMMAND_TIMEOUT_MS = 30_000;
167
+ // `pm2 kill` with an already-empty fleet only tears down the God + socket.
168
+ const PM2_GOD_KILL_COMMAND_TIMEOUT_MS = 30_000;
156
169
  const PM2_START_VERIFY_MIN_TIMEOUT_MS = 60_000;
157
170
  const PM2_START_VERIFY_PER_PROCESS_MS = 2_000;
158
171
  const PM2_START_LATE_PUBLICATION_SETTLE_MS = 10_000;
@@ -190,28 +203,12 @@ function pm2Bin() {
190
203
  return symlink;
191
204
  return 'pm2';
192
205
  }
193
- /** Env for pm2 invocations with an isolated PM2_HOME. */
206
+ /** Env for pm2 invocations with an isolated PM2_HOME. The scrub set (session
207
+ * CLI homes, Claude/workflow markers, Dashboard H5 credentials, invoker
208
+ * terminal fingerprints, turn-scoped session identity — plus the TERM
209
+ * re-pin) lives in cli/pm2-env.ts so it stays assertable in a test. */
194
210
  function pm2Env(home = PM2_HOME) {
195
- const env = { ...process.env, PM2_HOME: home };
196
- // pm2 persists the caller's env into every managed app (and into dump.pm2
197
- // for resurrect), so a `botmux start/restart` invoked from ANY process that
198
- // carries a session-level CLI home pointer — most commonly a bot's own
199
- // session during self-upgrade, whose env holds its injected
200
- // CLAUDE_CONFIG_DIR — would poison ALL workers, making every non-isolated
201
- // bot read/write a sibling bot's home. Strip at this boundary so the daemon
202
- // stays session-agnostic; daemon/worker boot scrub the same keys against
203
- // stale dumps (see SESSION_CLI_HOME_ENV_KEYS for the full story, including
204
- // why GROK_HOME is exempt and why deleting beats pinning a default).
205
- scrubSessionCliHomeEnv(env);
206
- // Claude session markers ride the same pm2 env-persistence vector; baked in
207
- // they eventually flip transcript saving off fleet-wide once the tmux server
208
- // respawns from a poisoned daemon (see CLAUDE_SESSION_MARKER_ENV_KEYS).
209
- scrubClaudeSessionMarkerEnv(env);
210
- // Workflow/goal markers identify one short-lived node worker. Persisting
211
- // them in PM2 would make every daemon — and then every ordinary chat worker
212
- // it forks — run in workflow mode after a restart initiated from that node.
213
- scrubWorkflowWorkerEnv(env);
214
- return env;
211
+ return pm2CallerEnv(process.env, home);
215
212
  }
216
213
  function listPm2GodDaemonPids(home = PM2_HOME) {
217
214
  const marker = `God Daemon (${home})`;
@@ -716,7 +713,11 @@ function ecosystemConfig(bots = loadBotsJson(), activationAppId) {
716
713
  });
717
714
  apps.push({
718
715
  name: 'botmux-dashboard',
719
- script: join(PKG_ROOT, 'dist', 'dashboard.js'),
716
+ // index-dashboard.ts, NOT dashboard.js: the entry dotenv-loads
717
+ // ~/.botmux/.env before dynamically importing the dashboard, which is how
718
+ // the Feishu H5 credential family reaches the dashboard WITHOUT being baked
719
+ // into this file's shared env block (see DAEMON_ENV_KEYS).
720
+ script: join(PKG_ROOT, 'dist', 'index-dashboard.js'),
720
721
  interpreter,
721
722
  cwd: PKG_ROOT,
722
723
  autorestart: true,
@@ -742,7 +743,22 @@ function ecosystemConfig(bots = loadBotsJson(), activationAppId) {
742
743
  });
743
744
  const cfg = { apps };
744
745
  const tmpFile = join(CONFIG_DIR, 'ecosystem.config.json');
745
- writeFileSync(tmpFile, JSON.stringify(cfg, null, 2));
746
+ // Owner-only, same as bots.json kept as defense in depth. The Dashboard
747
+ // Feishu H5 app secret is no longer baked into the env block (the dashboard
748
+ // dotenv-loads it via index-dashboard.ts; DAEMON_ENV_KEYS excludes the
749
+ // family), but this file still bakes operator-configured hosts/ports/paths,
750
+ // an older install's copy may still carry the secret until overwritten, and
751
+ // 0600 keeps any future env-block regression from becoming world-readable.
752
+ // writeFileSync's `mode` applies only when the file is CREATED — chmod as
753
+ // well, or an install that already wrote it 0644 keeps the world-readable
754
+ // mode forever.
755
+ writeFileSync(tmpFile, JSON.stringify(cfg, null, 2), { mode: 0o600 });
756
+ if (process.platform !== 'win32') {
757
+ try {
758
+ chmodSync(tmpFile, 0o600);
759
+ }
760
+ catch { /* best-effort: exotic FS / mounted noperm */ }
761
+ }
746
762
  return tmpFile;
747
763
  }
748
764
  function hasConfig() {
@@ -2454,7 +2470,7 @@ async function cmdStart() {
2454
2470
  process.exit(1);
2455
2471
  }
2456
2472
  }
2457
- await withFileLock(PM2_FLEET_MUTATION_LOCK_TARGET, async () => {
2473
+ await withPm2FleetMutationLock(async () => {
2458
2474
  await withFileLock(BOTS_JSON_FILE, async () => {
2459
2475
  const lockedBots = loadBotsJson();
2460
2476
  if (JSON.stringify(lockedBots) !== JSON.stringify(botsForCheck)) {
@@ -3088,7 +3104,7 @@ async function cmdStop() {
3088
3104
  throw new Error('[stop] --bootstrap-shutdown-protocol requires --yes after confirming every Session/Riff workload is idle');
3089
3105
  }
3090
3106
  ensureConfigDir();
3091
- await withFileLock(PM2_FLEET_MUTATION_LOCK_TARGET, async () => {
3107
+ await withPm2FleetMutationLock(async () => {
3092
3108
  assertNoDuplicatePm2GodDaemons();
3093
3109
  cleanupLegacyPm2(bootstrapShutdownProtocol ? 'stop' : undefined);
3094
3110
  if (bootstrapShutdownProtocol) {
@@ -3186,7 +3202,7 @@ async function cmdRestart() {
3186
3202
  process.exit(1);
3187
3203
  }
3188
3204
  ensureConfigDir();
3189
- await withFileLock(PM2_FLEET_MUTATION_LOCK_TARGET, async () => {
3205
+ await withPm2FleetMutationLock(async () => {
3190
3206
  const includePm2 = process.argv.includes('--include-pm2');
3191
3207
  const includePluginServices = process.argv.includes('--with-plugin');
3192
3208
  const bootstrapShutdownProtocol = process.argv.includes('--bootstrap-shutdown-protocol');
@@ -3197,9 +3213,6 @@ async function cmdRestart() {
3197
3213
  if (bootstrapShutdownProtocol && includePm2) {
3198
3214
  throw new Error('[restart] --bootstrap-shutdown-protocol cannot be combined with --include-pm2');
3199
3215
  }
3200
- if (includePm2) {
3201
- assertIncludePm2RestartAdmission(listPm2GodDaemonPids());
3202
- }
3203
3216
  const restartIntentDir = resolveDataDir();
3204
3217
  let stagedRestartIntent = null;
3205
3218
  try {
@@ -3210,20 +3223,17 @@ async function cmdRestart() {
3210
3223
  preflightNodeSanity();
3211
3224
  await ensureSystemDependencies();
3212
3225
  cleanupLegacyPm2(bootstrapShutdownProtocol ? 'restart' : undefined);
3213
- if (bootstrapShutdownProtocol || includePm2) {
3214
- // An include-pm2 restart was admitted only when no live PM2 God existed;
3215
- // a read-only jlist probe would start one and invalidate that admission.
3216
- // Keep the existing include-pm2 clean-start path unchanged.
3217
- if (bootstrapShutdownProtocol)
3218
- bootstrapDeleteAllBotmuxProcesses('restart');
3219
- else
3220
- deleteAllBotmuxProcesses();
3226
+ if (bootstrapShutdownProtocol) {
3227
+ bootstrapDeleteAllBotmuxProcesses('restart');
3221
3228
  }
3222
3229
  else {
3223
3230
  // This process is the newly installed code generation even when the
3224
3231
  // Dashboard that spawned it is still the old in-memory generation. Do
3225
3232
  // the policy probe here, before the generic retirement path throws, so
3226
3233
  // the first-upgrade failure becomes durable and reaches the owner.
3234
+ // --include-pm2 takes this path too: the probe's lazy jlist may start a
3235
+ // PM2 God, which is harmless now — God retirement below runs after the
3236
+ // fleet is verified retired, against whichever God owns the home.
3227
3237
  const preflight = evaluateRestartShutdownPreflight();
3228
3238
  if (preflight.bootstrapRequired) {
3229
3239
  const detail = 'current daemon PM2 policy requires the one-time shutdown-protocol bootstrap';
@@ -3235,8 +3245,24 @@ async function cmdRestart() {
3235
3245
  }
3236
3246
  deleteAllBotmuxProcesses();
3237
3247
  }
3238
- if (includePluginServices)
3248
+ // --include-pm2 tears down the whole PM2_HOME, so ALL plugin services get
3249
+ // a graceful stop first instead of dying with the God; auto ones are
3250
+ // re-ensured after the restart, manually-started ones stay down. Stop
3251
+ // errors are collected into reports rather than thrown (service-manager
3252
+ // contract), so this path re-checks them: a service that failed to stop
3253
+ // must block the God retirement below, not die with the God.
3254
+ if (includePm2) {
3255
+ const pluginStopReports = await stopPluginServicesForCli(undefined, {});
3256
+ const failedStops = pluginStopReports.filter(report => report.action === 'failed');
3257
+ if (failedStops.length > 0) {
3258
+ throw new Error(`[restart --include-pm2] plugin service(s) failed to stop gracefully: `
3259
+ + `${failedStops.map(report => report.pluginId).join(', ')}; `
3260
+ + 'the PM2 God and every remaining process were left untouched — fix or delete these services, then rerun');
3261
+ }
3262
+ }
3263
+ else if (includePluginServices) {
3239
3264
  await stopPluginServicesForCli(undefined, { autoOnly: true });
3265
+ }
3240
3266
  cleanupStaleDaemonDescriptors();
3241
3267
  const retiredProjection = readVerifiedBotmuxPm2Projection('restart-start');
3242
3268
  assertNoUnregisteredLiveDaemonDescriptors('restart-start', retiredProjection);
@@ -3244,6 +3270,37 @@ async function cmdRestart() {
3244
3270
  throw new Error(`[restart-start] new PM2 core row(s) appeared after verified retirement: `
3245
3271
  + retiredProjection.map(entry => `${entry.name}:${entry.pid}`).join(', '));
3246
3272
  }
3273
+ // Only now — with the core fleet verified retired and plugin services
3274
+ // stopped — is the God a stateless supervisor of nothing, safe to retire
3275
+ // via its own control socket. The fresh `pm2 start` below then births a
3276
+ // new God from this CLI's (pm2Env-scrubbed) environment, which is what
3277
+ // makes --include-pm2 a genuinely complete restart.
3278
+ if (includePm2) {
3279
+ // Independent whole-registry proof, not just the core projection above:
3280
+ // every row still known to the God (a plugin stop that failed, an
3281
+ // orphaned row from an uninstalled plugin) blocks the kill fail-closed.
3282
+ assertPm2RegistryQuiescentForGodRetirement(parsePm2JlistOutputStrict(pm2Capture(['jlist'])).map(row => ({
3283
+ name: typeof row?.name === 'string' && row.name ? row.name : 'unknown',
3284
+ status: typeof row?.pm2_env?.status === 'string' ? row.pm2_env.status : undefined,
3285
+ pid: parsePm2Integer(row?.pid, { nonNegative: true }),
3286
+ })));
3287
+ const retired = await retireSoleLivePm2God({
3288
+ listGodPids: () => listPm2GodDaemonPids(),
3289
+ readStartIdentity: pid => readSupervisorProcessStartIdentity(pid),
3290
+ isAlive: pid => { try {
3291
+ process.kill(pid, 0);
3292
+ return true;
3293
+ }
3294
+ catch {
3295
+ return false;
3296
+ } },
3297
+ pm2Kill: () => runPm2(['kill'], true, PM2_HOME, PM2_GOD_KILL_COMMAND_TIMEOUT_MS),
3298
+ sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
3299
+ now: () => Date.now(),
3300
+ });
3301
+ if (retired)
3302
+ console.log(`已退役 PM2 God (pid ${retired.pid}),fleet 将以当前干净环境全新启动`);
3303
+ }
3247
3304
  await withFileLock(BOTS_JSON_FILE, async () => {
3248
3305
  const restartBots = loadBotsJson();
3249
3306
  const cfg = ecosystemConfig(restartBots);
@@ -3262,6 +3319,11 @@ async function cmdRestart() {
3262
3319
  start: timeoutMs => {
3263
3320
  assertBotsConfigSnapshotUnchanged('restart-start', restartBots);
3264
3321
  assertNoDuplicatePm2GodDaemons();
3322
+ // After retiring the God this start must be the one to birth
3323
+ // its successor; a God that appeared in between came from some
3324
+ // other client's environment and is refused.
3325
+ if (includePm2)
3326
+ assertNoReplacementPm2God(listPm2GodDaemonPids());
3265
3327
  preflightNodeSanity();
3266
3328
  runPm2(['start', cfg], true, PM2_HOME, timeoutMs);
3267
3329
  },
@@ -3318,7 +3380,7 @@ function retireExactBotmuxProcess(operation, target, fullProjection) {
3318
3380
  async function ensureBotDaemonStopped(appId, _opts = {}) {
3319
3381
  ensureConfigDir();
3320
3382
  try {
3321
- return await withFileLock(PM2_FLEET_MUTATION_LOCK_TARGET, async () => (withFileLock(BOTS_JSON_FILE, async () => {
3383
+ return await withPm2FleetMutationLock(async () => (withFileLock(BOTS_JSON_FILE, async () => {
3322
3384
  assertNoDuplicatePm2GodDaemons();
3323
3385
  preflightNodeSanity();
3324
3386
  cleanupLegacyPm2();
@@ -3381,7 +3443,7 @@ async function ensureBotDaemonStopped(appId, _opts = {}) {
3381
3443
  async function ensureBotDaemonStarted(appId, opts = {}) {
3382
3444
  ensureConfigDir();
3383
3445
  try {
3384
- return await withFileLock(PM2_FLEET_MUTATION_LOCK_TARGET, async () => (withFileLock(BOTS_JSON_FILE, async () => {
3446
+ return await withPm2FleetMutationLock(async () => (withFileLock(BOTS_JSON_FILE, async () => {
3385
3447
  assertNoDuplicatePm2GodDaemons();
3386
3448
  preflightNodeSanity();
3387
3449
  cleanupLegacyPm2();
@@ -3644,23 +3706,48 @@ function warnIfLegacyBotmuxAlive() {
3644
3706
  }
3645
3707
  if (!legacyPid)
3646
3708
  return;
3709
+ // Deliberately NO PM2 client here: this helper runs at the top of read-only
3710
+ // commands (status/logs), and a pm2 jlist against the legacy home would
3711
+ // lazily REVIVE a legacy God the moment the recorded one exits between the
3712
+ // kill(0) probe and the client's connect — a read command must never create
3713
+ // one. The process-table marker scan also closes the stale-pid-file hole:
3714
+ // kill(pid, 0) alone would accept an unrelated process that reused the pid.
3647
3715
  try {
3648
- process.kill(legacyPid, 0);
3716
+ if (!listPm2GodDaemonPids(legacyHome).includes(legacyPid))
3717
+ return;
3649
3718
  }
3650
3719
  catch {
3651
3720
  return;
3652
3721
  }
3722
+ // "Still has botmux processes" from PM2's own pid files, not from a client:
3723
+ // the legacy God maintains <name>-<id>.pid under <home>/pids while an app
3724
+ // process runs.
3653
3725
  try {
3654
- const output = pm2Capture(['jlist'], legacyHome);
3655
- const apps = parsePm2JlistOutput(output);
3656
- const hasBotmux = apps.some(a => a.name === PM2_NAME || a.name.startsWith(`${PM2_NAME}-`));
3657
- if (hasBotmux) {
3726
+ for (const entry of readdirSync(join(legacyHome, 'pids'))) {
3727
+ if (!entry.startsWith(PM2_NAME) || !entry.endsWith('.pid'))
3728
+ continue;
3729
+ let appPid = 0;
3730
+ try {
3731
+ appPid = parseInt(readFileSync(join(legacyHome, 'pids', entry), 'utf-8').trim(), 10);
3732
+ }
3733
+ catch {
3734
+ continue;
3735
+ }
3736
+ if (!appPid)
3737
+ continue;
3738
+ try {
3739
+ process.kill(appPid, 0);
3740
+ }
3741
+ catch {
3742
+ continue;
3743
+ }
3658
3744
  console.warn('⚠️ 检测到旧版 PM2_HOME (~/.pm2) 下仍有 botmux 进程,运行 `botmux restart` 完成迁移。\n');
3745
+ return;
3659
3746
  }
3660
3747
  }
3661
- catch { /* ignore */ }
3748
+ catch { /* no pids dir */ }
3662
3749
  }
3663
- function cmdLogs() {
3750
+ async function cmdLogs() {
3664
3751
  warnIfLegacyBotmuxAlive();
3665
3752
  const lines = process.argv.includes('--lines')
3666
3753
  ? process.argv[process.argv.indexOf('--lines') + 1] || '50'
@@ -3670,7 +3757,16 @@ function cmdLogs() {
3670
3757
  const botIdx = process.argv.includes('--bot')
3671
3758
  ? process.argv[process.argv.indexOf('--bot') + 1]
3672
3759
  : undefined;
3673
- let target;
3760
+ // No PM2 client at all: `pm2 logs` lazily births a God when none is alive
3761
+ // (Client.start → pingDaemon false → launchDaemon), which made this read
3762
+ // command a check/use race against `restart --include-pm2` — a God observed
3763
+ // under the fleet lock could be retired before the spawned client connected,
3764
+ // and the connecting client would then create a replacement God inside the
3765
+ // kill→start window. Tailing the log files pm2 itself writes (the exact
3766
+ // out_file/error_file paths ecosystemConfig pins) removes the raced resource
3767
+ // entirely: no interleaving of `botmux logs` can create a God, no fleet lock
3768
+ // is needed, and logs keep working while the fleet is stopped.
3769
+ let sources;
3674
3770
  if (botIdx !== undefined) {
3675
3771
  const numericIdx = /^\d+$/.test(botIdx) ? Number(botIdx) : undefined;
3676
3772
  const selectedIdx = numericIdx === undefined
@@ -3678,31 +3774,94 @@ function cmdLogs() {
3678
3774
  : numericIdx >= 0 && numericIdx < bots.length
3679
3775
  ? numericIdx
3680
3776
  : undefined;
3681
- target = selectedIdx !== undefined
3682
- ? botProcessName(bots[selectedIdx], selectedIdx, PM2_NAME)
3683
- : numericIdx !== undefined
3684
- ? `${PM2_NAME}-${botIdx}`
3685
- : botIdx;
3777
+ if (selectedIdx !== undefined) {
3778
+ sources = coreBotLogSources(bots, selectedIdx);
3779
+ }
3780
+ else {
3781
+ const wanted = numericIdx !== undefined ? `${PM2_NAME}-${botIdx}` : botIdx;
3782
+ sources = allBotmuxLogSources(bots).filter(source => source.label === wanted);
3783
+ if (sources.length === 0) {
3784
+ console.error(`✗ 未找到 ${wanted} 的日志文件(可用:不带 --bot 查看全部,或用 0-based index / pm2 名 / appId)`);
3785
+ process.exitCode = 1;
3786
+ return;
3787
+ }
3788
+ }
3686
3789
  }
3687
3790
  else {
3688
- // Show all botmux logs via pm2 regex match
3689
- target = `/^${PM2_NAME}/`;
3791
+ sources = allBotmuxLogSources(bots);
3792
+ }
3793
+ // Not `|| 50`: an explicit --lines 0 (follow-only) must stay 0.
3794
+ const rawLines = Number.parseInt(lines, 10);
3795
+ const parsedLines = Number.isSafeInteger(rawLines) && rawLines >= 0 ? rawLines : 50;
3796
+ console.log(`跟踪 ${sources.length} 个日志文件(Ctrl+C 退出;fleet 停止时也能查看历史并等待新日志)`);
3797
+ const follower = new LogFileFollower({ sources, writeLine: line => console.log(line) });
3798
+ follower.printInitialTail(parsedLines);
3799
+ follower.start();
3800
+ }
3801
+ /** Log files of one core bot daemon — the exact paths ecosystemConfig pins. */
3802
+ function coreBotLogSources(bots, index) {
3803
+ const label = botProcessName(bots[index], index, PM2_NAME);
3804
+ return [
3805
+ { label, stream: 'out', file: join(LOG_DIR, `daemon-${index}-out.log`) },
3806
+ { label, stream: 'err', file: join(LOG_DIR, `daemon-${index}-error.log`) },
3807
+ ];
3808
+ }
3809
+ /** Every botmux log file: core daemons + dashboard (ecosystemConfig paths)
3810
+ * plus plugin services (PM2 default logs dir under the shared PM2_HOME). */
3811
+ function allBotmuxLogSources(bots) {
3812
+ const sources = bots.flatMap((_bot, i) => coreBotLogSources(bots, i));
3813
+ sources.push({ label: 'botmux-dashboard', stream: 'out', file: join(LOG_DIR, 'dashboard-out.log') }, { label: 'botmux-dashboard', stream: 'err', file: join(LOG_DIR, 'dashboard-error.log') });
3814
+ const pluginLogsDir = join(PM2_HOME, 'logs');
3815
+ try {
3816
+ for (const entry of readdirSync(pluginLogsDir)) {
3817
+ const match = entry.match(/^(botmux-plugin-.+?)-(out|error)(?:-\d+)?\.log$/);
3818
+ if (!match)
3819
+ continue;
3820
+ sources.push({
3821
+ label: match[1],
3822
+ stream: match[2] === 'error' ? 'err' : 'out',
3823
+ file: join(pluginLogsDir, entry),
3824
+ });
3825
+ }
3690
3826
  }
3691
- // Use spawn for streaming output. Windows cannot spawn a .js CLI script
3692
- // directly, so run the bundled pm2 script through the current node.exe.
3693
- const pm2 = buildPm2SpawnCommand(pm2Bin(), ['logs', target, '--lines', lines]);
3694
- const child = spawn(pm2.command, pm2.args, {
3695
- stdio: 'inherit',
3696
- env: pm2Env(),
3697
- shell: pm2.shell ?? false,
3698
- });
3699
- child.on('exit', code => process.exit(code ?? 0));
3827
+ catch { /* no plugin logs yet */ }
3828
+ return sources;
3700
3829
  }
3701
- function cmdStatus() {
3830
+ async function cmdStatus() {
3702
3831
  warnIfLegacyBotmuxAlive();
3703
- runPm2(['status']);
3832
+ // A pm2 client with no live God lazily births one from this process's env.
3833
+ // During an include-pm2 restart's kill→start window that replacement God
3834
+ // would abort the restart with the whole fleet offline, and in a normally
3835
+ // stopped state it would silently resurrect a God nobody asked for — so a
3836
+ // read-only status must check for a God under the fleet lock and never
3837
+ // create one.
3838
+ let entered = false;
3839
+ try {
3840
+ await withPm2FleetMutationLock(async () => {
3841
+ entered = true;
3842
+ if (listPm2GodDaemonPids().length === 0) {
3843
+ console.log('PM2 God 未运行:fleet 已停止(status 不会隐式启动它;需要时运行 botmux start)。');
3844
+ return;
3845
+ }
3846
+ runPm2(['status']);
3847
+ }, { maxWaitMs: 3_000 });
3848
+ }
3849
+ catch (err) {
3850
+ if (entered)
3851
+ throw err;
3852
+ // Non-zero so automation doesn't mistake "couldn't observe" for success.
3853
+ console.log('fleet 停启操作进行中(fleet mutation 锁被占用),请稍后重试 botmux status。');
3854
+ process.exitCode = 1;
3855
+ }
3704
3856
  }
3705
3857
  function cmdUpgrade() {
3858
+ // 本地 checkout(有 .git/src):走 git pull --ff-only → 重新 build → 从本
3859
+ // checkout 重启,而不是拿全局包管理器去升级(那对 dev 部署无效,见
3860
+ // install-info.ts 的 isLocalDevInstall 说明)。
3861
+ if (isLocalDevInstall()) {
3862
+ cmdUpgradeLocalDev();
3863
+ return;
3864
+ }
3706
3865
  try {
3707
3866
  const plan = resolveGlobalInstallPlan();
3708
3867
  console.log(`🔄 升级中:${formatGlobalInstallCommand(plan)}`);
@@ -3719,6 +3878,82 @@ function cmdUpgrade() {
3719
3878
  process.exit(1);
3720
3879
  }
3721
3880
  }
3881
+ /** 在 checkout 目录里同步跑一条命令,stdio 直通;失败抛错。 */
3882
+ function runInCheckout(cwd, command, args) {
3883
+ const result = spawnSync(command, args, {
3884
+ cwd,
3885
+ stdio: 'inherit',
3886
+ shell: process.platform === 'win32',
3887
+ });
3888
+ if (result.error || result.status !== 0) {
3889
+ throw new Error(describeSpawnFailure(command, args, result));
3890
+ }
3891
+ }
3892
+ /**
3893
+ * 本地 checkout 的更新流程:git 干净检查 → git pull --ff-only → pnpm build →
3894
+ * 从本 checkout 的 dist/cli.js restart。dist/ 被 gitignore,只 pull 不 build
3895
+ * 重启后跑的还是旧代码,故 build 步不可省。定位/干净检查/命令定义与 dashboard
3896
+ * 共用 src/utils/local-dev-update.ts,避免两边逻辑漂移。
3897
+ */
3898
+ function cmdUpgradeLocalDev() {
3899
+ const dir = resolveLocalDevCheckoutDir();
3900
+ if (!isGitWorktree(dir)) {
3901
+ console.error(`❌ ${dir} 不是 git 工作树,无法用 git pull 更新。请手动更新或改用全局安装。`);
3902
+ process.exit(1);
3903
+ }
3904
+ console.log(`🔄 本地 checkout 更新:${dir}`);
3905
+ // git 干净检查 + pull + build 全程握同一把跨进程 update 锁(与 dashboard 的
3906
+ // /api/update/run 用的是同一个 target),避免 CLI 与 dashboard 同时对同一
3907
+ // checkout 交错跑 pnpm build 而互相清理/覆盖 dist。restart 不在锁内——它有
3908
+ // 自己的 restart lease。
3909
+ try {
3910
+ const lockTarget = globalInstallUpdateLockTarget();
3911
+ // daemon 正常会建好 dataDir;但 CLI 可能在 daemon 尚未起过的机器上先跑
3912
+ // update,锁文件父目录不存在会让 withFileLockSync 报 ENOENT 而盖掉真实错误。
3913
+ mkdirSync(dirname(lockTarget), { recursive: true });
3914
+ withFileLockSync(lockTarget, () => {
3915
+ // 1) fail closed:有未提交改动就中止(不偷偷 stash),让用户自己决定。
3916
+ let status;
3917
+ try {
3918
+ status = gitPorcelainStatus(dir);
3919
+ }
3920
+ catch (error) {
3921
+ throw new Error(`读取 git 状态失败:${error instanceof Error ? error.message : error}`);
3922
+ }
3923
+ if (status) {
3924
+ const err = new Error('dirty');
3925
+ err.dirtyStatus = status;
3926
+ throw err;
3927
+ }
3928
+ // 2~3) git pull --ff-only(分叉/冲突直接报错停下,不自动 merge)+ pnpm build。
3929
+ for (const { command, args } of localDevUpdateSteps()) {
3930
+ console.log(`→ ${command} ${args.join(' ')}`);
3931
+ runInCheckout(dir, command, args);
3932
+ }
3933
+ }, { maxWaitMs: 2_000 });
3934
+ }
3935
+ catch (error) {
3936
+ const dirty = error.dirtyStatus;
3937
+ if (dirty) {
3938
+ console.error('❌ 工作区有未提交改动,已中止更新(不会自动 stash)。请先提交或清理:');
3939
+ console.error(dirty);
3940
+ }
3941
+ else {
3942
+ console.error(`❌ 更新失败:${error instanceof Error ? error.message : error}`);
3943
+ }
3944
+ process.exit(1);
3945
+ }
3946
+ // 4) 用本 checkout 的 cli.js 重启 daemon(不走 PATH,避免被更靠前的全局 botmux 抢先)。
3947
+ try {
3948
+ console.log('→ restart daemon');
3949
+ runInCheckout(dir, process.execPath, [botmuxCliEntryAt(dir), 'restart']);
3950
+ console.log('\n✅ 本地更新完成,daemon 已从最新代码重启。');
3951
+ }
3952
+ catch (error) {
3953
+ console.error(`❌ 重启失败:${error instanceof Error ? error.message : error}`);
3954
+ process.exit(1);
3955
+ }
3956
+ }
3722
3957
  /**
3723
3958
  * Call one of the dashboard's loopback HMAC `/__cli/*` endpoints. Thin wrapper
3724
3959
  * over {@link callDashboard}, which handles 404 disambiguation and self-heals a
@@ -3792,10 +4027,10 @@ async function cmdDashboard(args) {
3792
4027
  }
3793
4028
  const { action, result: r } = execution;
3794
4029
  if (r.ok) {
3795
- // 首行保持纯 URL(脚本/复制取第一行即可);走中心化平台时再补一行本地直连兜底。
3796
- console.log(r.url);
3797
- if (r.localUrl)
3798
- console.log(`本地直连(平台异常时可用): ${r.localUrl}`);
4030
+ // 首行保持纯 URL(脚本/复制取第一行即可);随后依次是工作台直达入口、以及走
4031
+ // 中心化平台时的本地直连兜底。行顺序与首行契约见 formatDashboardSuccessLines。
4032
+ for (const line of formatDashboardSuccessLines(r))
4033
+ console.log(line);
3799
4034
  return;
3800
4035
  }
3801
4036
  const portFile = join(CONFIG_DIR, '.dashboard-port');
@@ -3967,6 +4202,7 @@ async function abandonSessionOffline(session) {
3967
4202
  delete latest.queuedActivationPending;
3968
4203
  delete latest.queuedActivationTail;
3969
4204
  delete latest.pendingRepoSetup;
4205
+ delete latest.previewTarget;
3970
4206
  applied = true;
3971
4207
  return true;
3972
4208
  });
@@ -3984,6 +4220,7 @@ function pruneSessionOfflineIfLedgerEmpty(session) {
3984
4220
  current.closedAt = new Date().toISOString();
3985
4221
  delete current.codexAppDispatchLedger;
3986
4222
  delete current.codexAppGenerationCommits;
4223
+ delete current.previewTarget;
3987
4224
  pruned = true;
3988
4225
  return true;
3989
4226
  });
@@ -4050,8 +4287,15 @@ async function abandonSessionAuthoritatively(session, online = listOnlineDaemons
4050
4287
  // stays fail-closed at the daemon's sessionCliIpcAuth check.
4051
4288
  const res = await postSessionCliIpc(ipcPort, session.sessionId, 'close', {});
4052
4289
  const body = await res.json().catch(() => ({}));
4053
- if (res.ok && body.ok)
4054
- return { ok: true, mode: 'daemon' };
4290
+ if (res.ok && body.ok) {
4291
+ // Shared parser: a body that DECLARES a residual must warn even when the
4292
+ // `residual` object is missing/malformed, or a bad payload silently
4293
+ // becomes an ordinary success again.
4294
+ const residual = parseCloseResidual(body);
4295
+ return residual
4296
+ ? { ok: true, mode: 'daemon', residual }
4297
+ : { ok: true, mode: 'daemon' };
4298
+ }
4055
4299
  // Surface the daemon's own rejection reason (e.g. origin_unproven) and
4056
4300
  // never fall back to a partial local kill: a fresh descriptor means the
4057
4301
  // daemon may still hold authoritative in-memory state.
@@ -4366,14 +4610,19 @@ function sessionBackingInfo(s, snapshot) {
4366
4610
  if (s.backendType === 'pty') {
4367
4611
  return { backendType: 'pty', probe: 'missing', label: 'pty' };
4368
4612
  }
4369
- if (s.backendType === 'riff') {
4370
- // Riff runs the CLI on a remote sandbox, not a local multiplexer pane: there
4371
- // is nothing to probe, attach to, or name as a PersistentBackendTarget
4372
- // (sessionPersistentTarget returns undefined for it, by design). Surface a
4373
- // stable label and report the nonexistent local backing as missing — exactly
4374
- // like pty — so it never slips into the legacy tmux branch and dereferences an
4375
- // undefined target.
4376
- return { backendType: 'riff', probe: 'missing', label: 'riff' };
4613
+ if (s.backendType === 'riff' || s.backendType === 'mojo') {
4614
+ // A remote backend (riff / mojo) runs its agent off-box, not in a local
4615
+ // multiplexer pane: there is nothing to probe, attach to, or name as a
4616
+ // PersistentBackendTarget (sessionPersistentTarget returns undefined for it, by
4617
+ // design). Surface a stable label and report the nonexistent local backing as
4618
+ // missing — exactly like pty — so it never slips into the legacy tmux branch
4619
+ // and dereferences an undefined target.
4620
+ //
4621
+ // Listed literally, not via isRemoteBackendType(): a boolean helper does not
4622
+ // narrow the union, and the exhaustiveness guard at the end of this function
4623
+ // depends on that narrowing. Adding a remote backend therefore fails to
4624
+ // compile here until it is listed — which is how mojo was caught.
4625
+ return { backendType: s.backendType, probe: 'missing', label: s.backendType };
4377
4626
  }
4378
4627
  if (s.backendType === undefined) {
4379
4628
  // Legacy rows predate backend stamping. Only tmux was externally attachable,
@@ -4417,6 +4666,35 @@ function hasRecoverableBackingSession(s, snapshot) {
4417
4666
  const target = sessionPersistentTarget(s);
4418
4667
  return !!target && backingProbe(snapshot, target) === 'exists';
4419
4668
  }
4669
+ async function requestDormantSessionWake(session, context) {
4670
+ const online = listOnlineDaemons();
4671
+ const daemon = session.larkAppId
4672
+ ? online.find(d => d.larkAppId === session.larkAppId)
4673
+ : online.length === 1 ? online[0] : undefined;
4674
+ if (!daemon) {
4675
+ return {
4676
+ ok: false,
4677
+ error: session.larkAppId || online.length === 0
4678
+ ? '所属 daemon 不在线,请先运行 botmux status'
4679
+ : '历史会话缺少 bot 归属,多 daemon 环境下无法安全唤醒',
4680
+ };
4681
+ }
4682
+ try {
4683
+ const path = `/api/sessions/${encodeURIComponent(session.sessionId)}/wake`;
4684
+ const response = await fetchDaemonIpc(daemon.ipcPort, path, {
4685
+ method: 'POST',
4686
+ signal: context.signal,
4687
+ headers: { [SESSION_WAKE_DEADLINE_HEADER]: String(context.deadlineMs) },
4688
+ });
4689
+ const body = await response.json().catch(() => ({}));
4690
+ if (response.ok && body?.ok)
4691
+ return { ok: true };
4692
+ return { ok: false, error: body?.error ?? `HTTP ${response.status}` };
4693
+ }
4694
+ catch (err) {
4695
+ return { ok: false, error: err?.message ?? String(err) };
4696
+ }
4697
+ }
4420
4698
  /** Shorten path for display: replace $HOME with ~. */
4421
4699
  function shortenPath(p) {
4422
4700
  const home = homedir();
@@ -4474,6 +4752,13 @@ function interactiveSessionPicker(active, probeSnapshot) {
4474
4752
  && backing.probe === 'exists'
4475
4753
  && !!('attachBackend' in backing && backing.attachBackend)
4476
4754
  && !!('target' in backing && backing.target),
4755
+ canWake: canWakeDormantBackendForAttach({
4756
+ isAdopt,
4757
+ probe: backing.probe,
4758
+ realManagedSession: isRealManagedSession(s),
4759
+ attachBackend: 'attachBackend' in backing ? backing.attachBackend : undefined,
4760
+ target: 'target' in backing ? backing.target : undefined,
4761
+ }),
4477
4762
  };
4478
4763
  });
4479
4764
  }
@@ -4621,6 +4906,9 @@ function interactiveSessionPicker(active, probeSnapshot) {
4621
4906
  else if (selected.canAttach) {
4622
4907
  footerLine = styledFooter('success', `${selected.attachBackend}: ${selected.backendTarget?.sessionName}`, width);
4623
4908
  }
4909
+ else if (selected.canWake) {
4910
+ footerLine = styledFooter('warn', `${selected.targetLabel}(Enter 恢复并连接)`, width);
4911
+ }
4624
4912
  else {
4625
4913
  footerLine = styledFooter('dim', `${selected.targetLabel}(不可连接)`, width);
4626
4914
  }
@@ -4638,7 +4926,8 @@ function interactiveSessionPicker(active, probeSnapshot) {
4638
4926
  process.stdout.write('\n');
4639
4927
  }
4640
4928
  // Keybinding hints
4641
- const fullHints = `↑/↓ 选择 ⏎ ${selected?.canAttach ? '连接' : '不可连接'} d 删除 q 退出`;
4929
+ const enterAction = selected?.canAttach ? '连接' : selected?.canWake ? '恢复' : '不可连接';
4930
+ const fullHints = `↑/↓ 选择 ⏎ ${enterAction} d 删除 q 退出`;
4642
4931
  const compactHints = '↑/↓ 选择 ⏎ d q';
4643
4932
  const hints = displayWidth(fullHints) <= width ? fullHints : compactHints;
4644
4933
  process.stdout.write(`\n${footerPrefix()}${styledFooter('dim', hints, width)}\n`);
@@ -4657,14 +4946,23 @@ function interactiveSessionPicker(active, probeSnapshot) {
4657
4946
  render();
4658
4947
  };
4659
4948
  process.stdout.on('resize', onResize);
4949
+ let deleteInFlight = false;
4950
+ let wakeInFlight = false;
4951
+ let wakeAbortController = null;
4952
+ let pickerClosed = false;
4660
4953
  function cleanup() {
4954
+ if (pickerClosed)
4955
+ return;
4956
+ pickerClosed = true;
4957
+ wakeAbortController?.abort();
4958
+ wakeAbortController = null;
4959
+ process.stdin.off('data', onInput);
4661
4960
  process.stdout.off('resize', onResize);
4662
4961
  process.stdin.setRawMode(false);
4663
4962
  process.stdin.pause();
4664
4963
  process.stdout.write('\x1b[?25h'); // show cursor
4665
4964
  process.stdout.write('\x1b[?1049l'); // leave alt screen
4666
4965
  }
4667
- let deleteInFlight = false;
4668
4966
  async function deleteSession(idx) {
4669
4967
  const r = rows[idx];
4670
4968
  const s = r.session;
@@ -4690,11 +4988,27 @@ function interactiveSessionPicker(active, probeSnapshot) {
4690
4988
  // abandonSessionAuthoritatively is `mode` (see its return type ~3573),
4691
4989
  // NOT master's `via` — that belongs to a different helper.
4692
4990
  flash = result.mode === 'daemon'
4693
- ? { style: 'success', text: `✓ 已删除 ${s.sessionId.substring(0, 8)}` }
4991
+ ? (result.residual
4992
+ // Local row closed, remote session still running: never the green
4993
+ // "deleted" text, or the operator walks away from a live agent that
4994
+ // still holds the injected credential.
4995
+ ? {
4996
+ style: 'warn',
4997
+ text: `⚠ 本地已删除 ${s.sessionId.substring(0, 8)},但${closeResidualClause(result.residual)}`,
4998
+ }
4999
+ : { style: 'success', text: `✓ 已删除 ${s.sessionId.substring(0, 8)}` })
4694
5000
  : { style: 'warn', text: `✓ 已离线删除 ${s.sessionId.substring(0, 8)}` };
4695
5001
  }
4696
- process.stdin.on('data', async (key) => {
4697
- if (deleteInFlight)
5002
+ async function onInput(key) {
5003
+ // Exit always wins, including while the wake HTTP or backend probe is
5004
+ // pending. cleanup aborts the shared deadline signal before restoring
5005
+ // the terminal, so Ctrl-C/q/Esc can never be swallowed by in-flight work.
5006
+ if (key === '\x03' || key === 'q' || key === '\x1b') {
5007
+ cleanup();
5008
+ resolve();
5009
+ return;
5010
+ }
5011
+ if (deleteInFlight || wakeInFlight)
4698
5012
  return;
4699
5013
  // Delete confirmation mode
4700
5014
  if (confirmDelete) {
@@ -4711,16 +5025,11 @@ function interactiveSessionPicker(active, probeSnapshot) {
4711
5025
  else {
4712
5026
  flash = { style: 'dim', text: '取消删除' };
4713
5027
  }
4714
- render();
5028
+ if (!pickerClosed)
5029
+ render();
4715
5030
  return;
4716
5031
  }
4717
5032
  flash = null;
4718
- // Ctrl-C or q or Esc
4719
- if (key === '\x03' || key === 'q' || key === '\x1b') {
4720
- cleanup();
4721
- resolve();
4722
- return;
4723
- }
4724
5033
  if (rows.length === 0) {
4725
5034
  // No sessions left, only q works
4726
5035
  render();
@@ -4752,11 +5061,40 @@ function interactiveSessionPicker(active, probeSnapshot) {
4752
5061
  render();
4753
5062
  return;
4754
5063
  }
4755
- if (!selected.canAttach) {
5064
+ if (!selected.canAttach && !selected.canWake) {
4756
5065
  flash = { style: 'warn', text: '该会话没有可连接的持久后端' };
4757
5066
  render();
4758
5067
  return;
4759
5068
  }
5069
+ if (selected.canWake) {
5070
+ const target = selected.backendTarget;
5071
+ if (!target) {
5072
+ flash = { style: 'error', text: '恢复目标缺失' };
5073
+ render();
5074
+ return;
5075
+ }
5076
+ wakeInFlight = true;
5077
+ const wakeController = new AbortController();
5078
+ wakeAbortController = wakeController;
5079
+ flash = { style: 'warn', text: `正在恢复 ${target.backendType}: ${persistentTargetDisplay(target)}…` };
5080
+ render();
5081
+ const recovered = await wakeDormantBackendForAttach({
5082
+ target,
5083
+ wake: context => requestDormantSessionWake(selected.session, context),
5084
+ probe: probePersistentBackendTarget,
5085
+ signal: wakeController.signal,
5086
+ });
5087
+ if (wakeAbortController === wakeController)
5088
+ wakeAbortController = null;
5089
+ wakeInFlight = false;
5090
+ if (pickerClosed)
5091
+ return;
5092
+ if (!recovered.ok) {
5093
+ flash = { style: 'error', text: `恢复失败: ${recovered.error}` };
5094
+ render();
5095
+ return;
5096
+ }
5097
+ }
4760
5098
  if (selected.attachBackend === 'zmx') {
4761
5099
  const target = selected.backendTarget;
4762
5100
  if (!target || target.backendType !== 'zmx') {
@@ -4795,7 +5133,8 @@ function interactiveSessionPicker(active, probeSnapshot) {
4795
5133
  resolve();
4796
5134
  return;
4797
5135
  }
4798
- });
5136
+ }
5137
+ process.stdin.on('data', onInput);
4799
5138
  });
4800
5139
  }
4801
5140
  /**
@@ -4974,6 +5313,7 @@ async function cmdDelete() {
4974
5313
  let closed = 0;
4975
5314
  let offline = 0;
4976
5315
  let failed = 0;
5316
+ let residual = 0;
4977
5317
  for (const s of toDelete) {
4978
5318
  // Explicit abandon boundary: route through the owning daemon so the ledger
4979
5319
  // FIFO is cleared atomically with close; offline fallback rereads the row
@@ -4990,9 +5330,16 @@ async function cmdDelete() {
4990
5330
  if (result.cleanedBacking)
4991
5331
  console.log(` killed ${result.cleanedBacking}`);
4992
5332
  }
5333
+ if (result.mode === 'daemon' && result.residual) {
5334
+ // Local row closed, remote session NOT cancelled. Reported per session AND
5335
+ // counted in the summary, so a bulk delete cannot bury it.
5336
+ residual++;
5337
+ console.warn(`⚠ ${s.sessionId.substring(0, 8)} ${s.title}:本地已关闭,但${closeResidualClause(result.residual)}`);
5338
+ continue;
5339
+ }
4993
5340
  console.log(`✓ ${s.sessionId.substring(0, 8)} ${s.title}${result.mode === 'offline' ? '(daemon 离线,本地收口)' : ''}`);
4994
5341
  }
4995
- console.log(`\n已关闭 ${closed} 个会话${offline ? `(${offline} 个离线收口)` : ''}${failed ? `,${failed} 个失败` : ''}`);
5342
+ console.log(`\n已关闭 ${closed} 个会话${offline ? `(${offline} 个离线收口)` : ''}${failed ? `,${failed} 个失败` : ''}${residual ? `,${residual} 个有残留需人工清理` : ''}`);
4996
5343
  if (failed > 0)
4997
5344
  process.exitCode = 1;
4998
5345
  }
@@ -5200,7 +5547,7 @@ async function cmdSuspend() {
5200
5547
  if (failed > 0)
5201
5548
  process.exitCode = 1;
5202
5549
  }
5203
- /** 会话级 CLI IPC(slash/cd/close)的 POST:与 postAsk 同款双路径——能读 host secret
5550
+ /** 会话级 CLI IPC(slash/cd/close/preview)的 POST:与 postAsk 同款双路径——能读 host secret
5204
5551
  * (非隔离进程)走 trusted-host HMAC 签名;读不到(沙箱 BOTMUX_SEND_RELAY /
5205
5552
  * macOS 读隔离 carve-out)改带本会话当前轮换的 origin capability,由 daemon
5206
5553
  * handler 与活跃记录比对。两条路都不读 bots.json。 */
@@ -5233,6 +5580,82 @@ async function postSessionCliIpc(ipcPort, sessionId, route, payload) {
5233
5580
  ? fetchDaemonIpc(ipcPort, path, init, hostSecret)
5234
5581
  : fetch(`http://127.0.0.1:${ipcPort}${path}`, init);
5235
5582
  }
5583
+ /** `botmux preview <port>` registers a reachable loopback Web service for the
5584
+ * exact current session. There is intentionally no `--session` escape hatch:
5585
+ * ancestry/env resolution plus the rotating session capability prove which
5586
+ * agent is volunteering the port. The daemon performs the authoritative port
5587
+ * validation/probe and persists the result. */
5588
+ async function cmdPreview(rest) {
5589
+ if (rest.length !== 1 || !/^\d+$/.test(rest[0])) {
5590
+ console.error('用法: botmux preview <port>');
5591
+ process.exit(1);
5592
+ }
5593
+ const port = Number(rest[0]);
5594
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
5595
+ console.error('✗ 端口必须是 1-65535 的整数');
5596
+ process.exit(1);
5597
+ }
5598
+ const ctx = findAncestorSessionContext();
5599
+ if (!ctx?.sessionId) {
5600
+ console.error('✗ 无法定位当前会话;preview 只能在 botmux 会话内注册');
5601
+ process.exit(1);
5602
+ }
5603
+ let discoveredPort;
5604
+ try {
5605
+ discoveredPort = findDaemon(process.env.BOTMUX_LARK_APP_ID)?.ipcPort;
5606
+ }
5607
+ catch {
5608
+ // Sandboxed/read-isolated sessions cannot enumerate daemon descriptors.
5609
+ }
5610
+ const ipcPort = resolveDaemonIpcPort(discoveredPort, process.env.BOTMUX_DAEMON_IPC_PORT);
5611
+ if (!ipcPort) {
5612
+ console.error('✗ 无法定位当前会话的 daemon;请确认 daemon 正在运行');
5613
+ process.exit(1);
5614
+ }
5615
+ let response;
5616
+ try {
5617
+ response = await postSessionCliIpc(ipcPort, ctx.sessionId, 'preview', { port });
5618
+ }
5619
+ catch {
5620
+ console.error('✗ 无法连接当前会话的 daemon');
5621
+ process.exit(1);
5622
+ }
5623
+ const body = await response.json().catch(() => ({}));
5624
+ if (response.ok && body.ok && typeof body.preview?.path === 'string') {
5625
+ console.log(`✓ Web 预览已注册: ${body.preview.path}`);
5626
+ return;
5627
+ }
5628
+ const error = body.error ?? `HTTP ${response.status}`;
5629
+ if (error === 'preview_unreachable') {
5630
+ console.error('✗ 端口不可达;请先让 Web 服务监听本机 loopback/0.0.0.0 后再注册');
5631
+ }
5632
+ else if (error === 'preview_owner_unverified') {
5633
+ // P1-12:端口上确实有人在监听,但证明不了那是本会话起的进程。宁可没有预览,
5634
+ // 也不把 Dashboard 用户代理进一个来路不明的本机服务。
5635
+ console.error('✗ 无法确认该端口由本会话的进程持有;请在会话内直接启动 Web 服务后再注册'
5636
+ + '(不要用 setsid/nohup 脱离进程树,也不要注册别的程序占用的端口)');
5637
+ }
5638
+ else if (error === 'preview_unsupported') {
5639
+ console.error('✗ 当前会话后端(远端 sandbox)的 Web 服务不在本机,Dashboard 预览不支持');
5640
+ }
5641
+ else if (error === 'preview_generation_changed') {
5642
+ console.error('✗ 注册期间会话已换代或已关闭;请在新一轮里重新注册');
5643
+ }
5644
+ else if (error === 'preview_target_changed') {
5645
+ // P1-3:并发注册(例如同时注册两个端口)时,后到的一方不再无声覆盖先落地的那个。
5646
+ console.error('✗ 注册期间本会话的预览目标已被另一次注册改写;同一时刻只能有一个预览目标,请确认后重试');
5647
+ }
5648
+ else if (error === 'remote_host_forbidden') {
5649
+ console.error('✗ 只允许注册本机 loopback 服务');
5650
+ }
5651
+ else if (error === 'origin_unproven' || error === 'managed_action_required') {
5652
+ console.error('✗ 当前会话归属校验失败');
5653
+ }
5654
+ else {
5655
+ console.error(`✗ 预览注册失败: ${error}`);
5656
+ }
5657
+ process.exit(1);
5658
+ }
5236
5659
  async function cmdChat(argv) {
5237
5660
  const sub = argv[0] ?? '';
5238
5661
  if (sub !== 'rename') {
@@ -6048,7 +6471,7 @@ botmux v${getVersion()} — IM ↔ AI 编程 CLI 桥接
6048
6471
  start 启动 daemon,并启动 mode=auto 的插件 service
6049
6472
  stop 停止 daemon(默认不停止插件 service;--with-plugin 显式停止 mode=auto 的插件 service)
6050
6473
  restart 重启 daemon(默认不停止插件 service,core 启动后确保 mode=auto 正在运行;--with-plugin 显式先停再启动 auto service)
6051
- --include-pm2 仅允许“入场时没有 live PM2 God”的干净启动;若已有 live God,整条命令会在 fleet/breadcrumb 零改动处拒绝,且不会信号或重启现存 God
6474
+ --include-pm2 fleet 安全退役并验证后,经 PM2_HOME socket 退役 PM2 God(绝不按 PID 发信号),再以当前干净环境全新启动——彻底重启整棵进程树;会先优雅停止全部插件 service(auto 的启动后自动恢复)
6052
6475
  首次升级若旧 daemon 缺少 shutdown protocol:先独立确认所有 Session/Riff 工作均 idle,再一次性运行
6053
6476
  botmux restart --bootstrap-shutdown-protocol --yes;普通 stop/restart 仍保持 fail-closed
6054
6477
  logs 查看 daemon 日志(--lines N, --bot <0-based-index|pm2-name|appId>)
@@ -6060,6 +6483,9 @@ botmux v${getVersion()} — IM ↔ AI 编程 CLI 桥接
6060
6483
  显式轮换 token,并打印新的登录 URL
6061
6484
  device enroll|status|logout
6062
6485
  在宿主终端注册、查看或清除 desktop device 凭证(AI CLI 会话内拒绝)
6486
+ mojo-containment list|revoke
6487
+ 查看 / 显式撤销无法自证静止的 mojo containment handle(设备隔离
6488
+ blocker 的可审计操作员出口;revoke 需 --yes,存活证据需 --force)
6063
6489
  list 列出活跃会话(交互式选择并连接 tmux)
6064
6490
  --plain 纯文本表格输出(管道/脚本场景)
6065
6491
  delete <id> 关闭指定会话(支持 ID 前缀匹配)
@@ -6077,6 +6503,10 @@ botmux v${getVersion()} — IM ↔ AI 编程 CLI 桥接
6077
6503
  term-link [id] 获取活跃会话的「可操作终端」(带写 token)。不回显链接,改由
6078
6504
  daemon 把可操作卡片私密发给 owner(群内仅你可见,话题/单聊回退 DM)。
6079
6505
  单个活跃会话可省略 id
6506
+ preview <port> (会话内)注册当前会话已启动的本机 Web 服务;Dashboard 登录后通过
6507
+ 同源 /preview/<sessionId>/ 访问,不暴露本机地址或任何 token。
6508
+ 端口必须由本会话的进程持有(在会话内直接启动,别 setsid/nohup
6509
+ 脱离进程树);换代/关闭后需重新注册,远端 sandbox 后端不支持
6080
6510
  autostart enable 注册开机自启(macOS launchd / Linux user systemd / Windows Task Scheduler,无需 sudo)
6081
6511
  autostart disable 注销开机自启
6082
6512
  autostart status 查看自启状态
@@ -7177,7 +7607,7 @@ import { resolveFeedbackPolicyForDelivery, resolveFeedbackTeamId } from './servi
7177
7607
  import { normalizeFeedbackPolicy } from './services/feedback-policy.js';
7178
7608
  import { applyInlineMentions } from './im/lark/inline-mentions.js';
7179
7609
  import { renderBrandTemplate } from './im/lark/brand-template.js';
7180
- import { resolveBrandLabel, resolveUsageDisplay } from './bot-registry.js';
7610
+ import { effectiveDefaultWorkingDir, loadBotConfigs, resolveBrandLabel, resolveUsageDisplay } from './bot-registry.js';
7181
7611
  import { config } from './config.js';
7182
7612
  import { getSessionUsageSnapshot } from './core/cost-calculator.js';
7183
7613
  import { resolveQuoteTarget, validateMentionDecision, mentionBackAmbiguity, mentionBackAmbiguityError, parseAttentionFlag, attentionUsageError, managedVcQuoteError, managedVcCustomCardError, managedVcSendControlError, managedVcSendPayloadError, containsLarkAtTag, } from './services/send-policy.js';
@@ -7230,6 +7660,7 @@ async function relaySend(rest, relayDir) {
7230
7660
  const pos = positionals(rest, ['--card', '--text', '--top-level', '--no-quote', '--mention-back', '--no-mention', '--anyway', '--voice', '--slash']);
7231
7661
  content = pos.length > 0 ? pos.join(' ') : await readStdin();
7232
7662
  }
7663
+ content = stripTrailingOaiMemoryCitation(content);
7233
7664
  const preparedCardContent = cardJsonArg === undefined && cardFile === undefined && !rest.includes('--voice')
7234
7665
  ? prepareCardMarkdown(extractCardText(content), process.cwd(), 'filesystem')
7235
7666
  : undefined;
@@ -8301,6 +8732,9 @@ async function cmdSend(rest) {
8301
8732
  content = await readStdin();
8302
8733
  }
8303
8734
  }
8735
+ // Keep memory attribution in the model's rollout, but never render the
8736
+ // complete internal suffix into Lark or count it in send markers.
8737
+ content = stripTrailingOaiMemoryCitation(content);
8304
8738
  if (!contentFile && !customCardRequested)
8305
8739
  rejectLikelyWindowsStdinMojibake(content);
8306
8740
  const managedPayloadError = managedVcSendPayloadError({
@@ -8991,8 +9425,8 @@ async function cmdSend(rest) {
8991
9425
  };
8992
9426
  try {
8993
9427
  // A file-sandbox relay supplies a host-private copy normalized inside the
8994
- // sandbox namespace. Voice/doc-comment paths returned above and therefore
8995
- // continue using the untouched raw content.
9428
+ // sandbox namespace. Voice/doc-comment paths returned above use the same
9429
+ // already-sanitized outbound content, without card-markdown preparation.
8996
9430
  let text = extractCardText(content);
8997
9431
  const preparedContentFile = process.env.BOTMUX_CARD_PREPARED_CONTENT_FILE;
8998
9432
  if (preparedContentFile) {
@@ -9939,6 +10373,7 @@ async function cmdReport(rest) {
9939
10373
  const pos = positionals(rest, ['--top-level', '--legacy-dispatch']);
9940
10374
  content = pos.length ? pos.join(' ') : await readStdin();
9941
10375
  }
10376
+ content = stripTrailingOaiMemoryCitation(content);
9942
10377
  if (!contentFile)
9943
10378
  rejectLikelyWindowsStdinMojibake(content);
9944
10379
  if (!content.trim()) {
@@ -10307,6 +10742,16 @@ botmux create-group — 用一组机器人新建飞书群
10307
10742
  --json-status 可选;在 chatId 后追加一行结构化完成状态。默认 stdout 无论完整成功或
10308
10743
  部分失败都保持历史兼容,只输出单行 chatId;部分失败仍以非零退出表示。
10309
10744
 
10745
+ 团队模式(跨机建聚焦新群,走中心化平台):
10746
+ botmux create-group --team <teamId> --agent <appId> [--agent ...] [--name "群名"]
10747
+
10748
+ 用途:把「同团队、已 opt-in」的**别人机器上的** agent(用 bots list --scope team 发现到的 appId)
10749
+ 和它们各自的 owner 一起拉进一个平台代建的聚焦新群,全程 machine-auth。
10750
+ 正因为发起人在别人 bot 进群前 @不到它,这条只认 appId、不依赖任何飞书 @,天然绕开视角问题。
10751
+ --agent 至少一个、可多次、按 appId 去重。团队模式忽略 --bot/--kickoff/--working-dir(那些是本机建群用的)。
10752
+ 未传 --team:本机唯一团队则自动用它,多个要求显式指定。
10753
+ (往**已存在**的团队群补人是独立命令:botmux bots invite --chat <chatId> --team X --agent ...)
10754
+
10310
10755
  行为:
10311
10756
  - 第一个解析到的 bot 作为 creator(决定建群身份 + 初始群主 + open_id app scope)。
10312
10757
  - 邀请用户 / 转让群主 / @通知 对象都从 creator 的 resolvedAllowedUsers 取首个 open_id(email 自动转换;
@@ -10325,6 +10770,21 @@ botmux create-group — 用一组机器人新建飞书群
10325
10770
  `);
10326
10771
  return;
10327
10772
  }
10773
+ // Deprecation 闸:--chat 曾经是 create-group 的「往已有群补人」模式,现已拆成独立命令
10774
+ // `bots invite`。若仍传 --chat,必须**报错早退**而不是静默忽略——否则用户本意补人、
10775
+ // 却因 --chat 被丢弃照常建了个新群(真实副作用,多一个群)。放在 team 分流之前拦。
10776
+ if (hasFlagOrEq(rest, '--chat')) {
10777
+ console.error('create-group 不再支持 --chat(往已有群补人已拆成独立命令)。请改用:');
10778
+ console.error(' botmux bots invite --chat <chatId> --team <id> --agent <appId>...');
10779
+ process.exit(1);
10780
+ }
10781
+ // 团队模式:走平台端点3 建新群,与本机飞书建群是两条完全不同的路径。必须在 transport
10782
+ // 闸门之前分流——平台代建群,本机 bot 不需要飞书传输身份(沙盒/apiOnly 也能发起)。
10783
+ // 「往已有群补人」是独立的 `bots invite`,不在这里。
10784
+ if (hasFlagOrEq(rest, '--team') || hasFlagOrEq(rest, '--agent')) {
10785
+ await cmdCreateGroupTeam(rest);
10786
+ return;
10787
+ }
10328
10788
  // create-group builds a real Feishu group (cross-bot). A no-transport turn
10329
10789
  // (apiOnly bot or HTTP virtual session) may not originate one — central gate.
10330
10790
  assertTurnTransportOrExit('create-group');
@@ -10535,6 +10995,110 @@ botmux create-group — 用一组机器人新建飞书群
10535
10995
  if (!completion.success)
10536
10996
  process.exitCode = 1;
10537
10997
  }
10998
+ /**
10999
+ * `botmux create-group --team <teamId> --agent <appId>...` — 跨机拉群(走中心化平台端点3)。
11000
+ *
11001
+ * 与本机飞书建群(cmdCreateGroup 主体)是两条不同路径:这里把「同团队、已 opt-in」的、
11002
+ * **别人机器上的** agent(appId 从 bots list --scope team 发现而来)+ 各自 owner + 本机 owner
11003
+ * 一起拉进一个平台代建的聚焦新群。全程 machine-auth、只认 appId、不依赖任何飞书 @。
11004
+ *
11005
+ * 硬约束:CLI 零授权判断(团队成员校验 + opt-in 闸 team.bots 全在平台);平台把未 opt-in /
11006
+ * 拉不动的对象放进 invalidBotIds / invalidOwnerUnionIds 原样带回,这里只如实展示、不解释成
11007
+ * "失败"(未 opt-in 是对方 owner 没加进团队,不是本次调用错)。
11008
+ *
11009
+ * 输出协议对齐主命令:成功拿到 chatId → stdout 写单行 chatId(skill 友好);--json-status
11010
+ * 追加一行结构化 JSON(含 shareLink / invalid* / 部分失败标记)。有 invalid* 时 exit 非零,
11011
+ * 但 stdout 的 chatId 已经可用(群已建,别重建)。
11012
+ */
11013
+ /**
11014
+ * 解析团队维度命令的目标 teamId:显式 --team 优先;否则打端点1,唯一即用、多个要求指定、
11015
+ * 零个报错。用于「必须落到一个具体 team」的动作(建群 / 补人)——list 侧的空 team 是正常态、
11016
+ * 输出空列表,不走这里。`action` 只用于文案("拉群"/"补人")。
11017
+ */
11018
+ async function resolveSingleTeamIdOrExit(fetchTeams, describeTeamAgentsFailure, teamArg, action) {
11019
+ if (teamArg)
11020
+ return teamArg;
11021
+ const teamsRes = await fetchTeams();
11022
+ if (!teamsRes.ok) {
11023
+ if (teamsRes.reason === 'unbound')
11024
+ console.error(`本机未绑定平台。${action}需要先 botmux bind。`);
11025
+ else if (teamsRes.reason === 'not_deployed')
11026
+ console.error('平台尚未部署团队端点,等上线后重试。');
11027
+ else
11028
+ console.error(`拉取团队列表失败:${describeTeamAgentsFailure(teamsRes)}`);
11029
+ process.exit(1);
11030
+ }
11031
+ const teams = teamsRes.value;
11032
+ if (teams.length === 0) {
11033
+ console.error(`本机未加入任何平台团队,无法${action}。`);
11034
+ process.exit(1);
11035
+ }
11036
+ if (teams.length > 1) {
11037
+ console.error('本机属于多个平台团队,请用 --team <id> 指定其一:');
11038
+ for (const t of teams)
11039
+ console.error(` ${t.teamId} ${t.teamName}`);
11040
+ process.exit(1);
11041
+ }
11042
+ return teams[0].teamId;
11043
+ }
11044
+ async function cmdCreateGroupTeam(rest) {
11045
+ const { fetchTeams, createTeamGroup, describeTeamAgentsFailure, rateLimitRetryHint } = await import('./platform/team-agents-client.js');
11046
+ const jsonStatus = rest.includes('--json-status');
11047
+ const name = argValue(rest, '--name');
11048
+ const appIds = [...new Set(argValues(rest, '--agent').map(s => s.trim()).filter(Boolean))];
11049
+ if (appIds.length === 0) {
11050
+ console.error('团队模式需要至少一个 --agent <appId>(用 botmux bots list --scope team 发现 appId)。');
11051
+ process.exit(1);
11052
+ }
11053
+ const teamId = await resolveSingleTeamIdOrExit(fetchTeams, describeTeamAgentsFailure, argValue(rest, '--team'), '拉群');
11054
+ const res = await createTeamGroup({ teamId, appIds, ...(name !== undefined ? { name } : {}) });
11055
+ if (!res.ok) {
11056
+ if (res.reason === 'unbound')
11057
+ console.error('本机未绑定平台。拉群需要先 botmux bind。');
11058
+ else if (res.reason === 'not_deployed')
11059
+ console.error('平台尚未部署拉群端点(/v1/machine/groups)。等平台上线后重试。');
11060
+ else if (res.reason === 'rate_limited')
11061
+ console.error(`拉群被限流,${rateLimitRetryHint(res)}。`);
11062
+ else if (res.reason === 'client' && res.status === 403) {
11063
+ // 403 not_in_team_bots:所选 agent(或本机 bot)没 opt-in 进团队。平台可能带回具体 appIds。
11064
+ const which = res.appIds && res.appIds.length ? `:${res.appIds.join('、')}` : '';
11065
+ console.error(`拉群被拒(403 ${res.error}):请确认相关 bot 已由其 owner 在平台「管理机器人」加入该团队${which}。`);
11066
+ }
11067
+ else if (res.reason === 'client' && res.status === 404) {
11068
+ console.error(`团队不存在或本机 owner 不是其成员:teamId=${teamId}`);
11069
+ }
11070
+ else {
11071
+ console.error(`拉群失败:${describeTeamAgentsFailure(res)}`);
11072
+ }
11073
+ process.exit(1);
11074
+ }
11075
+ const { chatId, shareLink, invalidBotIds, invalidOwnerUnionIds } = res.value;
11076
+ if (!chatId) {
11077
+ // 平台回 ok 但没给 chatId:异常,别静默成功。
11078
+ console.error(`平台未返回 chatId(ok=${res.value.ok})。`);
11079
+ if (jsonStatus)
11080
+ console.log(JSON.stringify({ ...res.value, ok: false }));
11081
+ process.exit(1);
11082
+ }
11083
+ // stdout 单行 chatId(skill 友好,主命令同款协议)。
11084
+ console.log(chatId);
11085
+ const hasInvalid = invalidBotIds.length > 0 || invalidOwnerUnionIds.length > 0;
11086
+ if (jsonStatus) {
11087
+ console.log(JSON.stringify({ ok: true, chatId, shareLink, invalidBotIds, invalidOwnerUnionIds, teamId }));
11088
+ }
11089
+ // 未 opt-in / 拉不动的对象走 stderr 提示(不污染 stdout 的 chatId 契约)。
11090
+ if (invalidBotIds.length > 0) {
11091
+ console.error(`⚠️ 以下 agent 未加入团队或拉取失败,未入群:${invalidBotIds.join('、')}`);
11092
+ }
11093
+ if (invalidOwnerUnionIds.length > 0) {
11094
+ console.error(`⚠️ 以下 owner 未能拉入:${invalidOwnerUnionIds.join('、')}`);
11095
+ }
11096
+ if (shareLink)
11097
+ console.error(`群分享链接:${shareLink}`);
11098
+ // 部分失败 → 非零退出(但群已建、chatId 已在 stdout,调用方应复用不重建)。
11099
+ if (hasInvalid)
11100
+ process.exitCode = 1;
11101
+ }
10538
11102
  // ─── Bots subcommand ─────────────────────────────────────────────────────────
10539
11103
  // ─── botmux ask v0.1.7 ───────────────────────────────────────────────────────
10540
11104
  //
@@ -11179,8 +11743,28 @@ async function cmdUserPromptHook() {
11179
11743
  }
11180
11744
  async function cmdBots(sub, rest) {
11181
11745
  process.env.SESSION_DATA_DIR ??= resolveDataDir();
11746
+ // `bots invite`:往**已存在的团队群**补人(同 team、已 opt-in 的 agent + 各自 owner),
11747
+ // 打平台端点4(machine-auth)。独立子命令——与「建新群」的 create-group 语义分开,
11748
+ // 不再靠 create-group --chat 区分(那个入口易被误读成"建群")。在会话/transport 闸门前分流。
11749
+ if (sub === 'invite') {
11750
+ await cmdBotsInvite(rest);
11751
+ return;
11752
+ }
11182
11753
  if (sub !== 'list' && sub !== 'ls' && sub !== '') {
11183
- console.error('用法: botmux bots list [--session-id ID]');
11754
+ console.error('用法: botmux bots list [--scope chat|team] [--team <id>] [--session-id ID]');
11755
+ console.error(' botmux bots invite --chat <chatId> --team <id> --agent <appId>...');
11756
+ process.exit(1);
11757
+ }
11758
+ // `--scope team`:跨机发现同团队、已 opt-in 的 agent(打平台端点2,machine-auth)。
11759
+ // 与默认的 chat scope 完全不同——它不读飞书群花名册、不需要会话/传输身份,
11760
+ // 所以在会话解析 / transport 闸门之前分流。空 team 列表是正常态(没别人 opt-in)。
11761
+ const scope = (argValue(rest, '--scope') ?? 'chat').toLowerCase();
11762
+ if (scope === 'team') {
11763
+ await cmdBotsListTeam(rest);
11764
+ return;
11765
+ }
11766
+ if (scope !== 'chat') {
11767
+ console.error(`未知 --scope "${scope}"(仅支持 chat | team)`);
11184
11768
  process.exit(1);
11185
11769
  }
11186
11770
  // `bots list` reads the Feishu chat roster (listChatBotMembers). A no-transport
@@ -11208,6 +11792,26 @@ async function cmdBots(sub, rest) {
11208
11792
  botEntries = JSON.parse(readFileSync(botInfoPath, 'utf-8'));
11209
11793
  }
11210
11794
  catch { /* */ }
11795
+ const collaborationFactsFor = (appIds) => {
11796
+ const facts = Object.create(null);
11797
+ const wanted = new Set(appIds.filter(Boolean));
11798
+ try {
11799
+ for (const cfg of loadBotConfigs()) {
11800
+ if (!wanted.has(cfg.larkAppId))
11801
+ continue;
11802
+ facts[cfg.larkAppId] = {
11803
+ workspaceSource: cfg.oncallChats?.some(c => c.chatId === s.chatId)
11804
+ ? 'oncall'
11805
+ : effectiveDefaultWorkingDir(cfg) ? 'default' : 'unknown',
11806
+ mentionMode: cfg.regularGroupMentionMode ?? 'always',
11807
+ replyMode: cfg.chatReplyModes?.[s.chatId] ?? cfg.regularGroupReplyMode ?? 'chat-topic',
11808
+ transport: cfg.apiOnly !== true,
11809
+ };
11810
+ }
11811
+ }
11812
+ catch { /* config unreadable under isolation: all facts stay unknown */ }
11813
+ return facts;
11814
+ };
11211
11815
  try {
11212
11816
  const { listChatBotMembers } = await import('./im/lark/client.js');
11213
11817
  const chatBots = await listChatBotMembers(appId, s.chatId);
@@ -11215,15 +11819,220 @@ async function cmdBots(sub, rest) {
11215
11819
  // botmux daemon on this host). 'introduce' = discovered via /introduce
11216
11820
  // collaboration command (external bot, possibly other-tenant). isSelf is
11217
11821
  // retained (not filtered) so the model can still identify itself when needed.
11218
- const result = formatChatBotsForCli(chatBots, appId);
11219
- console.log(JSON.stringify({ sessionId: sid, chatId: s.chatId, bots: result, total: result.length }, null, 2));
11822
+ const result = formatChatBotsForCli(chatBots, appId, collaborationFactsFor(chatBots.map(bot => bot.larkAppId)));
11823
+ console.log(JSON.stringify({ sessionId: sid, chatId: s.chatId, bots: result, total: result.length, collaborationHelp }, null, 2));
11220
11824
  }
11221
11825
  catch (err) {
11222
11826
  // Fallback to bots-info.json
11223
- const result = formatBotInfoEntriesForCli(botEntries, appId);
11224
- console.log(JSON.stringify({ sessionId: sid, bots: result, total: result.length, note: `chat query failed: ${err.message}` }, null, 2));
11827
+ const result = formatBotInfoEntriesForCli(botEntries, appId, collaborationFactsFor(botEntries.map(bot => bot.larkAppId)));
11828
+ console.log(JSON.stringify({ sessionId: sid, bots: result, total: result.length, collaborationHelp, note: `chat query failed: ${err.message}` }, null, 2));
11225
11829
  }
11226
11830
  }
11831
+ /**
11832
+ * `botmux bots list --scope team [--team <id>]` — 跨机发现同团队、已 opt-in(owner
11833
+ * 加进 team.bots)的 agent,打平台端点2(machine-auth,Bearer=machineToken)。
11834
+ *
11835
+ * 与 chat scope 的关键区别 + 硬约束:
11836
+ * - 走 machine-auth,不读飞书群花名册、不需要传输身份 → 沙盒/apiOnly bot 也能查。
11837
+ * - **CLI 不做任何授权判断**:团队成员校验 + opt-in 闸全在平台,这里只透传、只展示。
11838
+ * - specialties / mentionable 是 agent 自报 → 仅展示,不当可信凭据。
11839
+ * - 未传 --team:先打端点1 拉本机 owner 的团队;唯一就用它,多个要求显式 --team
11840
+ * (不猜),零个则提示本机没加入任何平台团队。
11841
+ */
11842
+ async function cmdBotsListTeam(rest) {
11843
+ const { fetchTeamAgents, fetchTeams, describeTeamAgentsFailure } = await import('./platform/team-agents-client.js');
11844
+ const jsonOut = argFlag(rest, '--json');
11845
+ let teamId = argValue(rest, '--team');
11846
+ let teamNameHint;
11847
+ if (!teamId) {
11848
+ const teamsRes = await fetchTeams();
11849
+ if (!teamsRes.ok) {
11850
+ if (teamsRes.reason === 'unbound') {
11851
+ console.error('本机未绑定平台(~/.botmux/platform.json 缺失)。团队发现需要先 botmux bind。');
11852
+ }
11853
+ else if (teamsRes.reason === 'not_deployed') {
11854
+ console.error('平台尚未部署团队端点(/v1/machine/teams)。等平台上线后重试。');
11855
+ }
11856
+ else {
11857
+ console.error(`拉取团队列表失败:${describeTeamAgentsFailure(teamsRes)}`);
11858
+ }
11859
+ process.exit(1);
11860
+ }
11861
+ const teams = teamsRes.value;
11862
+ if (teams.length === 0) {
11863
+ // 空不是错误:本机 owner 还没加入任何平台团队。
11864
+ console.log(JSON.stringify({ scope: 'team', teams: [], agents: [], total: 0, note: '本机未加入任何平台团队' }, null, 2));
11865
+ return;
11866
+ }
11867
+ if (teams.length > 1) {
11868
+ console.error('本机属于多个平台团队,请用 --team <id> 指定其一:');
11869
+ for (const t of teams)
11870
+ console.error(` ${t.teamId} ${t.teamName}`);
11871
+ process.exit(1);
11872
+ }
11873
+ teamId = teams[0].teamId;
11874
+ teamNameHint = teams[0].teamName;
11875
+ }
11876
+ const res = await fetchTeamAgents(teamId);
11877
+ if (!res.ok) {
11878
+ if (res.reason === 'unbound') {
11879
+ console.error('本机未绑定平台(~/.botmux/platform.json 缺失)。团队发现需要先 botmux bind。');
11880
+ }
11881
+ else if (res.reason === 'not_deployed') {
11882
+ console.error('平台尚未部署团队 agent 发现端点(/v1/machine/agents)。等平台上线后重试。');
11883
+ }
11884
+ else if (res.reason === 'client' && res.status === 404) {
11885
+ console.error(`团队不存在或本机 owner 不是其成员:teamId=${teamId}`);
11886
+ }
11887
+ else {
11888
+ console.error(`拉取团队 agent 失败:${describeTeamAgentsFailure(res)}`);
11889
+ }
11890
+ process.exit(1);
11891
+ }
11892
+ const { teamName, agents } = res.value;
11893
+ // 输出对齐 chat scope 的 JSON 形状(scope 标注 + total),字段是平台端点2 的原样透传。
11894
+ // 提醒:发现列表只含**已加入 team.bots** 的 agent(owner 在平台「管理机器人」显式加入)。
11895
+ console.log(JSON.stringify({
11896
+ scope: 'team',
11897
+ teamId,
11898
+ teamName: teamName || teamNameHint || teamId,
11899
+ agents,
11900
+ total: agents.length,
11901
+ note: '发现列表只含已加入团队(owner 在平台「管理机器人」opt-in)的 agent;specialties/mentionable 为 agent 自报,仅供参考不可信。',
11902
+ }, null, jsonOut ? 0 : 2));
11903
+ }
11904
+ /**
11905
+ * (a) 自动化辅助:把平台后端 app(platformAppId)拉进目标群 `chatId`。
11906
+ *
11907
+ * 飞书约束:加应用的 app 自己得在群里。所以找一个**已在该群、且本机可用凭据**的 bot 当代理
11908
+ * (addBotToChat proxy)。优先用本会话 bot(它天然在本群且已注册),否则遍历 bots.json 里其它
11909
+ * 非 apiOnly bot、逐个 isInChat 命中即用。全都不在群/加失败(无 scope、群需群主审批)→ 返回失败,
11910
+ * 由调用方回退到手动引导。
11911
+ */
11912
+ async function tryAutoAddPlatformBot(chatId, platformAppId) {
11913
+ try {
11914
+ const { findLocalBotInChat } = await import('./im/lark/client.js');
11915
+ const { addBotToChat } = await import('./services/groups-store.js');
11916
+ // 用安静探测客户端找一个「已在目标群」的本机 bot 当代理(本会话 bot 优先)。
11917
+ // 不在群的 bot 探测 miss 被静音,不刷 axios 400 噪音(见 client.findLocalBotInChat)。
11918
+ const proxy = await findLocalBotInChat(chatId, process.env.BOTMUX_LARK_APP_ID);
11919
+ if (!proxy)
11920
+ return { ok: false, reason: '本机没有已在该群的 bot 可当代理' };
11921
+ // 代理 bot 需已在注册表(getBotClient 可用)——从 bots.json 注册一次(幂等)。
11922
+ try {
11923
+ const { loadBotConfigs, registerBot } = await import('./bot-registry.js');
11924
+ const cfg = loadBotConfigs().find((c) => c.larkAppId === proxy.larkAppId);
11925
+ if (cfg)
11926
+ registerBot(cfg);
11927
+ }
11928
+ catch { /* 已注册或读配置失败 → addBotToChat 若拿不到 client 会报错并回退 */ }
11929
+ const r = await addBotToChat(proxy.larkAppId, chatId, [platformAppId]);
11930
+ const one = r[0];
11931
+ if (one?.ok)
11932
+ return { ok: true, proxyName: proxy.cliId ? `${proxy.cliId}/${proxy.larkAppId}` : proxy.larkAppId };
11933
+ // 加失败(无 scope / 群需群主审批 / invalid)→ 回退手动引导。
11934
+ return { ok: false, reason: `代理 ${proxy.larkAppId} 添加失败:${one?.error ?? 'unknown'}` };
11935
+ }
11936
+ catch (e) {
11937
+ return { ok: false, reason: String(e?.message ?? e) };
11938
+ }
11939
+ }
11940
+ /**
11941
+ * `botmux bots invite --chat <chatId> --team <id> --agent <appId>...`
11942
+ * —— 往**已存在的团队群**补人(同 team、已 opt-in 的 agent + 各自 owner),打平台端点4。
11943
+ *
11944
+ * 独立于 create-group(建新群):语义是「群已经在了,往里加人」,不该跟建群混在一个命令里。
11945
+ * 全程 machine-auth、只认 appId(发起人在别人 bot 进群前 @不到它,靠 appId 绕开视角问题)。
11946
+ * 授权/opt-in 闸/大厅排除全在平台,CLI 零判断、只如实展示平台判定。
11947
+ * 若平台回 platform_bot_not_in_chat 且带 platformAppId → 自动用群内本机 bot 把平台 app 拉进群、重试。
11948
+ *
11949
+ * 输出:stdout 单行 chatId(skill 友好,与 create-group 一致);--json-status 追加
11950
+ * {ok, chatId, invalidBotIds, invalidOwnerUnionIds, teamId};invalid* 非空 → 非零退出。
11951
+ */
11952
+ async function cmdBotsInvite(rest) {
11953
+ const { addTeamGroupMembers, fetchTeams, describeTeamAgentsFailure, rateLimitRetryHint, shouldTryAutoAddPlatformBot } = await import('./platform/team-agents-client.js');
11954
+ const jsonStatus = rest.includes('--json-status');
11955
+ const chatArg = (argValue(rest, '--chat') ?? '').trim();
11956
+ const appIds = [...new Set(argValues(rest, '--agent').map(s => s.trim()).filter(Boolean))];
11957
+ if (!chatArg) {
11958
+ console.error('用法: botmux bots invite --chat <chatId> --team <id> --agent <appId>...');
11959
+ console.error('--chat 必填:目标群 chatId(须已有平台机器人 BotmuxPlatform 在场,且你本人已在该群,非机器人大厅)。');
11960
+ console.error('补人恒把 agent + 各自 owner 一起拉进群。');
11961
+ process.exit(1);
11962
+ }
11963
+ if (appIds.length === 0) {
11964
+ console.error('至少一个 --agent <appId>(用 botmux bots list --scope team 发现 appId)。');
11965
+ process.exit(1);
11966
+ }
11967
+ const teamId = await resolveSingleTeamIdOrExit(fetchTeams, describeTeamAgentsFailure, argValue(rest, '--team'), '补人');
11968
+ let res = await addTeamGroupMembers({ chatId: chatArg, teamId, appIds });
11969
+ // (a) 自动化:撞 platform_bot_not_in_chat 且平台回传了 platformAppId → 用群里已有的本机 bot
11970
+ // 当代理,把平台后端 app 拉进目标群,再自动重试一次补人。让「往任意我在的群补人」尽量无感;
11971
+ // 碰到「加 bot 需群主审批 / 代理 bot 无成员管理 scope」时 addBotToChat 会失败,落回下面的手动提示。
11972
+ let autoAddAttempted = false;
11973
+ if (shouldTryAutoAddPlatformBot(res)) {
11974
+ const added = await tryAutoAddPlatformBot(chatArg, res.platformAppId);
11975
+ if (added.ok) {
11976
+ autoAddAttempted = true;
11977
+ console.error(`(已用群内 bot「${added.proxyName}」把平台应用拉进群,重试补人…)`);
11978
+ res = await addTeamGroupMembers({ chatId: chatArg, teamId, appIds });
11979
+ }
11980
+ else {
11981
+ // 自动拉失败:给可操作的手动引导(用平台回传的 app 名/id),不静默。
11982
+ const appLabel = res.platformAppName ? `${res.platformAppName}(${res.platformAppId})` : res.platformAppId;
11983
+ console.error(`补人前置:平台应用不在目标群,且自动添加未成功(${added.reason})。`);
11984
+ console.error(`请在群设置里手动添加应用:${appLabel},然后重试 botmux bots invite。`);
11985
+ process.exit(1);
11986
+ }
11987
+ }
11988
+ if (!res.ok) {
11989
+ if (res.reason === 'unbound')
11990
+ console.error('本机未绑定平台。补人需要先 botmux bind。');
11991
+ else if (res.reason === 'not_deployed')
11992
+ console.error('平台尚未部署补人端点(/v1/machine/groups/:chatId/members)。等平台上线后重试。');
11993
+ else if (res.reason === 'rate_limited')
11994
+ console.error(`补人被限流,${rateLimitRetryHint(res)}。`);
11995
+ else if (res.reason === 'client' && res.status === 403) {
11996
+ // 403 分支(平台按 error code 返回):可行性/归属/opt-in/大厅,各给可操作的提示。
11997
+ const which = res.appIds && res.appIds.length ? `(${res.appIds.join('、')})` : '';
11998
+ const appLabel = res.platformAppName ? `${res.platformAppName}(${res.platformAppId ?? ''})` : (res.platformAppId ?? '平台应用');
11999
+ const hint = res.error === 'platform_bot_not_in_chat'
12000
+ // 已自动拉过还撞它:飞书加 app 通常同步生效,多半是刚加完成员表还没刷新——提示稍等重试,
12001
+ // 别让用户对着刚拉进去的 app 再手动加一遍(pi review nit)。没自动拉过才引导手动添加。
12002
+ ? (autoAddAttempted
12003
+ ? `平台应用已自动拉进群、但补人仍报它不在——飞书成员同步通常几秒内完成,请稍等片刻重试 botmux bots invite`
12004
+ : `平台应用还不在目标群里——请在群设置添加应用:${appLabel},再补人`)
12005
+ : res.error === 'requester_not_in_chat' ? '你本人还不在目标群里——只能往「你自己已在场」的群补人(先加入该群)'
12006
+ : res.error === 'requester_bot_not_in_chat' ? '你本人还不在目标群里——只能往「你自己已在场」的群补人(先加入该群)'
12007
+ : res.error === 'chat_is_hall' ? '目标群是机器人大厅,不允许往里补人(大厅是 bot-only 身份登记群)'
12008
+ : res.error === 'chat_not_in_team' ? '目标群不是本团队的协作群'
12009
+ : `相关 bot${which} 未由其 owner 在平台「管理机器人」加入该团队`;
12010
+ console.error(`补人被拒(403 ${res.error}):${hint}。`);
12011
+ }
12012
+ else if (res.reason === 'client' && res.status === 404) {
12013
+ console.error(`团队不存在或本机 owner 不是其成员:teamId=${teamId}`);
12014
+ }
12015
+ else {
12016
+ console.error(`补人失败:${describeTeamAgentsFailure(res)}`);
12017
+ }
12018
+ process.exit(1);
12019
+ }
12020
+ const { invalidBotIds, invalidOwnerUnionIds } = res.value;
12021
+ console.log(chatArg); // stdout 单行 chatId(与 create-group 一致)
12022
+ const hasInvalid = invalidBotIds.length > 0 || invalidOwnerUnionIds.length > 0;
12023
+ if (jsonStatus) {
12024
+ console.log(JSON.stringify({ ok: true, chatId: chatArg, invalidBotIds, invalidOwnerUnionIds, teamId }));
12025
+ }
12026
+ // 补人幂等(已在群内视作成功);平台 200 不列「实际加了谁」,成功即 invalid* 为空。
12027
+ if (!hasInvalid)
12028
+ console.error('✅ 补人成功(选中的 agent 及其 owner 均已在群内或已加入)。');
12029
+ if (invalidBotIds.length > 0)
12030
+ console.error(`⚠️ 以下 agent 未加入团队或补入失败:${invalidBotIds.join('、')}`);
12031
+ if (invalidOwnerUnionIds.length > 0)
12032
+ console.error(`⚠️ 以下 owner 未能补入:${invalidOwnerUnionIds.join('、')}`);
12033
+ if (hasInvalid)
12034
+ process.exitCode = 1;
12035
+ }
11227
12036
  // ─── botmux lang ─────────────────────────────────────────────────────────────
11228
12037
  /** Notify every online daemon to hot-reload its UI locale from disk, so a
11229
12038
  * `botmux lang` change takes effect on live cards without a restart. Best
@@ -11551,6 +12360,9 @@ if (process.env.BOTMUX_WORKFLOW === '1') {
11551
12360
  'bots',
11552
12361
  'skill',
11553
12362
  'hook',
12363
+ // Local-only, session-capability-bound preview registration. It has no chat,
12364
+ // workflow, deployment, or external messaging effect.
12365
+ 'preview',
11554
12366
  'session-ready',
11555
12367
  'mcp',
11556
12368
  'ask', // dedicated cmdAsk guard emits the humanGate-specific guidance
@@ -11764,6 +12576,7 @@ async function stopPluginServicesForCli(pluginIds, options = {}) {
11764
12576
  console.log('\n插件 host service:');
11765
12577
  console.log(formatPluginServiceReports(reports));
11766
12578
  }
12579
+ return reports;
11767
12580
  }
11768
12581
  function requirePluginId(raw) {
11769
12582
  const id = raw?.trim();
@@ -12339,10 +13152,10 @@ switch (command) {
12339
13152
  await cmdRestart();
12340
13153
  break;
12341
13154
  case 'logs':
12342
- cmdLogs();
13155
+ await cmdLogs();
12343
13156
  break;
12344
13157
  case 'status':
12345
- cmdStatus();
13158
+ await cmdStatus();
12346
13159
  break;
12347
13160
  case 'upgrade':
12348
13161
  case 'update':
@@ -12362,6 +13175,13 @@ switch (command) {
12362
13175
  process.exitCode = await runDeviceCommand(process.argv.slice(3));
12363
13176
  break;
12364
13177
  }
13178
+ case 'mojo-containment': {
13179
+ // The auditable operator exit for fail-closed containment blockers that can
13180
+ // never self-release (weak handles on non-cgroup hosts, unprovable handles).
13181
+ const { runMojoContainmentCommand } = await import('./core/mojo-containment-command.js');
13182
+ process.exitCode = await runMojoContainmentCommand(process.argv.slice(3));
13183
+ break;
13184
+ }
12365
13185
  case 'list':
12366
13186
  case 'ls':
12367
13187
  await cmdList();
@@ -12409,6 +13229,9 @@ switch (command) {
12409
13229
  case 'term-link':
12410
13230
  await cmdTermLink(process.argv.slice(3));
12411
13231
  break;
13232
+ case 'preview':
13233
+ await cmdPreview(process.argv.slice(3));
13234
+ break;
12412
13235
  case 'schedule':
12413
13236
  await cmdSchedule(process.argv[3] ?? '', process.argv.slice(4));
12414
13237
  break;