botmux 3.16.1 → 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 (595) 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/dispatch.d.ts.map +1 -1
  103. package/dist/core/dispatch.js +5 -4
  104. package/dist/core/dispatch.js.map +1 -1
  105. package/dist/core/existing-app-server.d.ts +22 -0
  106. package/dist/core/existing-app-server.d.ts.map +1 -0
  107. package/dist/core/existing-app-server.js +85 -0
  108. package/dist/core/existing-app-server.js.map +1 -0
  109. package/dist/core/native-topic-id.d.ts +9 -0
  110. package/dist/core/native-topic-id.d.ts.map +1 -0
  111. package/dist/core/native-topic-id.js +14 -0
  112. package/dist/core/native-topic-id.js.map +1 -0
  113. package/dist/core/per-bot-env.d.ts.map +1 -1
  114. package/dist/core/per-bot-env.js +10 -0
  115. package/dist/core/per-bot-env.js.map +1 -1
  116. package/dist/core/session-activity.d.ts +11 -0
  117. package/dist/core/session-activity.d.ts.map +1 -1
  118. package/dist/core/session-activity.js +23 -0
  119. package/dist/core/session-activity.js.map +1 -1
  120. package/dist/core/session-group-birth.d.ts.map +1 -1
  121. package/dist/core/session-group-birth.js +23 -8
  122. package/dist/core/session-group-birth.js.map +1 -1
  123. package/dist/core/session-manager.d.ts +1 -4
  124. package/dist/core/session-manager.d.ts.map +1 -1
  125. package/dist/core/session-manager.js +16 -20
  126. package/dist/core/session-manager.js.map +1 -1
  127. package/dist/core/shared-adopt.d.ts +20 -0
  128. package/dist/core/shared-adopt.d.ts.map +1 -0
  129. package/dist/core/shared-adopt.js +26 -0
  130. package/dist/core/shared-adopt.js.map +1 -0
  131. package/dist/core/skills/customize-command.d.ts +7 -0
  132. package/dist/core/skills/customize-command.d.ts.map +1 -0
  133. package/dist/core/skills/customize-command.js +303 -0
  134. package/dist/core/skills/customize-command.js.map +1 -0
  135. package/dist/core/terminal-write-auth.d.ts +15 -0
  136. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  137. package/dist/core/terminal-write-auth.js +15 -0
  138. package/dist/core/terminal-write-auth.js.map +1 -1
  139. package/dist/core/terminal-write-frame.d.ts +30 -0
  140. package/dist/core/terminal-write-frame.d.ts.map +1 -0
  141. package/dist/core/terminal-write-frame.js +44 -0
  142. package/dist/core/terminal-write-frame.js.map +1 -0
  143. package/dist/core/types.d.ts +14 -4
  144. package/dist/core/types.d.ts.map +1 -1
  145. package/dist/core/types.js +6 -8
  146. package/dist/core/types.js.map +1 -1
  147. package/dist/core/worker-pool.d.ts +21 -4
  148. package/dist/core/worker-pool.d.ts.map +1 -1
  149. package/dist/core/worker-pool.js +415 -100
  150. package/dist/core/worker-pool.js.map +1 -1
  151. package/dist/core/worker-startup-retry.d.ts +52 -0
  152. package/dist/core/worker-startup-retry.d.ts.map +1 -0
  153. package/dist/core/worker-startup-retry.js +68 -0
  154. package/dist/core/worker-startup-retry.js.map +1 -0
  155. package/dist/daemon.d.ts +9 -8
  156. package/dist/daemon.d.ts.map +1 -1
  157. package/dist/daemon.js +467 -171
  158. package/dist/daemon.js.map +1 -1
  159. package/dist/dashboard/autostart-api.d.ts +26 -0
  160. package/dist/dashboard/autostart-api.d.ts.map +1 -0
  161. package/dist/dashboard/autostart-api.js +88 -0
  162. package/dist/dashboard/autostart-api.js.map +1 -0
  163. package/dist/dashboard/bot-payload.d.ts +5 -0
  164. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  165. package/dist/dashboard/bot-payload.js +2 -0
  166. package/dist/dashboard/bot-payload.js.map +1 -1
  167. package/dist/dashboard/control-csrf.d.ts +29 -1
  168. package/dist/dashboard/control-csrf.d.ts.map +1 -1
  169. package/dist/dashboard/control-csrf.js +45 -11
  170. package/dist/dashboard/control-csrf.js.map +1 -1
  171. package/dist/dashboard/customization-api.d.ts +23 -0
  172. package/dist/dashboard/customization-api.d.ts.map +1 -0
  173. package/dist/dashboard/customization-api.js +198 -0
  174. package/dist/dashboard/customization-api.js.map +1 -0
  175. package/dist/dashboard/debug-terminal.d.ts +13 -1
  176. package/dist/dashboard/debug-terminal.d.ts.map +1 -1
  177. package/dist/dashboard/debug-terminal.js +18 -4
  178. package/dist/dashboard/debug-terminal.js.map +1 -1
  179. package/dist/dashboard/settings-write-applier.d.ts +0 -23
  180. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  181. package/dist/dashboard/settings-write-applier.js +7 -28
  182. package/dist/dashboard/settings-write-applier.js.map +1 -1
  183. package/dist/dashboard/terminal-control-route.d.ts +32 -0
  184. package/dist/dashboard/terminal-control-route.d.ts.map +1 -0
  185. package/dist/dashboard/terminal-control-route.js +70 -0
  186. package/dist/dashboard/terminal-control-route.js.map +1 -0
  187. package/dist/dashboard/terminal-control.d.ts +64 -13
  188. package/dist/dashboard/terminal-control.d.ts.map +1 -1
  189. package/dist/dashboard/terminal-control.js +77 -14
  190. package/dist/dashboard/terminal-control.js.map +1 -1
  191. package/dist/dashboard/terminal-front-proxy.d.ts.map +1 -1
  192. package/dist/dashboard/terminal-front-proxy.js +22 -8
  193. package/dist/dashboard/terminal-front-proxy.js.map +1 -1
  194. package/dist/dashboard/vc-consumer-profiles-api.d.ts +69 -18
  195. package/dist/dashboard/vc-consumer-profiles-api.d.ts.map +1 -1
  196. package/dist/dashboard/vc-consumer-profiles-api.js +149 -29
  197. package/dist/dashboard/vc-consumer-profiles-api.js.map +1 -1
  198. package/dist/dashboard/web/agent-workbench-api.d.ts +34 -2
  199. package/dist/dashboard/web/agent-workbench-api.d.ts.map +1 -1
  200. package/dist/dashboard/web/agent-workbench-api.js +39 -4
  201. package/dist/dashboard/web/agent-workbench-api.js.map +1 -1
  202. package/dist/dashboard/web/agent-workbench-appearance.d.ts +6 -3
  203. package/dist/dashboard/web/agent-workbench-appearance.d.ts.map +1 -1
  204. package/dist/dashboard/web/agent-workbench-appearance.js +7 -4
  205. package/dist/dashboard/web/agent-workbench-appearance.js.map +1 -1
  206. package/dist/dashboard/web/agent-workbench-model.d.ts +2 -0
  207. package/dist/dashboard/web/agent-workbench-model.d.ts.map +1 -1
  208. package/dist/dashboard/web/agent-workbench-model.js.map +1 -1
  209. package/dist/dashboard/web/agent-workbench-page.js +2 -2
  210. package/dist/dashboard/web/agent-workbench-page.js.map +1 -1
  211. package/dist/dashboard/web/agent-workbench-panes.d.ts +59 -4
  212. package/dist/dashboard/web/agent-workbench-panes.d.ts.map +1 -1
  213. package/dist/dashboard/web/agent-workbench-panes.js +493 -85
  214. package/dist/dashboard/web/agent-workbench-panes.js.map +1 -1
  215. package/dist/dashboard/web/agent-workbench-session-list.d.ts +11 -7
  216. package/dist/dashboard/web/agent-workbench-session-list.d.ts.map +1 -1
  217. package/dist/dashboard/web/agent-workbench-session-list.js +24 -31
  218. package/dist/dashboard/web/agent-workbench-session-list.js.map +1 -1
  219. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts +198 -0
  220. package/dist/dashboard/web/agent-workbench-terminal-control.d.ts.map +1 -0
  221. package/dist/dashboard/web/agent-workbench-terminal-control.js +292 -0
  222. package/dist/dashboard/web/agent-workbench-terminal-control.js.map +1 -0
  223. package/dist/dashboard/web/agent-workbench-view.d.ts +3 -2
  224. package/dist/dashboard/web/agent-workbench-view.d.ts.map +1 -1
  225. package/dist/dashboard/web/agent-workbench-view.js +69 -50
  226. package/dist/dashboard/web/agent-workbench-view.js.map +1 -1
  227. package/dist/dashboard/web/app.d.ts.map +1 -1
  228. package/dist/dashboard/web/app.js +58 -7
  229. package/dist/dashboard/web/app.js.map +1 -1
  230. package/dist/dashboard/web/bot-defaults-page.d.ts +24 -0
  231. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  232. package/dist/dashboard/web/bot-defaults-page.js +191 -16
  233. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  234. package/dist/dashboard/web/bot-defaults.d.ts +17 -0
  235. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  236. package/dist/dashboard/web/bot-defaults.js +51 -2
  237. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  238. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  239. package/dist/dashboard/web/bot-onboarding.js +32 -8
  240. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  241. package/dist/dashboard/web/confirm-modal.d.ts +35 -0
  242. package/dist/dashboard/web/confirm-modal.d.ts.map +1 -0
  243. package/dist/dashboard/web/confirm-modal.js +111 -0
  244. package/dist/dashboard/web/confirm-modal.js.map +1 -0
  245. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  246. package/dist/dashboard/web/connectors-page.js +2 -1
  247. package/dist/dashboard/web/connectors-page.js.map +1 -1
  248. package/dist/dashboard/web/customization-page.d.ts +3 -0
  249. package/dist/dashboard/web/customization-page.d.ts.map +1 -0
  250. package/dist/dashboard/web/customization-page.js +315 -0
  251. package/dist/dashboard/web/customization-page.js.map +1 -0
  252. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  253. package/dist/dashboard/web/dashboard-routes.js +1 -0
  254. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  255. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  256. package/dist/dashboard/web/groups-page.js +11 -9
  257. package/dist/dashboard/web/groups-page.js.map +1 -1
  258. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  259. package/dist/dashboard/web/i18n.js +226 -42
  260. package/dist/dashboard/web/i18n.js.map +1 -1
  261. package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
  262. package/dist/dashboard/web/monitor-room.js +3 -2
  263. package/dist/dashboard/web/monitor-room.js.map +1 -1
  264. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  265. package/dist/dashboard/web/overview-page.js +98 -63
  266. package/dist/dashboard/web/overview-page.js.map +1 -1
  267. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  268. package/dist/dashboard/web/roles-page.js +4 -3
  269. package/dist/dashboard/web/roles-page.js.map +1 -1
  270. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  271. package/dist/dashboard/web/schedules-page.js +165 -18
  272. package/dist/dashboard/web/schedules-page.js.map +1 -1
  273. package/dist/dashboard/web/sessions-kanban.d.ts +2 -0
  274. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
  275. package/dist/dashboard/web/sessions-kanban.js +142 -24
  276. package/dist/dashboard/web/sessions-kanban.js.map +1 -1
  277. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  278. package/dist/dashboard/web/sessions-page.js +177 -94
  279. package/dist/dashboard/web/sessions-page.js.map +1 -1
  280. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  281. package/dist/dashboard/web/sessions.js +5 -4
  282. package/dist/dashboard/web/sessions.js.map +1 -1
  283. package/dist/dashboard/web/settings-page.d.ts +0 -8
  284. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  285. package/dist/dashboard/web/settings-page.js +185 -65
  286. package/dist/dashboard/web/settings-page.js.map +1 -1
  287. package/dist/dashboard/web/skills/skill-library-tab.d.ts +2 -0
  288. package/dist/dashboard/web/skills/skill-library-tab.d.ts.map +1 -1
  289. package/dist/dashboard/web/skills/skill-library-tab.js +15 -1
  290. package/dist/dashboard/web/skills/skill-library-tab.js.map +1 -1
  291. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  292. package/dist/dashboard/web/skills-page.js +5 -4
  293. package/dist/dashboard/web/skills-page.js.map +1 -1
  294. package/dist/dashboard/web/store.d.ts +5 -0
  295. package/dist/dashboard/web/store.d.ts.map +1 -1
  296. package/dist/dashboard/web/store.js +4 -0
  297. package/dist/dashboard/web/store.js.map +1 -1
  298. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  299. package/dist/dashboard/web/team-federation-page.js +3 -2
  300. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  301. package/dist/dashboard/web/toast.d.ts +15 -0
  302. package/dist/dashboard/web/toast.d.ts.map +1 -0
  303. package/dist/dashboard/web/toast.js +70 -0
  304. package/dist/dashboard/web/toast.js.map +1 -0
  305. package/dist/dashboard/web/v3-components.d.ts.map +1 -1
  306. package/dist/dashboard/web/v3-components.js +2 -1
  307. package/dist/dashboard/web/v3-components.js.map +1 -1
  308. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts +10 -12
  309. package/dist/dashboard/web/vc-consumer-profiles-section.d.ts.map +1 -1
  310. package/dist/dashboard/web/vc-consumer-profiles-section.js +219 -200
  311. package/dist/dashboard/web/vc-consumer-profiles-section.js.map +1 -1
  312. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
  313. package/dist/dashboard/web/whiteboards-page.js +2 -1
  314. package/dist/dashboard/web/whiteboards-page.js.map +1 -1
  315. package/dist/dashboard-web/app.js +1 -1
  316. package/dist/dashboard-web/chunks/agent-workbench-dock-page-LZSUOCEQ.js +1 -0
  317. package/dist/dashboard-web/chunks/agent-workbench-page-YONDRJU7.js +1 -0
  318. package/dist/dashboard-web/chunks/bot-defaults-page-X7LV6373.js +1 -0
  319. package/dist/dashboard-web/chunks/chunk-2PVRDXEL.js +1 -0
  320. package/dist/dashboard-web/chunks/chunk-53JUKBU2.js +10 -0
  321. package/dist/dashboard-web/chunks/{chunk-5CJMHTT4.js → chunk-5QX4OYOR.js} +1 -1
  322. package/dist/dashboard-web/chunks/{chunk-NACP7BMR.js → chunk-5VRVJFQR.js} +1 -1
  323. package/dist/dashboard-web/chunks/{chunk-FDZ7WOBW.js → chunk-6ROOZWYV.js} +1 -1
  324. package/dist/dashboard-web/chunks/{chunk-F6KNPF4R.js → chunk-CK22743G.js} +15 -19
  325. package/dist/dashboard-web/chunks/chunk-CVNIMUPY.js +25 -0
  326. package/dist/dashboard-web/chunks/chunk-GJC2TMWS.js +2 -0
  327. package/dist/dashboard-web/chunks/chunk-OT6L6G7R.js +1 -0
  328. package/dist/dashboard-web/chunks/chunk-QITAMHW6.js +1 -0
  329. package/dist/dashboard-web/chunks/chunk-RHVFZC5Z.js +1 -0
  330. package/dist/dashboard-web/chunks/chunk-SOBMN2VY.js +1 -0
  331. package/dist/dashboard-web/chunks/chunk-SZOB6VK2.js +1 -0
  332. package/dist/dashboard-web/chunks/chunk-XVIXNQGC.js +44 -0
  333. package/dist/dashboard-web/chunks/chunk-YUJF34ME.js +1 -0
  334. package/dist/dashboard-web/chunks/connectors-page-LB2HEJ7F.js +1 -0
  335. package/dist/dashboard-web/chunks/customization-page-4GXBYLG6.js +105 -0
  336. package/dist/dashboard-web/chunks/feedback-page-IZQ7VLDG.js +1 -0
  337. package/dist/dashboard-web/chunks/groups-page-4WCP5OEV.js +8 -0
  338. package/dist/dashboard-web/chunks/{insights-page-HJFDYR5N.js → insights-page-TXQHIPQL.js} +1 -1
  339. package/dist/dashboard-web/chunks/{monitor-room-V7PCP47V.js → monitor-room-VBRVWXCM.js} +8 -8
  340. package/dist/dashboard-web/chunks/monitoring-page-QDDZWERE.js +2 -0
  341. package/dist/dashboard-web/chunks/office-page-PENFNYPG.js +1 -0
  342. package/dist/dashboard-web/chunks/overview-page-BZQDP6MP.js +1 -0
  343. package/dist/dashboard-web/chunks/plugin-page-JX4GSWZS.js +1 -0
  344. package/dist/dashboard-web/chunks/roles-page-VPNINZNK.js +1 -0
  345. package/dist/dashboard-web/chunks/schedules-page-BITO6HEA.js +1 -0
  346. package/dist/dashboard-web/chunks/sessions-page-ZKCOVTD2.js +8 -0
  347. package/dist/dashboard-web/chunks/settings-page-UT6JDTS6.js +2 -0
  348. package/dist/dashboard-web/chunks/skills-page-GULXVAJY.js +4 -0
  349. package/dist/dashboard-web/chunks/team-federation-page-CUUJWGEG.js +1 -0
  350. package/dist/dashboard-web/chunks/v3-page-5WVJ5AUR.js +94 -0
  351. package/dist/dashboard-web/chunks/whiteboards-page-FZ2CME4H.js +1 -0
  352. package/dist/dashboard-web/design-tokens.css +236 -0
  353. package/dist/dashboard-web/index.html +1 -0
  354. package/dist/dashboard-web/style.css +1930 -114
  355. package/dist/dashboard.js +360 -279
  356. package/dist/dashboard.js.map +1 -1
  357. package/dist/desktop/renderer/app.js +6 -0
  358. package/dist/desktop/renderer/app.js.map +1 -1
  359. package/dist/dsh-runner.js +139 -17
  360. package/dist/dsh-runner.js.map +1 -1
  361. package/dist/global-config.d.ts +33 -2
  362. package/dist/global-config.d.ts.map +1 -1
  363. package/dist/global-config.js +62 -0
  364. package/dist/global-config.js.map +1 -1
  365. package/dist/i18n/en.d.ts.map +1 -1
  366. package/dist/i18n/en.js +12 -4
  367. package/dist/i18n/en.js.map +1 -1
  368. package/dist/i18n/index.d.ts +27 -3
  369. package/dist/i18n/index.d.ts.map +1 -1
  370. package/dist/i18n/index.js +33 -4
  371. package/dist/i18n/index.js.map +1 -1
  372. package/dist/i18n/zh.d.ts.map +1 -1
  373. package/dist/i18n/zh.js +12 -4
  374. package/dist/i18n/zh.js.map +1 -1
  375. package/dist/im/lark/card-builder.d.ts +2 -0
  376. package/dist/im/lark/card-builder.d.ts.map +1 -1
  377. package/dist/im/lark/card-builder.js +7 -4
  378. package/dist/im/lark/card-builder.js.map +1 -1
  379. package/dist/im/lark/card-handler.d.ts.map +1 -1
  380. package/dist/im/lark/card-handler.js +70 -16
  381. package/dist/im/lark/card-handler.js.map +1 -1
  382. package/dist/im/lark/event-dispatcher.d.ts +38 -3
  383. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  384. package/dist/im/lark/event-dispatcher.js +163 -13
  385. package/dist/im/lark/event-dispatcher.js.map +1 -1
  386. package/dist/im/lark/v3-blocked-card-handler.d.ts.map +1 -1
  387. package/dist/im/lark/v3-blocked-card-handler.js +2 -1
  388. package/dist/im/lark/v3-blocked-card-handler.js.map +1 -1
  389. package/dist/im/lark/v3-blocked-card.d.ts +1 -1
  390. package/dist/im/lark/v3-blocked-card.d.ts.map +1 -1
  391. package/dist/im/lark/v3-blocked-card.js +14 -0
  392. package/dist/im/lark/v3-blocked-card.js.map +1 -1
  393. package/dist/index-core-only.js +2 -0
  394. package/dist/index-core-only.js.map +1 -1
  395. package/dist/index-daemon.js +3 -0
  396. package/dist/index-daemon.js.map +1 -1
  397. package/dist/platform/binding.d.ts +11 -1
  398. package/dist/platform/binding.d.ts.map +1 -1
  399. package/dist/platform/binding.js +3 -28
  400. package/dist/platform/binding.js.map +1 -1
  401. package/dist/services/codex-app-runner-protocol.d.ts +5 -0
  402. package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
  403. package/dist/services/codex-app-runner-protocol.js +5 -0
  404. package/dist/services/codex-app-runner-protocol.js.map +1 -1
  405. package/dist/services/customization-store.d.ts +93 -0
  406. package/dist/services/customization-store.d.ts.map +1 -0
  407. package/dist/services/customization-store.js +542 -0
  408. package/dist/services/customization-store.js.map +1 -0
  409. package/dist/services/file-bridge-path.d.ts.map +1 -1
  410. package/dist/services/file-bridge-path.js +3 -0
  411. package/dist/services/file-bridge-path.js.map +1 -1
  412. package/dist/services/grok-transcript.d.ts.map +1 -1
  413. package/dist/services/grok-transcript.js +44 -9
  414. package/dist/services/grok-transcript.js.map +1 -1
  415. package/dist/services/local-cli-opener.d.ts.map +1 -1
  416. package/dist/services/local-cli-opener.js +12 -3
  417. package/dist/services/local-cli-opener.js.map +1 -1
  418. package/dist/services/model-catalog.d.ts +54 -0
  419. package/dist/services/model-catalog.d.ts.map +1 -0
  420. package/dist/services/model-catalog.js +147 -0
  421. package/dist/services/model-catalog.js.map +1 -0
  422. package/dist/services/omp-transcript.d.ts +27 -0
  423. package/dist/services/omp-transcript.d.ts.map +1 -0
  424. package/dist/services/omp-transcript.js +214 -0
  425. package/dist/services/omp-transcript.js.map +1 -0
  426. package/dist/services/platform-team-store.d.ts +16 -8
  427. package/dist/services/platform-team-store.d.ts.map +1 -1
  428. package/dist/services/platform-team-store.js +17 -10
  429. package/dist/services/platform-team-store.js.map +1 -1
  430. package/dist/services/project-scanner.d.ts.map +1 -1
  431. package/dist/services/project-scanner.js +13 -8
  432. package/dist/services/project-scanner.js.map +1 -1
  433. package/dist/services/resumable-session-discovery.d.ts.map +1 -1
  434. package/dist/services/resumable-session-discovery.js +117 -35
  435. package/dist/services/resumable-session-discovery.js.map +1 -1
  436. package/dist/services/structured-bridge-clis.d.ts +5 -3
  437. package/dist/services/structured-bridge-clis.d.ts.map +1 -1
  438. package/dist/services/structured-bridge-clis.js +5 -1
  439. package/dist/services/structured-bridge-clis.js.map +1 -1
  440. package/dist/services/vc-meeting-action-gate.d.ts.map +1 -1
  441. package/dist/services/vc-meeting-action-gate.js +15 -4
  442. package/dist/services/vc-meeting-action-gate.js.map +1 -1
  443. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts +46 -66
  444. package/dist/services/vc-meeting-consumer-profile-bootstrap.d.ts.map +1 -1
  445. package/dist/services/vc-meeting-consumer-profile-bootstrap.js +33 -292
  446. package/dist/services/vc-meeting-consumer-profile-bootstrap.js.map +1 -1
  447. package/dist/services/vc-meeting-consumer-profile-templates.js +3 -3
  448. package/dist/services/vc-meeting-consumer-profile-templates.js.map +1 -1
  449. package/dist/services/vc-meeting-delivery-store.d.ts +18 -0
  450. package/dist/services/vc-meeting-delivery-store.d.ts.map +1 -1
  451. package/dist/services/vc-meeting-delivery-store.js +55 -0
  452. package/dist/services/vc-meeting-delivery-store.js.map +1 -1
  453. package/dist/services/vc-meeting-send-policy.d.ts +21 -1
  454. package/dist/services/vc-meeting-send-policy.d.ts.map +1 -1
  455. package/dist/services/vc-meeting-send-policy.js +30 -2
  456. package/dist/services/vc-meeting-send-policy.js.map +1 -1
  457. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts +78 -0
  458. package/dist/services/vc-meeting-shared-consumer-catalog-store.d.ts.map +1 -0
  459. package/dist/services/vc-meeting-shared-consumer-catalog-store.js +227 -0
  460. package/dist/services/vc-meeting-shared-consumer-catalog-store.js.map +1 -0
  461. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts +53 -0
  462. package/dist/services/vc-meeting-shared-consumer-catalog.d.ts.map +1 -0
  463. package/dist/services/vc-meeting-shared-consumer-catalog.js +161 -0
  464. package/dist/services/vc-meeting-shared-consumer-catalog.js.map +1 -0
  465. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  466. package/dist/setup/bot-config-editor.js +1 -0
  467. package/dist/setup/bot-config-editor.js.map +1 -1
  468. package/dist/setup/open-platform-automation.d.ts +20 -0
  469. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  470. package/dist/setup/open-platform-automation.js +27 -0
  471. package/dist/setup/open-platform-automation.js.map +1 -1
  472. package/dist/skills/customization-bundle.d.ts +83 -0
  473. package/dist/skills/customization-bundle.d.ts.map +1 -0
  474. package/dist/skills/customization-bundle.js +214 -0
  475. package/dist/skills/customization-bundle.js.map +1 -0
  476. package/dist/skills/definitions.d.ts.map +1 -1
  477. package/dist/skills/definitions.js +10 -3
  478. package/dist/skills/definitions.js.map +1 -1
  479. package/dist/skills/effective-builtins.d.ts +38 -0
  480. package/dist/skills/effective-builtins.d.ts.map +1 -0
  481. package/dist/skills/effective-builtins.js +96 -0
  482. package/dist/skills/effective-builtins.js.map +1 -0
  483. package/dist/skills/injection-mode.d.ts +4 -1
  484. package/dist/skills/injection-mode.d.ts.map +1 -1
  485. package/dist/skills/injection-mode.js +11 -3
  486. package/dist/skills/injection-mode.js.map +1 -1
  487. package/dist/skills/installer.d.ts.map +1 -1
  488. package/dist/skills/installer.js +24 -1
  489. package/dist/skills/installer.js.map +1 -1
  490. package/dist/skills/prompt-fragments.d.ts +58 -0
  491. package/dist/skills/prompt-fragments.d.ts.map +1 -0
  492. package/dist/skills/prompt-fragments.js +88 -0
  493. package/dist/skills/prompt-fragments.js.map +1 -0
  494. package/dist/types.d.ts +51 -0
  495. package/dist/types.d.ts.map +1 -1
  496. package/dist/utils/cli-usage-limit.d.ts +50 -0
  497. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  498. package/dist/utils/cli-usage-limit.js +43 -0
  499. package/dist/utils/cli-usage-limit.js.map +1 -1
  500. package/dist/utils/idle-detector.d.ts +12 -0
  501. package/dist/utils/idle-detector.d.ts.map +1 -1
  502. package/dist/utils/idle-detector.js +88 -2
  503. package/dist/utils/idle-detector.js.map +1 -1
  504. package/dist/utils/stuck-detector.d.ts +5 -0
  505. package/dist/utils/stuck-detector.d.ts.map +1 -1
  506. package/dist/utils/stuck-detector.js +41 -0
  507. package/dist/utils/stuck-detector.js.map +1 -1
  508. package/dist/utils/usage-limit-tracker.d.ts +46 -0
  509. package/dist/utils/usage-limit-tracker.d.ts.map +1 -0
  510. package/dist/utils/usage-limit-tracker.js +91 -0
  511. package/dist/utils/usage-limit-tracker.js.map +1 -0
  512. package/dist/utils/user-token.js +1 -1
  513. package/dist/utils/user-token.js.map +1 -1
  514. package/dist/worker.js +542 -106
  515. package/dist/worker.js.map +1 -1
  516. package/dist/workflows/definition.d.ts +14 -14
  517. package/dist/workflows/events/payloads.d.ts +12 -12
  518. package/dist/workflows/events/schema.d.ts +164 -164
  519. package/dist/workflows/migration/v2-to-v3.js +1 -1
  520. package/dist/workflows/migration/v2-to-v3.js.map +1 -1
  521. package/dist/workflows/v3/architect.d.ts.map +1 -1
  522. package/dist/workflows/v3/architect.js +11 -5
  523. package/dist/workflows/v3/architect.js.map +1 -1
  524. package/dist/workflows/v3/artifact-contract-declarations.d.ts +22 -0
  525. package/dist/workflows/v3/artifact-contract-declarations.d.ts.map +1 -0
  526. package/dist/workflows/v3/artifact-contract-declarations.js +95 -0
  527. package/dist/workflows/v3/artifact-contract-declarations.js.map +1 -0
  528. package/dist/workflows/v3/contract.d.ts +1 -0
  529. package/dist/workflows/v3/contract.d.ts.map +1 -1
  530. package/dist/workflows/v3/contract.js.map +1 -1
  531. package/dist/workflows/v3/daemon-run.d.ts +2 -2
  532. package/dist/workflows/v3/daemon-run.d.ts.map +1 -1
  533. package/dist/workflows/v3/daemon-run.js +4 -0
  534. package/dist/workflows/v3/daemon-run.js.map +1 -1
  535. package/dist/workflows/v3/dag.d.ts +7 -0
  536. package/dist/workflows/v3/dag.d.ts.map +1 -1
  537. package/dist/workflows/v3/dag.js +69 -8
  538. package/dist/workflows/v3/dag.js.map +1 -1
  539. package/dist/workflows/v3/ephemeral-pool.js +1 -1
  540. package/dist/workflows/v3/ephemeral-pool.js.map +1 -1
  541. package/dist/workflows/v3/event-contract.d.ts +3 -1
  542. package/dist/workflows/v3/event-contract.d.ts.map +1 -1
  543. package/dist/workflows/v3/host.d.ts.map +1 -1
  544. package/dist/workflows/v3/host.js +5 -1
  545. package/dist/workflows/v3/host.js.map +1 -1
  546. package/dist/workflows/v3/journal.d.ts +1 -1
  547. package/dist/workflows/v3/journal.d.ts.map +1 -1
  548. package/dist/workflows/v3/journal.js.map +1 -1
  549. package/dist/workflows/v3/library-materialize.d.ts.map +1 -1
  550. package/dist/workflows/v3/library-materialize.js +9 -1
  551. package/dist/workflows/v3/library-materialize.js.map +1 -1
  552. package/dist/workflows/v3/library-schema.d.ts +3 -1
  553. package/dist/workflows/v3/library-schema.d.ts.map +1 -1
  554. package/dist/workflows/v3/library-schema.js +20 -4
  555. package/dist/workflows/v3/library-schema.js.map +1 -1
  556. package/dist/workflows/v3/library-store.d.ts.map +1 -1
  557. package/dist/workflows/v3/library-store.js +15 -3
  558. package/dist/workflows/v3/library-store.js.map +1 -1
  559. package/dist/workflows/v3/run-envelope.d.ts +16 -16
  560. package/dist/workflows/v3/shared-node-runtime.d.ts +2 -1
  561. package/dist/workflows/v3/shared-node-runtime.d.ts.map +1 -1
  562. package/dist/workflows/v3/shared-node-runtime.js +48 -7
  563. package/dist/workflows/v3/shared-node-runtime.js.map +1 -1
  564. package/package.json +2 -1
  565. package/dist/dashboard-web/chunks/agent-workbench-dock-page-IKAC5CFL.js +0 -1
  566. package/dist/dashboard-web/chunks/agent-workbench-page-M53UN5EB.js +0 -1
  567. package/dist/dashboard-web/chunks/bot-defaults-page-YKE5SDZF.js +0 -10
  568. package/dist/dashboard-web/chunks/chunk-22WIADUT.js +0 -2
  569. package/dist/dashboard-web/chunks/chunk-24ONYQMR.js +0 -1
  570. package/dist/dashboard-web/chunks/chunk-2W6SCN3M.js +0 -1
  571. package/dist/dashboard-web/chunks/chunk-4XNJX6B3.js +0 -1
  572. package/dist/dashboard-web/chunks/chunk-5THX4DI2.js +0 -66
  573. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
  574. package/dist/dashboard-web/chunks/chunk-AGCPF6MT.js +0 -1
  575. package/dist/dashboard-web/chunks/chunk-FXW3V4JR.js +0 -1
  576. package/dist/dashboard-web/chunks/chunk-SYC6D5XU.js +0 -1
  577. package/dist/dashboard-web/chunks/connectors-page-VOISXXII.js +0 -1
  578. package/dist/dashboard-web/chunks/feedback-page-RN6HRZMS.js +0 -1
  579. package/dist/dashboard-web/chunks/groups-page-UQT6DCB7.js +0 -8
  580. package/dist/dashboard-web/chunks/monitoring-page-NKEKVQAP.js +0 -2
  581. package/dist/dashboard-web/chunks/office-page-GVJF73XW.js +0 -1
  582. package/dist/dashboard-web/chunks/overview-page-KHLV6VTI.js +0 -1
  583. package/dist/dashboard-web/chunks/plugin-page-J3OF5IY4.js +0 -1
  584. package/dist/dashboard-web/chunks/roles-page-WQIRSOLZ.js +0 -1
  585. package/dist/dashboard-web/chunks/schedules-page-J2DEY2X6.js +0 -1
  586. package/dist/dashboard-web/chunks/sessions-page-7BQFEA5P.js +0 -8
  587. package/dist/dashboard-web/chunks/settings-page-BJEIOFJH.js +0 -2
  588. package/dist/dashboard-web/chunks/skills-page-HGIFR3IU.js +0 -4
  589. package/dist/dashboard-web/chunks/team-federation-page-MR5ZPL4B.js +0 -1
  590. package/dist/dashboard-web/chunks/v3-page-GBR5JWPE.js +0 -94
  591. package/dist/dashboard-web/chunks/whiteboards-page-QAZM4BN3.js +0 -1
  592. package/dist/services/vc-meeting-consumer-profile-store.d.ts +0 -39
  593. package/dist/services/vc-meeting-consumer-profile-store.d.ts.map +0 -1
  594. package/dist/services/vc-meeting-consumer-profile-store.js +0 -191
  595. 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;
@@ -4404,7 +4442,7 @@ export function teardownAuthoritativePersistentBackingBeforeClose(target) {
4404
4442
  export function buildStreamingCardJson(ds, status) {
4405
4443
  const botCfg = getBot(ds.larkAppId).config;
4406
4444
  const effectiveCliId = sessionCliId(ds, botCfg);
4407
- 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, !!ds.adoptedFrom, false, localeForBot(ds.larkAppId), cardUsageLimit(ds), writableTerminalLinkFor(ds), isLocalCliOpenReady(ds, { cliId: effectiveCliId }), getDaemonStreamingCardUsageSnapshot(ds, effectiveCliId), sessionRuntimeDisplayName(ds, botCfg), codexServiceTierBadge(effectiveCliId, ds.codexServiceTier));
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));
4408
4446
  }
4409
4447
  /**
4410
4448
  * Close from a BACKGROUND path — one with no user surface to report to
@@ -4449,9 +4487,8 @@ export async function closeSession(sessionId, opts) {
4449
4487
  const stored = sessionStore.getOwnedSession(sessionId);
4450
4488
  const closeFrozenBackendType = ds?.initConfig?.backendType
4451
4489
  ?? ds?.session.backendType ?? stored?.backendType;
4452
- const isOwnedClose = !ds?.initConfig?.adoptMode
4453
- && !ds?.adoptedFrom
4454
- && !stored?.adoptedFrom;
4490
+ const isOwnedClose = !(ds && isSharedAdoptSession(ds))
4491
+ && !(stored && isSharedAdoptPersistedSession(stored));
4455
4492
  const isOwnedRiffClose = isOwnedClose && closeFrozenBackendType === 'riff';
4456
4493
  const isOwnedMojoClose = isOwnedClose && closeFrozenBackendType === 'mojo';
4457
4494
  const closeJournal = ds?.session.mojoCloseJournal ?? stored?.mojoCloseJournal;
@@ -4851,10 +4888,11 @@ function removeInactiveRegistration(map, key, ds) {
4851
4888
  }
4852
4889
  function persistedActiveSessionKey(session, fallbackLarkAppId) {
4853
4890
  const larkAppId = session.larkAppId ?? fallbackLarkAppId;
4854
- const anchor = session.vcMeetingReceiver
4855
- ? `vc-receiver:${session.sessionId}`
4856
- : storedSessionAnchorId(session);
4857
- 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);
4858
4896
  }
4859
4897
  /**
4860
4898
  * Return an active persisted row that deliberately reserves this routing key
@@ -5080,7 +5118,14 @@ function failOrdinaryImDelivery(record, reason) {
5080
5118
  requireOrdinaryTurnRecoveryAttention(record.ds.session, record.turnId, 'recovery_delivery_failed');
5081
5119
  return;
5082
5120
  }
5083
- 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))
5084
5129
  return;
5085
5130
  const loc = botLocale(getBot(record.ds.larkAppId).config);
5086
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: `
@@ -5170,8 +5215,13 @@ function shouldTrackOrdinaryImDelivery(ds, message) {
5170
5215
  && (message.turnId.startsWith('om_') || message.turnId.startsWith('bmx-recovery-'))
5171
5216
  && message.dispatchAttempt === undefined
5172
5217
  && (message.type !== 'init' || (!!message.prompt && !message.adoptMode))
5173
- && !ds.adoptedFrom
5174
- && !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)
5175
5225
  && Number.isSafeInteger(ds.workerGeneration)
5176
5226
  && (ds.workerGeneration ?? 0) > 0
5177
5227
  && ds.session.workerGeneration === ds.workerGeneration;
@@ -5490,8 +5540,6 @@ targetScope, opts) {
5490
5540
  runtimeWorkingDir: ds.workingDir,
5491
5541
  runtimeAdoptedFrom: ds.adoptedFrom,
5492
5542
  });
5493
- if (ds.session.vcMeetingReceiver)
5494
- return { ok: false, error: 'vc_receiver_not_relayable' };
5495
5543
  if (hasProtectedSessionMutationOwnership(ds)) {
5496
5544
  return { ok: false, error: 'codex_app_dispatch_pending' };
5497
5545
  }
@@ -5565,7 +5613,7 @@ targetScope, opts) {
5565
5613
  // Adopt sessions wrap a CLI process that botmux didn't spawn — the user
5566
5614
  // owns it inside their own tmux pane, so moving routing here would be
5567
5615
  // surprising and we don't control the tmux session's lifecycle. Refuse.
5568
- if (ds.session.adoptedFrom)
5616
+ if (isSharedAdoptSession(ds))
5569
5617
  return { ok: false, error: 'adopt_not_relayable' };
5570
5618
  // Busy worker: refuse immediately rather than waiting. An idle-wait loop
5571
5619
  // (previously 60s) created an asymmetry with the peer-dispatch HTTP
@@ -5646,7 +5694,6 @@ targetScope, opts) {
5646
5694
  const persistedTargetConflicts = sessionStore.listSessions().filter(item => item.sessionId !== ds.session.sessionId
5647
5695
  && item.status === 'active'
5648
5696
  && (!item.larkAppId || item.larkAppId === ds.larkAppId)
5649
- && !item.vcMeetingReceiver
5650
5697
  && (item.scope === 'chat' ? item.chatId : item.rootMessageId) === targetAnchor
5651
5698
  && !runtimeIds.has(item.sessionId));
5652
5699
  if (persistedTargetConflicts.some(item => hasProtectedSessionMutationOwnership(item)
@@ -5836,6 +5883,12 @@ export function isForkCapableSession(ds) {
5836
5883
  const cliId = sessionCliId(ds, botCfg);
5837
5884
  if (!FORK_CAPABLE_CLI_IDS.has(cliId))
5838
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;
5839
5892
  // Codex terminal mode is forkable; Codex under Hybrid RPC input is not (the
5840
5893
  // thread is an app-server live session, no local rollout to `codex fork`).
5841
5894
  //
@@ -5883,13 +5936,11 @@ export async function forkSession(sessionId, targetChatId, targetRootMessageId,
5883
5936
  return { ok: false, error: 'fork_unsupported_backend' };
5884
5937
  // ── Front guards (mirror transferSession; a fork needs a clean, complete
5885
5938
  // source node exactly as a relay does) ──
5886
- if (ds.session.vcMeetingReceiver)
5887
- return { ok: false, error: 'vc_receiver_not_forkable' };
5888
5939
  if (ds.pendingRepo)
5889
5940
  return { ok: false, error: 'not_started_yet' };
5890
5941
  if (!isRelayableRealSession(ds))
5891
5942
  return { ok: false, error: 'not_started_yet' };
5892
- if (ds.session.adoptedFrom)
5943
+ if (isSharedAdoptSession(ds))
5893
5944
  return { ok: false, error: 'adopt_not_forkable' };
5894
5945
  if (isSessionLifecycleInFlight(ds))
5895
5946
  return { ok: false, error: 'worker_busy' };
@@ -6747,7 +6798,18 @@ export function mojoLivePatchForSession(ds) {
6747
6798
  /** Sessions with a quarantine notice send in flight, so it cannot double-post. */
6748
6799
  const mojoQuarantineNoticeInFlight = new Set();
6749
6800
  /**
6750
- * 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.
6751
6813
  *
6752
6814
  * Previously setupWorkerHandlers held this as a closure and the quarantine notice
6753
6815
  * COPIED three of its four checks — dropping `ordinaryManagedSuppression`, so a
@@ -7339,6 +7401,32 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7339
7401
  const cb = requireCallbacks();
7340
7402
  const bot = getBot(ds.larkAppId);
7341
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
+ }
7342
7430
  // A bare /repo placeholder (and a non-Codex empty group-join setup) owns no
7343
7431
  // model turn. Starting its CLI with an empty prompt must not mint a queued
7344
7432
  // activation token: the worker has nothing to submit and could never ACK it.
@@ -7490,7 +7578,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7490
7578
  // worker left parked but couldn't clean (hard-killed while parked, daemon
7491
7579
  // still alive → next message reforks here). The fresh CLI spawns under the
7492
7580
  // real bmx-<sid>; without this, bmx-diag-<sid> + its .ansi file would leak.
7493
- if (!ds.initConfig?.adoptMode && !ds.adoptedFrom)
7581
+ if (!isSharedAdoptSession(ds))
7494
7582
  reclaimParkedCrashDiagnostic(ds);
7495
7583
  agentCfg = sessionAgentConfig(ds, botCfg);
7496
7584
  if (!initTurnId && prompt.length > 0 && agentCfg.cliId === 'codex-app') {
@@ -7499,7 +7587,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7499
7587
  ensureCliEnv(agentCfg.cliId, agentCfg.cliPathOverride);
7500
7588
  let nativeSessionTitle;
7501
7589
  let nativeSessionTitlePrompt;
7502
- if (agentCfg.cliId === 'codex' && !ds.adoptedFrom) {
7590
+ if (agentCfg.cliId === 'codex' && !isSharedAdoptSession(ds)) {
7503
7591
  const isFreshNativeSession = !resume && !ds.session.cliSessionId;
7504
7592
  const titlePrompt = extractBotmuxLarkNativeSessionTitlePrompt(promptPayload.codexAppInput?.text ?? prompt, bot.botName ? [{ name: bot.botName }] : undefined);
7505
7593
  if (isFreshNativeSession && !ds.session.nativeSessionTitleUserDefined) {
@@ -7735,17 +7823,28 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7735
7823
  reason: 'worker_fork_error',
7736
7824
  message: reason,
7737
7825
  });
7738
- // A dedicated VC receiver and a silent schedule have no auxiliary Lark
7739
- // output channel. Keep lifecycle/dashboard state above, but never leak a
7740
- // fork diagnostic into the chat.
7741
- 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)) {
7742
7838
  logger.info(`[${t}] Managed/silent fork failure kept out of auxiliary Lark UI `
7743
7839
  + `turn=${initAttributionTurnId?.slice(0, 12) ?? '-'} attempt=${initDispatchAttempt}: ${reason}`);
7744
7840
  return;
7745
7841
  }
7746
7842
  const cliName = sessionCliDisplayName(ds, botCfg);
7747
7843
  const message = tr('worker.start_failed', { cliName, reason }, botLocale(botCfg));
7748
- 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
7749
7848
  ? { sourceSessionId: ds.session.sessionId }
7750
7849
  : undefined).catch(replyErr => logger.error(`[${t}] Failed to deliver worker fork error to Lark: ${replyErr}`));
7751
7850
  });
@@ -7788,8 +7887,16 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
7788
7887
  // dsh runner turn timeout: read live from bot config so tuning bots.json
7789
7888
  // takes effect on the next worker fork without recreating the session.
7790
7889
  turnTimeoutMs: botCfg.turnTimeoutMs,
7890
+ // dsh runtime variant (official runner vs dsh-tui PTY TUI).
7891
+ dshRuntime: botCfg.dshRuntime,
7791
7892
  disableCliBypass: botCfg.disableCliBypass === true,
7792
- 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 } : {}),
7793
7900
  // Startup commands run on every fresh spawn (incl. resume) so session-only
7794
7901
  // settings like `/effort ultracode` are re-established. Adopt sessions are
7795
7902
  // observed, not driven — forkAdoptWorker intentionally omits this.
@@ -8153,6 +8260,24 @@ function invalidateTuiPrompt(ds, reason, outcome = 'failed') {
8153
8260
  clearTuiPromptAuthority(ds);
8154
8261
  publishAttentionPatch(ds);
8155
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
+ }
8156
8281
  function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureNotified: false }, reservedWorkerGeneration) {
8157
8282
  const cb = requireCallbacks();
8158
8283
  const t = tag(ds);
@@ -8218,13 +8343,35 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8218
8343
  };
8219
8344
  /** Auxiliary worker UI is never an authorized output channel for a dedicated
8220
8345
  * VC receiver. Dashboard/audit state is still updated before these guards. */
8221
- // Delegates to the shared policy (auxUiSuppressedFor) so this and the mojo
8222
- // quarantine notice cannot drift apart. Suppressing here is the authoritative
8223
- // fix, NOT a fake "success" message id from sessionReply (a synthetic id would
8224
- // get stored as streamCardId and later scheduleCardPatch updateMessage would
8225
- // still dial Feishu). Dashboard/web-terminal state is updated before these
8226
- // guards, so the terminal view is unaffected.
8227
- 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
+ };
8228
8375
  /** final_output is the sole exception: listener_thread and exact IM replies
8229
8376
  * may proceed into the durable action ledger; silent/stale attempts do not. */
8230
8377
  const managedFinalOutputSuppressed = (turnId, dispatchAttempt) => {
@@ -8232,13 +8379,16 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8232
8379
  return true;
8233
8380
  if (isTriggerFinalSuppressed(ds, turnId))
8234
8381
  return true;
8235
- 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)) {
8236
8386
  return ordinaryManagedSuppression(turnId, dispatchAttempt);
8237
8387
  }
8238
8388
  // Resolve every Lark-facing worker event against durable origin state. The
8239
8389
  // receipt freezes responseMode, so terminal→idle updates and daemon restore
8240
8390
  // cannot become loud merely because an in-memory suppression map was
8241
- // cleared/lost. Missing attribution on a dedicated receiver fails closed.
8391
+ // cleared/lost. Missing attribution on a meeting-driven turn fails closed.
8242
8392
  const decision = evaluateVcMeetingManagedSend(config.session.dataDir, {
8243
8393
  receiverSessionId: ds.session.sessionId,
8244
8394
  receiverSession: true,
@@ -8280,9 +8430,75 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8280
8430
  logger.error(`[${t}] Failed to deliver worker startup failure to Lark: ${err?.message ?? err}`);
8281
8431
  }
8282
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
+ };
8283
8499
  // Adopt mode flags — computed once, used in all buildStreamingCard calls.
8284
8500
  // Bridge mode (the v3 default for /adopt) hides the legacy takeover button.
8285
- const isAdopt = !!ds.adoptedFrom;
8501
+ const isAdopt = isSharedAdoptSession(ds);
8286
8502
  const showTakeover = false;
8287
8503
  worker.on('message', async (msg) => {
8288
8504
  // Every IPC message is scoped to the child generation that emitted it.
@@ -8436,6 +8652,14 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8436
8652
  }
8437
8653
  startupState.ready = true;
8438
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
+ }
8439
8663
  // Restore the daemon-owned display mode BEFORE any card handling: a
8440
8664
  // fresh worker always boots with displayMode='hidden', and every early
8441
8665
  // break below (managed / replyAlreadySent / streamingCardDisabled /
@@ -8606,8 +8830,10 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8606
8830
  // POST a second card; the in-flight POST becomes this turn's card.
8607
8831
  if (ds.streamCardId === CARD_POSTING_SENTINEL)
8608
8832
  break;
8833
+ const pendingNewTurn = !!ds.streamCardPending;
8609
8834
  const postingGeneration = ds.streamCardTurnGeneration ?? 0;
8610
8835
  const cardReplyTarget = captureStreamingCardReplyTarget(ds, msg.turnId);
8836
+ const statusRevisionAtPost = ds.streamCardStatusRevision ?? 0;
8611
8837
  ds.streamCardId = CARD_POSTING_SENTINEL;
8612
8838
  try {
8613
8839
  ds.streamCardNonce = randomBytes(4).toString('hex');
@@ -8615,7 +8841,9 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8615
8841
  // See PATCH-branch comment above re: lastScreenStatus preference.
8616
8842
  // For relay (kill+fork with surviving tmux/CLI), this avoids the
8617
8843
  // jarring "启动中" right after the M1 "已接力" announcement.
8618
- const initStatus = ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting');
8844
+ const initStatus = pendingNewTurn
8845
+ ? turnStartingCardStatus(ds, effectiveCliId)
8846
+ : (ds.usageLimit ? 'limited' : (ds.lastScreenStatus ?? 'starting'));
8619
8847
  const streamCardJson = buildStreamingCard(ds.session.sessionId, sessionAnchorId(ds), readOnlyUrl, initTitle,
8620
8848
  // For /relay resume, ds.lastScreenContent is the cached pane
8621
8849
  // from before the kill+fork — using it avoids a blank flash
@@ -8656,6 +8884,9 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8656
8884
  // resume where the CLI kept running), arm here — same authorized arm
8657
8885
  // point as the reuse branch, now that streamCardId is the real id.
8658
8886
  syncUsageRefreshTimer(ds);
8887
+ if (!superseded) {
8888
+ reconcilePostedStartingCard(ds, cardReplyTarget.turnId, statusRevisionAtPost);
8889
+ }
8659
8890
  if (superseded && ds.streamCardPendingTurnId) {
8660
8891
  void postTurnStartingCard(ds, cb.sessionReply, ds.streamCardPendingTurnId);
8661
8892
  }
@@ -8676,7 +8907,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8676
8907
  // Fallback: send static session card
8677
8908
  try {
8678
8909
  const localCliReadyAtBuild = isLocalCliOpenReady(ds, { cliId: effectiveCliId });
8679
- 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));
8680
8911
  const fallbackCardId = await scopedReply(cardJson, 'interactive', msg.turnId);
8681
8912
  if (!ownsLifecycleMutation()) {
8682
8913
  void deleteMessage(ds.larkAppId, fallbackCardId).catch(() => { });
@@ -8684,7 +8915,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8684
8915
  }
8685
8916
  if (!localCliReadyAtBuild && isLocalCliOpenEnabled()
8686
8917
  && isLocalCliOpenReady(ds, { cliId: effectiveCliId })) {
8687
- 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));
8688
8919
  try {
8689
8920
  await updateMessage(ds.larkAppId, fallbackCardId, readyCardJson);
8690
8921
  }
@@ -8878,7 +9109,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
8878
9109
  const prevStatus = ds.lastScreenStatus;
8879
9110
  updateUsageLimitState(ds, msg.usageLimit);
8880
9111
  ds.lastScreenContent = msg.content;
8881
- ds.lastScreenStatus = (msg.usageLimit ?? ds.usageLimit) ? 'limited' : msg.status;
9112
+ ds.lastScreenStatus = resolveUsageAwareScreenStatus(ds, msg.status, msg.usageLimit);
8882
9113
  bumpStreamCardStatusRevision(ds);
8883
9114
  // A suspend that arrived mid-turn parked itself here. Defer until this
8884
9115
  // screen_update has finished using process state — suspendWorker nulls
@@ -9073,7 +9304,7 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
9073
9304
  break;
9074
9305
  const prevStatus = ds.lastScreenStatus;
9075
9306
  updateUsageLimitState(ds, msg.usageLimit);
9076
- ds.lastScreenStatus = (msg.usageLimit ?? ds.usageLimit) ? 'limited' : msg.status;
9307
+ ds.lastScreenStatus = resolveUsageAwareScreenStatus(ds, msg.status, msg.usageLimit);
9077
9308
  bumpStreamCardStatusRevision(ds);
9078
9309
  // Same deferred-suspend checkpoint as the screen_update branch, and
9079
9310
  // deferred for the same reason (see runPendingSuspendIfSettled).
@@ -9433,9 +9664,45 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
9433
9664
  break;
9434
9665
  }
9435
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
+ }
9436
9703
  // Do NOT auto-restart in adopt mode — there's nothing to restart
9437
- if (ds.adoptedFrom) {
9438
- logger.info(`[${t}] Adopted session ended`);
9704
+ if (isSharedAdoptSession(ds)) {
9705
+ logger.info(`[${t}] Shared adopted session ended`);
9439
9706
  // Freeze the streaming card
9440
9707
  if (!suppressExitUi && ds.streamCardId && workerHasInitialized(ds)) {
9441
9708
  const readUrl = readableTerminalUrlFor(ds);
@@ -9571,7 +9838,12 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
9571
9838
  logger.error(`[${t}] Worker error: ${msg.message}`);
9572
9839
  // `error` is a fatal launch-generation signal. It normally arrives
9573
9840
  // during init, but can also follow a previously-ready worker whose CLI
9574
- // 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;
9575
9847
  await notifyStartupFailure(msg.message, msg.turnId, msg.dispatchAttempt);
9576
9848
  break;
9577
9849
  }
@@ -10403,14 +10675,19 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
10403
10675
  ds.worker = null;
10404
10676
  ds.workerReady = false;
10405
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;
10406
10686
  // A queued suspend for THIS generation is now moot — the worker it was
10407
10687
  // about is gone. Leaving it set would suspend the replacement on its
10408
10688
  // first idle (the deferred checkpoint's own "worker gone" branch cannot
10409
10689
  // help: it only runs on a screen update that will never arrive).
10410
10690
  clearPendingSuspendClaim(ds, 'worker exited');
10411
- // Dead worker generation — stop the periodic usage refresh immediately
10412
- // instead of waiting a tick for it to self-clear on !workerHasInitialized.
10413
- clearUsageRefreshTimer(ds);
10414
10691
  ds.workerToken = null;
10415
10692
  ds.workerViewToken = null;
10416
10693
  ds.managedTurnOrigin = undefined;
@@ -10438,6 +10715,13 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
10438
10715
  sessionStore.updateSession(ds.session);
10439
10716
  if (exitedPreviewTarget !== undefined)
10440
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
+ });
10441
10725
  }
10442
10726
  if (!transferRetirement) {
10443
10727
  try {
@@ -10568,10 +10852,11 @@ async function finishTurnReactions(ds) {
10568
10852
  return;
10569
10853
  // Detach the batch first so a second idle edge can't double-flip it.
10570
10854
  ds.pendingAckReactions = [];
10571
- // A dedicated receiver has no progress-reaction channel. Clear any stale
10572
- // in-memory entries restored from an older build without touching Lark.
10573
- if (ds.session.vcMeetingReceiver)
10574
- 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.
10575
10860
  const silent = silentTurnReactions(ds);
10576
10861
  const doneEmoji = doneReactionEmojiFor(ds);
10577
10862
  for (const ack of list) {
@@ -10755,7 +11040,14 @@ function deliverFinalOutput(ds, msg, t, attempt, onComplete, isStillOwned = () =
10755
11040
  const imOrigin = msg.dispatchAttempt === undefined
10756
11041
  ? resolveVcMeetingImTurnOrigin(ds.session, msg.turnId)
10757
11042
  : undefined;
10758
- 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)
10759
11051
  ? evaluateVcMeetingManagedSend(config.session.dataDir, {
10760
11052
  receiverSessionId: ds.session.sessionId,
10761
11053
  receiverSession: true,
@@ -10773,7 +11065,7 @@ function deliverFinalOutput(ds, msg, t, attempt, onComplete, isStillOwned = () =
10773
11065
  return;
10774
11066
  }
10775
11067
  const revalidateManagedSend = () => {
10776
- if (!ds.session.vcMeetingReceiver)
11068
+ if (!isMeetingDrivenTurn(ds, msg.turnId, msg.dispatchAttempt))
10777
11069
  return;
10778
11070
  const current = evaluateVcMeetingManagedSend(config.session.dataDir, {
10779
11071
  receiverSessionId: ds.session.sessionId,
@@ -10851,11 +11143,14 @@ function deliverFinalOutput(ds, msg, t, attempt, onComplete, isStillOwned = () =
10851
11143
  const feedbackRequesterSubjectId = recipientOpenId;
10852
11144
  const feedback = feedbackPolicy && feedbackRequesterSubjectId ? { policy: feedbackPolicy } : undefined;
10853
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));
10854
11151
  const cardJson = msg.kind === 'local-turn' || msg.kind === 'local-turn-headless'
10855
11152
  ? buildContextualReplyCard({
10856
- title: msg.kind === 'local-turn-headless'
10857
- ? tr('card.local_turn_resumed', undefined, localeForBot(ds.larkAppId))
10858
- : tr('card.local_turn', undefined, localeForBot(ds.larkAppId)),
11153
+ title: localTurnTitle,
10859
11154
  userText: msg.kind === 'local-turn' ? safeUserText ?? '' : undefined,
10860
11155
  assistantText: safeAssistantText,
10861
11156
  assistantLabel: storedSessionCliDisplayName(ds),
@@ -11314,6 +11609,7 @@ export function forkAdoptWorker(ds, opts) {
11314
11609
  cliSessionId: isStructuredBridge ? adopted.sessionId : undefined,
11315
11610
  model: agentCfg.model,
11316
11611
  turnTimeoutMs: botCfg.turnTimeoutMs,
11612
+ dshRuntime: botCfg.dshRuntime,
11317
11613
  disableCliBypass: botCfg.disableCliBypass === true,
11318
11614
  codexRpcInput: botCfg.codexRpcInput === true || config.codexRpcInputDefault,
11319
11615
  // Adopt is normally observe-only (prompt=''), driven later by 'message'
@@ -11528,7 +11824,20 @@ export function killStalePids(activeSessions_, runtimeSessions) {
11528
11824
  try {
11529
11825
  // Check if process exists (signal 0 doesn't kill, just checks)
11530
11826
  process.kill(session.pid, 0);
11531
- // 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.
11532
11841
  logger.info(`Killing stale CLI process (pid: ${session.pid}, session: ${session.sessionId})`);
11533
11842
  try {
11534
11843
  process.kill(-session.pid, 'SIGTERM');
@@ -11794,6 +12103,12 @@ function cleanupPersistentBackendSessions(backendType, activeSessions_, runtimeS
11794
12103
  for (const session of activeSessions_) {
11795
12104
  if (!belongsToBackend(session))
11796
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;
11797
12112
  const sessionCliId = session.cliId;
11798
12113
  if (!sessionCliId || !session.larkAppId)
11799
12114
  continue;