commandmate 0.46.0 → 0.46.1

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 (285) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +259 -259
  3. package/.next/app-path-routes-manifest.json +14 -14
  4. package/.next/build-manifest.json +5 -5
  5. package/.next/prerender-manifest.json +9 -9
  6. package/.next/react-loadable-manifest.json +2 -2
  7. package/.next/required-server-files.json +1 -1
  8. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
  10. package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/app/release-notes/route_client-reference-manifest.js +1 -1
  12. package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/capabilities/route.js +1 -1
  18. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
  27. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/hooks/claude-done/route.js +9 -9
  29. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  30. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
  32. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/relays/[relayId]/cancel/route.js.nft.json +1 -1
  39. package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/relays/route.js.nft.json +1 -1
  41. package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  45. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
  46. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/repositories/clone/route.js +1 -1
  48. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
  49. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/repositories/restore/route.js +1 -1
  52. package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
  53. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/repositories/route.js +1 -1
  55. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  56. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/repositories/scan/route.js +1 -1
  58. package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
  59. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/repositories/sync/route.js +1 -1
  61. package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
  62. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
  65. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  81. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  82. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
  84. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -1
  86. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
  87. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  92. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  93. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/worktrees/[id]/direct-input/route.js +1 -1
  95. package/.next/server/app/api/worktrees/[id]/direct-input/route.js.nft.json +1 -1
  96. package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
  126. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  128. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  131. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/logs/route.js +9 -9
  134. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  141. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  144. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  148. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  150. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  151. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  153. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  155. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  157. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  158. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  159. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
  160. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  161. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  163. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  165. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  166. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  167. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  168. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  169. package/.next/server/app/api/worktrees/[id]/special-keys/route.js +1 -1
  170. package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
  171. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  172. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  173. package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
  174. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  175. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  176. package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
  177. package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
  178. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  179. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  180. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  181. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  182. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  183. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  184. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  185. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  186. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  187. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  188. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  189. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  190. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  191. package/.next/server/app/api/worktrees/route.js +1 -1
  192. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  193. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  194. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  195. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  196. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  197. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  198. package/.next/server/app/page_client-reference-manifest.js +1 -1
  199. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  200. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  201. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  202. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  203. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  204. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  205. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  206. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  207. package/.next/server/app/worktrees/[id]/page.js +7 -7
  208. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  209. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  210. package/.next/server/app-paths-manifest.json +14 -14
  211. package/.next/server/chunks/1501.js +2 -2
  212. package/.next/server/chunks/1559.js +1 -1
  213. package/.next/server/chunks/2460.js +1 -1
  214. package/.next/server/chunks/2908.js +1 -1
  215. package/.next/server/chunks/2984.js +25 -0
  216. package/.next/server/chunks/5209.js +1 -1
  217. package/.next/server/chunks/5292.js +2 -2
  218. package/.next/server/chunks/5845.js +1 -1
  219. package/.next/server/chunks/6130.js +1 -1
  220. package/.next/server/chunks/6401.js +1 -1
  221. package/.next/server/chunks/6722.js +1 -1
  222. package/.next/server/chunks/8581.js +4 -4
  223. package/.next/server/chunks/8728.js +2 -2
  224. package/.next/server/chunks/8791.js +1 -1
  225. package/.next/server/chunks/8918.js +5 -5
  226. package/.next/server/chunks/9390.js +1 -1
  227. package/.next/server/chunks/9627.js +2 -1
  228. package/.next/server/chunks/9731.js +1 -1
  229. package/.next/server/middleware-build-manifest.js +1 -1
  230. package/.next/server/middleware-manifest.json +5 -5
  231. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  232. package/.next/server/pages/500.html +1 -1
  233. package/.next/server/server-reference-manifest.json +1 -1
  234. package/.next/static/chunks/{2407-acb644c5487e1720.js → 2407-9703c569370b8a9d.js} +1 -1
  235. package/.next/static/chunks/{2717.21abf5b6bbd45520.js → 2717.1583400c272dc1d9.js} +1 -1
  236. package/.next/static/chunks/5371-0dc947683e46e78c.js +1 -0
  237. package/.next/static/chunks/5794-4eee51ff1e878aa6.js +1 -0
  238. package/.next/static/chunks/{8407-9a72e6f07c5d5458.js → 8407-155f84e5ba05f1d5.js} +1 -1
  239. package/.next/static/chunks/app/{layout-ae30ce63569c22be.js → layout-3e357690446cd2da.js} +1 -1
  240. package/.next/static/chunks/app/worktrees/[id]/page-effbf0796881dd5a.js +1 -0
  241. package/.next/static/chunks/{webpack-c0b821f340afdebf.js → webpack-468b7b33030234b3.js} +1 -1
  242. package/.next/trace +13 -13
  243. package/dist/cli/commands/wait.d.ts.map +1 -1
  244. package/dist/cli/commands/wait.js +6 -1
  245. package/dist/cli/types/api-responses.d.ts +8 -6
  246. package/dist/cli/types/api-responses.d.ts.map +1 -1
  247. package/dist/lib/polling/auto-yes-suppression-reason.d.ts +13 -1
  248. package/dist/lib/polling/auto-yes-suppression-reason.d.ts.map +1 -1
  249. package/dist/lib/polling/auto-yes-suppression-reason.js +3 -0
  250. package/dist/server/src/lib/assistant-response-saver.js +13 -0
  251. package/dist/server/src/lib/auto-yes-poller.js +252 -54
  252. package/dist/server/src/lib/conversation-logger.js +14 -2
  253. package/dist/server/src/lib/db/chat-db.js +2 -1
  254. package/dist/server/src/lib/history/user-turn-recorder.js +14 -0
  255. package/dist/server/src/lib/hooks/sources/antigravity/history.js +28 -0
  256. package/dist/server/src/lib/hooks/sources/claude/history.js +7 -0
  257. package/dist/server/src/lib/hooks/sources/codex/history.js +7 -0
  258. package/dist/server/src/lib/hooks/sources/command-code/history.js +7 -0
  259. package/dist/server/src/lib/hooks/sources/transcript-history.js +34 -0
  260. package/dist/server/src/lib/log-manager.js +7 -2
  261. package/dist/server/src/lib/polling/auto-yes-enter-fallback-state.js +22 -3
  262. package/dist/server/src/lib/polling/auto-yes-poller-state.js +234 -0
  263. package/dist/server/src/lib/polling/auto-yes-suppression-reason.js +3 -0
  264. package/dist/server/src/lib/polling/pending-scraped-response.js +119 -3
  265. package/dist/server/src/lib/polling/poller-owner.js +110 -0
  266. package/dist/server/src/lib/polling/response-checker.js +202 -39
  267. package/dist/server/src/lib/polling/response-dedup.js +8 -0
  268. package/dist/server/src/lib/polling/response-poller-core.js +131 -12
  269. package/dist/server/src/lib/polling/structured-history-gate.js +20 -1
  270. package/dist/server/src/lib/prompt-answer-sender.js +59 -12
  271. package/dist/server/src/lib/realtime/terminal-broadcast.js +14 -0
  272. package/dist/server/src/lib/session/chat-turn-progress.js +57 -6
  273. package/dist/server/src/lib/session/current-output-builder.js +19 -0
  274. package/dist/server/src/lib/session/worktree-session-reconcile.js +83 -10
  275. package/dist/server/src/lib/timer-manager.js +7 -0
  276. package/dist/server/src/lib/tmux/tmux.js +24 -2
  277. package/dist/server/src/lib/verification/env-clean-gate.js +30 -2
  278. package/package.json +1 -1
  279. package/release-notes/0.46.1.json +40 -0
  280. package/.next/server/chunks/5625.js +0 -25
  281. package/.next/static/chunks/5371-a94ed618148287a0.js +0 -1
  282. package/.next/static/chunks/5794-947fb4934d8be193.js +0 -1
  283. package/.next/static/chunks/app/worktrees/[id]/page-2eb1d0fd0a077b7e.js +0 -1
  284. /package/.next/static/{zI2nQ6WKboSbFbMF4EtrJ → QxDf7lm-JJkWoj-TyWiSB}/_buildManifest.js +0 -0
  285. /package/.next/static/{zI2nQ6WKboSbFbMF4EtrJ → QxDf7lm-JJkWoj-TyWiSB}/_ssgManifest.js +0 -0
@@ -9,13 +9,17 @@ exports.stopPolling = stopPolling;
9
9
  exports.stopAllPolling = stopAllPolling;
10
10
  exports.getActivePollers = getActivePollers;
11
11
  exports.migrateResponsePollerWorktreeIds = migrateResponsePollerWorktreeIds;
12
- const async_hooks_1 = require("async_hooks");
13
12
  const logger_1 = require("../../lib/logger");
14
13
  const tui_accumulator_1 = require("../tui-accumulator");
15
14
  const prompt_dedup_1 = require("./prompt-dedup");
16
15
  const response_dedup_1 = require("./response-dedup");
17
16
  const prompt_dedup_state_1 = require("./prompt-dedup-state");
18
17
  const response_checker_1 = require("./response-checker");
18
+ // Issue #3663: the migration's own entry points into the held-scrape store,
19
+ // imported from the store itself rather than through `./response-checker`'s
20
+ // re-export (suites that replace the checker by path keep working unchanged).
21
+ const pending_scraped_response_1 = require("./pending-scraped-response");
22
+ const poller_owner_1 = require("./poller-owner");
19
23
  const terminal_broadcast_1 = require("../../lib/realtime/terminal-broadcast");
20
24
  const terminal_session_ownership_1 = require("../../lib/realtime/terminal-session-ownership");
21
25
  const session_name_1 = require("../../lib/cli-tools/session-name");
@@ -58,7 +62,7 @@ const coordinator = (0, global_state_1.getOrInitGlobal)('__responsePollerCoordin
58
62
  owners: new Map(),
59
63
  running: new Map(),
60
64
  pendingRestart: new Map(),
61
- ownerStorage: new async_hooks_1.AsyncLocalStorage(),
65
+ latestOwners: new Map(),
62
66
  generationCounter: 0,
63
67
  pausedOnPrompt: new Set(),
64
68
  }));
@@ -68,6 +72,7 @@ const coordinator = (0, global_state_1.getOrInitGlobal)('__responsePollerCoordin
68
72
  // `globalThis` across HMR, and a poller started before the reload would
69
73
  // otherwise crash the first tick after it.
70
74
  coordinator.pausedOnPrompt ??= new Set();
75
+ coordinator.latestOwners ??= new Map();
71
76
  /**
72
77
  * Active pollers map: "worktreeId:instanceId" -> NodeJS.Timeout
73
78
  *
@@ -101,11 +106,17 @@ exports.pollingStartTimes = coordinator.pollingStartTimes;
101
106
  * @param instanceId - Optional agent instance ID (defaults to cliToolId)
102
107
  */
103
108
  function getPollerKey(worktreeId, cliToolId, instanceId) {
104
- return `${worktreeId}:${instanceId ?? cliToolId}`;
109
+ // Issue #3705: spelled in `./poller-owner`, so the writers that ask whether a
110
+ // tick may still act for this key name it the same way without importing here.
111
+ return (0, poller_owner_1.pollerKeyOf)(worktreeId, cliToolId, instanceId);
105
112
  }
106
113
  /** Hand `pollerKey` to a brand-new chain, retiring whichever chain held it. */
107
114
  function takeOwnership(pollerKey) {
108
- const previous = coordinator.owners.get(pollerKey);
115
+ // Issue #3705: the newest chain the key ever had, not only the one holding it
116
+ // now. A chain that was stopped (a route, a migration that displaced it) has
117
+ // left `owners`, but a tick of it may still be in flight, and it has a
118
+ // successor from this moment on.
119
+ const previous = coordinator.owners.get(pollerKey) ?? coordinator.latestOwners.get(pollerKey);
109
120
  if (previous)
110
121
  previous.superseded = true;
111
122
  const owner = {
@@ -116,6 +127,7 @@ function takeOwnership(pollerKey) {
116
127
  selfStopped: false,
117
128
  };
118
129
  coordinator.owners.set(pollerKey, owner);
130
+ coordinator.latestOwners.set(pollerKey, owner);
119
131
  return owner;
120
132
  }
121
133
  /**
@@ -171,7 +183,7 @@ function startPolling(worktreeId, cliToolId, instanceId) {
171
183
  // previous turn.
172
184
  inFlight.owner.superseded = true;
173
185
  if (Date.now() - inFlight.since < exports.STALLED_TICK_TIMEOUT) {
174
- coordinator.pendingRestart.set(pollerKey, target);
186
+ coordinator.pendingRestart.set(pollerKey, { target, behind: inFlight.owner });
175
187
  return;
176
188
  }
177
189
  logger.warn('poller:restart-past-stalled-tick', {
@@ -293,7 +305,11 @@ async function runPollTick(target, owner) {
293
305
  // Check for response
294
306
  let recorded = false;
295
307
  try {
296
- recorded = await coordinator.ownerStorage.run(owner, () => (0, response_checker_1.checkForResponse)(target.worktreeId, target.cliToolId, target.instanceId));
308
+ // The token rides through `checkForResponse()` (`./poller-owner`), so a
309
+ // `stopPolling()` raised inside a superseded tick can be told apart from
310
+ // one raised by a route, `session-cleanup` or `kill-session` (#2223), and
311
+ // the held-scrape store can turn away a tick that was retired (#3663).
312
+ recorded = await poller_owner_1.pollerOwnerStorage.run(owner, () => (0, response_checker_1.checkForResponse)(target.worktreeId, target.cliToolId, target.instanceId));
297
313
  }
298
314
  catch (error) {
299
315
  logger.error('error:', { error: error instanceof Error ? error.message : String(error) });
@@ -310,8 +326,14 @@ async function runPollTick(target, owner) {
310
326
  // Issue #2223: skipped once another chain owns the key — a superseded tick
311
327
  // would push the previous turn's screen on top of the live one, and its
312
328
  // `version` counter would make the client drop the real frame that follows.
329
+ //
330
+ // Issue #3705: and the snapshot carries the owner explicitly
331
+ // (`snapshotOwnerStorage`), because this is only where it starts. Its
332
+ // capture and the writers inside `buildCurrentOutput` resume after awaits,
333
+ // and they ask again then (`maySnapshotPublish`): a successor that took the
334
+ // key in the meantime is not overwritten by the turn that ended.
313
335
  if (!owner.superseded) {
314
- void (0, terminal_broadcast_1.broadcastTerminalSnapshot)(target.worktreeId, target.cliToolId, target.instanceId);
336
+ void poller_owner_1.snapshotOwnerStorage.run(owner, () => (0, terminal_broadcast_1.broadcastTerminalSnapshot)(target.worktreeId, target.cliToolId, target.instanceId));
315
337
  }
316
338
  // Schedule next poll ONLY after current one completes
317
339
  // Guard: only if THIS chain is still the poller for the key (Issue #2223)
@@ -326,10 +348,15 @@ async function runPollTick(target, owner) {
326
348
  // Apply a restart that arrived while this tick was in flight. Doing it here
327
349
  // rather than in startPolling() is what keeps the old and new chains from
328
350
  // polling the same pane at the same time.
351
+ //
352
+ // Issue #3705: only the restart handed to THIS tick. One queued behind a
353
+ // later chain's tick belongs to that tick: applied here, by a tick that was
354
+ // cut loose past STALLED_TICK_TIMEOUT and came back, it would stop the chain
355
+ // whose tick is still running and start the next one beside it.
329
356
  const pending = coordinator.pendingRestart.get(pollerKey);
330
- if (pending) {
357
+ if (pending && pending.behind === owner) {
331
358
  coordinator.pendingRestart.delete(pollerKey);
332
- restartChain(pollerKey, pending);
359
+ restartChain(pollerKey, pending.target);
333
360
  }
334
361
  }
335
362
  }
@@ -391,7 +418,7 @@ function stopPollingByKey(pollerKey, options = {}) {
391
418
  // (worktree gone, session not running, prompt detected, TUI reply saved).
392
419
  // Honouring that verdict would kill the chain that replaced it — the old
393
420
  // tick is describing a turn that is already over.
394
- const caller = coordinator.ownerStorage.getStore();
421
+ const caller = poller_owner_1.pollerOwnerStorage.getStore();
395
422
  if (caller && caller.pollerKey === pollerKey && (caller.superseded || caller.stopped)) {
396
423
  logger.info('poller:stale-stop-ignored', {
397
424
  pollerKey,
@@ -538,6 +565,18 @@ function splitPollerKey(pollerKey) {
538
565
  * alike — which is lifted off every source key in one pass and only then
539
566
  * written, so a pair handled early cannot overwrite a source handled later.
540
567
  *
568
+ * Issue #3663: the held scrapes (#2436) move too, by the same lift-then-place
569
+ * rule, keeping everything but their worktree ID, which becomes the new one.
570
+ * Every chain whose key changes hands is retired (`stopped`), so a tick still
571
+ * awaiting the transcript reader cannot come back and hold, drop or write
572
+ * under it: the moved chains' old owners, and a chain already running on a
573
+ * destination key that no source vacates — that one is stopped like any
574
+ * other, which writes its held scrape under the ID it carries before the moved
575
+ * one arrives. A chain that cannot be resolved has its held scrape written
576
+ * under the NEW ID: the old one is gone from the table, and a row for it fails
577
+ * the foreign key and is lost. A chain with a tick running at the moment of
578
+ * the migration does not hand its response hash on (see the dedup move below).
579
+ *
541
580
  * @param renames - Worktree ID pairs being applied
542
581
  * @param resolveCliToolId - Maps a poller's (worktree, instance) back to its CLI
543
582
  * tool, which the poller key does not encode for alias instances. Callers pass
@@ -565,7 +604,7 @@ function migrateResponsePollerWorktreeIds(renames, resolveCliToolId) {
565
604
  continue;
566
605
  const cliToolId = resolveCliToolId(parts.worktreeId, parts.instanceId);
567
606
  if (!cliToolId) {
568
- abandoned.push(pollerKey);
607
+ abandoned.push({ pollerKey, newWorktreeId });
569
608
  continue;
570
609
  }
571
610
  moves.push({
@@ -579,6 +618,16 @@ function migrateResponsePollerWorktreeIds(renames, resolveCliToolId) {
579
618
  // Phase 1: stop every affected timer and lift the state off the old keys.
580
619
  // Done for the whole batch before any write so a swap cannot self-collide.
581
620
  const carried = new Map();
621
+ // Issue #3663: the moves whose chain has a tick running right now — read
622
+ // before anything below touches the coordinator. See the response hash below.
623
+ // The chain that holds the key now, not merely some tick under the key: a
624
+ // chain retired earlier leaves its `running` entry until its own tick
625
+ // returns (the epilogue removes it by owner), and the chain that replaced it
626
+ // may not have started a tick yet.
627
+ const midTick = moves.filter((move) => {
628
+ const inFlight = coordinator.running.get(move.oldKey);
629
+ return inFlight !== undefined && inFlight.owner === coordinator.owners.get(move.oldKey);
630
+ });
582
631
  for (const move of moves) {
583
632
  const timerId = exports.activePollers.get(move.oldKey);
584
633
  if (timerId)
@@ -608,10 +657,15 @@ function migrateResponsePollerWorktreeIds(renames, resolveCliToolId) {
608
657
  // An abandoned key is never a move source, so nothing still to be moved is
609
658
  // lost here; and the paused chains are collected after it, as before, so a
610
659
  // pause the stop ends is not carried.
611
- for (const pollerKey of abandoned) {
660
+ //
661
+ // Issue #3663: its held scrape is pointed at the new ID first, so the stop
662
+ // writes it there — the old ID is no longer in the table.
663
+ for (const { pollerKey, newWorktreeId } of abandoned) {
664
+ (0, pending_scraped_response_1.retargetPendingScrapedResponse)(pollerKey, newWorktreeId);
612
665
  stopPollingByKey(pollerKey);
613
666
  logger.warn('poller:migrate-abandoned', { pollerKey });
614
667
  }
668
+ const abandonedKeys = new Set(abandoned.map(({ pollerKey }) => pollerKey));
615
669
  // Issue #2230: a chain paused on a prompt has no timer, so the loops here
616
670
  // never see it — but it is holding the response hash for the turn it paused
617
671
  // on, and `/respond` will resume it under the NEW ID. Move the hash and the
@@ -626,11 +680,76 @@ function migrateResponsePollerWorktreeIds(renames, resolveCliToolId) {
626
680
  continue;
627
681
  pausedMoves.push({ oldKey: pollerKey, newKey: `${newWorktreeId}:${parts.instanceId}` });
628
682
  }
683
+ // Issue #3663: every held scrape under a renamed worktree ID moves — a
684
+ // running chain's, a paused chain's, and one whose chain is already gone.
685
+ // The abandoned ones were written above and are no longer held.
686
+ const pendingMoves = [];
687
+ for (const pollerKey of (0, pending_scraped_response_1.listPendingScrapedResponseKeys)()) {
688
+ if (abandonedKeys.has(pollerKey))
689
+ continue;
690
+ const parts = splitPollerKey(pollerKey);
691
+ if (!parts)
692
+ continue;
693
+ const newWorktreeId = targets.get(parts.worktreeId);
694
+ if (!newWorktreeId)
695
+ continue;
696
+ pendingMoves.push({ oldKey: pollerKey, newKey: `${newWorktreeId}:${parts.instanceId}`, newWorktreeId });
697
+ }
698
+ // Issue #3663: a destination a chain (running or paused) moves onto, that no
699
+ // source of this batch vacates, may already be held by another chain — B:x
700
+ // running while A:x moves to B:x. That chain loses the key, so it is stopped
701
+ // here like any other stop: its held scrape is written under the ID it
702
+ // carries (the current one), its owner is retired so its in-flight tick can
703
+ // no longer hold or drop anything, and its caches are cleared before the
704
+ // moved state is written. A destination nothing is on is left alone, and so
705
+ // is every other instance of the destination worktree.
706
+ const vacated = new Set([
707
+ ...moves.map((move) => move.oldKey),
708
+ ...pausedMoves.map((move) => move.oldKey),
709
+ ...pendingMoves.map((move) => move.oldKey),
710
+ ]);
711
+ // A held scrape moved on its own (no chain, no pause) changes the key's
712
+ // owner just the same, so its destination is handled alike (#3663 review).
713
+ const occupied = new Set([
714
+ ...moves.map((move) => move.newKey),
715
+ ...pausedMoves.map((move) => move.newKey),
716
+ ...pendingMoves.map((move) => move.newKey),
717
+ ]);
718
+ for (const pollerKey of occupied) {
719
+ if (vacated.has(pollerKey) || abandonedKeys.has(pollerKey))
720
+ continue;
721
+ const held = coordinator.owners.has(pollerKey) ||
722
+ exports.activePollers.has(pollerKey) ||
723
+ coordinator.pausedOnPrompt.has(pollerKey) ||
724
+ (0, pending_scraped_response_1.hasPendingScrapedResponse)(pollerKey);
725
+ if (!held)
726
+ continue;
727
+ stopPollingByKey(pollerKey);
728
+ logger.warn('poller:migrate-displaced', { pollerKey });
729
+ }
629
730
  // Issue #3553: the dedup state of the whole batch moves in one call per map
630
731
  // family, each lifting every source before writing any destination. Re-keyed
631
732
  // pair by pair, a swap left A with its own state and B empty.
733
+ //
734
+ // Issue #3663: except the response hash of a chain whose tick is running.
735
+ // That tick may have registered the hash of a reply it has not yet saved or
736
+ // held — it is awaiting the transcript reader, and the hold it comes back
737
+ // with is refused (its owner is retired above). Handed on, the hash would
738
+ // make the successor skip a reply nobody wrote. Dropped, the successor judges
739
+ // the screen itself and holds or saves it. The price, taken knowingly: a
740
+ // reply that tick HAD already saved may be saved once more by the successor
741
+ // — a duplicate row rather than a lost one, the trade #2436 makes. Deciding
742
+ // here, once, replaces marking the hash and taking it back later, whose
743
+ // lifetime every later move, resume and restart would have to carry.
744
+ // The prompt hash and the prompt-skip tally move as before.
745
+ // Dropped on the source key only, before the batch moves: the destination is
746
+ // left as the batch leaves it, so what another source of a many-to-one move
747
+ // brings there (a chain that saved its reply and is waiting) still stands.
748
+ for (const move of midTick)
749
+ (0, response_dedup_1.clearResponseHashCache)(move.oldKey);
632
750
  (0, prompt_dedup_1.renamePromptHashCacheKeys)(moves);
633
751
  (0, response_dedup_1.renameResponseHashCacheKeys)([...moves, ...pausedMoves]);
752
+ (0, pending_scraped_response_1.movePendingScrapedResponses)(pendingMoves);
634
753
  // Issue #1695: the prompt hash moved above, so the guard keeps suppressing
635
754
  // the prompt on screen under the new ID. Its tally has to move with it, or
636
755
  // `capture --json` reports zero skips for a session that is still skipping.
@@ -84,6 +84,7 @@ const tool_id_4 = require("../../lib/hooks/sources/command-code/tool-id");
84
84
  const registry_1 = require("../../lib/hooks/sources/registry");
85
85
  const relay_triggers_1 = require("../../lib/relay/relay-triggers");
86
86
  const global_state_1 = require("../global-state");
87
+ const poller_owner_1 = require("./poller-owner");
87
88
  const logger = (0, logger_1.createLogger)('lib/polling/structured-history-gate');
88
89
  /**
89
90
  * One in-flight capture per instance (Issue #2246).
@@ -382,7 +383,25 @@ async function captureStructuredHistoryTurn(worktreeId, cliToolId, instanceId, c
382
383
  // back. So a provisional capture announces nothing at all; the relay
383
384
  // keeps waiting on the deadline it already has, and the settled capture
384
385
  // that follows announces the finished body.
385
- if (captured && completion === 'settled') {
386
+ //
387
+ // Issue #3701: a poller tick retired while it awaited the reader
388
+ // announces nothing under the key it lost, and claims nothing, so its
389
+ // successor still announces this completion. Asked by the poller key
390
+ // (`getPollerKey`'s `worktreeId:instanceId`), not `captureKey`. The
391
+ // Stop hook runs outside any tick and is never refused. Issue #3705:
392
+ // what the reader itself writes during the `await` (rows, user rows,
393
+ // the cursor, agy's follow list) is asked the same question by the
394
+ // reader, after its own last `await` (`mayReaderWrite`).
395
+ const pollerKey = (0, poller_owner_1.pollerKeyOf)(worktreeId, cliToolId, resolvedInstanceId);
396
+ if (captured && completion === 'settled' && !(0, poller_owner_1.mayActForPollerKey)(pollerKey)) {
397
+ logger.info('structured-history-completion-refused', {
398
+ worktreeId,
399
+ cliToolId,
400
+ instanceId: resolvedInstanceId,
401
+ generation: (0, poller_owner_1.currentPollerOwner)()?.generation,
402
+ });
403
+ }
404
+ else if (captured && completion === 'settled') {
386
405
  if (claimCompletionAnnouncement(captureKey, readerReport.completionKey)) {
387
406
  (0, relay_triggers_1.onRelayTurnCompleted)(worktreeId, cliToolId, resolvedInstanceId, true);
388
407
  }
@@ -7,7 +7,7 @@
7
7
  * the promptType/defaultOptionNumber fallback introduced in Bug1).
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.MultiSelectAnswerRejectedError = exports.MULTI_SELECT_NOT_COMMITTED_REASON = exports.FreeTextAtChoiceOnlyPromptError = exports.FreeTextAnswerRejectedError = exports.FREE_TEXT_AT_MENU_ROW_REASON = exports.PromptAnswerRejectedError = exports.ANSWER_MODE_KEYS_REASON = void 0;
10
+ exports.PromptAnswerAbortedError = exports.MultiSelectAnswerRejectedError = exports.MULTI_SELECT_NOT_COMMITTED_REASON = exports.FreeTextAtChoiceOnlyPromptError = exports.FreeTextAnswerRejectedError = exports.FREE_TEXT_AT_MENU_ROW_REASON = exports.PromptAnswerRejectedError = exports.ANSWER_MODE_KEYS_REASON = void 0;
11
11
  exports.sendPromptAnswer = sendPromptAnswer;
12
12
  const tmux_1 = require("./tmux/tmux");
13
13
  const frame_1 = require("../lib/detection/tools/frame");
@@ -665,7 +665,7 @@ async function recaptureAfterKeys(sessionName) {
665
665
  * @throws {MultiSelectAnswerRejectedError} whenever the question was not
666
666
  * committed; `keysSent` says whether the pane was touched.
667
667
  */
668
- async function sendCommandCodeMultiSelectAnswer(sessionName, promptData, wanted) {
668
+ async function sendCommandCodeMultiSelectAnswer(sessionName, promptData, wanted, stillCurrent) {
669
669
  const cursor = promptData.options.find((option) => option.isDefault === true);
670
670
  if (cursor === undefined) {
671
671
  throw new MultiSelectAnswerRejectedError('cursor-unknown', false, wanted);
@@ -679,12 +679,13 @@ async function sendCommandCodeMultiSelectAnswer(sessionName, promptData, wanted)
679
679
  ...current.filter((n) => !wanted.includes(n)),
680
680
  ].sort((a, b) => a - b);
681
681
  if (toggles.length > 0) {
682
- await (0, tmux_1.sendSpecialKeys)(sessionName, toggles.map(String));
682
+ await sendSpecialKeysWhileCurrent(sessionName, toggles.map(String), stillCurrent);
683
683
  }
684
684
  const keysSent = toggles.length > 0;
685
685
  // 2. Read the screen back and refuse unless it is the same question with
686
686
  // exactly the requested boxes ticked.
687
687
  const afterToggle = await recaptureAfterKeys(sessionName);
688
+ assertStillCurrent(stillCurrent, keysSent);
688
689
  if (afterToggle === null) {
689
690
  throw new MultiSelectAnswerRejectedError('recapture-failed', keysSent, wanted);
690
691
  }
@@ -711,14 +712,12 @@ async function sendCommandCodeMultiSelectAnswer(sessionName, promptData, wanted)
711
712
  if (cursorNow === undefined) {
712
713
  throw new MultiSelectAnswerRejectedError('cursor-unknown', keysSent, wanted);
713
714
  }
714
- await (0, tmux_1.sendSpecialKeys)(sessionName, [
715
- ...downKeys(verified.options.length - cursorNow.number + 1),
716
- 'Enter',
717
- ]);
715
+ await sendSpecialKeysWhileCurrent(sessionName, [...downKeys(verified.options.length - cursorNow.number + 1), 'Enter'], stillCurrent);
718
716
  // 4. The second stage. `Submit` opens the Review page and the answer is sent
719
717
  // from there; `Next` advances to the following question, which IS the
720
718
  // commit for this one (its tab turns `✔`).
721
719
  const afterConfirm = await recaptureAfterKeys(sessionName);
720
+ assertStillCurrent(stillCurrent, true);
722
721
  if (afterConfirm === null) {
723
722
  throw new MultiSelectAnswerRejectedError('recapture-failed', true, wanted);
724
723
  }
@@ -729,7 +728,7 @@ async function sendCommandCodeMultiSelectAnswer(sessionName, promptData, wanted)
729
728
  if (review.hasUnansweredWarning) {
730
729
  throw new MultiSelectAnswerRejectedError('review-unanswered', true, wanted);
731
730
  }
732
- await (0, tmux_1.sendSpecialKeys)(sessionName, ['Enter']);
731
+ await sendSpecialKeysWhileCurrent(sessionName, ['Enter'], stillCurrent);
733
732
  return;
734
733
  }
735
734
  const next = (0, dialog_1.readCommandCodeQuestionDialog)(afterConfirm);
@@ -740,6 +739,50 @@ async function sendCommandCodeMultiSelectAnswer(sessionName, promptData, wanted)
740
739
  throw new MultiSelectAnswerRejectedError('not-committed', true, wanted);
741
740
  }
742
741
  }
742
+ /**
743
+ * The caller's `stillCurrent` said no after one of the send's waits
744
+ * (Issue #3706): the keys not yet sent were not sent. Keys already typed cannot
745
+ * be taken back, so {@link keysSent} says whether the pane was touched; either
746
+ * way the answer was NOT completed and must not be recorded as one.
747
+ */
748
+ class PromptAnswerAbortedError extends Error {
749
+ keysSent;
750
+ constructor(
751
+ /** Whether any key reached the pane before the stop. */
752
+ keysSent) {
753
+ super(`Prompt answer stopped by its caller (${keysSent ? 'some keys were sent' : 'no key was sent'})`);
754
+ this.keysSent = keysSent;
755
+ this.name = 'PromptAnswerAbortedError';
756
+ }
757
+ }
758
+ exports.PromptAnswerAbortedError = PromptAnswerAbortedError;
759
+ /** Stop here when the caller's `stillCurrent` says no (Issue #3706). */
760
+ function assertStillCurrent(stillCurrent, keysSent) {
761
+ if (stillCurrent && !stillCurrent())
762
+ throw new PromptAnswerAbortedError(keysSent);
763
+ }
764
+ /**
765
+ * `sendSpecialKeys`, asking `stillCurrent` between the keys when the caller
766
+ * gave one (Issue #3706). Without it the call is exactly the old two-argument
767
+ * one, so manual answers and every other caller send as before.
768
+ */
769
+ async function sendSpecialKeysWhileCurrent(sessionName, keys, stillCurrent) {
770
+ if (!stillCurrent) {
771
+ await (0, tmux_1.sendSpecialKeys)(sessionName, keys);
772
+ return;
773
+ }
774
+ try {
775
+ await (0, tmux_1.sendSpecialKeys)(sessionName, keys, { shouldContinue: stillCurrent });
776
+ }
777
+ catch (error) {
778
+ // By name rather than `instanceof`: test doubles of `./tmux/tmux` need not
779
+ // export the class.
780
+ if (error instanceof Error && error.name === 'SpecialKeysAbortedError') {
781
+ throw new PromptAnswerAbortedError(true);
782
+ }
783
+ throw error;
784
+ }
785
+ }
743
786
  /**
744
787
  * Send an answer to a tmux session, using cursor-key navigation for
745
788
  * Claude Code / Antigravity multiple-choice prompts and text input for
@@ -750,7 +793,7 @@ async function sendCommandCodeMultiSelectAnswer(sessionName, promptData, wanted)
750
793
  * - src/lib/auto-yes-manager.ts (L340-399)
751
794
  */
752
795
  async function sendPromptAnswer(params) {
753
- const { sessionName, answer, cliToolId, promptData, fallbackPromptType, fallbackDefaultOptionNumber } = params;
796
+ const { sessionName, answer, cliToolId, promptData, fallbackPromptType, fallbackDefaultOptionNumber, stillCurrent } = params;
754
797
  // Issue #2755: Command Code's checkbox question, before every guard below.
755
798
  // Its answer is a SET (`"1,3"`), which none of them can read — `/^\d+$/` is
756
799
  // how all three ask "is this a number?", so a set would be judged as free
@@ -760,7 +803,7 @@ async function sendPromptAnswer(params) {
760
803
  // a two-stage confirm, not a digit and an Enter.
761
804
  const multiSelect = readMultiSelectRequest(params);
762
805
  if (multiSelect !== null) {
763
- await sendCommandCodeMultiSelectAnswer(sessionName, multiSelect.promptData, multiSelect.wanted);
806
+ await sendCommandCodeMultiSelectAnswer(sessionName, multiSelect.promptData, multiSelect.wanted, stillCurrent);
764
807
  (0, tmux_capture_cache_1.invalidateCache)(sessionName);
765
808
  return;
766
809
  }
@@ -781,6 +824,8 @@ async function sendPromptAnswer(params) {
781
824
  // the Enter after them confirms that row — so the cursor has to be checked
782
825
  // here too, before the branch.
783
826
  assertFreeTextCursorIsOnTheField(params);
827
+ // Issue #3706: the guard above may have read the pane; nothing is typed yet.
828
+ assertStillCurrent(stillCurrent, false);
784
829
  // Determine if this is an arrow-key-navigated multiple-choice prompt.
785
830
  // Claude Code and Antigravity (agy) both render selection menus that only
786
831
  // respond to cursor navigation + Enter, not to typed option numbers
@@ -829,7 +874,7 @@ async function sendPromptAnswer(params) {
829
874
  keys.push(...buildNavigationKeys(downToNext));
830
875
  // 4. Enter to submit
831
876
  keys.push('Enter');
832
- await (0, tmux_1.sendSpecialKeys)(sessionName, keys);
877
+ await sendSpecialKeysWhileCurrent(sessionName, keys, stillCurrent);
833
878
  }
834
879
  else {
835
880
  // Single-select: navigate and Enter to select
@@ -847,7 +892,7 @@ async function sendPromptAnswer(params) {
847
892
  const keys = (isAskUserQuestionPicker && offset === 0)
848
893
  ? ['Down', 'Up', 'Enter']
849
894
  : [...navigationKeys, 'Enter'];
850
- await (0, tmux_1.sendSpecialKeys)(sessionName, keys);
895
+ await sendSpecialKeysWhileCurrent(sessionName, keys, stillCurrent);
851
896
  }
852
897
  }
853
898
  else {
@@ -858,6 +903,8 @@ async function sendPromptAnswer(params) {
858
903
  if (!shouldSuppressEnter(params, resolvedSubmitMode, dialog)) {
859
904
  // Wait a moment for the input to be processed
860
905
  await new Promise(resolve => setTimeout(resolve, cli_tool_timing_config_1.TUI_TEXT_INPUT_WAIT_MS));
906
+ // Issue #3706: the text is typed; the Enter that submits it is not.
907
+ assertStillCurrent(stillCurrent, true);
861
908
  // Send Enter
862
909
  await (0, tmux_1.sendKeys)(sessionName, '', true);
863
910
  }
@@ -24,6 +24,7 @@ const manager_1 = require("../../lib/cli-tools/manager");
24
24
  const tmux_capture_cache_1 = require("../../lib/tmux/tmux-capture-cache");
25
25
  const global_state_1 = require("../global-state");
26
26
  const terminal_session_ownership_1 = require("./terminal-session-ownership");
27
+ const poller_owner_1 = require("../../lib/polling/poller-owner");
27
28
  const logger = (0, logger_1.createLogger)('terminal-broadcast');
28
29
  const versionCounters = (0, global_state_1.getOrInitGlobal)('__terminalSnapshotVersions', () => new Map());
29
30
  exports.INTERACTION_SNAPSHOT_RETRY_DELAYS_MS = [100, 250, 500, 750];
@@ -43,11 +44,24 @@ async function broadcastTerminalSnapshot(worktreeId, cliToolId, instanceId) {
43
44
  try {
44
45
  if (!(await isPushableSession(worktreeId, cliToolId, instanceId)))
45
46
  return;
47
+ // Issue #3705: the response poller starts this snapshot inside its tick's
48
+ // owner (`snapshotOwnerStorage`) and the question is asked again after each
49
+ // `await`: a successor that took the key meanwhile is not overwritten by
50
+ // the turn that ended, and its `version` is not spent on it. Outside a
51
+ // poller tick, always yes.
52
+ if (!(0, poller_owner_1.maySnapshotPublish)())
53
+ throw new poller_owner_1.RetiredSnapshotError();
46
54
  const db = (0, db_instance_1.getDbInstance)();
47
55
  const payload = await (0, current_output_builder_1.buildCurrentOutput)(db, worktreeId, cliToolId, instanceId);
56
+ if (!(0, poller_owner_1.maySnapshotPublish)())
57
+ throw new poller_owner_1.RetiredSnapshotError();
48
58
  emitTerminalSnapshot(worktreeId, cliToolId, payload, instanceId);
49
59
  }
50
60
  catch (error) {
61
+ if (error instanceof poller_owner_1.RetiredSnapshotError) {
62
+ logger.info('terminal-snapshot:retired', { worktreeId, cliToolId, instanceId });
63
+ return;
64
+ }
51
65
  logger.error('terminal-snapshot:failed', {
52
66
  worktreeId,
53
67
  error: error instanceof Error ? error.message : String(error),
@@ -52,6 +52,7 @@ exports.publishChatTurnProgress = publishChatTurnProgress;
52
52
  const types_1 = require("../../lib/realtime/types");
53
53
  const logger_1 = require("../../lib/logger");
54
54
  const global_state_1 = require("../global-state");
55
+ const poller_owner_1 = require("../../lib/polling/poller-owner");
55
56
  // Issue #3215: the name is the one these lines have always been logged under.
56
57
  // It is what an operator greps for, so it did not move with the code.
57
58
  const logger = (0, logger_1.createLogger)('current-output-builder');
@@ -169,6 +170,19 @@ function logChatTurnProgressOutcome(target, outcome, turnKey, extra = {}) {
169
170
  * @returns The frame to broadcast, or null when there is nothing new to say
170
171
  */
171
172
  async function buildChatTurnProgress(target, source, now = Date.now()) {
173
+ const prepared = await prepareChatTurnProgress(target, source, now);
174
+ return prepared === null ? null : commitChatTurnProgress(target, prepared);
175
+ }
176
+ /**
177
+ * The half of {@link buildChatTurnProgress} that awaits: the throttle, the read
178
+ * and the bound — and nothing remembered about the body (Issue #3705).
179
+ *
180
+ * Split so that {@link emitChatTurnProgress} can remember a body only once it is
181
+ * actually going out. Remembered at the build and refused at the broadcast (a
182
+ * poller snapshot superseded in the `await` between the two), the body stood
183
+ * as "already sent" and suppressed the successor's identical frame.
184
+ */
185
+ async function prepareChatTurnProgress(target, source, now) {
172
186
  const key = chatTurnProgressKey(target);
173
187
  const state = chatTurnProgressState.get(key) ?? newChatTurnProgressState();
174
188
  if (now - state.askedAt < types_1.CHAT_TURN_PROGRESS_MIN_INTERVAL_MS)
@@ -186,20 +200,43 @@ async function buildChatTurnProgress(target, source, now = Date.now()) {
186
200
  }
187
201
  if (!draft || draft.body.length === 0)
188
202
  return null;
203
+ // Issue #3705: the frame of a poller snapshot whose tick was superseded while
204
+ // the transcript was read is not built — and, above all, not remembered: the
205
+ // turn key and body are what suppress the successor's identical frame.
206
+ if (!(0, poller_owner_1.maySnapshotPublish)())
207
+ return null;
189
208
  const bounded = (0, types_1.truncateChatTurnProgressBody)(draft.body);
190
209
  if (draft.turnKey === state.turnKey && bounded.body === state.body)
191
210
  return null;
192
- state.turnKey = draft.turnKey;
193
- state.body = bounded.body;
211
+ return {
212
+ turnKey: draft.turnKey,
213
+ body: bounded.body,
214
+ partial: bounded.truncated || draft.partial === true,
215
+ };
216
+ }
217
+ /**
218
+ * Remember a prepared frame and give it its version — synchronous, so the
219
+ * caller can make it the last step before the broadcast (Issue #3705). The
220
+ * no-change rule is applied again against what is remembered NOW, since another
221
+ * frame may have gone out while the read was in flight.
222
+ */
223
+ function commitChatTurnProgress(target, prepared) {
224
+ const key = chatTurnProgressKey(target);
225
+ const state = chatTurnProgressState.get(key) ?? newChatTurnProgressState();
226
+ if (prepared.turnKey === state.turnKey && prepared.body === state.body)
227
+ return null;
228
+ state.turnKey = prepared.turnKey;
229
+ state.body = prepared.body;
194
230
  state.version += 1;
231
+ chatTurnProgressState.set(key, state);
195
232
  return {
196
233
  type: types_1.CHAT_TURN_PROGRESS_EVENT_TYPE,
197
234
  worktreeId: target.worktreeId,
198
235
  cliToolId: target.cliToolId,
199
236
  instanceId: target.instanceId ?? target.cliToolId,
200
- turnKey: draft.turnKey,
201
- body: bounded.body,
202
- partial: bounded.truncated || draft.partial === true,
237
+ turnKey: prepared.turnKey,
238
+ body: prepared.body,
239
+ partial: prepared.partial,
203
240
  version: state.version,
204
241
  done: false,
205
242
  };
@@ -232,9 +269,18 @@ async function emitChatTurnProgress(target, source, now = Date.now()) {
232
269
  logChatTurnProgressOutcome(target, 'no-subscribers', null);
233
270
  return false;
234
271
  }
235
- const event = await buildChatTurnProgress(target, source, now);
272
+ const prepared = await prepareChatTurnProgress(target, source, now);
236
273
  // Not logged: null is the throttle, an unchanged body, or a turn with no
237
274
  // text yet — the quiet, correct majority of ticks.
275
+ if (!prepared)
276
+ return false;
277
+ // Issue #3705: asked after the build's `await`, and BEFORE the body is
278
+ // remembered — a frame refused here leaves nothing behind that would make
279
+ // the successor's same body look already sent. From here to the broadcast
280
+ // there is no `await`.
281
+ if (!(0, poller_owner_1.maySnapshotPublish)())
282
+ return false;
283
+ const event = commitChatTurnProgress(target, prepared);
238
284
  if (!event)
239
285
  return false;
240
286
  broadcast(target.worktreeId, event);
@@ -302,6 +348,11 @@ async function publishChatTurnProgress(db, worktreeId, cliToolId, instanceId) {
302
348
  // sessions nobody is watching, which is the bargain that was already being
303
349
  // struck one frame later.
304
350
  const { hasRoomSubscribers } = await Promise.resolve().then(() => __importStar(require('../../lib/ws-server')));
351
+ // Issue #3705: the progress of a superseded tick's snapshot is not started.
352
+ // The detached read below keeps the snapshot's owner in its async context
353
+ // and asks again before it remembers or broadcasts anything.
354
+ if (!(0, poller_owner_1.maySnapshotPublish)())
355
+ return;
305
356
  if (!hasRoomSubscribers(worktreeId)) {
306
357
  logChatTurnProgressOutcome(target, 'no-subscribers', null);
307
358
  return;
@@ -48,6 +48,7 @@ const auto_yes_dialog_gate_1 = require("../../lib/polling/auto-yes-dialog-gate")
48
48
  const layer_disagreement_1 = require("../../lib/session/layer-disagreement");
49
49
  const structured_prompt_1 = require("../../lib/session/structured-prompt");
50
50
  const chat_turn_progress_1 = require("./chat-turn-progress");
51
+ const poller_owner_1 = require("../../lib/polling/poller-owner");
51
52
  const structured_status_merge_1 = require("./structured-status-merge");
52
53
  const current_output_history_writers_1 = require("./current-output-history-writers");
53
54
  const detection_divergence_1 = require("./detection-divergence");
@@ -74,6 +75,11 @@ const logger = (0, logger_1.createLogger)('current-output-builder');
74
75
  */
75
76
  async function buildCurrentOutput(db, worktreeId, cliToolId, instanceId, resolution) {
76
77
  const payload = await buildPayload(db, worktreeId, cliToolId, instanceId);
78
+ // Issue #3705: the payload's own writers asked after their awaits (see
79
+ // `buildPayload`); the progress frame below is one more write, of the turn
80
+ // the payload describes.
81
+ if (!(0, poller_owner_1.maySnapshotPublish)())
82
+ throw new poller_owner_1.RetiredSnapshotError();
77
83
  // Issue #2199. Fire-and-forget, and placed HERE rather than in the poller for
78
84
  // one reason: this is the function both live paths run through. The WebSocket
79
85
  // push calls it from `broadcastTerminalSnapshot` on every generating poller
@@ -565,6 +571,15 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
565
571
  const sessionName = cliTool.getSessionName(worktreeId, instanceId);
566
572
  const { stopEventAt, eventSource, askUserQuestion, structuredEvents } = readAgentEvents(worktreeId, cliToolId, instanceId, resolvedInstanceId);
567
573
  const running = await cliTool.isRunning(worktreeId, instanceId);
574
+ // Issue #3705: a terminal snapshot the response poller started for a tick
575
+ // that has since been superseded writes nothing from here on — not the
576
+ // latch, the starting state, the unclassified-frame row, the structured
577
+ // prompt row or the status evidence — because they would describe the turn
578
+ // that ended on top of the successor's. Thrown, not answered, so the payload
579
+ // type stays what the route reads; `broadcastTerminalSnapshot` catches it.
580
+ // Never thrown outside a poller snapshot (`maySnapshotPublish`).
581
+ if (!(0, poller_owner_1.maySnapshotPublish)())
582
+ throw new poller_owner_1.RetiredSnapshotError();
568
583
  if (!running) {
569
584
  // Issue #1926: the latch describes a process, and this one is gone. Dropped
570
585
  // rather than aged out so the next session on this key starts with no
@@ -632,6 +647,10 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
632
647
  const sessionState = (0, db_1.getSessionState)(db, worktreeId, resolvedInstanceId);
633
648
  const lastCapturedLine = sessionState?.lastCapturedLine || 0;
634
649
  const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, status_capture_config_1.STATUS_CAPTURE_LINES, instanceId);
650
+ // Issue #3705: the last `await` of the build. Everything below it, through
651
+ // `observeStatusEvidence`, runs without one.
652
+ if (!(0, poller_owner_1.maySnapshotPublish)())
653
+ throw new poller_owner_1.RetiredSnapshotError();
635
654
  const lines = output.split('\n');
636
655
  const totalLines = lines.length;
637
656
  // Issue #1670: `content` is "everything the poller has not saved yet", which