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
@@ -88,6 +88,7 @@ const logger = (0, logger_1.createLogger)('auto-yes-poller');
88
88
  const path_validator_1 = require("./security/path-validator");
89
89
  const auto_yes_state_1 = require("./auto-yes-state");
90
90
  const global_state_1 = require("./global-state");
91
+ const auto_yes_suppression_reason_1 = require("./polling/auto-yes-suppression-reason");
91
92
  const auto_yes_poller_state_1 = require("./polling/auto-yes-poller-state");
92
93
  const auto_yes_prompt_suppression_1 = require("./polling/auto-yes-prompt-suppression");
93
94
  /** In-memory storage for poller states (globalThis for hot reload persistence) */
@@ -158,8 +159,8 @@ function isPollerActive(compositeKey) {
158
159
  * @param compositeKey - Composite key
159
160
  * @param timestamp - Timestamp value (Date.now())
160
161
  */
161
- function updateLastServerResponseTimestamp(compositeKey, timestamp) {
162
- const pollerState = getPollerState(compositeKey);
162
+ function updateLastServerResponseTimestamp(compositeKey, timestamp, owner) {
163
+ const pollerState = ownedPollerState(compositeKey, owner);
163
164
  if (pollerState) {
164
165
  pollerState.lastServerResponseTimestamp = timestamp;
165
166
  }
@@ -170,9 +171,10 @@ function updateLastServerResponseTimestamp(compositeKey, timestamp) {
170
171
  * and after an answer is sent.
171
172
  *
172
173
  * @param compositeKey - Composite key
174
+ * @param owner - The tick's own poller state (Issue #3706); see {@link ownedPollerState}
173
175
  */
174
- function resetErrorCount(compositeKey) {
175
- const pollerState = getPollerState(compositeKey);
176
+ function resetErrorCount(compositeKey, owner) {
177
+ const pollerState = ownedPollerState(compositeKey, owner);
176
178
  if (pollerState) {
177
179
  pollerState.consecutiveErrors = 0;
178
180
  pollerState.currentInterval = auto_yes_state_1.POLLING_INTERVAL_MS;
@@ -183,9 +185,10 @@ function resetErrorCount(compositeKey) {
183
185
  * [IA-MF-001] compositeKey-based: extracts worktreeId/cliToolId for disableAutoYes.
184
186
  *
185
187
  * @param compositeKey - Composite key
188
+ * @param owner - The tick's own poller state (Issue #3706); see {@link ownedPollerState}
186
189
  */
187
- function incrementErrorCount(compositeKey) {
188
- const pollerState = getPollerState(compositeKey);
190
+ function incrementErrorCount(compositeKey, owner) {
191
+ const pollerState = ownedPollerState(compositeKey, owner);
189
192
  if (pollerState) {
190
193
  pollerState.consecutiveErrors++;
191
194
  pollerState.currentInterval = (0, auto_yes_state_1.calculateBackoffInterval)(pollerState.consecutiveErrors);
@@ -209,6 +212,62 @@ function incrementErrorCount(compositeKey) {
209
212
  }
210
213
  }
211
214
  }
215
+ /**
216
+ * The poller state under `compositeKey`, but only when it is `owner` — the
217
+ * tick's own (Issue #3706). Without `owner` (callers outside a tick, and tests
218
+ * driving `detectAndRespondToPrompt` with a hand-built state) it is the plain
219
+ * by-key read this always was.
220
+ */
221
+ function ownedPollerState(compositeKey, owner) {
222
+ const pollerState = getPollerState(compositeKey);
223
+ if (owner !== undefined && pollerState !== owner)
224
+ return undefined;
225
+ return pollerState;
226
+ }
227
+ /** Is the tick's poller still the one under its key (not stopped, not replaced)? */
228
+ function isPollerCurrent(generation) {
229
+ return getPollerState(generation.compositeKey) === generation.pollerState;
230
+ }
231
+ /**
232
+ * May the tick still act: its poller, its grant (the same enable, not expired)
233
+ * and its session (no relaunch since) are all still the ones it started under.
234
+ * Checked after capture, after each wait before a key, and inside the send.
235
+ */
236
+ function isTickCurrent(generation) {
237
+ if (!isPollerCurrent(generation))
238
+ return false;
239
+ // `getAutoYesState` returns the stored object until it expires, when it
240
+ // stores (and returns) a disabled one — so an expiry fails identity too.
241
+ const grant = (0, auto_yes_state_1.getAutoYesState)(generation.worktreeId, generation.cliToolId, generation.instanceId);
242
+ if (grant !== generation.grant || !grant.enabled)
243
+ return false;
244
+ return isSameSession(generation);
245
+ }
246
+ /**
247
+ * Is the session the tick answered still the one it started on (Issue #3706)?
248
+ * What the follow-ups of an answer already sent — the response poller restart,
249
+ * the terminal push — are judged by: a disable after the keys landed does not
250
+ * un-send them, a relaunch does make them about another process.
251
+ */
252
+ function isSameSession(generation) {
253
+ return (0, auto_yes_enter_fallback_1.getEnterFallbackSessionEpoch)(generation.compositeKey) === generation.sessionEpoch;
254
+ }
255
+ /**
256
+ * End a tick that found its generation gone (Issue #3706). It changes nothing;
257
+ * it re-arms its timer only when its own poller is still the current one — a
258
+ * renewed grant or a relaunched session under the same poller — so the chain
259
+ * goes on and the next tick starts from the new state.
260
+ */
261
+ function endStaleTick(generation) {
262
+ logger.debug('poller:auto-yes-tick-superseded', {
263
+ worktreeId: generation.worktreeId,
264
+ cliToolId: generation.cliToolId,
265
+ instanceId: generation.instanceId,
266
+ pollerCurrent: isPollerCurrent(generation),
267
+ });
268
+ if (isPollerCurrent(generation))
269
+ scheduleNextPoll(generation);
270
+ }
212
271
  /**
213
272
  * Check if the given prompt has already been answered recently.
214
273
  *
@@ -409,9 +468,13 @@ function isStillEnterFallbackScreen(prompt, fresh) {
409
468
  * 6. a capture taken now, past the capture cache, must still be that screen
410
469
  * ({@link isStillEnterFallbackScreen}).
411
470
  *
471
+ * Issue #3706: with a `generation` (a tick of `pollAutoYes`), the waits of
472
+ * steps 5 and 6 are each followed by {@link isTickCurrent}; a tick that lost
473
+ * its generation sends nothing and changes nothing.
474
+ *
412
475
  * @returns true when the Enter was sent; the caller returns `responded`
413
476
  */
414
- async function tryEnterFallback(prompt, promptDetection, dialogGate, rawOutput) {
477
+ async function tryEnterFallback(prompt, promptDetection, dialogGate, rawOutput, generation) {
415
478
  const { worktreeId, cliToolId, instanceId, pollerState, promptData, frameKey } = prompt;
416
479
  const judgement = dialogGate.dialog === null ? (0, auto_yes_enter_fallback_1.judgeEnterFallback)(cliToolId, rawOutput) : null;
417
480
  if (judgement === null || !judgement.eligible) {
@@ -481,6 +544,8 @@ async function tryEnterFallback(prompt, promptDetection, dialogGate, rawOutput)
481
544
  // 5. Never another server's session (Issue #2865).
482
545
  const sessionName = manager_1.CLIToolManager.getInstance().getTool(cliToolId).getSessionName(worktreeId, instanceId);
483
546
  const ownership = await (0, worktree_session_ownership_1.checkWorktreeSessionOwnership)(worktreeId, sessionName);
547
+ if (generation !== undefined && !isTickCurrent(generation))
548
+ return false;
484
549
  if (ownership === null || ownership.verdict === 'foreign') {
485
550
  (0, auto_yes_poller_state_1.warnOncePerFrame)(pollerState, `${frameKey}\u0000${sessionName}\u0000${ownership ? 'foreign' : 'worktree_not_found'}`, 'poller:auto-yes-skipped-foreign-session', {
486
551
  worktreeId,
@@ -500,6 +565,8 @@ async function tryEnterFallback(prompt, promptDetection, dialogGate, rawOutput)
500
565
  // has the box back. Everything that decided the Enter is judged again on the
501
566
  // fresh frame; any difference sends nothing, and the next tick starts over.
502
567
  const fresh = await (0, cli_session_1.captureSessionOutputFresh)(worktreeId, cliToolId, (rawOutput ?? '').split('\n').length, instanceId);
568
+ if (generation !== undefined && !isTickCurrent(generation))
569
+ return false;
503
570
  if (!isStillEnterFallbackScreen(prompt, fresh)) {
504
571
  pollerState.enterFallbackCandidateKey = null;
505
572
  logger.debug('poller:auto-yes-enter-fallback-fresh-frame-differs', {
@@ -516,13 +583,20 @@ async function tryEnterFallback(prompt, promptDetection, dialogGate, rawOutput)
516
583
  finally {
517
584
  (0, tmux_capture_cache_1.invalidateCache)(sessionName);
518
585
  }
519
- pollerState.enterFallbackSentKey = frameKey;
520
- pollerState.enterFallbackCandidateKey = null;
521
- (0, auto_yes_enter_fallback_1.recordEnterFallbackSent)(worktreeId, cliToolId, instanceId, {
522
- promptType: promptData.type,
523
- refusalReason: judgement.refusalReason,
524
- screenKey,
525
- });
586
+ // Issue #3706: the Enter is out, but the per-screen record is the CURRENT
587
+ // session's: when the poller was stopped (the record was forgotten with it)
588
+ // or a new session began while `send-keys` was awaited, the return of this
589
+ // send must not put the record back. The audit row below is kept either way.
590
+ if (generation === undefined || (isPollerCurrent(generation) && isSameSession(generation))) {
591
+ pollerState.enterFallbackSentKey = frameKey;
592
+ pollerState.enterFallbackCandidateKey = null;
593
+ (0, auto_yes_enter_fallback_1.recordEnterFallbackSent)(worktreeId, cliToolId, instanceId, {
594
+ promptType: promptData.type,
595
+ refusalReason: judgement.refusalReason,
596
+ screenKey,
597
+ ...(generation !== undefined ? { epoch: generation.sessionEpoch } : {}),
598
+ });
599
+ }
526
600
  logger.info('poller:auto-yes-enter-fallback-sent', {
527
601
  worktreeId,
528
602
  cliToolId,
@@ -531,7 +605,7 @@ async function tryEnterFallback(prompt, promptDetection, dialogGate, rawOutput)
531
605
  refusalReason: judgement.refusalReason,
532
606
  composerState: judgement.composerState,
533
607
  });
534
- await finishAnsweredPrompt(prompt, promptDetection, exports.AUTO_YES_ENTER_FALLBACK_ANSWER);
608
+ await finishAnsweredPrompt(prompt, promptDetection, exports.AUTO_YES_ENTER_FALLBACK_ANSWER, generation);
535
609
  return true;
536
610
  }
537
611
  /**
@@ -543,24 +617,42 @@ async function tryEnterFallback(prompt, promptDetection, dialogGate, rawOutput)
543
617
  * @param promptDetection - The detection `prompt.promptData` was read from; its
544
618
  * content becomes the audit row's
545
619
  * @param answer - The answer that was sent
620
+ * @param generation - The tick's generation (Issue #3706). The keys are out, so
621
+ * the task event and the audit row are always written: they record what the
622
+ * agent actually received. What belongs to the poller is written to the
623
+ * tick's own poller state only, never to a successor under the same key, and
624
+ * the response poller restart and the terminal push are skipped when the
625
+ * session was relaunched meanwhile.
546
626
  */
547
- async function finishAnsweredPrompt(prompt, promptDetection, answer) {
627
+ async function finishAnsweredPrompt(prompt, promptDetection, answer, generation) {
548
628
  const { worktreeId, cliToolId, instanceId, compositeKey, pollerState, promptData, promptKey } = prompt;
549
- // 6. Update timestamp and reset error count
550
- updateLastServerResponseTimestamp(compositeKey, Date.now());
551
- resetErrorCount(compositeKey);
552
- // 7. Record answered prompt key and timestamp
553
- pollerState.lastAnsweredPromptKey = promptKey;
554
- pollerState.lastAnsweredAt = Date.now();
555
- pollerState.lastSkipWarnKey = null;
556
- logger.info('poller:response-sent', { worktreeId, cliToolId, instanceId });
629
+ // Issue #3706: the keys went to the session the tick started on. When a new
630
+ // one was begun while they were being sent, the new process never received
631
+ // this answer, so nothing that stands for "this instance's prompt was just
632
+ // answered" is written: not the dedup key (it would keep Auto-Yes off the new
633
+ // process's identical question), not the poller's response time, not the
634
+ // task event (the task looked up now may be the new session's). Only the
635
+ // audit row is, and it may not adopt a pending row saved after the tick began.
636
+ const sameSession = generation === undefined || isSameSession(generation);
637
+ if (sameSession) {
638
+ // 6. Update timestamp and reset error count
639
+ updateLastServerResponseTimestamp(compositeKey, Date.now(), generation?.pollerState);
640
+ resetErrorCount(compositeKey, generation?.pollerState);
641
+ // 7. Record answered prompt key and timestamp
642
+ pollerState.lastAnsweredPromptKey = promptKey;
643
+ pollerState.lastAnsweredAt = Date.now();
644
+ pollerState.lastSkipWarnKey = null;
645
+ }
646
+ logger.info('poller:response-sent', { worktreeId, cliToolId, instanceId, sameSession });
557
647
  // Issue #1548: the prompt was answered without a human. Raised only after
558
648
  // the keys actually reached tmux, so a send that threw is not logged as an
559
649
  // answer. No-ops when this instance is not running a contract.
560
- (0, task_transition_service_1.applyEventToActiveTask)((0, db_instance_1.getDbInstance)(), worktreeId, cliToolId,
561
- // Undefined means the primary instance, which the task lookup identifies
562
- // by the tool id itself (see getActiveTaskForInstance).
563
- instanceId ?? cliToolId, 'prompt_answered_auto', { promptType: promptData.type });
650
+ if (sameSession) {
651
+ (0, task_transition_service_1.applyEventToActiveTask)((0, db_instance_1.getDbInstance)(), worktreeId, cliToolId,
652
+ // Undefined means the primary instance, which the task lookup identifies
653
+ // by the tool id itself (see getActiveTaskForInstance).
654
+ instanceId ?? cliToolId, 'prompt_answered_auto', { promptType: promptData.type });
655
+ }
564
656
  // Issue #1685: persist question/options/answer to chat history so the audit
565
657
  // trail survives even when the answer landed inside the response poller's
566
658
  // interval and the prompt was never saved as a pending message. Must never
@@ -575,6 +667,7 @@ async function finishAnsweredPrompt(prompt, promptDetection, answer) {
575
667
  answer,
576
668
  answeredBy: 'auto',
577
669
  content: promptDetection.rawContent || promptDetection.cleanContent,
670
+ ...(sameSession ? {} : { pendingSavedAtOrBefore: generation.startedAt }),
578
671
  });
579
672
  }
580
673
  catch (recordError) {
@@ -605,6 +698,10 @@ async function finishAnsweredPrompt(prompt, promptDetection, answer) {
605
698
  Promise.resolve().then(() => __importStar(require('../lib/polling/response-poller'))),
606
699
  Promise.resolve().then(() => __importStar(require('../lib/realtime/terminal-broadcast'))),
607
700
  ]);
701
+ // Re-read after the await, not the `sameSession` taken before the audit: a
702
+ // relaunch while the modules loaded makes the restart about the new process.
703
+ if (!sameSession || (generation !== undefined && !isSameSession(generation)))
704
+ return;
608
705
  startResponsePolling(worktreeId, cliToolId, instanceId);
609
706
  void broadcastTerminalSnapshotAfterInteraction(worktreeId, cliToolId, instanceId);
610
707
  }
@@ -621,10 +718,18 @@ async function finishAnsweredPrompt(prompt, promptDetection, answer) {
621
718
  * @param rawOutput - The SAME tick's capture with its box drawing intact
622
719
  * (Issue #2522). Omitting it costs Command Code's `AskUserQuestion` and
623
720
  * nothing else; see {@link capturePollerFrame}.
721
+ * @param generation - The calling tick's generation (Issue #3706). Given, every
722
+ * wait before a key — the ownership check, the Enter fallback's fresh capture,
723
+ * each wait inside the send — is followed by {@link isTickCurrent}, and a tick
724
+ * that lost it types nothing more and returns `no_answer`. Omitted (direct
725
+ * callers, tests), nothing is checked, as before.
624
726
  * @returns 'responded' | 'no_prompt' | 'duplicate' | 'no_answer' | 'error'
625
727
  */
626
- async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, cleanOutput, precomputedLines, instanceId, rawOutput) {
728
+ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, cleanOutput, precomputedLines, instanceId, rawOutput, generation) {
627
729
  const compositeKey = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
730
+ // Issue #3706 / #3713: the send in flight, settled without an await once it
731
+ // comes back — as a partial answer when it was stopped with keys on the pane.
732
+ let attempt = null;
628
733
  try {
629
734
  // 1. Detect prompt.
630
735
  //
@@ -683,6 +788,7 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
683
788
  const frame = (0, frame_1.normalizeFrame)(rawOutput ?? cleanOutput, cliToolId);
684
789
  const promptDetection = (0, response_checker_1.detectPromptOnCleanFrame)(cleanOutput, cliToolId, precomputedLines, rawOutput, receiptScope, frame);
685
790
  if (!promptDetection.isPrompt || !promptDetection.promptData) {
791
+ (0, auto_yes_poller_state_1.forgetPartialAnswer)(compositeKey);
686
792
  pollerState.lastAnsweredPromptKey = null;
687
793
  pollerState.lastAnsweredAt = null;
688
794
  pollerState.lastSkipWarnKey = null;
@@ -708,6 +814,37 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
708
814
  promptKey,
709
815
  frameKey,
710
816
  };
817
+ // 2.5. Issue #3706: an earlier answer to this prompt was stopped after some
818
+ // of its keys reached the pane (a `y` whose Enter was withheld). Typing the
819
+ // answer again would append to what is already in the input (`yy`), and
820
+ // what the pane holds now cannot be told from the frame, so the prompt is
821
+ // left for a human until it leaves the screen, another prompt replaces it
822
+ // (`partialAnswerScreenKey`: options and approvalTarget count, the cursor
823
+ // does not), or the session is relaunched. Recorded as a suppression, so
824
+ // the prompt Push and `cmate wait` see "withheld", not "answering".
825
+ const screenKey = (0, auto_yes_poller_state_1.partialAnswerScreenKey)(promptDetection.promptData);
826
+ const partial = (0, auto_yes_poller_state_1.getPartialAnswer)(compositeKey, epoch);
827
+ if (partial !== null) {
828
+ if (partial.screenKey === screenKey) {
829
+ (0, auto_yes_prompt_suppression_1.suppressAndWarnOnce)(judged, {
830
+ reason: auto_yes_suppression_reason_1.PARTIAL_ANSWER_SUPPRESSION_REASON,
831
+ mode: null,
832
+ promptType: promptDetection.promptData.type,
833
+ }, `${frameKey}\u0000partial-answer`, 'poller:auto-yes-skipped-partial-answer', { promptType: promptDetection.promptData.type, stoppedAt: partial.at });
834
+ return 'no_answer';
835
+ }
836
+ // Another prompt is on screen: only the sends at the forgotten one's
837
+ // screen go with it (#3713) — one in flight at this screen keeps its hold.
838
+ (0, auto_yes_poller_state_1.forgetPartialAnswer)(compositeKey, partial.screenKey);
839
+ }
840
+ // Issue #3713: a worktree ID move carried an answer still being sent to
841
+ // this session's key. Its keys may already be on the pane, and how its send
842
+ // ends is not known yet — so this screen gets no keys until it comes back
843
+ // (recorded as a partial answer above, or gone).
844
+ if ((0, auto_yes_poller_state_1.hasMovedSendInFlight)(compositeKey, epoch, screenKey)) {
845
+ (0, auto_yes_poller_state_1.warnOncePerFrame)(pollerState, `${frameKey}\u0000moved-send-in-flight`, 'poller:auto-yes-skipped-moved-send-in-flight', { worktreeId, cliToolId, instanceId, promptType: promptDetection.promptData.type });
846
+ return 'no_answer';
847
+ }
711
848
  // 3., 3.2., 3.5. Frames that are not Auto-Yes's to answer: see
712
849
  // `suppressIfNotOursToAnswer`.
713
850
  const notOurs = (0, auto_yes_prompt_suppression_1.suppressIfNotOursToAnswer)(judged, frame);
@@ -720,7 +857,7 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
720
857
  // 3.6. Issue #3397: a refused frame may be a choice screen CommandMate
721
858
  // cannot read; with Auto-Yes on it gets one Enter. See `tryEnterFallback`.
722
859
  if (notOurs.kind === 'dialog-gate-refused') {
723
- return (await tryEnterFallback(judged, promptDetection, notOurs.dialogGate, rawOutput))
860
+ return (await tryEnterFallback(judged, promptDetection, notOurs.dialogGate, rawOutput, generation))
724
861
  ? 'responded'
725
862
  : 'no_answer';
726
863
  }
@@ -759,6 +896,8 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
759
896
  // Issue #2865: never auto-answer a same-named session another CommandMate
760
897
  // server created. The poller keeps running; it just does not type.
761
898
  const ownership = await (0, worktree_session_ownership_1.checkWorktreeSessionOwnership)(worktreeId, sessionName);
899
+ if (generation !== undefined && !isTickCurrent(generation))
900
+ return 'no_answer';
762
901
  if (ownership === null || ownership.verdict === 'foreign') {
763
902
  (0, auto_yes_poller_state_1.warnOncePerFrame)(pollerState, `${frameKey}\u0000${sessionName}\u0000${ownership ? 'foreign' : 'worktree_not_found'}`, 'poller:auto-yes-skipped-foreign-session', {
764
903
  worktreeId,
@@ -770,23 +909,42 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
770
909
  });
771
910
  return 'no_answer';
772
911
  }
912
+ attempt = (0, auto_yes_poller_state_1.beginSendAttempt)(compositeKey, screenKey, generation?.sessionEpoch ?? epoch);
773
913
  try {
774
914
  await (0, prompt_answer_sender_1.sendPromptAnswer)({
775
915
  sessionName,
776
916
  answer,
777
917
  cliToolId,
778
918
  promptData: promptDetection.promptData,
919
+ // Issue #3706: asked after each wait inside the send.
920
+ ...(generation !== undefined ? { stillCurrent: () => isTickCurrent(generation) } : {}),
779
921
  });
780
922
  }
781
923
  finally {
782
924
  (0, tmux_capture_cache_1.invalidateCache)(sessionName);
783
925
  }
926
+ (0, auto_yes_poller_state_1.settleSendAttempt)(attempt, false);
784
927
  // 6., 7. The answer reached tmux: see `finishAnsweredPrompt`.
785
- await finishAnsweredPrompt(judged, promptDetection, answer);
928
+ await finishAnsweredPrompt(judged, promptDetection, answer, generation);
786
929
  return 'responded';
787
930
  }
788
- catch {
789
- incrementErrorCount(compositeKey);
931
+ catch (error) {
932
+ // Issue #3706: a tick that lost its generation — the send stopped part-way
933
+ // (`PromptAnswerAbortedError`), or failed after the grant went — is not a
934
+ // failure of the poller that now holds the key, and not an answer either.
935
+ if (generation !== undefined && !isTickCurrent(generation)) {
936
+ const keysSent = error instanceof Error && 'keysSent' in error ? Boolean(error.keysSent) : null;
937
+ // A stop with keys already on the pane: the next tick must not type the
938
+ // answer again over them (step 2.5). Recorded under the attempt's key as
939
+ // it is now — the new one when a worktree ID move carried it (#3713).
940
+ if (attempt !== null)
941
+ (0, auto_yes_poller_state_1.settleSendAttempt)(attempt, keysSent === true);
942
+ logger.info('poller:auto-yes-answer-abandoned', { worktreeId, cliToolId, instanceId, keysSent });
943
+ return 'no_answer';
944
+ }
945
+ if (attempt !== null)
946
+ (0, auto_yes_poller_state_1.settleSendAttempt)(attempt, false);
947
+ incrementErrorCount(compositeKey, generation?.pollerState);
790
948
  logger.warn('poller:detect-respond-error', { worktreeId, cliToolId, instanceId });
791
949
  return 'error';
792
950
  }
@@ -797,19 +955,36 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
797
955
  * @param worktreeId - Worktree identifier
798
956
  * @param cliToolId - CLI tool type being polled
799
957
  * @param instanceId - Agent instance ID being polled (Issue #896; equals cliToolId for primary)
958
+ * @param owner - The poller state whose timer fired (Issue #3706); a timer of a
959
+ * poller that has since been replaced does nothing
800
960
  */
801
- async function pollAutoYes(worktreeId, cliToolId, instanceId) {
961
+ async function pollAutoYes(worktreeId, cliToolId, instanceId, owner) {
802
962
  const compositeKey = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
803
963
  // 1. Validate context
804
964
  const pollerState = getPollerState(compositeKey);
965
+ if (owner !== undefined && pollerState !== owner)
966
+ return;
805
967
  const contextResult = validatePollingContext(compositeKey, pollerState);
806
968
  if (contextResult !== 'valid') {
807
969
  return;
808
970
  }
971
+ const grant = (0, auto_yes_state_1.getAutoYesState)(worktreeId, cliToolId, instanceId);
972
+ if (!grant?.enabled)
973
+ return;
974
+ // Issue #3706: what this tick acts for, compared after every wait below.
975
+ const generation = {
976
+ compositeKey,
977
+ worktreeId,
978
+ cliToolId,
979
+ instanceId,
980
+ pollerState: pollerState,
981
+ grant,
982
+ sessionEpoch: (0, auto_yes_enter_fallback_1.getEnterFallbackSessionEpoch)(compositeKey),
983
+ startedAt: Date.now(),
984
+ };
809
985
  try {
810
986
  // 2. Capture and clean output
811
- const autoYesState = (0, auto_yes_state_1.getAutoYesState)(worktreeId, cliToolId, instanceId);
812
- const captureLines = autoYesState?.stopPattern
987
+ const captureLines = grant.stopPattern
813
988
  ? auto_yes_config_1.FULL_CAPTURE_LINES
814
989
  : auto_yes_config_1.REDUCED_CAPTURE_LINES;
815
990
  // Issue #2522: one capture, both spellings. The cleaned string drives every
@@ -817,6 +992,10 @@ async function pollAutoYes(worktreeId, cliToolId, instanceId) {
817
992
  // so a dialog anchored on box drawing is not invisible to the one path that
818
993
  // can answer it without a human.
819
994
  const { raw: rawOutput, clean: cleanOutput } = await capturePollerFrame(worktreeId, cliToolId, captureLines, instanceId);
995
+ if (!isTickCurrent(generation)) {
996
+ endStaleTick(generation);
997
+ return;
998
+ }
820
999
  if (pollerState.waitingForSession) {
821
1000
  pollerState.waitingForSession = false;
822
1001
  logger.info('poller:session-appeared', { worktreeId, cliToolId, instanceId });
@@ -827,23 +1006,27 @@ async function pollAutoYes(worktreeId, cliToolId, instanceId) {
827
1006
  return;
828
1007
  }
829
1008
  // 4. Detect and respond to prompt
830
- const result = await detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, cleanOutput, lines, instanceId, rawOutput);
1009
+ const result = await detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, cleanOutput, lines, instanceId, rawOutput, generation);
1010
+ if (!isTickCurrent(generation)) {
1011
+ endStaleTick(generation);
1012
+ return;
1013
+ }
831
1014
  // Issue #3329: a poll that ran to the end ends the run of errors, so only
832
1015
  // failures in a row reach AUTO_STOP_ERROR_THRESHOLD — not ones scattered
833
1016
  // over hours. Not on 'error': a capture that works followed by an answer
834
1017
  // that fails every time must still back off and stop.
835
1018
  if (result !== 'error') {
836
- resetErrorCount(compositeKey);
1019
+ resetErrorCount(compositeKey, generation.pollerState);
837
1020
  }
838
1021
  if (result === 'responded') {
839
- scheduleNextPoll(worktreeId, cliToolId, instanceId, auto_yes_state_1.COOLDOWN_INTERVAL_MS);
1022
+ scheduleNextPoll(generation, auto_yes_state_1.COOLDOWN_INTERVAL_MS);
840
1023
  return;
841
1024
  }
842
1025
  // 5. Thinking check
843
1026
  if (result === 'no_prompt') {
844
1027
  const recentLines = lines.slice(-auto_yes_state_1.THINKING_CHECK_LINE_COUNT).join('\n');
845
1028
  if ((0, cli_patterns_1.detectThinking)(cliToolId, recentLines)) {
846
- scheduleNextPoll(worktreeId, cliToolId, instanceId, auto_yes_config_1.THINKING_POLLING_INTERVAL_MS);
1029
+ scheduleNextPoll(generation, auto_yes_config_1.THINKING_POLLING_INTERVAL_MS);
847
1030
  return;
848
1031
  }
849
1032
  }
@@ -853,18 +1036,23 @@ async function pollAutoYes(worktreeId, cliToolId, instanceId) {
853
1036
  // the session it answers for (`auto-yes-lifecycle`), and `send --auto-yes`
854
1037
  // enables it before the session starts — so wait until `expiresAt`, at the
855
1038
  // backoff cap, instead of counting toward `consecutive_errors`.
856
- if (await isSessionMissing(worktreeId, cliToolId, instanceId)) {
1039
+ const sessionMissing = await isSessionMissing(worktreeId, cliToolId, instanceId);
1040
+ if (!isTickCurrent(generation)) {
1041
+ endStaleTick(generation);
1042
+ return;
1043
+ }
1044
+ if (sessionMissing) {
857
1045
  if (!pollerState.waitingForSession) {
858
1046
  pollerState.waitingForSession = true;
859
1047
  logger.info('poller:waiting-for-session', { worktreeId, cliToolId, instanceId });
860
1048
  }
861
- scheduleNextPoll(worktreeId, cliToolId, instanceId, auto_yes_state_1.MAX_BACKOFF_MS);
1049
+ scheduleNextPoll(generation, auto_yes_state_1.MAX_BACKOFF_MS);
862
1050
  return;
863
1051
  }
864
- incrementErrorCount(compositeKey);
1052
+ incrementErrorCount(compositeKey, generation.pollerState);
865
1053
  logger.warn('poller:poll-error', { worktreeId, cliToolId, instanceId, error: (0, errors_1.getErrorMessage)(error) });
866
1054
  }
867
- scheduleNextPoll(worktreeId, cliToolId, instanceId);
1055
+ scheduleNextPoll(generation);
868
1056
  }
869
1057
  /**
870
1058
  * Whether a failed capture failed because the session does not exist
@@ -883,19 +1071,20 @@ async function isSessionMissing(worktreeId, cliToolId, instanceId) {
883
1071
  /**
884
1072
  * Schedule the next polling iteration
885
1073
  *
886
- * @param worktreeId - Worktree identifier
887
- * @param cliToolId - CLI tool type being polled
888
- * @param instanceId - Agent instance ID being polled (Issue #896)
1074
+ * Issue #3706: on the tick's OWN poller state, and only while it is still the
1075
+ * one under its key. Re-reading by key let a tick that outlived a disable →
1076
+ * enable write its timer over the new poller's `timerId`, leaving two chains.
1077
+ *
1078
+ * @param generation - The tick's generation
889
1079
  * @param overrideInterval - Optional override for the next poll delay
890
1080
  */
891
- function scheduleNextPoll(worktreeId, cliToolId, instanceId, overrideInterval) {
892
- const compositeKey = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
893
- const pollerState = getPollerState(compositeKey);
894
- if (!pollerState)
1081
+ function scheduleNextPoll(generation, overrideInterval) {
1082
+ if (!isPollerCurrent(generation))
895
1083
  return;
1084
+ const { worktreeId, cliToolId, instanceId, pollerState } = generation;
896
1085
  const interval = Math.max(overrideInterval ?? pollerState.currentInterval, auto_yes_state_1.POLLING_INTERVAL_MS);
897
1086
  pollerState.timerId = setTimeout(() => {
898
- pollAutoYes(worktreeId, cliToolId, instanceId);
1087
+ void pollAutoYes(worktreeId, cliToolId, instanceId, pollerState);
899
1088
  }, interval);
900
1089
  }
901
1090
  // =============================================================================
@@ -958,7 +1147,7 @@ function startAutoYesPolling(worktreeId, cliToolId, instanceId) {
958
1147
  (0, auto_yes_policy_1.invalidateSessionAutoYesPolicy)(compositeKey);
959
1148
  // Start polling immediately
960
1149
  pollerState.timerId = setTimeout(() => {
961
- pollAutoYes(worktreeId, cliToolId, effectiveInstanceId);
1150
+ void pollAutoYes(worktreeId, cliToolId, effectiveInstanceId, pollerState);
962
1151
  }, auto_yes_state_1.POLLING_INTERVAL_MS);
963
1152
  logger.info('poller:started', { worktreeId, cliToolId, instanceId: effectiveInstanceId });
964
1153
  return { started: true };
@@ -998,6 +1187,7 @@ function stopAllAutoYesPolling() {
998
1187
  }
999
1188
  autoYesPollerStates.clear();
1000
1189
  (0, auto_yes_enter_fallback_1.clearEnterFallbacks)();
1190
+ (0, auto_yes_poller_state_1.clearPartialAnswers)();
1001
1191
  }
1002
1192
  /**
1003
1193
  * Get all composite keys that have active auto-yes poller entries.
@@ -1017,12 +1207,20 @@ function getAutoYesPollerCompositeKeys() {
1017
1207
  * [SF-001] Uses shared filterCompositeKeysByWorktree (DRY).
1018
1208
  *
1019
1209
  * @param worktreeId - Worktree identifier
1210
+ * @param options.keepPartialAnswers - Issue #3713 (re-instruction 1, P2-1): the
1211
+ * disable route's "every agent" passes it — a disable, like one agent's
1212
+ * `stopAutoYesPolling`, keeps the memory of an answer stopped part-way and
1213
+ * the sends still in flight, so a disable → enable does not type over a
1214
+ * partial input. Omitted (the worktree being deleted, `session-cleanup` via
1215
+ * `auto-yes-lifecycle`), both are dropped and a late send records nothing.
1020
1216
  */
1021
- function stopAutoYesPollingByWorktree(worktreeId) {
1217
+ function stopAutoYesPollingByWorktree(worktreeId, options) {
1022
1218
  const pollerKeys = (0, auto_yes_state_1.filterCompositeKeysByWorktree)(Array.from(autoYesPollerStates.keys()), worktreeId);
1023
1219
  pollerKeys.forEach(key => stopAutoYesPolling(key));
1024
1220
  // Issue #3397: and the records of instances whose poller had already stopped.
1025
1221
  (0, auto_yes_enter_fallback_1.forgetEnterFallbacksByWorktree)(worktreeId);
1222
+ if (!options?.keepPartialAnswers)
1223
+ (0, auto_yes_poller_state_1.forgetPartialAnswersByWorktree)(worktreeId);
1026
1224
  }
1027
1225
  /**
1028
1226
  * Check if any auto-yes poller is active for a given worktreeId.
@@ -8,18 +8,30 @@ exports.recordClaudeConversation = recordClaudeConversation;
8
8
  const db_1 = require("./db");
9
9
  const log_manager_1 = require("./log-manager");
10
10
  const logger_1 = require("./logger");
11
+ const poller_owner_1 = require("./polling/poller-owner");
11
12
  const logger = (0, logger_1.createLogger)('conversation-logger');
12
13
  /**
13
14
  * Persist the latest Claude response alongside the most recent user prompt.
14
15
  * Errors are swallowed so the calling API route can continue responding.
15
16
  */
16
- async function recordClaudeConversation(db, worktreeId, claudeResponse, cliToolId = 'claude') {
17
+ async function recordClaudeConversation(db, worktreeId, claudeResponse, cliToolId = 'claude', options = {}) {
18
+ const { pollerKey } = options;
19
+ const mayWrite = pollerKey === undefined ? undefined : () => (0, poller_owner_1.mayActForPollerKey)(pollerKey);
20
+ if (mayWrite && !mayWrite()) {
21
+ logger.info('conversation:log-refused', { worktreeId, cliToolId });
22
+ return;
23
+ }
17
24
  const lastUserMessage = (0, db_1.getLastUserMessage)(db, worktreeId);
18
25
  if (!lastUserMessage) {
19
26
  return;
20
27
  }
21
28
  try {
22
- await (0, log_manager_1.createLog)(worktreeId, lastUserMessage.content, claudeResponse, cliToolId);
29
+ const logPath = await (0, log_manager_1.createLog)(worktreeId, lastUserMessage.content, claudeResponse, cliToolId, {
30
+ mayWrite,
31
+ });
32
+ if (logPath === null) {
33
+ logger.info('conversation:log-refused', { worktreeId, cliToolId });
34
+ }
23
35
  }
24
36
  catch (error) {
25
37
  logger.error('conversation:log-failed', { error: error instanceof Error ? error.message : String(error) });
@@ -693,9 +693,10 @@ function recordAnsweredPrompt(db, params) {
693
693
  AND message_type = 'prompt'
694
694
  AND json_extract(prompt_data, '$.status') = 'pending'
695
695
  ${exports.ACTIVE_FILTER}
696
+ ${params.pendingSavedAtOrBefore !== undefined ? 'AND timestamp <= ?' : ''}
696
697
  ORDER BY timestamp DESC
697
698
  `)
698
- .all(worktreeId, resolvedInstanceId);
699
+ .all(worktreeId, resolvedInstanceId, ...(params.pendingSavedAtOrBefore !== undefined ? [params.pendingSavedAtOrBefore] : []));
699
700
  let target = null;
700
701
  for (const row of rows) {
701
702
  try {
@@ -101,6 +101,7 @@ exports.USER_TURN_ADOPTION_WINDOW_MS = void 0;
101
101
  exports.normalizeUserTurnContent = normalizeUserTurnContent;
102
102
  exports.recordUserTurn = recordUserTurn;
103
103
  const logger_1 = require("../../lib/logger");
104
+ const poller_owner_1 = require("../../lib/polling/poller-owner");
104
105
  const logger = (0, logger_1.createLogger)('lib/history/user-turn-recorder');
105
106
  /**
106
107
  * How far from the agent's own clock a `/send` row may sit and still be the
@@ -182,6 +183,19 @@ async function recordUserTurn(target, key, content, timestampMs, options = {}) {
182
183
  timestampMs: existing.timestamp.getTime(),
183
184
  };
184
185
  }
186
+ // Issue #3705: the last `await` is behind us, and from here the recorder
187
+ // writes — a key onto an existing `/send` row, or a new row — with none
188
+ // between. A retired tick does neither under the instance it lost: both are
189
+ // keyed by `request_id`, which stops the same row being written twice and
190
+ // says nothing about who may write it.
191
+ if (!(0, poller_owner_1.mayActForAgentInstance)(target)) {
192
+ logger.info('user-turn-record-refused', {
193
+ ...describe(target),
194
+ requestId: key,
195
+ generation: (0, poller_owner_1.currentPollerOwner)()?.generation,
196
+ });
197
+ return { outcome: 'refused', messageId: null, timestampMs: null };
198
+ }
185
199
  const at = timestampMs > 0 ? timestampMs : Date.now();
186
200
  const adopted = adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at, options);
187
201
  if (adopted)