botmux 3.16.0 → 3.17.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 (619) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/adapters/backend/riff-backend.d.ts +84 -1
  3. package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
  4. package/dist/adapters/backend/riff-backend.js +202 -19
  5. package/dist/adapters/backend/riff-backend.js.map +1 -1
  6. package/dist/adapters/backend/sandbox.d.ts +8 -0
  7. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  8. package/dist/adapters/backend/sandbox.js +15 -0
  9. package/dist/adapters/backend/sandbox.js.map +1 -1
  10. package/dist/adapters/backend/tmux-backend.d.ts +61 -7
  11. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/tmux-backend.js +80 -9
  13. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  14. package/dist/adapters/backend/tmux-pipe-backend.d.ts +62 -4
  15. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  16. package/dist/adapters/backend/tmux-pipe-backend.js +240 -27
  17. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  18. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  19. package/dist/adapters/cli/claude-code.js +4 -2
  20. package/dist/adapters/cli/claude-code.js.map +1 -1
  21. package/dist/adapters/cli/coco.d.ts.map +1 -1
  22. package/dist/adapters/cli/coco.js +27 -0
  23. package/dist/adapters/cli/coco.js.map +1 -1
  24. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  25. package/dist/adapters/cli/codex-app.js +28 -0
  26. package/dist/adapters/cli/codex-app.js.map +1 -1
  27. package/dist/adapters/cli/codex.d.ts.map +1 -1
  28. package/dist/adapters/cli/codex.js +51 -23
  29. package/dist/adapters/cli/codex.js.map +1 -1
  30. package/dist/adapters/cli/dsh-tui.d.ts +24 -0
  31. package/dist/adapters/cli/dsh-tui.d.ts.map +1 -0
  32. package/dist/adapters/cli/dsh-tui.js +99 -0
  33. package/dist/adapters/cli/dsh-tui.js.map +1 -0
  34. package/dist/adapters/cli/dsh.d.ts.map +1 -1
  35. package/dist/adapters/cli/dsh.js +15 -9
  36. package/dist/adapters/cli/dsh.js.map +1 -1
  37. package/dist/adapters/cli/fs-policy.d.ts +98 -1
  38. package/dist/adapters/cli/fs-policy.d.ts.map +1 -1
  39. package/dist/adapters/cli/fs-policy.js +182 -7
  40. package/dist/adapters/cli/fs-policy.js.map +1 -1
  41. package/dist/adapters/cli/grok.js +16 -18
  42. package/dist/adapters/cli/grok.js.map +1 -1
  43. package/dist/adapters/cli/model-catalog-json.d.ts +17 -0
  44. package/dist/adapters/cli/model-catalog-json.d.ts.map +1 -0
  45. package/dist/adapters/cli/model-catalog-json.js +43 -0
  46. package/dist/adapters/cli/model-catalog-json.js.map +1 -0
  47. package/dist/adapters/cli/oh-my-pi.d.ts +2 -0
  48. package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
  49. package/dist/adapters/cli/oh-my-pi.js +45 -13
  50. package/dist/adapters/cli/oh-my-pi.js.map +1 -1
  51. package/dist/adapters/cli/opencode.js +1 -1
  52. package/dist/adapters/cli/opencode.js.map +1 -1
  53. package/dist/adapters/cli/registry.d.ts +2 -1
  54. package/dist/adapters/cli/registry.d.ts.map +1 -1
  55. package/dist/adapters/cli/registry.js +6 -1
  56. package/dist/adapters/cli/registry.js.map +1 -1
  57. package/dist/adapters/cli/relay.d.ts.map +1 -1
  58. package/dist/adapters/cli/relay.js +7 -0
  59. package/dist/adapters/cli/relay.js.map +1 -1
  60. package/dist/adapters/cli/seed.d.ts.map +1 -1
  61. package/dist/adapters/cli/seed.js +6 -0
  62. package/dist/adapters/cli/seed.js.map +1 -1
  63. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  64. package/dist/adapters/cli/shared-hints.js +20 -14
  65. package/dist/adapters/cli/shared-hints.js.map +1 -1
  66. package/dist/adapters/cli/traex.d.ts +4 -0
  67. package/dist/adapters/cli/traex.d.ts.map +1 -1
  68. package/dist/adapters/cli/traex.js +153 -0
  69. package/dist/adapters/cli/traex.js.map +1 -1
  70. package/dist/adapters/cli/types.d.ts +42 -1
  71. package/dist/adapters/cli/types.d.ts.map +1 -1
  72. package/dist/autostart.d.ts +6 -0
  73. package/dist/autostart.d.ts.map +1 -1
  74. package/dist/autostart.js +21 -1
  75. package/dist/autostart.js.map +1 -1
  76. package/dist/bot-registry.d.ts +27 -0
  77. package/dist/bot-registry.d.ts.map +1 -1
  78. package/dist/bot-registry.js +62 -2
  79. package/dist/bot-registry.js.map +1 -1
  80. package/dist/cli/vc-agent.d.ts.map +1 -1
  81. package/dist/cli/vc-agent.js +21 -3
  82. package/dist/cli/vc-agent.js.map +1 -1
  83. package/dist/cli.d.ts.map +1 -1
  84. package/dist/cli.js +92 -1
  85. package/dist/cli.js.map +1 -1
  86. package/dist/codex-app-runner.js +28 -0
  87. package/dist/codex-app-runner.js.map +1 -1
  88. package/dist/core/bot-steer-directive.d.ts +25 -0
  89. package/dist/core/bot-steer-directive.d.ts.map +1 -0
  90. package/dist/core/bot-steer-directive.js +47 -0
  91. package/dist/core/bot-steer-directive.js.map +1 -0
  92. package/dist/core/command-handler.d.ts.map +1 -1
  93. package/dist/core/command-handler.js +102 -18
  94. package/dist/core/command-handler.js.map +1 -1
  95. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  96. package/dist/core/dashboard-ipc-server.js +53 -13
  97. package/dist/core/dashboard-ipc-server.js.map +1 -1
  98. package/dist/core/dashboard-rows.d.ts +3 -0
  99. package/dist/core/dashboard-rows.d.ts.map +1 -1
  100. package/dist/core/dashboard-rows.js +26 -5
  101. package/dist/core/dashboard-rows.js.map +1 -1
  102. package/dist/core/dashboard-url.d.ts +10 -1
  103. package/dist/core/dashboard-url.d.ts.map +1 -1
  104. package/dist/core/dashboard-url.js +12 -2
  105. package/dist/core/dashboard-url.js.map +1 -1
  106. package/dist/core/dispatch.d.ts.map +1 -1
  107. package/dist/core/dispatch.js +5 -4
  108. package/dist/core/dispatch.js.map +1 -1
  109. package/dist/core/existing-app-server.d.ts +22 -0
  110. package/dist/core/existing-app-server.d.ts.map +1 -0
  111. package/dist/core/existing-app-server.js +85 -0
  112. package/dist/core/existing-app-server.js.map +1 -0
  113. package/dist/core/native-topic-id.d.ts +9 -0
  114. package/dist/core/native-topic-id.d.ts.map +1 -0
  115. package/dist/core/native-topic-id.js +14 -0
  116. package/dist/core/native-topic-id.js.map +1 -0
  117. package/dist/core/per-bot-env.d.ts.map +1 -1
  118. package/dist/core/per-bot-env.js +10 -0
  119. package/dist/core/per-bot-env.js.map +1 -1
  120. package/dist/core/plugins/mcp/gateway.d.ts +27 -3
  121. package/dist/core/plugins/mcp/gateway.d.ts.map +1 -1
  122. package/dist/core/plugins/mcp/gateway.js +272 -26
  123. package/dist/core/plugins/mcp/gateway.js.map +1 -1
  124. package/dist/core/plugins/mcp/host.d.ts +18 -0
  125. package/dist/core/plugins/mcp/host.d.ts.map +1 -1
  126. package/dist/core/plugins/mcp/host.js +80 -16
  127. package/dist/core/plugins/mcp/host.js.map +1 -1
  128. package/dist/core/plugins/mcp/launch-record.d.ts +14 -0
  129. package/dist/core/plugins/mcp/launch-record.d.ts.map +1 -0
  130. package/dist/core/plugins/mcp/launch-record.js +63 -0
  131. package/dist/core/plugins/mcp/launch-record.js.map +1 -0
  132. package/dist/core/session-activity.d.ts +11 -0
  133. package/dist/core/session-activity.d.ts.map +1 -1
  134. package/dist/core/session-activity.js +23 -0
  135. package/dist/core/session-activity.js.map +1 -1
  136. package/dist/core/session-group-birth.d.ts.map +1 -1
  137. package/dist/core/session-group-birth.js +23 -8
  138. package/dist/core/session-group-birth.js.map +1 -1
  139. package/dist/core/session-manager.d.ts +1 -4
  140. package/dist/core/session-manager.d.ts.map +1 -1
  141. package/dist/core/session-manager.js +16 -20
  142. package/dist/core/session-manager.js.map +1 -1
  143. package/dist/core/shared-adopt.d.ts +20 -0
  144. package/dist/core/shared-adopt.d.ts.map +1 -0
  145. package/dist/core/shared-adopt.js +26 -0
  146. package/dist/core/shared-adopt.js.map +1 -0
  147. package/dist/core/skills/customize-command.d.ts +7 -0
  148. package/dist/core/skills/customize-command.d.ts.map +1 -0
  149. package/dist/core/skills/customize-command.js +303 -0
  150. package/dist/core/skills/customize-command.js.map +1 -0
  151. package/dist/core/terminal-write-auth.d.ts +15 -0
  152. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  153. package/dist/core/terminal-write-auth.js +15 -0
  154. package/dist/core/terminal-write-auth.js.map +1 -1
  155. package/dist/core/terminal-write-frame.d.ts +30 -0
  156. package/dist/core/terminal-write-frame.d.ts.map +1 -0
  157. package/dist/core/terminal-write-frame.js +44 -0
  158. package/dist/core/terminal-write-frame.js.map +1 -0
  159. package/dist/core/types.d.ts +14 -4
  160. package/dist/core/types.d.ts.map +1 -1
  161. package/dist/core/types.js +6 -8
  162. package/dist/core/types.js.map +1 -1
  163. package/dist/core/worker-pool.d.ts +31 -6
  164. package/dist/core/worker-pool.d.ts.map +1 -1
  165. package/dist/core/worker-pool.js +444 -102
  166. package/dist/core/worker-pool.js.map +1 -1
  167. package/dist/core/worker-startup-retry.d.ts +52 -0
  168. package/dist/core/worker-startup-retry.d.ts.map +1 -0
  169. package/dist/core/worker-startup-retry.js +68 -0
  170. package/dist/core/worker-startup-retry.js.map +1 -0
  171. package/dist/daemon.d.ts +9 -8
  172. package/dist/daemon.d.ts.map +1 -1
  173. package/dist/daemon.js +467 -171
  174. package/dist/daemon.js.map +1 -1
  175. package/dist/dashboard/autostart-api.d.ts +26 -0
  176. package/dist/dashboard/autostart-api.d.ts.map +1 -0
  177. package/dist/dashboard/autostart-api.js +88 -0
  178. package/dist/dashboard/autostart-api.js.map +1 -0
  179. package/dist/dashboard/bot-payload.d.ts +5 -0
  180. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  181. package/dist/dashboard/bot-payload.js +2 -0
  182. package/dist/dashboard/bot-payload.js.map +1 -1
  183. package/dist/dashboard/control-csrf.d.ts +29 -1
  184. package/dist/dashboard/control-csrf.d.ts.map +1 -1
  185. package/dist/dashboard/control-csrf.js +49 -10
  186. package/dist/dashboard/control-csrf.js.map +1 -1
  187. package/dist/dashboard/customization-api.d.ts +23 -0
  188. package/dist/dashboard/customization-api.d.ts.map +1 -0
  189. package/dist/dashboard/customization-api.js +198 -0
  190. package/dist/dashboard/customization-api.js.map +1 -0
  191. package/dist/dashboard/debug-terminal.d.ts +13 -1
  192. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  193. package/dist/dashboard/debug-terminal.js +18 -4
  194. package/dist/dashboard/debug-terminal.js.map +1 -1
  195. package/dist/dashboard/settings-write-applier.d.ts +0 -23
  196. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  197. package/dist/dashboard/settings-write-applier.js +7 -28
  198. package/dist/dashboard/settings-write-applier.js.map +1 -1
  199. package/dist/dashboard/terminal-control-route.d.ts +32 -0
  200. package/dist/dashboard/terminal-control-route.d.ts.map +1 -0
  201. package/dist/dashboard/terminal-control-route.js +70 -0
  202. package/dist/dashboard/terminal-control-route.js.map +1 -0
  203. package/dist/dashboard/terminal-control.d.ts +64 -13
  204. package/dist/dashboard/terminal-control.d.ts.map +1 -1
  205. package/dist/dashboard/terminal-control.js +77 -14
  206. package/dist/dashboard/terminal-control.js.map +1 -1
  207. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -1
  208. package/dist/dashboard/terminal-front-proxy.js +41 -9
  209. package/dist/dashboard/terminal-front-proxy.js.map +1 -1
  210. package/dist/dashboard/vc-consumer-profiles-api.d.ts +69 -18
  211. package/dist/dashboard/vc-consumer-profiles-api.d.ts.map +1 -1
  212. package/dist/dashboard/vc-consumer-profiles-api.js +149 -29
  213. package/dist/dashboard/vc-consumer-profiles-api.js.map +1 -1
  214. package/dist/dashboard/web/agent-workbench-api.d.ts +34 -2
  215. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -1
  216. package/dist/dashboard/web/agent-workbench-api.js +39 -4
  217. package/dist/dashboard/web/agent-workbench-api.js.map +1 -1
  218. package/dist/dashboard/web/agent-workbench-appearance.d.ts +6 -3
  219. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -1
  220. package/dist/dashboard/web/agent-workbench-appearance.js +7 -4
  221. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -1
  222. package/dist/dashboard/web/agent-workbench-model.d.ts +2 -0
  223. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -1
  224. package/dist/dashboard/web/agent-workbench-model.js.map +1 -1
  225. package/dist/dashboard/web/agent-workbench-page.js +2 -2
  226. package/dist/dashboard/web/agent-workbench-page.js.map +1 -1
  227. package/dist/dashboard/web/agent-workbench-panes.d.ts +59 -4
  228. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -1
  229. package/dist/dashboard/web/agent-workbench-panes.js +493 -85
  230. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -1
  231. package/dist/dashboard/web/agent-workbench-session-list.d.ts +11 -7
  232. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -1
  233. package/dist/dashboard/web/agent-workbench-session-list.js +24 -31
  234. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -1
  235. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts +198 -0
  236. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts.map +1 -0
  237. package/dist/dashboard/web/agent-workbench-terminal-control.js +292 -0
  238. package/dist/dashboard/web/agent-workbench-terminal-control.js.map +1 -0
  239. package/dist/dashboard/web/agent-workbench-view.d.ts +3 -2
  240. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -1
  241. package/dist/dashboard/web/agent-workbench-view.js +69 -50
  242. package/dist/dashboard/web/agent-workbench-view.js.map +1 -1
  243. package/dist/dashboard/web/app.d.ts.map +1 -1
  244. package/dist/dashboard/web/app.js +58 -7
  245. package/dist/dashboard/web/app.js.map +1 -1
  246. package/dist/dashboard/web/bot-defaults-page.d.ts +24 -0
  247. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  248. package/dist/dashboard/web/bot-defaults-page.js +191 -16
  249. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  250. package/dist/dashboard/web/bot-defaults.d.ts +17 -0
  251. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  252. package/dist/dashboard/web/bot-defaults.js +51 -2
  253. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  254. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  255. package/dist/dashboard/web/bot-onboarding.js +32 -8
  256. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  257. package/dist/dashboard/web/confirm-modal.d.ts +35 -0
  258. package/dist/dashboard/web/confirm-modal.d.ts.map +1 -0
  259. package/dist/dashboard/web/confirm-modal.js +111 -0
  260. package/dist/dashboard/web/confirm-modal.js.map +1 -0
  261. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  262. package/dist/dashboard/web/connectors-page.js +2 -1
  263. package/dist/dashboard/web/connectors-page.js.map +1 -1
  264. package/dist/dashboard/web/customization-page.d.ts +3 -0
  265. package/dist/dashboard/web/customization-page.d.ts.map +1 -0
  266. package/dist/dashboard/web/customization-page.js +315 -0
  267. package/dist/dashboard/web/customization-page.js.map +1 -0
  268. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  269. package/dist/dashboard/web/dashboard-routes.js +1 -0
  270. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  271. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  272. package/dist/dashboard/web/groups-page.js +11 -9
  273. package/dist/dashboard/web/groups-page.js.map +1 -1
  274. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  275. package/dist/dashboard/web/i18n.js +226 -42
  276. package/dist/dashboard/web/i18n.js.map +1 -1
  277. package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
  278. package/dist/dashboard/web/monitor-room.js +3 -2
  279. package/dist/dashboard/web/monitor-room.js.map +1 -1
  280. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  281. package/dist/dashboard/web/overview-page.js +98 -63
  282. package/dist/dashboard/web/overview-page.js.map +1 -1
  283. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  284. package/dist/dashboard/web/roles-page.js +4 -3
  285. package/dist/dashboard/web/roles-page.js.map +1 -1
  286. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  287. package/dist/dashboard/web/schedules-page.js +165 -18
  288. package/dist/dashboard/web/schedules-page.js.map +1 -1
  289. package/dist/dashboard/web/sessions-kanban.d.ts +2 -0
  290. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
  291. package/dist/dashboard/web/sessions-kanban.js +142 -24
  292. package/dist/dashboard/web/sessions-kanban.js.map +1 -1
  293. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  294. package/dist/dashboard/web/sessions-page.js +177 -94
  295. package/dist/dashboard/web/sessions-page.js.map +1 -1
  296. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  297. package/dist/dashboard/web/sessions.js +5 -4
  298. package/dist/dashboard/web/sessions.js.map +1 -1
  299. package/dist/dashboard/web/settings-page.d.ts +0 -8
  300. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  301. package/dist/dashboard/web/settings-page.js +185 -65
  302. package/dist/dashboard/web/settings-page.js.map +1 -1
  303. package/dist/dashboard/web/skills/skill-library-tab.d.ts +2 -0
  304. package/dist/dashboard/web/skills/skill-library-tab.d.ts.map +1 -1
  305. package/dist/dashboard/web/skills/skill-library-tab.js +15 -1
  306. package/dist/dashboard/web/skills/skill-library-tab.js.map +1 -1
  307. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  308. package/dist/dashboard/web/skills-page.js +5 -4
  309. package/dist/dashboard/web/skills-page.js.map +1 -1
  310. package/dist/dashboard/web/store.d.ts +5 -0
  311. package/dist/dashboard/web/store.d.ts.map +1 -1
  312. package/dist/dashboard/web/store.js +4 -0
  313. package/dist/dashboard/web/store.js.map +1 -1
  314. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  315. package/dist/dashboard/web/team-federation-page.js +3 -2
  316. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  317. package/dist/dashboard/web/toast.d.ts +15 -0
  318. package/dist/dashboard/web/toast.d.ts.map +1 -0
  319. package/dist/dashboard/web/toast.js +70 -0
  320. package/dist/dashboard/web/toast.js.map +1 -0
  321. package/dist/dashboard/web/v3-components.d.ts.map +1 -1
  322. package/dist/dashboard/web/v3-components.js +2 -1
  323. package/dist/dashboard/web/v3-components.js.map +1 -1
  324. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts +10 -12
  325. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
  326. package/dist/dashboard/web/vc-consumer-profiles-section.js +219 -200
  327. package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
  328. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
  329. package/dist/dashboard/web/whiteboards-page.js +2 -1
  330. package/dist/dashboard/web/whiteboards-page.js.map +1 -1
  331. package/dist/dashboard-web/app.js +1 -1
  332. package/dist/dashboard-web/chunks/agent-workbench-dock-page-LZSUOCEQ.js +1 -0
  333. package/dist/dashboard-web/chunks/agent-workbench-page-YONDRJU7.js +1 -0
  334. package/dist/dashboard-web/chunks/bot-defaults-page-X7LV6373.js +1 -0
  335. package/dist/dashboard-web/chunks/chunk-2PVRDXEL.js +1 -0
  336. package/dist/dashboard-web/chunks/chunk-53JUKBU2.js +10 -0
  337. package/dist/dashboard-web/chunks/{chunk-5CJMHTT4.js → chunk-5QX4OYOR.js} +1 -1
  338. package/dist/dashboard-web/chunks/{chunk-NACP7BMR.js → chunk-5VRVJFQR.js} +1 -1
  339. package/dist/dashboard-web/chunks/{chunk-FDZ7WOBW.js → chunk-6ROOZWYV.js} +1 -1
  340. package/dist/dashboard-web/chunks/{chunk-F6KNPF4R.js → chunk-CK22743G.js} +15 -19
  341. package/dist/dashboard-web/chunks/chunk-CVNIMUPY.js +25 -0
  342. package/dist/dashboard-web/chunks/chunk-GJC2TMWS.js +2 -0
  343. package/dist/dashboard-web/chunks/chunk-OT6L6G7R.js +1 -0
  344. package/dist/dashboard-web/chunks/chunk-QITAMHW6.js +1 -0
  345. package/dist/dashboard-web/chunks/chunk-RHVFZC5Z.js +1 -0
  346. package/dist/dashboard-web/chunks/chunk-SOBMN2VY.js +1 -0
  347. package/dist/dashboard-web/chunks/chunk-SZOB6VK2.js +1 -0
  348. package/dist/dashboard-web/chunks/chunk-XVIXNQGC.js +44 -0
  349. package/dist/dashboard-web/chunks/chunk-YUJF34ME.js +1 -0
  350. package/dist/dashboard-web/chunks/connectors-page-LB2HEJ7F.js +1 -0
  351. package/dist/dashboard-web/chunks/customization-page-4GXBYLG6.js +105 -0
  352. package/dist/dashboard-web/chunks/feedback-page-IZQ7VLDG.js +1 -0
  353. package/dist/dashboard-web/chunks/groups-page-4WCP5OEV.js +8 -0
  354. package/dist/dashboard-web/chunks/{insights-page-HJFDYR5N.js → insights-page-TXQHIPQL.js} +1 -1
  355. package/dist/dashboard-web/chunks/{monitor-room-V7PCP47V.js → monitor-room-VBRVWXCM.js} +8 -8
  356. package/dist/dashboard-web/chunks/monitoring-page-QDDZWERE.js +2 -0
  357. package/dist/dashboard-web/chunks/office-page-PENFNYPG.js +1 -0
  358. package/dist/dashboard-web/chunks/overview-page-BZQDP6MP.js +1 -0
  359. package/dist/dashboard-web/chunks/plugin-page-JX4GSWZS.js +1 -0
  360. package/dist/dashboard-web/chunks/roles-page-VPNINZNK.js +1 -0
  361. package/dist/dashboard-web/chunks/schedules-page-BITO6HEA.js +1 -0
  362. package/dist/dashboard-web/chunks/sessions-page-ZKCOVTD2.js +8 -0
  363. package/dist/dashboard-web/chunks/settings-page-UT6JDTS6.js +2 -0
  364. package/dist/dashboard-web/chunks/skills-page-GULXVAJY.js +4 -0
  365. package/dist/dashboard-web/chunks/team-federation-page-CUUJWGEG.js +1 -0
  366. package/dist/dashboard-web/chunks/v3-page-5WVJ5AUR.js +94 -0
  367. package/dist/dashboard-web/chunks/whiteboards-page-FZ2CME4H.js +1 -0
  368. package/dist/dashboard-web/design-tokens.css +236 -0
  369. package/dist/dashboard-web/index.html +1 -0
  370. package/dist/dashboard-web/style.css +1930 -114
  371. package/dist/dashboard.js +360 -279
  372. package/dist/dashboard.js.map +1 -1
  373. package/dist/desktop/renderer/app.js +6 -0
  374. package/dist/desktop/renderer/app.js.map +1 -1
  375. package/dist/dsh-runner.js +139 -17
  376. package/dist/dsh-runner.js.map +1 -1
  377. package/dist/global-config.d.ts +33 -2
  378. package/dist/global-config.d.ts.map +1 -1
  379. package/dist/global-config.js +62 -0
  380. package/dist/global-config.js.map +1 -1
  381. package/dist/i18n/en.d.ts.map +1 -1
  382. package/dist/i18n/en.js +13 -4
  383. package/dist/i18n/en.js.map +1 -1
  384. package/dist/i18n/index.d.ts +27 -3
  385. package/dist/i18n/index.d.ts.map +1 -1
  386. package/dist/i18n/index.js +33 -4
  387. package/dist/i18n/index.js.map +1 -1
  388. package/dist/i18n/zh.d.ts.map +1 -1
  389. package/dist/i18n/zh.js +13 -4
  390. package/dist/i18n/zh.js.map +1 -1
  391. package/dist/im/lark/card-builder.d.ts +2 -0
  392. package/dist/im/lark/card-builder.d.ts.map +1 -1
  393. package/dist/im/lark/card-builder.js +7 -4
  394. package/dist/im/lark/card-builder.js.map +1 -1
  395. package/dist/im/lark/card-handler.d.ts.map +1 -1
  396. package/dist/im/lark/card-handler.js +135 -20
  397. package/dist/im/lark/card-handler.js.map +1 -1
  398. package/dist/im/lark/event-dispatcher.d.ts +38 -3
  399. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  400. package/dist/im/lark/event-dispatcher.js +163 -13
  401. package/dist/im/lark/event-dispatcher.js.map +1 -1
  402. package/dist/im/lark/v3-blocked-card-handler.d.ts.map +1 -1
  403. package/dist/im/lark/v3-blocked-card-handler.js +2 -1
  404. package/dist/im/lark/v3-blocked-card-handler.js.map +1 -1
  405. package/dist/im/lark/v3-blocked-card.d.ts +1 -1
  406. package/dist/im/lark/v3-blocked-card.d.ts.map +1 -1
  407. package/dist/im/lark/v3-blocked-card.js +14 -0
  408. package/dist/im/lark/v3-blocked-card.js.map +1 -1
  409. package/dist/index-core-only.js +2 -0
  410. package/dist/index-core-only.js.map +1 -1
  411. package/dist/index-daemon.js +3 -0
  412. package/dist/index-daemon.js.map +1 -1
  413. package/dist/platform/binding.d.ts +37 -0
  414. package/dist/platform/binding.d.ts.map +1 -1
  415. package/dist/platform/binding.js +15 -0
  416. package/dist/platform/binding.js.map +1 -1
  417. package/dist/services/bridge-turn-journal.d.ts +63 -0
  418. package/dist/services/bridge-turn-journal.d.ts.map +1 -0
  419. package/dist/services/bridge-turn-journal.js +149 -0
  420. package/dist/services/bridge-turn-journal.js.map +1 -0
  421. package/dist/services/bridge-turn-queue.d.ts +8 -1
  422. package/dist/services/bridge-turn-queue.d.ts.map +1 -1
  423. package/dist/services/bridge-turn-queue.js +2 -1
  424. package/dist/services/bridge-turn-queue.js.map +1 -1
  425. package/dist/services/codex-app-runner-protocol.d.ts +5 -0
  426. package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
  427. package/dist/services/codex-app-runner-protocol.js +5 -0
  428. package/dist/services/codex-app-runner-protocol.js.map +1 -1
  429. package/dist/services/customization-store.d.ts +93 -0
  430. package/dist/services/customization-store.d.ts.map +1 -0
  431. package/dist/services/customization-store.js +542 -0
  432. package/dist/services/customization-store.js.map +1 -0
  433. package/dist/services/file-bridge-path.d.ts.map +1 -1
  434. package/dist/services/file-bridge-path.js +3 -0
  435. package/dist/services/file-bridge-path.js.map +1 -1
  436. package/dist/services/grok-transcript.d.ts.map +1 -1
  437. package/dist/services/grok-transcript.js +44 -9
  438. package/dist/services/grok-transcript.js.map +1 -1
  439. package/dist/services/local-cli-opener.d.ts.map +1 -1
  440. package/dist/services/local-cli-opener.js +12 -3
  441. package/dist/services/local-cli-opener.js.map +1 -1
  442. package/dist/services/model-catalog.d.ts +54 -0
  443. package/dist/services/model-catalog.d.ts.map +1 -0
  444. package/dist/services/model-catalog.js +147 -0
  445. package/dist/services/model-catalog.js.map +1 -0
  446. package/dist/services/omp-transcript.d.ts +27 -0
  447. package/dist/services/omp-transcript.d.ts.map +1 -0
  448. package/dist/services/omp-transcript.js +214 -0
  449. package/dist/services/omp-transcript.js.map +1 -0
  450. package/dist/services/platform-team-store.d.ts +16 -8
  451. package/dist/services/platform-team-store.d.ts.map +1 -1
  452. package/dist/services/platform-team-store.js +17 -10
  453. package/dist/services/platform-team-store.js.map +1 -1
  454. package/dist/services/project-scanner.d.ts.map +1 -1
  455. package/dist/services/project-scanner.js +13 -8
  456. package/dist/services/project-scanner.js.map +1 -1
  457. package/dist/services/resumable-session-discovery.d.ts.map +1 -1
  458. package/dist/services/resumable-session-discovery.js +117 -35
  459. package/dist/services/resumable-session-discovery.js.map +1 -1
  460. package/dist/services/structured-bridge-clis.d.ts +5 -3
  461. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  462. package/dist/services/structured-bridge-clis.js +5 -1
  463. package/dist/services/structured-bridge-clis.js.map +1 -1
  464. package/dist/services/vc-meeting-action-gate.d.ts.map +1 -1
  465. package/dist/services/vc-meeting-action-gate.js +15 -4
  466. package/dist/services/vc-meeting-action-gate.js.map +1 -1
  467. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts +46 -66
  468. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts.map +1 -1
  469. package/dist/services/vc-meeting-consumer-profile-bootstrap.js +33 -292
  470. package/dist/services/vc-meeting-consumer-profile-bootstrap.js.map +1 -1
  471. package/dist/services/vc-meeting-consumer-profile-templates.js +3 -3
  472. package/dist/services/vc-meeting-consumer-profile-templates.js.map +1 -1
  473. package/dist/services/vc-meeting-delivery-store.d.ts +18 -0
  474. package/dist/services/vc-meeting-delivery-store.d.ts.map +1 -1
  475. package/dist/services/vc-meeting-delivery-store.js +55 -0
  476. package/dist/services/vc-meeting-delivery-store.js.map +1 -1
  477. package/dist/services/vc-meeting-send-policy.d.ts +21 -1
  478. package/dist/services/vc-meeting-send-policy.d.ts.map +1 -1
  479. package/dist/services/vc-meeting-send-policy.js +30 -2
  480. package/dist/services/vc-meeting-send-policy.js.map +1 -1
  481. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts +78 -0
  482. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts.map +1 -0
  483. package/dist/services/vc-meeting-shared-consumer-catalog-store.js +227 -0
  484. package/dist/services/vc-meeting-shared-consumer-catalog-store.js.map +1 -0
  485. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts +53 -0
  486. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts.map +1 -0
  487. package/dist/services/vc-meeting-shared-consumer-catalog.js +161 -0
  488. package/dist/services/vc-meeting-shared-consumer-catalog.js.map +1 -0
  489. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  490. package/dist/setup/bot-config-editor.js +1 -0
  491. package/dist/setup/bot-config-editor.js.map +1 -1
  492. package/dist/setup/open-platform-automation.d.ts +20 -0
  493. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  494. package/dist/setup/open-platform-automation.js +27 -0
  495. package/dist/setup/open-platform-automation.js.map +1 -1
  496. package/dist/skills/customization-bundle.d.ts +83 -0
  497. package/dist/skills/customization-bundle.d.ts.map +1 -0
  498. package/dist/skills/customization-bundle.js +214 -0
  499. package/dist/skills/customization-bundle.js.map +1 -0
  500. package/dist/skills/definitions.d.ts.map +1 -1
  501. package/dist/skills/definitions.js +10 -3
  502. package/dist/skills/definitions.js.map +1 -1
  503. package/dist/skills/effective-builtins.d.ts +38 -0
  504. package/dist/skills/effective-builtins.d.ts.map +1 -0
  505. package/dist/skills/effective-builtins.js +96 -0
  506. package/dist/skills/effective-builtins.js.map +1 -0
  507. package/dist/skills/injection-mode.d.ts +4 -1
  508. package/dist/skills/injection-mode.d.ts.map +1 -1
  509. package/dist/skills/injection-mode.js +11 -3
  510. package/dist/skills/injection-mode.js.map +1 -1
  511. package/dist/skills/installer.d.ts.map +1 -1
  512. package/dist/skills/installer.js +24 -1
  513. package/dist/skills/installer.js.map +1 -1
  514. package/dist/skills/prompt-fragments.d.ts +58 -0
  515. package/dist/skills/prompt-fragments.d.ts.map +1 -0
  516. package/dist/skills/prompt-fragments.js +88 -0
  517. package/dist/skills/prompt-fragments.js.map +1 -0
  518. package/dist/types.d.ts +51 -0
  519. package/dist/types.d.ts.map +1 -1
  520. package/dist/utils/cli-usage-limit.d.ts +50 -0
  521. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  522. package/dist/utils/cli-usage-limit.js +43 -0
  523. package/dist/utils/cli-usage-limit.js.map +1 -1
  524. package/dist/utils/idle-detector.d.ts +12 -0
  525. package/dist/utils/idle-detector.d.ts.map +1 -1
  526. package/dist/utils/idle-detector.js +88 -2
  527. package/dist/utils/idle-detector.js.map +1 -1
  528. package/dist/utils/stuck-detector.d.ts +5 -0
  529. package/dist/utils/stuck-detector.d.ts.map +1 -1
  530. package/dist/utils/stuck-detector.js +41 -0
  531. package/dist/utils/stuck-detector.js.map +1 -1
  532. package/dist/utils/usage-limit-tracker.d.ts +46 -0
  533. package/dist/utils/usage-limit-tracker.d.ts.map +1 -0
  534. package/dist/utils/usage-limit-tracker.js +91 -0
  535. package/dist/utils/usage-limit-tracker.js.map +1 -0
  536. package/dist/utils/user-token.js +1 -1
  537. package/dist/utils/user-token.js.map +1 -1
  538. package/dist/worker.js +812 -125
  539. package/dist/worker.js.map +1 -1
  540. package/dist/workflows/definition.d.ts +14 -14
  541. package/dist/workflows/events/payloads.d.ts +12 -12
  542. package/dist/workflows/events/schema.d.ts +164 -164
  543. package/dist/workflows/migration/v2-to-v3.js +1 -1
  544. package/dist/workflows/migration/v2-to-v3.js.map +1 -1
  545. package/dist/workflows/v3/architect.d.ts.map +1 -1
  546. package/dist/workflows/v3/architect.js +11 -5
  547. package/dist/workflows/v3/architect.js.map +1 -1
  548. package/dist/workflows/v3/artifact-contract-declarations.d.ts +22 -0
  549. package/dist/workflows/v3/artifact-contract-declarations.d.ts.map +1 -0
  550. package/dist/workflows/v3/artifact-contract-declarations.js +95 -0
  551. package/dist/workflows/v3/artifact-contract-declarations.js.map +1 -0
  552. package/dist/workflows/v3/contract.d.ts +1 -0
  553. package/dist/workflows/v3/contract.d.ts.map +1 -1
  554. package/dist/workflows/v3/contract.js.map +1 -1
  555. package/dist/workflows/v3/daemon-run.d.ts +2 -2
  556. package/dist/workflows/v3/daemon-run.d.ts.map +1 -1
  557. package/dist/workflows/v3/daemon-run.js +4 -0
  558. package/dist/workflows/v3/daemon-run.js.map +1 -1
  559. package/dist/workflows/v3/dag.d.ts +7 -0
  560. package/dist/workflows/v3/dag.d.ts.map +1 -1
  561. package/dist/workflows/v3/dag.js +69 -8
  562. package/dist/workflows/v3/dag.js.map +1 -1
  563. package/dist/workflows/v3/ephemeral-pool.js +1 -1
  564. package/dist/workflows/v3/ephemeral-pool.js.map +1 -1
  565. package/dist/workflows/v3/event-contract.d.ts +3 -1
  566. package/dist/workflows/v3/event-contract.d.ts.map +1 -1
  567. package/dist/workflows/v3/host.d.ts.map +1 -1
  568. package/dist/workflows/v3/host.js +5 -1
  569. package/dist/workflows/v3/host.js.map +1 -1
  570. package/dist/workflows/v3/journal.d.ts +1 -1
  571. package/dist/workflows/v3/journal.d.ts.map +1 -1
  572. package/dist/workflows/v3/journal.js.map +1 -1
  573. package/dist/workflows/v3/library-materialize.d.ts.map +1 -1
  574. package/dist/workflows/v3/library-materialize.js +9 -1
  575. package/dist/workflows/v3/library-materialize.js.map +1 -1
  576. package/dist/workflows/v3/library-schema.d.ts +3 -1
  577. package/dist/workflows/v3/library-schema.d.ts.map +1 -1
  578. package/dist/workflows/v3/library-schema.js +20 -4
  579. package/dist/workflows/v3/library-schema.js.map +1 -1
  580. package/dist/workflows/v3/library-store.d.ts.map +1 -1
  581. package/dist/workflows/v3/library-store.js +15 -3
  582. package/dist/workflows/v3/library-store.js.map +1 -1
  583. package/dist/workflows/v3/run-envelope.d.ts +16 -16
  584. package/dist/workflows/v3/shared-node-runtime.d.ts +2 -1
  585. package/dist/workflows/v3/shared-node-runtime.d.ts.map +1 -1
  586. package/dist/workflows/v3/shared-node-runtime.js +48 -7
  587. package/dist/workflows/v3/shared-node-runtime.js.map +1 -1
  588. package/package.json +2 -1
  589. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +0 -1
  590. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +0 -1
  591. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +0 -10
  592. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +0 -2
  593. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +0 -1
  594. package/dist/dashboard-web/chunks/chunk-2W6SCN3M.js +0 -1
  595. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +0 -1
  596. package/dist/dashboard-web/chunks/chunk-5THX4DI2.js +0 -66
  597. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
  598. package/dist/dashboard-web/chunks/chunk-AGCPF6MT.js +0 -1
  599. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +0 -1
  600. package/dist/dashboard-web/chunks/chunk-SYC6D5XU.js +0 -1
  601. package/dist/dashboard-web/chunks/connectors-page-VOISXXII.js +0 -1
  602. package/dist/dashboard-web/chunks/feedback-page-RN6HRZMS.js +0 -1
  603. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +0 -8
  604. package/dist/dashboard-web/chunks/monitoring-page-NKEKVQAP.js +0 -2
  605. package/dist/dashboard-web/chunks/office-page-GVJF73XW.js +0 -1
  606. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +0 -1
  607. package/dist/dashboard-web/chunks/plugin-page-J3OF5IY4.js +0 -1
  608. package/dist/dashboard-web/chunks/roles-page-WQIRSOLZ.js +0 -1
  609. package/dist/dashboard-web/chunks/schedules-page-J2DEY2X6.js +0 -1
  610. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +0 -8
  611. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +0 -2
  612. package/dist/dashboard-web/chunks/skills-page-HGIFR3IU.js +0 -4
  613. package/dist/dashboard-web/chunks/team-federation-page-MR5ZPL4B.js +0 -1
  614. package/dist/dashboard-web/chunks/v3-page-GBR5JWPE.js +0 -94
  615. package/dist/dashboard-web/chunks/whiteboards-page-QAZM4BN3.js +0 -1
  616. package/dist/services/vc-meeting-consumer-profile-store.d.ts +0 -39
  617. package/dist/services/vc-meeting-consumer-profile-store.d.ts.map +0 -1
  618. package/dist/services/vc-meeting-consumer-profile-store.js +0 -191
  619. package/dist/services/vc-meeting-consumer-profile-store.js.map +0 -1
@@ -36,6 +36,8 @@ import { hasUnprovenContainment } from './mojo-containment.js';
36
36
  import { MOJO_EXPLICIT_CLOSE_RESULT_TIMEOUT_MS } from '../adapters/backend/mojo-budgets.js';
37
37
  import { buildEffectiveMojoConfig, diffMojoSessionIdentity, MOJO_IDENTITY_KEYS, pickMojoLivePatch, isMojoRemoteGone, mojoUnprovableEnvKeys, pickMojoSessionIdentity, } from '../adapters/backend/mojo-types.js';
38
38
  import { sanitizePerBotEnv } from './per-bot-env.js';
39
+ import { normalizeExistingAppServerEndpoint } from './existing-app-server.js';
40
+ import { isExistingAppServerSharedAdoptPersistedSession, isSharedAdoptPersistedSession, isSharedAdoptSession, } from './shared-adopt.js';
39
41
  import { closeResidualClause } from './close-residual.js';
40
42
  import { logger } from '../utils/logger.js';
41
43
  import { createCliAdapterSync } from '../adapters/cli/registry.js';
@@ -308,7 +310,7 @@ import { hasProtectedSessionMutationOwnership } from './session-mutation-guard.j
308
310
  import { DONE_REACTION_EMOJI_TYPE } from './pending-response.js';
309
311
  import { buildTerminalUrl } from './terminal-url.js';
310
312
  import { prependBotmuxBin, resolveBotmuxWrapperBinDir } from './botmux-wrapper.js';
311
- import { usageLimitStateKey } from '../utils/cli-usage-limit.js';
313
+ import { usageLimitStateKey, isActiveWorkRuntimeStatus } from '../utils/cli-usage-limit.js';
312
314
  import { evaluateVcMeetingManagedSend, resolveVcMeetingImTurnOrigin, } from '../services/vc-meeting-send-policy.js';
313
315
  import { finishVcMeetingImReply, prepareVcMeetingDeliveryReply, prepareVcMeetingImReply, } from '../services/vc-meeting-im-reply.js';
314
316
  import { neutralizeLarkAtTags } from '../services/send-policy.js';
@@ -327,6 +329,7 @@ import { recordDispatchInputCommit } from './dispatch.js';
327
329
  import { sendWorkerIpc } from './worker-ipc.js';
328
330
  import { cleanupExplicitSessionBacking } from './explicit-session-backing-cleanup.js';
329
331
  import { REMOTE_ADMISSION_RESTORE_TIMEOUT_MS } from './shutdown-budgets.js';
332
+ import { MAX_STARTUP_AUTO_RETRIES, isTransientStartupFailure, startupAutoRetryDelayMs, } from './worker-startup-retry.js';
330
333
  import { managedOriginCapabilityPath, replaceManagedOriginCapabilityFile, } from './managed-origin-capability.js';
331
334
  const __filename = fileURLToPath(import.meta.url);
332
335
  const __dirname = dirname(__filename);
@@ -570,7 +573,7 @@ function scheduleLocalCliOpenReadinessPatch(ds) {
570
573
  const botCfg = getBot(ds.larkAppId).config;
571
574
  const effectiveCliId = sessionCliId(ds, botCfg);
572
575
  const status = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
573
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
576
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
574
577
  scheduleCardPatch(ds, cardJson);
575
578
  }
576
579
  function flushPendingLocalCliOpenReadinessPatch(ds) {
@@ -582,7 +585,7 @@ function flushPendingLocalCliOpenReadinessPatch(ds) {
582
585
  /** PATCH the live card when the executor reports a different active runtime.
583
586
  * Runtime identity stays attached to the streaming usage line. */
584
587
  function scheduleActiveRuntimePatch(ds) {
585
- if (ds.session.vcMeetingReceiver || streamingCardDisabled(ds) || ds.suppressRecoveryCard) {
588
+ if (streamingCardDisabled(ds) || ds.suppressRecoveryCard) {
586
589
  ds.pendingActiveRuntimeCardRefresh = undefined;
587
590
  return;
588
591
  }
@@ -602,7 +605,7 @@ function scheduleActiveRuntimePatch(ds) {
602
605
  const botCfg = getBot(ds.larkAppId).config;
603
606
  const effectiveCliId = sessionCliId(ds, botCfg);
604
607
  const status = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
605
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
608
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
606
609
  scheduleCardPatch(ds, cardJson);
607
610
  }
608
611
  function flushPendingActiveRuntimePatch(ds) {
@@ -613,7 +616,7 @@ function flushPendingActiveRuntimePatch(ds) {
613
616
  }
614
617
  /** PATCH a live card when rollout settings change, even if the PTY is static. */
615
618
  function scheduleCodexServiceTierPatch(ds) {
616
- if (ds.session.vcMeetingReceiver || streamingCardDisabled(ds) || ds.suppressRecoveryCard) {
619
+ if (streamingCardDisabled(ds) || ds.suppressRecoveryCard) {
617
620
  ds.pendingCodexTierCardRefresh = undefined;
618
621
  return;
619
622
  }
@@ -633,7 +636,7 @@ function scheduleCodexServiceTierPatch(ds) {
633
636
  const botCfg = getBot(ds.larkAppId).config;
634
637
  const effectiveCliId = sessionCliId(ds, botCfg);
635
638
  const status = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
636
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), buildTerminalUrl(ds), ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
639
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), buildTerminalUrl(ds), ds.currentTurnTitle || ds.session.title || getCliDisplayName(effectiveCliId), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
637
640
  scheduleCardPatch(ds, cardJson);
638
641
  }
639
642
  /** How often the live streaming card is re-PATCHed with fresh usage while a
@@ -692,7 +695,7 @@ export function refreshStreamingCardUsage(ds) {
692
695
  // run for a port=0 backend (ZMX reports ready without a Web Terminal), a raw
693
696
  // URL would render a fake `:undefined`/backend-less link. Mirror every other
694
697
  // card path — empty string when there is no real terminal.
695
- readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', ds.lastScreenStatus ?? 'working', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }),
698
+ readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', ds.lastScreenStatus ?? 'working', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }),
696
699
  // fresh:true — the whole point of the tick is to break the 15s throttle so
697
700
  // the total/turn usage actually climbs on-screen every interval.
698
701
  getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId, { fresh: true }), sessionRuntimeDisplayName(ds, botCfg),
@@ -764,7 +767,7 @@ export function scheduleRiffAccessUrlPatch(ds) {
764
767
  const botCfg = getBot(ds.larkAppId).config;
765
768
  const effectiveCliId = sessionCliId(ds, botCfg);
766
769
  const status = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
767
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), buildTerminalUrl(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
770
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), buildTerminalUrl(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
768
771
  scheduleCardPatch(ds, cardJson);
769
772
  }
770
773
  function flushPendingRiffUrlPatch(ds) {
@@ -786,9 +789,7 @@ function sessionCliId(ds, botCfg) {
786
789
  function ordinaryTurnRecoveryEligible(ds, botCfg = getBot(ds.larkAppId).config) {
787
790
  return sessionCliId(ds, botCfg) === 'claude-code'
788
791
  && ds.session.status === 'active'
789
- && !ds.adoptedFrom
790
- && !ds.session.adoptedFrom
791
- && ds.initConfig?.adoptMode !== true
792
+ && !isSharedAdoptSession(ds)
792
793
  && !ds.session.vcMeetingReceiver
793
794
  && larkTransportEnabled({ chatId: ds.chatId, apiOnly: botCfg.apiOnly });
794
795
  }
@@ -1267,11 +1268,6 @@ export function cardUsageLimit(ds) {
1267
1268
  return ds.lastScreenStatus === 'limited' ? ds.usageLimit : undefined;
1268
1269
  }
1269
1270
  function scheduleUsageLimitCardPatch(ds) {
1270
- // Dedicated VC receivers keep limit state for dashboard/audit only. A timer
1271
- // must never revive or mutate an old/manual Lark card after the synchronous
1272
- // screen_update path has already suppressed auxiliary UI.
1273
- if (ds.session.vcMeetingReceiver)
1274
- return;
1275
1271
  if (ds.lastScreenStatus !== 'limited')
1276
1272
  return;
1277
1273
  if (!ds.streamCardId || ds.streamCardId === CARD_POSTING_SENTINEL || !workerHasInitialized(ds))
@@ -1280,7 +1276,7 @@ function scheduleUsageLimitCardPatch(ds) {
1280
1276
  const effectiveCliId = sessionCliId(ds, bot.config);
1281
1277
  const readUrl = readableTerminalUrlFor(ds);
1282
1278
  const turnTitle = ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, bot.config);
1283
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'limited', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), ds.usageLimit, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, bot.config), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1279
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, turnTitle, ds.lastScreenContent ?? '', 'limited', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), ds.usageLimit, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, bot.config), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1284
1280
  scheduleCardPatch(ds, cardJson);
1285
1281
  }
1286
1282
  function armUsageLimitRetryTimer(ds, previous) {
@@ -1328,6 +1324,31 @@ function updateUsageLimitState(ds, usageLimit) {
1328
1324
  persistStreamCardState(ds);
1329
1325
  armUsageLimitRetryTimer(ds, previous);
1330
1326
  }
1327
+ /**
1328
+ * Resolve the daemon-side screen status in the presence of a usage limit.
1329
+ *
1330
+ * Limit state is intentionally sticky across ticks that carry no fresh verdict
1331
+ * (the worker re-emits only while the limit text stays on screen; screenshot
1332
+ * ticks arrive every 10s), so a recorded limit survives ordinary idle frames
1333
+ * and the cooldown card stays up until the user retries. But it must
1334
+ * self-heal on evidence of active work: a blocked CLI does not produce
1335
+ * `working`/`analyzing` frames, so a limit that survives such a frame was a
1336
+ * false positive (a flicker-frame screen scan, or a structured emit the CLI
1337
+ * recovered from). Without this the card keeps showing 「限额已达」 for the
1338
+ * rest of the turn even though the CLI is demonstrably still running — the
1339
+ * reported 误报. The worker-side status gate (detectScreenUsageLimit) prevents
1340
+ * the detection while active; this is the daemon-side net for limits recorded
1341
+ * during an idle flicker before work resumed.
1342
+ */
1343
+ function resolveUsageAwareScreenStatus(ds, workerStatus, freshUsageLimit) {
1344
+ if (freshUsageLimit)
1345
+ return 'limited';
1346
+ if (ds.usageLimit && isActiveWorkRuntimeStatus(workerStatus)) {
1347
+ clearUsageLimitState(ds);
1348
+ return workerStatus;
1349
+ }
1350
+ return ds.usageLimit ? 'limited' : workerStatus;
1351
+ }
1331
1352
  const WORKER_ERROR_MARKER = '[botmux-worker-error]';
1332
1353
  function logWorkerStderr(t, line) {
1333
1354
  if (!line)
@@ -1475,7 +1496,7 @@ function reconcilePostedStartingCard(ds, turnId, revisionAtPost) {
1475
1496
  const botCfg = getBot(ds.larkAppId).config;
1476
1497
  const effectiveCliId = sessionCliId(ds, botCfg);
1477
1498
  const status = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? turnStartingCardStatus(ds, effectiveCliId));
1478
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId, { fresh: status === 'idle' }), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1499
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), status === 'limited' ? ds.usageLimit : undefined, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId, { fresh: status === 'idle' }), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1479
1500
  scheduleCardPatch(ds, cardJson, turnId);
1480
1501
  }
1481
1502
  /**
@@ -1495,7 +1516,7 @@ export async function postTurnStartingCard(ds, sessionReply, turnId) {
1495
1516
  return false;
1496
1517
  if (ds.streamCardId === CARD_POSTING_SENTINEL)
1497
1518
  return false;
1498
- if (ds.session.vcMeetingReceiver || streamingCardDisabled(ds, turnId))
1519
+ if (streamingCardDisabled(ds, turnId))
1499
1520
  return false;
1500
1521
  if (!workerHasInitialized(ds))
1501
1522
  return false;
@@ -1519,7 +1540,7 @@ export async function postTurnStartingCard(ds, sessionReply, turnId) {
1519
1540
  const nonce = randomBytes(4).toString('hex');
1520
1541
  const status = turnStartingCardStatus(ds, effectiveCliId);
1521
1542
  const statusRevisionAtPost = ds.streamCardStatusRevision ?? 0;
1522
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), '', status, effectiveCliId, ds.displayMode ?? 'hidden', nonce, undefined, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), ds.usageLimit, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1543
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), '', status, effectiveCliId, ds.displayMode ?? 'hidden', nonce, undefined, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), ds.usageLimit, writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1523
1544
  ds.streamCardNonce = nonce;
1524
1545
  ds.streamCardId = CARD_POSTING_SENTINEL;
1525
1546
  const ownsPostIdentity = () => ds.session === sessionAtPost
@@ -1600,10 +1621,6 @@ export async function postTurnStartingCard(ds, sessionReply, turnId) {
1600
1621
  * that so the card keeps live-patching afterwards even when the bot opted out.
1601
1622
  */
1602
1623
  export async function postFreshStreamingCard(ds, sessionReply) {
1603
- // Receiver terminals can contain meeting-derived private context. Never
1604
- // publish one into the listener chat as a streaming-card side channel.
1605
- if (ds.session.vcMeetingReceiver)
1606
- return false;
1607
1624
  if (isDocNativeSession(ds))
1608
1625
  return false;
1609
1626
  if (!workerHasInitialized(ds))
@@ -1624,7 +1641,7 @@ export async function postFreshStreamingCard(ds, sessionReply) {
1624
1641
  const prevPending = ds.streamCardPending;
1625
1642
  const cardReplyTarget = captureStreamingCardReplyTarget(ds);
1626
1643
  ds.streamCardNonce = randomBytes(4).toString('hex');
1627
- const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, title, ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1644
+ const cardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readUrl, title, ds.lastScreenContent ?? '', status, effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
1628
1645
  ds.streamCardId = CARD_POSTING_SENTINEL;
1629
1646
  try {
1630
1647
  ds.streamCardId = await sessionReply(sessionAnchorId(ds), cardJson, 'interactive', ds.larkAppId, cardReplyTarget.turnId);
@@ -1773,7 +1790,7 @@ export function buildWritableTerminalCard(ds) {
1773
1790
  if (ds.riffAccessUrl) {
1774
1791
  const botCfg = getBot(ds.larkAppId).config;
1775
1792
  const effectiveCliId = sessionCliId(ds, botCfg);
1776
- return buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), ds.riffAccessUrl, ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, true, !!ds.adoptedFrom, localeForBot(ds.larkAppId), false, sessionRuntimeDisplayName(ds, botCfg));
1793
+ return buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), ds.riffAccessUrl, ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, true, isSharedAdoptSession(ds), localeForBot(ds.larkAppId), false, sessionRuntimeDisplayName(ds, botCfg));
1777
1794
  }
1778
1795
  const port = ds.workerPort ?? ds.session.webPort;
1779
1796
  if (!port || !ds.workerToken)
@@ -1781,7 +1798,7 @@ export function buildWritableTerminalCard(ds) {
1781
1798
  const botCfg = getBot(ds.larkAppId).config;
1782
1799
  const effectiveCliId = sessionCliId(ds, botCfg);
1783
1800
  return buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), buildTerminalUrl(ds, { write: true }), ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, true, // showManageButtons — write-link card includes restart & close
1784
- !!ds.adoptedFrom, // adoptMode — disconnect, never close-the-CLI
1801
+ isSharedAdoptSession(ds), // shared adopt — disconnect, never close the source conversation
1785
1802
  localeForBot(ds.larkAppId), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), sessionRuntimeDisplayName(ds, botCfg));
1786
1803
  }
1787
1804
  /**
@@ -1852,7 +1869,7 @@ function buildSubstituteControlCard(ds) {
1852
1869
  const botCfg = getBot(ds.larkAppId).config;
1853
1870
  const effectiveCliId = sessionCliId(ds, botCfg);
1854
1871
  return buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), '', // Manage-only: this backend intentionally has no Web Terminal URL.
1855
- ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, true, !!ds.adoptedFrom, localeForBot(ds.larkAppId), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), sessionRuntimeDisplayName(ds, botCfg));
1872
+ ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, true, isSharedAdoptSession(ds), localeForBot(ds.larkAppId), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), sessionRuntimeDisplayName(ds, botCfg));
1856
1873
  }
1857
1874
  /**
1858
1875
  * DM a control card to the bot's owner(s) for a substitute-mode session.
@@ -2052,8 +2069,14 @@ export const restartCounts = new Map();
2052
2069
  /** Track which CLI adapters have had skills installed this daemon lifecycle */
2053
2070
  const skillsInstalledCliIds = new Set();
2054
2071
  /**
2055
- * Ensure built-in skills are installed for a given CLI.
2056
- * Synchronous and idempotent runs once per CLI per daemon lifecycle.
2072
+ * Ensure built-in skills are installed for a given CLI. Synchronous and
2073
+ * idempotent. Two cadences:
2074
+ * - Skill FILES (whiteboard / workflow family / built-in skills) are
2075
+ * re-evaluated on EVERY spawn, before the `skillsInstalledCliIds` gate, so
2076
+ * runtime switches (whiteboard, workflow) and user customization (skill body
2077
+ * override / disable) take effect on the next session without a restart.
2078
+ * - Hook install + botmux-ask fallback do not vary at runtime, so they run
2079
+ * once per CLI per daemon lifecycle (behind the gate).
2057
2080
  */
2058
2081
  export function ensureCliSkills(cliId, cliPathOverride) {
2059
2082
  const adapter = createCliAdapterSync(cliId, cliPathOverride);
@@ -2074,9 +2097,15 @@ export function ensureCliSkills(cliId, cliPathOverride) {
2074
2097
  // Workflow skill 家族同理:跟随机器级 workflow 开关,每次 spawn 重新评估,
2075
2098
  // 关闭时删除已装的三个 skill,下一个会话即生效。
2076
2099
  ensureWorkflowSkills(cliId, pluginSkillsDir, isWorkflowFeatureEnabled());
2100
+ // 内置 skill 文件跟随用户自定义(正文覆盖 / 停用),必须每次 spawn 重新落盘
2101
+ // ——与上面白板 / workflow 同样放在 once-cache 之前,否则改了 skill 正文或
2102
+ // 停用某 skill 后,claude 家族(--plugin-dir)的新会话仍读旧文件、要等 daemon
2103
+ // 重启才生效,和自定义页「下一个会话即生效」不符。ensureSkills 幂等(内容相同
2104
+ // 则跳过),每次 spawn 的代价只是几个小文件的读比对。
2105
+ ensurePluginSkills(cliId, adapter.pluginDir);
2077
2106
  if (skillsInstalledCliIds.has(cliId))
2078
2107
  return;
2079
- ensurePluginSkills(cliId, adapter.pluginDir);
2108
+ // Hook 接管与 botmux-ask 兜底 skill 不随自定义变化 → 每个 daemon 生命周期装一次即可。
2080
2109
  if (adapter.hookInstall) {
2081
2110
  try {
2082
2111
  installHook(cliId, adapter.hookInstall, hookCommandFor(cliId));
@@ -2118,12 +2147,15 @@ export function ensureCliSkills(cliId, cliPathOverride) {
2118
2147
  ensureWorkflowSkills(cliId, skillsDir, globalInstall && isWorkflowFeatureEnabled());
2119
2148
  if (!globalInstall && skillsDir)
2120
2149
  removeGlobalBotmuxSkills(skillsDir);
2121
- if (skillsInstalledCliIds.has(cliId))
2122
- return;
2123
- // 安装是稳定动作,留在 once-cache 里跑一次即可(幂等,内容相同不重写)。
2150
+ // 内置 skill 文件跟随用户自定义(正文覆盖 / 停用),必须每次 spawn 重新落盘
2151
+ // ——同白板 / workflow 放在 once-cache 之前。global 模式落全局盘;幂等(内容相同
2152
+ // 则跳过),停用的 skill 目录会被 ensureSkills 内部清掉,下一个会话即生效。
2124
2153
  if (globalInstall)
2125
2154
  ensureSkills(cliId, skillsDir);
2126
- // askUserQuestion 接管策略与 skill 文件无关:hook 该装还得装(Codex 无 hook,
2155
+ if (skillsInstalledCliIds.has(cliId))
2156
+ return;
2157
+ // 以下都不随自定义变化 → 每个 daemon 生命周期装一次即可。
2158
+ // askUserQuestion 接管策略与 skill 文件无关:hook 该装还得装(Codex 无 hook,
2127
2159
  // 靠 botmux-ask skill / catalog 兜底)。
2128
2160
  if (adapter.hookInstall) {
2129
2161
  try {
@@ -2518,7 +2550,7 @@ export async function detachWorkerForTransfer(ds, opts = {}) {
2518
2550
  * adopt-skip decision is unit-testable without spawning a worker.
2519
2551
  */
2520
2552
  export function shouldDestroyPaneBeforeRestart(ds) {
2521
- return !ds.initConfig?.adoptMode && !ds.adoptedFrom;
2553
+ return !isSharedAdoptSession(ds);
2522
2554
  }
2523
2555
  /**
2524
2556
  * Destroy a still-alive persistent backing pane (tmux/herdr/zellij/zmx) before a
@@ -2818,7 +2850,12 @@ function resolveMojoCancelLaunch(ds, remoteId, label) {
2818
2850
  };
2819
2851
  }
2820
2852
  function destroyOrphanedBackingSession(ds, opts = {}) {
2821
- if (ds.initConfig?.adoptMode || ds.adoptedFrom)
2853
+ // Traditional `/adopt` observes a user-owned tmux/zellij/Herdr target and
2854
+ // must never destroy it. An App Server shared adopt is different: its
2855
+ // persistent bmx-* pane is BotMux's own `codex --remote` client, so reclaim
2856
+ // that pane while leaving the external App Server untouched.
2857
+ if (!isExistingAppServerSharedAdoptPersistedSession(ds.session)
2858
+ && isSharedAdoptSession(ds))
2822
2859
  return;
2823
2860
  reclaimParkedCrashDiagnostic(ds);
2824
2861
  // Riff cancellation is asynchronous and cannot be made safe from this
@@ -3464,7 +3501,8 @@ async function prepareRiffExplicitClose(ds, stored) {
3464
3501
  }
3465
3502
  if (backendType !== 'riff')
3466
3503
  return { ok: true };
3467
- if (ds?.initConfig?.adoptMode || ds?.adoptedFrom || session.adoptedFrom)
3504
+ if ((ds && isSharedAdoptSession(ds))
3505
+ || isSharedAdoptPersistedSession(session))
3468
3506
  return { ok: true };
3469
3507
  if (ds?.worker && !ds.worker.killed) {
3470
3508
  if (!stored || stored.status !== 'active') {
@@ -3562,7 +3600,8 @@ async function prepareMojoExplicitClose(ds, stored) {
3562
3600
  const session = ds?.session ?? stored;
3563
3601
  if (!session)
3564
3602
  return { ok: true };
3565
- if (ds?.initConfig?.adoptMode || ds?.adoptedFrom || session.adoptedFrom)
3603
+ if ((ds && isSharedAdoptSession(ds))
3604
+ || isSharedAdoptPersistedSession(session))
3566
3605
  return { ok: true };
3567
3606
  // A row whose lineage was PARKED (restore-time quarantine moves the id here and
3568
3607
  // clears the active slot) still has a remote session running. It is reported as
@@ -4385,9 +4424,8 @@ function teardownAuthoritativePersistentBackingBeforeCloseImpl(target, closeWins
4385
4424
  }
4386
4425
  const backendType = ds ? getSessionPersistentBackendType(ds) : session.backendType;
4387
4426
  if (backendType !== 'zmx'
4388
- || ds?.initConfig?.adoptMode
4389
- || ds?.adoptedFrom
4390
- || session.adoptedFrom
4427
+ || (ds && isSharedAdoptSession(ds))
4428
+ || isSharedAdoptPersistedSession(session)
4391
4429
  || session.queued
4392
4430
  || session.status === 'closed')
4393
4431
  return;
@@ -4396,6 +4434,16 @@ function teardownAuthoritativePersistentBackingBeforeCloseImpl(target, closeWins
4396
4434
  export function teardownAuthoritativePersistentBackingBeforeClose(target) {
4397
4435
  teardownAuthoritativePersistentBackingBeforeCloseImpl(target, false);
4398
4436
  }
4437
+ /** Render the live streaming-card JSON for `ds` (🖥️ header + usage line +
4438
+ * 显示输出/终端/关闭会话 buttons). Factored so callers outside the normal
4439
+ * screen-update flow — notably the Lark 恢复会话 button — can restore the SAME
4440
+ * card the session had while running, instead of a stripped-down variant.
4441
+ * `status` defaults to the session's last known screen status. */
4442
+ export function buildStreamingCardJson(ds, status) {
4443
+ const botCfg = getBot(ds.larkAppId).config;
4444
+ const effectiveCliId = sessionCliId(ds, botCfg);
4445
+ return buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readableTerminalUrlFor(ds), ds.currentTurnTitle || ds.session.title || sessionCliDisplayName(ds, botCfg), ds.lastScreenContent ?? '', status ?? ds.lastScreenStatus ?? 'starting', effectiveCliId, ds.displayMode ?? 'hidden', ds.streamCardNonce, ds.currentImageKey, isSharedAdoptSession(ds), false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
4446
+ }
4399
4447
  /**
4400
4448
  * Close from a BACKGROUND path — one with no user surface to report to
4401
4449
  * (trigger-session cleanup, deferred-schedule settlement, sweeps).
@@ -4425,14 +4473,22 @@ export async function closeSessionForBackgroundCleanup(sessionId, context) {
4425
4473
  }
4426
4474
  return result;
4427
4475
  }
4428
- export async function closeSession(sessionId) {
4476
+ export async function closeSession(sessionId, opts) {
4477
+ // `awaitWorkerExit` (default true): whether to block on the worker process
4478
+ // actually exiting before returning. A busy CLI wedges in node-pty teardown
4479
+ // and only dies at the ~7s SIGKILL backstop, so callers behind a tight ACK
4480
+ // window (the Lark card "关闭会话" button, whose callback must ACK inside ~3s
4481
+ // or the client surfaces the "code: 300000" toast) pass false: the logical
4482
+ // close below is fully synchronous, and killWorker already armed the kill
4483
+ // backstop, so the worker WILL die in the background — the caller need not
4484
+ // wait for it. Bridge-marker cleanup still runs, deferred behind the fence.
4485
+ const awaitWorkerExit = opts?.awaitWorkerExit ?? true;
4429
4486
  const ds = findActiveBySessionId(sessionId);
4430
4487
  const stored = sessionStore.getOwnedSession(sessionId);
4431
4488
  const closeFrozenBackendType = ds?.initConfig?.backendType
4432
4489
  ?? ds?.session.backendType ?? stored?.backendType;
4433
- const isOwnedClose = !ds?.initConfig?.adoptMode
4434
- && !ds?.adoptedFrom
4435
- && !stored?.adoptedFrom;
4490
+ const isOwnedClose = !(ds && isSharedAdoptSession(ds))
4491
+ && !(stored && isSharedAdoptPersistedSession(stored));
4436
4492
  const isOwnedRiffClose = isOwnedClose && closeFrozenBackendType === 'riff';
4437
4493
  const isOwnedMojoClose = isOwnedClose && closeFrozenBackendType === 'mojo';
4438
4494
  const closeJournal = ds?.session.mojoCloseJournal ?? stored?.mojoCloseJournal;
@@ -4625,8 +4681,16 @@ export async function closeSession(sessionId) {
4625
4681
  publishClosedSessionPatch(sessionId, after?.closedAt ? Date.parse(after.closedAt) : undefined, { tokenUsage: after ? composeRowFromClosed(after).tokenUsage : null });
4626
4682
  }
4627
4683
  if (wasOpen && hadLiveWorker) {
4628
- await closeFenceFor(sessionId, closeWorkerGeneration);
4629
- sessionStore.cleanupSessionBridgeSendMarkersNow(sessionId);
4684
+ if (awaitWorkerExit) {
4685
+ await closeFenceFor(sessionId, closeWorkerGeneration);
4686
+ sessionStore.cleanupSessionBridgeSendMarkersNow(sessionId);
4687
+ }
4688
+ else {
4689
+ // Don't block the caller on the worker exiting (killWorker already armed
4690
+ // the SIGKILL backstop). Defer bridge-marker cleanup behind the same
4691
+ // fence so a mid-flight send is still credited until the worker ACKs/exits.
4692
+ sessionStore.cleanupSessionBridgeSendMarkers(sessionId);
4693
+ }
4630
4694
  }
4631
4695
  // All authoritative map/status/store/event state above transitions
4632
4696
  // synchronously, before the first await. Lark reaction/unsubscribe cleanup is
@@ -4824,10 +4888,11 @@ function removeInactiveRegistration(map, key, ds) {
4824
4888
  }
4825
4889
  function persistedActiveSessionKey(session, fallbackLarkAppId) {
4826
4890
  const larkAppId = session.larkAppId ?? fallbackLarkAppId;
4827
- const anchor = session.vcMeetingReceiver
4828
- ? `vc-receiver:${session.sessionId}`
4829
- : storedSessionAnchorId(session);
4830
- return sessionKey(anchor, larkAppId);
4891
+ // Plan B: a VC meeting agent is an ordinary chat-scope session keyed by its
4892
+ // normal chat anchor — this must stay in lockstep with activeSessionKey()
4893
+ // (core/types.ts) so a restored meeting row re-registers at the SAME slot the
4894
+ // live path uses. The vcMeetingReceiver marker is delivery metadata only.
4895
+ return sessionKey(storedSessionAnchorId(session), larkAppId);
4831
4896
  }
4832
4897
  /**
4833
4898
  * Return an active persisted row that deliberately reserves this routing key
@@ -5053,7 +5118,14 @@ function failOrdinaryImDelivery(record, reason) {
5053
5118
  requireOrdinaryTurnRecoveryAttention(record.ds.session, record.turnId, 'recovery_delivery_failed');
5054
5119
  return;
5055
5120
  }
5056
- if (record.ds.session.vcMeetingReceiver || isSilentScheduledTurn(record.ds, record.turnId))
5121
+ // Plan B: a meeting agent is an ordinary chat-scope session. This tracker only
5122
+ // ever holds ORDINARY IM turns (durable transcript deliveries use the receipt
5123
+ // path, not this map), so a plain user turn's delivery failure must be reported
5124
+ // like any session — otherwise the user's message is silently dropped. Only a
5125
+ // stamped meeting @mention follow-up stays fenced (isMeetingDrivenTurn); a
5126
+ // silent scheduled turn stays suppressed as before. (isMeetingDrivenTurn
5127
+ // supersedes the pre-Plan-B `vcMeetingReceiver` blanket check.)
5128
+ if (isMeetingDrivenTurn(record.ds, record.turnId) || isSilentScheduledTurn(record.ds, record.turnId))
5057
5129
  return;
5058
5130
  const loc = botLocale(getBot(record.ds.larkAppId).config);
5059
5131
  void requireCallbacks().sessionReply(sessionAnchorId(record.ds), tr('worker.input_delivery_failed', { turnId: record.turnId.substring(0, 16) }, loc), 'text', record.ds.larkAppId, record.turnId).catch(err => logger.error(`[${tag(record.ds)}] Failed to report ordinary IM worker delivery failure: `
@@ -5143,8 +5215,13 @@ function shouldTrackOrdinaryImDelivery(ds, message) {
5143
5215
  && (message.turnId.startsWith('om_') || message.turnId.startsWith('bmx-recovery-'))
5144
5216
  && message.dispatchAttempt === undefined
5145
5217
  && (message.type !== 'init' || (!!message.prompt && !message.adoptMode))
5146
- && !ds.adoptedFrom
5147
- && !ds.session.vcMeetingReceiver
5218
+ && !isSharedAdoptSession(ds)
5219
+ // Plan B: a meeting agent is an ordinary chat-scope session. A plain user
5220
+ // turn on it IS an ordinary IM delivery (track it for receipt-ACK + retry so
5221
+ // its failure is reported). Only a meeting-driven turn — a stamped @mention
5222
+ // follow-up here, since durable deliveries are already excluded by the
5223
+ // dispatchAttempt===undefined guard above — stays on the receipt/lease path.
5224
+ && !isMeetingDrivenTurn(ds, message.turnId, message.dispatchAttempt)
5148
5225
  && Number.isSafeInteger(ds.workerGeneration)
5149
5226
  && (ds.workerGeneration ?? 0) > 0
5150
5227
  && ds.session.workerGeneration === ds.workerGeneration;
@@ -5463,8 +5540,6 @@ targetScope, opts) {
5463
5540
  runtimeWorkingDir: ds.workingDir,
5464
5541
  runtimeAdoptedFrom: ds.adoptedFrom,
5465
5542
  });
5466
- if (ds.session.vcMeetingReceiver)
5467
- return { ok: false, error: 'vc_receiver_not_relayable' };
5468
5543
  if (hasProtectedSessionMutationOwnership(ds)) {
5469
5544
  return { ok: false, error: 'codex_app_dispatch_pending' };
5470
5545
  }
@@ -5538,7 +5613,7 @@ targetScope, opts) {
5538
5613
  // Adopt sessions wrap a CLI process that botmux didn't spawn — the user
5539
5614
  // owns it inside their own tmux pane, so moving routing here would be
5540
5615
  // surprising and we don't control the tmux session's lifecycle. Refuse.
5541
- if (ds.session.adoptedFrom)
5616
+ if (isSharedAdoptSession(ds))
5542
5617
  return { ok: false, error: 'adopt_not_relayable' };
5543
5618
  // Busy worker: refuse immediately rather than waiting. An idle-wait loop
5544
5619
  // (previously 60s) created an asymmetry with the peer-dispatch HTTP
@@ -5619,7 +5694,6 @@ targetScope, opts) {
5619
5694
  const persistedTargetConflicts = sessionStore.listSessions().filter(item => item.sessionId !== ds.session.sessionId
5620
5695
  && item.status === 'active'
5621
5696
  && (!item.larkAppId || item.larkAppId === ds.larkAppId)
5622
- && !item.vcMeetingReceiver
5623
5697
  && (item.scope === 'chat' ? item.chatId : item.rootMessageId) === targetAnchor
5624
5698
  && !runtimeIds.has(item.sessionId));
5625
5699
  if (persistedTargetConflicts.some(item => hasProtectedSessionMutationOwnership(item)
@@ -5809,6 +5883,12 @@ export function isForkCapableSession(ds) {
5809
5883
  const cliId = sessionCliId(ds, botCfg);
5810
5884
  if (!FORK_CAPABLE_CLI_IDS.has(cliId))
5811
5885
  return false;
5886
+ // An external App Server thread is not a local rollout. `codex fork` would
5887
+ // resolve against the terminal client's own CODEX_HOME and could fork an
5888
+ // unrelated/empty local session, so refuse rather than claiming a copy was
5889
+ // made. A future server-side thread/fork primitive can add an explicit path.
5890
+ if (ds.session.existingAppServerEndpoint)
5891
+ return false;
5812
5892
  // Codex terminal mode is forkable; Codex under Hybrid RPC input is not (the
5813
5893
  // thread is an app-server live session, no local rollout to `codex fork`).
5814
5894
  //
@@ -5856,13 +5936,11 @@ export async function forkSession(sessionId, targetChatId, targetRootMessageId,
5856
5936
  return { ok: false, error: 'fork_unsupported_backend' };
5857
5937
  // ── Front guards (mirror transferSession; a fork needs a clean, complete
5858
5938
  // source node exactly as a relay does) ──
5859
- if (ds.session.vcMeetingReceiver)
5860
- return { ok: false, error: 'vc_receiver_not_forkable' };
5861
5939
  if (ds.pendingRepo)
5862
5940
  return { ok: false, error: 'not_started_yet' };
5863
5941
  if (!isRelayableRealSession(ds))
5864
5942
  return { ok: false, error: 'not_started_yet' };
5865
- if (ds.session.adoptedFrom)
5943
+ if (isSharedAdoptSession(ds))
5866
5944
  return { ok: false, error: 'adopt_not_forkable' };
5867
5945
  if (isSessionLifecycleInFlight(ds))
5868
5946
  return { ok: false, error: 'worker_busy' };
@@ -6720,7 +6798,18 @@ export function mojoLivePatchForSession(ds) {
6720
6798
  /** Sessions with a quarantine notice send in flight, so it cannot double-post. */
6721
6799
  const mojoQuarantineNoticeInFlight = new Set();
6722
6800
  /**
6723
- * THE auxiliary-UI suppression policy. One definition, two call sites.
6801
+ * Auxiliary-UI suppression policy for the **mojo quarantine notice** path.
6802
+ *
6803
+ * NOTE (Plan B merge, 2026-08): setupWorkerHandlers' `managedAuxUiSuppressed`
6804
+ * no longer delegates here — it keeps its own inline copy that gates on
6805
+ * `isMeetingDrivenTurn` instead of this function's pre-Plan-B blanket
6806
+ * `ds.session.vcMeetingReceiver` check. That blanket check would re-suppress a
6807
+ * plain user turn on a meeting-agent chat session (the "手动@不回复" regression),
6808
+ * which is exactly why the streaming/aux path had to diverge. This function is
6809
+ * therefore currently the mojo-quarantine caller's policy only; migrating it to
6810
+ * `isMeetingDrivenTurn` (so the two converge again without the regression) is a
6811
+ * tracked follow-up. ordinaryTurnRecoveryEligible (below) carries the same
6812
+ * pre-Plan-B blanket and is on the same follow-up.
6724
6813
  *
6725
6814
  * Previously setupWorkerHandlers held this as a closure and the quarantine notice
6726
6815
  * COPIED three of its four checks — dropping `ordinaryManagedSuppression`, so a
@@ -7312,6 +7401,32 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7312
7401
  const cb = requireCallbacks();
7313
7402
  const bot = getBot(ds.larkAppId);
7314
7403
  const botCfg = bot.config;
7404
+ if (botCfg.existingAppServer
7405
+ && botCfg.cliId === 'codex'
7406
+ && ds.session.existingAppServerEndpoint === undefined) {
7407
+ throw new Error('this BotMux bot attaches only to an existing Codex App conversation; '
7408
+ + 'use /adopt to select the thread before sending task input');
7409
+ }
7410
+ let existingAppServerEndpoint;
7411
+ if (ds.session.existingAppServerEndpoint !== undefined) {
7412
+ existingAppServerEndpoint = normalizeExistingAppServerEndpoint(ds.session.existingAppServerEndpoint, `session ${ds.session.sessionId} existingAppServerEndpoint`);
7413
+ if (ds.session.cliId !== 'codex') {
7414
+ throw new Error('existing Codex App Server session must use cliId "codex"; '
7415
+ + 'refusing to launch another CLI against this thread');
7416
+ }
7417
+ if (!ds.session.cliSessionId) {
7418
+ throw new Error('existing Codex App Server session has no selected thread id; '
7419
+ + 'use /adopt to select a Codex App conversation first');
7420
+ }
7421
+ if (ds.session.sandbox === true || botCfg.readIsolation === true) {
7422
+ throw new Error('existing Codex App Server attachment cannot run under sandbox/readIsolation; '
7423
+ + 'the external app-server would remain outside that boundary');
7424
+ }
7425
+ // Remote TUI attach always resumes the explicitly selected remote thread.
7426
+ // Never let later cold-resume/restart policy demote it into a new local
7427
+ // Codex session.
7428
+ resume = true;
7429
+ }
7315
7430
  // A bare /repo placeholder (and a non-Codex empty group-join setup) owns no
7316
7431
  // model turn. Starting its CLI with an empty prompt must not mint a queued
7317
7432
  // activation token: the worker has nothing to submit and could never ACK it.
@@ -7463,7 +7578,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7463
7578
  // worker left parked but couldn't clean (hard-killed while parked, daemon
7464
7579
  // still alive → next message reforks here). The fresh CLI spawns under the
7465
7580
  // real bmx-<sid>; without this, bmx-diag-<sid> + its .ansi file would leak.
7466
- if (!ds.initConfig?.adoptMode && !ds.adoptedFrom)
7581
+ if (!isSharedAdoptSession(ds))
7467
7582
  reclaimParkedCrashDiagnostic(ds);
7468
7583
  agentCfg = sessionAgentConfig(ds, botCfg);
7469
7584
  if (!initTurnId && prompt.length > 0 && agentCfg.cliId === 'codex-app') {
@@ -7472,7 +7587,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7472
7587
  ensureCliEnv(agentCfg.cliId, agentCfg.cliPathOverride);
7473
7588
  let nativeSessionTitle;
7474
7589
  let nativeSessionTitlePrompt;
7475
- if (agentCfg.cliId === 'codex' && !ds.adoptedFrom) {
7590
+ if (agentCfg.cliId === 'codex' && !isSharedAdoptSession(ds)) {
7476
7591
  const isFreshNativeSession = !resume && !ds.session.cliSessionId;
7477
7592
  const titlePrompt = extractBotmuxLarkNativeSessionTitlePrompt(promptPayload.codexAppInput?.text ?? prompt, bot.botName ? [{ name: bot.botName }] : undefined);
7478
7593
  if (isFreshNativeSession && !ds.session.nativeSessionTitleUserDefined) {
@@ -7708,17 +7823,28 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7708
7823
  reason: 'worker_fork_error',
7709
7824
  message: reason,
7710
7825
  });
7711
- // A dedicated VC receiver and a silent schedule have no auxiliary Lark
7712
- // output channel. Keep lifecycle/dashboard state above, but never leak a
7713
- // fork diagnostic into the chat.
7714
- if (ds.session.vcMeetingReceiver || isSilentScheduledTurn(ds, initAttributionTurnId)) {
7826
+ // Plan B: a meeting agent is an ordinary chat-scope session. A fork
7827
+ // diagnostic must stay out of the chat only for a genuinely meeting-driven
7828
+ // opening a durable transcript delivery (initDispatchAttempt set) or a
7829
+ // stamped meeting @mention follow-up — because that path is fenced to the
7830
+ // receipt/lease chain and could otherwise leak on a silent delivery. A plain
7831
+ // user turn on this session gets its fork failure surfaced like any session
7832
+ // (else the user's own turn fails silently). Silent scheduled turns stay
7833
+ // suppressed as before.
7834
+ const forkErrorMeetingDriven = !!ds.session.vcMeetingReceiver
7835
+ && (initDispatchAttempt !== undefined
7836
+ || resolveVcMeetingImTurnOrigin(ds.session, initAttributionTurnId) !== undefined);
7837
+ if (forkErrorMeetingDriven || isSilentScheduledTurn(ds, initAttributionTurnId)) {
7715
7838
  logger.info(`[${t}] Managed/silent fork failure kept out of auxiliary Lark UI `
7716
7839
  + `turn=${initAttributionTurnId?.slice(0, 12) ?? '-'} attempt=${initDispatchAttempt}: ${reason}`);
7717
7840
  return;
7718
7841
  }
7719
7842
  const cliName = sessionCliDisplayName(ds, botCfg);
7720
7843
  const message = tr('worker.start_failed', { cliName, reason }, botLocale(botCfg));
7721
- void cb.sessionReply(sessionAnchorId(ds), message, 'text', ds.larkAppId, fallbackTurnId(ds, initAttributionTurnId), ds.session.vcMeetingReceiver
7844
+ void cb.sessionReply(sessionAnchorId(ds), message, 'text', ds.larkAppId, fallbackTurnId(ds, initAttributionTurnId),
7845
+ // A meeting-driven turn (listener_thread delivery) that DOES surface must
7846
+ // still attribute to the meeting session so the send policy resolves it.
7847
+ forkErrorMeetingDriven
7722
7848
  ? { sourceSessionId: ds.session.sessionId }
7723
7849
  : undefined).catch(replyErr => logger.error(`[${t}] Failed to deliver worker fork error to Lark: ${replyErr}`));
7724
7850
  });
@@ -7761,8 +7887,16 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7761
7887
  // dsh runner turn timeout: read live from bot config so tuning bots.json
7762
7888
  // takes effect on the next worker fork without recreating the session.
7763
7889
  turnTimeoutMs: botCfg.turnTimeoutMs,
7890
+ // dsh runtime variant (official runner vs dsh-tui PTY TUI).
7891
+ dshRuntime: botCfg.dshRuntime,
7764
7892
  disableCliBypass: botCfg.disableCliBypass === true,
7765
- codexRpcInput: botCfg.codexRpcInput === true || config.codexRpcInputDefault,
7893
+ // Existing App Server attachment owns neither an app-server nor a JSON-RPC
7894
+ // input channel. It is a normal official remote TUI, so all user input goes
7895
+ // through its terminal and must never trigger BotMux's self-owned RPC engine.
7896
+ codexRpcInput: existingAppServerEndpoint
7897
+ ? false
7898
+ : botCfg.codexRpcInput === true || config.codexRpcInputDefault,
7899
+ ...(existingAppServerEndpoint ? { existingAppServerEndpoint } : {}),
7766
7900
  // Startup commands run on every fresh spawn (incl. resume) so session-only
7767
7901
  // settings like `/effort ultracode` are re-established. Adopt sessions are
7768
7902
  // observed, not driven — forkAdoptWorker intentionally omits this.
@@ -8126,6 +8260,24 @@ function invalidateTuiPrompt(ds, reason, outcome = 'failed') {
8126
8260
  clearTuiPromptAuthority(ds);
8127
8261
  publishAttentionPatch(ds);
8128
8262
  }
8263
+ /**
8264
+ * Plan B: a VC meeting agent is an ordinary chat-scope session that hosts BOTH
8265
+ * meeting transcript deliveries and plain user IM turns. A turn is
8266
+ * "meeting-driven" only when it is a durable transcript delivery (dispatchAttempt
8267
+ * set) or a stamped meeting @mention follow-up (vcMeetingImTurnOrigin). Only
8268
+ * meeting-driven turns are subject to the durable silent/listener_thread output
8269
+ * policy and the receipt-fenced auxiliary-UI suppression; a plain user turn on
8270
+ * this session is an ordinary turn and must post / notify normally (else the
8271
+ * user's own reply is silently swallowed — the "手动@机器人也不回复" bug). Shared
8272
+ * across setupWorkerHandlers and deliverFinalOutput so every gate stays aligned.
8273
+ */
8274
+ function isMeetingDrivenTurn(ds, turnId, dispatchAttempt) {
8275
+ if (!ds.session.vcMeetingReceiver)
8276
+ return false;
8277
+ if (dispatchAttempt !== undefined)
8278
+ return true;
8279
+ return resolveVcMeetingImTurnOrigin(ds.session, turnId) !== undefined;
8280
+ }
8129
8281
  function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureNotified: false }, reservedWorkerGeneration) {
8130
8282
  const cb = requireCallbacks();
8131
8283
  const t = tag(ds);
@@ -8191,13 +8343,35 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8191
8343
  };
8192
8344
  /** Auxiliary worker UI is never an authorized output channel for a dedicated
8193
8345
  * VC receiver. Dashboard/audit state is still updated before these guards. */
8194
- // Delegates to the shared policy (auxUiSuppressedFor) so this and the mojo
8195
- // quarantine notice cannot drift apart. Suppressing here is the authoritative
8196
- // fix, NOT a fake "success" message id from sessionReply (a synthetic id would
8197
- // get stored as streamCardId and later scheduleCardPatch updateMessage would
8198
- // still dial Feishu). Dashboard/web-terminal state is updated before these
8199
- // guards, so the terminal view is unaffected.
8200
- const managedAuxUiSuppressed = (turnId, dispatchAttempt) => auxUiSuppressedFor(ds, turnId, dispatchAttempt);
8346
+ const managedAuxUiSuppressed = (turnId, dispatchAttempt) => {
8347
+ // No-transport session (apiOnly bot or HTTP virtual chat): there is no real
8348
+ // Feishu chat to render a card/reaction into. Suppress ALL auxiliary UI at
8349
+ // the single source every aux-UI handler funnels through this is the
8350
+ // authoritative fix, NOT a fake "success" message id from sessionReply (a
8351
+ // synthetic id would get stored as streamCardId and later scheduleCardPatch
8352
+ // updateMessage would still dial Feishu). Dashboard/web-terminal state is
8353
+ // still updated before these guards, so the terminal view is unaffected.
8354
+ if (!larkTransportEnabled({ chatId: ds.chatId, apiOnly: getBot(ds.larkAppId).config.apiOnly }))
8355
+ return true;
8356
+ if (isSilentScheduledTurn(ds, turnId))
8357
+ return true;
8358
+ // Plan B: a VC meeting agent is an ordinary chat-scope session, so the live
8359
+ // STREAMING CARD is no longer gated here at all — it surfaces through the
8360
+ // normal post/patch path like any group session (that was the "看不到流式
8361
+ // 卡片" report; those sites dropped their VC guard). What remains funnelled
8362
+ // through managedAuxUiSuppressed is out-of-band auxiliary UI (startup-failure
8363
+ // notices, usage-limit patches, exit UI): for a genuinely meeting-driven turn
8364
+ // — a durable transcript delivery (dispatchAttempt set) or a stamped meeting
8365
+ // @mention follow-up — that diagnostic stays fenced to the receipt/retry
8366
+ // chain and must never leak out-of-band (a silent delivery especially). A
8367
+ // plain user turn on this session has neither marker and notifies normally.
8368
+ // (isMeetingDrivenTurn supersedes the pre-Plan-B per-bot-receiver blanket
8369
+ // check that master's auxUiSuppressedFor still uses — keeping this inline is
8370
+ // the load-bearing "手动@不回复" fix.)
8371
+ if (isMeetingDrivenTurn(ds, turnId, dispatchAttempt))
8372
+ return true;
8373
+ return ordinaryManagedSuppression(turnId, dispatchAttempt);
8374
+ };
8201
8375
  /** final_output is the sole exception: listener_thread and exact IM replies
8202
8376
  * may proceed into the durable action ledger; silent/stale attempts do not. */
8203
8377
  const managedFinalOutputSuppressed = (turnId, dispatchAttempt) => {
@@ -8205,13 +8379,16 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8205
8379
  return true;
8206
8380
  if (isTriggerFinalSuppressed(ds, turnId))
8207
8381
  return true;
8208
- if (!ds.session.vcMeetingReceiver) {
8382
+ // Only a genuinely meeting-driven turn is subject to the durable meeting-send
8383
+ // policy; a plain user turn on this session posts through the ordinary path
8384
+ // (see isMeetingDrivenTurn).
8385
+ if (!isMeetingDrivenTurn(ds, turnId, dispatchAttempt)) {
8209
8386
  return ordinaryManagedSuppression(turnId, dispatchAttempt);
8210
8387
  }
8211
8388
  // Resolve every Lark-facing worker event against durable origin state. The
8212
8389
  // receipt freezes responseMode, so terminal→idle updates and daemon restore
8213
8390
  // cannot become loud merely because an in-memory suppression map was
8214
- // cleared/lost. Missing attribution on a dedicated receiver fails closed.
8391
+ // cleared/lost. Missing attribution on a meeting-driven turn fails closed.
8215
8392
  const decision = evaluateVcMeetingManagedSend(config.session.dataDir, {
8216
8393
  receiverSessionId: ds.session.sessionId,
8217
8394
  receiverSession: true,
@@ -8253,9 +8430,75 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8253
8430
  logger.error(`[${t}] Failed to deliver worker startup failure to Lark: ${err?.message ?? err}`);
8254
8431
  }
8255
8432
  };
8433
+ /**
8434
+ * Daemon-side self-heal for TRANSIENT startup/relaunch failures (see
8435
+ * worker-startup-retry.ts; 2026-08-23 tmux restart-storm incident). Returns
8436
+ * true when a silent bounded retry was scheduled INSTEAD of a user-visible
8437
+ * notify. Scope is deliberately narrow:
8438
+ * - only turn-less failures — a turn-carrying failure has a waiting sender
8439
+ * and (for durable VC/at-most-once turns) its own delivery state machine
8440
+ * that must keep owning retries;
8441
+ * - only reasons the classifier deems transient host/backend pressure;
8442
+ * - bounded by MAX_STARTUP_AUTO_RETRIES per failing streak (reset on a
8443
+ * worker generation reaching `ready`), so a persistent failure still
8444
+ * surfaces to the user with the standard card.
8445
+ * The retry itself is a blank re-fork (`forkWorker(ds, '', true)`) — exactly
8446
+ * what a daemon restart or the next inbound message would do: re-attach when
8447
+ * the backing pane survived (the 08-23 case — pane and CLI were alive, only
8448
+ * the worker had died), cold `--resume` otherwise.
8449
+ */
8450
+ const scheduleTransientStartupRetry = (reason, turnId, dispatchAttempt) => {
8451
+ if (turnId !== undefined || dispatchAttempt !== undefined)
8452
+ return false;
8453
+ if (startupState.failureNotified)
8454
+ return false;
8455
+ if (!isTransientStartupFailure(reason))
8456
+ return false;
8457
+ if (ds.session.status !== 'active')
8458
+ return false;
8459
+ const attempts = (ds.startupAutoRetry?.attempts ?? 0) + 1;
8460
+ if (attempts > MAX_STARTUP_AUTO_RETRIES)
8461
+ return false;
8462
+ // Mark this generation handled so the pre-ready exit guard cannot post a
8463
+ // duplicate start_exited_early card behind the scheduled retry.
8464
+ startupState.failureNotified = true;
8465
+ const delayMs = startupAutoRetryDelayMs(ds.session.sessionId, attempts);
8466
+ logger.warn(`[${t}] Transient worker startup failure (${reason}); `
8467
+ + `auto-retry ${attempts}/${MAX_STARTUP_AUTO_RETRIES} in ${Math.round(delayMs / 1000)}s`);
8468
+ const timer = setTimeout(() => {
8469
+ if (ds.startupAutoRetry?.timer === timer)
8470
+ ds.startupAutoRetry.timer = undefined;
8471
+ // Lifecycle guards: the session may have been closed, replaced in the
8472
+ // registry, or revived by an inbound message while the timer slept.
8473
+ if (ds.session.status !== 'active')
8474
+ return;
8475
+ if (ds.worker && !ds.worker.killed)
8476
+ return;
8477
+ if (activeSessionsRegistry && activeSessionsRegistry.get(activeSessionKey(ds)) !== ds)
8478
+ return;
8479
+ logger.info(`[${t}] Auto-retrying worker start (${attempts}/${MAX_STARTUP_AUTO_RETRIES}) `
8480
+ + 'after transient startup failure');
8481
+ try {
8482
+ forkWorker(ds, '', true);
8483
+ }
8484
+ catch (err) {
8485
+ logger.error(`[${t}] Startup auto-retry fork failed: ${err?.message ?? err}`);
8486
+ }
8487
+ }, delayMs);
8488
+ timer.unref?.();
8489
+ // An inbound message can revive the session between two failing
8490
+ // generations; if the replacement fails too, the previous generation's
8491
+ // still-pending timer must not survive as an orphan (its guards make a
8492
+ // double blank re-fork merely wasteful, not harmful — but one streak owns
8493
+ // exactly one pending retry).
8494
+ if (ds.startupAutoRetry?.timer)
8495
+ clearTimeout(ds.startupAutoRetry.timer);
8496
+ ds.startupAutoRetry = { attempts, timer };
8497
+ return true;
8498
+ };
8256
8499
  // Adopt mode flags — computed once, used in all buildStreamingCard calls.
8257
8500
  // Bridge mode (the v3 default for /adopt) hides the legacy takeover button.
8258
- const isAdopt = !!ds.adoptedFrom;
8501
+ const isAdopt = isSharedAdoptSession(ds);
8259
8502
  const showTakeover = false;
8260
8503
  worker.on('message', async (msg) => {
8261
8504
  // Every IPC message is scoped to the child generation that emitted it.
@@ -8409,6 +8652,14 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8409
8652
  }
8410
8653
  startupState.ready = true;
8411
8654
  ds.workerReady = true;
8655
+ // A generation reached ready: the transient-startup failing streak is
8656
+ // over. Clear the budget and any still-pending retry timer (an inbound
8657
+ // message may have revived the session while a retry slept).
8658
+ if (ds.startupAutoRetry) {
8659
+ if (ds.startupAutoRetry.timer)
8660
+ clearTimeout(ds.startupAutoRetry.timer);
8661
+ ds.startupAutoRetry = undefined;
8662
+ }
8412
8663
  // Restore the daemon-owned display mode BEFORE any card handling: a
8413
8664
  // fresh worker always boots with displayMode='hidden', and every early
8414
8665
  // break below (managed / replyAlreadySent / streamingCardDisabled /
@@ -8579,8 +8830,10 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8579
8830
  // POST a second card; the in-flight POST becomes this turn's card.
8580
8831
  if (ds.streamCardId === CARD_POSTING_SENTINEL)
8581
8832
  break;
8833
+ const pendingNewTurn = !!ds.streamCardPending;
8582
8834
  const postingGeneration = ds.streamCardTurnGeneration ?? 0;
8583
8835
  const cardReplyTarget = captureStreamingCardReplyTarget(ds, msg.turnId);
8836
+ const statusRevisionAtPost = ds.streamCardStatusRevision ?? 0;
8584
8837
  ds.streamCardId = CARD_POSTING_SENTINEL;
8585
8838
  try {
8586
8839
  ds.streamCardNonce = randomBytes(4).toString('hex');
@@ -8588,7 +8841,9 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8588
8841
  // See PATCH-branch comment above re: lastScreenStatus preference.
8589
8842
  // For relay (kill+fork with surviving tmux/CLI), this avoids the
8590
8843
  // jarring "启动中" right after the M1 "已接力" announcement.
8591
- const initStatus = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
8844
+ const initStatus = pendingNewTurn
8845
+ ? turnStartingCardStatus(ds, effectiveCliId)
8846
+ : (ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting'));
8592
8847
  const streamCardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, initTitle,
8593
8848
  // For /relay resume, ds.lastScreenContent is the cached pane
8594
8849
  // from before the kill+fork — using it avoids a blank flash
@@ -8629,6 +8884,9 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8629
8884
  // resume where the CLI kept running), arm here — same authorized arm
8630
8885
  // point as the reuse branch, now that streamCardId is the real id.
8631
8886
  syncUsageRefreshTimer(ds);
8887
+ if (!superseded) {
8888
+ reconcilePostedStartingCard(ds, cardReplyTarget.turnId, statusRevisionAtPost);
8889
+ }
8632
8890
  if (superseded && ds.streamCardPendingTurnId) {
8633
8891
  void postTurnStartingCard(ds, cb.sessionReply, ds.streamCardPendingTurnId);
8634
8892
  }
@@ -8649,7 +8907,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8649
8907
  // Fallback: send static session card
8650
8908
  try {
8651
8909
  const localCliReadyAtBuild = isLocalCliOpenReady(ds, { cliId: effectiveCliId });
8652
- const cardJson = buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, undefined, !!ds.adoptedFrom, loc, localCliReadyAtBuild, sessionRuntimeDisplayName(ds, botCfg));
8910
+ const cardJson = buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, undefined, isSharedAdoptSession(ds), loc, localCliReadyAtBuild, sessionRuntimeDisplayName(ds, botCfg));
8653
8911
  const fallbackCardId = await scopedReply(cardJson, 'interactive', msg.turnId);
8654
8912
  if (!ownsLifecycleMutation()) {
8655
8913
  void deleteMessage(ds.larkAppId, fallbackCardId).catch(() => { });
@@ -8657,7 +8915,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8657
8915
  }
8658
8916
  if (!localCliReadyAtBuild && isLocalCliOpenEnabled()
8659
8917
  && isLocalCliOpenReady(ds, { cliId: effectiveCliId })) {
8660
- const readyCardJson = buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, undefined, !!ds.adoptedFrom, loc, true, sessionRuntimeDisplayName(ds, botCfg));
8918
+ const readyCardJson = buildSessionCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, ds.session.title || sessionCliDisplayName(ds, botCfg), effectiveCliId, undefined, isSharedAdoptSession(ds), loc, true, sessionRuntimeDisplayName(ds, botCfg));
8661
8919
  try {
8662
8920
  await updateMessage(ds.larkAppId, fallbackCardId, readyCardJson);
8663
8921
  }
@@ -8851,7 +9109,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8851
9109
  const prevStatus = ds.lastScreenStatus;
8852
9110
  updateUsageLimitState(ds, msg.usageLimit);
8853
9111
  ds.lastScreenContent = msg.content;
8854
- ds.lastScreenStatus = (msg.usageLimit ?? ds.usageLimit) ? 'limited' : msg.status;
9112
+ ds.lastScreenStatus = resolveUsageAwareScreenStatus(ds, msg.status, msg.usageLimit);
8855
9113
  bumpStreamCardStatusRevision(ds);
8856
9114
  // A suspend that arrived mid-turn parked itself here. Defer until this
8857
9115
  // screen_update has finished using process state — suspendWorker nulls
@@ -9046,7 +9304,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
9046
9304
  break;
9047
9305
  const prevStatus = ds.lastScreenStatus;
9048
9306
  updateUsageLimitState(ds, msg.usageLimit);
9049
- ds.lastScreenStatus = (msg.usageLimit ?? ds.usageLimit) ? 'limited' : msg.status;
9307
+ ds.lastScreenStatus = resolveUsageAwareScreenStatus(ds, msg.status, msg.usageLimit);
9050
9308
  bumpStreamCardStatusRevision(ds);
9051
9309
  // Same deferred-suspend checkpoint as the screen_update branch, and
9052
9310
  // deferred for the same reason (see runPendingSuspendIfSettled).
@@ -9406,9 +9664,45 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
9406
9664
  break;
9407
9665
  }
9408
9666
  const suppressExitUi = managedAuxUiSuppressed(msg.turnId, msg.dispatchAttempt);
9667
+ if (msg.codexAppActiveWriter === true && effectiveCliId === 'codex-app') {
9668
+ // `thread/resume` was explicitly rejected because another Codex
9669
+ // app-server currently owns the persisted thread. This is a safe,
9670
+ // expected handoff conflict — never spend restart budget or let the
9671
+ // generic second-restart policy drop cliSessionId and create a fresh
9672
+ // context. Park the logical Botmux session so its next message
9673
+ // retries the SAME thread after the external writer releases it.
9674
+ const thread = ds.session.cliSessionId ?? ds.session.sessionId;
9675
+ logger.warn(`[${t}] Codex App resume blocked by external active writer; preserving `
9676
+ + `thread ${thread.substring(0, 12)}`);
9677
+ const suspended = suspendWorker(ds, 'codex_app_active_writer');
9678
+ if (!suspended) {
9679
+ // The runner process already exited and there is no safe live
9680
+ // backend suspension path. Retire only this Node worker; never
9681
+ // destroy the external app-server that owns the thread.
9682
+ retireWorkerProcessOnly(ds, 'codex_app_active_writer');
9683
+ ds.hasHistory = true;
9684
+ ds.session.suspendedColdResume = true;
9685
+ sessionStore.updateSession(ds.session);
9686
+ }
9687
+ if (!suppressExitUi) {
9688
+ const threadLabel = `${thread.substring(0, 16)}…`;
9689
+ const message = loc === 'zh'
9690
+ ? `⚠️ Codex App 历史会话(${threadLabel})当前正由另一个 Codex App / app-server 持有。`
9691
+ + 'BotMux 已保留原上下文,不会新建空会话;请在 App 侧释放该会话后,再发送一条消息重试恢复。'
9692
+ : `⚠️ Codex App thread (${threadLabel}) is currently held by another Codex App / app-server writer. `
9693
+ + 'BotMux preserved the original context and did not create a fresh session; release it in the App, then send another message to retry.';
9694
+ try {
9695
+ await scopedReply(message, 'text', msg.turnId);
9696
+ }
9697
+ catch (replyErr) {
9698
+ logger.error(`[${t}] Failed to report Codex App active-writer conflict: ${replyErr}`);
9699
+ }
9700
+ }
9701
+ break;
9702
+ }
9409
9703
  // Do NOT auto-restart in adopt mode — there's nothing to restart
9410
- if (ds.adoptedFrom) {
9411
- logger.info(`[${t}] Adopted session ended`);
9704
+ if (isSharedAdoptSession(ds)) {
9705
+ logger.info(`[${t}] Shared adopted session ended`);
9412
9706
  // Freeze the streaming card
9413
9707
  if (!suppressExitUi && ds.streamCardId && workerHasInitialized(ds)) {
9414
9708
  const readUrl = readableTerminalUrlFor(ds);
@@ -9544,7 +9838,12 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
9544
9838
  logger.error(`[${t}] Worker error: ${msg.message}`);
9545
9839
  // `error` is a fatal launch-generation signal. It normally arrives
9546
9840
  // during init, but can also follow a previously-ready worker whose CLI
9547
- // recovery/restart fails; that later failure must remain user-visible.
9841
+ // recovery/restart fails; that later failure must remain user-visible
9842
+ // UNLESS it is a turn-less transient (post-outage restart storm), where
9843
+ // a bounded silent re-fork self-heals without waking anyone (08-23:
9844
+ // idle sessions got "会话启动失败" cards while their panes were alive).
9845
+ if (scheduleTransientStartupRetry(msg.message, msg.turnId, msg.dispatchAttempt))
9846
+ break;
9548
9847
  await notifyStartupFailure(msg.message, msg.turnId, msg.dispatchAttempt);
9549
9848
  break;
9550
9849
  }
@@ -10376,14 +10675,19 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
10376
10675
  ds.worker = null;
10377
10676
  ds.workerReady = false;
10378
10677
  ds.workerPort = null;
10678
+ // A dead worker can no longer refresh its card/usage view.
10679
+ clearUsageRefreshTimer(ds);
10680
+ // A worker owns the terminal HTTP listener. Persisting its former port
10681
+ // after exit leaves the Dashboard proxy routing `/terminal/<session>` to
10682
+ // a dead socket until some later ready event overwrites it. Clear both
10683
+ // durable and aggregated state now so a persistent backend can be
10684
+ // re-attached on demand instead of returning a stale 502.
10685
+ ds.session.webPort = undefined;
10379
10686
  // A queued suspend for THIS generation is now moot — the worker it was
10380
10687
  // about is gone. Leaving it set would suspend the replacement on its
10381
10688
  // first idle (the deferred checkpoint's own "worker gone" branch cannot
10382
10689
  // help: it only runs on a screen update that will never arrive).
10383
10690
  clearPendingSuspendClaim(ds, 'worker exited');
10384
- // Dead worker generation — stop the periodic usage refresh immediately
10385
- // instead of waiting a tick for it to self-clear on !workerHasInitialized.
10386
- clearUsageRefreshTimer(ds);
10387
10691
  ds.workerToken = null;
10388
10692
  ds.workerViewToken = null;
10389
10693
  ds.managedTurnOrigin = undefined;
@@ -10411,6 +10715,13 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
10411
10715
  sessionStore.updateSession(ds.session);
10412
10716
  if (exitedPreviewTarget !== undefined)
10413
10717
  publishSessionPreviewCleared(ds.session.sessionId);
10718
+ dashboardEventBus.publish({
10719
+ type: 'session.update',
10720
+ body: {
10721
+ sessionId: ds.session.sessionId,
10722
+ patch: { webPort: null, workerPid: null },
10723
+ },
10724
+ });
10414
10725
  }
10415
10726
  if (!transferRetirement) {
10416
10727
  try {
@@ -10541,10 +10852,11 @@ async function finishTurnReactions(ds) {
10541
10852
  return;
10542
10853
  // Detach the batch first so a second idle edge can't double-flip it.
10543
10854
  ds.pendingAckReactions = [];
10544
- // A dedicated receiver has no progress-reaction channel. Clear any stale
10545
- // in-memory entries restored from an older build without touching Lark.
10546
- if (ds.session.vcMeetingReceiver)
10547
- return;
10855
+ // Plan B: a meeting agent is an ordinary chat-scope session. Pending ack
10856
+ // reactions only ever exist for a real inbound user message (the ✋ is placed
10857
+ // when that message arrives — transcript deliveries have no inbound message to
10858
+ // react to), so a non-empty list here always belongs to a plain user turn and
10859
+ // must settle to ✅ like any session. No VC special-case.
10548
10860
  const silent = silentTurnReactions(ds);
10549
10861
  const doneEmoji = doneReactionEmojiFor(ds);
10550
10862
  for (const ack of list) {
@@ -10728,7 +11040,14 @@ function deliverFinalOutput(ds, msg, t, attempt, onComplete, isStillOwned = () =
10728
11040
  const imOrigin = msg.dispatchAttempt === undefined
10729
11041
  ? resolveVcMeetingImTurnOrigin(ds.session, msg.turnId)
10730
11042
  : undefined;
10731
- const managedDecision = ds.session.vcMeetingReceiver
11043
+ // Plan B: the meeting agent is an ordinary chat-scope session, so it also
11044
+ // delivers plain user turns. Apply the durable meeting-send policy ONLY to
11045
+ // a genuinely meeting-driven turn (a durable transcript delivery, or a
11046
+ // stamped meeting @mention follow-up). A plain user turn is not
11047
+ // meeting-driven → managedDecision stays undefined → it delivers through
11048
+ // the ordinary path. Without this gate a plain user reply resolves
11049
+ // `origin_unproven` and is silently dropped (the "手动@机器人也不回复" bug).
11050
+ const managedDecision = isMeetingDrivenTurn(ds, msg.turnId, msg.dispatchAttempt)
10732
11051
  ? evaluateVcMeetingManagedSend(config.session.dataDir, {
10733
11052
  receiverSessionId: ds.session.sessionId,
10734
11053
  receiverSession: true,
@@ -10746,7 +11065,7 @@ function deliverFinalOutput(ds, msg, t, attempt, onComplete, isStillOwned = () =
10746
11065
  return;
10747
11066
  }
10748
11067
  const revalidateManagedSend = () => {
10749
- if (!ds.session.vcMeetingReceiver)
11068
+ if (!isMeetingDrivenTurn(ds, msg.turnId, msg.dispatchAttempt))
10750
11069
  return;
10751
11070
  const current = evaluateVcMeetingManagedSend(config.session.dataDir, {
10752
11071
  receiverSessionId: ds.session.sessionId,
@@ -10824,11 +11143,14 @@ function deliverFinalOutput(ds, msg, t, attempt, onComplete, isStillOwned = () =
10824
11143
  const feedbackRequesterSubjectId = recipientOpenId;
10825
11144
  const feedback = feedbackPolicy && feedbackRequesterSubjectId ? { policy: feedbackPolicy } : undefined;
10826
11145
  cardUsage ??= getDaemonReplyCardUsageSnapshot(ds, effectiveCliId);
11146
+ const localTurnTitle = msg.kind === 'local-turn-headless'
11147
+ ? tr('card.local_turn_resumed', undefined, localeForBot(ds.larkAppId))
11148
+ : isExistingAppServerSharedAdoptPersistedSession(ds.session)
11149
+ ? tr('card.codex_app_shared_turn', undefined, localeForBot(ds.larkAppId))
11150
+ : tr('card.local_turn', undefined, localeForBot(ds.larkAppId));
10827
11151
  const cardJson = msg.kind === 'local-turn' || msg.kind === 'local-turn-headless'
10828
11152
  ? buildContextualReplyCard({
10829
- title: msg.kind === 'local-turn-headless'
10830
- ? tr('card.local_turn_resumed', undefined, localeForBot(ds.larkAppId))
10831
- : tr('card.local_turn', undefined, localeForBot(ds.larkAppId)),
11153
+ title: localTurnTitle,
10832
11154
  userText: msg.kind === 'local-turn' ? safeUserText ?? '' : undefined,
10833
11155
  assistantText: safeAssistantText,
10834
11156
  assistantLabel: storedSessionCliDisplayName(ds),
@@ -11287,6 +11609,7 @@ export function forkAdoptWorker(ds, opts) {
11287
11609
  cliSessionId: isStructuredBridge ? adopted.sessionId : undefined,
11288
11610
  model: agentCfg.model,
11289
11611
  turnTimeoutMs: botCfg.turnTimeoutMs,
11612
+ dshRuntime: botCfg.dshRuntime,
11290
11613
  disableCliBypass: botCfg.disableCliBypass === true,
11291
11614
  codexRpcInput: botCfg.codexRpcInput === true || config.codexRpcInputDefault,
11292
11615
  // Adopt is normally observe-only (prompt=''), driven later by 'message'
@@ -11501,7 +11824,20 @@ export function killStalePids(activeSessions_, runtimeSessions) {
11501
11824
  try {
11502
11825
  // Check if process exists (signal 0 doesn't kill, just checks)
11503
11826
  process.kill(session.pid, 0);
11504
- // Process exists kill its process group
11827
+ // Existing-App-Server share: the worker and its BotMux-owned remote TUI
11828
+ // can be in one process group. Reap only the stale worker PID; killing
11829
+ // the group would also terminate `codex --remote`, even though the
11830
+ // replacement daemon is expected to reattach that client. The external
11831
+ // App Server itself is never ours to signal.
11832
+ if (isExistingAppServerSharedAdoptPersistedSession(session)) {
11833
+ logger.info(`Killing stale shared-App-Server worker only (pid: ${session.pid}, session: ${session.sessionId})`);
11834
+ try {
11835
+ process.kill(session.pid, 'SIGTERM');
11836
+ }
11837
+ catch { /* already gone */ }
11838
+ continue;
11839
+ }
11840
+ // Ordinary managed sessions own their complete process group.
11505
11841
  logger.info(`Killing stale CLI process (pid: ${session.pid}, session: ${session.sessionId})`);
11506
11842
  try {
11507
11843
  process.kill(-session.pid, 'SIGTERM');
@@ -11767,6 +12103,12 @@ function cleanupPersistentBackendSessions(backendType, activeSessions_, runtimeS
11767
12103
  for (const session of activeSessions_) {
11768
12104
  if (!belongsToBackend(session))
11769
12105
  continue;
12106
+ // The remote TUI deliberately uses cliId=codex even when the Bot's
12107
+ // default/new-session runtime remains codex-app. This is a shared-adopt
12108
+ // binding, not a stale CLI selection, so keep its bmx-* client alive for
12109
+ // the restore path. Explicit /detach remains responsible for cleanup.
12110
+ if (isExistingAppServerSharedAdoptPersistedSession(session))
12111
+ continue;
11770
12112
  const sessionCliId = session.cliId;
11771
12113
  if (!sessionCliId || !session.larkAppId)
11772
12114
  continue;