botmux 3.10.0 → 3.11.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 (539) hide show
  1. package/dist/.runtime-build-id +1 -1
  2. package/dist/adapters/backend/herdr-backend.d.ts +4 -3
  3. package/dist/adapters/backend/herdr-backend.d.ts.map +1 -1
  4. package/dist/adapters/backend/herdr-backend.js +11 -8
  5. package/dist/adapters/backend/herdr-backend.js.map +1 -1
  6. package/dist/adapters/backend/pty-backend.d.ts +1 -1
  7. package/dist/adapters/backend/pty-backend.d.ts.map +1 -1
  8. package/dist/adapters/backend/pty-backend.js +4 -1
  9. package/dist/adapters/backend/pty-backend.js.map +1 -1
  10. package/dist/adapters/backend/riff-backend.d.ts +29 -5
  11. package/dist/adapters/backend/riff-backend.d.ts.map +1 -1
  12. package/dist/adapters/backend/riff-backend.js +302 -31
  13. package/dist/adapters/backend/riff-backend.js.map +1 -1
  14. package/dist/adapters/backend/sandbox.d.ts +6 -0
  15. package/dist/adapters/backend/sandbox.d.ts.map +1 -1
  16. package/dist/adapters/backend/sandbox.js +7 -0
  17. package/dist/adapters/backend/sandbox.js.map +1 -1
  18. package/dist/adapters/backend/tmux-backend.d.ts +1 -1
  19. package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
  20. package/dist/adapters/backend/tmux-backend.js +4 -1
  21. package/dist/adapters/backend/tmux-backend.js.map +1 -1
  22. package/dist/adapters/backend/tmux-pipe-backend.d.ts +1 -1
  23. package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
  24. package/dist/adapters/backend/tmux-pipe-backend.js +1 -1
  25. package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
  26. package/dist/adapters/backend/types.d.ts +37 -3
  27. package/dist/adapters/backend/types.d.ts.map +1 -1
  28. package/dist/adapters/backend/types.js.map +1 -1
  29. package/dist/adapters/backend/zellij-backend.d.ts +3 -3
  30. package/dist/adapters/backend/zellij-backend.d.ts.map +1 -1
  31. package/dist/adapters/backend/zellij-backend.js +9 -3
  32. package/dist/adapters/backend/zellij-backend.js.map +1 -1
  33. package/dist/adapters/backend/zellij-observe-backend.d.ts +3 -3
  34. package/dist/adapters/backend/zellij-observe-backend.d.ts.map +1 -1
  35. package/dist/adapters/backend/zellij-observe-backend.js +12 -7
  36. package/dist/adapters/backend/zellij-observe-backend.js.map +1 -1
  37. package/dist/adapters/cli/codex-app.d.ts.map +1 -1
  38. package/dist/adapters/cli/codex-app.js +11 -2
  39. package/dist/adapters/cli/codex-app.js.map +1 -1
  40. package/dist/adapters/cli/read-isolation.d.ts +35 -2
  41. package/dist/adapters/cli/read-isolation.d.ts.map +1 -1
  42. package/dist/adapters/cli/read-isolation.js +88 -7
  43. package/dist/adapters/cli/read-isolation.js.map +1 -1
  44. package/dist/adapters/cli/runner-input.d.ts +15 -14
  45. package/dist/adapters/cli/runner-input.d.ts.map +1 -1
  46. package/dist/adapters/cli/runner-input.js +64 -26
  47. package/dist/adapters/cli/runner-input.js.map +1 -1
  48. package/dist/adapters/cli/types.d.ts +20 -5
  49. package/dist/adapters/cli/types.d.ts.map +1 -1
  50. package/dist/bot-registry.d.ts +24 -0
  51. package/dist/bot-registry.d.ts.map +1 -1
  52. package/dist/bot-registry.js +110 -5
  53. package/dist/bot-registry.js.map +1 -1
  54. package/dist/cli/fleet-shutdown.d.ts +47 -0
  55. package/dist/cli/fleet-shutdown.d.ts.map +1 -0
  56. package/dist/cli/fleet-shutdown.js +387 -0
  57. package/dist/cli/fleet-shutdown.js.map +1 -0
  58. package/dist/cli/pm2-descriptor-guard.d.ts +19 -0
  59. package/dist/cli/pm2-descriptor-guard.d.ts.map +1 -0
  60. package/dist/cli/pm2-descriptor-guard.js +122 -0
  61. package/dist/cli/pm2-descriptor-guard.js.map +1 -0
  62. package/dist/cli/pm2-exact-start.d.ts +16 -0
  63. package/dist/cli/pm2-exact-start.d.ts.map +1 -0
  64. package/dist/cli/pm2-exact-start.js +63 -0
  65. package/dist/cli/pm2-exact-start.js.map +1 -0
  66. package/dist/cli/pm2-god-admission.d.ts +8 -0
  67. package/dist/cli/pm2-god-admission.d.ts.map +1 -0
  68. package/dist/cli/pm2-god-admission.js +25 -0
  69. package/dist/cli/pm2-god-admission.js.map +1 -0
  70. package/dist/cli/pm2-jlist.d.ts +13 -0
  71. package/dist/cli/pm2-jlist.d.ts.map +1 -0
  72. package/dist/cli/pm2-jlist.js +108 -0
  73. package/dist/cli/pm2-jlist.js.map +1 -0
  74. package/dist/cli/pm2-preflight.d.ts +10 -0
  75. package/dist/cli/pm2-preflight.d.ts.map +1 -0
  76. package/dist/cli/pm2-preflight.js +23 -0
  77. package/dist/cli/pm2-preflight.js.map +1 -0
  78. package/dist/cli/pm2-shutdown-capability.d.ts +27 -0
  79. package/dist/cli/pm2-shutdown-capability.d.ts.map +1 -0
  80. package/dist/cli/pm2-shutdown-capability.js +161 -0
  81. package/dist/cli/pm2-shutdown-capability.js.map +1 -0
  82. package/dist/cli/pm2-start-transaction.d.ts +55 -0
  83. package/dist/cli/pm2-start-transaction.d.ts.map +1 -0
  84. package/dist/cli/pm2-start-transaction.js +220 -0
  85. package/dist/cli/pm2-start-transaction.js.map +1 -0
  86. package/dist/cli/send-dispatch.d.ts +12 -0
  87. package/dist/cli/send-dispatch.d.ts.map +1 -1
  88. package/dist/cli/send-dispatch.js +31 -4
  89. package/dist/cli/send-dispatch.js.map +1 -1
  90. package/dist/cli/session-list-liveness.d.ts +1 -0
  91. package/dist/cli/session-list-liveness.d.ts.map +1 -1
  92. package/dist/cli/session-list-liveness.js +6 -0
  93. package/dist/cli/session-list-liveness.js.map +1 -1
  94. package/dist/cli/session-picker-layout.d.ts +26 -0
  95. package/dist/cli/session-picker-layout.d.ts.map +1 -0
  96. package/dist/cli/session-picker-layout.js +123 -0
  97. package/dist/cli/session-picker-layout.js.map +1 -0
  98. package/dist/cli/session-picker-viewport.d.ts +49 -0
  99. package/dist/cli/session-picker-viewport.d.ts.map +1 -0
  100. package/dist/cli/session-picker-viewport.js +46 -0
  101. package/dist/cli/session-picker-viewport.js.map +1 -0
  102. package/dist/cli/supervisor-shutdown-client.d.ts +31 -0
  103. package/dist/cli/supervisor-shutdown-client.d.ts.map +1 -0
  104. package/dist/cli/supervisor-shutdown-client.js +146 -0
  105. package/dist/cli/supervisor-shutdown-client.js.map +1 -0
  106. package/dist/cli/terminal-width.d.ts +48 -0
  107. package/dist/cli/terminal-width.d.ts.map +1 -0
  108. package/dist/cli/terminal-width.js +119 -0
  109. package/dist/cli/terminal-width.js.map +1 -0
  110. package/dist/cli/vc-agent.d.ts.map +1 -1
  111. package/dist/cli/vc-agent.js +1 -1
  112. package/dist/cli/vc-agent.js.map +1 -1
  113. package/dist/cli.d.ts.map +1 -1
  114. package/dist/cli.js +2269 -716
  115. package/dist/cli.js.map +1 -1
  116. package/dist/codex-app-runner.js +1630 -125
  117. package/dist/codex-app-runner.js.map +1 -1
  118. package/dist/core/ask-api.d.ts +8 -1
  119. package/dist/core/ask-api.d.ts.map +1 -1
  120. package/dist/core/ask-api.js +18 -0
  121. package/dist/core/ask-api.js.map +1 -1
  122. package/dist/core/ask-broker.d.ts +20 -0
  123. package/dist/core/ask-broker.d.ts.map +1 -1
  124. package/dist/core/ask-broker.js +467 -47
  125. package/dist/core/ask-broker.js.map +1 -1
  126. package/dist/core/ask-persist-store.d.ts +126 -0
  127. package/dist/core/ask-persist-store.d.ts.map +1 -0
  128. package/dist/core/ask-persist-store.js +188 -0
  129. package/dist/core/ask-persist-store.js.map +1 -0
  130. package/dist/core/ask-types.d.ts +80 -0
  131. package/dist/core/ask-types.d.ts.map +1 -1
  132. package/dist/core/ask-types.js +62 -0
  133. package/dist/core/ask-types.js.map +1 -1
  134. package/dist/core/cli-session-id-publisher.d.ts +20 -0
  135. package/dist/core/cli-session-id-publisher.d.ts.map +1 -0
  136. package/dist/core/cli-session-id-publisher.js +21 -0
  137. package/dist/core/cli-session-id-publisher.js.map +1 -0
  138. package/dist/core/command-handler.d.ts.map +1 -1
  139. package/dist/core/command-handler.js +427 -255
  140. package/dist/core/command-handler.js.map +1 -1
  141. package/dist/core/cost-calculator.d.ts +13 -0
  142. package/dist/core/cost-calculator.d.ts.map +1 -1
  143. package/dist/core/cost-calculator.js +385 -26
  144. package/dist/core/cost-calculator.js.map +1 -1
  145. package/dist/core/dashboard-ipc-server.d.ts +9 -0
  146. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  147. package/dist/core/dashboard-ipc-server.js +842 -288
  148. package/dist/core/dashboard-ipc-server.js.map +1 -1
  149. package/dist/core/explicit-session-backing-cleanup.d.ts +49 -0
  150. package/dist/core/explicit-session-backing-cleanup.d.ts.map +1 -0
  151. package/dist/core/explicit-session-backing-cleanup.js +76 -0
  152. package/dist/core/explicit-session-backing-cleanup.js.map +1 -0
  153. package/dist/core/idle-worker-sweeper.d.ts +20 -0
  154. package/dist/core/idle-worker-sweeper.d.ts.map +1 -1
  155. package/dist/core/idle-worker-sweeper.js +18 -0
  156. package/dist/core/idle-worker-sweeper.js.map +1 -1
  157. package/dist/core/inflight-input-tracker.d.ts +10 -0
  158. package/dist/core/inflight-input-tracker.d.ts.map +1 -1
  159. package/dist/core/inflight-input-tracker.js +7 -0
  160. package/dist/core/inflight-input-tracker.js.map +1 -1
  161. package/dist/core/managed-origin-attestation.d.ts +51 -0
  162. package/dist/core/managed-origin-attestation.d.ts.map +1 -0
  163. package/dist/core/managed-origin-attestation.js +208 -0
  164. package/dist/core/managed-origin-attestation.js.map +1 -0
  165. package/dist/core/managed-origin-capability.d.ts +62 -3
  166. package/dist/core/managed-origin-capability.d.ts.map +1 -1
  167. package/dist/core/managed-origin-capability.js +419 -9
  168. package/dist/core/managed-origin-capability.js.map +1 -1
  169. package/dist/core/pending-repo-journal.d.ts +6 -0
  170. package/dist/core/pending-repo-journal.d.ts.map +1 -0
  171. package/dist/core/pending-repo-journal.js +85 -0
  172. package/dist/core/pending-repo-journal.js.map +1 -0
  173. package/dist/core/persistent-backend.d.ts +6 -1
  174. package/dist/core/persistent-backend.d.ts.map +1 -1
  175. package/dist/core/persistent-backend.js +12 -6
  176. package/dist/core/persistent-backend.js.map +1 -1
  177. package/dist/core/process-start-identity.d.ts +3 -0
  178. package/dist/core/process-start-identity.d.ts.map +1 -0
  179. package/dist/core/process-start-identity.js +62 -0
  180. package/dist/core/process-start-identity.js.map +1 -0
  181. package/dist/core/raw-command-writer.d.ts +34 -0
  182. package/dist/core/raw-command-writer.d.ts.map +1 -0
  183. package/dist/core/raw-command-writer.js +48 -0
  184. package/dist/core/raw-command-writer.js.map +1 -0
  185. package/dist/core/reply-target.d.ts +85 -22
  186. package/dist/core/reply-target.d.ts.map +1 -1
  187. package/dist/core/reply-target.js +267 -32
  188. package/dist/core/reply-target.js.map +1 -1
  189. package/dist/core/resource-monitor/runtime.d.ts.map +1 -1
  190. package/dist/core/resource-monitor/runtime.js +4 -1
  191. package/dist/core/resource-monitor/runtime.js.map +1 -1
  192. package/dist/core/restart-report.d.ts +6 -1
  193. package/dist/core/restart-report.d.ts.map +1 -1
  194. package/dist/core/restart-report.js +22 -4
  195. package/dist/core/restart-report.js.map +1 -1
  196. package/dist/core/riff-shutdown-detach.d.ts +164 -0
  197. package/dist/core/riff-shutdown-detach.d.ts.map +1 -0
  198. package/dist/core/riff-shutdown-detach.js +787 -0
  199. package/dist/core/riff-shutdown-detach.js.map +1 -0
  200. package/dist/core/riff-worker-shutdown-readiness.d.ts +17 -0
  201. package/dist/core/riff-worker-shutdown-readiness.d.ts.map +1 -0
  202. package/dist/core/riff-worker-shutdown-readiness.js +21 -0
  203. package/dist/core/riff-worker-shutdown-readiness.js.map +1 -0
  204. package/dist/core/session-manager.d.ts +6 -6
  205. package/dist/core/session-manager.d.ts.map +1 -1
  206. package/dist/core/session-manager.js +1227 -594
  207. package/dist/core/session-manager.js.map +1 -1
  208. package/dist/core/session-marker.d.ts +1 -1
  209. package/dist/core/session-marker.d.ts.map +1 -1
  210. package/dist/core/session-marker.js +2 -2
  211. package/dist/core/session-marker.js.map +1 -1
  212. package/dist/core/session-mutation-guard.d.ts +17 -0
  213. package/dist/core/session-mutation-guard.d.ts.map +1 -0
  214. package/dist/core/session-mutation-guard.js +33 -0
  215. package/dist/core/session-mutation-guard.js.map +1 -0
  216. package/dist/core/shutdown-budgets.d.ts +27 -0
  217. package/dist/core/shutdown-budgets.d.ts.map +1 -0
  218. package/dist/core/shutdown-budgets.js +47 -0
  219. package/dist/core/shutdown-budgets.js.map +1 -0
  220. package/dist/core/supervisor-shutdown-ipc.d.ts +10 -0
  221. package/dist/core/supervisor-shutdown-ipc.d.ts.map +1 -0
  222. package/dist/core/supervisor-shutdown-ipc.js +10 -0
  223. package/dist/core/supervisor-shutdown-ipc.js.map +1 -0
  224. package/dist/core/supervisor-shutdown-protocol.d.ts +16 -0
  225. package/dist/core/supervisor-shutdown-protocol.d.ts.map +1 -0
  226. package/dist/core/supervisor-shutdown-protocol.js +16 -0
  227. package/dist/core/supervisor-shutdown-protocol.js.map +1 -0
  228. package/dist/core/trigger-final-suppression.d.ts +4 -0
  229. package/dist/core/trigger-final-suppression.d.ts.map +1 -1
  230. package/dist/core/trigger-final-suppression.js +6 -11
  231. package/dist/core/trigger-final-suppression.js.map +1 -1
  232. package/dist/core/trigger-session.d.ts +70 -0
  233. package/dist/core/trigger-session.d.ts.map +1 -1
  234. package/dist/core/trigger-session.js +923 -145
  235. package/dist/core/trigger-session.js.map +1 -1
  236. package/dist/core/types.d.ts +94 -1
  237. package/dist/core/types.d.ts.map +1 -1
  238. package/dist/core/types.js +9 -0
  239. package/dist/core/types.js.map +1 -1
  240. package/dist/core/worker-pool.d.ts +194 -13
  241. package/dist/core/worker-pool.d.ts.map +1 -1
  242. package/dist/core/worker-pool.js +2597 -547
  243. package/dist/core/worker-pool.js.map +1 -1
  244. package/dist/daemon.d.ts +76 -2
  245. package/dist/daemon.d.ts.map +1 -1
  246. package/dist/daemon.js +2255 -773
  247. package/dist/daemon.js.map +1 -1
  248. package/dist/dashboard/bot-payload.d.ts +20 -0
  249. package/dist/dashboard/bot-payload.d.ts.map +1 -1
  250. package/dist/dashboard/bot-payload.js +24 -0
  251. package/dist/dashboard/bot-payload.js.map +1 -1
  252. package/dist/dashboard/feed-groups.d.ts +17 -0
  253. package/dist/dashboard/feed-groups.d.ts.map +1 -0
  254. package/dist/dashboard/feed-groups.js +75 -0
  255. package/dist/dashboard/feed-groups.js.map +1 -0
  256. package/dist/dashboard/session-card-model.d.ts +1 -1
  257. package/dist/dashboard/session-card-model.d.ts.map +1 -1
  258. package/dist/dashboard/session-card-model.js +8 -5
  259. package/dist/dashboard/session-card-model.js.map +1 -1
  260. package/dist/dashboard/web/bot-defaults-page.d.ts +4 -0
  261. package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
  262. package/dist/dashboard/web/bot-defaults-page.js +139 -46
  263. package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
  264. package/dist/dashboard/web/bot-defaults.d.ts +4 -0
  265. package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
  266. package/dist/dashboard/web/bot-defaults.js.map +1 -1
  267. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  268. package/dist/dashboard/web/connectors-page.js +40 -3
  269. package/dist/dashboard/web/connectors-page.js.map +1 -1
  270. package/dist/dashboard/web/groups-page.d.ts.map +1 -1
  271. package/dist/dashboard/web/groups-page.js +95 -2
  272. package/dist/dashboard/web/groups-page.js.map +1 -1
  273. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  274. package/dist/dashboard/web/i18n.js +86 -40
  275. package/dist/dashboard/web/i18n.js.map +1 -1
  276. package/dist/dashboard/web/kanban-model.js +1 -1
  277. package/dist/dashboard/web/kanban-model.js.map +1 -1
  278. package/dist/dashboard/web/overview-page.d.ts.map +1 -1
  279. package/dist/dashboard/web/overview-page.js +3 -2
  280. package/dist/dashboard/web/overview-page.js.map +1 -1
  281. package/dist/dashboard/web/overview.d.ts +3 -0
  282. package/dist/dashboard/web/overview.d.ts.map +1 -1
  283. package/dist/dashboard/web/overview.js +2 -0
  284. package/dist/dashboard/web/overview.js.map +1 -1
  285. package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -1
  286. package/dist/dashboard/web/sessions-kanban.js +2 -0
  287. package/dist/dashboard/web/sessions-kanban.js.map +1 -1
  288. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  289. package/dist/dashboard/web/sessions-page.js +103 -2
  290. package/dist/dashboard/web/sessions-page.js.map +1 -1
  291. package/dist/dashboard/web/sessions.d.ts.map +1 -1
  292. package/dist/dashboard/web/sessions.js +3 -2
  293. package/dist/dashboard/web/sessions.js.map +1 -1
  294. package/dist/dashboard/web/ui.d.ts +8 -0
  295. package/dist/dashboard/web/ui.d.ts.map +1 -1
  296. package/dist/dashboard/web/ui.js +15 -0
  297. package/dist/dashboard/web/ui.js.map +1 -1
  298. package/dist/dashboard-web/app.js +1 -1
  299. package/dist/dashboard-web/chunks/bot-defaults-page-GED4FEMS.js +10 -0
  300. package/dist/dashboard-web/chunks/chunk-6J5LS5AU.js +51 -0
  301. package/dist/dashboard-web/chunks/{chunk-ZU3UUKAR.js → chunk-HK7NIBJG.js} +1 -1
  302. package/dist/dashboard-web/chunks/{chunk-3UE7S77S.js → chunk-KRHWZEXU.js} +1 -1
  303. package/dist/dashboard-web/chunks/chunk-VBXJOJFH.js +1 -0
  304. package/dist/dashboard-web/chunks/connectors-page-EELM66G3.js +1 -0
  305. package/dist/dashboard-web/chunks/groups-page-WKUV6E42.js +8 -0
  306. package/dist/dashboard-web/chunks/{insights-page-T2ZX2RWL.js → insights-page-54UBPPOC.js} +1 -1
  307. package/dist/dashboard-web/chunks/{monitor-room-YNVBAGR6.js → monitor-room-PORHVKRH.js} +1 -1
  308. package/dist/dashboard-web/chunks/{monitoring-page-LHM55YWN.js → monitoring-page-XGA7AXNL.js} +1 -1
  309. package/dist/dashboard-web/chunks/{office-page-ATXVP7PW.js → office-page-AO3AFG45.js} +1 -1
  310. package/dist/dashboard-web/chunks/overview-page-F5BUTYR6.js +1 -0
  311. package/dist/dashboard-web/chunks/{roles-page-56VWM6CD.js → roles-page-M5GIASJU.js} +1 -1
  312. package/dist/dashboard-web/chunks/{schedules-page-S5K6U4EO.js → schedules-page-O232WQRR.js} +1 -1
  313. package/dist/dashboard-web/chunks/sessions-page-YFRPSTIL.js +6 -0
  314. package/dist/dashboard-web/chunks/{settings-page-QUZDQARR.js → settings-page-OJAZLZQT.js} +1 -1
  315. package/dist/dashboard-web/chunks/{skills-page-IFY7AEND.js → skills-page-EFZQPWV4.js} +1 -1
  316. package/dist/dashboard-web/chunks/{team-federation-page-C7FJQPFZ.js → team-federation-page-ZJVAP7HW.js} +1 -1
  317. package/dist/dashboard-web/chunks/{v3-page-2LMF3WWR.js → v3-page-GYWTRGO2.js} +1 -1
  318. package/dist/dashboard-web/chunks/{whiteboards-page-3XC6QRMY.js → whiteboards-page-MSDAG6P7.js} +1 -1
  319. package/dist/dashboard-web/style.css +255 -0
  320. package/dist/dashboard.js +172 -7
  321. package/dist/dashboard.js.map +1 -1
  322. package/dist/desktop/renderer/app.js +21 -0
  323. package/dist/desktop/renderer/app.js.map +1 -1
  324. package/dist/i18n/en.d.ts.map +1 -1
  325. package/dist/i18n/en.js +35 -9
  326. package/dist/i18n/en.js.map +1 -1
  327. package/dist/i18n/zh.d.ts.map +1 -1
  328. package/dist/i18n/zh.js +35 -9
  329. package/dist/i18n/zh.js.map +1 -1
  330. package/dist/im/lark/ask-card.d.ts +23 -0
  331. package/dist/im/lark/ask-card.d.ts.map +1 -1
  332. package/dist/im/lark/ask-card.js +94 -4
  333. package/dist/im/lark/ask-card.js.map +1 -1
  334. package/dist/im/lark/card-builder.d.ts +6 -1
  335. package/dist/im/lark/card-builder.d.ts.map +1 -1
  336. package/dist/im/lark/card-builder.js +112 -15
  337. package/dist/im/lark/card-builder.js.map +1 -1
  338. package/dist/im/lark/card-handler.d.ts +1 -1
  339. package/dist/im/lark/card-handler.d.ts.map +1 -1
  340. package/dist/im/lark/card-handler.js +496 -310
  341. package/dist/im/lark/card-handler.js.map +1 -1
  342. package/dist/im/lark/client.d.ts +7 -0
  343. package/dist/im/lark/client.d.ts.map +1 -1
  344. package/dist/im/lark/client.js +39 -26
  345. package/dist/im/lark/client.js.map +1 -1
  346. package/dist/im/lark/doc-comment.d.ts +7 -4
  347. package/dist/im/lark/doc-comment.d.ts.map +1 -1
  348. package/dist/im/lark/doc-comment.js +19 -6
  349. package/dist/im/lark/doc-comment.js.map +1 -1
  350. package/dist/im/lark/event-dispatcher.d.ts +16 -0
  351. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  352. package/dist/im/lark/event-dispatcher.js +88 -23
  353. package/dist/im/lark/event-dispatcher.js.map +1 -1
  354. package/dist/im/lark/forward-followup-content.d.ts.map +1 -1
  355. package/dist/im/lark/forward-followup-content.js +9 -1
  356. package/dist/im/lark/forward-followup-content.js.map +1 -1
  357. package/dist/im/lark/grant-command.d.ts.map +1 -1
  358. package/dist/im/lark/grant-command.js +7 -4
  359. package/dist/im/lark/grant-command.js.map +1 -1
  360. package/dist/im/lark/md-card.d.ts +11 -0
  361. package/dist/im/lark/md-card.d.ts.map +1 -1
  362. package/dist/im/lark/md-card.js +46 -0
  363. package/dist/im/lark/md-card.js.map +1 -1
  364. package/dist/im/lark/message-parser.d.ts +23 -2
  365. package/dist/im/lark/message-parser.d.ts.map +1 -1
  366. package/dist/im/lark/message-parser.js +58 -3
  367. package/dist/im/lark/message-parser.js.map +1 -1
  368. package/dist/im/lark/overview-card.d.ts.map +1 -1
  369. package/dist/im/lark/overview-card.js +2 -1
  370. package/dist/im/lark/overview-card.js.map +1 -1
  371. package/dist/services/async-trigger-state.d.ts +6 -1
  372. package/dist/services/async-trigger-state.d.ts.map +1 -1
  373. package/dist/services/async-trigger-state.js +21 -0
  374. package/dist/services/async-trigger-state.js.map +1 -1
  375. package/dist/services/async-trigger-store.d.ts +23 -1
  376. package/dist/services/async-trigger-store.d.ts.map +1 -1
  377. package/dist/services/async-trigger-store.js +96 -13
  378. package/dist/services/async-trigger-store.js.map +1 -1
  379. package/dist/services/bridge-fallback-gate.d.ts +40 -0
  380. package/dist/services/bridge-fallback-gate.d.ts.map +1 -1
  381. package/dist/services/bridge-fallback-gate.js +124 -24
  382. package/dist/services/bridge-fallback-gate.js.map +1 -1
  383. package/dist/services/codex-app-runner-protocol.d.ts +8 -0
  384. package/dist/services/codex-app-runner-protocol.d.ts.map +1 -1
  385. package/dist/services/codex-app-runner-protocol.js +9 -2
  386. package/dist/services/codex-app-runner-protocol.js.map +1 -1
  387. package/dist/services/codex-bridge-queue.d.ts +1 -0
  388. package/dist/services/codex-bridge-queue.d.ts.map +1 -1
  389. package/dist/services/codex-bridge-queue.js +1 -0
  390. package/dist/services/codex-bridge-queue.js.map +1 -1
  391. package/dist/services/codex-service-tier.d.ts +2 -0
  392. package/dist/services/codex-service-tier.d.ts.map +1 -1
  393. package/dist/services/codex-service-tier.js +1 -0
  394. package/dist/services/codex-service-tier.js.map +1 -1
  395. package/dist/services/codex-transcript.d.ts +27 -0
  396. package/dist/services/codex-transcript.d.ts.map +1 -1
  397. package/dist/services/codex-transcript.js +340 -2
  398. package/dist/services/codex-transcript.js.map +1 -1
  399. package/dist/services/grant-policy.d.ts +3 -0
  400. package/dist/services/grant-policy.d.ts.map +1 -1
  401. package/dist/services/grant-policy.js +5 -0
  402. package/dist/services/grant-policy.js.map +1 -1
  403. package/dist/services/grant-prefs-store.d.ts +6 -3
  404. package/dist/services/grant-prefs-store.d.ts.map +1 -1
  405. package/dist/services/grant-prefs-store.js +42 -12
  406. package/dist/services/grant-prefs-store.js.map +1 -1
  407. package/dist/services/hook-runner.d.ts +20 -1
  408. package/dist/services/hook-runner.d.ts.map +1 -1
  409. package/dist/services/hook-runner.js +17 -13
  410. package/dist/services/hook-runner.js.map +1 -1
  411. package/dist/services/idempotency-store.d.ts +134 -0
  412. package/dist/services/idempotency-store.d.ts.map +1 -0
  413. package/dist/services/idempotency-store.js +299 -0
  414. package/dist/services/idempotency-store.js.map +1 -0
  415. package/dist/services/jsonl-cursor.d.ts +7 -3
  416. package/dist/services/jsonl-cursor.d.ts.map +1 -1
  417. package/dist/services/jsonl-cursor.js +55 -31
  418. package/dist/services/jsonl-cursor.js.map +1 -1
  419. package/dist/services/open-platform-rename.d.ts.map +1 -1
  420. package/dist/services/open-platform-rename.js +1 -108
  421. package/dist/services/open-platform-rename.js.map +1 -1
  422. package/dist/services/restart-intent-store.d.ts +22 -2
  423. package/dist/services/restart-intent-store.d.ts.map +1 -1
  424. package/dist/services/restart-intent-store.js +160 -18
  425. package/dist/services/restart-intent-store.js.map +1 -1
  426. package/dist/services/sandbox-store.d.ts +22 -0
  427. package/dist/services/sandbox-store.d.ts.map +1 -1
  428. package/dist/services/sandbox-store.js +37 -8
  429. package/dist/services/sandbox-store.js.map +1 -1
  430. package/dist/services/send-policy.d.ts +41 -33
  431. package/dist/services/send-policy.d.ts.map +1 -1
  432. package/dist/services/send-policy.js +39 -40
  433. package/dist/services/send-policy.js.map +1 -1
  434. package/dist/services/session-store.d.ts +66 -0
  435. package/dist/services/session-store.d.ts.map +1 -1
  436. package/dist/services/session-store.js +403 -67
  437. package/dist/services/session-store.js.map +1 -1
  438. package/dist/services/submit-notification.d.ts +3 -0
  439. package/dist/services/submit-notification.d.ts.map +1 -0
  440. package/dist/services/submit-notification.js +15 -0
  441. package/dist/services/submit-notification.js.map +1 -0
  442. package/dist/services/traex-transcript.d.ts +25 -2
  443. package/dist/services/traex-transcript.d.ts.map +1 -1
  444. package/dist/services/traex-transcript.js +133 -1
  445. package/dist/services/traex-transcript.js.map +1 -1
  446. package/dist/services/trigger-types.d.ts +13 -1
  447. package/dist/services/trigger-types.d.ts.map +1 -1
  448. package/dist/services/trigger-types.js +39 -0
  449. package/dist/services/trigger-types.js.map +1 -1
  450. package/dist/services/voice/index.d.ts +3 -2
  451. package/dist/services/voice/index.d.ts.map +1 -1
  452. package/dist/services/voice/index.js +5 -5
  453. package/dist/services/voice/index.js.map +1 -1
  454. package/dist/services/voice/openai.d.ts +2 -1
  455. package/dist/services/voice/openai.d.ts.map +1 -1
  456. package/dist/services/voice/openai.js +2 -1
  457. package/dist/services/voice/openai.js.map +1 -1
  458. package/dist/services/voice/sami.d.ts +5 -2
  459. package/dist/services/voice/sami.d.ts.map +1 -1
  460. package/dist/services/voice/sami.js +19 -4
  461. package/dist/services/voice/sami.js.map +1 -1
  462. package/dist/setup/bots-store.d.ts.map +1 -1
  463. package/dist/setup/bots-store.js +10 -4
  464. package/dist/setup/bots-store.js.map +1 -1
  465. package/dist/setup/lark-scopes.json +2 -0
  466. package/dist/setup/open-platform-automation.d.ts +9 -1
  467. package/dist/setup/open-platform-automation.d.ts.map +1 -1
  468. package/dist/setup/open-platform-automation.js +44 -15
  469. package/dist/setup/open-platform-automation.js.map +1 -1
  470. package/dist/setup/open-platform-visibility.d.ts +42 -0
  471. package/dist/setup/open-platform-visibility.d.ts.map +1 -0
  472. package/dist/setup/open-platform-visibility.js +133 -0
  473. package/dist/setup/open-platform-visibility.js.map +1 -0
  474. package/dist/setup/verify-permissions.d.ts.map +1 -1
  475. package/dist/setup/verify-permissions.js +6 -0
  476. package/dist/setup/verify-permissions.js.map +1 -1
  477. package/dist/skills/definitions.js +2 -2
  478. package/dist/types.d.ts +290 -13
  479. package/dist/types.d.ts.map +1 -1
  480. package/dist/utils/bot-routing.d.ts +1 -0
  481. package/dist/utils/bot-routing.d.ts.map +1 -1
  482. package/dist/utils/bot-routing.js +1 -1
  483. package/dist/utils/bot-routing.js.map +1 -1
  484. package/dist/utils/child-env.d.ts +1 -1
  485. package/dist/utils/child-env.d.ts.map +1 -1
  486. package/dist/utils/child-env.js +9 -0
  487. package/dist/utils/child-env.js.map +1 -1
  488. package/dist/utils/codex-app-control.d.ts +438 -0
  489. package/dist/utils/codex-app-control.d.ts.map +1 -0
  490. package/dist/utils/codex-app-control.js +2336 -0
  491. package/dist/utils/codex-app-control.js.map +1 -0
  492. package/dist/utils/codex-app-dispatch-ledger.d.ts +78 -0
  493. package/dist/utils/codex-app-dispatch-ledger.d.ts.map +1 -0
  494. package/dist/utils/codex-app-dispatch-ledger.js +149 -0
  495. package/dist/utils/codex-app-dispatch-ledger.js.map +1 -0
  496. package/dist/utils/codex-app-turn-dispatch.d.ts +77 -0
  497. package/dist/utils/codex-app-turn-dispatch.d.ts.map +1 -0
  498. package/dist/utils/codex-app-turn-dispatch.js +141 -0
  499. package/dist/utils/codex-app-turn-dispatch.js.map +1 -0
  500. package/dist/utils/codex-app-turn-liveness.d.ts +125 -0
  501. package/dist/utils/codex-app-turn-liveness.d.ts.map +1 -0
  502. package/dist/utils/codex-app-turn-liveness.js +301 -0
  503. package/dist/utils/codex-app-turn-liveness.js.map +1 -0
  504. package/dist/utils/idle-detector.d.ts +12 -0
  505. package/dist/utils/idle-detector.d.ts.map +1 -1
  506. package/dist/utils/idle-detector.js +19 -0
  507. package/dist/utils/idle-detector.js.map +1 -1
  508. package/dist/utils/input-gate.d.ts +87 -0
  509. package/dist/utils/input-gate.d.ts.map +1 -1
  510. package/dist/utils/input-gate.js +86 -0
  511. package/dist/utils/input-gate.js.map +1 -1
  512. package/dist/utils/lark-upload.d.ts.map +1 -1
  513. package/dist/utils/lark-upload.js +52 -1
  514. package/dist/utils/lark-upload.js.map +1 -1
  515. package/dist/utils/pending-input-queue.d.ts +15 -0
  516. package/dist/utils/pending-input-queue.d.ts.map +1 -1
  517. package/dist/utils/pending-input-queue.js +5 -1
  518. package/dist/utils/pending-input-queue.js.map +1 -1
  519. package/dist/utils/user-token.d.ts.map +1 -1
  520. package/dist/utils/user-token.js +53 -2
  521. package/dist/utils/user-token.js.map +1 -1
  522. package/dist/worker-ipc-preload.d.ts +12 -0
  523. package/dist/worker-ipc-preload.d.ts.map +1 -0
  524. package/dist/worker-ipc-preload.js +44 -0
  525. package/dist/worker-ipc-preload.js.map +1 -0
  526. package/dist/worker.js +2604 -325
  527. package/dist/worker.js.map +1 -1
  528. package/dist/workflows/v3/session-relay-client.d.ts +1 -0
  529. package/dist/workflows/v3/session-relay-client.d.ts.map +1 -1
  530. package/dist/workflows/v3/session-relay-client.js +12 -3
  531. package/dist/workflows/v3/session-relay-client.js.map +1 -1
  532. package/package.json +1 -1
  533. package/dist/dashboard-web/chunks/bot-defaults-page-LTVOUDIS.js +0 -10
  534. package/dist/dashboard-web/chunks/chunk-DCS35UMD.js +0 -1
  535. package/dist/dashboard-web/chunks/chunk-MFQPCVHY.js +0 -51
  536. package/dist/dashboard-web/chunks/connectors-page-5KWDIMKC.js +0 -1
  537. package/dist/dashboard-web/chunks/groups-page-FHOKYXMW.js +0 -8
  538. package/dist/dashboard-web/chunks/overview-page-QYI3KBDD.js +0 -1
  539. package/dist/dashboard-web/chunks/sessions-page-WBP4N7D2.js +0 -6
@@ -9,8 +9,38 @@
9
9
  */
10
10
  import { randomUUID } from 'node:crypto';
11
11
  import { logger } from '../utils/logger.js';
12
+ import { askKeyFor, dispatchUuidForKey, HANDOFF_RETENTION_MS, } from './ask-persist-store.js';
13
+ import { AskDispatchError } from './ask-types.js';
14
+ /** Origins for which restart-resume + durable handoff are enabled. Only a
15
+ * caller that has a reconnecting claimant after a daemon restart may persist:
16
+ * the hook process survives in the CLI's tmux/persistent-backend session and
17
+ * re-POSTs with the same requestId. An explicit `botmux ask buttons` CLI exits
18
+ * on restart (no claimant) and a PTY-backend hook doesn't survive, so those
19
+ * must NOT persist/handoff — else they leave orphan records (codex P1-4). */
20
+ const RESUMABLE_ORIGINS = new Set(['hook']);
12
21
  const pending = new Map();
13
22
  let dispatcher = null;
23
+ /** Injected durable store (codex P1-4: no global-dataDir reads from the broker).
24
+ * Wired once at daemon bootstrap via setAskPersistStore; null → persistence is
25
+ * a no-op (unit tests that don't exercise restart-resume需要绑定各自 temp
26
+ * store,绝不回落到全局目录). */
27
+ let persistStore = null;
28
+ /** Effective handoff-retention window. Defaults to the durable-store constant;
29
+ * a test may shrink it via `_setHandoffRetentionForTest` so the absolute-expiry
30
+ * reaper (codex P1-4) can be exercised with real timers instead of a 24h wait
31
+ * or fake-timer gymnastics around the async dispatch loop. */
32
+ let handoffRetentionMs = HANDOFF_RETENTION_MS;
33
+ /** Wire the durable persist store. Called once at daemon bootstrap with the
34
+ * real dir; tests inject a temp store (and only clean their own sentinel dir). */
35
+ export function setAskPersistStore(store) {
36
+ persistStore = store;
37
+ }
38
+ /** Shrink the handoff-retention window — for tests only, so the absolute-expiry
39
+ * reaper (codex P1-4) can be verified with a short real timer. Restored to the
40
+ * default by `_resetForTest`. */
41
+ export function _setHandoffRetentionForTest(ms) {
42
+ handoffRetentionMs = ms;
43
+ }
14
44
  /** IM-side canTalk predicate, wired by the daemon at bootstrap. Lets the broker
15
45
  * honour the bot's canTalk gate without importing Lark types: whoever may
16
46
  * address the bot in this chat may answer its `botmux ask`. Returns false until
@@ -57,29 +87,82 @@ export function registerAsk(input) {
57
87
  if (!dispatcher) {
58
88
  throw new Error('ask-broker: cardDispatcher not wired — daemon bootstrap bug');
59
89
  }
90
+ const originKind = input.originKind ?? 'hook';
91
+ // Resumability is gated by TWO independent facts (codex P1-4):
92
+ // 1. the origin has a reconnecting claimant (only 'hook' re-POSTs after a
93
+ // restart; an explicit `botmux ask buttons` process exits), AND
94
+ // 2. the issuing session's backend actually SURVIVES a daemon restart —
95
+ // computed by the daemon from the authenticated session's frozen backend
96
+ // (tmux/herdr/zellij/zmx), NOT trusted from the client. A PTY-backed hook
97
+ // dies with the daemon, so persisting it would orphan a record no one can
98
+ // ever re-claim. Undefined backend signal → false (fail closed).
99
+ // A caller-supplied requestId is still required (it's the re-attach identity).
100
+ const resumable = RESUMABLE_ORIGINS.has(originKind) &&
101
+ input.requestId !== undefined &&
102
+ input.backendSurvivesRestart === true;
103
+ // Invocation identity: prefer the caller-supplied requestId (hook generates it
104
+ // once and reuses it across reconnect retries). A caller without one (explicit
105
+ // `botmux ask buttons`) gets a synthesized id and is NOT resumable — it has no
106
+ // reconnecting claimant, so persisting/handing off would orphan (codex P1-4).
107
+ const requestId = input.requestId ?? randomUUID();
108
+ // Identity is SCOPED to the authenticated (larkAppId, sessionId): a requestId
109
+ // is an idempotency id, never a bearer secret — a different session reusing it
110
+ // lands on a different key and can't reclaim this ask (codex P1-3).
111
+ const askKey = askKeyFor(input.larkAppId, input.sessionId, originKind, requestId);
112
+ // Same-key handling — covers ALL states of an existing entry with this key
113
+ // (codex P1-1). The scoped key already pins (app/session/origin/request); here
114
+ // we additionally require the FULL immutable identity (chat/root/questions) to
115
+ // match before joining/replaying/re-attaching.
116
+ const existing = findByKey(askKey);
117
+ if (existing) {
118
+ if (sameIdentity(existing, input)) {
119
+ // active (still awaiting a click): add another waiter → both callers get the
120
+ // one result; no second ask, no second card.
121
+ if (!existing.settled && !existing.dormant) {
122
+ logger.info?.(`ask-broker: active replay joined ask ${existing.askId} (key=${askKey})`);
123
+ return new Promise((resolve) => { existing.waiters.push(resolve); });
124
+ }
125
+ // recent-terminal (settled, still retained): return the same terminal result.
126
+ if (existing.settled && existing.terminalResult && !existing.dormant) {
127
+ logger.info?.(`ask-broker: replay returned retained terminal result for ${existing.askId}`);
128
+ return Promise.resolve(existing.terminalResult);
129
+ }
130
+ // dormant (restored after restart): re-attach (handoff or fresh waiter).
131
+ if (existing.dormant) {
132
+ return reattachByRequest(existing);
133
+ }
134
+ }
135
+ else {
136
+ // Same key, DIFFERENT immutable identity (crafted / stale-but-mutated
137
+ // re-POST). FAIL CLOSED (codex P1-2): never fall through to a second ask —
138
+ // that would post an ambiguous second card and a click could resolve
139
+ // either. The caller gets a terminal `invalidated` and no card is sent.
140
+ logger.warn?.(`ask-broker: identity mismatch on key ${askKey} (existing ask ${existing.askId}) — rejecting re-register`);
141
+ return Promise.resolve({
142
+ kind: 'invalidated',
143
+ reason: 'ask identity mismatch (same key, different question set / chat)',
144
+ selected: null, by: null, comment: null, timedOut: false,
145
+ });
146
+ }
147
+ }
60
148
  const askId = randomUUID();
61
149
  const nonce = randomUUID().slice(0, 8);
62
150
  const createdAt = Date.now();
63
151
  const deadlineAt = createdAt + input.timeoutMs;
64
152
  return new Promise((resolve) => {
65
153
  const timeoutHandle = setTimeout(() => {
66
- settle(askId, {
67
- kind: 'timedOut',
68
- selected: null,
69
- by: null,
70
- comment: null,
71
- timedOut: true,
72
- });
154
+ settle(askId, { kind: 'timedOut', selected: null, by: null, comment: null, timedOut: true });
73
155
  }, input.timeoutMs);
74
- // Don't keep the event loop alive just because an ask is pending.
75
156
  timeoutHandle.unref?.();
76
- // 为每个问题初始化空的勾选集合
77
157
  const selections = new Map();
78
- for (let i = 0; i < input.questions.length; i++) {
158
+ for (let i = 0; i < input.questions.length; i++)
79
159
  selections.set(i, new Set());
80
- }
81
160
  const ask = {
82
161
  askId,
162
+ askKey,
163
+ requestId,
164
+ originKind,
165
+ resumable,
83
166
  nonce,
84
167
  larkAppId: input.larkAppId,
85
168
  chatId: input.chatId,
@@ -90,33 +173,180 @@ export function registerAsk(input) {
90
173
  createdAt,
91
174
  deadlineAt,
92
175
  settled: false,
93
- resolve,
176
+ waiters: [resolve],
94
177
  timeoutHandle,
95
178
  selections,
96
179
  };
97
180
  pending.set(askId, ask);
98
- // Card dispatch is async — store the messageId once it lands.
99
- void dispatcher
100
- .send(snapshot(ask))
101
- .then(({ messageId }) => {
102
- const cur = pending.get(askId);
103
- if (cur && !cur.settled)
104
- cur.cardMessageId = messageId;
105
- })
106
- .catch((err) => {
107
- const msg = err instanceof Error ? err.message : String(err);
108
- logger.warn?.(`ask-broker: ${askId} card dispatch failed: ${msg}`);
109
- settle(askId, {
110
- kind: 'invalidated',
111
- reason: `card dispatch failed: ${msg}`,
112
- selected: null,
113
- by: null,
114
- comment: null,
115
- timedOut: false,
116
- });
117
- });
181
+ // Persist ONLY resumable origins (codex P1-4). A restart before the card
182
+ // lands still leaves a resumable record; restore/re-attach re-sends.
183
+ if (resumable) {
184
+ persistFromInternal(ask);
185
+ logger.info?.(`ask-broker: registered + persisted ask ${askId} (key=${askKey}, session=${input.sessionId})`);
186
+ }
187
+ void sendCardForAsk(ask);
188
+ });
189
+ }
190
+ /** Immutable-identity equality: a reattach/replay must match the ORIGINAL ask on
191
+ * every caller-independent field, not just the (already-scoped) key. Guards
192
+ * against a stale/crafted requestId landing on a mismatched ask (codex P1-3). */
193
+ function sameIdentity(ask, input) {
194
+ return (ask.larkAppId === input.larkAppId &&
195
+ ask.sessionId === input.sessionId &&
196
+ ask.chatId === input.chatId &&
197
+ ask.rootMessageId === input.rootMessageId &&
198
+ ask.originKind === (input.originKind ?? 'hook') &&
199
+ questionsShape(ask.questions) === questionsShape(input.questions));
200
+ }
201
+ /** Stable shape string for question-set equality (prompt + multiSelect + each
202
+ * option's key AND label, order-sensitive). Two question sets that would render
203
+ * a DIFFERENT card to the user — including a relabelled option whose key is
204
+ * unchanged — must not re-attach/replay (codex P1-2). */
205
+ function questionsShape(qs) {
206
+ return JSON.stringify(qs.map((q) => ({
207
+ p: q.prompt,
208
+ m: !!q.multiSelect,
209
+ o: q.options.map((o) => [o.key, o.label]),
210
+ })));
211
+ }
212
+ /** Max card (re-)dispatch attempts on transient failures before giving up and
213
+ * invalidating. Small: a card send is a single Feishu POST; if a handful of
214
+ * attempts across a few seconds all fail transiently, the chat is unreachable
215
+ * and waiting longer won't help. */
216
+ const CARD_DISPATCH_MAX_ATTEMPTS = 4;
217
+ /** Base backoff between transient re-sends (linear: base × attempt, capped). */
218
+ const CARD_DISPATCH_BACKOFF_MS = 500;
219
+ const CARD_DISPATCH_BACKOFF_CAP_MS = 4_000;
220
+ /** Dispatch (or idempotently re-dispatch) the card and record its messageId. The
221
+ * dispatcher gets a stable `dispatchUuid` (derived from the scoped key) so any
222
+ * re-send — a bounded transient retry here, or a restart re-attach — returns the
223
+ * ORIGINAL message_id instead of posting a duplicate (codex P1-1/P1-3).
224
+ *
225
+ * Retry policy (codex P1-3 partial-success): a `retryable` AskDispatchError
226
+ * (5xx/429/network) is retried up to CARD_DISPATCH_MAX_ATTEMPTS with linear
227
+ * backoff, reusing the SAME uuid — so a send that landed server-side before the
228
+ * socket broke converges to one logical card. A non-retryable error
229
+ * (deterministic 4xx / withdrawn) — or a plain untyped throw (fail closed) —
230
+ * invalidates immediately. */
231
+ function sendCardForAsk(ask) {
232
+ void (async () => {
233
+ for (let attempt = 1; attempt <= CARD_DISPATCH_MAX_ATTEMPTS; attempt++) {
234
+ // Bail if the ask settled out from under us (timeout / invalidate / a click
235
+ // on a restored dormant card) — nothing left to dispatch.
236
+ const live = pending.get(ask.askId);
237
+ if (!live || live.settled)
238
+ return;
239
+ try {
240
+ const { messageId } = await dispatcher.send(snapshot(ask));
241
+ const cur = pending.get(ask.askId);
242
+ if (cur && !cur.settled) {
243
+ cur.cardMessageId = messageId;
244
+ if (cur.resumable)
245
+ persistFromInternal(cur);
246
+ }
247
+ return; // sent (or server-deduped to the original) — done
248
+ }
249
+ catch (err) {
250
+ const retryable = err instanceof AskDispatchError && err.retryable;
251
+ const msg = err instanceof Error ? err.message : String(err);
252
+ if (retryable && attempt < CARD_DISPATCH_MAX_ATTEMPTS) {
253
+ const backoff = Math.min(CARD_DISPATCH_BACKOFF_MS * attempt, CARD_DISPATCH_BACKOFF_CAP_MS);
254
+ logger.warn?.(`ask-broker: ${ask.askId} card dispatch attempt ${attempt} failed (transient): ${msg} — retrying in ${backoff}ms`);
255
+ await sleep(backoff);
256
+ continue;
257
+ }
258
+ // Deterministic failure, exhausted retries, or an untyped throw (fail
259
+ // closed): invalidate so the blocked CLI unblocks instead of hanging.
260
+ logger.warn?.(`ask-broker: ${ask.askId} card dispatch failed (${retryable ? 'transient, retries exhausted' : 'not retryable'}): ${msg}`);
261
+ settle(ask.askId, {
262
+ kind: 'invalidated', reason: `card dispatch failed: ${msg}`,
263
+ selected: null, by: null, comment: null, timedOut: false,
264
+ });
265
+ return;
266
+ }
267
+ }
268
+ })();
269
+ }
270
+ /** Promise-based sleep whose timer never keeps the process alive (unref). */
271
+ function sleep(ms) {
272
+ return new Promise((resolve) => {
273
+ const h = setTimeout(resolve, ms);
274
+ h.unref?.();
275
+ });
276
+ }
277
+ /** Find an entry by its scoped stable key (any state). */
278
+ function findByKey(askKey) {
279
+ for (const ask of pending.values())
280
+ if (ask.askKey === askKey)
281
+ return ask;
282
+ return undefined;
283
+ }
284
+ /**
285
+ * Re-attach a reconnecting hook to a restored DORMANT ask. Two cases:
286
+ * - answer already stashed (click → reattach): deliver the durable-handoff
287
+ * result immediately, settle, remove the persisted record. No new card/timer.
288
+ * - still awaiting a click: install a waiter + timeout re-armed to the ORIGINAL
289
+ * absolute deadline; re-send the card if it was never confirmed sent.
290
+ */
291
+ function reattachByRequest(ask) {
292
+ if (ask.answeredResult) {
293
+ const result = ask.answeredResult;
294
+ ask.dormant = false;
295
+ ask.settled = true;
296
+ ask.settledAt = Date.now();
297
+ ask.terminalResult = result;
298
+ clearTimeout(ask.timeoutHandle);
299
+ clearTimeout(ask.handoffExpiryHandle); // claimed → cancel the unclaimed-stash reaper
300
+ persistStore?.remove(ask.askKey); // claimed → durable record no longer needed
301
+ gcSettled();
302
+ logger.info?.(`ask-broker: re-attach delivered stashed answer for ask ${ask.askId} (key=${ask.askKey})`);
303
+ return Promise.resolve(result);
304
+ }
305
+ return new Promise((resolve) => {
306
+ ask.dormant = false;
307
+ ask.waiters.push(resolve);
308
+ clearTimeout(ask.timeoutHandle);
309
+ const remaining = Math.max(0, ask.deadlineAt - Date.now());
310
+ ask.timeoutHandle = setTimeout(() => {
311
+ settle(ask.askId, { kind: 'timedOut', selected: null, by: null, comment: null, timedOut: true });
312
+ }, remaining);
313
+ ask.timeoutHandle.unref?.();
314
+ logger.info?.(`ask-broker: re-attached hook to restored ask ${ask.askId} (key=${ask.askKey}, ` +
315
+ `${Math.round(remaining / 1000)}s left, card=${ask.cardMessageId ? 'live' : 'MISSING→resend'})`);
316
+ if (!ask.cardMessageId)
317
+ sendCardForAsk(ask);
118
318
  });
119
319
  }
320
+ /** Build the persisted projection from a live internal ask and write it. Only
321
+ * called for resumable asks. */
322
+ function persistFromInternal(ask) {
323
+ if (!persistStore || !ask.resumable)
324
+ return;
325
+ // A settled ask with a stashed answer is a durable handoff that must be KEPT
326
+ // until claimed; a settled ask without one has nothing left to resume.
327
+ if (ask.settled && !ask.answeredResult)
328
+ return;
329
+ const persisted = {
330
+ v: 2,
331
+ askKey: ask.askKey,
332
+ requestId: ask.requestId,
333
+ originKind: ask.originKind,
334
+ askId: ask.askId,
335
+ nonce: ask.nonce,
336
+ larkAppId: ask.larkAppId,
337
+ chatId: ask.chatId,
338
+ rootMessageId: ask.rootMessageId,
339
+ sessionId: ask.sessionId,
340
+ chatType: ask.chatType,
341
+ questions: ask.questions,
342
+ createdAt: ask.createdAt,
343
+ deadlineAt: ask.deadlineAt,
344
+ cardMessageId: ask.cardMessageId,
345
+ selections: ask.questions.map((_, i) => [...(ask.selections.get(i) ?? new Set())]),
346
+ ...(ask.answeredResult ? { answeredResult: ask.answeredResult, answeredAt: ask.settledAt ?? Date.now() } : {}),
347
+ };
348
+ persistStore.put(persisted);
349
+ }
120
350
  /**
121
351
  * 勾选/取消勾选某问题的某个选项(累积模式,不 settle)。
122
352
  *
@@ -159,6 +389,9 @@ export function toggleAsk(args) {
159
389
  sel.clear();
160
390
  sel.add(args.key);
161
391
  }
392
+ // Persist the updated checkbox state so a restart mid-multi-select keeps the
393
+ // boxes the user already ticked (best-effort; never blocks the toggle).
394
+ persistFromInternal(ask);
162
395
  return 'toggled';
163
396
  }
164
397
  /**
@@ -322,6 +555,105 @@ export function invalidateAll(reason) {
322
555
  }
323
556
  return ids.length;
324
557
  }
558
+ /**
559
+ * Restore pending asks from disk after a daemon restart. Each becomes a DORMANT
560
+ * entry: its card is still live in Feishu (we do NOT re-post — cardMessageId is
561
+ * preserved), so a click can settle it, but there is no waiter Promise until the
562
+ * surviving CLI hook reconnects and re-registers (findDormantByKey →
563
+ * reattachDormantAsk installs a fresh resolve). Each dormant ask arms a timer to
564
+ * its ORIGINAL absolute deadline so it can't linger forever if the CLI never
565
+ * comes back. Called once during daemon bootstrap. Returns the count restored.
566
+ *
567
+ * Idempotent-ish: an askKey already present (e.g. re-invoked) is skipped.
568
+ */
569
+ export function restorePersistedAsks(now = Date.now(), larkAppId) {
570
+ let restored = 0;
571
+ if (!persistStore) {
572
+ logger.info?.('ask-broker: restore sweep skipped — no persist store wired');
573
+ return 0;
574
+ }
575
+ for (const p of persistStore.list(now)) {
576
+ // One bot per daemon process: only restore asks this daemon can actually
577
+ // serve (its own bot's sessions). Another bot's daemon owns the rest.
578
+ if (larkAppId && p.larkAppId !== larkAppId)
579
+ continue;
580
+ // Skip if this key is already live (defensive — boot runs once).
581
+ let dup = false;
582
+ for (const a of pending.values())
583
+ if (a.askKey === p.askKey && !a.settled) {
584
+ dup = true;
585
+ break;
586
+ }
587
+ if (dup)
588
+ continue;
589
+ const selections = new Map();
590
+ for (let i = 0; i < p.questions.length; i++) {
591
+ selections.set(i, new Set(p.selections?.[i] ?? []));
592
+ }
593
+ const hasStashedAnswer = p.answeredResult !== undefined;
594
+ // A stashed-answer restore is already terminal (settled), awaiting only the
595
+ // hook's claim — it must NOT arm a deadline timer (there's nothing left to
596
+ // time out). A still-awaiting-click restore arms the ORIGINAL absolute
597
+ // deadline so it can't linger forever if the CLI never returns.
598
+ const remaining = Math.max(0, p.deadlineAt - now);
599
+ const timeoutHandle = hasStashedAnswer
600
+ ? undefined
601
+ : setTimeout(() => {
602
+ settle(p.askId, {
603
+ kind: 'timedOut', selected: null, by: null, comment: null, timedOut: true,
604
+ });
605
+ }, remaining);
606
+ timeoutHandle?.unref?.();
607
+ const ask = {
608
+ askId: p.askId,
609
+ askKey: p.askKey,
610
+ requestId: p.requestId,
611
+ originKind: p.originKind,
612
+ resumable: true, // only resumable origins were ever persisted
613
+ nonce: p.nonce,
614
+ larkAppId: p.larkAppId,
615
+ chatId: p.chatId,
616
+ rootMessageId: p.rootMessageId,
617
+ sessionId: p.sessionId,
618
+ chatType: p.chatType,
619
+ questions: p.questions,
620
+ createdAt: p.createdAt,
621
+ deadlineAt: p.deadlineAt,
622
+ cardMessageId: p.cardMessageId,
623
+ // A stashed-answer restore is terminal-but-unclaimed: settled=true so
624
+ // gcSettled/other paths treat it as done, dormant=true so a hook re-POST
625
+ // routes to reattachByRequest to CLAIM it.
626
+ settled: hasStashedAnswer,
627
+ settledAt: hasStashedAnswer ? (p.answeredAt ?? now) : undefined,
628
+ dormant: true,
629
+ waiters: [], // attached when the reconnecting hook re-registers
630
+ // Carry a stashed answer forward (user answered before restart): the
631
+ // reconnecting hook claims it via reattachByRequest.
632
+ ...(p.answeredResult ? { answeredResult: p.answeredResult, terminalResult: p.answeredResult } : {}),
633
+ // timeoutHandle is only meaningful for the awaiting-click case; a stashed
634
+ // restore uses a no-op cleared handle so clearTimeout calls stay safe.
635
+ timeoutHandle: timeoutHandle ?? setTimeout(() => { }, 0),
636
+ selections,
637
+ };
638
+ if (timeoutHandle === undefined) {
639
+ clearTimeout(ask.timeoutHandle);
640
+ }
641
+ pending.set(p.askId, ask);
642
+ // Re-arm the absolute handoff-expiry reaper for a restored stash (codex
643
+ // P1-4): a SECOND restart before the hook claims must still reap memory +
644
+ // disk at the ORIGINAL answeredAt + retention, not restart the clock.
645
+ if (hasStashedAnswer)
646
+ armHandoffExpiry(ask);
647
+ restored++;
648
+ logger.info?.(`ask-broker: restored pending ask ${p.askId} (key=${p.askKey}, session=${p.sessionId}, ` +
649
+ `dormant${p.answeredResult ? '+answered' : ''}, ${Math.round((p.deadlineAt - now) / 1000)}s left) — awaiting hook re-attach`);
650
+ }
651
+ // Always log the boot sweep outcome (even 0) so a live restart test can
652
+ // confirm the restore path ran, not just infer it from behaviour.
653
+ logger.info?.(`ask-broker: restore sweep complete — ${restored} pending ask(s) restored` +
654
+ (larkAppId ? ` for ${larkAppId}` : ''));
655
+ return restored;
656
+ }
325
657
  /** Internal — settle an ask exactly once and notify the dispatcher's onSettle
326
658
  * hook (best-effort, never blocks broker state transitions). The settled
327
659
  * entry stays in the map for `SETTLED_RETENTION_MS` so late race-losers get
@@ -330,48 +662,129 @@ function settle(askId, result) {
330
662
  const ask = pending.get(askId);
331
663
  if (!ask || ask.settled)
332
664
  return;
665
+ // Durable handoff (codex P1-1): a dormant ask (restored after a restart, no
666
+ // waiter yet) that receives an ANSWER must NOT drop it into the void. Stash
667
+ // the terminal result and KEEP the persisted record so the reconnecting hook
668
+ // can claim it (reattachDormantAsk delivers + removes). Only answered results
669
+ // are worth stashing — a dormant ask that timed out / was invalidated has no
670
+ // consumer to hand off to, so it settles+cleans normally.
671
+ if (ask.dormant && ask.waiters.length === 0 && result.kind === 'answered') {
672
+ ask.settled = true;
673
+ ask.settledAt = Date.now();
674
+ ask.answeredResult = result;
675
+ ask.terminalResult = result;
676
+ clearTimeout(ask.timeoutHandle);
677
+ persistFromInternal(ask); // re-persist WITH answeredResult + answeredAt (kept until claimed)
678
+ // Arm the absolute handoff-expiry timer (codex P1-4): if the reconnecting
679
+ // hook never claims this stash (CLI gone for good), reap memory + disk
680
+ // TOGETHER at answeredAt + HANDOFF_RETENTION_MS — not just on the next boot
681
+ // list() sweep, which a long-running daemon never performs.
682
+ armHandoffExpiry(ask);
683
+ logger.info?.(`ask-broker: stashed answer for dormant ask ${askId} (key=${ask.askKey}) — awaiting hook claim`);
684
+ // Still notify the card layer so the Feishu card flips to its settled view.
685
+ notifyOnSettle(ask, result);
686
+ return;
687
+ }
333
688
  ask.settled = true;
334
689
  ask.settledAt = Date.now();
690
+ ask.terminalResult = result; // retained for a same-requestId replay in the ambiguous window
335
691
  clearTimeout(ask.timeoutHandle);
692
+ // The durable record's job is done the moment the ask leaves the pending
693
+ // state (delivered to live waiters, or a terminal non-answer) — drop it so a
694
+ // later restart doesn't resurrect a settled ask.
695
+ persistStore?.remove(ask.askKey);
336
696
  // Reap older settled entries opportunistically — keeps the map bounded
337
697
  // without paying for a dedicated GC timer.
338
698
  gcSettled();
339
- try {
340
- ask.resolve(result);
341
- }
342
- catch (err) {
343
- logger.warn?.(`ask-broker: ${askId} resolve threw: ${err instanceof Error ? err.message : String(err)}`);
344
- }
345
- if (dispatcher?.onSettle) {
699
+ // Resolve every waiter (normally one; >1 when a same-requestId active replay
700
+ // joined). A dormant ask has no waiters — the loop simply no-ops.
701
+ const waiters = ask.waiters;
702
+ ask.waiters = [];
703
+ for (const w of waiters) {
346
704
  try {
347
- void Promise.resolve(dispatcher.onSettle(snapshot(ask), result)).catch((err) => {
348
- logger.warn?.(`ask-broker: ${askId} onSettle failed: ${err instanceof Error ? err.message : String(err)}`);
349
- });
705
+ w(result);
350
706
  }
351
707
  catch (err) {
352
- logger.warn?.(`ask-broker: ${askId} onSettle threw: ${err instanceof Error ? err.message : String(err)}`);
708
+ logger.warn?.(`ask-broker: ${askId} waiter threw: ${err instanceof Error ? err.message : String(err)}`);
353
709
  }
354
710
  }
711
+ notifyOnSettle(ask, result);
712
+ }
713
+ /** Notify the IM-side dispatcher's onSettle hook (best-effort — never blocks
714
+ * broker state). Shared by the normal settle path and the dormant-answer stash. */
715
+ function notifyOnSettle(ask, result) {
716
+ if (!dispatcher?.onSettle)
717
+ return;
718
+ try {
719
+ void Promise.resolve(dispatcher.onSettle(snapshot(ask), result)).catch((err) => {
720
+ logger.warn?.(`ask-broker: ${ask.askId} onSettle failed: ${err instanceof Error ? err.message : String(err)}`);
721
+ });
722
+ }
723
+ catch (err) {
724
+ logger.warn?.(`ask-broker: ${ask.askId} onSettle threw: ${err instanceof Error ? err.message : String(err)}`);
725
+ }
355
726
  }
356
727
  /** Strip broker-internal fields before handing a snapshot to the IM-side
357
- * dispatcher. Keeps the dispatcher contract narrow. */
728
+ * dispatcher. Keeps the dispatcher contract narrow (PendingAsk only). */
358
729
  function snapshot(ask) {
359
- const { resolve: _r, timeoutHandle: _t, settledAt: _sat, selections: _sel, ...rest } = ask;
730
+ const {
731
+ // Runtime-only / broker-internal fields excluded from the IM contract:
732
+ waiters: _w, timeoutHandle: _t, settledAt: _sat, selections: _sel, askKey: _ak, requestId: _rid, originKind: _ok, resumable: _rs, dormant: _dm, answeredResult: _ar, terminalResult: _tr, ...rest } = ask;
360
733
  return {
361
734
  ...rest,
362
735
  selections: ask.questions.map((_, i) => [...(ask.selections.get(i) ?? new Set())]),
736
+ // EVERY ask carries a scoped dedupe token (codex P1-1): the broker's bounded
737
+ // retry re-sends the card, and a re-send without a uuid posts a DUPLICATE on
738
+ // the Feishu side. The uuid is the card's dedupe identity for THIS broker
739
+ // lifetime — derived from the scoped askKey, independent of persistence.
740
+ // `resumable` gates only cross-restart persistence, NOT intra-process
741
+ // dispatch idempotency, so explicit / PTY asks (which also retry) dedupe too.
742
+ dispatchUuid: dispatchUuidForKey(ask.askKey),
363
743
  };
364
744
  }
365
745
  /** Drop settled entries that have aged past the retention window. Cheap O(n)
366
- * walk — n is tiny in practice (≤ a few dozen pending+recent asks). */
746
+ * walk — n is tiny in practice (≤ a few dozen pending+recent asks). A dormant
747
+ * ask holding a stashed answer (durable handoff, awaiting the hook's claim) is
748
+ * NEVER reaped from memory here — it lives until claimed or its deadline; on
749
+ * disk it survives regardless, and a restart re-hydrates it. */
367
750
  function gcSettled() {
368
751
  const cutoff = Date.now() - SETTLED_RETENTION_MS;
369
752
  for (const [id, ask] of pending) {
753
+ if (ask.dormant && ask.answeredResult)
754
+ continue; // unclaimed handoff — keep
370
755
  if (ask.settled && ask.settledAt !== undefined && ask.settledAt < cutoff) {
371
756
  pending.delete(id);
372
757
  }
373
758
  }
374
759
  }
760
+ /**
761
+ * Arm an absolute-expiry timer for an unclaimed handoff stash (codex P1-4). A
762
+ * stashed answer is kept until the reconnecting hook claims it — but if the CLI
763
+ * is gone for good, nothing would ever remove it from a long-running daemon's
764
+ * memory (gcSettled deliberately skips unclaimed handoffs) OR from disk (the
765
+ * disk reap only runs in list() at boot). This timer closes both: at
766
+ * `answeredAt + HANDOFF_RETENTION_MS` it deletes the in-memory entry AND the
767
+ * persisted record TOGETHER, so memory and disk never diverge.
768
+ *
769
+ * Idempotent: clears any prior handle first (re-arming on a second restore is
770
+ * fine). The timer is unref'd so it never keeps the process alive.
771
+ */
772
+ function armHandoffExpiry(ask) {
773
+ clearTimeout(ask.handoffExpiryHandle);
774
+ const stashedAt = ask.settledAt ?? Date.now();
775
+ const fireIn = Math.max(0, stashedAt + handoffRetentionMs - Date.now());
776
+ ask.handoffExpiryHandle = setTimeout(() => {
777
+ // Only reap if STILL an unclaimed stash (a claim clears this handle, but
778
+ // guard against a late fire racing a claim).
779
+ const cur = pending.get(ask.askId);
780
+ if (!cur || !cur.dormant || !cur.answeredResult)
781
+ return;
782
+ pending.delete(cur.askId);
783
+ persistStore?.remove(cur.askKey);
784
+ logger.info?.(`ask-broker: handoff stash expired unclaimed for ask ${cur.askId} (key=${cur.askKey}) — reaped memory + disk`);
785
+ }, fireIn);
786
+ ask.handoffExpiryHandle.unref?.();
787
+ }
375
788
  // ---- diagnostics for tests ---------------------------------------------------
376
789
  /** Count of asks still awaiting a click / timeout — excludes settled entries
377
790
  * retained within the race-loser feedback window. For tests and metrics only. */
@@ -442,10 +855,17 @@ export function _allAskIds() {
442
855
  /** Reset broker state — for tests only. Does NOT resolve outstanding promises,
443
856
  * so tests must not call this while real CLI processes might be waiting. */
444
857
  export function _resetForTest() {
445
- for (const ask of pending.values())
858
+ for (const ask of pending.values()) {
446
859
  clearTimeout(ask.timeoutHandle);
860
+ clearTimeout(ask.handoffExpiryHandle);
861
+ }
447
862
  pending.clear();
448
863
  dispatcher = null;
449
864
  canTalkChecker = null;
865
+ handoffRetentionMs = HANDOFF_RETENTION_MS; // restore default retention
866
+ // Detach the injected store — never delete files here (codex P1-4: a helper
867
+ // reaping a shared dataDir could wipe a LIVE pending ask). Tests own their
868
+ // temp dir's cleanup via their own teardown.
869
+ persistStore = null;
450
870
  }
451
871
  //# sourceMappingURL=ask-broker.js.map