botmux 3.4.3 → 3.5.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 (350) hide show
  1. package/README.en.md +10 -7
  2. package/README.md +11 -6
  3. package/dist/adapters/backend/herdr-backend.d.ts +43 -6
  4. package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
  5. package/dist/adapters/backend/herdr-backend.js +362 -28
  6. package/dist/adapters/backend/herdr-backend.js.map +1 -1
  7. package/dist/adapters/backend/reproduce-command.d.ts +39 -0
  8. package/dist/adapters/backend/reproduce-command.d.ts.map +1 -0
  9. package/dist/adapters/backend/reproduce-command.js +63 -0
  10. package/dist/adapters/backend/reproduce-command.js.map +1 -0
  11. package/dist/adapters/backend/session-backend-selector.d.ts +10 -1
  12. package/dist/adapters/backend/session-backend-selector.d.ts.map +1 -1
  13. package/dist/adapters/backend/session-backend-selector.js +73 -4
  14. package/dist/adapters/backend/session-backend-selector.js.map +1 -1
  15. package/dist/adapters/backend/types.d.ts +16 -0
  16. package/dist/adapters/backend/types.d.ts.map +1 -1
  17. package/dist/adapters/backend/types.js.map +1 -1
  18. package/dist/adapters/cli/pi-initial-prompt.d.ts.map +1 -1
  19. package/dist/adapters/cli/pi-initial-prompt.js +5 -1
  20. package/dist/adapters/cli/pi-initial-prompt.js.map +1 -1
  21. package/dist/adapters/cli/traex.d.ts.map +1 -1
  22. package/dist/adapters/cli/traex.js +5 -0
  23. package/dist/adapters/cli/traex.js.map +1 -1
  24. package/dist/adapters/cli/types.d.ts +3 -3
  25. package/dist/adapters/cli/types.d.ts.map +1 -1
  26. package/dist/adapters/hook-installer.d.ts +7 -0
  27. package/dist/adapters/hook-installer.d.ts.map +1 -1
  28. package/dist/adapters/hook-installer.js +56 -0
  29. package/dist/adapters/hook-installer.js.map +1 -1
  30. package/dist/bot-registry.d.ts +13 -0
  31. package/dist/bot-registry.d.ts.map +1 -1
  32. package/dist/bot-registry.js +118 -2
  33. package/dist/bot-registry.js.map +1 -1
  34. package/dist/cli/capabilities.d.ts +24 -0
  35. package/dist/cli/capabilities.d.ts.map +1 -0
  36. package/dist/cli/capabilities.js +25 -0
  37. package/dist/cli/capabilities.js.map +1 -0
  38. package/dist/cli/create-group-resolver.d.ts +24 -0
  39. package/dist/cli/create-group-resolver.d.ts.map +1 -1
  40. package/dist/cli/create-group-resolver.js +23 -0
  41. package/dist/cli/create-group-resolver.js.map +1 -1
  42. package/dist/cli/daemon-lifecycle-env.d.ts +5 -7
  43. package/dist/cli/daemon-lifecycle-env.d.ts.map +1 -1
  44. package/dist/cli/daemon-lifecycle-env.js +13 -7
  45. package/dist/cli/daemon-lifecycle-env.js.map +1 -1
  46. package/dist/cli/exact-chat-grant-client.d.ts +48 -0
  47. package/dist/cli/exact-chat-grant-client.d.ts.map +1 -0
  48. package/dist/cli/exact-chat-grant-client.js +108 -0
  49. package/dist/cli/exact-chat-grant-client.js.map +1 -0
  50. package/dist/cli/exact-chat-grant.d.ts +18 -0
  51. package/dist/cli/exact-chat-grant.d.ts.map +1 -0
  52. package/dist/cli/exact-chat-grant.js +97 -0
  53. package/dist/cli/exact-chat-grant.js.map +1 -0
  54. package/dist/cli.d.ts +10 -1
  55. package/dist/cli.d.ts.map +1 -1
  56. package/dist/cli.js +770 -126
  57. package/dist/cli.js.map +1 -1
  58. package/dist/codex-rpc-engine.d.ts +26 -0
  59. package/dist/codex-rpc-engine.d.ts.map +1 -1
  60. package/dist/codex-rpc-engine.js +113 -2
  61. package/dist/codex-rpc-engine.js.map +1 -1
  62. package/dist/config.d.ts +16 -0
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +16 -0
  65. package/dist/config.js.map +1 -1
  66. package/dist/core/bot-live-control.d.ts +41 -0
  67. package/dist/core/bot-live-control.d.ts.map +1 -0
  68. package/dist/core/bot-live-control.js +135 -0
  69. package/dist/core/bot-live-control.js.map +1 -0
  70. package/dist/core/command-handler.d.ts.map +1 -1
  71. package/dist/core/command-handler.js +28 -8
  72. package/dist/core/command-handler.js.map +1 -1
  73. package/dist/core/daemon-config-fence.d.ts +8 -0
  74. package/dist/core/daemon-config-fence.d.ts.map +1 -0
  75. package/dist/core/daemon-config-fence.js +13 -0
  76. package/dist/core/daemon-config-fence.js.map +1 -0
  77. package/dist/core/dashboard-events.d.ts +5 -0
  78. package/dist/core/dashboard-events.d.ts.map +1 -1
  79. package/dist/core/dashboard-events.js.map +1 -1
  80. package/dist/core/dashboard-ipc-server.d.ts +21 -2
  81. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  82. package/dist/core/dashboard-ipc-server.js +232 -5
  83. package/dist/core/dashboard-ipc-server.js.map +1 -1
  84. package/dist/core/dispatch-registry.d.ts +12 -0
  85. package/dist/core/dispatch-registry.d.ts.map +1 -0
  86. package/dist/core/dispatch-registry.js +33 -0
  87. package/dist/core/dispatch-registry.js.map +1 -0
  88. package/dist/core/dispatch.d.ts +99 -0
  89. package/dist/core/dispatch.d.ts.map +1 -1
  90. package/dist/core/dispatch.js +136 -0
  91. package/dist/core/dispatch.js.map +1 -1
  92. package/dist/core/maintenance.d.ts.map +1 -1
  93. package/dist/core/maintenance.js +10 -3
  94. package/dist/core/maintenance.js.map +1 -1
  95. package/dist/core/persistent-backend.d.ts +16 -2
  96. package/dist/core/persistent-backend.d.ts.map +1 -1
  97. package/dist/core/persistent-backend.js +40 -0
  98. package/dist/core/persistent-backend.js.map +1 -1
  99. package/dist/core/session-discovery.d.ts +8 -1
  100. package/dist/core/session-discovery.d.ts.map +1 -1
  101. package/dist/core/session-discovery.js +140 -8
  102. package/dist/core/session-discovery.js.map +1 -1
  103. package/dist/core/session-manager.d.ts.map +1 -1
  104. package/dist/core/session-manager.js +12 -8
  105. package/dist/core/session-manager.js.map +1 -1
  106. package/dist/core/session-title.d.ts +8 -0
  107. package/dist/core/session-title.d.ts.map +1 -1
  108. package/dist/core/session-title.js +54 -0
  109. package/dist/core/session-title.js.map +1 -1
  110. package/dist/core/terminal-write-auth.d.ts +15 -0
  111. package/dist/core/terminal-write-auth.d.ts.map +1 -1
  112. package/dist/core/terminal-write-auth.js +20 -0
  113. package/dist/core/terminal-write-auth.js.map +1 -1
  114. package/dist/core/trigger-session.d.ts.map +1 -1
  115. package/dist/core/trigger-session.js +3 -2
  116. package/dist/core/trigger-session.js.map +1 -1
  117. package/dist/core/types.d.ts +33 -0
  118. package/dist/core/types.d.ts.map +1 -1
  119. package/dist/core/types.js.map +1 -1
  120. package/dist/core/worker-pool.d.ts +27 -8
  121. package/dist/core/worker-pool.d.ts.map +1 -1
  122. package/dist/core/worker-pool.js +389 -19
  123. package/dist/core/worker-pool.js.map +1 -1
  124. package/dist/daemon.d.ts +4 -2
  125. package/dist/daemon.d.ts.map +1 -1
  126. package/dist/daemon.js +397 -26
  127. package/dist/daemon.js.map +1 -1
  128. package/dist/dashboard/bot-onboarding.d.ts +127 -1
  129. package/dist/dashboard/bot-onboarding.d.ts.map +1 -1
  130. package/dist/dashboard/bot-onboarding.js +1104 -25
  131. package/dist/dashboard/bot-onboarding.js.map +1 -1
  132. package/dist/dashboard/debug-terminal.d.ts +18 -0
  133. package/dist/dashboard/debug-terminal.d.ts.map +1 -0
  134. package/dist/dashboard/debug-terminal.js +338 -0
  135. package/dist/dashboard/debug-terminal.js.map +1 -0
  136. package/dist/dashboard/public-redact.d.ts +2 -0
  137. package/dist/dashboard/public-redact.d.ts.map +1 -1
  138. package/dist/dashboard/public-redact.js +12 -3
  139. package/dist/dashboard/public-redact.js.map +1 -1
  140. package/dist/dashboard/registry.d.ts +8 -0
  141. package/dist/dashboard/registry.d.ts.map +1 -1
  142. package/dist/dashboard/registry.js +13 -0
  143. package/dist/dashboard/registry.js.map +1 -1
  144. package/dist/dashboard/settings-write-applier.d.ts +47 -1
  145. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  146. package/dist/dashboard/settings-write-applier.js +102 -1
  147. package/dist/dashboard/settings-write-applier.js.map +1 -1
  148. package/dist/dashboard/web/app.d.ts.map +1 -1
  149. package/dist/dashboard/web/app.js +2 -0
  150. package/dist/dashboard/web/app.js.map +1 -1
  151. package/dist/dashboard/web/bot-defaults-page.d.ts +1 -0
  152. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  153. package/dist/dashboard/web/bot-defaults-page.js +24 -5
  154. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  155. package/dist/dashboard/web/bot-defaults.d.ts +15 -0
  156. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  157. package/dist/dashboard/web/bot-defaults.js +19 -0
  158. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  159. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  160. package/dist/dashboard/web/i18n.js +76 -6
  161. package/dist/dashboard/web/i18n.js.map +1 -1
  162. package/dist/dashboard/web/icon-tooltip.d.ts +3 -0
  163. package/dist/dashboard/web/icon-tooltip.d.ts.map +1 -0
  164. package/dist/dashboard/web/icon-tooltip.js +108 -0
  165. package/dist/dashboard/web/icon-tooltip.js.map +1 -0
  166. package/dist/dashboard/web/sessions-kanban.js +2 -2
  167. package/dist/dashboard/web/sessions-kanban.js.map +1 -1
  168. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  169. package/dist/dashboard/web/sessions-page.js +48 -9
  170. package/dist/dashboard/web/sessions-page.js.map +1 -1
  171. package/dist/dashboard/web/sessions.d.ts +2 -0
  172. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  173. package/dist/dashboard/web/sessions.js +46 -2
  174. package/dist/dashboard/web/sessions.js.map +1 -1
  175. package/dist/dashboard/web/settings-page.d.ts +78 -0
  176. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  177. package/dist/dashboard/web/settings-page.js +88 -1
  178. package/dist/dashboard/web/settings-page.js.map +1 -1
  179. package/dist/dashboard/web/store.d.ts +2 -0
  180. package/dist/dashboard/web/store.d.ts.map +1 -1
  181. package/dist/dashboard/web/store.js +13 -1
  182. package/dist/dashboard/web/store.js.map +1 -1
  183. package/dist/dashboard-web/app.js +1 -1
  184. package/dist/dashboard-web/chunks/bot-defaults-page-O376WSER.js +5 -0
  185. package/dist/dashboard-web/chunks/{chunk-JNRSPKLS.js → chunk-EJDYUINZ.js} +1 -1
  186. package/dist/dashboard-web/chunks/{chunk-CUV65BIQ.js → chunk-H7P5UV4W.js} +6 -6
  187. package/dist/dashboard-web/chunks/{chunk-P6GO5KT4.js → chunk-QCPPKCX3.js} +1 -1
  188. package/dist/dashboard-web/chunks/{chunk-UBOLHQCK.js → chunk-WXPMNEYV.js} +1 -1
  189. package/dist/dashboard-web/chunks/{connectors-page-KTC2QWNW.js → connectors-page-IR54AOAI.js} +1 -1
  190. package/dist/dashboard-web/chunks/{groups-page-B3EKGOLY.js → groups-page-3P4TLUMF.js} +1 -1
  191. package/dist/dashboard-web/chunks/{insights-page-X6RXNCWX.js → insights-page-TDCZV56A.js} +1 -1
  192. package/dist/dashboard-web/chunks/{monitor-room-LLRA5LQW.js → monitor-room-QWCWL3SX.js} +1 -1
  193. package/dist/dashboard-web/chunks/{monitoring-page-7MT5FEOS.js → monitoring-page-IOWFSNVC.js} +1 -1
  194. package/dist/dashboard-web/chunks/{office-page-KD46D5TD.js → office-page-5AUPEHZP.js} +1 -1
  195. package/dist/dashboard-web/chunks/{overview-page-JUWNH32O.js → overview-page-QJMI6NLW.js} +1 -1
  196. package/dist/dashboard-web/chunks/{roles-page-P6F7NLOI.js → roles-page-WJVB52ET.js} +1 -1
  197. package/dist/dashboard-web/chunks/{schedules-page-XIHWZST4.js → schedules-page-RVRWHP4V.js} +1 -1
  198. package/dist/dashboard-web/chunks/sessions-page-KWLGBX6G.js +3 -0
  199. package/dist/dashboard-web/chunks/settings-page-TNZEPC7V.js +2 -0
  200. package/dist/dashboard-web/chunks/{skills-page-A46KVWBL.js → skills-page-7YT5TIX5.js} +1 -1
  201. package/dist/dashboard-web/chunks/{team-federation-page-SDUC56DF.js → team-federation-page-NJZKTU7A.js} +1 -1
  202. package/dist/dashboard-web/chunks/{v3-page-J5JOTUSQ.js → v3-page-NQ73UA2Q.js} +1 -1
  203. package/dist/dashboard-web/chunks/{whiteboards-page-U7LSOAWI.js → whiteboards-page-DSM3MDMN.js} +1 -1
  204. package/dist/dashboard-web/style.css +198 -7
  205. package/dist/dashboard.js +294 -7
  206. package/dist/dashboard.js.map +1 -1
  207. package/dist/features/codex-notifier/adoption.d.ts +11 -0
  208. package/dist/features/codex-notifier/adoption.d.ts.map +1 -0
  209. package/dist/features/codex-notifier/adoption.js +11 -0
  210. package/dist/features/codex-notifier/adoption.js.map +1 -0
  211. package/dist/features/codex-notifier/app-opener.d.ts +21 -0
  212. package/dist/features/codex-notifier/app-opener.d.ts.map +1 -0
  213. package/dist/features/codex-notifier/app-opener.js +33 -0
  214. package/dist/features/codex-notifier/app-opener.js.map +1 -0
  215. package/dist/features/codex-notifier/card-action.d.ts +27 -0
  216. package/dist/features/codex-notifier/card-action.d.ts.map +1 -0
  217. package/dist/features/codex-notifier/card-action.js +84 -0
  218. package/dist/features/codex-notifier/card-action.js.map +1 -0
  219. package/dist/features/codex-notifier/card.d.ts +12 -0
  220. package/dist/features/codex-notifier/card.d.ts.map +1 -0
  221. package/dist/features/codex-notifier/card.js +150 -0
  222. package/dist/features/codex-notifier/card.js.map +1 -0
  223. package/dist/features/codex-notifier/codex-context.d.ts +12 -0
  224. package/dist/features/codex-notifier/codex-context.d.ts.map +1 -0
  225. package/dist/features/codex-notifier/codex-context.js +161 -0
  226. package/dist/features/codex-notifier/codex-context.js.map +1 -0
  227. package/dist/features/codex-notifier/config.d.ts +9 -0
  228. package/dist/features/codex-notifier/config.d.ts.map +1 -0
  229. package/dist/features/codex-notifier/config.js +11 -0
  230. package/dist/features/codex-notifier/config.js.map +1 -0
  231. package/dist/features/codex-notifier/confirmed-turn.d.ts +20 -0
  232. package/dist/features/codex-notifier/confirmed-turn.d.ts.map +1 -0
  233. package/dist/features/codex-notifier/confirmed-turn.js +130 -0
  234. package/dist/features/codex-notifier/confirmed-turn.js.map +1 -0
  235. package/dist/features/codex-notifier/emitter.d.ts +13 -0
  236. package/dist/features/codex-notifier/emitter.d.ts.map +1 -0
  237. package/dist/features/codex-notifier/emitter.js +33 -0
  238. package/dist/features/codex-notifier/emitter.js.map +1 -0
  239. package/dist/features/codex-notifier/event-store.d.ts +29 -0
  240. package/dist/features/codex-notifier/event-store.d.ts.map +1 -0
  241. package/dist/features/codex-notifier/event-store.js +217 -0
  242. package/dist/features/codex-notifier/event-store.js.map +1 -0
  243. package/dist/features/codex-notifier/event.d.ts +46 -0
  244. package/dist/features/codex-notifier/event.d.ts.map +1 -0
  245. package/dist/features/codex-notifier/event.js +188 -0
  246. package/dist/features/codex-notifier/event.js.map +1 -0
  247. package/dist/features/codex-notifier/hook-cli.d.ts +23 -0
  248. package/dist/features/codex-notifier/hook-cli.d.ts.map +1 -0
  249. package/dist/features/codex-notifier/hook-cli.js +130 -0
  250. package/dist/features/codex-notifier/hook-cli.js.map +1 -0
  251. package/dist/features/codex-notifier/hook-installer.d.ts +19 -0
  252. package/dist/features/codex-notifier/hook-installer.d.ts.map +1 -0
  253. package/dist/features/codex-notifier/hook-installer.js +158 -0
  254. package/dist/features/codex-notifier/hook-installer.js.map +1 -0
  255. package/dist/features/codex-notifier/index.d.ts +21 -0
  256. package/dist/features/codex-notifier/index.d.ts.map +1 -0
  257. package/dist/features/codex-notifier/index.js +19 -0
  258. package/dist/features/codex-notifier/index.js.map +1 -0
  259. package/dist/features/codex-notifier/internal-turn.d.ts +5 -0
  260. package/dist/features/codex-notifier/internal-turn.d.ts.map +1 -0
  261. package/dist/features/codex-notifier/internal-turn.js +43 -0
  262. package/dist/features/codex-notifier/internal-turn.js.map +1 -0
  263. package/dist/features/codex-notifier/outbox-worker.d.ts +71 -0
  264. package/dist/features/codex-notifier/outbox-worker.d.ts.map +1 -0
  265. package/dist/features/codex-notifier/outbox-worker.js +290 -0
  266. package/dist/features/codex-notifier/outbox-worker.js.map +1 -0
  267. package/dist/features/codex-notifier/outbox.d.ts +25 -0
  268. package/dist/features/codex-notifier/outbox.d.ts.map +1 -0
  269. package/dist/features/codex-notifier/outbox.js +225 -0
  270. package/dist/features/codex-notifier/outbox.js.map +1 -0
  271. package/dist/features/codex-notifier/paths.d.ts +7 -0
  272. package/dist/features/codex-notifier/paths.d.ts.map +1 -0
  273. package/dist/features/codex-notifier/paths.js +20 -0
  274. package/dist/features/codex-notifier/paths.js.map +1 -0
  275. package/dist/features/codex-notifier/screen-lock.d.ts +12 -0
  276. package/dist/features/codex-notifier/screen-lock.d.ts.map +1 -0
  277. package/dist/features/codex-notifier/screen-lock.js +47 -0
  278. package/dist/features/codex-notifier/screen-lock.js.map +1 -0
  279. package/dist/features/codex-notifier/side-conversation-monitor.d.ts +102 -0
  280. package/dist/features/codex-notifier/side-conversation-monitor.d.ts.map +1 -0
  281. package/dist/features/codex-notifier/side-conversation-monitor.js +774 -0
  282. package/dist/features/codex-notifier/side-conversation-monitor.js.map +1 -0
  283. package/dist/features/codex-notifier/types.d.ts +42 -0
  284. package/dist/features/codex-notifier/types.d.ts.map +1 -0
  285. package/dist/features/codex-notifier/types.js +2 -0
  286. package/dist/features/codex-notifier/types.js.map +1 -0
  287. package/dist/features/codex-notifier/worker-lock.d.ts +8 -0
  288. package/dist/features/codex-notifier/worker-lock.d.ts.map +1 -0
  289. package/dist/features/codex-notifier/worker-lock.js +108 -0
  290. package/dist/features/codex-notifier/worker-lock.js.map +1 -0
  291. package/dist/global-config.d.ts +13 -0
  292. package/dist/global-config.d.ts.map +1 -1
  293. package/dist/global-config.js +30 -0
  294. package/dist/global-config.js.map +1 -1
  295. package/dist/im/lark/card-builder.js +2 -2
  296. package/dist/im/lark/card-builder.js.map +1 -1
  297. package/dist/im/lark/card-handler.d.ts +2 -0
  298. package/dist/im/lark/card-handler.d.ts.map +1 -1
  299. package/dist/im/lark/card-handler.js +165 -34
  300. package/dist/im/lark/card-handler.js.map +1 -1
  301. package/dist/im/lark/client.d.ts +62 -4
  302. package/dist/im/lark/client.d.ts.map +1 -1
  303. package/dist/im/lark/client.js +237 -29
  304. package/dist/im/lark/client.js.map +1 -1
  305. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  306. package/dist/im/lark/event-dispatcher.js +20 -30
  307. package/dist/im/lark/event-dispatcher.js.map +1 -1
  308. package/dist/services/codex-app-threads.d.ts +47 -0
  309. package/dist/services/codex-app-threads.d.ts.map +1 -1
  310. package/dist/services/codex-app-threads.js +502 -10
  311. package/dist/services/codex-app-threads.js.map +1 -1
  312. package/dist/services/exact-chat-grant.d.ts +83 -0
  313. package/dist/services/exact-chat-grant.d.ts.map +1 -0
  314. package/dist/services/exact-chat-grant.js +249 -0
  315. package/dist/services/exact-chat-grant.js.map +1 -0
  316. package/dist/services/group-creator.d.ts +11 -0
  317. package/dist/services/group-creator.d.ts.map +1 -1
  318. package/dist/services/group-creator.js +18 -9
  319. package/dist/services/group-creator.js.map +1 -1
  320. package/dist/services/local-cli-opener.d.ts.map +1 -1
  321. package/dist/services/local-cli-opener.js +10 -5
  322. package/dist/services/local-cli-opener.js.map +1 -1
  323. package/dist/services/traex-user-input.d.ts +19 -0
  324. package/dist/services/traex-user-input.d.ts.map +1 -0
  325. package/dist/services/traex-user-input.js +57 -0
  326. package/dist/services/traex-user-input.js.map +1 -0
  327. package/dist/setup/open-platform-automation.d.ts +24 -3
  328. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  329. package/dist/setup/open-platform-automation.js +45 -8
  330. package/dist/setup/open-platform-automation.js.map +1 -1
  331. package/dist/setup/verify-permissions.d.ts +18 -0
  332. package/dist/setup/verify-permissions.d.ts.map +1 -1
  333. package/dist/setup/verify-permissions.js +60 -0
  334. package/dist/setup/verify-permissions.js.map +1 -1
  335. package/dist/types.d.ts +70 -4
  336. package/dist/types.d.ts.map +1 -1
  337. package/dist/utils/stuck-detector.d.ts +45 -0
  338. package/dist/utils/stuck-detector.d.ts.map +1 -0
  339. package/dist/utils/stuck-detector.js +188 -0
  340. package/dist/utils/stuck-detector.js.map +1 -0
  341. package/dist/utils/stuck-key-guard.d.ts +75 -0
  342. package/dist/utils/stuck-key-guard.d.ts.map +1 -0
  343. package/dist/utils/stuck-key-guard.js +77 -0
  344. package/dist/utils/stuck-key-guard.js.map +1 -0
  345. package/dist/worker.js +705 -93
  346. package/dist/worker.js.map +1 -1
  347. package/package.json +1 -1
  348. package/dist/dashboard-web/chunks/bot-defaults-page-B5AY4B2J.js +0 -5
  349. package/dist/dashboard-web/chunks/sessions-page-2VECQ27T.js +0 -3
  350. package/dist/dashboard-web/chunks/settings-page-HMIUE2JA.js +0 -2
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
11
11
  import { ensureSkills, ensureAskSkill, ensurePluginSkills, ensureWhiteboardSkill, removeGlobalBotmuxSkills } from '../skills/installer.js';
12
12
  import { shouldInstallGlobalSkills } from '../skills/injection-mode.js';
13
13
  import { whiteboardEnabled } from '../services/whiteboard-store.js';
14
- import { installHook } from '../adapters/hook-installer.js';
14
+ import { cleanupTraexAskHooks, installHook } from '../adapters/hook-installer.js';
15
15
  import { hookCommandFor } from '../adapters/hook-command.js';
16
16
  import { randomBytes, randomUUID } from 'node:crypto';
17
17
  import { config } from '../config.js';
@@ -24,6 +24,7 @@ import { loadFrozenCards, saveFrozenCards } from '../services/frozen-card-store.
24
24
  import { hashUrlForLog, cancelRiffTaskById } from '../adapters/backend/riff-backend.js';
25
25
  import { logger } from '../utils/logger.js';
26
26
  import { createCliAdapterSync } from '../adapters/cli/registry.js';
27
+ import { traeHome } from '../services/traex-paths.js';
27
28
  import { botLocale, localeForBot, t as tr } from '../i18n/index.js';
28
29
  import { claudeJsonlPathForSession } from '../adapters/cli/claude-code.js';
29
30
  import { findUniqueClaudeSessionByCwd } from './session-discovery.js';
@@ -34,7 +35,7 @@ import { listDocSubscriptionsForSession, removeDocSubscription } from '../servic
34
35
  import { TmuxBackend } from '../adapters/backend/tmux-backend.js';
35
36
  import { HerdrBackend } from '../adapters/backend/herdr-backend.js';
36
37
  import { sandboxEnabled } from '../adapters/backend/sandbox.js';
37
- import { isSuspendableBackendType, getSessionPersistentBackendType, persistentSessionName, killPersistentSession, resolvePairedSpawnBackendType } from './persistent-backend.js';
38
+ import { isSuspendableBackendType, getSessionPersistentBackendType, persistentBackendTargetForSession, killPersistentBackendTarget, managedTargetsForCliChange, resolvePairedSpawnBackendType, resolvePersistentBackendTarget } from './persistent-backend.js';
38
39
  import { getBot, getAllBots, resolveBrandLabel } from '../bot-registry.js';
39
40
  /** A random id minted once per daemon process (this lifetime). Stamped onto
40
41
  * isolated persistent panes so a suspend→resume reattach (same id) is
@@ -79,7 +80,9 @@ import { isLocalCliOpenEnabled, isLocalCliOpenReady } from '../services/local-cl
79
80
  import { isSilentScheduledTurn } from './silent-schedule-turns.js';
80
81
  import { writeDeferredTopicBinding } from './deferred-topic-binding.js';
81
82
  import { deferWorkerSpawnDuringDeviceIsolation } from './device-isolation-activation.js';
83
+ import { buildBotmuxLarkNativeSessionTitle, extractBotmuxLarkNativeSessionTitlePrompt, } from './session-title.js';
82
84
  import { acknowledgeSessionReady } from './session-ready-handshake.js';
85
+ import { recordDispatchInputCommit } from './dispatch.js';
83
86
  const __filename = fileURLToPath(import.meta.url);
84
87
  const __dirname = dirname(__filename);
85
88
  const WORKER_SIGTERM_BACKSTOP_MS = 2_000;
@@ -652,13 +655,13 @@ export async function postPrivateSnapshotCard(ds, audience) {
652
655
  * Deliver the write-enabled session card (the "🔑 获取操作链接" card, which carries
653
656
  * a write-token terminal URL + manage buttons) privately to a single operator.
654
657
  *
655
- * Prefers an in-chat "visible-to-you" ephemeral card so the operator never has
656
- * to leave the conversation. Feishu's ephemeral API only works in plain `group`
657
- * chats topic / thread groups reject with {@link LARK_CODE_EPHEMERAL_NOT_GROUP}
658
- * (18053) and p2p chats are unsupported and chatType can't distinguish a topic
659
- * group from a regular one (both record 'group'), so we attempt ephemeral for any
660
- * non-p2p chat and fall back to a private DM on ANY failure. p2p chats skip the
661
- * doomed ephemeral attempt and DM directly (the DM lands in that same 1:1 chat).
658
+ * Prefers an in-chat "visible-to-you" ephemeral card in a flat group so the
659
+ * operator never has to leave the conversation. Thread-scope sessions and
660
+ * chat-scope sessions currently folded into a thread go straight to DM:
661
+ * Feishu may accept their ephemeral message without rendering it in the topic
662
+ * panel. p2p chats also DM directly (the DM lands in that same 1:1 chat).
663
+ *
664
+ * Other group chats attempt ephemeral first and fall back to DM on ANY failure.
662
665
  *
663
666
  * Both channels are private, so the DM fallback never leaks the write token —
664
667
  * unlike the private /card snapshot (which fails closed), here we fail OVER.
@@ -667,7 +670,10 @@ export async function postPrivateSnapshotCard(ds, audience) {
667
670
  */
668
671
  export async function deliverWriteLinkCard(ds, operatorOpenId, cardJson) {
669
672
  const who = operatorOpenId.substring(0, 8);
670
- if (ds.chatType !== 'p2p') {
673
+ const replyTarget = ds.currentReplyTarget ?? ds.session.currentReplyTarget;
674
+ const isThreaded = ds.scope === 'thread'
675
+ || (!!replyTarget?.rootMessageId && replyTarget.quoteOnly !== true);
676
+ if (ds.chatType !== 'p2p' && !isThreaded) {
671
677
  try {
672
678
  await sendEphemeralCard(ds.larkAppId, ds.chatId, operatorOpenId, cardJson);
673
679
  logger.info(`[${tag(ds)}] write link delivered via ephemeral card to ${who}…`);
@@ -909,6 +915,12 @@ const skillsInstalledCliIds = new Set();
909
915
  */
910
916
  export function ensureCliSkills(cliId, cliPathOverride) {
911
917
  const adapter = createCliAdapterSync(cliId, cliPathOverride);
918
+ if (cliId === 'traex') {
919
+ cleanupTraexAskHooks([
920
+ join(traeHome(), 'hooks.json'),
921
+ join(traeHome(), 'cli', 'hooks.json'),
922
+ ]);
923
+ }
912
924
  // Claude-family CLIs deliver skills per-session via `--plugin-dir` (no global
913
925
  // leak), so they always materialise their plugin dir — the builtin-injection
914
926
  // mode does not apply to them. Everything below the branch is the global-dir
@@ -1084,6 +1096,10 @@ export function killWorker(ds) {
1084
1096
  // daemon-side copy synchronously; the worker may never get a chance to send
1085
1097
  // its ordered revoke IPC on close/crash paths.
1086
1098
  ds.managedTurnOrigin = undefined;
1099
+ // The worker/CLI generation is ending — any outstanding stuck-warning card
1100
+ // must be invalidated so a late click cannot inject keys into a replacement
1101
+ // worker (or into nothing, if no replacement comes).
1102
+ invalidateStuckWarning(ds, 'killWorker');
1087
1103
  if (!ds.worker || ds.worker.killed) {
1088
1104
  // No live worker to receive {type:'close'}, so its destroySession() — which
1089
1105
  // tears down the persistent backing session (tmux/herdr/zellij) — never
@@ -1149,7 +1165,7 @@ function destroyOrphanedBackingSession(ds) {
1149
1165
  if (!backendType)
1150
1166
  return;
1151
1167
  try {
1152
- killPersistentSession(backendType, persistentSessionName(backendType, ds.session.sessionId));
1168
+ killPersistentBackendTarget(persistentBackendTargetForSession(ds));
1153
1169
  logger.info(`[${tag(ds)}] killWorker: no live worker — destroyed orphaned ${backendType} backing session`);
1154
1170
  }
1155
1171
  catch (err) {
@@ -1201,6 +1217,10 @@ export function suspendWorker(ds, reason = 'suspended_idle') {
1201
1217
  ds.workerToken = null;
1202
1218
  ds.workerViewToken = null;
1203
1219
  ds.managedTurnOrigin = undefined;
1220
+ // The worker is being suspended — the CLI will be destroyed and cold-resumed
1221
+ // later. Invalidate any stuck-warning card so a late click cannot inject keys
1222
+ // after the CLI is gone (or into a different CLI on resume).
1223
+ invalidateStuckWarning(ds, 'suspendWorker');
1204
1224
  // Screen state describes the process we just stopped. Keeping it would make
1205
1225
  // the dashboard hydrate this process-less logical session as idle/working.
1206
1226
  ds.lastScreenStatus = undefined;
@@ -1617,11 +1637,32 @@ export function sendWorkerInput(ds, payload, turnId, opts = {}) {
1617
1637
  return false;
1618
1638
  const normalized = typeof payload === 'string' ? { content: payload } : payload;
1619
1639
  const codexAppInput = codexAppInputForSession(ds, normalized.codexAppInput, turnId);
1640
+ let nativeSessionTitlePrompt;
1641
+ let nativeSessionTitle;
1642
+ if (ds.session.nativeSessionTitleAwaitingContent && !ds.session.nativeSessionTitleUserDefined && !ds.adoptedFrom) {
1643
+ const bot = getBot(ds.larkAppId);
1644
+ const effectiveCliId = ds.session.cliId ?? bot.config.cliId;
1645
+ if (effectiveCliId === 'codex') {
1646
+ nativeSessionTitlePrompt = extractBotmuxLarkNativeSessionTitlePrompt(normalized.codexAppInput?.text ?? normalized.content, bot.botName ? [{ name: bot.botName }] : undefined);
1647
+ if (nativeSessionTitlePrompt) {
1648
+ nativeSessionTitle = buildBotmuxLarkNativeSessionTitle(nativeSessionTitlePrompt);
1649
+ ds.session.nativeSessionTitle = nativeSessionTitle;
1650
+ ds.session.nativeSessionTitleAwaitingContent = undefined;
1651
+ if (ds.initConfig) {
1652
+ ds.initConfig.nativeSessionTitle = nativeSessionTitle;
1653
+ ds.initConfig.nativeSessionTitlePrompt = nativeSessionTitlePrompt;
1654
+ }
1655
+ sessionStore.updateSession(ds.session);
1656
+ }
1657
+ }
1658
+ }
1620
1659
  const vcMeetingImTurnOrigin = resolveVcMeetingImTurnOrigin(ds.session, turnId);
1621
1660
  ds.worker.send({
1622
1661
  type: 'message',
1623
1662
  content: normalized.content,
1624
1663
  ...(codexAppInput ? { codexAppInput } : {}),
1664
+ ...(nativeSessionTitle ? { nativeSessionTitle } : {}),
1665
+ ...(nativeSessionTitlePrompt ? { nativeSessionTitlePrompt } : {}),
1625
1666
  ...(turnId ? { turnId } : {}),
1626
1667
  ...(opts.dispatchAttempt !== undefined ? { dispatchAttempt: opts.dispatchAttempt } : {}),
1627
1668
  ...(vcMeetingImTurnOrigin
@@ -1737,6 +1778,10 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
1737
1778
  }
1738
1779
  sessionStore.updateSession(ds.session);
1739
1780
  }
1781
+ // Reserve and durably publish the replacement lifetime before killing an
1782
+ // existing worker. A failed reservation leaves the old worker untouched;
1783
+ // a successful reservation immediately invalidates any late old-worker ACK.
1784
+ const workerGeneration = reserveWorkerGeneration(ds);
1740
1785
  // Guard against double-fork: if a worker is already running, kill it first
1741
1786
  if (ds.worker && !ds.worker.killed) {
1742
1787
  logger.warn(`[${t}] Worker already running (pid: ${ds.worker.pid}), killing before re-fork`);
@@ -1769,6 +1814,38 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
1769
1814
  reclaimParkedCrashDiagnostic(ds);
1770
1815
  const agentCfg = sessionAgentConfig(ds, botCfg);
1771
1816
  ensureCliEnv(agentCfg.cliId, agentCfg.cliPathOverride);
1817
+ let nativeSessionTitle;
1818
+ let nativeSessionTitlePrompt;
1819
+ if (agentCfg.cliId === 'codex' && !ds.adoptedFrom) {
1820
+ const isFreshNativeSession = !resume && !ds.session.cliSessionId;
1821
+ const titlePrompt = extractBotmuxLarkNativeSessionTitlePrompt(promptPayload.codexAppInput?.text ?? prompt, bot.botName ? [{ name: bot.botName }] : undefined);
1822
+ if (isFreshNativeSession && !ds.session.nativeSessionTitleUserDefined) {
1823
+ ds.session.nativeSessionTitle = buildBotmuxLarkNativeSessionTitle(titlePrompt ? ds.session.title : undefined, bot.botName ? [{ name: bot.botName }] : undefined, ds.chatType === 'group' ? ds.session.chatDisplayName : undefined);
1824
+ ds.session.nativeSessionTitleAwaitingContent = titlePrompt ? undefined : true;
1825
+ nativeSessionTitlePrompt = titlePrompt;
1826
+ sessionStore.updateSession(ds.session);
1827
+ }
1828
+ else if (ds.session.nativeSessionTitleAwaitingContent
1829
+ && !ds.session.nativeSessionTitleUserDefined
1830
+ && titlePrompt) {
1831
+ ds.session.nativeSessionTitle = buildBotmuxLarkNativeSessionTitle(titlePrompt);
1832
+ ds.session.nativeSessionTitleAwaitingContent = undefined;
1833
+ nativeSessionTitlePrompt = titlePrompt;
1834
+ sessionStore.updateSession(ds.session);
1835
+ }
1836
+ else if (isFreshNativeSession && !ds.session.nativeSessionTitle) {
1837
+ ds.session.nativeSessionTitle = ds.session.title;
1838
+ sessionStore.updateSession(ds.session);
1839
+ }
1840
+ if (isFreshNativeSession
1841
+ || (resume && !!ds.session.cliSessionId && !!ds.session.nativeSessionTitle)
1842
+ || (resume
1843
+ && !!ds.session.nativeSessionTitleAwaitingContent
1844
+ && !!ds.session.nativeSessionTitle)
1845
+ || (!!nativeSessionTitlePrompt && !!ds.session.nativeSessionTitle)) {
1846
+ nativeSessionTitle = ds.session.nativeSessionTitle;
1847
+ }
1848
+ }
1772
1849
  // Claude Code blocks on the interactive folder-trust dialog the first time
1773
1850
  // it runs in an untrusted workingDir; pre-accept it so the spawn doesn't hang.
1774
1851
  // Seed CLI (Claude Code fork) has the same dialog — drive both off the
@@ -1893,10 +1970,15 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
1893
1970
  // getSessionPersistentBackendType). A brand-new session (no stamp) resolves
1894
1971
  // from live config, so a dashboard backend switch only affects NEW sessions.
1895
1972
  backendType: resolvePairedSpawnBackendType(agentCfg.cliId, ds.session.backendType, botCfg.backendType, config.daemon.backendType),
1973
+ // Shared Herdr is not derivable from sessionId: preserve the exact host +
1974
+ // managed-agent affinity across daemon/worker replacement.
1975
+ persistentBackendTarget: ds.session.persistentBackendTarget,
1896
1976
  backendConfig: botCfg.riff,
1897
1977
  riffParentTaskId: ds.session.riffParentTaskId,
1898
1978
  riffRepoDirs: ds.session.riffRepoDirs,
1899
1979
  deferredScheduleRun: ds.session.deferredScheduleRun,
1980
+ ...(nativeSessionTitle ? { nativeSessionTitle } : {}),
1981
+ ...(nativeSessionTitlePrompt ? { nativeSessionTitlePrompt } : {}),
1900
1982
  prompt,
1901
1983
  ...(promptCodexAppInput ? { promptCodexAppInput } : {}),
1902
1984
  resume,
@@ -1937,7 +2019,7 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
1937
2019
  sessionStore.updateSession(ds.session);
1938
2020
  }
1939
2021
  // Use shared handler for IPC messages and exit
1940
- setupWorkerHandlers(ds, worker, startupState);
2022
+ setupWorkerHandlers(ds, worker, startupState, workerGeneration);
1941
2023
  ds.worker = worker;
1942
2024
  ds.spawnedAt = Date.now();
1943
2025
  ds.cliVersion = currentCliVersion;
@@ -1967,11 +2049,57 @@ export function forkWorker(ds, promptInput, resumeOrTurnId = false) {
1967
2049
  recordOwnershipForDaemonSession(ds);
1968
2050
  }
1969
2051
  // ─── Shared worker IPC handler ──────────────────────────────────────────────
1970
- function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureNotified: false }) {
2052
+ /**
2053
+ * Clear the stuck-warning authority markers WITHOUT patching the card. Used by
2054
+ * ACK paths (tui_keys_delivered / stuck_warning_expired) where the caller has
2055
+ * already resolved the card to its final state — we only need to drop the
2056
+ * nonce/cardId/turnId so a late duplicate click cannot re-inject keys.
2057
+ */
2058
+ export function clearStuckWarningAuthority(ds) {
2059
+ ds.stuckWarningCardId = undefined;
2060
+ ds.stuckWarningTurnId = undefined;
2061
+ ds.stuckWarningNonce = undefined;
2062
+ ds.stuckWarningPageType = undefined;
2063
+ ds.stuckWarningProcessing = false;
2064
+ ds.stuckWarningCliLifetime = undefined;
2065
+ }
2066
+ /**
2067
+ * Resolve any active stuck-warning card (PATCH it to "done") AND clear its
2068
+ * markers. Called from every path where the CLI can recover or be replaced:
2069
+ * prompt_ready, claude_exit, worker exit/kill/suspend/refork, and the worker's
2070
+ * own stale-card notification. Centralising here avoids missing an exit path
2071
+ * and leaving a clickable card that can inject keys into a different CLI.
2072
+ *
2073
+ * ACK paths (tui_keys_delivered / stuck_warning_expired) do NOT use this —
2074
+ * they patch the card themselves with a context-specific message and then call
2075
+ * clearStuckWarningAuthority() to drop the markers.
2076
+ */
2077
+ export function invalidateStuckWarning(ds, reason) {
2078
+ if (!ds.stuckWarningCardId && !ds.stuckWarningTurnId && ds.stuckWarningNonce === undefined)
2079
+ return;
2080
+ const t = tag(ds);
2081
+ if (ds.stuckWarningCardId) {
2082
+ const locDs = localeForBot(ds.larkAppId);
2083
+ const resolvedCard = buildTuiPromptResolvedCard(tr('card.action.tui_done', undefined, locDs), locDs);
2084
+ updateMessage(ds.larkAppId, ds.stuckWarningCardId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to resolve stuck-warning card (${reason}): ${err}`));
2085
+ }
2086
+ logger.debug(`[${t}] invalidateStuckWarning (${reason}): turn=${ds.stuckWarningTurnId ?? 'none'} nonce=${ds.stuckWarningNonce ?? 'none'}`);
2087
+ clearStuckWarningAuthority(ds);
2088
+ }
2089
+ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureNotified: false }, reservedWorkerGeneration) {
1971
2090
  const cb = requireCallbacks();
1972
2091
  const t = tag(ds);
1973
- const workerGeneration = (ds.workerGeneration ?? 0) + 1;
1974
- ds.workerGeneration = workerGeneration;
2092
+ const workerGeneration = reservedWorkerGeneration
2093
+ ?? reserveWorkerGeneration(ds);
2094
+ if (ds.workerGeneration !== workerGeneration
2095
+ || ds.session.workerGeneration !== workerGeneration) {
2096
+ throw new Error('worker generation reservation changed before IPC setup');
2097
+ }
2098
+ // A new worker generation is starting. As a backstop, invalidate any
2099
+ // stuck-warning card posted by the previous generation — explicit kill/suspend/
2100
+ // exit paths should already have done this, but fork/refork/takeover paths
2101
+ // can leave a stale card that would otherwise inject keys into the new CLI.
2102
+ invalidateStuckWarning(ds, 'new_worker_generation');
1975
2103
  // Managed turn authority is issued by one concrete worker lifetime. A
1976
2104
  // replacement must advertise a fresh capability before daemon-mediated
1977
2105
  // exits may use it; carrying the old value across a restore/refork would
@@ -2063,6 +2191,29 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2063
2191
  worker.on('message', async (msg) => {
2064
2192
  const effectiveCliId = sessionCliId(ds, botCfg);
2065
2193
  switch (msg.type) {
2194
+ case 'persistent_backend_target': {
2195
+ ds.session.persistentBackendTarget = msg.target;
2196
+ sessionStore.updateSession(ds.session);
2197
+ break;
2198
+ }
2199
+ case 'turn_input_committed': {
2200
+ // Bind the receipt to the exact live worker generation. A late ACK
2201
+ // from a replaced worker cannot make the replacement appear to have
2202
+ // accepted this dispatch turn.
2203
+ if (ds.worker !== worker
2204
+ || ds.workerGeneration !== workerGeneration
2205
+ || ds.session.workerGeneration !== workerGeneration) {
2206
+ logger.warn(`[${t}] Ignored turn_input_committed from stale worker generation`);
2207
+ break;
2208
+ }
2209
+ if (recordDispatchInputCommit(ds.session, msg.turnId, workerGeneration)) {
2210
+ sessionStore.updateSession(ds.session);
2211
+ }
2212
+ else {
2213
+ logger.warn(`[${t}] Ignored unbound input commit turn=${msg.turnId.slice(0, 16)}`);
2214
+ }
2215
+ break;
2216
+ }
2066
2217
  case 'session_ready_ack': {
2067
2218
  if (ds.worker !== worker) {
2068
2219
  logger.warn(`[${t}] Ignored session_ready_ack from stale worker generation`);
@@ -2096,6 +2247,10 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2096
2247
  ds.workerViewToken = msg.viewToken ?? null;
2097
2248
  // Persist port so it can be reused after daemon restart
2098
2249
  ds.session.webPort = msg.port;
2250
+ // Dashboard「复现命令」:worker 上报本次冷启的近似复现命令。只存内存字段、
2251
+ // 绝不落盘(含凭证);warm reattach 时 worker 不重算,故为空——这是有意的
2252
+ // (reattach 不代表本次新算命令真的执行了)。worker 每次 ready 会重报。
2253
+ ds.spawnCommand = msg.spawnCommand;
2099
2254
  sessionStore.updateSession(ds.session);
2100
2255
  const readOnlyUrl = buildTerminalUrl(ds);
2101
2256
  const writeUrl = buildTerminalUrl(ds, { write: true });
@@ -2123,6 +2278,18 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2123
2278
  logger.info(`[${t}] Managed/silent turn — suppressing ready/streaming card output`);
2124
2279
  break;
2125
2280
  }
2281
+ // Pi can finish a very fast startup turn through `botmux send` before
2282
+ // Herdr reports idle and the worker emits ready. Posting the initial
2283
+ // card now would put a stale "Starting" card *after* the final reply,
2284
+ // with no later transcript output to withdraw it (the explicit send
2285
+ // intentionally suppresses bridge fallback). Keep the terminal/runtime
2286
+ // state above, but suppress this already-completed turn's card.
2287
+ if (msg.replyAlreadySent) {
2288
+ ds.streamCardPending = false;
2289
+ persistStreamCardState(ds);
2290
+ logger.info(`[${t}] Explicit reply landed before worker ready — skipping stale starting card`);
2291
+ break;
2292
+ }
2126
2293
  // Bot opted out of the streaming card: the terminal is up and the
2127
2294
  // final answer will still arrive via `botmux send`; just don't post the
2128
2295
  // live status card. (workerPort/token above are still set so the web
@@ -2277,6 +2444,8 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2277
2444
  break;
2278
2445
  }
2279
2446
  case 'prompt_ready': {
2447
+ if (ds.worker !== worker)
2448
+ break;
2280
2449
  logger.info(`[${t}] ${getCliDisplayName(effectiveCliId)} is ready for input`);
2281
2450
  // A live prompt means a (re)spawn reached a working CLI — clear the lazy
2282
2451
  // cold-resume marker set when we parked a crash diagnostic shell. The
@@ -2319,6 +2488,8 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2319
2488
  ...(followUpCodexAppInput ? { codexAppInput: followUpCodexAppInput } : {}),
2320
2489
  }, { codexAppInputAccepted: !!followUpCodexAppInput });
2321
2490
  }
2491
+ // CLI reached its prompt — any previously posted stuck warning is stale.
2492
+ invalidateStuckWarning(ds, 'prompt_ready');
2322
2493
  break;
2323
2494
  }
2324
2495
  case 'cli_session_id': {
@@ -2340,6 +2511,21 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2340
2511
  }
2341
2512
  break;
2342
2513
  }
2514
+ case 'native_session_title_generated': {
2515
+ if (ds.worker !== worker || ds.session.nativeSessionTitleUserDefined)
2516
+ break;
2517
+ const title = msg.title.trim();
2518
+ if (!title)
2519
+ break;
2520
+ ds.session.nativeSessionTitle = title;
2521
+ ds.session.nativeSessionTitleAwaitingContent = undefined;
2522
+ if (ds.initConfig) {
2523
+ ds.initConfig.nativeSessionTitle = title;
2524
+ ds.initConfig.nativeSessionTitlePrompt = undefined;
2525
+ }
2526
+ sessionStore.updateSession(ds.session);
2527
+ break;
2528
+ }
2343
2529
  case 'screen_update': {
2344
2530
  // Wait for `ready` (workerPort) before any card work — the read link
2345
2531
  // is the LOCAL log terminal for every backend including riff
@@ -2551,6 +2737,137 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2551
2737
  }
2552
2738
  break;
2553
2739
  }
2740
+ case 'stuck_warning': {
2741
+ // Ignore stuck_warning from a stale worker generation — the CLI may have
2742
+ // been replaced and a new worker owns the session now.
2743
+ if (ds.worker !== worker)
2744
+ break;
2745
+ // AI-free StuckDetector fired: a written input hasn't produced a
2746
+ // completed turn within the timeout AND the PTY has been quiet, AND the
2747
+ // snapshot matches a known Codex hook-review screen. The detector only
2748
+ // emits when it matches level 1 or level 2 — there is no "unknown
2749
+ // stall" path, so we always build an interactive card with the exact
2750
+ // keys the screen documents.
2751
+ // Dedup: skip if we already posted a warning for THIS turn, OR if a
2752
+ // stuck-warning card is already active (covers the no-turnId case where
2753
+ // a second stall fires before the first card is resolved).
2754
+ const isDuplicateTurn = msg.turnId !== undefined && ds.stuckWarningTurnId === msg.turnId;
2755
+ const hasActiveCard = !!ds.stuckWarningCardId;
2756
+ if (isDuplicateTurn || hasActiveCard) {
2757
+ logger.debug(`[${t}] Stuck warning dedup skipped (turn=${msg.turnId ?? 'none'}, activeCard=${hasActiveCard})`);
2758
+ break;
2759
+ }
2760
+ // Allocate a daemon-side nonce for this warning (NOT the worker's
2761
+ // generation — the daemon is the authority on which warning is active).
2762
+ // If the CLI recovers (prompt_ready) or the worker is replaced while the
2763
+ // POST is in flight, invalidateStuckWarning clears the nonce; when the
2764
+ // POST returns we check it is still current and, if not, resolve the card
2765
+ // immediately instead of registering it as active.
2766
+ // Allocate a daemon-side nonce for this warning from a monotonic
2767
+ // counter that is NEVER cleared (even when the active authority is
2768
+ // dropped). This prevents the nonce-reuse race: warning nonce=1 POST
2769
+ // awaits → prompt_ready clears active nonce → warning nonce=1 again
2770
+ // would let the old POST register against the new authority. With a
2771
+ // monotonic counter the second warning gets nonce=2, and the old
2772
+ // POST's nonce=1 check fails.
2773
+ const nonce = (ds.stuckWarningNonceCounter ?? 0) + 1;
2774
+ ds.stuckWarningNonceCounter = nonce;
2775
+ ds.stuckWarningNonce = nonce;
2776
+ ds.stuckWarningTurnId = msg.turnId;
2777
+ ds.stuckWarningCliLifetime = msg.cliLifetime;
2778
+ const pageType = msg.matchedPattern;
2779
+ ds.stuckWarningPageType = pageType;
2780
+ const secs = Math.round(msg.elapsedMs / 1000);
2781
+ logger.info(`[${t}] Stuck warning: turn unresolved for ${secs}s (${pageType}) nonce=${nonce}`);
2782
+ emitSessionLifecycleHook(ds, 'session.requires_attention', {
2783
+ reason: 'stuck_warning',
2784
+ elapsedMs: msg.elapsedMs,
2785
+ matchedPattern: pageType,
2786
+ });
2787
+ if (managedAuxUiSuppressed(msg.turnId, msg.dispatchAttempt))
2788
+ break;
2789
+ let description;
2790
+ let options;
2791
+ if (pageType === 'hook review level 2') {
2792
+ // Level 2 — per-hook review: t=trust, Esc=go back. No Enter here.
2793
+ description = `⚠️ Codex 卡在单项 hook 审核界面——消息已写入但 ${secs}s 未完成处理。\n\n选择操作以继续,或打开终端手动处理:`;
2794
+ options = [
2795
+ { label: 't', text: '信任 (trust)', selected: false, type: 'confirm', keys: ['t'] },
2796
+ { label: 'Esc', text: '返回 (go back)', selected: false, type: 'select', keys: ['Escape'] },
2797
+ ];
2798
+ }
2799
+ else {
2800
+ // Level 1 — hooks browser: t=trust all, Enter=review hooks, Esc=close.
2801
+ description = `⚠️ Codex 卡在 hook 审核界面——消息已写入但 ${secs}s 未完成处理。\n\n选择操作以继续,或打开终端手动处理:`;
2802
+ options = [
2803
+ { label: 't', text: '信任全部 (trust all)', selected: false, type: 'confirm', keys: ['t'] },
2804
+ { label: 'Enter', text: '逐项审核 (review hooks)', selected: false, type: 'select', keys: ['Enter'] },
2805
+ { label: 'Esc', text: '关闭 (close)', selected: false, type: 'select', keys: ['Escape'] },
2806
+ ];
2807
+ }
2808
+ try {
2809
+ const cardJson = buildTuiPromptCard(sessionAnchorId(ds), ds.session.sessionId, description, options, false, undefined, loc);
2810
+ const cardMsgId = await scopedReply(cardJson, 'interactive', msg.turnId);
2811
+ // Authority check: if the warning was invalidated (prompt_ready,
2812
+ // CLI exit, worker replace) OR the worker was replaced while the POST
2813
+ // was in flight, resolve the card immediately and do NOT register it
2814
+ // as active — a late click would otherwise inject keys into a
2815
+ // recovered/replaced CLI. Verify the full tuple: same worker process,
2816
+ // same worker generation, same nonce.
2817
+ if (ds.worker !== worker || ds.workerGeneration !== workerGeneration || ds.stuckWarningNonce !== nonce) {
2818
+ logger.debug(`[${t}] Stuck warning card POSTed but authority stale (nonce=${nonce}, current=${ds.stuckWarningNonce ?? 'none'}, workerGen=${workerGeneration}) — resolving`);
2819
+ const locDs = localeForBot(ds.larkAppId);
2820
+ const resolvedCard = buildTuiPromptResolvedCard(tr('card.action.tui_done', undefined, locDs), locDs);
2821
+ updateMessage(ds.larkAppId, cardMsgId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to resolve stale stuck-warning card: ${err}`));
2822
+ break;
2823
+ }
2824
+ ds.stuckWarningCardId = cardMsgId;
2825
+ publishAttentionPatch(ds);
2826
+ }
2827
+ catch (err) {
2828
+ logger.warn(`[${t}] Failed to post stuck warning card: ${err}`);
2829
+ // Card send failed — clear markers ONLY if this nonce is still
2830
+ // current AND we still own the worker. A newer warning (nonce+1) may
2831
+ // have started while this POST was in flight; we must not wipe the
2832
+ // newer state.
2833
+ if (ds.worker === worker && ds.workerGeneration === workerGeneration && ds.stuckWarningNonce === nonce) {
2834
+ clearStuckWarningAuthority(ds);
2835
+ }
2836
+ }
2837
+ break;
2838
+ }
2839
+ case 'stuck_warning_expired': {
2840
+ // Ignore from a stale worker generation.
2841
+ if (ds.worker !== worker || ds.workerGeneration !== workerGeneration)
2842
+ break;
2843
+ // Worker refused to inject keys from a stuck-warning card because the
2844
+ // current screen no longer matches the page type the card was built for.
2845
+ // Resolve the card with a "page changed" message so the user knows the
2846
+ // action was NOT performed, then clear the authority.
2847
+ if (ds.stuckWarningNonce === msg.nonce && ds.stuckWarningCardId) {
2848
+ const locDs = localeForBot(ds.larkAppId);
2849
+ const resolvedCard = buildTuiPromptResolvedCard('页面已变化,未发送按键', locDs);
2850
+ updateMessage(ds.larkAppId, ds.stuckWarningCardId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to update stuck-warning card on expired: ${err}`));
2851
+ clearStuckWarningAuthority(ds);
2852
+ }
2853
+ break;
2854
+ }
2855
+ case 'tui_keys_delivered': {
2856
+ // Ignore from a stale worker generation.
2857
+ if (ds.worker !== worker || ds.workerGeneration !== workerGeneration)
2858
+ break;
2859
+ // Worker confirmed the keys were written to the PTY. Clear the card
2860
+ // authority and render success. We only do this AFTER the worker ACK
2861
+ // (not on click), so a rejected click (stuck_warning_expired) does not
2862
+ // falsely report success.
2863
+ if (ds.stuckWarningNonce === msg.nonce && ds.stuckWarningCardId) {
2864
+ const locDs = localeForBot(ds.larkAppId);
2865
+ const resolvedCard = buildTuiPromptResolvedCard(tr('card.action.tui_done', undefined, locDs), locDs);
2866
+ updateMessage(ds.larkAppId, ds.stuckWarningCardId, resolvedCard).catch(err => logger.debug(`[${t}] Failed to resolve stuck-warning card on delivered: ${err}`));
2867
+ clearStuckWarningAuthority(ds);
2868
+ }
2869
+ break;
2870
+ }
2554
2871
  case 'claude_exit': {
2555
2872
  // CLI-generation authority must not outlive the concrete worker/CLI
2556
2873
  // pair that issued it. A delayed message from a replaced Node worker
@@ -2560,6 +2877,10 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2560
2877
  break;
2561
2878
  }
2562
2879
  ds.managedTurnOrigin = undefined;
2880
+ // The worker/CLI generation ended. Disable an outstanding stuck-warning
2881
+ // card before any replacement worker can be attached; otherwise a late
2882
+ // click could inject its keys into the replacement CLI.
2883
+ invalidateStuckWarning(ds, 'claude_exit');
2563
2884
  logger.info(`[${t}] ${getCliDisplayName(effectiveCliId)} exited (code: ${msg.code}, signal: ${msg.signal})`);
2564
2885
  ds.hasHistory = true;
2565
2886
  try {
@@ -2979,6 +3300,19 @@ function setupWorkerHandlers(ds, worker, startupState = { ready: false, failureN
2979
3300
  ds.worker = null;
2980
3301
  ds.workerPort = null;
2981
3302
  ds.managedTurnOrigin = undefined;
3303
+ // This worker generation is gone. Invalidate any stuck-warning card it
3304
+ // posted so a late click cannot inject keys into a replacement worker.
3305
+ invalidateStuckWarning(ds, 'worker_exit');
3306
+ // Fence this lifetime before a polling dispatcher can observe its last
3307
+ // ACK. Keeping the old receipt is useful audit evidence, but the
3308
+ // persisted current generation advances immediately so it cannot count
3309
+ // as acceptance after the worker has died. A stale takeover worker never
3310
+ // enters this branch and therefore cannot fence the replacement.
3311
+ const fencedGeneration = Math.max(workerGeneration, ds.workerGeneration ?? 0, ds.session.workerGeneration ?? 0) + 1;
3312
+ ds.workerGeneration = fencedGeneration;
3313
+ ds.session.workerGeneration = fencedGeneration;
3314
+ ds.session.pid = undefined;
3315
+ sessionStore.updateSession(ds.session);
2982
3316
  }
2983
3317
  try {
2984
3318
  const notified = cb.onWorkerExit?.(ds, {
@@ -3345,6 +3679,28 @@ export const __testOnly_setupWorkerHandlers = setupWorkerHandlers;
3345
3679
  export const __testOnly_finishTurnReactions = finishTurnReactions;
3346
3680
  export const __testOnly_finalOutputDedupeKey = finalOutputDedupeKey;
3347
3681
  // ─── Fork adopt worker ──────────────────────────────────────────────────────
3682
+ function reserveWorkerGeneration(ds) {
3683
+ const previousDaemonGeneration = ds.workerGeneration;
3684
+ const previousSessionGeneration = ds.session.workerGeneration;
3685
+ const workerGeneration = Math.max(previousDaemonGeneration ?? 0, previousSessionGeneration ?? 0) + 1;
3686
+ ds.workerGeneration = workerGeneration;
3687
+ ds.session.workerGeneration = workerGeneration;
3688
+ try {
3689
+ sessionStore.updateSession(ds.session);
3690
+ }
3691
+ catch (error) {
3692
+ if (previousDaemonGeneration === undefined)
3693
+ delete ds.workerGeneration;
3694
+ else
3695
+ ds.workerGeneration = previousDaemonGeneration;
3696
+ if (previousSessionGeneration === undefined)
3697
+ delete ds.session.workerGeneration;
3698
+ else
3699
+ ds.session.workerGeneration = previousSessionGeneration;
3700
+ throw error;
3701
+ }
3702
+ return workerGeneration;
3703
+ }
3348
3704
  export function forkAdoptWorker(ds, opts) {
3349
3705
  const cb = requireCallbacks();
3350
3706
  const workerPath = join(__dirname, '..', 'worker.js');
@@ -3362,6 +3718,9 @@ export function forkAdoptWorker(ds, opts) {
3362
3718
  logger.warn(`[${t}] read-isolation bot: refusing to adopt existing CLI (would run unisolated); will cold-start isolated on next message`);
3363
3719
  return;
3364
3720
  }
3721
+ // Reserve before replacing an existing bridge worker for the same reason as
3722
+ // forkWorker: persistence failure must leave the old lifetime untouched.
3723
+ const workerGeneration = reserveWorkerGeneration(ds);
3365
3724
  // Guard against double-fork
3366
3725
  if (ds.worker && !ds.worker.killed) {
3367
3726
  logger.warn(`[${t}] Worker already running, killing before adopt-fork`);
@@ -3541,7 +3900,7 @@ export function forkAdoptWorker(ds, opts) {
3541
3900
  sessionStore.updateSession(ds.session);
3542
3901
  }
3543
3902
  // Use shared handler
3544
- setupWorkerHandlers(ds, worker, startupState);
3903
+ setupWorkerHandlers(ds, worker, startupState, workerGeneration);
3545
3904
  ds.worker = worker;
3546
3905
  ds.spawnedAt = Date.now();
3547
3906
  ds.cliVersion = '';
@@ -3705,9 +4064,17 @@ function cleanupPersistentBackendSessions(backendType, activeSessions_) {
3705
4064
  const currentCliId = config.daemon.cliId;
3706
4065
  if (!multiBot && lastCliId && lastCliId !== currentCliId) {
3707
4066
  logger.info(`CLI_ID changed (${lastCliId} → ${currentCliId}), killing all ${backendType} sessions`);
4067
+ // Legacy per-topic hosts are still enumerable by bmx-* name.
3708
4068
  for (const name of backend.listBotmuxSessions()) {
3709
4069
  backend.killSession(name);
3710
4070
  }
4071
+ // Machine-wide Herdr agents are not separate bmx-* sessions. Tear down
4072
+ // each persisted managed target precisely so a CLI switch cannot reattach
4073
+ // the old executable, while never stopping the shared `botmux` host or an
4074
+ // explicitly adopted user pane.
4075
+ for (const target of managedTargetsForCliChange(backendType, activeSessions_)) {
4076
+ killPersistentBackendTarget(target);
4077
+ }
3711
4078
  }
3712
4079
  else {
3713
4080
  const activeNames = new Set(activeSessions_.map(s => backend.sessionName(s.sessionId)));
@@ -3730,9 +4097,12 @@ function cleanupPersistentBackendSessions(backendType, activeSessions_) {
3730
4097
  continue;
3731
4098
  }
3732
4099
  if (botCliId && sessionCliId !== botCliId) {
3733
- const name = backend.sessionName(session.sessionId);
3734
- logger.info(`CLI mismatch for ${session.sessionId.substring(0, 8)} (session=${sessionCliId}, bot=${botCliId}), killing ${backendType} ${name}`);
3735
- backend.killSession(name);
4100
+ const target = resolvePersistentBackendTarget(backendType, session.sessionId, session.persistentBackendTarget);
4101
+ const label = target.backendType === 'herdr' && target.agentName
4102
+ ? `${target.sessionName}/${target.agentName}`
4103
+ : target.sessionName;
4104
+ logger.info(`CLI mismatch for ${session.sessionId.substring(0, 8)} (session=${sessionCliId}, bot=${botCliId}), killing ${backendType} ${label}`);
4105
+ killPersistentBackendTarget(target);
3736
4106
  }
3737
4107
  }
3738
4108
  }