botmux 2.107.0 → 2.108.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 (515) hide show
  1. package/README.en.md +17 -14
  2. package/README.md +19 -14
  3. package/dist/adapters/backend/sandbox.d.ts +3 -1
  4. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  5. package/dist/adapters/backend/sandbox.js +19 -3
  6. package/dist/adapters/backend/sandbox.js.map +1 -1
  7. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  8. package/dist/adapters/backend/tmux-backend.js +16 -1
  9. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  10. package/dist/adapters/backend/tmux-pipe-backend.d.ts +24 -15
  11. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/tmux-pipe-backend.js +75 -43
  13. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  14. package/dist/adapters/cli/genius.d.ts.map +1 -1
  15. package/dist/adapters/cli/genius.js +9 -15
  16. package/dist/adapters/cli/genius.js.map +1 -1
  17. package/dist/adapters/cli/grok.d.ts +73 -0
  18. package/dist/adapters/cli/grok.d.ts.map +1 -0
  19. package/dist/adapters/cli/grok.js +298 -0
  20. package/dist/adapters/cli/grok.js.map +1 -0
  21. package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
  22. package/dist/adapters/cli/oh-my-pi.js +133 -12
  23. package/dist/adapters/cli/oh-my-pi.js.map +1 -1
  24. package/dist/adapters/cli/registry.d.ts +2 -1
  25. package/dist/adapters/cli/registry.d.ts.map +1 -1
  26. package/dist/adapters/cli/registry.js +3 -1
  27. package/dist/adapters/cli/registry.js.map +1 -1
  28. package/dist/adapters/cli/shared-hints.d.ts +8 -4
  29. package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
  30. package/dist/adapters/cli/shared-hints.js +8 -4
  31. package/dist/adapters/cli/shared-hints.js.map +1 -1
  32. package/dist/adapters/cli/types.d.ts +18 -16
  33. package/dist/adapters/cli/types.d.ts.map +1 -1
  34. package/dist/adapters/hook-installer.d.ts +5 -4
  35. package/dist/adapters/hook-installer.d.ts.map +1 -1
  36. package/dist/adapters/hook-installer.js +42 -0
  37. package/dist/adapters/hook-installer.js.map +1 -1
  38. package/dist/cli/send-dispatch.d.ts +21 -0
  39. package/dist/cli/send-dispatch.d.ts.map +1 -1
  40. package/dist/cli/send-dispatch.js +159 -0
  41. package/dist/cli/send-dispatch.js.map +1 -1
  42. package/dist/cli/session-list-liveness.d.ts +22 -0
  43. package/dist/cli/session-list-liveness.d.ts.map +1 -0
  44. package/dist/cli/session-list-liveness.js +22 -0
  45. package/dist/cli/session-list-liveness.js.map +1 -0
  46. package/dist/cli.d.ts.map +1 -1
  47. package/dist/cli.js +182 -33
  48. package/dist/cli.js.map +1 -1
  49. package/dist/core/ask-types.d.ts +4 -0
  50. package/dist/core/ask-types.d.ts.map +1 -1
  51. package/dist/core/ask-types.js +10 -0
  52. package/dist/core/ask-types.js.map +1 -1
  53. package/dist/core/cost-calculator.d.ts.map +1 -1
  54. package/dist/core/cost-calculator.js +35 -13
  55. package/dist/core/cost-calculator.js.map +1 -1
  56. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  57. package/dist/core/dashboard-ipc-server.js +89 -16
  58. package/dist/core/dashboard-ipc-server.js.map +1 -1
  59. package/dist/core/dashboard-rows.d.ts.map +1 -1
  60. package/dist/core/dashboard-rows.js +6 -2
  61. package/dist/core/dashboard-rows.js.map +1 -1
  62. package/dist/core/dashboard-url.d.ts +6 -5
  63. package/dist/core/dashboard-url.d.ts.map +1 -1
  64. package/dist/core/dashboard-url.js +11 -8
  65. package/dist/core/dashboard-url.js.map +1 -1
  66. package/dist/core/maintenance.d.ts +11 -8
  67. package/dist/core/maintenance.d.ts.map +1 -1
  68. package/dist/core/maintenance.js +45 -20
  69. package/dist/core/maintenance.js.map +1 -1
  70. package/dist/core/per-bot-env.d.ts.map +1 -1
  71. package/dist/core/per-bot-env.js +4 -0
  72. package/dist/core/per-bot-env.js.map +1 -1
  73. package/dist/core/session-discovery.d.ts.map +1 -1
  74. package/dist/core/session-discovery.js +4 -0
  75. package/dist/core/session-discovery.js.map +1 -1
  76. package/dist/core/skills/cli-admin-command.d.ts.map +1 -1
  77. package/dist/core/skills/cli-admin-command.js +8 -2
  78. package/dist/core/skills/cli-admin-command.js.map +1 -1
  79. package/dist/core/skills/im-command.d.ts.map +1 -1
  80. package/dist/core/skills/im-command.js +3 -1
  81. package/dist/core/skills/im-command.js.map +1 -1
  82. package/dist/core/skills/sources.d.ts +33 -1
  83. package/dist/core/skills/sources.d.ts.map +1 -1
  84. package/dist/core/skills/sources.js +97 -0
  85. package/dist/core/skills/sources.js.map +1 -1
  86. package/dist/core/skills/types.d.ts +8 -0
  87. package/dist/core/skills/types.d.ts.map +1 -1
  88. package/dist/core/terminal-url.d.ts.map +1 -1
  89. package/dist/core/terminal-url.js +12 -2
  90. package/dist/core/terminal-url.js.map +1 -1
  91. package/dist/core/terminal-write-auth.d.ts +34 -0
  92. package/dist/core/terminal-write-auth.d.ts.map +1 -0
  93. package/dist/core/terminal-write-auth.js +76 -0
  94. package/dist/core/terminal-write-auth.js.map +1 -0
  95. package/dist/core/tmux-recovery.d.ts +11 -0
  96. package/dist/core/tmux-recovery.d.ts.map +1 -0
  97. package/dist/core/tmux-recovery.js +19 -0
  98. package/dist/core/tmux-recovery.js.map +1 -0
  99. package/dist/core/types.d.ts +10 -4
  100. package/dist/core/types.d.ts.map +1 -1
  101. package/dist/core/types.js.map +1 -1
  102. package/dist/core/update-check.d.ts +2 -2
  103. package/dist/core/update-check.js +2 -2
  104. package/dist/core/worker-pool.d.ts +3 -0
  105. package/dist/core/worker-pool.d.ts.map +1 -1
  106. package/dist/core/worker-pool.js +70 -3
  107. package/dist/core/worker-pool.js.map +1 -1
  108. package/dist/daemon.d.ts.map +1 -1
  109. package/dist/daemon.js +15 -8
  110. package/dist/daemon.js.map +1 -1
  111. package/dist/dashboard/auth.d.ts +1 -1
  112. package/dist/dashboard/auth.d.ts.map +1 -1
  113. package/dist/dashboard/auth.js +7 -2
  114. package/dist/dashboard/auth.js.map +1 -1
  115. package/dist/dashboard/bot-payload.d.ts +3 -0
  116. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  117. package/dist/dashboard/bot-payload.js +3 -0
  118. package/dist/dashboard/bot-payload.js.map +1 -1
  119. package/dist/dashboard/connector-api.d.ts.map +1 -1
  120. package/dist/dashboard/connector-api.js +39 -13
  121. package/dist/dashboard/connector-api.js.map +1 -1
  122. package/dist/dashboard/history-senders.d.ts +37 -0
  123. package/dist/dashboard/history-senders.d.ts.map +1 -0
  124. package/dist/dashboard/history-senders.js +60 -0
  125. package/dist/dashboard/history-senders.js.map +1 -0
  126. package/dist/dashboard/roles-batch.d.ts +31 -0
  127. package/dist/dashboard/roles-batch.d.ts.map +1 -0
  128. package/dist/dashboard/roles-batch.js +87 -0
  129. package/dist/dashboard/roles-batch.js.map +1 -0
  130. package/dist/dashboard/settings-card-model.d.ts +4 -2
  131. package/dist/dashboard/settings-card-model.d.ts.map +1 -1
  132. package/dist/dashboard/settings-card-model.js +13 -5
  133. package/dist/dashboard/settings-card-model.js.map +1 -1
  134. package/dist/dashboard/settings-write-applier.d.ts +4 -1
  135. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  136. package/dist/dashboard/settings-write-applier.js +6 -1
  137. package/dist/dashboard/settings-write-applier.js.map +1 -1
  138. package/dist/dashboard/skill-install-request.d.ts +4 -0
  139. package/dist/dashboard/skill-install-request.d.ts.map +1 -1
  140. package/dist/dashboard/skill-install-request.js +11 -1
  141. package/dist/dashboard/skill-install-request.js.map +1 -1
  142. package/dist/dashboard/trigger-api.d.ts +9 -1
  143. package/dist/dashboard/trigger-api.d.ts.map +1 -1
  144. package/dist/dashboard/trigger-api.js +48 -10
  145. package/dist/dashboard/trigger-api.js.map +1 -1
  146. package/dist/dashboard/web/app.d.ts.map +1 -1
  147. package/dist/dashboard/web/app.js +286 -271
  148. package/dist/dashboard/web/app.js.map +1 -1
  149. package/dist/dashboard/web/bot-defaults-page.d.ts +26 -0
  150. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  151. package/dist/dashboard/web/bot-defaults-page.js +1408 -12
  152. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  153. package/dist/dashboard/web/bot-defaults.d.ts +91 -4
  154. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  155. package/dist/dashboard/web/bot-defaults.js +43 -2141
  156. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  157. package/dist/dashboard/web/bot-onboarding.d.ts +6 -1
  158. package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
  159. package/dist/dashboard/web/bot-onboarding.js +261 -306
  160. package/dist/dashboard/web/bot-onboarding.js.map +1 -1
  161. package/dist/dashboard/web/connectors-page.d.ts +4 -0
  162. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  163. package/dist/dashboard/web/connectors-page.js +228 -48
  164. package/dist/dashboard/web/connectors-page.js.map +1 -1
  165. package/dist/dashboard/web/dashboard-api.d.ts +9 -0
  166. package/dist/dashboard/web/dashboard-api.d.ts.map +1 -0
  167. package/dist/dashboard/web/dashboard-api.js +18 -0
  168. package/dist/dashboard/web/dashboard-api.js.map +1 -0
  169. package/dist/dashboard/web/dashboard-components.d.ts +92 -0
  170. package/dist/dashboard/web/dashboard-components.d.ts.map +1 -0
  171. package/dist/dashboard/web/dashboard-components.js +250 -0
  172. package/dist/dashboard/web/dashboard-components.js.map +1 -0
  173. package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
  174. package/dist/dashboard/web/dashboard-routes.js +30 -183
  175. package/dist/dashboard/web/dashboard-routes.js.map +1 -1
  176. package/dist/dashboard/web/floating-scrollbars.d.ts +2 -0
  177. package/dist/dashboard/web/floating-scrollbars.d.ts.map +1 -0
  178. package/dist/dashboard/web/floating-scrollbars.js +264 -0
  179. package/dist/dashboard/web/floating-scrollbars.js.map +1 -0
  180. package/dist/dashboard/web/groups-api.d.ts +36 -0
  181. package/dist/dashboard/web/groups-api.d.ts.map +1 -0
  182. package/dist/dashboard/web/groups-api.js +44 -0
  183. package/dist/dashboard/web/groups-api.js.map +1 -0
  184. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  185. package/dist/dashboard/web/groups-page.js +746 -15
  186. package/dist/dashboard/web/groups-page.js.map +1 -1
  187. package/dist/dashboard/web/groups.d.ts +51 -18
  188. package/dist/dashboard/web/groups.d.ts.map +1 -1
  189. package/dist/dashboard/web/groups.js +145 -1096
  190. package/dist/dashboard/web/groups.js.map +1 -1
  191. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  192. package/dist/dashboard/web/i18n.js +545 -255
  193. package/dist/dashboard/web/i18n.js.map +1 -1
  194. package/dist/dashboard/web/insights-page.d.ts.map +1 -1
  195. package/dist/dashboard/web/insights-page.js +1047 -14
  196. package/dist/dashboard/web/insights-page.js.map +1 -1
  197. package/dist/dashboard/web/insights.d.ts +177 -7
  198. package/dist/dashboard/web/insights.d.ts.map +1 -1
  199. package/dist/dashboard/web/insights.js +187 -1926
  200. package/dist/dashboard/web/insights.js.map +1 -1
  201. package/dist/dashboard/web/legacy-workflow-page.d.ts +3 -0
  202. package/dist/dashboard/web/legacy-workflow-page.d.ts.map +1 -0
  203. package/dist/dashboard/web/legacy-workflow-page.js +717 -0
  204. package/dist/dashboard/web/legacy-workflow-page.js.map +1 -0
  205. package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
  206. package/dist/dashboard/web/monitor-room.js +10 -8
  207. package/dist/dashboard/web/monitor-room.js.map +1 -1
  208. package/dist/dashboard/web/monitoring-page.d.ts.map +1 -1
  209. package/dist/dashboard/web/monitoring-page.js +188 -14
  210. package/dist/dashboard/web/monitoring-page.js.map +1 -1
  211. package/dist/dashboard/web/office-page.d.ts.map +1 -1
  212. package/dist/dashboard/web/office-page.js +11 -23
  213. package/dist/dashboard/web/office-page.js.map +1 -1
  214. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  215. package/dist/dashboard/web/overview-page.js +141 -147
  216. package/dist/dashboard/web/overview-page.js.map +1 -1
  217. package/dist/dashboard/web/overview.d.ts +0 -1
  218. package/dist/dashboard/web/overview.d.ts.map +1 -1
  219. package/dist/dashboard/web/overview.js +3 -275
  220. package/dist/dashboard/web/overview.js.map +1 -1
  221. package/dist/dashboard/web/preferences.d.ts +1 -6
  222. package/dist/dashboard/web/preferences.d.ts.map +1 -1
  223. package/dist/dashboard/web/preferences.js +0 -21
  224. package/dist/dashboard/web/preferences.js.map +1 -1
  225. package/dist/dashboard/web/role-batch.d.ts +9 -0
  226. package/dist/dashboard/web/role-batch.d.ts.map +1 -0
  227. package/dist/dashboard/web/role-batch.js +42 -0
  228. package/dist/dashboard/web/role-batch.js.map +1 -0
  229. package/dist/dashboard/web/roles-page.d.ts.map +1 -1
  230. package/dist/dashboard/web/roles-page.js +538 -13
  231. package/dist/dashboard/web/roles-page.js.map +1 -1
  232. package/dist/dashboard/web/roles.d.ts +98 -1
  233. package/dist/dashboard/web/roles.d.ts.map +1 -1
  234. package/dist/dashboard/web/roles.js +117 -903
  235. package/dist/dashboard/web/roles.js.map +1 -1
  236. package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
  237. package/dist/dashboard/web/schedules-page.js +82 -24
  238. package/dist/dashboard/web/schedules-page.js.map +1 -1
  239. package/dist/dashboard/web/sessions-kanban.d.ts +67 -0
  240. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -0
  241. package/dist/dashboard/web/sessions-kanban.js +481 -0
  242. package/dist/dashboard/web/sessions-kanban.js.map +1 -0
  243. package/dist/dashboard/web/sessions-page.d.ts +4 -0
  244. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  245. package/dist/dashboard/web/sessions-page.js +1633 -18
  246. package/dist/dashboard/web/sessions-page.js.map +1 -1
  247. package/dist/dashboard/web/sessions.d.ts +45 -4
  248. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  249. package/dist/dashboard/web/sessions.js +53 -2535
  250. package/dist/dashboard/web/sessions.js.map +1 -1
  251. package/dist/dashboard/web/settings-page.d.ts +0 -2
  252. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  253. package/dist/dashboard/web/settings-page.js +127 -117
  254. package/dist/dashboard/web/settings-page.js.map +1 -1
  255. package/dist/dashboard/web/skills-page.d.ts +10 -1
  256. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  257. package/dist/dashboard/web/skills-page.js +235 -93
  258. package/dist/dashboard/web/skills-page.js.map +1 -1
  259. package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
  260. package/dist/dashboard/web/team-federation-page.js +446 -28
  261. package/dist/dashboard/web/team-federation-page.js.map +1 -1
  262. package/dist/dashboard/web/team-federation.d.ts +91 -2
  263. package/dist/dashboard/web/team-federation.d.ts.map +1 -1
  264. package/dist/dashboard/web/team-federation.js +87 -474
  265. package/dist/dashboard/web/team-federation.js.map +1 -1
  266. package/dist/dashboard/web/theme-menu.d.ts +1 -0
  267. package/dist/dashboard/web/theme-menu.d.ts.map +1 -1
  268. package/dist/dashboard/web/theme-menu.js +79 -20
  269. package/dist/dashboard/web/theme-menu.js.map +1 -1
  270. package/dist/dashboard/web/ui.d.ts +1 -2
  271. package/dist/dashboard/web/ui.d.ts.map +1 -1
  272. package/dist/dashboard/web/ui.js +8 -20
  273. package/dist/dashboard/web/ui.js.map +1 -1
  274. package/dist/dashboard/web/v3-api.d.ts +16 -0
  275. package/dist/dashboard/web/v3-api.d.ts.map +1 -0
  276. package/dist/dashboard/web/v3-api.js +21 -0
  277. package/dist/dashboard/web/v3-api.js.map +1 -0
  278. package/dist/dashboard/web/v3-components.d.ts +2 -0
  279. package/dist/dashboard/web/v3-components.d.ts.map +1 -0
  280. package/dist/dashboard/web/v3-components.js +290 -0
  281. package/dist/dashboard/web/v3-components.js.map +1 -0
  282. package/dist/dashboard/web/v3-model.d.ts +80 -0
  283. package/dist/dashboard/web/v3-model.d.ts.map +1 -0
  284. package/dist/dashboard/web/v3-model.js +240 -0
  285. package/dist/dashboard/web/v3-model.js.map +1 -0
  286. package/dist/dashboard/web/v3-page.d.ts.map +1 -1
  287. package/dist/dashboard/web/v3-page.js +2 -22
  288. package/dist/dashboard/web/v3-page.js.map +1 -1
  289. package/dist/dashboard/web/v3-terminal.d.ts +14 -0
  290. package/dist/dashboard/web/v3-terminal.d.ts.map +1 -1
  291. package/dist/dashboard/web/v3-terminal.js +31 -19
  292. package/dist/dashboard/web/v3-terminal.js.map +1 -1
  293. package/dist/dashboard/web/webhook-logs-page.d.ts +16 -0
  294. package/dist/dashboard/web/webhook-logs-page.d.ts.map +1 -0
  295. package/dist/dashboard/web/webhook-logs-page.js +195 -0
  296. package/dist/dashboard/web/webhook-logs-page.js.map +1 -0
  297. package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
  298. package/dist/dashboard/web/whiteboards-page.js +18 -23
  299. package/dist/dashboard/web/whiteboards-page.js.map +1 -1
  300. package/dist/dashboard/web/workflow-version-switch.d.ts +8 -0
  301. package/dist/dashboard/web/workflow-version-switch.d.ts.map +1 -0
  302. package/dist/dashboard/web/workflow-version-switch.js +9 -0
  303. package/dist/dashboard/web/workflow-version-switch.js.map +1 -0
  304. package/dist/dashboard/web/workflows.d.ts +272 -1
  305. package/dist/dashboard/web/workflows.d.ts.map +1 -1
  306. package/dist/dashboard/web/workflows.js +211 -1397
  307. package/dist/dashboard/web/workflows.js.map +1 -1
  308. package/dist/dashboard/webhook-routes.d.ts.map +1 -1
  309. package/dist/dashboard/webhook-routes.js +92 -39
  310. package/dist/dashboard/webhook-routes.js.map +1 -1
  311. package/dist/dashboard-web/app.js +1 -65
  312. package/dist/dashboard-web/brand-logo.png +0 -0
  313. package/dist/dashboard-web/chunks/bot-defaults-page-HSQ6HR3R.js +1 -0
  314. package/dist/dashboard-web/chunks/chunk-6TPODDDC.js +1 -0
  315. package/dist/dashboard-web/chunks/chunk-73EJP2MO.js +1 -0
  316. package/dist/dashboard-web/chunks/chunk-BRTZFELS.js +1 -0
  317. package/dist/dashboard-web/chunks/chunk-EVK77ASG.js +1 -0
  318. package/dist/dashboard-web/chunks/chunk-R3FXBFQH.js +1 -0
  319. package/dist/dashboard-web/chunks/chunk-VRLL7SNU.js +1 -0
  320. package/dist/dashboard-web/chunks/chunk-XCPSHD3G.js +37 -0
  321. package/dist/dashboard-web/chunks/chunk-XJ7BGPJH.js +1 -0
  322. package/dist/dashboard-web/chunks/chunk-YVRWYGXR.js +1 -0
  323. package/dist/dashboard-web/chunks/chunk-ZJING6FY.js +66 -0
  324. package/dist/dashboard-web/chunks/connectors-page-ODUA5UCW.js +1 -0
  325. package/dist/dashboard-web/chunks/groups-page-OYX5M6NK.js +8 -0
  326. package/dist/dashboard-web/chunks/insights-page-YCQO6OCR.js +22 -0
  327. package/dist/dashboard-web/chunks/legacy-workflow-page-XJAUNONP.js +2 -0
  328. package/dist/dashboard-web/chunks/{monitor-room-RFX4HPS5.js → monitor-room-WTZNYAFV.js} +17 -15
  329. package/dist/dashboard-web/chunks/monitoring-page-R6LJUK7G.js +2 -0
  330. package/dist/dashboard-web/chunks/office-page-4OXK3GNR.js +1 -0
  331. package/dist/dashboard-web/chunks/overview-page-4MVEJY6U.js +1 -0
  332. package/dist/dashboard-web/chunks/roles-page-NQDW27VV.js +1 -0
  333. package/dist/dashboard-web/chunks/schedules-page-JRNKYHOX.js +1 -0
  334. package/dist/dashboard-web/chunks/sessions-page-LF7UVI7B.js +3 -0
  335. package/dist/dashboard-web/chunks/settings-page-WGW7KQQS.js +2 -0
  336. package/dist/dashboard-web/chunks/skills-page-AMD4XP3T.js +3 -0
  337. package/dist/dashboard-web/chunks/team-federation-page-SYG3K4GG.js +1 -0
  338. package/dist/dashboard-web/chunks/v3-page-6NFFWNUK.js +94 -0
  339. package/dist/dashboard-web/chunks/whiteboards-page-FBRUK2AS.js +1 -0
  340. package/dist/dashboard-web/favicon.png +0 -0
  341. package/dist/dashboard-web/index.html +5 -69
  342. package/dist/dashboard-web/style.css +18835 -7211
  343. package/dist/dashboard.js +150 -32
  344. package/dist/dashboard.js.map +1 -1
  345. package/dist/global-config.d.ts +2 -2
  346. package/dist/global-config.d.ts.map +1 -1
  347. package/dist/i18n/en.d.ts.map +1 -1
  348. package/dist/i18n/en.js +3 -0
  349. package/dist/i18n/en.js.map +1 -1
  350. package/dist/i18n/zh.d.ts.map +1 -1
  351. package/dist/i18n/zh.js +3 -0
  352. package/dist/i18n/zh.js.map +1 -1
  353. package/dist/im/lark/card-builder.d.ts +7 -5
  354. package/dist/im/lark/card-builder.d.ts.map +1 -1
  355. package/dist/im/lark/card-builder.js +10 -7
  356. package/dist/im/lark/card-builder.js.map +1 -1
  357. package/dist/im/lark/card-handler.d.ts.map +1 -1
  358. package/dist/im/lark/card-handler.js +25 -1
  359. package/dist/im/lark/card-handler.js.map +1 -1
  360. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  361. package/dist/im/lark/event-dispatcher.js +84 -0
  362. package/dist/im/lark/event-dispatcher.js.map +1 -1
  363. package/dist/platform/binding.d.ts +9 -0
  364. package/dist/platform/binding.d.ts.map +1 -1
  365. package/dist/platform/binding.js +14 -0
  366. package/dist/platform/binding.js.map +1 -1
  367. package/dist/platform/tunnel-client.d.ts.map +1 -1
  368. package/dist/platform/tunnel-client.js +9 -0
  369. package/dist/platform/tunnel-client.js.map +1 -1
  370. package/dist/services/bot-config-store.d.ts +1 -1
  371. package/dist/services/bot-config-store.d.ts.map +1 -1
  372. package/dist/services/bot-config-store.js +11 -5
  373. package/dist/services/bot-config-store.js.map +1 -1
  374. package/dist/services/file-bridge-path.d.ts +8 -0
  375. package/dist/services/file-bridge-path.d.ts.map +1 -0
  376. package/dist/services/file-bridge-path.js +75 -0
  377. package/dist/services/file-bridge-path.js.map +1 -0
  378. package/dist/services/grok-paths.d.ts +27 -0
  379. package/dist/services/grok-paths.d.ts.map +1 -0
  380. package/dist/services/grok-paths.js +106 -0
  381. package/dist/services/grok-paths.js.map +1 -0
  382. package/dist/services/grok-transcript.d.ts +67 -0
  383. package/dist/services/grok-transcript.d.ts.map +1 -0
  384. package/dist/services/grok-transcript.js +630 -0
  385. package/dist/services/grok-transcript.js.map +1 -0
  386. package/dist/services/hermes-session-filter.d.ts +28 -0
  387. package/dist/services/hermes-session-filter.d.ts.map +1 -0
  388. package/dist/services/hermes-session-filter.js +53 -0
  389. package/dist/services/hermes-session-filter.js.map +1 -0
  390. package/dist/services/hermes-transcript.d.ts +3 -0
  391. package/dist/services/hermes-transcript.d.ts.map +1 -1
  392. package/dist/services/hermes-transcript.js +20 -2
  393. package/dist/services/hermes-transcript.js.map +1 -1
  394. package/dist/services/resumable-session-discovery.d.ts +3 -0
  395. package/dist/services/resumable-session-discovery.d.ts.map +1 -1
  396. package/dist/services/resumable-session-discovery.js +6 -1
  397. package/dist/services/resumable-session-discovery.js.map +1 -1
  398. package/dist/services/skill-registry-store.d.ts +9 -0
  399. package/dist/services/skill-registry-store.d.ts.map +1 -1
  400. package/dist/services/skill-registry-store.js +304 -1
  401. package/dist/services/skill-registry-store.js.map +1 -1
  402. package/dist/services/structured-bridge-clis.d.ts +29 -0
  403. package/dist/services/structured-bridge-clis.d.ts.map +1 -0
  404. package/dist/services/structured-bridge-clis.js +76 -0
  405. package/dist/services/structured-bridge-clis.js.map +1 -0
  406. package/dist/services/trigger-log-store.d.ts +59 -0
  407. package/dist/services/trigger-log-store.d.ts.map +1 -1
  408. package/dist/services/trigger-log-store.js +81 -10
  409. package/dist/services/trigger-log-store.js.map +1 -1
  410. package/dist/services/usage-ledger.d.ts +4 -1
  411. package/dist/services/usage-ledger.d.ts.map +1 -1
  412. package/dist/services/usage-ledger.js +48 -10
  413. package/dist/services/usage-ledger.js.map +1 -1
  414. package/dist/services/webhook-audit.d.ts +10 -0
  415. package/dist/services/webhook-audit.d.ts.map +1 -0
  416. package/dist/services/webhook-audit.js +116 -0
  417. package/dist/services/webhook-audit.js.map +1 -0
  418. package/dist/setup/bot-config-editor.d.ts.map +1 -1
  419. package/dist/setup/bot-config-editor.js +2 -0
  420. package/dist/setup/bot-config-editor.js.map +1 -1
  421. package/dist/setup/ensure-tmux.d.ts +26 -12
  422. package/dist/setup/ensure-tmux.d.ts.map +1 -1
  423. package/dist/setup/ensure-tmux.js +85 -18
  424. package/dist/setup/ensure-tmux.js.map +1 -1
  425. package/dist/skills/definitions.d.ts.map +1 -1
  426. package/dist/skills/definitions.js +14 -1
  427. package/dist/skills/definitions.js.map +1 -1
  428. package/dist/skills/injection-mode.d.ts +16 -0
  429. package/dist/skills/injection-mode.d.ts.map +1 -1
  430. package/dist/skills/injection-mode.js +25 -0
  431. package/dist/skills/injection-mode.js.map +1 -1
  432. package/dist/types.d.ts +9 -0
  433. package/dist/types.d.ts.map +1 -1
  434. package/dist/utils/global-install.d.ts +22 -0
  435. package/dist/utils/global-install.d.ts.map +1 -0
  436. package/dist/utils/global-install.js +106 -0
  437. package/dist/utils/global-install.js.map +1 -0
  438. package/dist/utils/install-diagnostics.d.ts +1 -1
  439. package/dist/utils/install-diagnostics.d.ts.map +1 -1
  440. package/dist/utils/install-diagnostics.js +6 -4
  441. package/dist/utils/install-diagnostics.js.map +1 -1
  442. package/dist/utils/install-info.d.ts +2 -0
  443. package/dist/utils/install-info.d.ts.map +1 -1
  444. package/dist/utils/install-info.js +10 -4
  445. package/dist/utils/install-info.js.map +1 -1
  446. package/dist/worker.js +293 -172
  447. package/dist/worker.js.map +1 -1
  448. package/dist/workflows/events/payloads.d.ts +10 -10
  449. package/dist/workflows/events/schema.d.ts +28 -28
  450. package/package.json +8 -7
  451. package/dist/dashboard/web/connectors.d.ts +0 -2
  452. package/dist/dashboard/web/connectors.d.ts.map +0 -1
  453. package/dist/dashboard/web/connectors.js +0 -2
  454. package/dist/dashboard/web/connectors.js.map +0 -1
  455. package/dist/dashboard/web/office.d.ts +0 -2
  456. package/dist/dashboard/web/office.d.ts.map +0 -1
  457. package/dist/dashboard/web/office.js +0 -2
  458. package/dist/dashboard/web/office.js.map +0 -1
  459. package/dist/dashboard/web/schedules.d.ts +0 -2
  460. package/dist/dashboard/web/schedules.d.ts.map +0 -1
  461. package/dist/dashboard/web/schedules.js +0 -2
  462. package/dist/dashboard/web/schedules.js.map +0 -1
  463. package/dist/dashboard/web/settings.d.ts +0 -2
  464. package/dist/dashboard/web/settings.d.ts.map +0 -1
  465. package/dist/dashboard/web/settings.js +0 -2
  466. package/dist/dashboard/web/settings.js.map +0 -1
  467. package/dist/dashboard/web/skills.d.ts +0 -2
  468. package/dist/dashboard/web/skills.d.ts.map +0 -1
  469. package/dist/dashboard/web/skills.js +0 -2
  470. package/dist/dashboard/web/skills.js.map +0 -1
  471. package/dist/dashboard/web/skin-intro.d.ts +0 -4
  472. package/dist/dashboard/web/skin-intro.d.ts.map +0 -1
  473. package/dist/dashboard/web/skin-intro.js +0 -49
  474. package/dist/dashboard/web/skin-intro.js.map +0 -1
  475. package/dist/dashboard/web/v3.d.ts +0 -3
  476. package/dist/dashboard/web/v3.d.ts.map +0 -1
  477. package/dist/dashboard/web/v3.js +0 -539
  478. package/dist/dashboard/web/v3.js.map +0 -1
  479. package/dist/dashboard/web/whiteboards.d.ts +0 -2
  480. package/dist/dashboard/web/whiteboards.d.ts.map +0 -1
  481. package/dist/dashboard/web/whiteboards.js +0 -2
  482. package/dist/dashboard/web/whiteboards.js.map +0 -1
  483. package/dist/dashboard-web/chunks/bot-defaults-page-OCIHPGEU.js +0 -438
  484. package/dist/dashboard-web/chunks/chunk-3N2PPQVD.js +0 -1
  485. package/dist/dashboard-web/chunks/chunk-3UJYRR2Q.js +0 -37
  486. package/dist/dashboard-web/chunks/chunk-5E3MJJL4.js +0 -1
  487. package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
  488. package/dist/dashboard-web/chunks/chunk-JTSE5VP6.js +0 -54
  489. package/dist/dashboard-web/chunks/chunk-LXIK7OUJ.js +0 -1
  490. package/dist/dashboard-web/chunks/chunk-PE6W3C2C.js +0 -1
  491. package/dist/dashboard-web/chunks/chunk-QW54DWU3.js +0 -325
  492. package/dist/dashboard-web/chunks/connectors-IGETYWCP.js +0 -1
  493. package/dist/dashboard-web/chunks/groups-page-MYBS4ZYJ.js +0 -207
  494. package/dist/dashboard-web/chunks/insights-page-UPA32V2S.js +0 -309
  495. package/dist/dashboard-web/chunks/monitoring-page-7T3BESZ2.js +0 -1
  496. package/dist/dashboard-web/chunks/office-page-6DXYOG55.js +0 -1
  497. package/dist/dashboard-web/chunks/overview-page-LJSBCQW4.js +0 -1
  498. package/dist/dashboard-web/chunks/roles-page-ESURTVQH.js +0 -98
  499. package/dist/dashboard-web/chunks/schedules-LML2ISOT.js +0 -1
  500. package/dist/dashboard-web/chunks/sessions-YB73XOTU.js +0 -1
  501. package/dist/dashboard-web/chunks/sessions-page-MCO5QR7Z.js +0 -1
  502. package/dist/dashboard-web/chunks/settings-page-OSDKNVS3.js +0 -2
  503. package/dist/dashboard-web/chunks/skills-page-PXBZJ6P6.js +0 -2
  504. package/dist/dashboard-web/chunks/team-federation-page-TTPVHFPX.js +0 -10
  505. package/dist/dashboard-web/chunks/v3-page-FVPUL665.js +0 -149
  506. package/dist/dashboard-web/chunks/whiteboards-page-HLCTYSSV.js +0 -1
  507. package/dist/dashboard-web/chunks/workflows-FCPM4S4D.js +0 -184
  508. package/dist/dashboard-web/skins/bluearchive-hero.webp +0 -0
  509. package/dist/dashboard-web/skins/dragonball-goku.webp +0 -0
  510. package/dist/dashboard-web/skins/dragonball-wukong.webp +0 -0
  511. package/dist/dashboard-web/skins/genshin-breeze.webp +0 -0
  512. package/dist/dashboard-web/skins/ikun-hero.webp +0 -0
  513. package/dist/dashboard-web/skins/prts-priestess.webp +0 -0
  514. package/dist/dashboard-web/skins/zzz-hero.webp +0 -0
  515. package/dist/dashboard-web/skins/zzz-pattern.svg +0 -25
package/dist/worker.js CHANGED
@@ -30,15 +30,22 @@ import { mergeQueuedCliInput } from './utils/pending-input-queue.js';
30
30
  import { ReadyGate, shouldArmReadyGate } from './utils/ready-gate.js';
31
31
  import { shouldRunStartupCommandsOnSpawn, shouldDeferInitialPromptForStartup } from './core/startup-commands.js';
32
32
  import { sanitizePerBotEnv } from './core/per-bot-env.js';
33
+ import { resolveTerminalWriteForRequest } from './core/terminal-write-auth.js';
34
+ import { readPlatformBinding } from './platform/binding.js';
35
+ import { loadPersistedToken } from './dashboard/auth.js';
33
36
  import { InflightInputTracker } from './core/inflight-input-tracker.js';
34
37
  import { shouldRunQuietRotation, evaluatePidResolverPullback, decideFingerprintSwitch, shouldHealAbsentBaseline, sessionIdFromJsonlPath, SESSION_ID_FILENAME_RE, } from './services/bridge-rotation-policy.js';
35
38
  import { CodexBridgeQueue } from './services/codex-bridge-queue.js';
36
39
  import { drainCodexRollout, findCodexRolloutBySessionId, findCodexRolloutByPid, splitCodexEventsByCutoff, extractLastCodexTurn } from './services/codex-transcript.js';
37
40
  import { findTraexRolloutBySessionId, findTraexRolloutByPid } from './services/traex-transcript.js';
38
41
  import { cocoEventsPathForSession, drainCocoEvents, findCocoSessionByPid } from './services/coco-transcript.js';
39
- import { currentHermesStateOffset, drainHermesStateDb } from './services/hermes-transcript.js';
42
+ import { currentHermesStateOffset, drainHermesStateDb, resolveHermesStateDbPath } from './services/hermes-transcript.js';
43
+ import { filterHermesEventsForBotmuxSession } from './services/hermes-session-filter.js';
40
44
  import { currentMtrSessionOffset, drainMtrSession, findLatestMtrSessionByDirectory, findMtrSessionById } from './services/mtr-transcript.js';
41
- import { drainPiTranscript, findPiTranscriptByPid, findPiTranscriptBySessionId } from './services/pi-transcript.js';
45
+ import { drainPiTranscript } from './services/pi-transcript.js';
46
+ import { drainGrokUpdates, grokSessionIdFromPath, } from './services/grok-transcript.js';
47
+ import { resolveFileBridgePath } from './services/file-bridge-path.js';
48
+ import { isStructuredBridgeAdoptIdleCli, isStructuredBridgeAdoptInputCli, isStructuredBridgeFallbackActive, } from './services/structured-bridge-clis.js';
42
49
  import { drainCursorTranscript, findCursorChatIdByPid, findCursorTranscriptByChatId, findCursorTranscriptByPid } from './services/cursor-transcript.js';
43
50
  import { shouldObserveCursorChatId, shouldPersistObservedCursorChatId } from './services/cursor-resume-policy.js';
44
51
  import { baselineJsonlCursor } from './services/jsonl-cursor.js';
@@ -65,7 +72,8 @@ import { zellijEnv } from './setup/ensure-zellij.js';
65
72
  import { isObserveBackend } from './adapters/backend/types.js';
66
73
  import { selectSessionBackend, decideBackendGate, backendGateUserMessage } from './adapters/backend/session-backend-selector.js';
67
74
  import { prepareSandbox, attachSandboxOutbox, startOutboxWatcher, sandboxEnabled, sandboxedClaudeDataDir } from './adapters/backend/sandbox.js';
68
- import { tmuxEnv, probeTmuxFunctional } from './setup/ensure-tmux.js';
75
+ import { tmuxEnv, probeTmuxFunctionalWithRetry } from './setup/ensure-tmux.js';
76
+ import { tmuxRestartJitterMs } from './core/tmux-recovery.js';
69
77
  import { IdleDetector } from './utils/idle-detector.js';
70
78
  import { ScreenAnalyzer } from './utils/screen-analyzer.js';
71
79
  import { captureToPng } from './utils/screenshot-renderer.js';
@@ -98,6 +106,7 @@ let sandboxTeardownDone = false; // guards the exit-time best-effort teardown fr
98
106
  * (non-forced) config, so healthy restarts (e.g. user `/restart`) are
99
107
  * unaffected. */
100
108
  let consecutiveInWorkerRestarts = 0;
109
+ let tmuxRestartTimer = null;
101
110
  /** Guard: user_notify for "resume → fresh fallback" is sent once per worker
102
111
  * lifecycle so a 4× crash loop does not spam the Lark thread with 4 copies
103
112
  * of the same warning. */
@@ -289,23 +298,25 @@ const authedClients = new WeakSet();
289
298
  /** Per-WS-client tmux/zellij attach PTYs. */
290
299
  const clientPtys = new Map();
291
300
  const writeToken = randomBytes(16).toString('hex');
301
+ // Active dashboard token, persisted by the dashboard process at this stable
302
+ // path (mirrors dashboard.ts TOKEN_PATH). The platform proxy injects it as the
303
+ // `botmux_dashboard_token` cookie on every request it fronts, so its presence
304
+ // proves a request traversed the platform's authenticated front door.
305
+ const DASHBOARD_TOKEN_PATH = join(homedir(), '.botmux', '.dashboard-token');
292
306
  /**
293
- * Resolve terminal write permission for one request, honoring a platform-injected
294
- * `X-Botmux-Role` header. The central platform fronts `/s/*` and sets the role
295
- * (owner | teammate | guest) after authenticating the viewer, stripping any
296
- * client-supplied header; it reaches the worker via dashboard /s bridge →
297
- * terminal-proxy here. When the header is present we trust it (platform-fronted
298
- * access): only `owner` may drive the terminal; everything else (teammate / guest
299
- * / anything else) is read-only. When the header is absent (local direct access,
300
- * no platform in front), fall back to the legacy write-token query param.
307
+ * Resolve terminal write permission for one request. The platform-injected
308
+ * `X-Botmux-Role` header is trusted only when this machine is bound to a central
309
+ * platform AND the request actually came through the platform proxy (proven by a
310
+ * matching `botmux_dashboard_token` cookie a secret a direct caller lacks).
311
+ * Otherwise write falls back to the `?token=` gate. See
312
+ * ./core/terminal-write-auth for the full rationale.
313
+ *
314
+ * Both the binding and the token are read PER REQUEST, never snapshotted:
315
+ * `botmux bind`/unbind and dashboard token rotation are hot-reloaded without
316
+ * restarting this worker, so a cached value would go stale.
301
317
  */
302
- function resolveTerminalWrite(req, tokenMatches) {
303
- const role = req.headers['x-botmux-role'];
304
- if (typeof role === 'string' && role) {
305
- const hasWrite = role === 'owner';
306
- return { hasWrite, platformReadonly: !hasWrite };
307
- }
308
- return { hasWrite: tokenMatches, platformReadonly: false };
318
+ function resolveTerminalWriteForReq(req, tokenMatches) {
319
+ return resolveTerminalWriteForRequest(req.headers, tokenMatches, () => readPlatformBinding() !== null, () => loadPersistedToken(DASHBOARD_TOKEN_PATH));
309
320
  }
310
321
  /** Lazily-written locked-mode zellij config for per-WS web-terminal attach
311
322
  * clients: cleared keybinds + locked mode so every keystroke passes straight
@@ -326,7 +337,7 @@ function ensureZellijAttachConfig() {
326
337
  }
327
338
  let sessionId = '';
328
339
  let lastInitConfig = null;
329
- 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', 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' };
340
+ 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', 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' };
330
341
  function cliName() { return CLI_DISPLAY_NAMES[lastInitConfig?.cliId ?? ''] ?? 'CLI'; }
331
342
  let isPromptReady = false;
332
343
  /** Mutex for async flushPending — prevents concurrent flush loops. */
@@ -719,6 +730,8 @@ let codexBridgeWatcher = null;
719
730
  let codexBridgeTimer = null;
720
731
  let hermesBridgeOffset = 0;
721
732
  let hermesBridgeBaselineDone = false;
733
+ let hermesBridgeDbPath;
734
+ let hermesBridgeSourceSessionId;
722
735
  let mtrBridgeSource;
723
736
  let mtrBridgeOffset = 0;
724
737
  let mtrBridgeBaselineDone = false;
@@ -1937,18 +1950,10 @@ function drainPathInto(path, fromOffset) {
1937
1950
  // marker IO / type-ahead serialisation / one-write-per-idle break) is
1938
1951
  // shared with the Claude path.
1939
1952
  function codexBridgeFallbackActive() {
1940
- // True for transcript-backed CLIs whose final output can be harvested
1941
- // when the model forgets to call `botmux send`.
1942
- const id = lastInitConfig?.cliId;
1943
- if (id === 'codex' || id === 'traex' || id === 'coco' || id === 'hermes' || id === 'mtr' || id === 'pi')
1944
- return true;
1945
- // Cursor only harvests its transcript in adopt mode: a botmux-spawned
1946
- // cursor session carries the botmux skill and replies via `botmux send`,
1947
- // and we never resolve a transcript path for it — so leave that flow
1948
- // (screen capture + botmux send) untouched and scope the bridge to adopt.
1949
- if (id === 'cursor')
1950
- return lastInitConfig?.adoptMode === true;
1951
- return false;
1953
+ // Transcript-backed CLIs whose final output can be harvested when the
1954
+ // model forgets to call `botmux send`. Cursor is adopt-only — see
1955
+ // services/structured-bridge-clis.ts (single source of the allowlist).
1956
+ return isStructuredBridgeFallbackActive(lastInitConfig?.cliId, lastInitConfig?.adoptMode === true);
1952
1957
  }
1953
1958
  // Both Codex and TRAE share the same rollout JSONL layout (response_item
1954
1959
  // messages), so drainCodexRollout works for both.
@@ -1964,9 +1969,15 @@ function structuredBridgeIsMtr() {
1964
1969
  function structuredBridgeIsPi() {
1965
1970
  return lastInitConfig?.cliId === 'pi';
1966
1971
  }
1972
+ function structuredBridgeIsGrok() {
1973
+ return lastInitConfig?.cliId === 'grok';
1974
+ }
1967
1975
  function codexBridgeIsCursor() {
1968
1976
  return lastInitConfig?.cliId === 'cursor';
1969
1977
  }
1978
+ function currentHermesBridgeDbPath() {
1979
+ return hermesBridgeDbPath ?? resolveHermesStateDbPath();
1980
+ }
1970
1981
  function structuredBridgeIngestPath(path, offset) {
1971
1982
  if (structuredBridgeIsCodex())
1972
1983
  return drainCodexRollout(path, offset);
@@ -1974,8 +1985,10 @@ function structuredBridgeIngestPath(path, offset) {
1974
1985
  return drainCursorTranscript(path, offset);
1975
1986
  if (structuredBridgeIsPi())
1976
1987
  return drainPiTranscript(path, offset);
1988
+ if (structuredBridgeIsGrok())
1989
+ return drainGrokUpdates(path, offset);
1977
1990
  if (structuredBridgeIsHermes()) {
1978
- const result = drainHermesStateDb(offset);
1991
+ const result = drainHermesStateDb(offset, currentHermesBridgeDbPath());
1979
1992
  return { events: result.events, newOffset: result.newOffset, pendingTail: '' };
1980
1993
  }
1981
1994
  return drainCocoEvents(path, offset);
@@ -2050,66 +2063,19 @@ function codexBridgeStartTimer() {
2050
2063
  return;
2051
2064
  }
2052
2065
  if (!codexBridgeRolloutPath) {
2053
- // Two discovery paths, in order: cliSessionId (known via writeInput
2054
- // result for non-adopt or daemon-side probe for adopt) → exact
2055
- // file by name; PID (adopt only) walk /proc/<pid>/fd. Adopt
2056
- // attaches via split-live (history absorbed, live ingested);
2057
- // non-adopt uses fresh-empty (queue's markTimeMs - 5s lower bound
2058
- // gates historical fingerprint matches without needing a split).
2059
- // Discovery primitives differ per CLI: codex walks ~/.codex/sessions
2060
- // by sid suffix; CoCo's events.jsonl path is deterministic from
2061
- // sid, so the lookup is just a path computation + existence check.
2062
- const isCoco = lastInitConfig?.cliId === 'coco';
2063
- const isTraex = lastInitConfig?.cliId === 'traex';
2064
- const isPi = lastInitConfig?.cliId === 'pi';
2065
- let path;
2066
- if (codexBridgePendingSessionId) {
2067
- if (isCoco) {
2068
- path = cocoEventsPathForSession(codexBridgePendingSessionId);
2069
- if (path && !existsSync(path))
2070
- path = undefined;
2071
- }
2072
- else if (isPi) {
2073
- path = findPiTranscriptBySessionId(codexBridgePendingSessionId, lastInitConfig?.workingDir);
2074
- }
2075
- else if (isTraex) {
2076
- path = findTraexRolloutBySessionId(codexBridgePendingSessionId);
2077
- }
2078
- else {
2079
- path = findCodexRolloutBySessionId(codexBridgePendingSessionId);
2080
- }
2081
- }
2082
- if (!path && codexAdoptPendingPid) {
2083
- if (isCoco) {
2084
- const probed = findCocoSessionByPid(codexAdoptPendingPid);
2085
- if (probed && existsSync(probed.eventsPath))
2086
- path = probed.eventsPath;
2087
- }
2088
- else if (isPi) {
2089
- const probed = findPiTranscriptByPid(codexAdoptPendingPid);
2090
- if (probed)
2091
- path = probed.path;
2092
- }
2093
- else if (isTraex) {
2094
- const probed = findTraexRolloutByPid(codexAdoptPendingPid);
2095
- if (probed)
2096
- path = probed.path;
2097
- }
2098
- else {
2099
- const probed = findCodexRolloutByPid(codexAdoptPendingPid);
2100
- if (probed)
2101
- path = probed.path;
2102
- }
2103
- }
2066
+ // Late-attach: cliSessionId (writeInput / daemon probe) then adopt
2067
+ // pid. Path lookup is centralized in resolveFileBridgePath so
2068
+ // adding a file-backed CLI does not grow this if-tree.
2069
+ // Adopt split-live; non-adopt fresh-empty (queue markTimeMs
2070
+ // lower bound handles history without a split).
2071
+ const path = resolveFileBridgePath(lastInitConfig?.cliId, {
2072
+ sessionId: codexBridgePendingSessionId,
2073
+ cwd: lastInitConfig?.workingDir,
2074
+ pid: codexAdoptPendingPid,
2075
+ });
2104
2076
  if (path) {
2105
2077
  codexBridgePendingSessionId = undefined;
2106
2078
  codexAdoptPendingPid = undefined;
2107
- // Adopt mode: split-live partitions drained events by
2108
- // codexAdoptStartMs so anything the user did AFTER adopt but
2109
- // BEFORE we found the rollout still emits (history is absorbed,
2110
- // live is ingested). Non-adopt: fresh-empty as before — queue's
2111
- // markTimeMs - 5s lower bound is enough since there's no
2112
- // local-turn synthesis on that path.
2113
2079
  const mode = lastInitConfig?.adoptMode ? 'split-live' : 'fresh-empty';
2114
2080
  codexBridgeAttach(path, mode);
2115
2081
  }
@@ -2124,20 +2090,38 @@ function codexBridgeStartTimer() {
2124
2090
  }, 1000);
2125
2091
  }
2126
2092
  function hermesBridgeAttach(mode) {
2127
- hermesBridgeOffset = currentHermesStateOffset();
2093
+ const dbPath = currentHermesBridgeDbPath();
2094
+ hermesBridgeOffset = currentHermesStateOffset(dbPath);
2128
2095
  hermesBridgeBaselineDone = true;
2129
- log(`Hermes bridge ${mode}: state.db offset=${hermesBridgeOffset}`);
2096
+ hermesBridgeSourceSessionId = undefined;
2097
+ log(`Hermes bridge ${mode}: ${dbPath} offset=${hermesBridgeOffset}`);
2130
2098
  codexBridgeStartTimer();
2131
2099
  }
2132
2100
  function hermesBridgeIngest() {
2133
2101
  if (!hermesBridgeBaselineDone)
2134
2102
  return;
2135
- const result = drainHermesStateDb(hermesBridgeOffset);
2103
+ const result = drainHermesStateDb(hermesBridgeOffset, currentHermesBridgeDbPath());
2136
2104
  hermesBridgeOffset = result.newOffset;
2137
- if (result.events.length > 0)
2105
+ const filtered = filterHermesEventsForBotmuxSession(result.events, {
2106
+ botmuxSessionId: sessionId,
2107
+ boundSourceSessionId: hermesBridgeSourceSessionId,
2108
+ });
2109
+ // Announce EVERY source bound this drain, not just the last. A single drain
2110
+ // can bind multiple sources when the worker starts unbound and Hermes
2111
+ // `/clear`-rotates mid-batch; the daemon accumulates them into its authorized
2112
+ // set, so a completed turn from an earlier source is not dropped as foreign.
2113
+ for (const boundSourceSessionId of filtered.newlyBoundSourceSessionIds) {
2114
+ send({ type: 'bridge_source_session', bridge: 'hermes', sourceSessionId: boundSourceSessionId });
2115
+ log(`Hermes bridge bound sourceSessionId=${boundSourceSessionId}`);
2116
+ }
2117
+ hermesBridgeSourceSessionId = filtered.boundSourceSessionId;
2118
+ for (const drop of filtered.drops) {
2119
+ log(`Hermes bridge dropped ${drop.kind} ${drop.uuid} from sourceSessionId=${drop.sourceSessionId ?? '?'} expected=${drop.expectedSourceSessionId ?? hermesBridgeSourceSessionId ?? 'unbound'} reason=${drop.reason}`);
2120
+ }
2121
+ if (filtered.events.length > 0)
2138
2122
  lastStructuredBridgeActivityAtMs = Date.now();
2139
- codexBridgeQueue.ingest(result.events);
2140
- if (result.events.some(e => e.kind === 'assistant_final')) {
2123
+ codexBridgeQueue.ingest(filtered.events);
2124
+ if (filtered.events.some(e => e.kind === 'assistant_final')) {
2141
2125
  idleDetector?.fireIdle();
2142
2126
  }
2143
2127
  }
@@ -2302,12 +2286,60 @@ function cursorBridgeAttach(path, mode = 'baseline-existing') {
2302
2286
  }
2303
2287
  codexBridgeAttach(path, mode === 'baseline-existing' ? 'baseline-existing-skip-tail' : mode);
2304
2288
  }
2289
+ /** Drop the current file-backed bridge attachment (watcher + path cursor).
2290
+ * Does NOT clear the pending-turn queue — mid-rotation submits still need
2291
+ * fingerprint matching against the next attach. */
2292
+ function codexBridgeDetachFile() {
2293
+ if (codexBridgeWatcher) {
2294
+ try {
2295
+ codexBridgeWatcher.close();
2296
+ }
2297
+ catch { /* ignore */ }
2298
+ codexBridgeWatcher = null;
2299
+ }
2300
+ codexBridgeRolloutPath = undefined;
2301
+ codexBridgeOffset = 0;
2302
+ codexBridgePendingTail = '';
2303
+ codexBridgeBaselineDone = false;
2304
+ }
2305
2305
  /** Called from flushPending after writeInput first returns a cliSessionId.
2306
2306
  * Tries to locate the rollout file immediately; if it's not on disk yet,
2307
2307
  * remembers the sid so the 1s poller can keep retrying. */
2308
2308
  function codexBridgeNotifyCliSessionId(cliSessionId) {
2309
- if (!codexBridgeFallbackActive() || codexBridgeRolloutPath)
2309
+ if (!codexBridgeFallbackActive())
2310
2310
  return;
2311
+ if (codexBridgeRolloutPath) {
2312
+ // Already attached — first-attach-wins for most CLIs. Exception: grok's
2313
+ // `/new` / `/clear` / `/resume` rotate to a NEW session directory at the
2314
+ // SAME pid, and writeInput's prompt_history verify reports the new
2315
+ // session id on the very next submit. Without a re-attach the bridge
2316
+ // stays wedged on the old updates.jsonl and the reply fallback / idle
2317
+ // signal go dark for the rest of the session.
2318
+ if (structuredBridgeIsGrok()) {
2319
+ const currentSid = grokSessionIdFromPath(codexBridgeRolloutPath);
2320
+ if (currentSid && currentSid.toLowerCase() === cliSessionId.toLowerCase())
2321
+ return;
2322
+ const next = resolveFileBridgePath('grok', {
2323
+ sessionId: cliSessionId,
2324
+ cwd: lastInitConfig?.workingDir,
2325
+ });
2326
+ if (next && next !== codexBridgeRolloutPath) {
2327
+ log(`Grok session rotated ${currentSid ?? '?'} → ${cliSessionId}; re-attaching bridge to ${next}`);
2328
+ codexBridgeDetachFile();
2329
+ codexBridgePendingSessionId = undefined;
2330
+ codexBridgeAttach(next, 'fresh-empty');
2331
+ }
2332
+ else if (!next) {
2333
+ // New session id reported but updates.jsonl not on disk yet —
2334
+ // detach the retired session and arm the poller for late-attach.
2335
+ log(`Grok session rotated ${currentSid ?? '?'} → ${cliSessionId}; waiting for updates.jsonl`);
2336
+ codexBridgeDetachFile();
2337
+ codexBridgePendingSessionId = cliSessionId;
2338
+ codexBridgeStartTimer();
2339
+ }
2340
+ }
2341
+ return;
2342
+ }
2311
2343
  if (structuredBridgeIsMtr()) {
2312
2344
  const source = findMtrSessionById(cliSessionId);
2313
2345
  if (source) {
@@ -2323,7 +2355,7 @@ function codexBridgeNotifyCliSessionId(cliSessionId) {
2323
2355
  if (codexBridgeIsCursor()) {
2324
2356
  // Cursor's cliSessionId is the chatId — the same UUID naming the
2325
2357
  // agent-transcript JSONL, so it resolves the path directly.
2326
- const cursorPath = findCursorTranscriptByChatId(cliSessionId);
2358
+ const cursorPath = resolveFileBridgePath('cursor', { sessionId: cliSessionId });
2327
2359
  if (cursorPath) {
2328
2360
  codexBridgePendingSessionId = undefined;
2329
2361
  cursorBridgeAttach(cursorPath, cursorLateAttachMode(cursorPath));
@@ -2334,11 +2366,10 @@ function codexBridgeNotifyCliSessionId(cliSessionId) {
2334
2366
  }
2335
2367
  return;
2336
2368
  }
2337
- const path = lastInitConfig?.cliId === 'traex'
2338
- ? findTraexRolloutBySessionId(cliSessionId)
2339
- : lastInitConfig?.cliId === 'pi'
2340
- ? findPiTranscriptBySessionId(cliSessionId, lastInitConfig?.workingDir)
2341
- : findCodexRolloutBySessionId(cliSessionId);
2369
+ const path = resolveFileBridgePath(lastInitConfig?.cliId, {
2370
+ sessionId: cliSessionId,
2371
+ cwd: lastInitConfig?.workingDir,
2372
+ });
2342
2373
  if (path) {
2343
2374
  codexBridgePendingSessionId = undefined;
2344
2375
  codexBridgeAttach(path, 'fresh-empty');
@@ -2423,6 +2454,7 @@ function emitReadyCodexTurns() {
2423
2454
  const turn = ready[i];
2424
2455
  if (!turn.finalText)
2425
2456
  continue;
2457
+ const sourceHermesSessionId = structuredBridgeIsHermes() ? turn.sourceSessionId : undefined;
2426
2458
  const nextBoundaryMs = (i + 1 < ready.length ? ready[i + 1].markTimeMs : nextPendingMarkTimeMs);
2427
2459
  if (shouldSuppressBridgeEmit({ markTimeMs: turn.markTimeMs, isLocal: turn.isLocal, finalText: turn.finalText }, nextBoundaryMs, markers, adoptMode)) {
2428
2460
  log(`Codex bridge fallback suppressed for turn ${turn.turnId.substring(0, 8)} (gate)`);
@@ -2438,6 +2470,7 @@ function emitReadyCodexTurns() {
2438
2470
  continue;
2439
2471
  send({
2440
2472
  type: 'final_output',
2473
+ ...(sourceHermesSessionId ? { sourceHermesSessionId } : {}),
2441
2474
  content: fields.content,
2442
2475
  lastUuid: turn.turnId,
2443
2476
  turnId: turn.turnId,
@@ -2446,7 +2479,13 @@ function emitReadyCodexTurns() {
2446
2479
  });
2447
2480
  continue;
2448
2481
  }
2449
- send({ type: 'final_output', content: turn.finalText, lastUuid: turn.turnId, turnId: turn.turnId });
2482
+ send({
2483
+ type: 'final_output',
2484
+ ...(sourceHermesSessionId ? { sourceHermesSessionId } : {}),
2485
+ content: turn.finalText,
2486
+ lastUuid: turn.turnId,
2487
+ turnId: turn.turnId,
2488
+ });
2450
2489
  }
2451
2490
  }
2452
2491
  function stopCodexBridge() {
@@ -2467,6 +2506,7 @@ function stopCodexBridge() {
2467
2506
  codexBridgeBaselineDone = false;
2468
2507
  hermesBridgeOffset = 0;
2469
2508
  hermesBridgeBaselineDone = false;
2509
+ hermesBridgeSourceSessionId = undefined;
2470
2510
  mtrBridgeSource = undefined;
2471
2511
  mtrBridgeOffset = 0;
2472
2512
  mtrBridgeBaselineDone = false;
@@ -4041,43 +4081,45 @@ function setupAdoptTranscriptBridges(cfg) {
4041
4081
  codexBridgeStartTimer();
4042
4082
  }
4043
4083
  }
4044
- else if (cfg.cliId === 'pi') {
4045
- const adoptStartMs = Date.now();
4046
- codexAdoptStartMs = adoptStartMs;
4047
- codexBridgeQueue.setLocalTurns(true, adoptStartMs);
4048
- let path;
4084
+ else if (cfg.cliId === 'pi' || cfg.cliId === 'grok') {
4085
+ // File-backed bridges share the same adopt attach skeleton (sid → pid →
4086
+ // split-live | pending). Path lookup is resolveFileBridgePath; pi is
4087
+ // intentionally folded here with grok so the two stay in lockstep.
4088
+ setupAdoptFileBridge(cfg);
4089
+ }
4090
+ }
4091
+ /** Adopt attach for file-backed structured bridges (pi/grok today; codex/
4092
+ * traex keep their own branches for rollout-specific probes). */
4093
+ function setupAdoptFileBridge(cfg) {
4094
+ const adoptStartMs = Date.now();
4095
+ codexAdoptStartMs = adoptStartMs;
4096
+ codexBridgeQueue.setLocalTurns(true, adoptStartMs);
4097
+ const path = resolveFileBridgePath(cfg.cliId, {
4098
+ sessionId: cfg.cliSessionId,
4099
+ cwd: cfg.adoptCwd ?? cfg.workingDir,
4100
+ pid: cfg.adoptCliPid,
4101
+ });
4102
+ if (path) {
4103
+ codexBridgeAttach(path, 'split-live');
4104
+ }
4105
+ else {
4049
4106
  if (cfg.cliSessionId)
4050
- path = findPiTranscriptBySessionId(cfg.cliSessionId, cfg.adoptCwd ?? cfg.workingDir);
4051
- if (!path && cfg.adoptCliPid) {
4052
- const probed = findPiTranscriptByPid(cfg.adoptCliPid);
4053
- if (probed)
4054
- path = probed.path;
4055
- }
4056
- if (path) {
4057
- codexBridgeAttach(path, 'split-live');
4058
- }
4059
- else {
4060
- if (cfg.cliSessionId)
4061
- codexBridgePendingSessionId = cfg.cliSessionId;
4062
- codexAdoptPendingPid = cfg.adoptCliPid;
4063
- codexBridgeStartTimer();
4064
- }
4107
+ codexBridgePendingSessionId = cfg.cliSessionId;
4108
+ codexAdoptPendingPid = cfg.adoptCliPid;
4109
+ codexBridgeStartTimer();
4065
4110
  }
4066
4111
  }
4067
4112
  function adoptIdleAdapter(cfg) {
4068
4113
  return cfg.bridgeJsonlPath
4069
4114
  ? createCliAdapterSync('claude-code', undefined)
4070
- : cfg.cliId === 'codex' || cfg.cliId === 'traex' || cfg.cliId === 'coco' || cfg.cliId === 'mtr' || cfg.cliId === 'pi'
4115
+ : isStructuredBridgeAdoptIdleCli(cfg.cliId)
4071
4116
  ? createCliAdapterSync(cfg.cliId, undefined)
4072
4117
  : { completionPattern: undefined, readyPattern: undefined };
4073
4118
  }
4074
4119
  function setupAdoptInputAdapter(cfg) {
4075
- if (cfg.cliId === 'codex' || cfg.cliId === 'traex' || cfg.cliId === 'pi') {
4120
+ if (isStructuredBridgeAdoptInputCli(cfg.cliId)) {
4076
4121
  cliAdapter = createCliAdapterSync(cfg.cliId, cfg.cliPathOverride);
4077
4122
  }
4078
- else if (cfg.cliId === 'mtr') {
4079
- cliAdapter = createCliAdapterSync('mtr', cfg.cliPathOverride);
4080
- }
4081
4123
  }
4082
4124
  function setupAdoptIdleDetection(cfg, label) {
4083
4125
  idleDetector = new IdleDetector(adoptIdleAdapter(cfg));
@@ -4211,8 +4253,16 @@ function spawnCli(cfg) {
4211
4253
  });
4212
4254
  effectiveBackendType = 'herdr';
4213
4255
  backend = herdrBe;
4256
+ // Same as tmux/zellij adopt: writeInput (grok preferSessionId via
4257
+ // findGrokSessionByPid, claude pid-state) needs cliPid/cliCwd on the
4258
+ // PtyHandle. spawn() overwrites cliCwd from opts.cwd — use adoptCwd when
4259
+ // present so session discovery stays on the CLI's real working dir.
4260
+ if (cfg.adoptCliPid)
4261
+ herdrBe.cliPid = cfg.adoptCliPid;
4262
+ const herdrAdoptCwd = cfg.adoptCwd ?? cfg.workingDir;
4263
+ herdrBe.cliCwd = herdrAdoptCwd;
4214
4264
  herdrBe.spawn('', [], {
4215
- cwd: cfg.workingDir,
4265
+ cwd: herdrAdoptCwd,
4216
4266
  cols,
4217
4267
  rows,
4218
4268
  env: process.env,
@@ -4252,6 +4302,12 @@ function spawnCli(cfg) {
4252
4302
  : new TmuxPipeBackend(cfg.adoptTmuxTarget, { cliPid: cfg.adoptCliPid });
4253
4303
  effectiveBackendType = cfg.adoptZellijPaneId ? 'zellij' : 'tmux';
4254
4304
  backend = observeBe;
4305
+ // writeInput (grok concurrent prompt_history binding, claude pid-state)
4306
+ // reads these fields off the PtyHandle — constructor only stores
4307
+ // watchCliPid for liveness, so surface them explicitly for adopt.
4308
+ if (cfg.adoptCliPid)
4309
+ observeBe.cliPid = cfg.adoptCliPid;
4310
+ observeBe.cliCwd = cfg.adoptCwd ?? cfg.workingDir;
4255
4311
  observeBe.spawn('', [], {
4256
4312
  cwd: cfg.workingDir,
4257
4313
  cols,
@@ -4300,7 +4356,7 @@ function spawnCli(cfg) {
4300
4356
  if (effectiveBackend === 'tmux') {
4301
4357
  hasExistingSession = TmuxBackend.hasSession(TmuxBackend.sessionName(cfg.sessionId));
4302
4358
  if (!hasExistingSession) {
4303
- const probe = probeTmuxFunctional();
4359
+ const probe = probeTmuxFunctionalWithRetry();
4304
4360
  available = probe.ok;
4305
4361
  if (!probe.ok)
4306
4362
  reason = probe.reason;
@@ -4759,6 +4815,10 @@ function spawnCli(cfg) {
4759
4815
  const perBotInjectKeys = Object.keys(perBotInjectEnv);
4760
4816
  if (perBotInjectKeys.length)
4761
4817
  log(`Injecting ${perBotInjectKeys.length} per-bot env var(s): ${perBotInjectKeys.join(', ')}`);
4818
+ const hermesUsesBotmuxSessionProfile = basename(cfg.cliPathOverride ?? '') === 'hermes-botmux-session';
4819
+ hermesBridgeDbPath = cfg.cliId === 'hermes'
4820
+ ? resolveHermesStateDbPath({ ...childEnv, ...perBotInjectEnv }, { botmuxSessionProfile: hermesUsesBotmuxSessionProfile })
4821
+ : undefined;
4762
4822
  // ── File sandbox (oncall): wrap the CLI in bwrap so it can only touch a
4763
4823
  // per-session project copy + de-identified config. The agent's `botmux send`
4764
4824
  // routes through a daemon-side outbox watcher (creds never enter the sandbox).
@@ -5047,15 +5107,15 @@ function spawnCli(cfg) {
5047
5107
  startWrapperRealPidResolve(cliPid);
5048
5108
  if (cliPid)
5049
5109
  observeCursorCliSessionId(cliPid);
5050
- // Wire pid + cwd so the claude-code adapter's writeInput can read
5051
- // ~/.claude/sessions/<pid>.json — the spawn-time pid-state record. Its
5052
- // `sessionId` is set ONCE at process start (Claude Code 2.1.123); a
5053
- // `--resume` lookup will surface here, but in-pane `/clear` won't, so a
5054
- // 'matching sessionId' answer is "no spawn-time rotation observed", not
5055
- // "no rotation at all". The pinned claudeJsonlPath above is still the
5056
- // initial guess; the resolver corrects it on first write when Claude was
5057
- // started with `--resume`.
5058
- if (claudeDataDir && cliPid) {
5110
+ // Wire pid + cwd so adapters' writeInput can bind submits to this process:
5111
+ // - claude-code: ~/.claude/sessions/<pid>.json
5112
+ // - grok: findGrokSessionByPid preferSessionId against shared
5113
+ // prompt_history (concurrent same-cwd workers must not cross-claim)
5114
+ // Claude's sessionId is set ONCE at process start (2.1.123); a `--resume`
5115
+ // lookup will surface here, but in-pane `/clear` won't. The pinned
5116
+ // claudeJsonlPath above is still the initial guess; the resolver corrects
5117
+ // it on first write when Claude was started with `--resume`.
5118
+ if (cliPid && (claudeDataDir || cfg.cliId === 'grok')) {
5059
5119
  backend.cliPid = cliPid;
5060
5120
  backend.cliCwd = cfg.workingDir;
5061
5121
  }
@@ -5085,7 +5145,7 @@ function spawnCli(cfg) {
5085
5145
  log(`Failed to write CLI PID marker (async): ${err.message}`);
5086
5146
  }
5087
5147
  }
5088
- if (claudeDataDir) {
5148
+ if (claudeDataDir || cfg.cliId === 'grok') {
5089
5149
  backend.cliPid = pid;
5090
5150
  backend.cliCwd = cfg.workingDir;
5091
5151
  }
@@ -5192,17 +5252,21 @@ function spawnCli(cfg) {
5192
5252
  codexBridgeStartTimer();
5193
5253
  }
5194
5254
  }
5195
- else if (cfg.cliId === 'pi') {
5196
- const piSessionId = effectiveCliSessionId ?? effectiveAdapterSessionId;
5197
- if (piSessionId) {
5198
- const transcriptPath = findPiTranscriptBySessionId(piSessionId, cfg.workingDir);
5199
- if (transcriptPath) {
5200
- codexBridgeAttach(transcriptPath, effectiveResume ? 'baseline-existing' : 'fresh-empty');
5201
- }
5202
- else {
5203
- codexBridgePendingSessionId = piSessionId;
5204
- codexBridgeStartTimer();
5205
- }
5255
+ else if (cfg.cliId === 'pi' || cfg.cliId === 'grok') {
5256
+ // File-backed: pin path when known (pi session id / grok --session-id
5257
+ // UUID), else arm the poller. Grok collision-fallback (dir already
5258
+ // exists no --session-id → grok mints id) is recovered via writeInput
5259
+ // codexBridgeNotifyCliSessionId.
5260
+ const sid = effectiveCliSessionId ?? effectiveAdapterSessionId;
5261
+ const path = sid
5262
+ ? resolveFileBridgePath(cfg.cliId, { sessionId: sid, cwd: cfg.workingDir })
5263
+ : undefined;
5264
+ if (path) {
5265
+ codexBridgeAttach(path, effectiveResume ? 'baseline-existing' : 'fresh-empty');
5266
+ }
5267
+ else if (sid) {
5268
+ codexBridgePendingSessionId = sid;
5269
+ codexBridgeStartTimer();
5206
5270
  }
5207
5271
  else {
5208
5272
  codexBridgeStartTimer();
@@ -5411,7 +5475,7 @@ function startWebServer(host, preferredPort) {
5411
5475
  return;
5412
5476
  }
5413
5477
  const tokenMatches = url.searchParams.get('token') === writeToken;
5414
- const { hasWrite, platformReadonly } = resolveTerminalWrite(req, tokenMatches);
5478
+ const { hasWrite, platformReadonly } = resolveTerminalWriteForReq(req, tokenMatches);
5415
5479
  const loginHdr = req.headers['x-botmux-login-url'];
5416
5480
  const loginUrl = typeof loginHdr === 'string' && /^https?:\/\/[^"'<>\s]+$/.test(loginHdr) ? loginHdr : '';
5417
5481
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
@@ -5429,7 +5493,7 @@ function startWebServer(host, preferredPort) {
5429
5493
  return;
5430
5494
  }
5431
5495
  const tokenMatches = url.searchParams.get('token') === writeToken;
5432
- const { hasWrite } = resolveTerminalWrite(req, tokenMatches);
5496
+ const { hasWrite } = resolveTerminalWriteForReq(req, tokenMatches);
5433
5497
  if (hasWrite)
5434
5498
  authedClients.add(ws);
5435
5499
  log(`WS client connected (total: ${wsClients.size}, write: ${hasWrite})`);
@@ -5787,6 +5851,45 @@ try{
5787
5851
  try{term.loadAddon(new CanvasAddon.CanvasAddon())}catch(_e2){}
5788
5852
  }
5789
5853
  fit.fit();
5854
+ // xterm parses writes asynchronously. On a brand-new page the first tmux /
5855
+ // zellij frame (or relay history seed) can therefore finish after the browser
5856
+ // has initialised the viewport scrollbar, leaving that viewport at scrollTop=0
5857
+ // even though the buffer already contains newer rows. Follow only the initial
5858
+ // write burst and explicitly settle at the bottom. Any deliberate user scroll
5859
+ // cancels this so loading a busy session never fights the reader.
5860
+ var _initialFollow=true,_initialFollowT=0;
5861
+ function _cancelInitialFollow(){
5862
+ if(!_initialFollow)return;
5863
+ _initialFollow=false;clearTimeout(_initialFollowT);
5864
+ }
5865
+ function _settleInitialBottom(){
5866
+ if(!_initialFollow)return;
5867
+ try{term.scrollToBottom()}catch(_e){}
5868
+ clearTimeout(_initialFollowT);
5869
+ _initialFollowT=setTimeout(function(){
5870
+ if(!_initialFollow)return;
5871
+ try{term.scrollToBottom()}catch(_e){}
5872
+ _initialFollow=false;
5873
+ },500);
5874
+ }
5875
+ var _initialViewport=term.element&&term.element.querySelector('.xterm-viewport');
5876
+ var _initialTerminalRoot=document.getElementById('terminal');
5877
+ if(_initialTerminalRoot){
5878
+ // Listen above xterm's own root: its wheel handler can stop propagation at
5879
+ // the target, while this ancestor still sees capture-phase user intent.
5880
+ _initialTerminalRoot.addEventListener('wheel',_cancelInitialFollow,{capture:true,passive:true});
5881
+ _initialTerminalRoot.addEventListener('touchstart',_cancelInitialFollow,{capture:true,passive:true});
5882
+ }
5883
+ if(_initialViewport){
5884
+ _initialViewport.addEventListener('pointerdown',function(e){
5885
+ // A pointer press on the native scrollbar targets the viewport itself;
5886
+ // presses on terminal cells target the screen/canvas and keep following.
5887
+ if(e.target===_initialViewport)_cancelInitialFollow();
5888
+ },{capture:true});
5889
+ }
5890
+ window.addEventListener('keydown',function(e){
5891
+ if(e.key==='PageUp'||e.key==='PageDown'||e.key==='Home'||e.key==='End')_cancelInitialFollow();
5892
+ },{capture:true});
5790
5893
  // ── OSC 52 clipboard ──
5791
5894
  var _clipBuf='';
5792
5895
  function _doCopy(text){
@@ -5872,7 +5975,7 @@ window.addEventListener('resize',onViewportResize);
5872
5975
  // Intercept OSC 52 clipboard sequence from tmux (set-clipboard on)
5873
5976
  var m=data.match(/\\x1b\\]52;[^;]*;([A-Za-z0-9+/=]+)(?:\\x07|\\x1b\\\\)/);
5874
5977
  if(m){try{_clipBuf=new TextDecoder().decode(Uint8Array.from(atob(m[1]),function(c){return c.charCodeAt(0)}));_doCopy(_clipBuf);_showCopied()}catch(ex){}}
5875
- term.write(data);
5978
+ term.write(data,_settleInitialBottom);
5876
5979
  };
5877
5980
  ws.onclose=function(){ws_=null;el.textContent='disconnected';el.className='err';setTimeout(connect,2000)};
5878
5981
  ws.onerror=function(){ws.close()};
@@ -6165,17 +6268,18 @@ process.on('message', async (raw) => {
6165
6268
  }
6166
6269
  }
6167
6270
  // Adopt mode write:
6168
- // - codex routes through cliAdapter.writeInput so the adapter's
6169
- // paste-detection delay + Enter-retry + history.jsonl verify
6170
- // loop handles Codex TUI's "\n treated as Enter" submit
6171
- // behaviour. Without it, Lark messages get stranded in the
6172
- // input box (user-reported "卡在输入框中").
6271
+ // - Structured-bridge adopt-input CLIs (codex/traex/pi/grok/mtr)
6272
+ // route through cliAdapter.writeInput so paste+Enter-retry +
6273
+ // transcript/history verify (and grok's prompt_history
6274
+ // cliSessionId re-attach after /new) all run. Hardcoding only
6275
+ // codex/traex left grok on raw sendText, so /new rotation never
6276
+ // re-attached the bridge.
6173
6277
  // - everything else keeps the simple raw sendText+Enter — the
6174
6278
  // claude-code adopt bridge has its own dual-write recovery
6175
6279
  // path, and the other CLIs' adopt flows haven't surfaced
6176
6280
  // this submit-detection issue.
6177
6281
  if (backend) {
6178
- if ((lastInitConfig?.cliId === 'codex' || lastInitConfig?.cliId === 'traex') && cliAdapter) {
6282
+ if (isStructuredBridgeAdoptInputCli(lastInitConfig?.cliId) && cliAdapter) {
6179
6283
  // writeInput is async but we're already inside an async
6180
6284
  // message handler. Errors are best-effort logged; the bridge
6181
6285
  // ingest path is unaffected because mark already happened
@@ -6187,11 +6291,11 @@ process.on('message', async (raw) => {
6187
6291
  codexBridgeNotifyCliSessionId(result.cliSessionId);
6188
6292
  }
6189
6293
  if (result && result.submitted === false) {
6190
- scheduleSubmitFailureNotify(content, result.recheck, 'Codex history', undefined, result.failureReason, turnSeq);
6294
+ scheduleSubmitFailureNotify(content, result.recheck, 'submit history', undefined, result.failureReason, turnSeq);
6191
6295
  }
6192
6296
  }
6193
6297
  catch (err) {
6194
- log(`Codex adopt writeInput error: ${err.message}`);
6298
+ log(`Adopt writeInput error (${lastInitConfig?.cliId}): ${err.message}`);
6195
6299
  }
6196
6300
  }
6197
6301
  else if ('sendText' in backend && 'sendSpecialKeys' in backend) {
@@ -6291,16 +6395,29 @@ process.on('message', async (raw) => {
6291
6395
  // process would never actually restart. destroySession() tears the session
6292
6396
  // down so the respawn starts a fresh CLI. (PTY has no destroySession, so
6293
6397
  // the ?. no-ops and killCli()'s kill() does the teardown.)
6294
- backend?.destroySession?.();
6295
- killCli();
6296
- awaitingFirstPrompt = true;
6297
- setTimeout(() => {
6298
- if (lastInitConfig) {
6299
- startScreenUpdates();
6300
- startScreenAnalyzer();
6301
- spawnCli({ ...lastInitConfig, resume: true, prompt: '' });
6302
- }
6303
- }, 500);
6398
+ const restart = () => {
6399
+ tmuxRestartTimer = null;
6400
+ backend?.destroySession?.();
6401
+ killCli();
6402
+ awaitingFirstPrompt = true;
6403
+ setTimeout(() => {
6404
+ if (lastInitConfig) {
6405
+ startScreenUpdates();
6406
+ startScreenAnalyzer();
6407
+ spawnCli({ ...lastInitConfig, resume: true, prompt: '' });
6408
+ }
6409
+ }, 500);
6410
+ };
6411
+ if (effectiveBackendType === 'tmux') {
6412
+ const delayMs = tmuxRestartJitterMs(lastInitConfig?.sessionId ?? '', consecutiveInWorkerRestarts);
6413
+ log(`Staggering tmux teardown/restart by ${delayMs}ms to avoid shared-server probe storms`);
6414
+ if (tmuxRestartTimer)
6415
+ clearTimeout(tmuxRestartTimer);
6416
+ tmuxRestartTimer = setTimeout(restart, delayMs);
6417
+ }
6418
+ else {
6419
+ restart();
6420
+ }
6304
6421
  break;
6305
6422
  }
6306
6423
  case 'tui_keys': {
@@ -6425,6 +6542,10 @@ process.on('message', async (raw) => {
6425
6542
  });
6426
6543
  // ─── Cleanup ─────────────────────────────────────────────────────────────────
6427
6544
  function cleanup() {
6545
+ if (tmuxRestartTimer) {
6546
+ clearTimeout(tmuxRestartTimer);
6547
+ tmuxRestartTimer = null;
6548
+ }
6428
6549
  for (const [, cp] of clientPtys) {
6429
6550
  try {
6430
6551
  cp.kill();