botmux 3.15.0 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (780) hide show
  1. package/README.en.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/.runtime-build-id +1 -1
  4. package/dist/adapters/backend/destroy-result.d.ts +159 -0
  5. package/dist/adapters/backend/destroy-result.d.ts.map +1 -0
  6. package/dist/adapters/backend/destroy-result.js +223 -0
  7. package/dist/adapters/backend/destroy-result.js.map +1 -0
  8. package/dist/adapters/backend/herdr-backend.d.ts +4 -0
  9. package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
  10. package/dist/adapters/backend/herdr-backend.js +23 -1
  11. package/dist/adapters/backend/herdr-backend.js.map +1 -1
  12. package/dist/adapters/backend/mojo-backend.d.ts +577 -0
  13. package/dist/adapters/backend/mojo-backend.d.ts.map +1 -0
  14. package/dist/adapters/backend/mojo-backend.js +2471 -0
  15. package/dist/adapters/backend/mojo-backend.js.map +1 -0
  16. package/dist/adapters/backend/mojo-budgets.d.ts +21 -0
  17. package/dist/adapters/backend/mojo-budgets.d.ts.map +1 -0
  18. package/dist/adapters/backend/mojo-budgets.js +22 -0
  19. package/dist/adapters/backend/mojo-budgets.js.map +1 -0
  20. package/dist/adapters/backend/mojo-isolated-workspace.d.ts +69 -0
  21. package/dist/adapters/backend/mojo-isolated-workspace.d.ts.map +1 -0
  22. package/dist/adapters/backend/mojo-isolated-workspace.js +213 -0
  23. package/dist/adapters/backend/mojo-isolated-workspace.js.map +1 -0
  24. package/dist/adapters/backend/mojo-process-tree.d.ts +292 -0
  25. package/dist/adapters/backend/mojo-process-tree.d.ts.map +1 -0
  26. package/dist/adapters/backend/mojo-process-tree.js +486 -0
  27. package/dist/adapters/backend/mojo-process-tree.js.map +1 -0
  28. package/dist/adapters/backend/mojo-types.d.ts +542 -0
  29. package/dist/adapters/backend/mojo-types.d.ts.map +1 -0
  30. package/dist/adapters/backend/mojo-types.js +682 -0
  31. package/dist/adapters/backend/mojo-types.js.map +1 -0
  32. package/dist/adapters/backend/reproduce-command.js +3 -3
  33. package/dist/adapters/backend/reproduce-command.js.map +1 -1
  34. package/dist/adapters/backend/riff-backend.js +1 -1
  35. package/dist/adapters/backend/riff-backend.js.map +1 -1
  36. package/dist/adapters/backend/sandbox.d.ts +44 -7
  37. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  38. package/dist/adapters/backend/sandbox.js +30 -7
  39. package/dist/adapters/backend/sandbox.js.map +1 -1
  40. package/dist/adapters/backend/session-backend-selector.d.ts +14 -1
  41. package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -1
  42. package/dist/adapters/backend/session-backend-selector.js +82 -10
  43. package/dist/adapters/backend/session-backend-selector.js.map +1 -1
  44. package/dist/adapters/backend/types.d.ts +101 -6
  45. package/dist/adapters/backend/types.d.ts.map +1 -1
  46. package/dist/adapters/backend/types.js.map +1 -1
  47. package/dist/adapters/backend/zmx-backend.d.ts +11 -0
  48. package/dist/adapters/backend/zmx-backend.d.ts.map +1 -1
  49. package/dist/adapters/backend/zmx-backend.js +16 -1
  50. package/dist/adapters/backend/zmx-backend.js.map +1 -1
  51. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  52. package/dist/adapters/cli/codex-app.js +2 -2
  53. package/dist/adapters/cli/codex-app.js.map +1 -1
  54. package/dist/adapters/cli/codex.d.ts.map +1 -1
  55. package/dist/adapters/cli/codex.js +7 -0
  56. package/dist/adapters/cli/codex.js.map +1 -1
  57. package/dist/adapters/cli/copilot.d.ts +3 -1
  58. package/dist/adapters/cli/copilot.d.ts.map +1 -1
  59. package/dist/adapters/cli/copilot.js +23 -5
  60. package/dist/adapters/cli/copilot.js.map +1 -1
  61. package/dist/adapters/cli/cursor.d.ts.map +1 -1
  62. package/dist/adapters/cli/cursor.js +13 -3
  63. package/dist/adapters/cli/cursor.js.map +1 -1
  64. package/dist/adapters/cli/grok.d.ts.map +1 -1
  65. package/dist/adapters/cli/grok.js +24 -5
  66. package/dist/adapters/cli/grok.js.map +1 -1
  67. package/dist/adapters/cli/kimi.d.ts.map +1 -1
  68. package/dist/adapters/cli/kimi.js +19 -1
  69. package/dist/adapters/cli/kimi.js.map +1 -1
  70. package/dist/adapters/cli/mojo.d.ts +28 -0
  71. package/dist/adapters/cli/mojo.d.ts.map +1 -0
  72. package/dist/adapters/cli/mojo.js +58 -0
  73. package/dist/adapters/cli/mojo.js.map +1 -0
  74. package/dist/adapters/cli/pi.d.ts +6 -4
  75. package/dist/adapters/cli/pi.d.ts.map +1 -1
  76. package/dist/adapters/cli/pi.js +6 -4
  77. package/dist/adapters/cli/pi.js.map +1 -1
  78. package/dist/adapters/cli/read-isolation.d.ts +251 -1
  79. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  80. package/dist/adapters/cli/read-isolation.js +254 -1
  81. package/dist/adapters/cli/read-isolation.js.map +1 -1
  82. package/dist/adapters/cli/registry.d.ts +13 -1
  83. package/dist/adapters/cli/registry.d.ts.map +1 -1
  84. package/dist/adapters/cli/registry.js +19 -1
  85. package/dist/adapters/cli/registry.js.map +1 -1
  86. package/dist/adapters/cli/runner-input.d.ts +3 -3
  87. package/dist/adapters/cli/runner-input.d.ts.map +1 -1
  88. package/dist/adapters/cli/runner-input.js +4 -3
  89. package/dist/adapters/cli/runner-input.js.map +1 -1
  90. package/dist/adapters/cli/shared-hints.d.ts +3 -1
  91. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  92. package/dist/adapters/cli/shared-hints.js +22 -6
  93. package/dist/adapters/cli/shared-hints.js.map +1 -1
  94. package/dist/adapters/cli/types.d.ts +29 -4
  95. package/dist/adapters/cli/types.d.ts.map +1 -1
  96. package/dist/bot-registry.d.ts +20 -0
  97. package/dist/bot-registry.d.ts.map +1 -1
  98. package/dist/bot-registry.js +30 -4
  99. package/dist/bot-registry.js.map +1 -1
  100. package/dist/cli/arg-utils.d.ts +5 -0
  101. package/dist/cli/arg-utils.d.ts.map +1 -1
  102. package/dist/cli/arg-utils.js +7 -0
  103. package/dist/cli/arg-utils.js.map +1 -1
  104. package/dist/cli/bots-list-output.d.ts +130 -2
  105. package/dist/cli/bots-list-output.d.ts.map +1 -1
  106. package/dist/cli/bots-list-output.js +173 -27
  107. package/dist/cli/bots-list-output.js.map +1 -1
  108. package/dist/cli/daemon-lifecycle-env.d.ts +10 -3
  109. package/dist/cli/daemon-lifecycle-env.d.ts.map +1 -1
  110. package/dist/cli/daemon-lifecycle-env.js +38 -11
  111. package/dist/cli/daemon-lifecycle-env.js.map +1 -1
  112. package/dist/cli/dashboard-command.d.ts +15 -0
  113. package/dist/cli/dashboard-command.d.ts.map +1 -1
  114. package/dist/cli/dashboard-command.js +22 -0
  115. package/dist/cli/dashboard-command.js.map +1 -1
  116. package/dist/cli/log-tail.d.ts +54 -0
  117. package/dist/cli/log-tail.d.ts.map +1 -0
  118. package/dist/cli/log-tail.js +167 -0
  119. package/dist/cli/log-tail.js.map +1 -0
  120. package/dist/cli/pm2-env.d.ts +16 -0
  121. package/dist/cli/pm2-env.d.ts.map +1 -0
  122. package/dist/cli/pm2-env.js +63 -0
  123. package/dist/cli/pm2-env.js.map +1 -0
  124. package/dist/cli/pm2-fleet-lock.d.ts +26 -0
  125. package/dist/cli/pm2-fleet-lock.d.ts.map +1 -0
  126. package/dist/cli/pm2-fleet-lock.js +50 -0
  127. package/dist/cli/pm2-fleet-lock.js.map +1 -0
  128. package/dist/cli/pm2-god-retirement.d.ts +59 -0
  129. package/dist/cli/pm2-god-retirement.d.ts.map +1 -0
  130. package/dist/cli/pm2-god-retirement.js +95 -0
  131. package/dist/cli/pm2-god-retirement.js.map +1 -0
  132. package/dist/cli/session-list-wake.d.ts +41 -0
  133. package/dist/cli/session-list-wake.d.ts.map +1 -0
  134. package/dist/cli/session-list-wake.js +102 -0
  135. package/dist/cli/session-list-wake.js.map +1 -0
  136. package/dist/cli/workflow.d.ts.map +1 -1
  137. package/dist/cli/workflow.js +26 -0
  138. package/dist/cli/workflow.js.map +1 -1
  139. package/dist/cli.d.ts.map +1 -1
  140. package/dist/cli.js +933 -115
  141. package/dist/cli.js.map +1 -1
  142. package/dist/codex-app-runner.js +261 -49
  143. package/dist/codex-app-runner.js.map +1 -1
  144. package/dist/core/cli-runtime-update.d.ts +8 -0
  145. package/dist/core/cli-runtime-update.d.ts.map +1 -1
  146. package/dist/core/cli-runtime-update.js +127 -8
  147. package/dist/core/cli-runtime-update.js.map +1 -1
  148. package/dist/core/close-residual.d.ts +64 -0
  149. package/dist/core/close-residual.d.ts.map +1 -0
  150. package/dist/core/close-residual.js +93 -0
  151. package/dist/core/close-residual.js.map +1 -0
  152. package/dist/core/closed-session-card.d.ts.map +1 -1
  153. package/dist/core/closed-session-card.js +11 -3
  154. package/dist/core/closed-session-card.js.map +1 -1
  155. package/dist/core/command-handler.d.ts.map +1 -1
  156. package/dist/core/command-handler.js +129 -27
  157. package/dist/core/command-handler.js.map +1 -1
  158. package/dist/core/cost-calculator.d.ts +3 -1
  159. package/dist/core/cost-calculator.d.ts.map +1 -1
  160. package/dist/core/cost-calculator.js +230 -1
  161. package/dist/core/cost-calculator.js.map +1 -1
  162. package/dist/core/daemon-background-close.d.ts +19 -0
  163. package/dist/core/daemon-background-close.d.ts.map +1 -0
  164. package/dist/core/daemon-background-close.js +39 -0
  165. package/dist/core/daemon-background-close.js.map +1 -0
  166. package/dist/core/dashboard-command/overview.d.ts +4 -0
  167. package/dist/core/dashboard-command/overview.d.ts.map +1 -1
  168. package/dist/core/dashboard-command/overview.js +8 -1
  169. package/dist/core/dashboard-command/overview.js.map +1 -1
  170. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  171. package/dist/core/dashboard-ipc-server.js +373 -41
  172. package/dist/core/dashboard-ipc-server.js.map +1 -1
  173. package/dist/core/dashboard-rows.d.ts +4 -0
  174. package/dist/core/dashboard-rows.d.ts.map +1 -1
  175. package/dist/core/dashboard-rows.js +3 -0
  176. package/dist/core/dashboard-rows.js.map +1 -1
  177. package/dist/core/dashboard-url.d.ts +37 -0
  178. package/dist/core/dashboard-url.d.ts.map +1 -1
  179. package/dist/core/dashboard-url.js +71 -0
  180. package/dist/core/dashboard-url.js.map +1 -1
  181. package/dist/core/deferred-schedule-settlement.d.ts +15 -1
  182. package/dist/core/deferred-schedule-settlement.d.ts.map +1 -1
  183. package/dist/core/deferred-schedule-settlement.js +4 -1
  184. package/dist/core/deferred-schedule-settlement.js.map +1 -1
  185. package/dist/core/device-isolation-daemon.d.ts +91 -1
  186. package/dist/core/device-isolation-daemon.d.ts.map +1 -1
  187. package/dist/core/device-isolation-daemon.js +346 -2
  188. package/dist/core/device-isolation-daemon.js.map +1 -1
  189. package/dist/core/local-terminal-opener.d.ts.map +1 -1
  190. package/dist/core/local-terminal-opener.js +5 -1
  191. package/dist/core/local-terminal-opener.js.map +1 -1
  192. package/dist/core/loopback-target.d.ts +14 -0
  193. package/dist/core/loopback-target.d.ts.map +1 -0
  194. package/dist/core/loopback-target.js +20 -0
  195. package/dist/core/loopback-target.js.map +1 -0
  196. package/dist/core/maintenance.d.ts.map +1 -1
  197. package/dist/core/maintenance.js +16 -2
  198. package/dist/core/maintenance.js.map +1 -1
  199. package/dist/core/mojo-containment-command.d.ts +20 -0
  200. package/dist/core/mojo-containment-command.d.ts.map +1 -0
  201. package/dist/core/mojo-containment-command.js +266 -0
  202. package/dist/core/mojo-containment-command.js.map +1 -0
  203. package/dist/core/mojo-containment.d.ts +575 -0
  204. package/dist/core/mojo-containment.d.ts.map +1 -0
  205. package/dist/core/mojo-containment.js +1158 -0
  206. package/dist/core/mojo-containment.js.map +1 -0
  207. package/dist/core/mojo-launcher-env-quarantine.d.ts +30 -0
  208. package/dist/core/mojo-launcher-env-quarantine.d.ts.map +1 -0
  209. package/dist/core/mojo-launcher-env-quarantine.js +175 -0
  210. package/dist/core/mojo-launcher-env-quarantine.js.map +1 -0
  211. package/dist/core/mojo-session-identity.d.ts +3 -0
  212. package/dist/core/mojo-session-identity.d.ts.map +1 -0
  213. package/dist/core/mojo-session-identity.js +126 -0
  214. package/dist/core/mojo-session-identity.js.map +1 -0
  215. package/dist/core/persistent-backend.d.ts +32 -8
  216. package/dist/core/persistent-backend.d.ts.map +1 -1
  217. package/dist/core/persistent-backend.js +55 -17
  218. package/dist/core/persistent-backend.js.map +1 -1
  219. package/dist/core/plugins/mcp/gateway.d.ts +14 -1
  220. package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
  221. package/dist/core/plugins/mcp/gateway.js +70 -3
  222. package/dist/core/plugins/mcp/gateway.js.map +1 -1
  223. package/dist/core/plugins/mcp/host.d.ts +2 -0
  224. package/dist/core/plugins/mcp/host.d.ts.map +1 -1
  225. package/dist/core/plugins/mcp/host.js +2 -0
  226. package/dist/core/plugins/mcp/host.js.map +1 -1
  227. package/dist/core/plugins/pm2.d.ts.map +1 -1
  228. package/dist/core/plugins/pm2.js +16 -3
  229. package/dist/core/plugins/pm2.js.map +1 -1
  230. package/dist/core/plugins/service-manager.d.ts +9 -0
  231. package/dist/core/plugins/service-manager.d.ts.map +1 -1
  232. package/dist/core/plugins/service-manager.js +33 -16
  233. package/dist/core/plugins/service-manager.js.map +1 -1
  234. package/dist/core/preview-port-owner.d.ts +109 -0
  235. package/dist/core/preview-port-owner.d.ts.map +1 -0
  236. package/dist/core/preview-port-owner.js +320 -0
  237. package/dist/core/preview-port-owner.js.map +1 -0
  238. package/dist/core/remote-cli-ids.d.ts +5 -0
  239. package/dist/core/remote-cli-ids.d.ts.map +1 -0
  240. package/dist/core/remote-cli-ids.js +24 -0
  241. package/dist/core/remote-cli-ids.js.map +1 -0
  242. package/dist/core/{riff-shutdown-detach.d.ts → remote-shutdown-detach.d.ts} +40 -39
  243. package/dist/core/remote-shutdown-detach.d.ts.map +1 -0
  244. package/dist/core/{riff-shutdown-detach.js → remote-shutdown-detach.js} +85 -83
  245. package/dist/core/remote-shutdown-detach.js.map +1 -0
  246. package/dist/core/{riff-worker-shutdown-readiness.d.ts → remote-worker-shutdown-readiness.d.ts} +5 -4
  247. package/dist/core/remote-worker-shutdown-readiness.d.ts.map +1 -0
  248. package/dist/core/{riff-worker-shutdown-readiness.js → remote-worker-shutdown-readiness.js} +4 -3
  249. package/dist/core/remote-worker-shutdown-readiness.js.map +1 -0
  250. package/dist/core/reply-target.d.ts +16 -1
  251. package/dist/core/reply-target.d.ts.map +1 -1
  252. package/dist/core/reply-target.js +61 -0
  253. package/dist/core/reply-target.js.map +1 -1
  254. package/dist/core/session-group-birth.d.ts +26 -0
  255. package/dist/core/session-group-birth.d.ts.map +1 -1
  256. package/dist/core/session-group-birth.js +81 -4
  257. package/dist/core/session-group-birth.js.map +1 -1
  258. package/dist/core/session-manager.d.ts +15 -2
  259. package/dist/core/session-manager.d.ts.map +1 -1
  260. package/dist/core/session-manager.js +259 -5
  261. package/dist/core/session-manager.js.map +1 -1
  262. package/dist/core/session-model.d.ts +46 -0
  263. package/dist/core/session-model.d.ts.map +1 -0
  264. package/dist/core/session-model.js +42 -0
  265. package/dist/core/session-model.js.map +1 -0
  266. package/dist/core/session-owner-reminder.d.ts +63 -0
  267. package/dist/core/session-owner-reminder.d.ts.map +1 -0
  268. package/dist/core/session-owner-reminder.js +196 -0
  269. package/dist/core/session-owner-reminder.js.map +1 -0
  270. package/dist/core/session-preview-registry.d.ts +53 -0
  271. package/dist/core/session-preview-registry.d.ts.map +1 -0
  272. package/dist/core/session-preview-registry.js +82 -0
  273. package/dist/core/session-preview-registry.js.map +1 -0
  274. package/dist/core/session-preview.d.ts +114 -0
  275. package/dist/core/session-preview.d.ts.map +1 -0
  276. package/dist/core/session-preview.js +184 -0
  277. package/dist/core/session-preview.js.map +1 -0
  278. package/dist/core/session-rename.d.ts.map +1 -1
  279. package/dist/core/session-rename.js +5 -3
  280. package/dist/core/session-rename.js.map +1 -1
  281. package/dist/core/session-wake-deadline.d.ts +5 -0
  282. package/dist/core/session-wake-deadline.d.ts.map +1 -0
  283. package/dist/core/session-wake-deadline.js +13 -0
  284. package/dist/core/session-wake-deadline.js.map +1 -0
  285. package/dist/core/shutdown-budgets.d.ts +7 -6
  286. package/dist/core/shutdown-budgets.d.ts.map +1 -1
  287. package/dist/core/shutdown-budgets.js +11 -10
  288. package/dist/core/shutdown-budgets.js.map +1 -1
  289. package/dist/core/terminal-control-grant.d.ts +74 -0
  290. package/dist/core/terminal-control-grant.d.ts.map +1 -0
  291. package/dist/core/terminal-control-grant.js +183 -0
  292. package/dist/core/terminal-control-grant.js.map +1 -0
  293. package/dist/core/terminal-proxy.d.ts.map +1 -1
  294. package/dist/core/terminal-proxy.js +4 -5
  295. package/dist/core/terminal-proxy.js.map +1 -1
  296. package/dist/core/terminal-write-auth.d.ts +4 -11
  297. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  298. package/dist/core/terminal-write-auth.js +19 -16
  299. package/dist/core/terminal-write-auth.js.map +1 -1
  300. package/dist/core/trigger-session.d.ts.map +1 -1
  301. package/dist/core/trigger-session.js +36 -29
  302. package/dist/core/trigger-session.js.map +1 -1
  303. package/dist/core/types.d.ts +88 -10
  304. package/dist/core/types.d.ts.map +1 -1
  305. package/dist/core/types.js +9 -7
  306. package/dist/core/types.js.map +1 -1
  307. package/dist/core/workbench-link.d.ts +81 -0
  308. package/dist/core/workbench-link.d.ts.map +1 -0
  309. package/dist/core/workbench-link.js +109 -0
  310. package/dist/core/workbench-link.js.map +1 -0
  311. package/dist/core/worker-pool.d.ts +312 -11
  312. package/dist/core/worker-pool.d.ts.map +1 -1
  313. package/dist/core/worker-pool.js +2494 -209
  314. package/dist/core/worker-pool.js.map +1 -1
  315. package/dist/daemon.d.ts +6 -0
  316. package/dist/daemon.d.ts.map +1 -1
  317. package/dist/daemon.js +443 -106
  318. package/dist/daemon.js.map +1 -1
  319. package/dist/dashboard/aggregator.d.ts +33 -3
  320. package/dist/dashboard/aggregator.d.ts.map +1 -1
  321. package/dist/dashboard/aggregator.js +93 -17
  322. package/dist/dashboard/aggregator.js.map +1 -1
  323. package/dist/dashboard/auth-session-connections.d.ts +59 -0
  324. package/dist/dashboard/auth-session-connections.d.ts.map +1 -0
  325. package/dist/dashboard/auth-session-connections.js +109 -0
  326. package/dist/dashboard/auth-session-connections.js.map +1 -0
  327. package/dist/dashboard/auth.d.ts +99 -1
  328. package/dist/dashboard/auth.d.ts.map +1 -1
  329. package/dist/dashboard/auth.js +176 -64
  330. package/dist/dashboard/auth.js.map +1 -1
  331. package/dist/dashboard/bot-payload.d.ts +1 -0
  332. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  333. package/dist/dashboard/bot-payload.js +3 -0
  334. package/dist/dashboard/bot-payload.js.map +1 -1
  335. package/dist/dashboard/control-audit.d.ts +112 -0
  336. package/dist/dashboard/control-audit.d.ts.map +1 -0
  337. package/dist/dashboard/control-audit.js +195 -0
  338. package/dist/dashboard/control-audit.js.map +1 -0
  339. package/dist/dashboard/control-csrf.d.ts +123 -0
  340. package/dist/dashboard/control-csrf.d.ts.map +1 -0
  341. package/dist/dashboard/control-csrf.js +392 -0
  342. package/dist/dashboard/control-csrf.js.map +1 -0
  343. package/dist/dashboard/daemon-reconcile.d.ts +26 -0
  344. package/dist/dashboard/daemon-reconcile.d.ts.map +1 -0
  345. package/dist/dashboard/daemon-reconcile.js +46 -0
  346. package/dist/dashboard/daemon-reconcile.js.map +1 -0
  347. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  348. package/dist/dashboard/debug-terminal.js +18 -4
  349. package/dist/dashboard/debug-terminal.js.map +1 -1
  350. package/dist/dashboard/events-sse.d.ts +60 -0
  351. package/dist/dashboard/events-sse.d.ts.map +1 -0
  352. package/dist/dashboard/events-sse.js +63 -0
  353. package/dist/dashboard/events-sse.js.map +1 -0
  354. package/dist/dashboard/h5-auth.d.ts +256 -0
  355. package/dist/dashboard/h5-auth.d.ts.map +1 -0
  356. package/dist/dashboard/h5-auth.js +825 -0
  357. package/dist/dashboard/h5-auth.js.map +1 -0
  358. package/dist/dashboard/managed-spawn.d.ts +36 -0
  359. package/dist/dashboard/managed-spawn.d.ts.map +1 -0
  360. package/dist/dashboard/managed-spawn.js +169 -0
  361. package/dist/dashboard/managed-spawn.js.map +1 -0
  362. package/dist/dashboard/preview-content-capability.d.ts +41 -0
  363. package/dist/dashboard/preview-content-capability.d.ts.map +1 -0
  364. package/dist/dashboard/preview-content-capability.js +127 -0
  365. package/dist/dashboard/preview-content-capability.js.map +1 -0
  366. package/dist/dashboard/preview-contract.d.ts +85 -0
  367. package/dist/dashboard/preview-contract.d.ts.map +1 -0
  368. package/dist/dashboard/preview-contract.js +198 -0
  369. package/dist/dashboard/preview-contract.js.map +1 -0
  370. package/dist/dashboard/preview-guard-page.d.ts +52 -0
  371. package/dist/dashboard/preview-guard-page.d.ts.map +1 -0
  372. package/dist/dashboard/preview-guard-page.js +162 -0
  373. package/dist/dashboard/preview-guard-page.js.map +1 -0
  374. package/dist/dashboard/preview-interaction.d.ts +57 -0
  375. package/dist/dashboard/preview-interaction.d.ts.map +1 -0
  376. package/dist/dashboard/preview-interaction.js +198 -0
  377. package/dist/dashboard/preview-interaction.js.map +1 -0
  378. package/dist/dashboard/preview-proxy.d.ts +113 -0
  379. package/dist/dashboard/preview-proxy.d.ts.map +1 -0
  380. package/dist/dashboard/preview-proxy.js +543 -0
  381. package/dist/dashboard/preview-proxy.js.map +1 -0
  382. package/dist/dashboard/public-redact.d.ts +23 -0
  383. package/dist/dashboard/public-redact.d.ts.map +1 -1
  384. package/dist/dashboard/public-redact.js +90 -21
  385. package/dist/dashboard/public-redact.js.map +1 -1
  386. package/dist/dashboard/request-identity.d.ts +77 -0
  387. package/dist/dashboard/request-identity.d.ts.map +1 -0
  388. package/dist/dashboard/request-identity.js +72 -0
  389. package/dist/dashboard/request-identity.js.map +1 -0
  390. package/dist/dashboard/session-cleanup.d.ts +5 -0
  391. package/dist/dashboard/session-cleanup.d.ts.map +1 -1
  392. package/dist/dashboard/session-cleanup.js +4 -0
  393. package/dist/dashboard/session-cleanup.js.map +1 -1
  394. package/dist/dashboard/settings-write-applier.d.ts +5 -1
  395. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  396. package/dist/dashboard/settings-write-applier.js +14 -0
  397. package/dist/dashboard/settings-write-applier.js.map +1 -1
  398. package/dist/dashboard/standing-link.d.ts +76 -0
  399. package/dist/dashboard/standing-link.d.ts.map +1 -0
  400. package/dist/dashboard/standing-link.js +111 -0
  401. package/dist/dashboard/standing-link.js.map +1 -0
  402. package/dist/dashboard/terminal-control.d.ts +105 -0
  403. package/dist/dashboard/terminal-control.d.ts.map +1 -0
  404. package/dist/dashboard/terminal-control.js +296 -0
  405. package/dist/dashboard/terminal-control.js.map +1 -0
  406. package/dist/dashboard/terminal-front-proxy.d.ts +55 -0
  407. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -0
  408. package/dist/dashboard/terminal-front-proxy.js +397 -0
  409. package/dist/dashboard/terminal-front-proxy.js.map +1 -0
  410. package/dist/dashboard/terminal-view-capability.d.ts +97 -0
  411. package/dist/dashboard/terminal-view-capability.d.ts.map +1 -0
  412. package/dist/dashboard/terminal-view-capability.js +145 -0
  413. package/dist/dashboard/terminal-view-capability.js.map +1 -0
  414. package/dist/dashboard/web/agent-workbench-api.d.ts +59 -0
  415. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -0
  416. package/dist/dashboard/web/agent-workbench-api.js +227 -0
  417. package/dist/dashboard/web/agent-workbench-api.js.map +1 -0
  418. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts +59 -0
  419. package/dist/dashboard/web/agent-workbench-appearance-menu.d.ts.map +1 -0
  420. package/dist/dashboard/web/agent-workbench-appearance-menu.js +195 -0
  421. package/dist/dashboard/web/agent-workbench-appearance-menu.js.map +1 -0
  422. package/dist/dashboard/web/agent-workbench-appearance.d.ts +222 -0
  423. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -0
  424. package/dist/dashboard/web/agent-workbench-appearance.js +499 -0
  425. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -0
  426. package/dist/dashboard/web/agent-workbench-capabilities.d.ts +13 -0
  427. package/dist/dashboard/web/agent-workbench-capabilities.d.ts.map +1 -0
  428. package/dist/dashboard/web/agent-workbench-capabilities.js +24 -0
  429. package/dist/dashboard/web/agent-workbench-capabilities.js.map +1 -0
  430. package/dist/dashboard/web/agent-workbench-chat.d.ts +90 -0
  431. package/dist/dashboard/web/agent-workbench-chat.d.ts.map +1 -0
  432. package/dist/dashboard/web/agent-workbench-chat.js +168 -0
  433. package/dist/dashboard/web/agent-workbench-chat.js.map +1 -0
  434. package/dist/dashboard/web/agent-workbench-dock-page.d.ts +3 -0
  435. package/dist/dashboard/web/agent-workbench-dock-page.d.ts.map +1 -0
  436. package/dist/dashboard/web/agent-workbench-dock-page.js +23 -0
  437. package/dist/dashboard/web/agent-workbench-dock-page.js.map +1 -0
  438. package/dist/dashboard/web/agent-workbench-dock-view.d.ts +22 -0
  439. package/dist/dashboard/web/agent-workbench-dock-view.d.ts.map +1 -0
  440. package/dist/dashboard/web/agent-workbench-dock-view.js +76 -0
  441. package/dist/dashboard/web/agent-workbench-dock-view.js.map +1 -0
  442. package/dist/dashboard/web/agent-workbench-model.d.ts +196 -0
  443. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -0
  444. package/dist/dashboard/web/agent-workbench-model.js +529 -0
  445. package/dist/dashboard/web/agent-workbench-model.js.map +1 -0
  446. package/dist/dashboard/web/agent-workbench-page.d.ts +3 -0
  447. package/dist/dashboard/web/agent-workbench-page.d.ts.map +1 -0
  448. package/dist/dashboard/web/agent-workbench-page.js +27 -0
  449. package/dist/dashboard/web/agent-workbench-page.js.map +1 -0
  450. package/dist/dashboard/web/agent-workbench-panes.d.ts +49 -0
  451. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -0
  452. package/dist/dashboard/web/agent-workbench-panes.js +468 -0
  453. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -0
  454. package/dist/dashboard/web/agent-workbench-session-list.d.ts +42 -0
  455. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -0
  456. package/dist/dashboard/web/agent-workbench-session-list.js +353 -0
  457. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -0
  458. package/dist/dashboard/web/agent-workbench-storage.d.ts +49 -0
  459. package/dist/dashboard/web/agent-workbench-storage.d.ts.map +1 -0
  460. package/dist/dashboard/web/agent-workbench-storage.js +197 -0
  461. package/dist/dashboard/web/agent-workbench-storage.js.map +1 -0
  462. package/dist/dashboard/web/agent-workbench-touch.d.ts +46 -0
  463. package/dist/dashboard/web/agent-workbench-touch.d.ts.map +1 -0
  464. package/dist/dashboard/web/agent-workbench-touch.js +96 -0
  465. package/dist/dashboard/web/agent-workbench-touch.js.map +1 -0
  466. package/dist/dashboard/web/agent-workbench-view.d.ts +34 -0
  467. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -0
  468. package/dist/dashboard/web/agent-workbench-view.js +439 -0
  469. package/dist/dashboard/web/agent-workbench-view.js.map +1 -0
  470. package/dist/dashboard/web/app.d.ts.map +1 -1
  471. package/dist/dashboard/web/app.js +63 -2
  472. package/dist/dashboard/web/app.js.map +1 -1
  473. package/dist/dashboard/web/bot-defaults-page.d.ts +11 -0
  474. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  475. package/dist/dashboard/web/bot-defaults-page.js +267 -36
  476. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  477. package/dist/dashboard/web/bot-defaults.d.ts +6 -0
  478. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  479. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  480. package/dist/dashboard/web/control-csrf.d.ts +4 -0
  481. package/dist/dashboard/web/control-csrf.d.ts.map +1 -0
  482. package/dist/dashboard/web/control-csrf.js +26 -0
  483. package/dist/dashboard/web/control-csrf.js.map +1 -0
  484. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  485. package/dist/dashboard/web/dashboard-routes.js +2 -0
  486. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  487. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  488. package/dist/dashboard/web/groups-page.js +11 -2
  489. package/dist/dashboard/web/groups-page.js.map +1 -1
  490. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  491. package/dist/dashboard/web/i18n.js +132 -6
  492. package/dist/dashboard/web/i18n.js.map +1 -1
  493. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  494. package/dist/dashboard/web/overview-page.js +3 -2
  495. package/dist/dashboard/web/overview-page.js.map +1 -1
  496. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  497. package/dist/dashboard/web/sessions-page.js +41 -1
  498. package/dist/dashboard/web/sessions-page.js.map +1 -1
  499. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  500. package/dist/dashboard/web/sessions.js +5 -1
  501. package/dist/dashboard/web/sessions.js.map +1 -1
  502. package/dist/dashboard/web/settings-page.d.ts +3 -0
  503. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  504. package/dist/dashboard/web/settings-page.js +56 -10
  505. package/dist/dashboard/web/settings-page.js.map +1 -1
  506. package/dist/dashboard/web/store.d.ts +27 -2
  507. package/dist/dashboard/web/store.d.ts.map +1 -1
  508. package/dist/dashboard/web/store.js +43 -4
  509. package/dist/dashboard/web/store.js.map +1 -1
  510. package/dist/dashboard/web/ui.d.ts +3 -0
  511. package/dist/dashboard/web/ui.d.ts.map +1 -1
  512. package/dist/dashboard/web/ui.js +11 -0
  513. package/dist/dashboard/web/ui.js.map +1 -1
  514. package/dist/dashboard/web/update-action.d.ts +12 -0
  515. package/dist/dashboard/web/update-action.d.ts.map +1 -1
  516. package/dist/dashboard/web/update-action.js +11 -0
  517. package/dist/dashboard/web/update-action.js.map +1 -1
  518. package/dist/dashboard/workbench-doctor.d.ts +114 -0
  519. package/dist/dashboard/workbench-doctor.d.ts.map +1 -0
  520. package/dist/dashboard/workbench-doctor.js +620 -0
  521. package/dist/dashboard/workbench-doctor.js.map +1 -0
  522. package/dist/dashboard/workbench-ticket.d.ts +165 -0
  523. package/dist/dashboard/workbench-ticket.d.ts.map +1 -0
  524. package/dist/dashboard/workbench-ticket.js +495 -0
  525. package/dist/dashboard/workbench-ticket.js.map +1 -0
  526. package/dist/dashboard-web/app.js +1 -1
  527. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +1 -0
  528. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +1 -0
  529. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +10 -0
  530. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +2 -0
  531. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +1 -0
  532. package/dist/dashboard-web/chunks/{chunk-Z7B3E3US.js → chunk-2W6SCN3M.js} +1 -1
  533. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +1 -0
  534. package/dist/dashboard-web/chunks/{chunk-VMWNR3SV.js → chunk-F6KNPF4R.js} +11 -9
  535. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +1 -0
  536. package/dist/dashboard-web/chunks/chunk-G5ZS2YUS.js +1 -0
  537. package/dist/dashboard-web/chunks/chunk-KTQ7B6ZR.js +1 -0
  538. package/dist/dashboard-web/chunks/{chunk-Z7DO42GJ.js → chunk-NACP7BMR.js} +1 -1
  539. package/dist/dashboard-web/chunks/{connectors-page-CPH6VVFY.js → connectors-page-VOISXXII.js} +1 -1
  540. package/dist/dashboard-web/chunks/{feedback-page-J2FKQ3LK.js → feedback-page-RN6HRZMS.js} +1 -1
  541. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +8 -0
  542. package/dist/dashboard-web/chunks/{insights-page-A2LWTKJD.js → insights-page-HJFDYR5N.js} +3 -3
  543. package/dist/dashboard-web/chunks/{monitor-room-2YR752E3.js → monitor-room-V7PCP47V.js} +1 -1
  544. package/dist/dashboard-web/chunks/{monitoring-page-BGYSKZGM.js → monitoring-page-NKEKVQAP.js} +1 -1
  545. package/dist/dashboard-web/chunks/{office-page-ZZHTEAE4.js → office-page-GVJF73XW.js} +1 -1
  546. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +1 -0
  547. package/dist/dashboard-web/chunks/{plugin-page-VEGTCEAS.js → plugin-page-J3OF5IY4.js} +1 -1
  548. package/dist/dashboard-web/chunks/{roles-page-YUC4YLJG.js → roles-page-WQIRSOLZ.js} +1 -1
  549. package/dist/dashboard-web/chunks/{schedules-page-2L2NKTDJ.js → schedules-page-J2DEY2X6.js} +1 -1
  550. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +8 -0
  551. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +2 -0
  552. package/dist/dashboard-web/chunks/{skills-page-DQPBBI3T.js → skills-page-HGIFR3IU.js} +1 -1
  553. package/dist/dashboard-web/chunks/{team-federation-page-LQKLDV4K.js → team-federation-page-MR5ZPL4B.js} +1 -1
  554. package/dist/dashboard-web/chunks/{v3-page-RVKQIMAP.js → v3-page-GBR5JWPE.js} +2 -2
  555. package/dist/dashboard-web/chunks/{whiteboards-page-S5K55HRZ.js → whiteboards-page-QAZM4BN3.js} +1 -1
  556. package/dist/dashboard-web/index.html +8 -1
  557. package/dist/dashboard-web/style.css +2006 -10
  558. package/dist/dashboard.js +1006 -263
  559. package/dist/dashboard.js.map +1 -1
  560. package/dist/global-config.d.ts +30 -0
  561. package/dist/global-config.d.ts.map +1 -1
  562. package/dist/global-config.js +33 -0
  563. package/dist/global-config.js.map +1 -1
  564. package/dist/i18n/en.d.ts.map +1 -1
  565. package/dist/i18n/en.js +31 -1
  566. package/dist/i18n/en.js.map +1 -1
  567. package/dist/i18n/zh.d.ts.map +1 -1
  568. package/dist/i18n/zh.js +28 -1
  569. package/dist/i18n/zh.js.map +1 -1
  570. package/dist/im/lark/card-builder.d.ts +1 -1
  571. package/dist/im/lark/card-builder.d.ts.map +1 -1
  572. package/dist/im/lark/card-builder.js +22 -6
  573. package/dist/im/lark/card-builder.js.map +1 -1
  574. package/dist/im/lark/card-handler.d.ts.map +1 -1
  575. package/dist/im/lark/card-handler.js +118 -24
  576. package/dist/im/lark/card-handler.js.map +1 -1
  577. package/dist/im/lark/client.d.ts +15 -0
  578. package/dist/im/lark/client.d.ts.map +1 -1
  579. package/dist/im/lark/client.js +30 -0
  580. package/dist/im/lark/client.js.map +1 -1
  581. package/dist/im/lark/event-dispatcher.d.ts +48 -0
  582. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  583. package/dist/im/lark/event-dispatcher.js +133 -22
  584. package/dist/im/lark/event-dispatcher.js.map +1 -1
  585. package/dist/im/lark/identity-cache.d.ts +14 -6
  586. package/dist/im/lark/identity-cache.d.ts.map +1 -1
  587. package/dist/im/lark/identity-cache.js +69 -26
  588. package/dist/im/lark/identity-cache.js.map +1 -1
  589. package/dist/im/lark/lark-hosts.d.ts +13 -0
  590. package/dist/im/lark/lark-hosts.d.ts.map +1 -1
  591. package/dist/im/lark/lark-hosts.js +16 -0
  592. package/dist/im/lark/lark-hosts.js.map +1 -1
  593. package/dist/im/lark/overview-card.d.ts +22 -0
  594. package/dist/im/lark/overview-card.d.ts.map +1 -1
  595. package/dist/im/lark/overview-card.js +53 -3
  596. package/dist/im/lark/overview-card.js.map +1 -1
  597. package/dist/im/lark/sessions-card.d.ts.map +1 -1
  598. package/dist/im/lark/sessions-card.js +26 -3
  599. package/dist/im/lark/sessions-card.js.map +1 -1
  600. package/dist/im/lark/workflow-slash-command.d.ts +6 -0
  601. package/dist/im/lark/workflow-slash-command.d.ts.map +1 -1
  602. package/dist/im/lark/workflow-slash-command.js +7 -0
  603. package/dist/im/lark/workflow-slash-command.js.map +1 -1
  604. package/dist/index-daemon.js +27 -4
  605. package/dist/index-daemon.js.map +1 -1
  606. package/dist/index-dashboard.d.ts +3 -0
  607. package/dist/index-dashboard.d.ts.map +1 -0
  608. package/dist/index-dashboard.js +94 -0
  609. package/dist/index-dashboard.js.map +1 -0
  610. package/dist/platform/team-agents-client.d.ts +199 -0
  611. package/dist/platform/team-agents-client.d.ts.map +1 -0
  612. package/dist/platform/team-agents-client.js +263 -0
  613. package/dist/platform/team-agents-client.js.map +1 -0
  614. package/dist/platform/tunnel-client.d.ts +6 -0
  615. package/dist/platform/tunnel-client.d.ts.map +1 -1
  616. package/dist/platform/tunnel-client.js.map +1 -1
  617. package/dist/services/async-trigger-state.d.ts +7 -3
  618. package/dist/services/async-trigger-state.d.ts.map +1 -1
  619. package/dist/services/async-trigger-state.js +28 -0
  620. package/dist/services/async-trigger-state.js.map +1 -1
  621. package/dist/services/async-trigger-store.d.ts +13 -4
  622. package/dist/services/async-trigger-store.d.ts.map +1 -1
  623. package/dist/services/async-trigger-store.js +55 -8
  624. package/dist/services/async-trigger-store.js.map +1 -1
  625. package/dist/services/bot-config-store.d.ts +1 -11
  626. package/dist/services/bot-config-store.d.ts.map +1 -1
  627. package/dist/services/bot-config-store.js +27 -3
  628. package/dist/services/bot-config-store.js.map +1 -1
  629. package/dist/services/bot-profile-store.d.ts +17 -2
  630. package/dist/services/bot-profile-store.d.ts.map +1 -1
  631. package/dist/services/bot-profile-store.js +93 -12
  632. package/dist/services/bot-profile-store.js.map +1 -1
  633. package/dist/services/bridge-turn-queue.d.ts +10 -1
  634. package/dist/services/bridge-turn-queue.d.ts.map +1 -1
  635. package/dist/services/bridge-turn-queue.js +29 -9
  636. package/dist/services/bridge-turn-queue.js.map +1 -1
  637. package/dist/services/claude-transcript.d.ts +19 -0
  638. package/dist/services/claude-transcript.d.ts.map +1 -1
  639. package/dist/services/claude-transcript.js +57 -5
  640. package/dist/services/claude-transcript.js.map +1 -1
  641. package/dist/services/codex-app-threads.d.ts +2 -0
  642. package/dist/services/codex-app-threads.d.ts.map +1 -1
  643. package/dist/services/codex-app-threads.js +1 -1
  644. package/dist/services/codex-app-threads.js.map +1 -1
  645. package/dist/services/codex-reasoning-effort.d.ts +8 -0
  646. package/dist/services/codex-reasoning-effort.d.ts.map +1 -1
  647. package/dist/services/codex-reasoning-effort.js +24 -0
  648. package/dist/services/codex-reasoning-effort.js.map +1 -1
  649. package/dist/services/cursor-resume-policy.d.ts.map +1 -1
  650. package/dist/services/cursor-resume-policy.js +15 -3
  651. package/dist/services/cursor-resume-policy.js.map +1 -1
  652. package/dist/services/open-platform-rename.d.ts.map +1 -1
  653. package/dist/services/open-platform-rename.js +3 -2
  654. package/dist/services/open-platform-rename.js.map +1 -1
  655. package/dist/services/ordinary-turn-recovery.d.ts +76 -0
  656. package/dist/services/ordinary-turn-recovery.d.ts.map +1 -0
  657. package/dist/services/ordinary-turn-recovery.js +349 -0
  658. package/dist/services/ordinary-turn-recovery.js.map +1 -0
  659. package/dist/services/resume-fresh-policy.d.ts +17 -0
  660. package/dist/services/resume-fresh-policy.d.ts.map +1 -0
  661. package/dist/services/resume-fresh-policy.js +24 -0
  662. package/dist/services/resume-fresh-policy.js.map +1 -0
  663. package/dist/services/session-group-title.d.ts +1 -0
  664. package/dist/services/session-group-title.d.ts.map +1 -1
  665. package/dist/services/session-group-title.js +16 -1
  666. package/dist/services/session-group-title.js.map +1 -1
  667. package/dist/services/session-groups-store.d.ts +30 -0
  668. package/dist/services/session-groups-store.d.ts.map +1 -1
  669. package/dist/services/session-groups-store.js +26 -4
  670. package/dist/services/session-groups-store.js.map +1 -1
  671. package/dist/services/session-owner-notification.d.ts +9 -0
  672. package/dist/services/session-owner-notification.d.ts.map +1 -0
  673. package/dist/services/session-owner-notification.js +10 -0
  674. package/dist/services/session-owner-notification.js.map +1 -0
  675. package/dist/services/session-owner-reminder-config-store.d.ts +9 -0
  676. package/dist/services/session-owner-reminder-config-store.d.ts.map +1 -0
  677. package/dist/services/session-owner-reminder-config-store.js +24 -0
  678. package/dist/services/session-owner-reminder-config-store.js.map +1 -0
  679. package/dist/services/session-owner-reminder-store.d.ts +5 -0
  680. package/dist/services/session-owner-reminder-store.d.ts.map +1 -0
  681. package/dist/services/session-owner-reminder-store.js +54 -0
  682. package/dist/services/session-owner-reminder-store.js.map +1 -0
  683. package/dist/services/session-store.d.ts +98 -21
  684. package/dist/services/session-store.d.ts.map +1 -1
  685. package/dist/services/session-store.js +406 -48
  686. package/dist/services/session-store.js.map +1 -1
  687. package/dist/services/structured-bridge-clis.d.ts +9 -5
  688. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  689. package/dist/services/structured-bridge-clis.js +9 -4
  690. package/dist/services/structured-bridge-clis.js.map +1 -1
  691. package/dist/services/transcript-resolver.d.ts +1 -1
  692. package/dist/services/transcript-resolver.d.ts.map +1 -1
  693. package/dist/services/transcript-resolver.js +6 -1
  694. package/dist/services/transcript-resolver.js.map +1 -1
  695. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  696. package/dist/setup/bot-config-editor.js +7 -2
  697. package/dist/setup/bot-config-editor.js.map +1 -1
  698. package/dist/setup/ensure-herdr-integrations.d.ts +13 -0
  699. package/dist/setup/ensure-herdr-integrations.d.ts.map +1 -1
  700. package/dist/setup/ensure-herdr-integrations.js +21 -3
  701. package/dist/setup/ensure-herdr-integrations.js.map +1 -1
  702. package/dist/setup/open-platform-automation.d.ts +1 -0
  703. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  704. package/dist/setup/open-platform-automation.js +107 -7
  705. package/dist/setup/open-platform-automation.js.map +1 -1
  706. package/dist/skills/definitions.d.ts +17 -0
  707. package/dist/skills/definitions.d.ts.map +1 -1
  708. package/dist/skills/definitions.js +78 -8
  709. package/dist/skills/definitions.js.map +1 -1
  710. package/dist/skills/injection-mode.d.ts +24 -3
  711. package/dist/skills/injection-mode.d.ts.map +1 -1
  712. package/dist/skills/injection-mode.js +64 -15
  713. package/dist/skills/injection-mode.js.map +1 -1
  714. package/dist/skills/installer.d.ts +18 -0
  715. package/dist/skills/installer.d.ts.map +1 -1
  716. package/dist/skills/installer.js +45 -1
  717. package/dist/skills/installer.js.map +1 -1
  718. package/dist/types.d.ts +221 -10
  719. package/dist/types.d.ts.map +1 -1
  720. package/dist/utils/child-env.d.ts +92 -2
  721. package/dist/utils/child-env.d.ts.map +1 -1
  722. package/dist/utils/child-env.js +195 -1
  723. package/dist/utils/child-env.js.map +1 -1
  724. package/dist/utils/cli-usage-limit.d.ts +25 -14
  725. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  726. package/dist/utils/cli-usage-limit.js +25 -15
  727. package/dist/utils/cli-usage-limit.js.map +1 -1
  728. package/dist/utils/daemon-rejection-guard.d.ts +35 -0
  729. package/dist/utils/daemon-rejection-guard.d.ts.map +1 -0
  730. package/dist/utils/daemon-rejection-guard.js +101 -0
  731. package/dist/utils/daemon-rejection-guard.js.map +1 -0
  732. package/dist/utils/dashboard-env.d.ts +53 -0
  733. package/dist/utils/dashboard-env.d.ts.map +1 -0
  734. package/dist/utils/dashboard-env.js +116 -0
  735. package/dist/utils/dashboard-env.js.map +1 -0
  736. package/dist/utils/global-install.d.ts.map +1 -1
  737. package/dist/utils/global-install.js +61 -5
  738. package/dist/utils/global-install.js.map +1 -1
  739. package/dist/utils/install-diagnostics.d.ts +14 -7
  740. package/dist/utils/install-diagnostics.d.ts.map +1 -1
  741. package/dist/utils/install-diagnostics.js +20 -11
  742. package/dist/utils/install-diagnostics.js.map +1 -1
  743. package/dist/utils/local-dev-update.d.ts +132 -0
  744. package/dist/utils/local-dev-update.d.ts.map +1 -0
  745. package/dist/utils/local-dev-update.js +176 -0
  746. package/dist/utils/local-dev-update.js.map +1 -0
  747. package/dist/utils/pending-input-queue.d.ts +12 -1
  748. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  749. package/dist/utils/pending-input-queue.js.map +1 -1
  750. package/dist/worker.js +1284 -225
  751. package/dist/worker.js.map +1 -1
  752. package/dist/workflows/definition.d.ts +68 -68
  753. package/dist/workflows/events/payloads.d.ts +14 -14
  754. package/dist/workflows/events/schema.d.ts +312 -312
  755. package/dist/workflows/migration/v2-run-archive-schema.d.ts +40 -40
  756. package/dist/workflows/v3/cli-run.d.ts.map +1 -1
  757. package/dist/workflows/v3/cli-run.js +7 -0
  758. package/dist/workflows/v3/cli-run.js.map +1 -1
  759. package/dist/workflows/v3/goal-cli.d.ts +3 -0
  760. package/dist/workflows/v3/goal-cli.d.ts.map +1 -1
  761. package/dist/workflows/v3/goal-cli.js +11 -0
  762. package/dist/workflows/v3/goal-cli.js.map +1 -1
  763. package/dist/workflows/v3/run-envelope.d.ts +144 -144
  764. package/package.json +3 -2
  765. package/dist/cli/pm2-god-admission.d.ts +0 -8
  766. package/dist/cli/pm2-god-admission.d.ts.map +0 -1
  767. package/dist/cli/pm2-god-admission.js +0 -25
  768. package/dist/cli/pm2-god-admission.js.map +0 -1
  769. package/dist/core/riff-shutdown-detach.d.ts.map +0 -1
  770. package/dist/core/riff-shutdown-detach.js.map +0 -1
  771. package/dist/core/riff-worker-shutdown-readiness.d.ts.map +0 -1
  772. package/dist/core/riff-worker-shutdown-readiness.js.map +0 -1
  773. package/dist/dashboard-web/chunks/bot-defaults-page-65SMZ4QY.js +0 -10
  774. package/dist/dashboard-web/chunks/chunk-G5XGBCM6.js +0 -1
  775. package/dist/dashboard-web/chunks/chunk-IKN6WURK.js +0 -1
  776. package/dist/dashboard-web/chunks/chunk-WLIVBPJC.js +0 -1
  777. package/dist/dashboard-web/chunks/groups-page-VDOOLS4M.js +0 -8
  778. package/dist/dashboard-web/chunks/overview-page-7NYPKQSB.js +0 -1
  779. package/dist/dashboard-web/chunks/sessions-page-DYZZMKFL.js +0 -8
  780. package/dist/dashboard-web/chunks/settings-page-LIULUSMH.js +0 -2
package/dist/daemon.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { readFileSync, existsSync, mkdirSync, unlinkSync } from 'node:fs';
4
+ import { installDaemonRejectionGuard } from './utils/daemon-rejection-guard.js';
4
5
  import { atomicWriteFileSync } from './utils/atomic-write.js';
5
6
  import { readPeerCrossRef } from './services/peer-cross-ref-store.js';
6
7
  import { readAllowedUsersResolveCache, writeAllowedUsersResolveCache } from './utils/allowed-users-cache.js';
@@ -10,7 +11,7 @@ import { fileURLToPath } from 'node:url';
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = dirname(__filename);
12
13
  import { config, getDashboardExternalHost, isVcMeetingAgentGloballyEnabled, vcMeetingAgentGlobalListenerBotAppId, } from './config.js';
13
- import { readGlobalConfig, repoPickerScanOptions } from './global-config.js';
14
+ import { readGlobalConfig, repoPickerScanOptions, isWorkflowFeatureEnabled } from './global-config.js';
14
15
  import { buildDashboardUrls } from './core/dashboard-url.js';
15
16
  import { resolveBotmuxDataDir } from './core/data-dir.js';
16
17
  import { reloadExactDaemonBotConfig } from './core/daemon-config-fence.js';
@@ -64,6 +65,15 @@ import { scrubTmuxServerGlobalEnv } from './setup/ensure-tmux.js';
64
65
  import { entryNeedsContactResolve } from './setup/bot-config-editor.js';
65
66
  import { invalidWorkingDirs } from './utils/working-dir.js';
66
67
  import { validateWorkingDir } from './core/working-dir.js';
68
+ function trustedCallerForTurn(larkAppId, senderOpenId, senderUnionId) {
69
+ if (!senderOpenId && !senderUnionId)
70
+ return undefined;
71
+ return {
72
+ ...(senderOpenId ? { requestUserOpenId: senderOpenId } : {}),
73
+ ...(senderUnionId ? { requestUserUnionId: senderUnionId } : {}),
74
+ requestLarkAppId: larkAppId,
75
+ };
76
+ }
67
77
  import { activeSessionKey, sessionKey, sessionAnchorId, storedSessionAnchorId, larkTransportEnabled, } from './core/types.js';
68
78
  import { stagePendingRepoSetup, persistPendingRepoCardMessageId } from './core/pending-repo-journal.js';
69
79
  import { setTerminalProxyPort, setTerminalExternalPort } from './core/terminal-url.js';
@@ -78,10 +88,11 @@ import { isLocalCliOpenReady } from './services/local-cli-opener.js';
78
88
  import { RECEIVED_REACTION_EMOJI_TYPE, SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE } from './core/pending-response.js';
79
89
  import { t as tr, localeForBot } from './i18n/index.js';
80
90
  import { createCliAdapterSync } from './adapters/cli/registry.js';
81
- import { initWorkerPool, setActiveSessionsRegistry, forkWorker, forkAdoptWorker, sendWorkerInput, promoteQueuedActivationTail, admitQueuedActivationTail, reserveQueuedActivationTailAdmission, codexAppCleanInputAcceptedForSession, hasQueuedActivationAdmissionGate, sendWorkerSessionInput, killWorker, reapOrphanWorkers, scheduleCardPatch, setCurrentCliVersion, getCurrentCliVersion, CARD_POSTING_SENTINEL, parkStreamCard, closeSession as closeSessionHelper, setActiveSessionIfActive, rollbackRejectedSessionAndGetWinner, ensureCliEnv, sweepGlobalBotmuxSkills, writableTerminalLinkFor, workerHasInitialized, sessionSupportsWebTerminal, readableTerminalUrlFor, findActiveBySessionId, withActiveSessionKeyLock, getDaemonBootId, getDaemonStreamingCardUsageSnapshot, postTurnStartingCard, isSessionTransferring, snapshotCodexAppFinalSettlements, codexAppFinalSettlementCount, } from './core/worker-pool.js';
91
+ import { initWorkerPool, setActiveSessionsRegistry, forkWorker, forkAdoptWorker, sendWorkerInput, promoteQueuedActivationTail, admitQueuedActivationTail, reserveQueuedActivationTailAdmission, codexAppCleanInputAcceptedForSession, hasQueuedActivationAdmissionGate, sendWorkerSessionInput, killWorker, retireWorkerProcessOnly, retiringWorkersForSession, reapOrphanWorkers, scheduleCardPatch, setCurrentCliVersion, getCurrentCliVersion, CARD_POSTING_SENTINEL, parkStreamCard, closeSession as closeSessionHelper, closeSessionForBackgroundCleanup, setActiveSessionIfActive, rollbackRejectedSessionAndGetWinner, ensureCliEnv, sweepGlobalBotmuxSkills, writableTerminalLinkFor, workerHasInitialized, sessionSupportsWebTerminal, readableTerminalUrlFor, findActiveBySessionId, withActiveSessionKeyLock, getDaemonBootId, getDaemonStreamingCardUsageSnapshot, postTurnStartingCard, isSessionTransferring, snapshotCodexAppFinalSettlements, codexAppFinalSettlementCount, migrateMojoSessionIdentities, mojoLivePatchForSession, } from './core/worker-pool.js';
82
92
  import { waitAllWithin, trackProducerQuiet, trackProcessExited } from './core/producer-quiescence.js';
83
93
  import { AbortDeadlineError, hasExactSafeJsonKeys, ipcRoute, isTrustedHostIpcRequest, JsonBodyTooLargeError, jsonRes, readJsonBody, runWithAbortDeadline, setBotName, setLarkAppId, startIpcServer, setBotRenamer, setBotAvatarChanger, armCoreOnlyReadinessGate, setCoreOnlyReady, setSupervisorShutdownHandler } from './core/dashboard-ipc-server.js';
84
94
  import { setDeviceIsolationDaemonIdentity } from './core/device-isolation-daemon.js';
95
+ import { reconcileContainmentHandlesOnBoot } from './core/mojo-containment.js';
85
96
  import { cancelSessionReadyAck, waitForSessionReadyAck, } from './core/session-ready-handshake.js';
86
97
  import { loadOrCreateDashboardSecret, loadPersistedToken } from './dashboard/auth.js';
87
98
  import { daemonIpcAuthHeaders, fetchDaemonIpc, loadDaemonIpcSecret } from './core/daemon-ipc-auth.js';
@@ -99,6 +110,7 @@ import { consumeQuota, removeChatGrant, removeGlobalGrant } from './services/gra
99
110
  import { abortCharge, commitCharge, beginCharge } from './services/quota-dedup.js';
100
111
  import { getSessionWorkingDir, getProjectScanDirs, getProjectScanDirsForBot, expandHome, downloadResources, formatAttachmentsHint, buildNewTopicCliInput, buildFollowUpCliInput, buildBridgeInputContent, buildReforkCliInput, getAvailableBots, restoreActiveSessions, executeScheduledTask, persistStreamCardState, rememberLastCliInput, ensureTerminalWorkerPort, ensureSessionWhiteboard, resumeSession, closeCliMismatchedSessionsForBot, } from './core/session-manager.js';
101
112
  import { triggerSessionTurn, reconcileIdempotencyLeasesOnBoot, convergeIdempotentAsyncTurnOnWorkerExit, externalEventOpensOwnTopic } from './core/trigger-session.js';
113
+ import { runIdempotencyFailClose, runWithdrawAutoClose, } from './core/daemon-background-close.js';
102
114
  import { runDetachedBotTurnMutation, tryWithBotTurnMutation, withBotTurnAdmission, withBotTurnMutation, } from './core/bot-turn-mutation-gate.js';
103
115
  import { claimInitialUserTurn, isInitialUserTurnPending, releaseInitialUserTurn } from './core/initial-user-turn.js';
104
116
  import { applyQueuedCodexAppLegacyFallback, mergeQueuedCodexAppTurn } from './core/session-create.js';
@@ -114,11 +126,14 @@ import { HerdrBackend } from './adapters/backend/herdr-backend.js';
114
126
  import { ZellijBackend } from './adapters/backend/zellij-backend.js';
115
127
  import { ZmxBackend } from './adapters/backend/zmx-backend.js';
116
128
  import { sweepIdleWorkersAfterTurnDrain, DEFAULT_MAX_LIVE_WORKERS } from './core/idle-worker-sweeper.js';
117
- import { getSessionPersistentBackendType, killPersistentBackendTarget, killPersistentSession, probePersistentBackendTarget, probePersistentSession, resolvePersistentBackendTarget, resolvePairedSpawnBackendType, } from './core/persistent-backend.js';
129
+ import { DEFAULT_SESSION_OWNER_REMINDER, SessionOwnerReminderController, } from './core/session-owner-reminder.js';
130
+ import { loadSessionOwnerReminderRecords, saveSessionOwnerReminderRecords, } from './services/session-owner-reminder-store.js';
131
+ import { sendSessionOwnerThreadNotification } from './services/session-owner-notification.js';
132
+ import { getSessionPersistentBackendType, isRemoteBackendSession, killPersistentBackendTarget, killPersistentSession, probePersistentBackendTarget, probePersistentSession, resolvePersistentBackendTarget, resolvePairedSpawnBackendType, } from './core/persistent-backend.js';
118
133
  import { handleCardAction, runAutoWorktreeCommit } from './im/lark/card-handler.js';
119
134
  import { setIssueActivate } from './im/lark/issue-command-deps.js';
120
135
  import { startIssueOutboxPump } from './services/issue-outbox-pump.js';
121
- import { parseWorkflowGrillTrigger, buildWorkflowGrillPrompt, isLegacyTemplateCommand, LEGACY_TEMPLATE_RETIRED_MESSAGE, WORKFLOW_USAGE, } from './im/lark/workflow-slash-command.js';
136
+ import { parseWorkflowGrillTrigger, buildWorkflowGrillPrompt, isLegacyTemplateCommand, LEGACY_TEMPLATE_RETIRED_MESSAGE, WORKFLOW_USAGE, WORKFLOW_DISABLED_MESSAGE, } from './im/lark/workflow-slash-command.js';
122
137
  import { parseV3SavedWorkflowCommand, v3SavedWorkflowUsage, } from './im/lark/v3-saved-workflow-command.js';
123
138
  import { authorizeV3SavedWorkflowInvocation, defaultV3SavedWorkflowExecutionServices, deliverV3SavedWorkflowNotification, executeV3SavedWorkflowCommand, resolveV3SavedWorkflowMessageTargets, } from './im/lark/v3-saved-workflow-handler.js';
124
139
  import { createV3GateRunner, preflightV3RunStart, readV3RunChatBinding, requestV3RunCancel, requestV3Retry, requestV3LoopGrant, } from './workflows/v3/daemon-run.js';
@@ -173,7 +188,7 @@ function republishResolvedAllowedUsers(larkAppId, resolved) {
173
188
  catch { /* best effort */ }
174
189
  }
175
190
  let vcMeetingTerminalReconciler;
176
- import { isBotMentioned, probeBotOpenId, startLarkEventDispatcher, markForwardFollowupsSessionsReady, writeBotInfoFile, canOperate, canRunDaemonCommand, evaluateTalk, evaluateBotTalk, evaluateAskAnswerTalk, grantCommandRestriction, isKnownPeerBot, checkRequiredScopes } from './im/lark/event-dispatcher.js';
191
+ import { isBotMentioned, probeBotOpenId, startLarkEventDispatcher, markForwardFollowupsSessionsReady, writeBotInfoFile, canOperate, canRunDaemonCommand, evaluateTalk, evaluateBotTalk, evaluateAskAnswerTalk, askCustomReplyCandidate, grantCommandRestriction, isKnownPeerBot, checkRequiredScopes } from './im/lark/event-dispatcher.js';
177
192
  import { getDocSubscription, listAllDocSubscriptions, putDocSubscription, removeDocSubscription, setDocCommentPollCursor } from './services/doc-subs-store.js';
178
193
  import { BOT_REPLY_SENTINEL, subscribeDocFile, unsubscribeDocFile, addCommentReaction, removeCommentReaction, hasBotSentinel, isBotAuthoredReply, listDocComments } from './im/lark/doc-comment.js';
179
194
  import { learnFromMentions, resolveSender, flushIdentityCacheSync } from './im/lark/identity-cache.js';
@@ -182,7 +197,7 @@ import { buildDocCommentTurnInput, buildDocWatchWarmupTurnInput } from './core/d
182
197
  import { advanceDocCommentCursor, docCommentRepliesAfterCursor, latestDocCommentPollCursor } from './core/doc-comment-poller.js';
183
198
  import { renderBufferedSenderBlock } from './core/session-manager.js';
184
199
  import { shutdownBackendDisposition } from './core/persistent-backend.js';
185
- import { abortRiffShutdownFleet, canAbortVerifiedExitedRiffPreparation, collectUniqueDaemonShutdownSessions, commitPreparedRiffShutdown, isPreparedRiffSessionCurrent, persistPreparedRiffShutdownFleet, prepareRiffFleetForShutdown, } from './core/riff-shutdown-detach.js';
200
+ import { abortRemoteShutdownFleet, canAbortVerifiedExitedRemotePreparation, collectUniqueDaemonShutdownSessions, commitPreparedRemoteShutdown, isPreparedRemoteSessionCurrent, persistPreparedRemoteShutdownFleet, prepareRemoteFleetForShutdown, } from './core/remote-shutdown-detach.js';
186
201
  import { BOT_TURN_MUTATION_SHUTDOWN_ACQUIRE_TIMEOUT_MS, DAEMON_SHUTDOWN_MAX_MS, DAEMON_WORKER_EXIT_GRACE_MS, } from './core/shutdown-budgets.js';
187
202
  import { evaluateVcMeetingConsumerIsolation } from './services/vc-meeting-consumer-isolation.js';
188
203
  import { markSessionActivity, announcePendingRepoSession, publishAttentionPatch, publishLastInputFromBotPatch, publishSessionMessagePreviewPatch, publishClosedSessionPatch, clearAgentAttention, } from './core/session-activity.js';
@@ -202,7 +217,7 @@ import { connectRealtimeVoiceTransport, createProtoRealtimeVoiceProtocol, fetchR
202
217
  import { createGroupWithBots } from './services/group-creator.js';
203
218
  import { addBotToChat, isInChat } from './services/groups-store.js';
204
219
  import { initSessionGroups, isSessionGroup, getSessionGroup, touchSessionGroup } from './services/session-groups-store.js';
205
- import { maybeBirthSessionGroup } from './core/session-group-birth.js';
220
+ import { maybeBirthSessionGroup, declinesSessionGroupBirthAsSlashCommand } from './core/session-group-birth.js';
206
221
  import { scheduleSessionGroupTitle } from './services/session-group-title.js';
207
222
  import { setChatReplyMode } from './services/chat-reply-mode-store.js';
208
223
  import { hasVcMeetingEndedTombstone, listVcMeetingRuntimeSessionsByListenerAndAgent, listVcMeetingRuntimeSessions, pruneExpiredVcMeetingRuntimeSessions, recordVcMeetingEndedTombstone, recordVcMeetingRuntimeSession, removeVcMeetingRuntimeSession, } from './services/vc-meeting-runtime-store.js';
@@ -231,6 +246,9 @@ const activeSessions = new Map();
231
246
  * (codex P1-2). While false, /api/asks returns a retryable 503 for unknown
232
247
  * sessions instead, so the reconnecting hook keeps waiting through the restore. */
233
248
  let sessionsRestored = false;
249
+ /** Once-per-daemon guard for the mojo containment boot reconciliation. The store
250
+ * is bot-agnostic, so it must run once regardless of how many bots start. */
251
+ let mojoContainmentReconciledThisBoot = false;
234
252
  const VC_MEETING_DELIVERY_LEASE_MS = 15 * 60_000;
235
253
  const VC_MEETING_DELIVERY_LEASE_SCAN_MS = 60_000;
236
254
  const VC_MEETING_RUNTIME_EXPIRY_ACK_TIMEOUT_MS = 3_000;
@@ -367,15 +385,37 @@ function vcMeetingPersistentBackendAvailable(backendType) {
367
385
  return ZellijBackend.isAvailable();
368
386
  return ZmxBackend.isAvailable();
369
387
  }
388
+ /**
389
+ * VC receiver teardown: this fence's job is to guarantee the local PRODUCER
390
+ * process is dead before hub replay is admitted. killWorker refuses an
391
+ * unprepared live remote worker (P0-2) — the fail-safe direction for lineage,
392
+ * but for THIS caller a silent refusal is fail-OPEN: the pane probes then read
393
+ * `missing` (mojo has no persistent pane) and the fence cleared while the
394
+ * producer kept running. Remote-frozen receivers are therefore retired
395
+ * process-only — no close IPC, no remote cancel, lineage and containment
396
+ * handles intact — with retireWorkerProcessOnly's SIGTERM→SIGKILL backstop
397
+ * guaranteeing death; local receivers keep killWorker so their backing
398
+ * session is torn down as before.
399
+ */
400
+ function teardownVcReceiverWorker(ds) {
401
+ if (isRemoteBackendSession(ds))
402
+ retireWorkerProcessOnly(ds, 'vc_runtime_lease');
403
+ else
404
+ killWorker(ds);
405
+ }
370
406
  let testOnlyVcMeetingBootBackingMissing;
371
407
  function vcMeetingBootBackingMissing(sessionId, destroy) {
372
408
  if (testOnlyVcMeetingBootBackingMissing) {
373
409
  return testOnlyVcMeetingBootBackingMissing(sessionId, destroy);
374
410
  }
375
411
  const ds = findActiveBySessionId(sessionId);
412
+ // Captured BEFORE teardown: retirement nulls ds.worker while the process
413
+ // dies asynchronously, and the producer-liveness check below needs the real
414
+ // ChildProcess to prove the exit.
415
+ const workerBefore = ds?.worker && !ds.worker.killed ? ds.worker : null;
376
416
  if (destroy && ds) {
377
417
  try {
378
- killWorker(ds);
418
+ teardownVcReceiverWorker(ds);
379
419
  }
380
420
  catch (err) {
381
421
  logger.warn(`[vc-delivery] boot recovery worker teardown failed session=${sessionId}: ${err}`);
@@ -416,6 +456,16 @@ function vcMeetingBootBackingMissing(sessionId, destroy) {
416
456
  + `backend=${backendType} session=${sessionId}`);
417
457
  }
418
458
  }
459
+ // The worker PROCESS is itself the producer this recovery gates on; pane
460
+ // probes say nothing about it (mojo has no pane at all, so they always read
461
+ // `missing`). Clear only once the previously-live worker has actually
462
+ // exited — the retirement's SIGTERM→SIGKILL backstop makes a later reprobe
463
+ // see it gone.
464
+ if (workerBefore && workerBefore.exitCode === null && workerBefore.signalCode === null) {
465
+ allMissing = false;
466
+ logger.error(`[vc-delivery] boot recovery worker process still live; receiver remains gated `
467
+ + `session=${sessionId}`);
468
+ }
419
469
  return allMissing;
420
470
  }
421
471
  function scheduleVcMeetingBootRecoveryProbe(key, sessionId, delayMs) {
@@ -449,9 +499,11 @@ function escalateVcMeetingBootRecovery(key, sessionId) {
449
499
  vcMeetingReceiverRecoveryEscalating.add(key);
450
500
  const ds = findActiveBySessionId(sessionId);
451
501
  if (ds)
452
- killWorker(ds);
453
- // Wait past killWorker's 7s SIGKILL backstop, then direct-kill and probe the
454
- // persisted exact backend. exists/unknown stays scoped-gated and reprobes.
502
+ teardownVcReceiverWorker(ds);
503
+ // Wait past the retirement's SIGTERM→SIGKILL backstop window (killWorker for
504
+ // local receivers, retireWorkerProcessOnly for remote-frozen ones — a plain
505
+ // killWorker would REFUSE a live remote worker and arm nothing), then
506
+ // direct-kill and probe. exists/unknown stays scoped-gated and reprobes.
455
507
  scheduleVcMeetingBootRecoveryProbe(key, sessionId, VC_MEETING_RUNTIME_EXPIRY_TEARDOWN_MS);
456
508
  }
457
509
  function acknowledgeVcMeetingReceiverRecovery(key) {
@@ -580,6 +632,34 @@ function createVcMeetingRuntimeLeaseRecovery(deps) {
580
632
  scheduleReprobe(fence, 'identity conflict');
581
633
  return false;
582
634
  }
635
+ // Captured BEFORE teardown: retirement nulls ds.worker while the process
636
+ // dies asynchronously; the producer-liveness gate below needs the real
637
+ // ChildProcess to prove the exit. Falls back to the process observed at
638
+ // arm time when the registry no longer resolves the ds — a deleted
639
+ // registry entry is not proof the producer died (fourth-round review).
640
+ const armedAlive = fence.armedWorker
641
+ && !fence.armedWorker.killed
642
+ && fence.armedWorker.exitCode === null
643
+ && fence.armedWorker.signalCode === null
644
+ ? fence.armedWorker : null;
645
+ // Third source, for a fence armed AFTER the registry delete (the other
646
+ // half of the collision-loser timing): a process-only retirement is still
647
+ // dying for this session id, and neither ds nor arm-time capture ever saw
648
+ // it. Pin it onto the fence so later reprobes keep tracking the same
649
+ // process.
650
+ let retiringAlive = null;
651
+ if (!armedAlive && !(ds?.worker && !ds.worker.killed)) {
652
+ for (const w of deps.resolveRetiringWorkers?.(fence.receiverSessionId) ?? []) {
653
+ if (w && !w.killed && w.exitCode === null && w.signalCode === null) {
654
+ retiringAlive = w;
655
+ fence.armedWorker = w;
656
+ break;
657
+ }
658
+ }
659
+ }
660
+ const workerBefore = (ds?.worker && !ds.worker.killed ? ds.worker : null)
661
+ ?? armedAlive
662
+ ?? retiringAlive;
583
663
  if (ds) {
584
664
  try {
585
665
  deps.killWorker(ds);
@@ -626,6 +706,17 @@ function createVcMeetingRuntimeLeaseRecovery(deps) {
626
706
  + `attempt=${fence.dispatchAttempt}`);
627
707
  }
628
708
  }
709
+ // The worker PROCESS is itself the producer this fence gates on; pane
710
+ // probes say nothing about it — mojo owns no pane, so they always read
711
+ // `missing`, which is exactly how a refused/incomplete teardown became
712
+ // fail-OPEN (third-round review, gate 3). Clear only once the previously
713
+ // live worker has actually EXITED; the retirement's SIGTERM→SIGKILL
714
+ // backstop guarantees a later reprobe sees it gone.
715
+ if (workerBefore && workerBefore.exitCode === null && workerBefore.signalCode === null) {
716
+ allMissing = false;
717
+ deps.error(`[vc-delivery] runtime lease worker process still live; delivery remains gated `
718
+ + `session=${fence.receiverSessionId} attempt=${fence.dispatchAttempt}`);
719
+ }
629
720
  // PTY has no surviving backing process once its worker has crossed the
630
721
  // SIGKILL backstop. Persistent backends unlock only after every applicable
631
722
  // deterministic name probes authoritatively missing. The backing proof is
@@ -680,8 +771,11 @@ function createVcMeetingRuntimeLeaseRecovery(deps) {
680
771
  deps.error(`[vc-delivery] runtime lease escalation failed to kill worker `
681
772
  + `session=${fence.receiverSessionId}: ${err instanceof Error ? err.message : String(err)}`);
682
773
  }
683
- // killWorker's live path has a 7s SIGKILL backstop. Only after that window
684
- // do we directly kill + probe the deterministic owned pane.
774
+ // The teardown above arms a SIGTERM→SIGKILL backstop (killWorker's close
775
+ // path for local receivers; retireWorkerProcessOnly for remote-frozen
776
+ // ones — a plain killWorker would REFUSE a live remote worker and arm
777
+ // nothing, which is why the fence used to fail open for mojo). Only after
778
+ // that window do we directly kill + probe the deterministic owned pane.
685
779
  fence.timer = setTimeout(() => {
686
780
  if (!isCurrent(fence) || fence.phase !== 'escalating')
687
781
  return;
@@ -718,6 +812,10 @@ function createVcMeetingRuntimeLeaseRecovery(deps) {
718
812
  teardownAndProbe(fence, !ds ? 'missing-session teardown' : 'workerless teardown');
719
813
  return;
720
814
  }
815
+ // Remember the live producer process itself: registry state can stop
816
+ // resolving this ds before the process has died, and the liveness gate in
817
+ // teardownAndProbe must keep seeing it.
818
+ fence.armedWorker = ds.worker;
721
819
  if ((ds.workerGeneration ?? 0) !== ref.workerGeneration) {
722
820
  deps.error(`[vc-delivery] runtime lease worker generation mismatch; escalating `
723
821
  + `session=${ref.receiverSessionId} receipt=${ref.workerGeneration} `
@@ -766,6 +864,12 @@ function createVcMeetingRuntimeLeaseRecovery(deps) {
766
864
  return false;
767
865
  }
768
866
  if (context.disposition === 'queued_removed') {
867
+ // No producer-exit proof required here, deliberately: `queued_removed`
868
+ // is an ACK from the EXACT expected worker generation saying it removed
869
+ // the queued delivery itself. The producer is alive, current and
870
+ // cooperating — the double-delivery risk this fence gates on is gone
871
+ // without anyone dying. Exit proof is demanded only on the teardown
872
+ // paths, where the producer's state is unknown or adversarial.
769
873
  clearFence(fence);
770
874
  return true;
771
875
  }
@@ -837,7 +941,8 @@ const vcMeetingRuntimeLeaseRecovery = createVcMeetingRuntimeLeaseRecovery({
837
941
  throw new Error('receiver worker is not live');
838
942
  ds.worker.send(message);
839
943
  },
840
- killWorker,
944
+ killWorker: teardownVcReceiverWorker,
945
+ resolveRetiringWorkers: retiringWorkersForSession,
841
946
  resolvePersistentScope(ds) {
842
947
  const backendType = getSessionPersistentBackendType(ds);
843
948
  if (backendType)
@@ -1943,19 +2048,34 @@ function scheduleDeferredScheduleSettlement(ds, context) {
1943
2048
  // Exact transcript terminal needs only a short filesystem visibility grace.
1944
2049
  // Screen-only adapters use a longer confirmation and re-check that the same
1945
2050
  // turn is still idle before reclaiming the hidden session.
2051
+ // Remote backends (riff / mojo) report their turn boundary over the network,
2052
+ // so allow the same longer grace as the screen-only path instead of the 300ms
2053
+ // local-filesystem one.
2054
+ const remoteTerminal = ds.session.backendType === 'riff'
2055
+ || ds.session.backendType === 'mojo';
1946
2056
  const delayMs = context.source === 'terminal'
1947
- ? (ds.session.backendType === 'riff' ? 1_500 : 300)
2057
+ ? (remoteTerminal ? 1_500 : 300)
1948
2058
  : 1_500;
1949
2059
  const timer = setTimeout(() => {
1950
2060
  deferredScheduleSettleTimers.delete(sessionId);
1951
2061
  void settleDeferredScheduleRun(ds, context, {
1952
2062
  reconcile: reconcileDeferredTopicBinding,
1953
- closeSession: closeSessionHelper,
2063
+ // Background path: no user surface, so a residual/refusal must at least be
2064
+ // logged with its remote id instead of being discarded.
2065
+ closeSession: (id) => closeSessionForBackgroundCleanup(id, 'deferred-schedule settlement'),
1954
2066
  }).then((result) => {
1955
2067
  if (result.action === 'materialized') {
1956
2068
  logger.info(`[scheduler] Deferred topic materialized session=${sessionId.slice(0, 8)} root=${result.rootMessageId.slice(0, 12)}`);
1957
2069
  return;
1958
2070
  }
2071
+ if (result.action === 'close_refused') {
2072
+ // Must NOT print "Auto-closed": the row is still active and its remote
2073
+ // session may still be running. Two contradictory log lines about the same
2074
+ // session is exactly how a false success hides.
2075
+ logger.error(`[scheduler] Deferred run close REFUSED (${result.error ?? 'unknown'}); session stays `
2076
+ + `ACTIVE session=${sessionId.slice(0, 8)} turn=${context.turnId.slice(0, 12)} source=${context.source}`);
2077
+ return;
2078
+ }
1959
2079
  if (result.action === 'closed') {
1960
2080
  logger.info(`[scheduler] Auto-closed silent deferred run with no botmux send `
1961
2081
  + `session=${sessionId.slice(0, 8)} turn=${context.turnId.slice(0, 12)} source=${context.source}`);
@@ -2375,8 +2495,11 @@ async function maybeSeedCardlessForceTopicTurn(args) {
2375
2495
  }
2376
2496
  }
2377
2497
  async function revokeQuotaGrant(larkAppId, chatId, senderOpenId, ev) {
2498
+ // 会话群里的 chatGrant 是**继承**来的:授权记录挂在出生前那个私聊上(ev.grantChatId),
2499
+ // 不在会话群自己身上。撤销必须落到来源 chat,否则删了个不存在的记录,硬上限失效。
2500
+ const grantChatId = ev.grantChatId ?? chatId;
2378
2501
  const result = ev.reason === 'chatGrant'
2379
- ? await removeChatGrant(larkAppId, chatId, senderOpenId)
2502
+ ? await removeChatGrant(larkAppId, grantChatId, senderOpenId)
2380
2503
  : ev.reason === 'globalGrant'
2381
2504
  ? await removeGlobalGrant(larkAppId, senderOpenId)
2382
2505
  : { ok: true, removed: false };
@@ -2397,6 +2520,20 @@ async function notifyQuotaExhausted(larkAppId, anchor, senderOpenId, limit) {
2397
2520
  export async function enforceMessageQuotaForCliInput(larkAppId, chatId, senderOpenId, messageId, anchor, senderUnionId, memberUnionId, chatType, botSender, opts) {
2398
2521
  if (opts?.listenerAuthorized)
2399
2522
  return true;
2523
+ // 会话群出生轮:**同一条消息**的授权判定与扣费,刚刚由本函数在原 DM 上下文里
2524
+ // 一起做完(handleNewTopicAdmitted 的建群前扣费点:扣费被拒必须零外部副作用,
2525
+ // 所以它必须排在 createGroupWithBots 之前)。这一轮绝不能再复查一次:
2526
+ // • 新群是 bot 刚为这个发送者建的,chat 维度的 talk 来源(chatGrant /
2527
+ // allowedChatGroup / oncall / p2p 专属的 p2pOpen)按定义不可能已经落在它
2528
+ // 上面,复查只会产出假阴性;
2529
+ // • 而这条消息**已经扣过一次费**——被假阴性丢掉就成了「扣了费还把任务丢了」:
2530
+ // 用户额度少一格,任务无声消失,连一句拒绝提示都没有(丢弃分支只打 debug 日志)。
2531
+ // 「扣了费就不能丢任务,丢任务就不能扣费」是这道闸的硬不变量,两侧只能同进同退。
2532
+ // 授权本身并没有被放宽:真正的判定就是 DM 那次(真实 chatId + chatType='p2p'),
2533
+ // 这里只是不拿一个必然更严的新上下文去推翻它。跨 chat 的授权继承缺口是另一件事
2534
+ //(新群里的**后续**消息仍按新 chat 判定),不由这条已扣费的出生轮来承担。
2535
+ if (opts?.alreadyAuthorizedAndCharged)
2536
+ return true;
2400
2537
  // senderUnionId(bot-locked)让 evaluateTalk 认出跨部署团队 peer bot(teamBot 腿);
2401
2538
  // memberUnionId(可为真人 union)走 teamMember 腿——否则外部闸门/群闸门放进来的
2402
2539
  // 团队 bot 或团队成员消息会在这里复查处被静默丢弃(#332 端到端断点,人腿同理)。
@@ -2994,6 +3131,13 @@ async function handleV3SavedWorkflowCommandIfAny(invocation) {
2994
3131
  await notify('❌ Saved Workflow 需要可验证的飞书用户身份。', 'authorization');
2995
3132
  return true;
2996
3133
  }
3134
+ // Machine-wide workflow kill-switch: refuse starting/authoring a workflow when
3135
+ // the feature is off. `cancel` (and other decrease-only management) stays
3136
+ // available so an in-flight run started before the flip can be wound down.
3137
+ if ((command.kind === 'save' || command.kind === 'run') && !isWorkflowFeatureEnabled()) {
3138
+ await notify(WORKFLOW_DISABLED_MESSAGE, 'validation');
3139
+ return true;
3140
+ }
2997
3141
  if (
2998
3142
  // Cancellation is decrease-only and is still protected below by the
2999
3143
  // immutable owner/chat/app binding (or explicit canOperate). Keep it
@@ -3129,6 +3273,7 @@ function beginNewTurn(ds, title, turnId) {
3129
3273
  ds.parkedStreamCardNonce = ds.streamCardNonce;
3130
3274
  ds.frozenCards.set(ds.streamCardNonce, {
3131
3275
  messageId: ds.streamCardId,
3276
+ replyTargetKey: ds.streamCardReplyTargetKey,
3132
3277
  content: ds.lastScreenContent ?? '',
3133
3278
  title: prevTitle,
3134
3279
  displayMode: prevMode,
@@ -3686,10 +3831,16 @@ async function adoptCodexNotifierEvent(larkAppId, event, cardMessageId, ownerOpe
3686
3831
  }
3687
3832
  // 接管原生 Codex App 线程时固定为纯 codex-app 启动,不能继承通知 Bot 的
3688
3833
  // wrapper/model;这些配置针对普通 CLI,会错误包装 app-server runner。
3834
+ // model 由 resolveSessionLaunchModel 在每次 spawn 时解析:这里把 cliId 钉成
3835
+ // codex-app 后,若通知 Bot 自己不是 codex-app,session 与 bot 的 cliId 不匹配
3836
+ // → 不会拿 bot 那个面向别的 CLI 的 model;清掉历史记录值让兜底也为空。
3837
+ // spawnModelOverride(trigger 的一次性 model)优先级最高且无条件,必须一并清掉,
3838
+ // 否则它会泄漏进接管后的 codex-app 启动。
3689
3839
  ds.session.cliId = 'codex-app';
3690
3840
  ds.session.cliPathOverride = botCfg.cliPathOverride;
3691
3841
  delete ds.session.wrapperCli;
3692
3842
  delete ds.session.model;
3843
+ ds.spawnModelOverride = undefined;
3693
3844
  ds.session.agentFrozen = true;
3694
3845
  sessionStore.updateSession(ds.session);
3695
3846
  // 默认 flat DM(p2pMode=chat)下这条按钮回调复用同一私聊的现有会话。若该会话
@@ -3907,6 +4058,14 @@ workflowDaemonMutationRoute('start', async (reply, params, _body, identity) => {
3907
4058
  const runId = params.runId;
3908
4059
  if (!isValidV3RunId(runId))
3909
4060
  return reply(400, { ok: false, error: 'bad_run_id' });
4061
+ // Machine-wide workflow kill-switch: refuse launching a NEW daemon-driven run
4062
+ // when the feature is off. This is the common sink for both `botmux workflow
4063
+ // start` and the grill `approve-dag` kick, so gating here closes the edge where
4064
+ // a run was grilled+approved before the flip. Restart recovery of an
4065
+ // already-started run (coldAttach / reconcileV3PendingGates) is a separate path
4066
+ // and is intentionally NOT gated, and `retry`/`grant`/`cancel` stay available.
4067
+ if (!isWorkflowFeatureEnabled())
4068
+ return reply(409, { ok: false, error: 'workflow_disabled' });
3910
4069
  const runDir = join(v3DefaultBaseDir(), runId);
3911
4070
  const preflight = preflightV3RunStart(runDir);
3912
4071
  if (!preflight.ok) {
@@ -13697,12 +13856,14 @@ export const __testOnly_onQueuedActivationSubmitted = onQueuedActivationSubmitte
13697
13856
  * deliberately no await between the final buffered-input snapshot, fork, and
13698
13857
  * release: a later handler either buffers before this block or observes the
13699
13858
  * live worker after it. */
13700
- function forkReservedInitialSession(ds, availableBots) {
13859
+ function forkReservedInitialSession(ds, availableBots, trustedCaller) {
13701
13860
  const userPrompt = ds.pendingPrompt ?? '';
13702
13861
  const input = buildReservedInitialInput(ds, availableBots);
13862
+ if (trustedCaller)
13863
+ input.trustedCaller = trustedCaller;
13703
13864
  rememberLastCliInput(ds, userPrompt, input);
13704
13865
  const turnId = ds.pendingTurnId ?? ds.session.pendingRepoSetup?.turnId;
13705
- forkWorker(ds, input, turnId ? { turnId } : false);
13866
+ forkWorker(ds, input, turnId ? { turnId, trustedCaller } : false);
13706
13867
  ds.pendingTurnId = undefined;
13707
13868
  // A no-project pendingRepo fallback enters forkWorker with `session.queued`
13708
13869
  // still set. forkWorker materializes the exact opening as a tokened
@@ -13805,15 +13966,15 @@ function isInitialSessionPassthrough(larkAppId, cmd) {
13805
13966
  /** `/fast` is a passthrough keystroke to Codex's native tier toggle — it only
13806
13967
  * reaches the executor on a real paste TUI. In RPC input mode the pane is a
13807
13968
  * pure viewer (turns go over JSON-RPC, keystrokes never reach the app-server),
13808
- * and the Riff backend turns the text+Enter into two remote task writes. On
13809
- * those backends the toggle is a silent no-op (or worse), so fail closed:
13969
+ * and a remote backend (riff / mojo) turns the text+Enter into two independent
13970
+ * remote turns. On those backends the toggle is a silent no-op (or worse), so fail closed:
13810
13971
  * reject with a clear message instead of pretending it worked. The read-only
13811
13972
  * card badge is unaffected — it reflects whatever tier Codex actually runs. */
13812
13973
  function fastToggleUnsupportedBackend(ds) {
13813
13974
  if (!ds)
13814
13975
  return false;
13815
13976
  const backendType = ds.initConfig?.backendType ?? ds.session.backendType;
13816
- if (backendType === 'riff')
13977
+ if (backendType === 'riff' || backendType === 'mojo')
13817
13978
  return true;
13818
13979
  return ds.initConfig?.codexRpcInput === true;
13819
13980
  }
@@ -13888,6 +14049,9 @@ function deliverPassthroughToExistingSession(ds, cmd, commandContent, anchor, la
13888
14049
  sendWorkerSessionInput(ds, {
13889
14050
  type: 'raw_input',
13890
14051
  content: commandContent,
14052
+ // Same reason as the worker-pool raw_input send: a passthrough is a real
14053
+ // mojo turn, so it needs the live credential snapshot too.
14054
+ ...(mojoLivePatchForSession(ds) ?? {}),
13891
14055
  turnId: turn.messageId,
13892
14056
  });
13893
14057
  turn.onDelivered?.();
@@ -14152,6 +14316,7 @@ async function handleNewTopicAdmitted(data, ctx) {
14152
14316
  // routing into thread-scope so the bot's first reply seeds a Lark thread.
14153
14317
  let scope = ctx.scope;
14154
14318
  let anchor = ctx.anchor;
14319
+ let quotaConsumedBeforeSessionGroupBirth = false;
14155
14320
  // ─── p2pMode='group':会话群出生 ────────────────────────────────────────
14156
14321
  // group 模式下,顶层 DM 消息在任何会话机制启动前先改道:建一个专属会话群
14157
14322
  //(bot 保留群主),把本轮 context 重写到新群(chat-scope)后递归。返回 null
@@ -14167,10 +14332,32 @@ async function handleNewTopicAdmitted(data, ctx) {
14167
14332
  && !substituteTrigger
14168
14333
  && scope === 'thread'
14169
14334
  && anchor === messageId
14170
- && !data?.message?.thread_id) {
14335
+ // 「话题内发送 + 同时发送到会话」的消息 root_id 为空但带 thread_id——
14336
+ // 语境属于既有话题,绝不当全新会话建群(回退 thread 旧行为)。
14337
+ && !data?.message?.thread_id
14338
+ // 斜杠命令判定必须**早于扣费**:/help、/login、/close 本来就不进 CLI,既不该
14339
+ // 被预扣一次额度,也不该在额度耗尽时被这道闸静默丢掉(下面的 return 会让命令
14340
+ // 根本执行不到)。maybeBirthSessionGroup 对同一条消息本就返回 null(命令不建
14341
+ // 群),所以这里只是把它的结论提前一步,建群行为一字未改;命令随后照常走
14342
+ // parseSlashCommandInvocation 分支,真正会注入 CLI 的消息(含未注册的 /foo,
14343
+ // 它落不进命令分支)仍由下面 pre-worker 那道闸按原样扣一次费——净扣费不变。
14344
+ // 谓词刻意复用 birth 内部的同一个:两侧同源才能保证「跳过扣费」与「不建群」
14345
+ // 是同一个判断——窄于它会退回本 bug,宽于它则会让本该建群的消息不再建群。
14346
+ && !declinesSessionGroupBirthAsSlashCommand(data)) {
14347
+ // A quota denial must have zero external side effects. Charge the original
14348
+ // DM before creating the real Feishu chat, then mark the rewritten turn so
14349
+ // the ordinary pre-worker gate below does not charge it a second time.
14350
+ const quotaSenderOpenId = data.sender?.sender_id?.open_id;
14351
+ const quotaSenderUnionId = data.sender?.sender_id?.union_id;
14352
+ const quotaTeamTrustUnionId = (data.sender?.sender_type === 'app' || data.sender?.sender_type === 'bot')
14353
+ ? quotaSenderUnionId
14354
+ : undefined;
14355
+ if (!await enforceMessageQuotaForCliInput(larkAppId, chatId, quotaSenderOpenId, messageId, anchor, quotaTeamTrustUnionId, quotaSenderUnionId, chatType))
14356
+ return;
14357
+ quotaConsumedBeforeSessionGroupBirth = true;
14171
14358
  const reborn = await maybeBirthSessionGroup(data, ctx);
14172
14359
  if (reborn)
14173
- return handleNewTopic(data, reborn);
14360
+ return handleNewTopic(data, { ...reborn, sessionGroupQuotaConsumed: true });
14174
14361
  }
14175
14362
  // ─── 会话群同群续聊 ─────────────────────────────────────────────────────
14176
14363
  // 会话群里的新消息发现登记的会话已关闭时,自动 resume 原会话(与话题内续聊
@@ -14252,6 +14439,7 @@ async function handleNewTopicAdmitted(data, ctx) {
14252
14439
  }
14253
14440
  // senderOpenId 已在上方(force-topic grant 限制前)声明;这里只补 master 新增的 senderUnionId。
14254
14441
  const senderUnionId = data.sender?.sender_id?.union_id;
14442
+ const trustedCaller = trustedCallerForTurn(larkAppId, senderOpenId, senderUnionId);
14255
14443
  // union_id 信任腿(canOperate/evaluateTalk 的 teamBot)只对**飞书盖章的 bot 发送方**
14256
14444
  // 生效:平台 roster 是成员机器自报的,若不锁 sender_type,恶意成员把某个真人的
14257
14445
  // union_id 报成"自家 bot",那个真人就会在全团队机器上被当队友 bot 放行(talk +
@@ -14328,6 +14516,10 @@ async function handleNewTopicAdmitted(data, ctx) {
14328
14516
  let workflowGrillPrompt;
14329
14517
  const newTopicGrill = parseWorkflowGrillTrigger(cmdContent);
14330
14518
  if (newTopicGrill) {
14519
+ if (!isWorkflowFeatureEnabled()) {
14520
+ await sessionReply(anchor, WORKFLOW_DISABLED_MESSAGE, 'text', larkAppId);
14521
+ return;
14522
+ }
14331
14523
  if (await replyGrantRestrictionIfNeeded(larkAppId, chatId, senderOpenId, anchor, '/workflow')) {
14332
14524
  return;
14333
14525
  }
@@ -14551,9 +14743,20 @@ async function handleNewTopicAdmitted(data, ctx) {
14551
14743
  && buildQuoteHint(parsed, scope, anchor, localeForBot(larkAppId)) === ''
14552
14744
  && !ctx.forwardSeedData
14553
14745
  && !messageListener;
14746
+ // Session-group birth charges the ORIGINAL DM before creating the Feishu chat
14747
+ // (a quota denial must have zero external side effects), so by the time the
14748
+ // rewritten group turn gets here THIS message is already authorized AND
14749
+ // already paid for — see `alreadyAuthorizedAndCharged` in
14750
+ // enforceMessageQuotaForCliInput for why re-deciding it here can only lose
14751
+ // the unit we just took. `skipCharge` stays set as well so that even if that
14752
+ // short-circuit is ever removed, the worst case is a redundant recheck, never
14753
+ // a double charge.
14754
+ const sessionGroupAlreadyCharged = !!ctx.sessionGroupQuotaConsumed
14755
+ || quotaConsumedBeforeSessionGroupBirth;
14554
14756
  if (!await enforceMessageQuotaForCliInput(larkAppId, chatId, senderOpenId, messageId, anchor, teamTrustUnionId, senderUnionId, chatType, isBotSenderType, {
14555
14757
  listenerAuthorized: !!messageListener,
14556
- skipCharge: isBareForceTopic,
14758
+ skipCharge: isBareForceTopic || sessionGroupAlreadyCharged,
14759
+ alreadyAuthorizedAndCharged: sessionGroupAlreadyCharged,
14557
14760
  })) {
14558
14761
  return;
14559
14762
  }
@@ -14835,7 +15038,7 @@ async function handleNewTopicAdmitted(data, ctx) {
14835
15038
  // Ack reaction targets the ORIGINAL inbound message (2nd arg); the turn id
14836
15039
  // (5th arg) is the reply anchor so provenance holds on session-group births.
14837
15040
  await noteTurnReceived(ds, messageId, content, newTopicSender, replyAnchorId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
14838
- forkReservedInitialSession(ds, availableBots);
15041
+ forkReservedInitialSession(ds, availableBots, trustedCaller);
14839
15042
  // fork 成功即开场已交给 CLI;fork 抛错则开场只存在于内存,保持重发提示。
14840
15043
  markIngressAdmitted(ctx);
14841
15044
  const reason = oncallEntry
@@ -14887,7 +15090,7 @@ async function handleNewTopicAdmitted(data, ctx) {
14887
15090
  // Ack reaction targets the ORIGINAL inbound message (2nd arg); the turn id
14888
15091
  // (5th arg) is the reply anchor so provenance holds on session-group births.
14889
15092
  await noteTurnReceived(ds, messageId, content, newTopicSender, replyAnchorId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
14890
- forkReservedInitialSession(ds, availableBots);
15093
+ forkReservedInitialSession(ds, availableBots, trustedCaller);
14891
15094
  logger.info(`Session ${session.sessionId} ready (no projects to select), total active: ${getActiveCount()}`);
14892
15095
  }
14893
15096
  }
@@ -15093,6 +15296,15 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId, opts) {
15093
15296
  anchor = chatId;
15094
15297
  scope = 'chat';
15095
15298
  }
15299
+ // bot.added 是成员变更事件,没有用户 message_id 可作首轮 turn 身份。非 shared
15300
+ // 路径没有 shared reply seed 兜底,若以 pendingTurnId=undefined 直接 fork,
15301
+ // worker 发布的 managed_turn_origin 不含 turnId,CLI 首轮 `botmux send` 的
15302
+ // managed-origin attestation 会被 daemon 以 origin_unproven 拒绝——首轮回复
15303
+ // 发不回飞书。这里自行铸造首轮 turn 身份:
15304
+ // - thread scope:seed 消息本身就是可见锚点且是真实 om_ id,直接复用;
15305
+ // - chat scope:没有任何消息存在,铸造 daemon-owned 合成 turn id(同 trigger
15306
+ // 会话的 trg_ 约定),仅作身份标识,不冒充用户消息 id。
15307
+ const joinTurnId = scope === 'thread' ? anchor : `join_${randomUUID()}`;
15096
15308
  const dsKey = sessionKey(anchor, larkAppId);
15097
15309
  if (activeSessions.has(dsKey)) {
15098
15310
  logger.info(`[auto-start:入群] ${chatId.substring(0, 12)} 锚点已有会话,跳过`);
@@ -15135,7 +15347,9 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId, opts) {
15135
15347
  ownerOpenId: operatorOpenId,
15136
15348
  currentTurnTitle: title,
15137
15349
  workingDir: pinnedWorkingDir,
15138
- pendingTurnId: undefined,
15350
+ // 非 shared 路径的首轮 turn 身份(joinTurnId);shared 路径由
15351
+ // armSharedReplyTarget 用 shared seed id 覆盖。
15352
+ pendingTurnId: needsSharedReply ? undefined : joinTurnId,
15139
15353
  };
15140
15354
  const registration = await claimNewDaemonSession(activeSessions, ds);
15141
15355
  if (!registration.accepted) {
@@ -15148,7 +15362,9 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId, opts) {
15148
15362
  stageClaimedPendingRepoSetup(activeSessions, ds, {
15149
15363
  mode: autoWt ? 'auto_worktree' : 'picker',
15150
15364
  ...(autoWt && pinnedWorkingDir ? { baseDir: pinnedWorkingDir } : {}),
15151
- turnId: anchor,
15365
+ // 非 shared 路径复用首轮 turn 身份;shared 路径保留 anchor(随后被
15366
+ // armSharedReplyTarget 的 shared seed id 覆盖,此处仅作未 arm 时的兜底)。
15367
+ turnId: needsSharedReply ? anchor : joinTurnId,
15152
15368
  });
15153
15369
  }
15154
15370
  const joinBootstrapExternallyTakenOver = () => (activeSessions.get(dsKey) !== ds
@@ -15195,6 +15411,11 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId, opts) {
15195
15411
  const closing = closeSessionHelper(session.sessionId);
15196
15412
  if (activeSessions.get(dsKey) === ds)
15197
15413
  activeSessions.delete(dsKey);
15414
+ // 入群去重锚点同样只清自己登记的那条:shared 路径在登记锚点之前就可能
15415
+ // 回滚(此时 get 为 undefined,no-op);非 shared 路径在锚点登记之后
15416
+ // 回滚,不清掉会让后续 bot.added 带着 stale 锚点走进来。
15417
+ if (groupJoinAnchorByChat.get(chatLiveKey) === dsKey)
15418
+ groupJoinAnchorByChat.delete(chatLiveKey);
15198
15419
  await closing;
15199
15420
  };
15200
15421
  // Shared-mode seed/reply authority is externally visible. Publish it only
@@ -15245,6 +15466,28 @@ async function handleBotAdded(chatId, operatorOpenId, larkAppId, opts) {
15245
15466
  // Register the anchor so a later duplicate bot.added for this chat is deduped
15246
15467
  // even in 话题群 (where dsKey is the seed id, not chatId).
15247
15468
  groupJoinAnchorByChat.set(chatLiveKey, dsKey);
15469
+ // 持久化非 shared 首轮的 turn provenance(shared 路径已由 armSharedReplyTarget
15470
+ // 写入)。thread scope 以 seed 为 reply root;chat scope 无根,落 plain chat
15471
+ // 目标且不写 sender——首轮无真人发送者,--mention 回指天然走 incomplete →
15472
+ // 显式指定的安全方向。没有这条记录,worker 虽带 turnId fork,daemon 侧的
15473
+ // 首轮回复目标/参与者窗口仍无据可查。
15474
+ if (!needsSharedReply) {
15475
+ try {
15476
+ beginReplyTargetTurn(ds, scope === 'thread' ? anchor : undefined, joinTurnId, new Date(now).toISOString());
15477
+ sessionStore.updateSession(ds.session);
15478
+ }
15479
+ catch (err) {
15480
+ // 持久化失败(文件系统/数据库瞬态错误)时必须回滚已发布的会话:此时
15481
+ // 会话已进 activeSessions 且 groupJoinAnchorByChat 已登记,finally 只
15482
+ // settle barrier 和释放 in-flight 锁——留下 active 但 worker-null 的会话
15483
+ // 会让后续 bot.added 被无限去重,自动开工无法重试。同
15484
+ // stageClaimedPendingRepoSetup 的 unpublish/close 回滚。
15485
+ logger.warn(`[auto-start:入群] ${chatId.substring(0, 12)} 首轮 turn provenance 持久化失败,回滚会话注册:`
15486
+ + `${err instanceof Error ? err.message : String(err)}`);
15487
+ await rollbackRegisteredJoinSession();
15488
+ return;
15489
+ }
15490
+ }
15248
15491
  // Auto-worktree: register PENDING, build worktree off-path, commit+fork later.
15249
15492
  if (pinnedWorkingDir && autoWt) {
15250
15493
  if (await replyInvalidWorkingDirs(anchor, larkAppId, ds) || joinBootstrapWasTakenOver()) {
@@ -15482,6 +15725,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
15482
15725
  // 类型的话,把真人 union_id 报成 bot 就能让真人在全团队被当队友 bot 放行
15483
15726
  //(talk + operate)。与旧联邦 team-bots「学习入口限 bot sender」同一不变量。
15484
15727
  const threadTeamTrustUnionId = (isBotSenderType || isForeignBot) ? threadSenderUnionId : undefined;
15728
+ const threadTrustedCaller = trustedCallerForTurn(larkAppId, threadSenderOpenId, threadSenderUnionId);
15485
15729
  const threadChatId = ctxChatId ?? data?.message?.chat_id;
15486
15730
  const clearAgentAttentionForHumanInbound = () => {
15487
15731
  if (isForeignBot || isBotSenderType)
@@ -15540,6 +15784,10 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
15540
15784
  // `/template` 只保留退役提示。
15541
15785
  const threadGrill = parseWorkflowGrillTrigger(cmdContent);
15542
15786
  if (threadGrill) {
15787
+ if (!isWorkflowFeatureEnabled()) {
15788
+ await sessionReply(anchor, WORKFLOW_DISABLED_MESSAGE, 'text', larkAppId);
15789
+ return;
15790
+ }
15543
15791
  if (await replyGrantRestrictionIfNeeded(larkAppId, threadChatId, threadSenderOpenId, anchor, '/workflow')) {
15544
15792
  return;
15545
15793
  }
@@ -15779,35 +16027,38 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
15779
16027
  // 回调 URL 已在上方 return,可用来中止)。答复权限 = canTalk,由
15780
16028
  // broker 在 submitCustomReply 内按注入的 canTalkChecker 判定:非授权人返回
15781
16029
  // 'unauthorized',这里 fall through 到正常路由。卡片由 broker.onSettle 自动 PATCH。
15782
- // `!threadGrill`:grill goal 分支只改写 promptContent 后 fall-through(不 return),
15783
- // cmdContent 仍是字面量 `/workflow new <目标>`;若不排除,待回答 ask 会把它当答案吞掉,
15784
- // grill 永远不启动。grill 必须穿过拦截器走正常转发。
15785
- if (threadSenderOpenId && threadChatId && !threadGrill) {
15786
- const askReplyText = cmdContent.trim();
15787
- if (askReplyText) {
15788
- const pendingAsk = findPendingAskByAnchor({ larkAppId, chatId: threadChatId, anchor });
15789
- if (pendingAsk) {
15790
- const outcome = submitCustomReply({
15791
- askId: pendingAsk.askId,
15792
- by: threadSenderOpenId,
15793
- text: askReplyText,
15794
- // Actor context so the broker's talk check uses the same predicate as
15795
- // the dispatcher gate / quota recheck: a bot text-reply → evaluateBotTalk
15796
- // (covers team-拉群 with no union_id), a platform teamMember human →
15797
- // evaluateTalk's teamMember union leg. Omitting it (card clicks) degrades
15798
- // to the plain evaluateTalk(openId, chatType).
15799
- actor: {
15800
- botSender: isBotSenderType || isForeignBot,
15801
- senderUnionId: threadTeamTrustUnionId,
15802
- memberUnionId: threadSenderUnionId,
15803
- },
15804
- });
15805
- if (outcome === 'accepted') {
15806
- logger.info(`[${anchor.substring(0, 12)}] ask custom reply accepted from ${threadSenderOpenId.substring(0, 12)}`);
15807
- return;
15808
- }
15809
- logger.info(`[${anchor.substring(0, 12)}] ask custom reply not accepted (${outcome}); falling through to normal routing`);
16030
+ // 「哪些消息算纯文字答复」收口在 askCustomReplyCandidate(含带资源消息与 grill
16031
+ // 触发的排除理由),拦截器这里只负责拿着结果去 settle。
16032
+ const askCandidate = askCustomReplyCandidate({
16033
+ senderOpenId: threadSenderOpenId,
16034
+ chatId: threadChatId,
16035
+ cmdContent,
16036
+ resourceCount: resources.length,
16037
+ isWorkflowGrillTrigger: !!threadGrill,
16038
+ });
16039
+ if (askCandidate) {
16040
+ const pendingAsk = findPendingAskByAnchor({ larkAppId, chatId: askCandidate.chatId, anchor });
16041
+ if (pendingAsk) {
16042
+ const outcome = submitCustomReply({
16043
+ askId: pendingAsk.askId,
16044
+ by: askCandidate.senderOpenId,
16045
+ text: askCandidate.text,
16046
+ // Actor context so the broker's talk check uses the same predicate as
16047
+ // the dispatcher gate / quota recheck: a bot text-reply → evaluateBotTalk
16048
+ // (covers team-拉群 with no union_id), a platform teamMember human →
16049
+ // evaluateTalk's teamMember union leg. Omitting it (card clicks) degrades
16050
+ // to the plain evaluateTalk(openId, chatType).
16051
+ actor: {
16052
+ botSender: isBotSenderType || isForeignBot,
16053
+ senderUnionId: threadTeamTrustUnionId,
16054
+ memberUnionId: threadSenderUnionId,
16055
+ },
16056
+ });
16057
+ if (outcome === 'accepted') {
16058
+ logger.info(`[${anchor.substring(0, 12)}] ask custom reply accepted from ${askCandidate.senderOpenId.substring(0, 12)}`);
16059
+ return;
15810
16060
  }
16061
+ logger.info(`[${anchor.substring(0, 12)}] ask custom reply not accepted (${outcome}); falling through to normal routing`);
15811
16062
  }
15812
16063
  }
15813
16064
  logger.info(`Reply in ${scope}-scope session ${anchor.substring(0, 12)}: ${content.substring(0, 100)} (resources: ${resources.length})`);
@@ -16396,7 +16647,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16396
16647
  ensureSessionWhiteboard(newDs);
16397
16648
  const availableBots = await getAvailableBots(larkAppId, autoCreateChatId);
16398
16649
  await noteTurnReceived(newDs, parsed.messageId, parsed.content, autoCreateSender, parsed.messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
16399
- forkReservedInitialSession(newDs, availableBots);
16650
+ forkReservedInitialSession(newDs, availableBots, threadTrustedCaller);
16400
16651
  // fork 成功即开场已交给 CLI;fork 抛错则开场只存在于内存,保持重发提示。
16401
16652
  markIngressAdmitted(ctx);
16402
16653
  const reason = oncallEntry
@@ -16436,7 +16687,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16436
16687
  ensureSessionWhiteboard(newDs);
16437
16688
  const availableBots = await getAvailableBots(larkAppId, autoCreateChatId);
16438
16689
  await noteTurnReceived(newDs, parsed.messageId, parsed.content, autoCreateSender, parsed.messageId, substituteTrigger ? SUBSTITUTE_RECEIVED_REACTION_EMOJI_TYPE : undefined);
16439
- forkReservedInitialSession(newDs, availableBots);
16690
+ forkReservedInitialSession(newDs, availableBots, threadTrustedCaller);
16440
16691
  }
16441
16692
  return;
16442
16693
  }
@@ -16514,7 +16765,10 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16514
16765
  // above (R4-B1); reuse the same frozen value here for the live-worker path.
16515
16766
  let accepted = false;
16516
16767
  try {
16517
- accepted = sendWorkerInput(ds, cliInput, parsed.messageId, codexAppSteerable ? { codexAppSteerable: true } : {});
16768
+ accepted = sendWorkerInput(ds, cliInput, parsed.messageId, {
16769
+ ...(codexAppSteerable ? { codexAppSteerable: true } : {}),
16770
+ ...(threadTrustedCaller ? { trustedCaller: threadTrustedCaller } : {}),
16771
+ });
16518
16772
  // Record the input as the session's last real CLI turn ONLY after the
16519
16773
  // worker accepted it. Recording before delivery (the old order) persisted
16520
16774
  // lastCliInput / lastUserPrompt / Codex-App sidecar for a turn that never
@@ -16610,6 +16864,8 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16610
16864
  // here, never re-inferred). System/recovery openings keep it absent.
16611
16865
  if (codexAppSteerable)
16612
16866
  currentFollowUp.codexAppSteerable = true;
16867
+ if (threadTrustedCaller)
16868
+ currentFollowUp.trustedCaller = threadTrustedCaller;
16613
16869
  admitQueuedActivationTail(ds, {
16614
16870
  userPrompt: promptContent,
16615
16871
  cliInput: currentFollowUp,
@@ -16636,6 +16892,9 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16636
16892
  resume: ds.session.queuedActivationResume ?? ds.hasHistory,
16637
16893
  turnId: ds.session.queuedActivationTurnId,
16638
16894
  dispatchAttempt: ds.session.queuedActivationDispatchAttempt,
16895
+ ...(ds.session.queuedActivationInput?.trustedCaller
16896
+ ? { trustedCaller: ds.session.queuedActivationInput.trustedCaller }
16897
+ : {}),
16639
16898
  });
16640
16899
  if (ownsInitialStartClaim())
16641
16900
  retainInitialStartClaim = true;
@@ -16661,6 +16920,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16661
16920
  resume: ds.hasHistory,
16662
16921
  turnId: ds.session.queuedActivationTurnId,
16663
16922
  dispatchAttempt: ds.session.queuedActivationDispatchAttempt,
16923
+ ...(retainedQueuedActivation.trustedCaller ? { trustedCaller: retainedQueuedActivation.trustedCaller } : {}),
16664
16924
  });
16665
16925
  if (ownsInitialStartClaim())
16666
16926
  retainInitialStartClaim = true;
@@ -16783,6 +17043,9 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16783
17043
  if (codexAppSteerable && !queuedDashboardTurn && !queuedHasDurableTail) {
16784
17044
  wrappedInput.codexAppSteerable = true;
16785
17045
  }
17046
+ if (threadTrustedCaller && !queuedHasDurableTail) {
17047
+ wrappedInput.trustedCaller = threadTrustedCaller;
17048
+ }
16786
17049
  reforkAccepted = forkWorker(ds, wrappedInput, {
16787
17050
  // See `hadPriorCliInput` above — an opening on a CLI that never took any
16788
17051
  // input cold-spawns rather than `--resume`-ing an empty session.
@@ -16790,6 +17053,7 @@ async function handleThreadReplyAdmitted(data, ctx, prepared) {
16790
17053
  turnId: queuedHasDurableTail
16791
17054
  ? (ds.session.queuedActivationTurnId ?? `queued-opening:${ds.session.sessionId}`)
16792
17055
  : parsed.messageId,
17056
+ ...(wrappedInput.trustedCaller ? { trustedCaller: wrappedInput.trustedCaller } : {}),
16793
17057
  });
16794
17058
  }
16795
17059
  }
@@ -17470,6 +17734,18 @@ async function waitForManagedActivationCommit(index, appId) {
17470
17734
  }
17471
17735
  }
17472
17736
  export async function startDaemon(botIndex) {
17737
+ // Survive a fire-and-forget rejection instead of dying from it. Installed here
17738
+ // rather than in index-daemon.ts on purpose: startDaemon has TWO entry points
17739
+ // (index-daemon.ts and index-core-only.ts), so guarding the entry file would
17740
+ // leave the other one unprotected. Idempotent, so an entry point may also call
17741
+ // it earlier without harm.
17742
+ //
17743
+ // Node's default is to terminate the process, which is right for a worker (it
17744
+ // owns one session) and wrong for the daemon (it owns every session): one
17745
+ // missed `.catch` on one session's teardown would otherwise end every other
17746
+ // live Lark session too. The rejection is logged loudly at error level — this
17747
+ // is a backstop for the next missed `.catch`, not permission to omit them.
17748
+ installDaemonRejectionGuard(logger);
17473
17749
  // Repair a shared tmux server polluted by an older botmux immediately on
17474
17750
  // daemon startup. This must not depend on restoring/spawning a bmx-* session:
17475
17751
  // a user-held tmux server can outlive every botmux pane and still leak stale
@@ -17886,8 +18162,7 @@ export async function startDaemon(botIndex) {
17886
18162
  // status becomes `closed` → trigger-result resolves `failed` (soft terminal),
17887
18163
  // and resolveIdempotencyHit sees a not-live session on retry. This is the
17888
18164
  // observable fail-closed path the round-8 finding requires.
17889
- void closeSessionHelper(ds.session.sessionId).catch(() => { });
17890
- logger.error(`[idempotency] fail-closed session ${ds.session.sessionId.slice(0, 8)} after exit-convergence write failure`);
18165
+ void runIdempotencyFailClose(ds.session.sessionId, closeSessionForBackgroundCleanup, logger);
17891
18166
  }
17892
18167
  };
17893
18168
  // Initialise worker pool with daemon callbacks
@@ -17908,9 +18183,7 @@ export async function startDaemon(botIndex) {
17908
18183
  logger.info(`[${sessionId.substring(0, 8)}] Withdraw auto-close deferred: Codex App FIFO became unsettled`);
17909
18184
  return false;
17910
18185
  }
17911
- await closeSessionHelper(sessionId);
17912
- logger.info(`[${sessionId.substring(0, 8)}] Session auto-closed (message withdrawn)`);
17913
- return true;
18186
+ return runWithdrawAutoClose(sessionId, closeSessionForBackgroundCleanup, logger);
17914
18187
  }).catch((err) => {
17915
18188
  logger.warn(`[${sessionId.substring(0, 8)}] Withdraw auto-close mutation failed; session retained: `
17916
18189
  + `${err instanceof Error ? err.message : String(err)}`);
@@ -18033,6 +18306,20 @@ export async function startDaemon(botIndex) {
18033
18306
  logger.error(`[idempotency] boot reconcile failed to converge — aborting bot startup (fail-closed): ${err instanceof Error ? err.message : err}`);
18034
18307
  throw err instanceof Error ? err : new Error(String(err));
18035
18308
  }
18309
+ // Release durable mojo containment handles a REBOOT provably killed, BEFORE the
18310
+ // device-isolation activation inventory ever synthesises a blocker from them —
18311
+ // otherwise a post-reboot handle keeps blocking activation forever (round-11
18312
+ // P1-1). Daemon-global and idempotent: guarded so it runs once no matter how
18313
+ // many bots start; any store/proc read failure fails closed inside the helper.
18314
+ if (!mojoContainmentReconciledThisBoot) {
18315
+ mojoContainmentReconciledThisBoot = true;
18316
+ try {
18317
+ reconcileContainmentHandlesOnBoot();
18318
+ }
18319
+ catch (err) {
18320
+ logger.warn(`[mojo] containment boot reconciliation threw (blockers retained): ${err instanceof Error ? err.message : String(err)}`);
18321
+ }
18322
+ }
18036
18323
  // Seed dashboard IPC botName with the custom displayName (falling back to the
18037
18324
  // bot's config id); the friendly name from /bot/v3/info is wired into the
18038
18325
  // registry descriptor (below) but the IPC server also needs its own copy for
@@ -18395,6 +18682,12 @@ export async function startDaemon(botIndex) {
18395
18682
  // Restore complete → /api/asks may now safely 403 unknown sessions again; a
18396
18683
  // reconnecting ask hook that raced the restore got retryable 503s until here.
18397
18684
  sessionsRestored = true;
18685
+ // Freeze the control plane of restored mojo sessions NOW, not at their next
18686
+ // worker fork. Restore completes before the dispatcher can deliver a message,
18687
+ // so this closes the window where an operator edits the bot's endpoint/workspace
18688
+ // between the restart and a session's next wake — which would otherwise let that
18689
+ // session adopt the NEW control plane as its original identity.
18690
+ migrateMojoSessionIdentities(activeSessions);
18398
18691
  // Now that activeSessions is populated, release the forward-followup flush
18399
18692
  // barrier. Persisted seeds were loaded into the buffer at dispatcher startup
18400
18693
  // (so root-linked clarifications can pair with them), but their flushes wait
@@ -18523,6 +18816,45 @@ export async function startDaemon(botIndex) {
18523
18816
  enforceLiveSessionCap('periodic');
18524
18817
  }, 60_000);
18525
18818
  idleWorkerSweepTimer.unref?.();
18819
+ const sessionOwnerReminder = new SessionOwnerReminderController({
18820
+ load: () => loadSessionOwnerReminderRecords(config.session.dataDir, cfg.larkAppId),
18821
+ save: records => saveSessionOwnerReminderRecords(config.session.dataDir, cfg.larkAppId, records),
18822
+ canSend: ds => larkTransportEnabled({
18823
+ chatId: ds.chatId,
18824
+ apiOnly: getBot(ds.larkAppId).config.apiOnly,
18825
+ }),
18826
+ send: async (ds, text, uuid) => {
18827
+ await sendSessionOwnerThreadNotification({
18828
+ larkAppId: ds.larkAppId,
18829
+ rootMessageId: ds.session.rootMessageId,
18830
+ ownerOpenId: ds.session.ownerOpenId,
18831
+ }, text, uuid);
18832
+ },
18833
+ onError: (ds, error) => logger.warn(`[session-owner-reminder] send failed session=${ds.session.sessionId}: `
18834
+ + `${error instanceof Error ? error.message : String(error)}`),
18835
+ });
18836
+ let sessionOwnerReminderScanInFlight = false;
18837
+ const scanSessionOwnerReminders = async () => {
18838
+ if (sessionOwnerReminderScanInFlight)
18839
+ return;
18840
+ sessionOwnerReminderScanInFlight = true;
18841
+ try {
18842
+ const currentConfig = getBot(cfg.larkAppId).config.sessionOwnerReminder
18843
+ ?? DEFAULT_SESSION_OWNER_REMINDER;
18844
+ await sessionOwnerReminder.scan(activeSessions.values(), currentConfig);
18845
+ }
18846
+ catch (error) {
18847
+ logger.warn(`[session-owner-reminder] scan failed: ${error instanceof Error ? error.message : String(error)}`);
18848
+ }
18849
+ finally {
18850
+ sessionOwnerReminderScanInFlight = false;
18851
+ }
18852
+ };
18853
+ const sessionOwnerReminderTimer = setInterval(() => {
18854
+ void scanSessionOwnerReminders();
18855
+ }, 60_000);
18856
+ sessionOwnerReminderTimer.unref?.();
18857
+ void scanSessionOwnerReminders();
18526
18858
  // Periodic sandbox reconciler: the daemon's SIGKILL straggler-reaper (and any
18527
18859
  // worker SIGKILL) bypasses worker-side killCli(), so the pre-created deny-mask
18528
18860
  // mountpoints + per-session tree of a killed-but-still-active sandboxed session
@@ -18701,8 +19033,8 @@ export async function startDaemon(botIndex) {
18701
19033
  }, 30_000);
18702
19034
  overloadTimer.unref?.();
18703
19035
  }
18704
- // Graceful shutdown. Riff owners first run a three-phase non-cancelling drain
18705
- // that durably ACKs every exact final task lineage as one fleet transaction.
19036
+ // Graceful shutdown. Remote owners first run a three-phase non-cancelling
19037
+ // drain that durably ACKs every exact final lineage as one fleet transaction.
18706
19038
  // No worker exits until every participant is prepared and fresh-verified.
18707
19039
  // Ordinary workers then receive SIGTERM / close IPC and the daemon waits up
18708
19040
  // to DAEMON_WORKER_EXIT_GRACE_MS for them to exit
@@ -18729,23 +19061,23 @@ export async function startDaemon(botIndex) {
18729
19061
  logger.error(`Daemon remains online: ${initialShutdownFleet.error}`);
18730
19062
  return;
18731
19063
  }
18732
- // Preflight Riff before stopping services/removing descriptors. A failed
19064
+ // Preflight all remote backends before stopping services/removing descriptors. A failed
18733
19065
  // drain or persistence write aborts every successfully prepared peer, so a
18734
19066
  // refused shutdown returns to a live fleet instead of leaving an early
18735
19067
  // participant workerless.
18736
- const riffCandidates = initialShutdownFleet.sessions
18737
- .filter(ds => shutdownBackendDisposition(ds) === 'riff-drain-detach');
18738
- const riffPrepareResults = await prepareRiffFleetForShutdown(riffCandidates, {
19068
+ const remoteCandidates = initialShutdownFleet.sessions
19069
+ .filter(ds => shutdownBackendDisposition(ds) === 'remote-drain-detach');
19070
+ const remotePrepareResults = await prepareRemoteFleetForShutdown(remoteCandidates, {
18739
19071
  deadlineMs: shutdownDeadlineMs,
18740
19072
  });
18741
- const riffPrepared = riffPrepareResults.filter((entry) => entry.result.ok);
18742
- const riffFenced = riffPrepareResults.filter((entry) => entry.result.fence !== 'none');
18743
- const abortRiffFleet = async (reason, retainFenced = new Set()) => {
19073
+ const remotePrepared = remotePrepareResults.filter((entry) => entry.result.ok);
19074
+ const remoteFenced = remotePrepareResults.filter((entry) => entry.result.fence !== 'none');
19075
+ const abortRemoteFleet = async (reason, retainFenced = new Set()) => {
18744
19076
  for (const ds of retainFenced) {
18745
- logger.error(`[${ds.session.sessionId.slice(0, 8)}] Riff shutdown participant remains fenced: `
19077
+ logger.error(`[${ds.session.sessionId.slice(0, 8)}] Remote shutdown participant remains fenced: `
18746
19078
  + 'durable/runtime ownership could not be reconciled safely');
18747
19079
  }
18748
- const aborts = await abortRiffShutdownFleet(riffFenced
19080
+ const aborts = await abortRemoteShutdownFleet(remoteFenced
18749
19081
  .filter(({ ds }) => !retainFenced.has(ds))
18750
19082
  .map(({ ds, result }) => ({ ds, result })), {
18751
19083
  deadlineMs: shutdownDeadlineMs,
@@ -18753,34 +19085,34 @@ export async function startDaemon(botIndex) {
18753
19085
  for (const { ds, result } of aborts) {
18754
19086
  if (result.ok)
18755
19087
  continue;
18756
- logger.error(`[${ds.session.sessionId.slice(0, 8)}] Riff shutdown rollback was not ACKed: `
19088
+ logger.error(`[${ds.session.sessionId.slice(0, 8)}] Remote shutdown rollback was not ACKed: `
18757
19089
  + `${result.error ?? 'unknown'}; admission remains fail-closed`);
18758
19090
  }
18759
19091
  setSessionLifecycleShutdown(false);
18760
19092
  shuttingDown = false;
18761
19093
  logger.error(`Daemon remains online: ${reason}`);
18762
19094
  };
18763
- const riffPrepareFailures = riffPrepareResults.filter(entry => !entry.result.ok);
18764
- if (riffPrepareFailures.length > 0) {
18765
- for (const { ds, result } of riffPrepareFailures) {
19095
+ const remotePrepareFailures = remotePrepareResults.filter(entry => !entry.result.ok);
19096
+ if (remotePrepareFailures.length > 0) {
19097
+ for (const { ds, result } of remotePrepareFailures) {
18766
19098
  if (result.ok)
18767
19099
  continue;
18768
19100
  logger.error(`[${ds.session.sessionId.slice(0, 8)}] Daemon shutdown prepare refused: `
18769
19101
  + `${result.error}${result.taskId ? ` (task ${result.taskId})` : ''}`);
18770
19102
  }
18771
- await abortRiffFleet(`${riffPrepareFailures.length} Riff owner(s) could not be safely drained`);
19103
+ await abortRemoteFleet(`${remotePrepareFailures.length} remote owner(s) could not be safely drained`);
18772
19104
  return;
18773
19105
  }
18774
- const riffPersistence = persistPreparedRiffShutdownFleet(riffPrepared, {
19106
+ const remotePersistence = persistPreparedRemoteShutdownFleet(remotePrepared, {
18775
19107
  deadlineMs: shutdownDeadlineMs,
18776
19108
  });
18777
- if (!riffPersistence.ok) {
18778
- const retainIds = new Set(riffPersistence.retainFencedSessionIds);
18779
- const retainFenced = new Set(riffPrepared
19109
+ if (!remotePersistence.ok) {
19110
+ const retainIds = new Set(remotePersistence.retainFencedSessionIds);
19111
+ const retainFenced = new Set(remotePrepared
18780
19112
  .filter(({ ds }) => retainIds.has(ds.session.sessionId))
18781
19113
  .map(({ ds }) => ds));
18782
- logger.error(`Daemon shutdown lineage verification refused: ${riffPersistence.error}`);
18783
- await abortRiffFleet(`${riffPersistence.sessionIds.length} Riff lineage row(s) could not be fresh-verified`, retainFenced);
19114
+ logger.error(`Daemon shutdown lineage verification refused: ${remotePersistence.error}`);
19115
+ await abortRemoteFleet(`${remotePersistence.sessionIds.length} remote lineage row(s) could not be fresh-verified`, retainFenced);
18784
19116
  return;
18785
19117
  }
18786
19118
  // Recheck generation ownership while every participant is still fenced and
@@ -18788,33 +19120,33 @@ export async function startDaemon(botIndex) {
18788
19120
  const currentShutdownFleet = collectUniqueDaemonShutdownSessions(activeSessions.values());
18789
19121
  if (!currentShutdownFleet.ok) {
18790
19122
  const ambiguousSessionId = currentShutdownFleet.sessionId;
18791
- await abortRiffFleet(`Riff ownership became ambiguous after shutdown preflight: ${currentShutdownFleet.error}`, new Set(riffPrepared
19123
+ await abortRemoteFleet(`Remote ownership became ambiguous after shutdown preflight: ${currentShutdownFleet.error}`, new Set(remotePrepared
18792
19124
  .filter(({ ds }) => ds.session.sessionId === ambiguousSessionId)
18793
19125
  .map(({ ds }) => ds)));
18794
19126
  return;
18795
19127
  }
18796
- const currentRiffOwners = currentShutdownFleet.sessions
18797
- .filter(ds => shutdownBackendDisposition(ds) === 'riff-drain-detach');
18798
- const riffPreparedOwners = new Set(riffPrepared.map(entry => entry.ds));
18799
- const riffGenerationMismatch = currentRiffOwners.some(ds => !riffPreparedOwners.has(ds))
18800
- || riffPrepared.some(({ ds, result }) => !currentRiffOwners.includes(ds) || !isPreparedRiffSessionCurrent(ds, result));
18801
- if (riffGenerationMismatch) {
18802
- const retainFenced = new Set(riffPrepared
18803
- .filter(({ ds, result }) => (!currentRiffOwners.includes(ds) || !isPreparedRiffSessionCurrent(ds, result))
18804
- && !canAbortVerifiedExitedRiffPreparation(ds, result))
19128
+ const currentRemoteOwners = currentShutdownFleet.sessions
19129
+ .filter(ds => shutdownBackendDisposition(ds) === 'remote-drain-detach');
19130
+ const remotePreparedOwners = new Set(remotePrepared.map(entry => entry.ds));
19131
+ const remoteGenerationMismatch = currentRemoteOwners.some(ds => !remotePreparedOwners.has(ds))
19132
+ || remotePrepared.some(({ ds, result }) => !currentRemoteOwners.includes(ds) || !isPreparedRemoteSessionCurrent(ds, result));
19133
+ if (remoteGenerationMismatch) {
19134
+ const retainFenced = new Set(remotePrepared
19135
+ .filter(({ ds, result }) => (!currentRemoteOwners.includes(ds) || !isPreparedRemoteSessionCurrent(ds, result))
19136
+ && !canAbortVerifiedExitedRemotePreparation(ds, result))
18805
19137
  .map(({ ds }) => ds));
18806
- await abortRiffFleet('Riff ownership changed after shutdown preflight', retainFenced);
19138
+ await abortRemoteFleet('Remote ownership changed after shutdown preflight', retainFenced);
18807
19139
  return;
18808
19140
  }
18809
19141
  // Validate-all above, then commit-all synchronously with no await or
18810
19142
  // callback boundary between peers.
18811
- const riffRetiredWorkers = [];
18812
- for (const { ds, result } of riffPrepared) {
18813
- if (!commitPreparedRiffShutdown(ds, result)) {
18814
- throw new Error(`Riff shutdown commit invariant lost for ${ds.session.sessionId}`);
19143
+ const remoteRetiredWorkers = [];
19144
+ for (const { ds, result } of remotePrepared) {
19145
+ if (!commitPreparedRemoteShutdown(ds, result)) {
19146
+ throw new Error(`Remote shutdown commit invariant lost for ${ds.session.sessionId}`);
18815
19147
  }
18816
19148
  if (result.worker)
18817
- riffRetiredWorkers.push(result.worker);
19149
+ remoteRetiredWorkers.push(result.worker);
18818
19150
  }
18819
19151
  scheduler.stopScheduler();
18820
19152
  // NOTE: turn-terminal queue drain + webhook dispatcher stop are deliberately
@@ -18854,6 +19186,7 @@ export async function startDaemon(botIndex) {
18854
19186
  deleteVcMeetingPendingInvite(key);
18855
19187
  clearInterval(descriptorHeartbeat);
18856
19188
  clearInterval(idleWorkerSweepTimer);
19189
+ clearInterval(sessionOwnerReminderTimer);
18857
19190
  if (memoryDiagnostics)
18858
19191
  clearInterval(memoryDiagnostics);
18859
19192
  removeDaemonDescriptor(cfg.larkAppId);
@@ -18899,7 +19232,10 @@ export async function startDaemon(botIndex) {
18899
19232
  }
18900
19233
  }
18901
19234
  };
18902
- for (const worker of riffRetiredWorkers)
19235
+ // Union of two renames: this branch generalised the collection
19236
+ // riffRetiredWorkers -> remoteRetiredWorkers (it now carries mojo too), while
19237
+ // master renamed the local helper trackWorkerExit -> trackWorker.
19238
+ for (const worker of remoteRetiredWorkers)
18903
19239
  trackWorker(worker);
18904
19240
  for (const ds of currentShutdownFleet.sessions) {
18905
19241
  const w = ds.worker;
@@ -18916,8 +19252,8 @@ export async function startDaemon(botIndex) {
18916
19252
  if (!w.killed) {
18917
19253
  logger.info(`Shutting down worker for session ${ds.session.sessionId}`);
18918
19254
  const disposition = shutdownBackendDisposition(ds);
18919
- if (disposition === 'riff-drain-detach') {
18920
- throw new Error(`undrained Riff generation after atomic commit: ${ds.session.sessionId}`);
19255
+ if (disposition === 'remote-drain-detach') {
19256
+ throw new Error(`undrained remote generation after atomic commit: ${ds.session.sessionId}`);
18921
19257
  }
18922
19258
  // Branch by the session's FROZEN backend (stamped on Session.backendType
18923
19259
  // at spawn), NOT the bot's live config — a dashboard backendType edit must
@@ -19066,6 +19402,7 @@ export async function startDaemon(botIndex) {
19066
19402
  setSupervisorShutdownHandler(null);
19067
19403
  clearInterval(descriptorHeartbeat);
19068
19404
  clearInterval(idleWorkerSweepTimer);
19405
+ clearInterval(sessionOwnerReminderTimer);
19069
19406
  clearInterval(docCommentPollTimer);
19070
19407
  if (memoryDiagnostics)
19071
19408
  clearInterval(memoryDiagnostics);