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
package/dist/worker.js CHANGED
@@ -15,15 +15,16 @@
15
15
  import { createHash, randomBytes } from 'node:crypto';
16
16
  import { accessSync, chmodSync, mkdirSync, writeFileSync, unlinkSync, rmdirSync, existsSync, statSync, lstatSync, readdirSync, readlinkSync, readFileSync, realpathSync, copyFileSync, watch as fsWatch, createWriteStream, constants as fsConstants } from 'node:fs';
17
17
  import { atomicWriteFileSync } from './utils/atomic-write.js';
18
- import { join, basename, dirname, delimiter } from 'node:path';
18
+ import { join, basename, dirname, delimiter, relative } from 'node:path';
19
19
  import { resolveBotmuxWrapperBinDir, prependBotmuxBin } from './core/botmux-wrapper.js';
20
20
  import { homedir, tmpdir, userInfo } from 'node:os';
21
21
  import { spawnSync } from 'node:child_process';
22
22
  import { evaluateCredentialOnlyIsolationGate, credentialIsolationRequired, deviceCredentialIsolationMarkerPath, isCredentialIsolationReservedBasename, buildCredentialIsolationRules, buildSeatbeltProfile, isolatedPaneOriginChannel, isolatedPaneReattachSafe, evaluatePersistentPaneMigration, executePersistentPaneMigration, persistentTeardownKillKind, isolationPaneMarkerPath, policyOffTombstonePath, policyOffTombstoneContent, policyOffTombstoneValid, provenancePendingContent, provenancePendingNonce, sendCredFilePath, botHomePath, buildCliExecutableReadCarveOuts, isolationPaneMarkerContent, isolationPanePolicyDigest, } from './adapters/cli/read-isolation.js';
23
- import { buildFsPolicy, compileToSeatbelt, migrateLegacySandboxFields, resolveRedirectedAdapterAuthPaths, FsPolicyConfigError } from './adapters/cli/fs-policy.js';
23
+ import { buildFsPolicy, compileToSeatbelt, migrateLegacySandboxFields, resolveRedirectedAdapterAuthPaths, resolveLarkCliLinuxStoreDir, larkCliChildDataRoot, FsPolicyConfigError } from './adapters/cli/fs-policy.js';
24
24
  import { buildCloseResultMessage, normalizeDestroyResult, mayRestoreWriteAdmission, interpretAbortOutcome, classifyRestartTeardown } from './adapters/backend/destroy-result.js';
25
25
  import { isRemoteBackendType, killPersistentBackendTarget, killPersistentSession, probePersistentBackendTarget, probePersistentSession, shouldRejectPersistentPostKillProbe } from './core/persistent-backend.js';
26
26
  import { finalizeRawCommandDelivery, writeRawCommandLine } from './core/raw-command-writer.js';
27
+ import { decodeTerminalWriteFrameSource, terminalWriteFrame, } from './core/terminal-write-frame.js';
27
28
  import { rawCommandWriteOptionsFor } from './core/raw-command-write-options.js';
28
29
  import { publishCliSessionIdToDaemon } from './core/cli-session-id-publisher.js';
29
30
  import { readProcessStartIdentity } from './core/session-marker.js';
@@ -46,6 +47,7 @@ import { remoteWorkerShutdownInputBlocker } from './core/remote-worker-shutdown-
46
47
  import { ReadyGate, shouldArmReadyGate } from './utils/ready-gate.js';
47
48
  import { shouldRunStartupCommandsOnSpawn, shouldDeferInitialPromptForStartup } from './core/startup-commands.js';
48
49
  import { sanitizePerBotEnv } from './core/per-bot-env.js';
50
+ import { normalizeExistingAppServerEndpoint } from './core/existing-app-server.js';
49
51
  import { resolveChildBotsConfig } from './core/config-dir.js';
50
52
  import { evaluateVcMeetingManagedSend, } from './services/vc-meeting-send-policy.js';
51
53
  import { TurnTerminalDeduper } from './services/turn-terminal-deduper.js';
@@ -58,7 +60,7 @@ import { readSessionMcpRuntimeManifest, sessionMcpRuntimeHostOnlyPaths, } from '
58
60
  import { prepareCliPluginGeneration } from './core/plugins/cli-generation.js';
59
61
  import { loadBotConfigs, resolveBrandLabel, resolveUsageDisplay, } from './bot-registry.js';
60
62
  import { readGlobalConfig, isWorkflowFeatureEnabled } from './global-config.js';
61
- import { deriveTerminalWriteToken, resolveTerminalAccessForRequest, safeTerminalTokenEqual, } from './core/terminal-write-auth.js';
63
+ import { deriveTerminalWriteToken, resolveTerminalAccessForRequest, safeTerminalTokenEqual, TERMINAL_PLATFORM_READONLY_HINT_HEADER, } from './core/terminal-write-auth.js';
62
64
  import { deriveWorkerViewGeneration, looksLikeTerminalControlGrant, TERMINAL_VIEW_FORWARD_HEADER, verifyTerminalControlGrant, verifyTerminalViewForward, } from './core/terminal-control-grant.js';
63
65
  import { appendControlAudit, controlAuditRecord } from './dashboard/control-audit.js';
64
66
  import { readPlatformBinding } from './platform/binding.js';
@@ -81,6 +83,7 @@ import { currentHermesStateOffset, drainHermesStateDb, resolveHermesStateDbPath
81
83
  import { filterHermesEventsForBotmuxSession } from './services/hermes-session-filter.js';
82
84
  import { currentMtrSessionOffset, drainMtrSession, findLatestMtrSessionByDirectory, findMtrSessionById } from './services/mtr-transcript.js';
83
85
  import { drainPiTranscript } from './services/pi-transcript.js';
86
+ import { drainOmpTranscript } from './services/omp-transcript.js';
84
87
  import { drainGrokUpdates, findGrokSessionByPid, grokSessionIdFromPath, } from './services/grok-transcript.js';
85
88
  import { resolveFileBridgePath } from './services/file-bridge-path.js';
86
89
  import { isStructuredBridgeAdoptIdleCli, isStructuredBridgeAdoptInputCli, isStructuredBridgeFallbackActive, isStructuredBridgeLifecycleBlockingCli, } from './services/structured-bridge-clis.js';
@@ -96,6 +99,7 @@ import { listenWebTerminalWithFallback } from './utils/web-terminal-listen.js';
96
99
  import { HerdrWebTerminalBinding } from './utils/herdr-web-terminal-binding.js';
97
100
  import { TERMINAL_FAVICON_DATA_URI } from './utils/terminal-favicon.js';
98
101
  import { t, setDefaultLocale } from './i18n/index.js';
102
+ import { registerPromptOverrideResolver } from './skills/effective-builtins.js';
99
103
  import { TerminalRenderer } from './utils/terminal-renderer.js';
100
104
  import { DEFAULT_RENDER_COLS, DEFAULT_RENDER_ROWS, MAX_RENDER_COLS, MAX_RENDER_ROWS, MIN_RENDER_COLS, MIN_RENDER_ROWS, clamp, resolveRenderDimensions, } from './utils/render-dimensions.js';
101
105
  import { createCliAdapterSync, locateOnPath } from './adapters/cli/registry.js';
@@ -107,6 +111,7 @@ import { delay } from './utils/timing.js';
107
111
  import { claudeJsonlPathForSession, resolveJsonlFromPid, findOpenClaudeSessionIds, syncClaudeResumeTargetToCwd, DEFAULT_CLAUDE_DATA_DIR } from './adapters/cli/claude-code.js';
108
112
  import { sessionReadyHookCommand } from './adapters/hook-command.js';
109
113
  import { mtrSessionIdForBotmuxSession } from './adapters/cli/mtr.js';
114
+ import { ompSessionDir } from './adapters/cli/oh-my-pi.js';
110
115
  import { strictInputHandle } from './adapters/cli/strict-input-handle.js';
111
116
  import { HerdrBackend } from './adapters/backend/herdr-backend.js';
112
117
  import { TmuxBackend } from './adapters/backend/tmux-backend.js';
@@ -130,7 +135,7 @@ import { tmuxEnv, probeTmuxFunctionalWithRetry } from './setup/ensure-tmux.js';
130
135
  import { probeZmxVersion } from './setup/ensure-zmx.js';
131
136
  import { tmuxRestartJitterMs } from './core/tmux-recovery.js';
132
137
  import { IdleDetector } from './utils/idle-detector.js';
133
- import { StuckDetector, matchHookReviewScreen } from './utils/stuck-detector.js';
138
+ import { StuckDetector, matchHookReviewScreen, shouldHoldInputForHookReview, } from './utils/stuck-detector.js';
134
139
  import { processStuckWarningTuiKeys, shouldRearmStuckDetector } from './utils/stuck-key-guard.js';
135
140
  import { sendTuiKeySequence, submitTuiTextInput } from './utils/tui-input-delivery.js';
136
141
  import { captureToPng } from './utils/screenshot-renderer.js';
@@ -138,7 +143,8 @@ import { snapshotToPng, snapshotToText, shouldCaptureScreen, isScreenSelfDriven
138
143
  import { chooseWebTerminalSeed } from './utils/web-terminal-seed.js';
139
144
  import { mergeHerdrWebSnapshot, renderHerdrWebHistory, } from './utils/herdr-web-history.js';
140
145
  import { parseWorkerRequestUrl } from './utils/worker-http.js';
141
- import { detectCliUsageLimit, usageLimitStateKey, structuredRateLimitState, isStructuredRateLimitAuthoritative } from './utils/cli-usage-limit.js';
146
+ import { structuredRateLimitState, isStructuredRateLimitAuthoritative } from './utils/cli-usage-limit.js';
147
+ import { createUsageLimitTracker } from './utils/usage-limit-tracker.js';
142
148
  import { uploadImageBuffer } from './utils/lark-upload.js';
143
149
  import { applySessionOwnerEnv, redactChildEnv, scrubClaudeSessionMarkerEnv, scrubSessionCliHomeEnv } from './utils/child-env.js';
144
150
  import { decideSubmitConfirmationAction, settleDeferredSubmitConfirmation, settleStaleWriteContinuation, } from './services/submit-confirmation.js';
@@ -153,7 +159,7 @@ import { fetchDaemonIpc } from './core/daemon-ipc-auth.js';
153
159
  import { withCodexAppContext } from './utils/codex-app-context.js';
154
160
  import { resolveCodexAppFinalTurnIdentity } from './adapters/cli/codex-app-turn.js';
155
161
  import { RunnerControlDecoder } from './adapters/cli/runner-control-channel.js';
156
- import { normalizeCodexAppLifecycleEvent, normalizeFinalUsage, } from './services/codex-app-runner-protocol.js';
162
+ import { CODEX_APP_ACTIVE_WRITER_EXIT_CODE, normalizeCodexAppLifecycleEvent, normalizeFinalUsage, } from './services/codex-app-runner-protocol.js';
157
163
  import { hasMatchingManagedOriginCapability, ensureManagedOriginAttestationDirectory, ensureManagedOriginCapabilityLeafSafe, ensureManagedOriginDataRootProbe, ensureManagedOriginIsolationSentinel, ensureManagedOriginRootLocator, managedOriginLegacyIsolationProbeAccess, managedOriginDataRootProbeAccess, managedOriginIsolationSentinelAccess, managedOriginAttestationDirectory, managedOriginCapabilityDirectory, managedOriginCapabilityPath, managedOriginRootLocatorPath, readManagedOriginAuthorityFile, RELAY_ORIGIN_CAPABILITY_BASENAME, replaceManagedOriginCapabilityFile, sweepManagedOriginAttestationProofs, } from './core/managed-origin-capability.js';
158
164
  import { CodexRpcEngine, } from './codex-rpc-engine.js';
159
165
  import { beginRuntimeWriteCycle, isCliBackendGenerationCurrent, PtyOutputGeneration, projectRuntimeScreenStatus, } from './utils/runtime-screen-status.js';
@@ -1410,6 +1416,7 @@ const adoptWriteQueue = new AsyncSerialQueue();
1410
1416
  * adapter's hermesBridgeAttach reads the correct mode. */
1411
1417
  let lastSpawnEffectiveResume = false;
1412
1418
  let lastSpawnEffectiveCliSessionId;
1419
+ let lastSpawnEffectiveAdapterSessionId;
1413
1420
  let lastSpawnDeferInitialPrompt = false;
1414
1421
  let lastSpawnQueuedInitialPrompt;
1415
1422
  let lastSpawnQueuedInitialPromptLogicalContent;
@@ -1678,7 +1685,7 @@ let closeRequested = false;
1678
1685
  let capturedSpawnCommand = null;
1679
1686
  let deferredTopicOutputTail = '';
1680
1687
  const reportedDeferredTopicRoots = new Set();
1681
- const CLI_DISPLAY_NAMES = { 'claude-code': 'Claude', seed: 'Seed', relay: 'Relay', aiden: 'Aiden', coco: 'CoCo', codex: 'Codex', 'codex-app': 'Codex App', cursor: 'Cursor', gemini: 'Gemini', genius: 'Genius', opencode: 'OpenCode', opencode2: 'OpenCode 2', antigravity: 'Antigravity', mtr: 'MTR', hermes: 'Hermes', mira: 'Mira', mir: 'Mir CLI', traex: 'TRAE', pi: 'Pi', copilot: 'Copilot', 'oh-my-pi': 'Oh My Pi', kimi: 'Kimi', grok: 'Grok Build', 'kiro-cli': 'Kiro', riff: 'Riff', reasonix: 'Reasonix', dsh: 'DeepSeek Harness', mojo: 'Mojo' };
1688
+ const CLI_DISPLAY_NAMES = { 'claude-code': 'Claude', seed: 'Seed', relay: 'Relay', aiden: 'Aiden', coco: 'CoCo', codex: 'Codex', 'codex-app': 'Codex App', cursor: 'Cursor', gemini: 'Gemini', genius: 'Genius', opencode: 'OpenCode', opencode2: 'OpenCode 2', antigravity: 'Antigravity', mtr: 'MTR', hermes: 'Hermes', mira: 'Mira', mir: 'Mir CLI', traex: 'TRAE', pi: 'Pi', copilot: 'Copilot', 'oh-my-pi': 'Oh My Pi', kimi: 'Kimi', grok: 'Grok Build', 'kiro-cli': 'Kiro', riff: 'Riff', reasonix: 'Reasonix', dsh: 'DeepSeek Harness', 'dsh-tui': 'DeepSeek Harness TUI', mojo: 'Mojo' };
1682
1689
  function cliName() {
1683
1690
  return (lastInitConfig?.cliRuntime?.source === 'configured'
1684
1691
  ? (lastInitConfig.cliRuntime.displayName?.trim() || lastInitConfig.cliRuntime.id)
@@ -1793,6 +1800,38 @@ let readyPatternSeenDuringHold = false;
1793
1800
  let awaitingPostSessionStartPromptEvidence = false;
1794
1801
  /** Scoped marker set only by IdleDetector's screen-driven callback. */
1795
1802
  let postSessionStartPromptEvidenceInFlight = false;
1803
+ /** A visible Codex Hook-review screen owns Enter/arrow keys. Keep queued IM
1804
+ * input out of that menu so an automated submit cannot accidentally select
1805
+ * “Trust all” or get consumed as menu navigation. */
1806
+ let hookReviewInputHold = false;
1807
+ let hookReviewInputHoldNotified = false;
1808
+ function refreshHookReviewInputHold(snapshot) {
1809
+ if (!snapshot.trim())
1810
+ return;
1811
+ const blocked = shouldHoldInputForHookReview(snapshot);
1812
+ if (blocked === hookReviewInputHold)
1813
+ return;
1814
+ hookReviewInputHold = blocked;
1815
+ if (blocked) {
1816
+ log('Hook review menu visible — holding queued input until the menu is resolved');
1817
+ return;
1818
+ }
1819
+ hookReviewInputHoldNotified = false;
1820
+ log('Hook review menu cleared — releasing queued input');
1821
+ if (hasPendingInputForFlush())
1822
+ queueMicrotask(() => { void flushPending(); });
1823
+ }
1824
+ function notifyHookReviewInputHold() {
1825
+ if (hookReviewInputHoldNotified)
1826
+ return;
1827
+ hookReviewInputHoldNotified = true;
1828
+ send({
1829
+ type: 'user_notify',
1830
+ turnId: currentBotmuxTurnId,
1831
+ message: '⚠️ Codex 正在显示 Hook 审核菜单。为避免消息误触“Trust all”或被菜单吞掉,'
1832
+ + 'BotMux 已暂存后续消息;请先在终端完成 Hook 选择,恢复普通输入框后会自动投递。',
1833
+ });
1834
+ }
1796
1835
  /** Wait until the PTY has been quiet for READY_FLUSH_SETTLE_MS (Ink render
1797
1836
  * drained), capped at READY_FLUSH_SETTLE_CAP_MS, then flush the held prompt.
1798
1837
  * An authoritative direct ready command (Hermes) can mark prompt readiness;
@@ -2028,6 +2067,15 @@ let pendingSessionRename = null;
2028
2067
  let nativeSessionTitleSyncInFlight;
2029
2068
  let nativeSessionTitleAppliedThreadId;
2030
2069
  let nativeSessionTitleRevision = 0;
2070
+ /** Every user-controlled input class that flushPending can deliver. Keep this
2071
+ * shared with review-menu release handling so a queued raw command, adopt
2072
+ * message, or native rename is not stranded after the menu disappears. */
2073
+ function hasPendingInputForFlush() {
2074
+ return pendingMessages.length > 0
2075
+ || pendingAdoptMessages.length > 0
2076
+ || pendingRawInputs.length > 0
2077
+ || pendingSessionRename !== null;
2078
+ }
2031
2079
  let nativeSessionTitleResumeUpdatedAt;
2032
2080
  let nativeSessionTitleCurrentGenerationResume = false;
2033
2081
  const nativeSessionTitleSyncAbortControllers = new Set();
@@ -3416,59 +3464,27 @@ function codexAppLivenessStatus(base, nowMs = Date.now()) {
3416
3464
  function structuredRateLimitAuthoritative() {
3417
3465
  return isStructuredRateLimitAuthoritative(cliAdapter);
3418
3466
  }
3419
- // Per-turn usage-limit state machine. Owns the turn counter plus the
3420
- // "did this turn hit a limit" / "suppress a stale retry-ready banner" flags, so
3421
- // classify()'s state writes are explicit method calls rather than hidden
3422
- // mutations of module globals from a function that otherwise reads as a pure
3423
- // mapper.
3424
- function createUsageLimitTracker() {
3425
- let turnSeq = 0;
3426
- let detectedTurn;
3427
- let suppressedRetryReadyKey;
3428
- return {
3429
- currentTurn() {
3430
- return turnSeq;
3431
- },
3432
- // Open a new turn; remember any stale retry-ready banner still on screen so
3433
- // classify() doesn't re-flag it as a fresh limit this turn.
3434
- beginTurn(snapshot) {
3435
- turnSeq++;
3436
- detectedTurn = undefined;
3437
- const current = detectCliUsageLimit(snapshot, undefined, { suppressRateKind: structuredRateLimitAuthoritative() });
3438
- suppressedRetryReadyKey = current.limited && current.retryReady
3439
- ? usageLimitStateKey(current)
3440
- : undefined;
3441
- return turnSeq;
3442
- },
3443
- // Map a runtime status to a usage-limit-aware status, recording whether this
3444
- // turn hit a limit (read back via detectedThisTurn).
3445
- classify(content, status) {
3446
- const detected = detectCliUsageLimit(content, undefined, { suppressRateKind: structuredRateLimitAuthoritative() });
3447
- if (!detected.limited)
3448
- return { status };
3449
- const key = usageLimitStateKey(detected);
3450
- if (detected.retryReady && key === suppressedRetryReadyKey) {
3451
- return { status };
3452
- }
3453
- suppressedRetryReadyKey = undefined;
3454
- detectedTurn = turnSeq;
3455
- return { status: 'limited', usageLimit: detected };
3456
- },
3457
- detectedThisTurn(seq) {
3458
- return detectedTurn === seq;
3459
- },
3460
- // Record a limit that came from a STRUCTURED signal (transcript error
3461
- // record) rather than screen text. Mirrors classify()'s state writes so
3462
- // the tracker stays coherent: mark this turn as having hit a limit (read
3463
- // by detectedThisTurn for the submit-confirmation recheck) and clear any
3464
- // stale retry-ready suppression. The actual emit is done by the caller.
3465
- noteStructuredLimit() {
3466
- suppressedRetryReadyKey = undefined;
3467
- detectedTurn = turnSeq;
3468
- },
3469
- };
3470
- }
3471
- const usageLimitTracker = createUsageLimitTracker();
3467
+ /**
3468
+ * PTY-quiescence window for the usage-limit active-work gate. A `working`
3469
+ * status alone does not prove output is progressing (it is the default
3470
+ * projection whenever promptReady === false), so the screen-scan suppression
3471
+ * only applies while PTY output is this fresh. A non-structured CLI blocked at
3472
+ * a rate/quota error screen that never renders its ready prompt goes silent
3473
+ * past this window and is correctly detected instead of being suppressed
3474
+ * forever. Aligns with the StuckDetector's PTY-quiescence threshold.
3475
+ */
3476
+ const USAGE_LIMIT_OUTPUT_ACTIVE_WINDOW_MS = 15_000;
3477
+ function isOutputActivelyProgressing() {
3478
+ return Date.now() - lastPtyActivityAtMs < USAGE_LIMIT_OUTPUT_ACTIVE_WINDOW_MS;
3479
+ }
3480
+ // Per-turn usage-limit state machine (extracted to utils/usage-limit-tracker
3481
+ // for unit testing). The structured-limit stickiness it owns is load-bearing:
3482
+ // a one-shot transcript emit must survive the daemon's working-frame self-heal
3483
+ // while the CLI stays genuinely blocked.
3484
+ const usageLimitTracker = createUsageLimitTracker({
3485
+ isRateKindSuppressed: structuredRateLimitAuthoritative,
3486
+ isOutputActive: isOutputActivelyProgressing,
3487
+ });
3472
3488
  function currentUsageLimitSnapshot() {
3473
3489
  if (!backendScreenEvidenceIsAuthoritativeForMutation())
3474
3490
  return '';
@@ -3588,6 +3604,10 @@ let activeRuntimePublished = false;
3588
3604
  const codexBridgeQueue = new CodexBridgeQueue();
3589
3605
  let codexBridgeWatcher = null;
3590
3606
  let codexBridgeTimer = null;
3607
+ let ompBridgeState = {};
3608
+ let ompQuietCandidateKey;
3609
+ let ompQuietCandidateCompleteOffset;
3610
+ const ompRetiredTranscriptPaths = new Set();
3591
3611
  /** Settings are observed on the same append-only cursor as bridge output.
3592
3612
  * The tracker owns rollout-generation clear/update semantics and publishes a
3593
3613
  * dedicated IPC event, independent of PTY redraw frequency. */
@@ -4766,7 +4786,7 @@ function maybeEmitStructuredRateLimit(events) {
4766
4786
  // Prefer a clock parsed from the record's own text ("... resets 10:40pm");
4767
4787
  // fall back to the shared bucketed cooldown when it carries none.
4768
4788
  const usageLimit = structuredRateLimitState(apiErrorMessageText(ev));
4769
- usageLimitTracker.noteStructuredLimit();
4789
+ usageLimitTracker.noteStructuredLimit(usageLimit);
4770
4790
  send({
4771
4791
  type: 'screen_update',
4772
4792
  content: currentUsageLimitSnapshot(),
@@ -5132,6 +5152,11 @@ function emitReadyTurns(opts = {}) {
5132
5152
  const fields = formatLocalTurnFields(rawUserText, postText);
5133
5153
  if (!fields)
5134
5154
  continue;
5155
+ // A harvested answer proves the CLI recovered from any structured
5156
+ // limit that parked it — mirror the daemon's final_output self-heal
5157
+ // (worker-pool clears ds.usageLimit there) by dropping the tracker's
5158
+ // re-emit latch so the next classify() does not re-pin the card.
5159
+ usageLimitTracker.noteTurnCompleted();
5135
5160
  send({
5136
5161
  type: 'final_output',
5137
5162
  content: fields.content,
@@ -5147,6 +5172,7 @@ function emitReadyTurns(opts = {}) {
5147
5172
  const headlessContent = formatHeadlessLocalTurnContent(postText);
5148
5173
  if (!headlessContent)
5149
5174
  continue;
5175
+ usageLimitTracker.noteTurnCompleted();
5150
5176
  send({
5151
5177
  type: 'final_output',
5152
5178
  content: headlessContent,
@@ -5164,6 +5190,7 @@ function emitReadyTurns(opts = {}) {
5164
5190
  const deliveredText = turn.restoredFromJournal
5165
5191
  ? `${t('worker.bridge_restored_turn_notice')}\n\n${postText}`
5166
5192
  : postText;
5193
+ usageLimitTracker.noteTurnCompleted();
5167
5194
  send({
5168
5195
  type: 'final_output',
5169
5196
  content: deliveredText,
@@ -5209,6 +5236,19 @@ function codexBridgeFallbackActive() {
5209
5236
  // services/structured-bridge-clis.ts (single source of the allowlist).
5210
5237
  return isStructuredBridgeFallbackActive(lastInitConfig?.cliId, lastInitConfig?.adoptMode === true);
5211
5238
  }
5239
+ /** A Codex App shared-adopt starts a SECOND official `codex --remote` client
5240
+ * against an external App Server/thread. It has the same transcript shape as
5241
+ * terminal `/adopt`, but is deliberately NOT `adoptMode`: BotMux still owns
5242
+ * the remote TUI and its normal Lark-input reliability rules. */
5243
+ function isExistingAppServerSharedBridge() {
5244
+ return !!lastInitConfig?.existingAppServerEndpoint;
5245
+ }
5246
+ /** `/adopt`'s split-live attach is also required for the App Server share:
5247
+ * existing history is absorbed while new, App-side turns after the attach
5248
+ * boundary can be synthesised back into the Lark conversation. */
5249
+ function codexBridgeUsesSplitLiveAttach() {
5250
+ return lastInitConfig?.adoptMode === true || isExistingAppServerSharedBridge();
5251
+ }
5212
5252
  /** Only drivers with a complete normal-final + interrupted-terminal contract
5213
5253
  * may let a transcript-started turn override the screen-ready heuristic. */
5214
5254
  function hasStructuredLifecycleBlock() {
@@ -5241,6 +5281,9 @@ function structuredBridgeIsMtr() {
5241
5281
  function structuredBridgeIsPi() {
5242
5282
  return lastInitConfig?.cliId === 'pi';
5243
5283
  }
5284
+ function structuredBridgeIsOmp() {
5285
+ return lastInitConfig?.cliId === 'oh-my-pi';
5286
+ }
5244
5287
  function structuredBridgeIsGrok() {
5245
5288
  return lastInitConfig?.cliId === 'grok';
5246
5289
  }
@@ -5250,7 +5293,7 @@ function codexBridgeIsCursor() {
5250
5293
  function currentHermesBridgeDbPath() {
5251
5294
  return hermesBridgeDbPath ?? resolveHermesStateDbPath();
5252
5295
  }
5253
- function structuredBridgeIngestPath(path, offset) {
5296
+ function structuredBridgeIngestPath(path, offset, opts = {}) {
5254
5297
  if (structuredBridgeIsCodex())
5255
5298
  return drainCodexRollout(path, offset);
5256
5299
  // adoptMode gates the drainer's bare-sentinel synthesis: adopt posts
@@ -5262,6 +5305,13 @@ function structuredBridgeIngestPath(path, offset) {
5262
5305
  return drainCursorTranscript(path, offset);
5263
5306
  if (structuredBridgeIsPi())
5264
5307
  return drainPiTranscript(path, offset);
5308
+ if (structuredBridgeIsOmp()) {
5309
+ const result = drainOmpTranscript(path, offset, ompBridgeState, {
5310
+ flushTrailingFinal: opts.flushOmpTrailingFinal,
5311
+ });
5312
+ ompBridgeState = result.state;
5313
+ return result;
5314
+ }
5265
5315
  if (structuredBridgeIsGrok())
5266
5316
  return drainGrokUpdates(path, offset);
5267
5317
  if (structuredBridgeIsHermes()) {
@@ -5353,12 +5403,14 @@ function codexBridgeStartTimer() {
5353
5403
  // fd is process-scoped, so following that fd cannot select a sibling
5354
5404
  // TRAE process merely because it shares the working directory.
5355
5405
  maybeFollowTraexSessionRotationViaPid();
5406
+ maybeFollowOmpTranscriptRotation();
5356
5407
  if (!codexBridgeRolloutPath) {
5357
5408
  // Late-attach: cliSessionId (writeInput / daemon probe) then adopt
5358
5409
  // pid. Path lookup is centralized in resolveFileBridgePath so
5359
5410
  // adding a file-backed CLI does not grow this if-tree.
5360
- // Adopt → split-live; non-adopt → fresh-empty (queue markTimeMs
5361
- // lower bound handles history without a split).
5411
+ // Terminal `/adopt` and an existing-App-Server share both need
5412
+ // split-live: absorb history before the attach boundary while keeping
5413
+ // new local/App-side turns after it available for forwarding.
5362
5414
  const path = resolveFileBridgePath(lastInitConfig?.cliId, {
5363
5415
  sessionId: codexBridgePendingSessionId,
5364
5416
  cwd: lastInitConfig?.workingDir,
@@ -5387,11 +5439,12 @@ function codexBridgeStartTimer() {
5387
5439
  }
5388
5440
  codexBridgePendingSessionId = undefined;
5389
5441
  codexAdoptPendingPid = undefined;
5390
- const mode = lastInitConfig?.adoptMode ? 'split-live' : 'fresh-empty';
5442
+ const mode = codexBridgeUsesSplitLiveAttach() ? 'split-live' : 'fresh-empty';
5391
5443
  codexBridgeAttach(path, mode);
5392
5444
  }
5393
5445
  }
5394
5446
  codexBridgeIngest();
5447
+ maybeFlushOmpTrailingFinalOnQuietTick();
5395
5448
  if (isPromptReady)
5396
5449
  emitReadyCodexTurns();
5397
5450
  }
@@ -5493,6 +5546,9 @@ function mtrBridgeIngest() {
5493
5546
  }
5494
5547
  }
5495
5548
  function codexBridgeAttach(rolloutPath, mode) {
5549
+ ompBridgeState = {};
5550
+ ompQuietCandidateKey = undefined;
5551
+ ompQuietCandidateCompleteOffset = undefined;
5496
5552
  codexBridgeRolloutPath = rolloutPath;
5497
5553
  if (structuredBridgeIsCodex())
5498
5554
  codexServiceTierTracker.bind(rolloutPath);
@@ -5676,6 +5732,9 @@ function codexBridgeDetachFile() {
5676
5732
  codexBridgeOffset = 0;
5677
5733
  codexBridgePendingTail = '';
5678
5734
  codexBridgeBaselineDone = false;
5735
+ ompBridgeState = {};
5736
+ ompQuietCandidateKey = undefined;
5737
+ ompQuietCandidateCompleteOffset = undefined;
5679
5738
  }
5680
5739
  /** Resolve the pid of the Codex process this worker observes (spawned child or
5681
5740
  * adopted pane), mirroring the grok/traex pid-follow resolution order. */
@@ -5796,7 +5855,7 @@ function codexBridgeNotifyCliSessionId(cliSessionId) {
5796
5855
  const pid = currentCodexObservedPid();
5797
5856
  const next = resolveFileBridgePath('codex', { sessionId: cliSessionId });
5798
5857
  if (next && next !== codexBridgeRolloutPath) {
5799
- const attachMode = lastInitConfig?.adoptMode ? 'split-live' : 'fresh-empty';
5858
+ const attachMode = codexBridgeUsesSplitLiveAttach() ? 'split-live' : 'fresh-empty';
5800
5859
  log(`Codex session binding corrected ${currentSid ?? '?'} → ${cliSessionId} (pid ${pid} owns it); re-attaching bridge to ${next}`);
5801
5860
  codexBridgeDetachFile();
5802
5861
  codexBridgePendingSessionId = undefined;
@@ -5925,7 +5984,7 @@ function codexBridgeNotifyCliSessionId(cliSessionId) {
5925
5984
  }
5926
5985
  return;
5927
5986
  }
5928
- // Codex INITIAL attach (no prior rollout bound). The multi-fd adopt case
5987
+ // Codex INITIAL attach (no prior rollout bound). The multi-fd terminal-adopt case
5929
5988
  // reaches here with codexBridgeRolloutPath still unset: findCodexRolloutByPid
5930
5989
  // returned undefined (ambiguous parent+sibling), so the adopt block armed the
5931
5990
  // poller instead of attaching. The cliSessionId is normally already
@@ -5963,7 +6022,7 @@ function codexBridgeNotifyCliSessionId(cliSessionId) {
5963
6022
  });
5964
6023
  if (path) {
5965
6024
  codexBridgePendingSessionId = undefined;
5966
- codexBridgeAttach(path, 'fresh-empty');
6025
+ codexBridgeAttach(path, codexBridgeUsesSplitLiveAttach() ? 'split-live' : 'fresh-empty');
5967
6026
  }
5968
6027
  else {
5969
6028
  codexBridgePendingSessionId = cliSessionId;
@@ -6015,6 +6074,30 @@ function maybeFollowTraexSessionRotationViaPid() {
6015
6074
  persistCliSessionId(observed.cliSessionId);
6016
6075
  codexBridgeNotifyCliSessionId(observed.cliSessionId);
6017
6076
  }
6077
+ /** OMP may create a new JSONL in the same isolated Botmux session directory.
6078
+ * Follow only that exact directory; no pid/global-session discovery is used. */
6079
+ function maybeFollowOmpTranscriptRotation() {
6080
+ if (!structuredBridgeIsOmp() || !codexBridgeRolloutPath
6081
+ || !lastSpawnEffectiveAdapterSessionId)
6082
+ return;
6083
+ const next = resolveFileBridgePath('oh-my-pi', {
6084
+ sessionId: lastSpawnEffectiveAdapterSessionId,
6085
+ });
6086
+ if (!next || next === codexBridgeRolloutPath || ompRetiredTranscriptPaths.has(next))
6087
+ return;
6088
+ const retired = codexBridgeRolloutPath;
6089
+ try {
6090
+ codexBridgeIngest();
6091
+ emitReadyCodexTurns();
6092
+ }
6093
+ catch (err) {
6094
+ log(`OMP pre-rotation bridge drain failed: ${err.message}`);
6095
+ }
6096
+ log(`OMP transcript rotated: ${codexBridgeRolloutPath} → ${next}`);
6097
+ ompRetiredTranscriptPaths.add(retired);
6098
+ codexBridgeDetachFile();
6099
+ codexBridgeAttach(next, 'fresh-empty');
6100
+ }
6018
6101
  function codexBridgeIngest(opts = {}) {
6019
6102
  // Follow-up RPC turns install their exact bridge mark only after the
6020
6103
  // turn/start ACK passes the generation fence. Ordinary ingest must not
@@ -6033,7 +6116,9 @@ function codexBridgeIngest(opts = {}) {
6033
6116
  }
6034
6117
  if (!codexBridgeRolloutPath || !codexBridgeBaselineDone)
6035
6118
  return;
6036
- const result = structuredBridgeIngestPath(codexBridgeRolloutPath, codexBridgeOffset);
6119
+ const result = structuredBridgeIngestPath(codexBridgeRolloutPath, codexBridgeOffset, {
6120
+ flushOmpTrailingFinal: opts.flushOmpTrailingFinal,
6121
+ });
6037
6122
  codexBridgeOffset = result.newOffset;
6038
6123
  codexBridgePendingTail = result.pendingTail;
6039
6124
  if (structuredBridgeIsTraex()) {
@@ -6069,6 +6154,42 @@ function codexBridgeIngest(opts = {}) {
6069
6154
  idleDetector?.fireIdle();
6070
6155
  }
6071
6156
  }
6157
+ /** Confirm an OMP file-tail terminal only after one complete quiet bridge tick
6158
+ * and an authoritative current viewport with no busy marker. This deliberately
6159
+ * runs while lifecycle blocking is asserted; ingesting the final releases it. */
6160
+ function maybeFlushOmpTrailingFinalOnQuietTick() {
6161
+ if (!structuredBridgeIsOmp())
6162
+ return;
6163
+ const candidate = ompBridgeState.provisionalFinal;
6164
+ if (!candidate) {
6165
+ ompQuietCandidateKey = undefined;
6166
+ ompQuietCandidateCompleteOffset = undefined;
6167
+ return;
6168
+ }
6169
+ const key = candidate.event.uuid;
6170
+ if (ompQuietCandidateKey !== key
6171
+ || ompQuietCandidateCompleteOffset !== codexBridgeOffset) {
6172
+ ompQuietCandidateKey = key;
6173
+ ompQuietCandidateCompleteOffset = codexBridgeOffset;
6174
+ return;
6175
+ }
6176
+ if (codexBridgePendingTail || !hasStructuredLifecycleBlock() || !backend
6177
+ || !backendScreenEvidenceIsAuthoritativeForMutation()
6178
+ || !cliAdapter?.busyPattern)
6179
+ return;
6180
+ try {
6181
+ const screen = captureBackendScreen(backend);
6182
+ if (!screen || cliAdapter.busyPattern.test(busyProbeRegion(screen)))
6183
+ return;
6184
+ }
6185
+ catch (err) {
6186
+ log(`OMP quiet-final viewport capture failed: ${err.message}`);
6187
+ return;
6188
+ }
6189
+ ompQuietCandidateKey = undefined;
6190
+ ompQuietCandidateCompleteOffset = undefined;
6191
+ codexBridgeIngest({ flushOmpTrailingFinal: true });
6192
+ }
6072
6193
  /** 将 Codex 的结构化 429 终态同步为既有的限流状态。 */
6073
6194
  function maybeEmitCodexStructuredRateLimit(events) {
6074
6195
  for (const ev of events) {
@@ -6076,7 +6197,7 @@ function maybeEmitCodexStructuredRateLimit(events) {
6076
6197
  continue;
6077
6198
  emittedRateLimitUuids.add(ev.uuid);
6078
6199
  const usageLimit = structuredRateLimitState(ev.terminalErrorSummary ?? '429 Too Many Requests');
6079
- usageLimitTracker.noteStructuredLimit();
6200
+ usageLimitTracker.noteStructuredLimit(usageLimit);
6080
6201
  send({
6081
6202
  type: 'screen_update',
6082
6203
  content: currentUsageLimitSnapshot(),
@@ -6453,11 +6574,12 @@ function emitReadyCodexTurns() {
6453
6574
  // that flag to settle completed-with-empty; a bare `completed` terminal (e.g.
6454
6575
  // the RPC-hydration timeout path) must never be read as silence.
6455
6576
  const nothingToSendTurns = new Set();
6456
- const adoptMode = lastInitConfig?.adoptMode === true;
6577
+ const terminalAdoptMode = lastInitConfig?.adoptMode === true;
6578
+ const sharedAppServerBridge = isExistingAppServerSharedBridge();
6457
6579
  // Adopt mode: model is the user's external Codex, no botmux send to
6458
6580
  // gate against — every assistant turn (Lark-driven OR locally typed)
6459
6581
  // should reach the thread. Skip marker IO entirely.
6460
- const markers = adoptMode ? [] : readSendMarkers();
6582
+ const markers = terminalAdoptMode ? [] : readSendMarkers();
6461
6583
  const remaining = codexBridgeQueue.peek();
6462
6584
  // Only a STARTED pending turn can bound the last ready turn's send window.
6463
6585
  // An unstarted turn hasn't been dequeued yet (its user event hasn't landed),
@@ -6472,6 +6594,13 @@ function emitReadyCodexTurns() {
6472
6594
  : undefined;
6473
6595
  for (let i = 0; i < ready.length; i++) {
6474
6596
  const turn = ready[i];
6597
+ // A shared App Server session still owns the BotMux remote TUI, so its
6598
+ // Lark-originated turns retain normal send-marker deduplication. Only a
6599
+ // turn synthesized from Codex App input is external/local: that side has
6600
+ // no pending Lark fingerprint and must be forwarded like terminal
6601
+ // `/adopt`, including both the App prompt and its final reply.
6602
+ const adoptMode = terminalAdoptMode
6603
+ || (sharedAppServerBridge && turn.isLocal === true);
6475
6604
  const sourceHermesSessionId = structuredBridgeIsHermes() ? turn.sourceSessionId : undefined;
6476
6605
  const nextBoundaryMs = (i + 1 < ready.length ? ready[i + 1].markTimeMs : nextPendingMarkTimeMs);
6477
6606
  const gateInput = {
@@ -6527,6 +6656,9 @@ function emitReadyCodexTurns() {
6527
6656
  const fields = formatLocalTurnFields(turn.userText ?? '', postContent);
6528
6657
  if (!fields)
6529
6658
  continue;
6659
+ // Harvested answer → drop the structured-limit re-emit latch (mirrors
6660
+ // the daemon's final_output self-heal; see emitReadyTurns).
6661
+ usageLimitTracker.noteTurnCompleted();
6530
6662
  send({
6531
6663
  type: 'final_output',
6532
6664
  ...(sourceHermesSessionId ? { sourceHermesSessionId } : {}),
@@ -6539,6 +6671,7 @@ function emitReadyCodexTurns() {
6539
6671
  });
6540
6672
  continue;
6541
6673
  }
6674
+ usageLimitTracker.noteTurnCompleted();
6542
6675
  send({
6543
6676
  type: 'final_output',
6544
6677
  ...(sourceHermesSessionId ? { sourceHermesSessionId } : {}),
@@ -6577,6 +6710,10 @@ function stopCodexBridge() {
6577
6710
  codexBridgeOffset = 0;
6578
6711
  codexBridgePendingTail = '';
6579
6712
  codexBridgeBaselineDone = false;
6713
+ ompBridgeState = {};
6714
+ ompQuietCandidateKey = undefined;
6715
+ ompQuietCandidateCompleteOffset = undefined;
6716
+ ompRetiredTranscriptPaths.clear();
6580
6717
  hermesBridgeOffset = 0;
6581
6718
  hermesBridgeBaselineDone = false;
6582
6719
  hermesBridgeSourceSessionId = undefined;
@@ -8011,6 +8148,13 @@ function handleVisibleStartupInteraction(data) {
8011
8148
  // row; never let its selection marker reach the first-prompt idle detector.
8012
8149
  if (dismissAidenCodexUpdateDialog(data))
8013
8150
  return true;
8151
+ // An existing App Server viewer shares the user's Codex App trust context.
8152
+ // Never auto-accept an interactive trust prompt here: it can include
8153
+ // project/plugin hooks owned outside BotMux. The operator can safely choose
8154
+ // "Continue without trusting" through the Web Terminal when Codex presents
8155
+ // its Hooks review menu.
8156
+ if (lastInitConfig?.existingAppServerEndpoint)
8157
+ return false;
8014
8158
  if (trustHandled)
8015
8159
  return false;
8016
8160
  const stripped = data.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
@@ -8820,6 +8964,7 @@ function onPtyData(data) {
8820
8964
  }
8821
8965
  }
8822
8966
  renderer?.write(data);
8967
+ refreshHookReviewInputHold(`${renderer?.rawSnapshot() ?? ''}\n${data}`);
8823
8968
  // In tmux-attach mode, each web client has its own tmux attach PTY —
8824
8969
  // no relay needed. In non-tmux mode AND in pipe mode (adopt-bridge),
8825
8970
  // broadcast through the shared scrollback so all connected web clients
@@ -8929,6 +9074,7 @@ async function onBackendScreenResync(snapshot) {
8929
9074
  // current viewport: a prior local attach may have changed the real geometry.
8930
9075
  const visibleSnapshot = nextRenderer?.rawSnapshot() ?? '';
8931
9076
  lastAnalyzerSnapshot = visibleSnapshot;
9077
+ refreshHookReviewInputHold(visibleSnapshot);
8932
9078
  // ZMX history does not carry the authoritative current PTY dimensions. A
8933
9079
  // local `zmx attach` can resize the session below our default 120x24 and that
8934
9080
  // size persists after detach, so even the rendered tail may include rows just
@@ -9638,12 +9784,9 @@ function scheduleSubmitFailureNotify(msg, recheck, transcriptLabel, bridgeTurnId
9638
9784
  case 'suppress-active':
9639
9785
  redriveRejectedStructuredReady();
9640
9786
  log(`Deferred recheck missing but later ${action.evidence} shows ${cliName()} is active — suppressing submit warning. preview="${preview}"`);
9641
- // For a durable turn, activity is evidence of possible submission, not
9642
- // a terminal result. Keep the arbiter closed and re-check until either
9643
- // the transcript confirms it or a quiet interval proves it stuck.
9644
- if (turnIdentity?.dispatchAttempt !== undefined) {
9645
- scheduleSubmitFailureNotify(msg, recheck, transcriptLabel, bridgeTurnId, undefined, turnSeq, turnIdentity, durableTerminalStatus);
9646
- }
9787
+ // activity 证据只能说明 CLI 仍在动,不能证明本次输入已提交
9788
+ // 普通 IM 和 durable turn 都继续重查,直到 transcript 命中或安静窗口落到 submit_unconfirmed
9789
+ scheduleSubmitFailureNotify(msg, recheck, transcriptLabel, bridgeTurnId, undefined, turnSeq, turnIdentity, durableTerminalStatus, structuredTarget);
9647
9790
  return;
9648
9791
  case 'notify-hard-failure':
9649
9792
  // failureReason is handled synchronously above.
@@ -9817,7 +9960,7 @@ async function flushPending() {
9817
9960
  }
9818
9961
  if (ambiguousSubmissionRecoveryHold?.backend === backend)
9819
9962
  return;
9820
- if (pendingMessages.length === 0 && pendingAdoptMessages.length === 0 && pendingRawInputs.length === 0 && pendingSessionRename === null)
9963
+ if (!hasPendingInputForFlush())
9821
9964
  return; // nothing to flush — keep isPromptReady
9822
9965
  if (sessionRenameInFlight())
9823
9966
  return; // wait for /rename to finish before any user input
@@ -9836,6 +9979,14 @@ async function flushPending() {
9836
9979
  return;
9837
9980
  if (bareShellLaunchBlocked)
9838
9981
  return; // launch is held at a bare shell — don't type prompts into it
9982
+ // A Hook-review page has an active selection and consumes Enter/arrow keys.
9983
+ // Re-check the rendered viewport right before a literal write; PTY chunks
9984
+ // can arrive between idle detection and this queue drain.
9985
+ refreshHookReviewInputHold(lastAnalyzerSnapshot || renderer?.rawSnapshot() || '');
9986
+ if (hookReviewInputHold) {
9987
+ notifyHookReviewInputHold();
9988
+ return;
9989
+ }
9839
9990
  // Screen-idle is not a durable receipt. A permission/AskUser prompt can look
9840
9991
  // idle while the logical delivery is unresolved, so no following IM or
9841
9992
  // meeting turn may cross this boundary until an explicit terminal releases
@@ -11161,6 +11312,27 @@ function scheduleBusyPatternIdleProbe(source) {
11161
11312
  }
11162
11313
  async function spawnCli(cfg, opts = {}) {
11163
11314
  const spawnGeneration = ++cliSpawnGeneration;
11315
+ // Experimental external App Server attachment: BotMux owns only this TUI
11316
+ // client, never the server or its JSON-RPC input stream. Re-establish the
11317
+ // remote argv state on EVERY spawn because killCli() deliberately clears the
11318
+ // generic RPC remote variables before a restart.
11319
+ if (cfg.existingAppServerEndpoint !== undefined) {
11320
+ if (cfg.cliId !== 'codex') {
11321
+ throw new Error('existing App Server attachment requires cliId "codex"');
11322
+ }
11323
+ if (!cfg.cliSessionId) {
11324
+ throw new Error('existing App Server attachment requires an already-selected Codex thread id');
11325
+ }
11326
+ if (cfg.codexRpcInput === true) {
11327
+ throw new Error('existing App Server attachment cannot enable BotMux codexRpcInput');
11328
+ }
11329
+ if (codexRpcEngine) {
11330
+ throw new Error('existing App Server attachment cannot share a BotMux-owned Codex RPC engine');
11331
+ }
11332
+ remoteWsUrl = normalizeExistingAppServerEndpoint(cfg.existingAppServerEndpoint, `worker existingAppServerEndpoint for session ${cfg.sessionId}`);
11333
+ remoteThreadId = cfg.cliSessionId;
11334
+ log(`External Codex App Server attach prepared for thread ${cfg.cliSessionId.substring(0, 12)}`);
11335
+ }
11164
11336
  // Prefer force-clear so a half-finished rename cannot block the new generation.
11165
11337
  forceClearSessionRenameInFlight();
11166
11338
  currentCliCredentialIsolated = false;
@@ -11330,7 +11502,16 @@ async function spawnCli(cfg, opts = {}) {
11330
11502
  log(`Adopt mode (${effectiveBackendType}): observing ${target} (${cols}x${rows})`);
11331
11503
  return;
11332
11504
  }
11333
- cliAdapter = createCliAdapterSync(cfg.cliId, cfg.cliPathOverride);
11505
+ // dsh runtime variant: when the bot selected dsh-tui (dshRuntime='tui'),
11506
+ // resolve the PTY-driven dsh-tui adapter instead of the headless JSON-RPC
11507
+ // runner. The bot's cliId stays 'dsh' (the toggle is a per-bot config field),
11508
+ // so every other dsh-specific branch (OSC decode, turn timeout, etc.) is
11509
+ // unaffected — dsh-tui simply doesn't emit OSC frames, making the decoder a
11510
+ // no-op for it.
11511
+ const effectiveCliId = cfg.cliId === 'dsh' && cfg.dshRuntime === 'tui'
11512
+ ? 'dsh-tui'
11513
+ : cfg.cliId;
11514
+ cliAdapter = createCliAdapterSync(effectiveCliId, cfg.cliPathOverride);
11334
11515
  // backendType trust-but-verify + HARD GATE (PTY 退役): an explicit per-bot
11335
11516
  // config (or BACKEND_TYPE env override) bypasses config.ts's default, so the
11336
11517
  // worker re-probes the requested persistent backend here. A requested
@@ -12283,6 +12464,8 @@ async function spawnCli(cfg, opts = {}) {
12283
12464
  bareShellLaunchBlocked = false;
12284
12465
  bareShellChecked = false;
12285
12466
  bareShellCheckInProgress = false;
12467
+ hookReviewInputHold = false;
12468
+ hookReviewInputHoldNotified = false;
12286
12469
  // ── Resume pre-flight check + two-tier fallback ──────────────────────────
12287
12470
  // Tier 0 (adapter capability): adapters whose buildArgs can only resume a
12288
12471
  // PRECISE cliSessionId (cursor/copilot/kimi — no --continue/latest fallback,
@@ -12404,6 +12587,8 @@ async function spawnCli(cfg, opts = {}) {
12404
12587
  // the fresh session's first turn.
12405
12588
  lastSpawnEffectiveResume = effectiveResume;
12406
12589
  lastSpawnEffectiveCliSessionId = effectiveCliSessionId;
12590
+ lastSpawnEffectiveAdapterSessionId = effectiveAdapterSessionId;
12591
+ ompRetiredTranscriptPaths.clear();
12407
12592
  // ttadk 网关:模型走 ttadk 自己的 `-m`(启动期注入到 ttadk 前缀,见下方 wrapperCli
12408
12593
  // 分支),不能再把 cfg.model 透给底层适配器,否则真实 CLI 会再吃一个 --model 重复。
12409
12594
  const ttadkGateway = isTtadkWrapper(cfg.wrapperCli);
@@ -12900,6 +13085,47 @@ async function spawnCli(cfg, opts = {}) {
12900
13085
  }
12901
13086
  return out;
12902
13087
  };
13088
+ // Canonicalize a path whose LEAF may not exist yet (e.g. a lark-cli keystore dir
13089
+ // lark-cli will create on first write): realpath the DEEPEST EXISTING ancestor
13090
+ // (resolving any symlinked prefix — symlinked $HOME, a symlinked data root) and
13091
+ // re-append the non-existent tail. A plain full-path realpath THROWS on the
13092
+ // missing leaf and returns the raw lexical value, so a symlinked ancestor would
13093
+ // stay unresolved and the policy could anchor a DIFFERENT namespace than the one
13094
+ // lark-cli opens after IT canonicalizes (a canonicalization TOCTOU). Input must already be
13095
+ // lexically clean (no `..`); resolveLarkCliLinuxStoreDir Cleans it first.
13096
+ const canonicalNearestAncestor = (p) => {
13097
+ if (!p.startsWith('/'))
13098
+ return p;
13099
+ const segs = p.split('/').filter(Boolean);
13100
+ for (let i = segs.length; i >= 0; i--) {
13101
+ const prefix = '/' + segs.slice(0, i).join('/');
13102
+ try {
13103
+ const real = realpathSync(prefix);
13104
+ return i === segs.length ? real : `${real}/${segs.slice(i).join('/')}`;
13105
+ }
13106
+ catch { /* ancestor missing → try a shallower prefix */ }
13107
+ }
13108
+ return p; // not even '/' resolved (impossible) → lexical fallback
13109
+ };
13110
+ // OMP keeps all transcripts under one shared state root. Materialize this
13111
+ // Botmux session's exact directory before allow-path existence filtering;
13112
+ // the policy below masks the shared sessions parent and re-opens only this
13113
+ // deeper directory read-write.
13114
+ const ompCurrentSessionDir = cliAdapter.id === 'oh-my-pi'
13115
+ ? ompSessionDir(effectiveAdapterSessionId)
13116
+ : undefined;
13117
+ if (ompCurrentSessionDir)
13118
+ mkdirSync(ompCurrentSessionDir, { recursive: true, mode: 0o700 });
13119
+ const canonicalOmpSessionsRoot = ompCurrentSessionDir
13120
+ ? canonical(dirname(dirname(ompCurrentSessionDir)))
13121
+ : undefined;
13122
+ const canonicalOmpSessionDir = ompCurrentSessionDir
13123
+ ? canonical(ompCurrentSessionDir)
13124
+ : undefined;
13125
+ if (canonicalOmpSessionsRoot && canonicalOmpSessionDir
13126
+ && relative(canonicalOmpSessionsRoot, canonicalOmpSessionDir) !== join('botmux', effectiveAdapterSessionId)) {
13127
+ throw new Error('[sandbox] OMP session directory escapes its managed sessions root');
13128
+ }
12903
13129
  // User three-tier lists: the new sandboxPaths field, or a pre-migration
12904
13130
  // session/config's legacy fields mapped through the SAME lossless mapping
12905
13131
  // the startup migration uses.
@@ -13019,6 +13245,9 @@ async function spawnCli(cfg, opts = {}) {
13019
13245
  if (process.platform === 'linux') {
13020
13246
  mandatoryDenyPaths.push(join(canonical(dataDir), 'sandboxes', cfg.sessionId));
13021
13247
  }
13248
+ if (canonicalOmpSessionsRoot) {
13249
+ mandatoryDenyPaths.push(canonicalOmpSessionsRoot);
13250
+ }
13022
13251
  if (process.platform === 'darwin') {
13023
13252
  const osUserHomeDir = userInfo().homedir;
13024
13253
  if (!osUserHomeDir) {
@@ -13150,6 +13379,23 @@ async function spawnCli(cfg, opts = {}) {
13150
13379
  // arbitrary parent dir (`/tmp`, `/etc`, a project root) and bricking the
13151
13380
  // core CLI (codex P1). Canonicalized so it shares the roots' namespace.
13152
13381
  loadedBotsConfigPath: cfg.loadedBotsConfigPath ? canonical(cfg.loadedBotsConfigPath) : undefined,
13382
+ // Linux lark-cli keystore (holds every bot's appsecret ciphertext + the shared
13383
+ // master key). Resolve it EXACTLY as the in-sandbox lark-cli will:
13384
+ // `<clean(LARKSUITE_CLI_DATA_DIR)>/lark-cli` when that env var is a valid ABSOLUTE
13385
+ // path (lexically Cleaned — `..`/`.`/`//` resolved, control chars rejected), else
13386
+ // `$HOME/.local/share/lark-cli` — lark-cli does NOT read XDG_DATA_HOME (verified by
13387
+ // strace on v1.0.76 + its keychain_other.go::StorageDir / SafeEnvDirPath). The
13388
+ // Clean happens INSIDE resolveLarkCliLinuxStoreDir (a `..`-bearing value
13389
+ // must be normalized here, or realpath throws on the nonexistent leaf, the raw `..`
13390
+ // survives, normalizeFsPath rejects it, and the policy anchors the DEFAULT store
13391
+ // while lark-cli opens the Clean'd real one → leak). canonicalNearestAncestor then
13392
+ // resolves any SYMLINKED ancestor even when the store leaf does not exist yet (a
13393
+ // full-path realpath would throw and skip symlink resolution → policy/CLI namespace
13394
+ // divergence). bwrap has no --clearenv, so the child inherits this bot's
13395
+ // LARKSUITE_CLI_DATA_DIR from the worker's own (frozen, non-agent-controllable)
13396
+ // process.env, and sandbox.ts pins the child to the SAME cleaned resolution
13397
+ // (--setenv/--unsetenv) so policy == CLI by construction. Ignored on darwin.
13398
+ larkCliLinuxStore: canonicalNearestAncestor(resolveLarkCliLinuxStoreDir(process.env.LARKSUITE_CLI_DATA_DIR, lexicalHome)),
13153
13399
  redirectedCliData: willRedirectCliData,
13154
13400
  cliDataPaths: willRedirectCliData ? undefined : keepExisting([
13155
13401
  cliAdapter.claudeDataDir,
@@ -13201,7 +13447,7 @@ async function spawnCli(cfg, opts = {}) {
13201
13447
  ]),
13202
13448
  botmuxInstallRoot,
13203
13449
  outbox,
13204
- extraWritePaths: keepExisting([process.env.TMPDIR]),
13450
+ extraWritePaths: keepExisting([process.env.TMPDIR, canonicalOmpSessionDir]),
13205
13451
  userPaths,
13206
13452
  mandatoryDenyPaths,
13207
13453
  mandatoryDenyRegexes,
@@ -13288,6 +13534,25 @@ async function spawnCli(cfg, opts = {}) {
13288
13534
  }
13289
13535
  }
13290
13536
  else {
13537
+ // Canonical lark-cli data root pinned into the child so the in-sandbox lark-cli
13538
+ // resolves the SAME physical keystore the policy denied/carved-out (avoiding a
13539
+ // symlinked-data-root namespace split). fsPolicyCtx.larkCliLinuxStore is the FULLY-canonical store dir (leaf
13540
+ // symlinks followed) — the physical location the policy protects. The child opens
13541
+ // `<pin>/lark-cli`, so pin = dirname(store) is same-source ONLY when the canonical
13542
+ // store's basename is still `lark-cli` (the common case, incl. a symlink that
13543
+ // resolves to another `lark-cli` dir). If the `lark-cli` leaf was a symlink to a
13544
+ // DIFFERENTLY-named dir (basename !== 'lark-cli'), dirname(store)+'/lark-cli' would
13545
+ // NOT equal the canonical store → child would open an unbound path (auth breaks) OR
13546
+ // a foreign one. There is no leak either way (the policy still denies the real dir,
13547
+ // and both candidate stores are locked), so degrade SAFELY: pin nothing → child
13548
+ // falls back to the default store (also locked), and log the exotic layout.
13549
+ const canonStore = fsPolicyCtx.larkCliLinuxStore;
13550
+ const childLarkDataRoot = larkCliChildDataRoot(canonStore);
13551
+ if (canonStore && childLarkDataRoot === null) {
13552
+ log(`[sandbox] lark-cli keystore ${canonStore} does not end in 'lark-cli' (symlinked leaf?) — `
13553
+ + `not pinning a custom LARKSUITE_CLI_DATA_DIR into the sandbox; the in-sandbox lark-cli will `
13554
+ + `use the default store. The real keystore is still denied/carve-out'd by policy (no leak).`);
13555
+ }
13291
13556
  const sbx = prepareDirectSandbox({
13292
13557
  sessionId: cfg.sessionId,
13293
13558
  dataDir,
@@ -13298,6 +13563,7 @@ async function spawnCli(cfg, opts = {}) {
13298
13563
  cliArgs: args,
13299
13564
  trustedBotmuxCommandPaths: [defaultGatewayEntry().command],
13300
13565
  mcpGatewaySocketPath: sessionMcpGatewayHost?.socketPath,
13566
+ larkCliDataDir: childLarkDataRoot,
13301
13567
  });
13302
13568
  if (!sbx) {
13303
13569
  // FAIL-SAFE: never silently run unsandboxed.
@@ -13839,6 +14105,13 @@ async function spawnCli(cfg, opts = {}) {
13839
14105
  // can verify they were spawned inside a botmux session by walking the
13840
14106
  // process tree and looking for a matching pid file in this directory.
13841
14107
  const cliPid = backend.getChildPid?.();
14108
+ if (cfg.existingAppServerEndpoint !== undefined) {
14109
+ // The local `codex --remote` client does not own the selected rollout fd;
14110
+ // writeInput must accept only the explicitly bound remote thread in the
14111
+ // shared history.jsonl instead of applying its normal local-PID ownership
14112
+ // filter (which would reject the correct App Server submission).
14113
+ backend.expectedCodexSessionId = cfg.cliSessionId;
14114
+ }
13842
14115
  publishLocalProcessAttestation(cliPid ?? undefined);
13843
14116
  if (cliPid && process.env.SESSION_DATA_DIR) {
13844
14117
  const markersDir = join(process.env.SESSION_DATA_DIR, '.botmux-cli-pids');
@@ -14042,10 +14315,21 @@ async function spawnCli(cfg, opts = {}) {
14042
14315
  hermesBridgeAttach(effectiveResume ? 'baseline-existing' : 'fresh-empty');
14043
14316
  }
14044
14317
  else if (cfg.cliId === 'codex') {
14318
+ if (cfg.existingAppServerEndpoint) {
14319
+ // A shared Codex App thread has two legitimate writers: the App and
14320
+ // BotMux's official `codex --remote` client. Enable local-turn
14321
+ // synthesis only from this share's attach boundary, so older thread
14322
+ // history is absorbed while future App-side turns are mirrored to Lark.
14323
+ codexAdoptStartMs = Date.now();
14324
+ codexBridgeQueue.setLocalTurns(true, codexAdoptStartMs);
14325
+ log('Codex bridge enabled App Server shared-turn forwarding');
14326
+ }
14045
14327
  if (effectiveCliSessionId) {
14046
14328
  const rolloutPath = findCodexRolloutBySessionId(effectiveCliSessionId);
14047
14329
  if (rolloutPath) {
14048
- codexBridgeAttach(rolloutPath, effectiveResume ? 'baseline-existing' : 'fresh-empty');
14330
+ codexBridgeAttach(rolloutPath, cfg.existingAppServerEndpoint
14331
+ ? 'split-live'
14332
+ : effectiveResume ? 'baseline-existing' : 'fresh-empty');
14049
14333
  }
14050
14334
  else {
14051
14335
  codexBridgePendingSessionId = effectiveCliSessionId;
@@ -14091,7 +14375,7 @@ async function spawnCli(cfg, opts = {}) {
14091
14375
  codexBridgeStartTimer();
14092
14376
  }
14093
14377
  }
14094
- else if (cfg.cliId === 'pi' || cfg.cliId === 'grok') {
14378
+ else if (cfg.cliId === 'pi' || cfg.cliId === 'grok' || cfg.cliId === 'oh-my-pi') {
14095
14379
  // File-backed: pin path when known (pi session id / grok --session-id
14096
14380
  // UUID), else arm the poller. Grok collision-fallback (dir already
14097
14381
  // exists → no --session-id → grok mints id) is recovered via writeInput
@@ -14245,7 +14529,8 @@ async function spawnCli(cfg, opts = {}) {
14245
14529
  // itself fail-opens on non-authoritative screens (ZMX), so a stale
14246
14530
  // `Working...` in ZMX history can never block a structured terminal.
14247
14531
  const busyGuardedIdle = evidenceSource === 'screen'
14248
- || (evidenceSource === 'external' && structuredBridgeIsPi());
14532
+ || (evidenceSource === 'external'
14533
+ && (structuredBridgeIsPi() || structuredBridgeIsOmp()));
14249
14534
  if (busyGuardedIdle && idleBackend
14250
14535
  && deferPromptReadyWhileBusy(`${cliName()} ${evidenceSource}-idle`, idleBackend))
14251
14536
  return;
@@ -14487,7 +14772,7 @@ async function spawnCli(cfg, opts = {}) {
14487
14772
  log('Suppressed claude_exit for intentional in-worker restart');
14488
14773
  }
14489
14774
  else {
14490
- send({ type: 'claude_exit', code, signal, logTail, canParkDiagnostic, turnId: exitedTurnId, dispatchAttempt: exitedDispatchAttempt });
14775
+ send({ type: 'claude_exit', code, signal, logTail, canParkDiagnostic, turnId: exitedTurnId, dispatchAttempt: exitedDispatchAttempt, ...(lastInitConfig?.cliId === 'codex-app' && code === CODEX_APP_ACTIVE_WRITER_EXIT_CODE ? { codexAppActiveWriter: true } : {}) });
14491
14776
  }
14492
14777
  });
14493
14778
  const isPersistentBackendReattach = actuallyReattachedPersistent;
@@ -14757,8 +15042,8 @@ function killCli(opts = {}) {
14757
15042
  appRunnerControlDecoder.reset();
14758
15043
  }
14759
15044
  async function restartCliProcess(reason, opts = {}) {
14760
- if (lastInitConfig?.adoptMode) {
14761
- log(`Restart ignored in adopt mode (${reason})`);
15045
+ if (lastInitConfig?.adoptMode || lastInitConfig?.existingAppServerEndpoint) {
15046
+ log(`Restart ignored in shared-adopt mode (${reason})`);
14762
15047
  return;
14763
15048
  }
14764
15049
  // Invalidate queued/deferred callbacks before async teardown begins. Waiting
@@ -14951,6 +15236,12 @@ function startWebServer(host, preferredPort) {
14951
15236
  res.end('Forbidden');
14952
15237
  return;
14953
15238
  }
15239
+ // #933 回归修复:平台注入的 Cookie/Role 会被中央前门剥掉(P1-6 / 内部 grant),
15240
+ // 此时 platformReadonly 判不出来;前门改用这个展示层提示头把「平台认证过的只读
15241
+ // 访客」这一事实带过来。只影响只读页渲染哪条横幅(SSO 登录引导 vs 纯只读提示),
15242
+ // 不参与任何授权判定——hasRead/hasWrite 在它之前就已定死。
15243
+ const platformReadonlyHint = !hasWrite
15244
+ && req.headers[TERMINAL_PLATFORM_READONLY_HINT_HEADER] !== undefined;
14954
15245
  const loginHdr = req.headers['x-botmux-login-url'];
14955
15246
  // The central front proxy only injects X-Botmux-Login-Url on the SPA 401
14956
15247
  // path, never on `/s/` terminal requests — so a read-only web terminal
@@ -14991,7 +15282,7 @@ function startWebServer(host, preferredPort) {
14991
15282
  || effectiveBackendType === 'tmux'
14992
15283
  || effectiveBackendType === 'zellij';
14993
15284
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
14994
- res.end(getTerminalHtml(hasWrite, platformReadonly, loginUrl, forceRemoteScroll, localTerminalBackend, allowReadOnlyRemoteScroll));
15285
+ res.end(getTerminalHtml(hasWrite, platformReadonly || platformReadonlyHint, loginUrl, forceRemoteScroll, localTerminalBackend, allowReadOnlyRemoteScroll));
14995
15286
  });
14996
15287
  wss = new WebSocketServer({
14997
15288
  server: httpServer,
@@ -15047,6 +15338,26 @@ function startWebServer(host, preferredPort) {
15047
15338
  if (hasWrite)
15048
15339
  authedClients.add(ws);
15049
15340
  log(`WS client connected (total: ${wsClients.size}, write: ${hasWrite})`);
15341
+ // The page only knows the verdict of its own HTTP GET. This upgrade was a
15342
+ // SECOND, independent authorization — and an iOS WebView does not send
15343
+ // cookies on it, so the very same page can be judged writable over HTTP
15344
+ // and read-only here. Tell the page what THIS socket actually got: it
15345
+ // stops forwarding input it knows will be dropped, and reports the truth
15346
+ // up to whatever embeds it (the Workbench terminal pane reads it to label
15347
+ // the channel).
15348
+ //
15349
+ // It used to ride the PTY byte stream as an OSC sequence, which the page
15350
+ // then scanned for on EVERY frame — so any process running inside a
15351
+ // read-only terminal could print the same bytes and flip the page's own
15352
+ // write verdict (or DoS itself into read-only). It is now an out-of-band
15353
+ // control frame that MUST be the first message on this socket, sent in the
15354
+ // same synchronous tick that registered it: the page only ever decodes
15355
+ // frame #1 of a connection as control, so PTY output can never be mistaken
15356
+ // for it. Nothing awaits between `wsClients.add` above and this send.
15357
+ try {
15358
+ ws.send(terminalWriteFrame(hasWrite));
15359
+ }
15360
+ catch { /* already closing */ }
15050
15361
  // A signed Dashboard grant is fixed-expiry — for READ scope as much as
15051
15362
  // for write (P1-5). Even if the central proxy's socket invalidation is
15052
15363
  // delayed or bypassed, the worker independently removes any granted
@@ -15502,6 +15813,18 @@ ${loginUrl ? `<a id="login-banner" href="${loginUrl}" target="_top" rel="noopene
15502
15813
  var isTouch='ontouchstart'in window||navigator.maxTouchPoints>0;
15503
15814
  if(isTouch){document.body.classList.add('touch');}
15504
15815
  var hasToken=${hasWrite};
15816
+ // hasToken 是**这一次 HTTP GET** 的判定,只当初值。真正说了算的是下面这条:worker 在
15817
+ // WebSocket 握手完成时把「这条连接实际拿到什么权限」发下来(OSC 1989 write),落成
15818
+ // wsHasWrite。两者会不一致——iOS WebView 的 WS 升级不带 Cookie,页面 HTTP 判可写、
15819
+ // WS 却只读,照抄 hasToken 就会让人对着一个不收字的终端打字。null = 还没连上。
15820
+ var wsHasWrite=null;
15821
+ // 「这条连接拿到什么权限」的解码器 —— 与 worker 侧同一份实现,原样嵌进来(这段页面
15822
+ // 代码住在模板字符串里,import 不进来,所以只能一份源码两处跑)。
15823
+ // 规则:只在**本连接的第一帧**上尝试,且要求整帧精确匹配。PTY 里跑的进程打出一模
15824
+ // 一样的字节也翻不动权限——它永远不可能是第一帧。
15825
+ var _wbDecodeWriteFrame=${decodeTerminalWriteFrameSource};
15826
+ // 本连接还没消费过首帧。每次 connect() 重置:上一条连接的结论套不到新连接上。
15827
+ var _wbFirstFrame=true;
15505
15828
  var platformReadonly=${platformReadonly};
15506
15829
  var remoteScroll=${forceRemoteScroll};
15507
15830
  var localTerminalBackend=${localTerminalBackend};
@@ -15579,6 +15902,39 @@ function _wbApplyAutoFontSize(){
15579
15902
  return true;
15580
15903
  }
15581
15904
  fit.fit();
15905
+ // 这条通道到底能不能写,以**已经建立的 WS** 为准(见页面顶部 wsHasWrite 那段注释)。
15906
+ // 结论一到手就做两件事:① 页面自己收起输入,别把注定被丢掉的字发出去;② 上抛给嵌入
15907
+ // 方(工作台终端面板据此显示「可输入 / 只读」,而不是照抄 HTTP 的 hasToken)。
15908
+ // 上抛的目标 origin 绝不用星号:优先取 location.ancestorOrigins(WebKit / Blink 都有,
15909
+ // iOS WebView 正是 WebKit),取不到就退回「父页给我们发外观消息时带的那个 origin」;
15910
+ // 两者都没有就不发——宁可不上抛,也不广播出去。
15911
+ // 注意:这一整段在 worker.ts 的模板字符串里,注释和代码都不能出现反引号,
15912
+ // 也不能出现「美元号 + 左花括号」的插值起始序列,否则会把模板字符串截断。
15913
+ var _wbParentOrigin=null;
15914
+ try{
15915
+ var _wbAo=window.location.ancestorOrigins;
15916
+ if(_wbAo&&_wbAo.length&&_wbAo[0]&&_wbAo[0]!=='null')_wbParentOrigin=_wbAo[0];
15917
+ }catch(_e){}
15918
+ function _wbPostWrite(){
15919
+ if(window.parent===window||!_wbParentOrigin)return;
15920
+ // wsHasWrite===null 也要上抛:那是「这条连接退回未知」的一次明确通知(重连开始时
15921
+ // 发),嵌入方据此把上一条连接的结论清掉,而不是继续显示一个过期的判定。
15922
+ try{window.parent.postMessage({type:'botmux:wb-terminal-write',write:wsHasWrite},_wbParentOrigin)}catch(_e){}
15923
+ }
15924
+ function _wbSetWsWrite(v){
15925
+ var changed=wsHasWrite!==v;
15926
+ wsHasWrite=v;
15927
+ if(changed){
15928
+ var _wb=document.getElementById('readonly-banner');
15929
+ // 页面以为自己可写、这条连接其实只读:把只读横幅亮出来。反过来(连上后确认可写)
15930
+ // 就把它收掉,别留一条与事实相反的提示。null(未知)两边都不动:还没有依据。
15931
+ if(_wb&&!platformReadonly){
15932
+ if(v===false&&hasToken)_wb.classList.add('show');
15933
+ else if(v===true)_wb.classList.remove('show');
15934
+ }
15935
+ }
15936
+ _wbPostWrite();
15937
+ }
15582
15938
  // 工作台下发的终端外观(消息类型 botmux:wb-appearance)。终端画布住在这个跨文档
15583
15939
  // iframe 里,父页换 class / 换 CSS 变量都传不进来,配色只能靠 postMessage 递过来。
15584
15940
  // 注意:这一整段在 worker.ts 的模板字符串里,注释和代码都不能出现反引号,
@@ -15602,17 +15958,22 @@ window.addEventListener('message',function(_ev){
15602
15958
  if(_ev.source===window||_ev.source!==window.parent)return;
15603
15959
  var _d=_ev.data;
15604
15960
  if(!_d||_d.type!=='botmux:wb-appearance')return;
15961
+ // 这一条消息本身就证明了父页的 origin(来源已经校验过是 window.parent)。
15962
+ // ancestorOrigins 拿不到时(Firefox)靠它补上,并把攒着的写权限结论补发一次。
15963
+ if(!_wbParentOrigin&&_ev.origin&&_ev.origin!=='null'){_wbParentOrigin=_ev.origin;_wbPostWrite();}
15605
15964
  var _theme=_wbSanitizeTheme(_d.theme);
15606
15965
  if(!_theme)return;
15607
15966
  try{
15608
15967
  term.options.theme=_theme;
15609
- // 阅读风是大行距 1.3;经典保持 xterm 默认的 1,「经典 = 原样」的一部分。
15968
+ // 阅读风走 1.15 的行距;经典保持 xterm 默认的 1,「经典 = 原样」的一部分。
15610
15969
  // 经典那套色值与上面写死的字面量逐色相同,所以没开阅读风的用户零变化。
15611
15970
  // 这两个数字的唯一出处是 WORKBENCH_TERM_LINE_HEIGHTS(agent-workbench-appearance.ts),
15612
15971
  // 接缝测试按那张表比对这一行的字面量:改一处必须两处一起改。
15613
15972
  // 曾经是 1.55,单元格高约 24.5px,CLI 底部那块固定 8 行的 chrome(提示 + 输入框 +
15614
- // 状态条)就要占掉约 196px —— 一屏四分之一全是 chrome,正文被挤走。
15615
- term.options.lineHeight=_d.termStyle==='reader'?1.3:1;
15973
+ // 状态条)就要占掉约 196px —— 一屏四分之一全是 chrome,正文被挤走,所以先降到 1.3。
15974
+ // 1.3 对中文密集的 TUI 内容还是偏松:汉字撑满 em 框,行间空隙观感接近隔行,于是
15975
+ // 再收一档到 1.15(单元格高约 18.2px),比经典松一成半,读起来才连成一段。
15976
+ term.options.lineHeight=_d.termStyle==='reader'?1.15:1;
15616
15977
  // 行距变了可视行数就变,必须复算一次。几何契约不动:只有画布内重绘。
15617
15978
  fit.fit();
15618
15979
  }catch(_e){}
@@ -15798,10 +16159,17 @@ function _sendInput(d){if(ws_&&ws_.readyState===1)ws_.send(JSON.stringify({type:
15798
16159
  var _MOTION_RE=/^(?:\\x1b\\[<(?:35|39|43|47|51|55|59|63);\\d+;\\d+[Mm])+$/;
15799
16160
  var _motionPend=null,_motionT=0;
15800
16161
  term.onData(function(d){
15801
- if(!hasToken){
16162
+ // 只有已建立的 WS 明确回报「可写」(wsHasWrite===true) 才放行输入。null(还没确认,
16163
+ // 含重连中)和 false(这条连接判成只读)一律不发——桌面也要等首帧。照抄 hasToken
16164
+ // (这一次 HTTP GET 的判定)会在 WS 不带 Cookie 的 iOS WebView 上让人对着一个不收字
16165
+ // 的终端打字,按键石沉大海最难自查(第 17 点)。
16166
+ if(wsHasWrite!==true){
15802
16167
  // Mouse escape sequences are input too: a TUI can bind clicks or wheel
15803
16168
  // events to actions. View links never forward terminal bytes.
15804
- _showReadonlyToast();return;
16169
+ // 已经确定只读(这条连接判 false,或 HTTP GET 本来就没给写)才弹只读提示;null 是
16170
+ // 还没连上的短暂过渡,此时 ws 多半也没 OPEN,静默丢弃即可,别闪一条与事实相反的只读。
16171
+ if(!hasToken||wsHasWrite===false)_showReadonlyToast();
16172
+ return;
15805
16173
  }
15806
16174
  if(_MOTION_RE.test(d)){
15807
16175
  // Trailing throttle: keep only the LATEST motion, flush every 90ms. Hover
@@ -15862,6 +16230,10 @@ if(typeof ResizeObserver!=='undefined'){
15862
16230
  var proto=location.protocol==='https:'?'wss':'ws';
15863
16231
  var ws=new WebSocket(proto+'://'+location.host+base+'/'+location.search);
15864
16232
  ws_=ws;ws.binaryType='arraybuffer';
16233
+ // 新连接 = 新的一次鉴权。上一条连接拿到的权限说明不了这一条,先退回未知(并把这
16234
+ // 个「未知」上抛给嵌入方),等这条连接自己的首帧说话。
16235
+ _wbFirstFrame=true;
16236
+ _wbSetWsWrite(null);
15865
16237
  // Force a resize on every (re)connect: clear the dedup memory first. On
15866
16238
  // reconnect the browser grid is usually unchanged, so without this the
15867
16239
  // dedup in sendResize() would suppress the resize — but a reconnect often
@@ -15871,6 +16243,14 @@ if(typeof ResizeObserver!=='undefined'){
15871
16243
  // (status-line update bleeds into the line below). Always re-assert size.
15872
16244
  ws.onopen=function(){el.textContent='connected';el.className='ok';_lastC=_lastR=0;sendResize()};
15873
16245
  ws.onmessage=function(e){
16246
+ // 带外控制帧只可能是**本连接的第一帧**,而且必须整帧精确匹配(见页面顶部
16247
+ // _wbDecodeWriteFrame 那段注释)。首帧一旦消费掉,这条连接上之后的每一个字节
16248
+ // 就都只是 PTY 输出 —— 里面的进程再怎么打印控制帧的字面量也翻不动权限。
16249
+ if(_wbFirstFrame){
16250
+ _wbFirstFrame=false;
16251
+ var _ctl=_wbDecodeWriteFrame(e.data,true);
16252
+ if(_ctl!==null){_wbSetWsWrite(_ctl);return;}
16253
+ }
15874
16254
  var data=typeof e.data==='string'?e.data:new TextDecoder().decode(e.data);
15875
16255
  // Snapshot-aware Herdr history replaces the buffer instead of appending a
15876
16256
  // mostly-overlapping full screen. Preserve the reader's anchor when older
@@ -15911,7 +16291,15 @@ if(typeof ResizeObserver!=='undefined'){
15911
16291
  if(m){try{_clipBuf=new TextDecoder().decode(Uint8Array.from(atob(m[1]),function(c){return c.charCodeAt(0)}));_doCopy(_clipBuf);_showCopied()}catch(ex){}}
15912
16292
  term.write(data,_settleInitialBottom);
15913
16293
  };
15914
- ws.onclose=function(){ws_=null;el.textContent='disconnected';el.className='err';setTimeout(connect,2000)};
16294
+ ws.onclose=function(){
16295
+ ws_=null;el.textContent='disconnected';el.className='err';
16296
+ // 关闭当下就把这条连接的写权限退回未知:先复位首帧标志(重连后要等新首帧才恢复
16297
+ // 结论),再 _wbSetWsWrite(null) —— 它同时收起输入(term.onData/toolbar/_fwdScroll
16298
+ // 的门禁都以 wsHasWrite===true 为准)并向嵌入方上抛 write:null。不这样做的话,断线
16299
+ // 到 2 秒后重连的空窗里,页面仍以上一条连接的旧判定放行输入、父页也还显示旧的可写。
16300
+ _wbFirstFrame=true;_wbSetWsWrite(null);
16301
+ setTimeout(connect,2000);
16302
+ };
15915
16303
  ws.onerror=function(){ws.close()};
15916
16304
  })();
15917
16305
 
@@ -15982,6 +16370,12 @@ function _cellAt(clientX,clientY){
15982
16370
  }
15983
16371
  function _fwdScroll(px,coord){
15984
16372
  if((!hasToken&&!readOnlyRemoteScroll)||!ws_||ws_.readyState!==1||!px)return;
16373
+ // 写链路(hasToken)转发的是 type:'input',与 term.onData / toolbar 同一条判据:只有已
16374
+ // 建立的 WS 明确回报可写(wsHasWrite===true)才放行;null(未确认,含重连中)与 false
16375
+ // (这条连接只读)一律不发——照抄 hasToken 会在 WS 不带 Cookie 时把输入打进一个原地
16376
+ // 丢字的终端(第 17 点)。只读远程滚动(hasToken=false)发的是 type:'scroll',不是输入,
16377
+ // 不受这道门约束(服务端另有校验)。
16378
+ if(hasToken&&wsHasWrite!==true)return;
15985
16379
  coord=coord||(((term.cols>>1)+1)+';'+((term.rows>>1)+1)); // never (1,1)
15986
16380
  var dir=px<0?-1:1;
15987
16381
  if(_scrollBurstDir&&dir!==_scrollBurstDir){_scrollAccum=0;_scrollBurstTicks=0;}
@@ -16226,6 +16620,9 @@ if(isTouch&&hasToken){
16226
16620
  var press=null;
16227
16621
  function fire(){
16228
16622
  if(!ws_||ws_.readyState!==1)return;
16623
+ // 与 term.onData 同一条判据:只有 WS 明确回报可写(wsHasWrite===true)才发;null
16624
+ // (还没确认)与 false(只读)都不发,桌面也等首帧(第 17 点)。
16625
+ if(wsHasWrite!==true){if(wsHasWrite===false)_showReadonlyToast();return;}
16229
16626
  var k=km[btn.getAttribute('data-k')];
16230
16627
  if(k)ws_.send(JSON.stringify({type:'input',data:k}));
16231
16628
  }
@@ -16598,6 +16995,10 @@ process.on('message', async (raw) => {
16598
16995
  if (msg.locale === 'zh' || msg.locale === 'en') {
16599
16996
  setDefaultLocale(msg.locale);
16600
16997
  }
16998
+ // Wire user prompt-key overrides into t() so worker-side prompt building
16999
+ // (claude-family `--append-system-prompt` via buildBotmuxSystemPromptText,
17000
+ // mojo catalog, etc.) honours customizations. Idempotent per worker.
17001
+ registerPromptOverrideResolver();
16601
17002
  // Scope session store to this bot's per-bot file.
16602
17003
  // Slice C0: workflow-spawned workers (BOTMUX_WORKFLOW=1) skip this —
16603
17004
  // their `sessionId` is synthetic (`wf-<runId>-<activityId>-...`) and
@@ -17048,12 +17449,23 @@ process.on('message', async (raw) => {
17048
17449
  // shell。bareShellCheckInProgress 覆盖“检查进行中”、bareShellLaunchBlocked
17049
17450
  // 覆盖“仍停在裸 shell 的安全 hold”两种状态,一并入队;若该进程随后出现
17050
17451
  // 真实 PTY prompt 且 leaf 已变为非 shell,markPromptReady 会恢复排空。
17452
+ // Hook-review menus are another direct-write hazard: raw_input normally
17453
+ // preserves busy delivery, but its Enter/arrow sequence must never drive
17454
+ // the menu's current selection.
17455
+ refreshHookReviewInputHold(lastAnalyzerSnapshot || renderer?.rawSnapshot() || '');
17051
17456
  if (cliRestartInProgress || rawInputRestartGate || sessionRenameInFlight()
17052
17457
  || shouldHoldCodexRunnerInput(codexRunnerFreshness)
17053
17458
  || injectionFlushing || shouldDeferUserFlush(pendingInjections)
17054
- || bareShellCheckInProgress || bareShellLaunchBlocked) {
17459
+ || bareShellCheckInProgress || bareShellLaunchBlocked
17460
+ || hookReviewInputHold) {
17055
17461
  freshnessInputQueue.enqueueRaw(msg);
17056
- log(`Deferred passthrough slash command until CLI input gate settles: ${msg.content}`);
17462
+ if (hookReviewInputHold) {
17463
+ notifyHookReviewInputHold();
17464
+ log(`Deferred passthrough slash command while Hook review is visible: ${msg.content}`);
17465
+ }
17466
+ else {
17467
+ log(`Deferred passthrough slash command until CLI input gate settles: ${msg.content}`);
17468
+ }
17057
17469
  }
17058
17470
  else {
17059
17471
  await deliverRawInput(msg);
@@ -17446,6 +17858,15 @@ process.on('message', async (raw) => {
17446
17858
  log('Late ready signal after timeout fallback — marking prompt ready now');
17447
17859
  markPromptReady();
17448
17860
  }
17861
+ // CLI 启动确证(SessionStart hook 已跑):复位连续重启计数——resume 目标
17862
+ // 显然存在且加载成功。这和 prompt 侧复位是同一语义,但更早:2026-08-20
17863
+ // 事故里 resume 后的 CLI 已 ready 二十多秒、还没等到 prompt 复位就被共享
17864
+ // server 抖动误杀,第二次重启因此被 tier-2 强制 FRESH 白丢上下文。到过
17865
+ // ready 的 spawn 崩溃不是「resume 目标不存在」问题,下一轮应有全新预算。
17866
+ if (consecutiveInWorkerRestarts > 0) {
17867
+ log(`SessionStart ready — resetting consecutive restart count (was ${consecutiveInWorkerRestarts})`);
17868
+ consecutiveInWorkerRestarts = 0;
17869
+ }
17449
17870
  if (msg.requestId) {
17450
17871
  send({ type: 'session_ready_ack', requestId: msg.requestId });
17451
17872
  }
@@ -17946,10 +18367,29 @@ function cleanup() {
17946
18367
  // cleanup retires the lease. SIGKILL residue is reclaimed by the next worker.
17947
18368
  releaseCodexAppPosixOwnerLease();
17948
18369
  }
17949
- process.on('SIGTERM', () => { stopScreenshotLoop(); killCli(); cleanup(); process.exit(0); });
17950
- process.on('SIGINT', () => { stopScreenshotLoop(); killCli(); cleanup(); process.exit(0); });
18370
+ /**
18371
+ * A shared existing-App-Server session owns this worker and its bmx-* remote
18372
+ * TUI, but never owns the App Server/thread behind that TUI. On daemon
18373
+ * restart, preserve the remote TUI so the replacement daemon can reattach it;
18374
+ * only retire this worker's HTTP/WebSocket observers. Ordinary managed sessions
18375
+ * retain the historical killCli shutdown path.
18376
+ */
18377
+ function shutdownWorkerForParentExit(reason) {
18378
+ stopScreenshotLoop();
18379
+ if (lastInitConfig?.existingAppServerEndpoint) {
18380
+ log(`Preserving existing-App-Server remote TUI during ${reason}`);
18381
+ cleanup();
18382
+ process.exit(0);
18383
+ return;
18384
+ }
18385
+ killCli();
18386
+ cleanup();
18387
+ process.exit(0);
18388
+ }
18389
+ process.on('SIGTERM', () => shutdownWorkerForParentExit('SIGTERM'));
18390
+ process.on('SIGINT', () => shutdownWorkerForParentExit('SIGINT'));
17951
18391
  // If parent daemon dies, IPC channel closes — clean up
17952
- process.on('disconnect', () => { log('Daemon disconnected'); stopScreenshotLoop(); killCli(); cleanup(); process.exit(0); });
18392
+ process.on('disconnect', () => { log('Daemon disconnected'); shutdownWorkerForParentExit('IPC disconnect'); });
17953
18393
  // Watchdog: belt-and-braces parent-death detection. SIGTERM and 'disconnect'
17954
18394
  // should both reach us when the daemon dies, but if main thread is stuck in
17955
18395
  // a sync path V8 silently buffers the signal and we end up as a ppid=1
@@ -17966,16 +18406,12 @@ setInterval(() => {
17966
18406
  const currentPpid = process.ppid;
17967
18407
  if (currentPpid !== ORIGINAL_PARENT_PID || currentPpid === 1) {
17968
18408
  log(`Watchdog: parent pid changed (${ORIGINAL_PARENT_PID} → ${currentPpid}) — daemon died, exiting`);
17969
- stopScreenshotLoop();
17970
18409
  try {
17971
- killCli();
18410
+ shutdownWorkerForParentExit('parent watchdog');
17972
18411
  }
17973
- catch { /* best-effort */ }
17974
- try {
17975
- cleanup();
18412
+ catch {
18413
+ process.exit(0); /* best-effort exit if teardown itself faults */
17976
18414
  }
17977
- catch { /* best-effort */ }
17978
- process.exit(0);
17979
18415
  }
17980
18416
  }, 30_000).unref();
17981
18417
  // ─── Sandbox crash-time teardown ─────────────────────────────────────────────