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
@@ -49,6 +49,7 @@ const session_name_1 = require("../../lib/cli-tools/session-name");
49
49
  const geometry_delegation_1 = require("../../lib/tmux/geometry-delegation");
50
50
  const tmux_session_surface_1 = require("../../lib/session/tmux-session-surface");
51
51
  const response_poller_core_1 = require("./response-poller-core");
52
+ const poller_owner_1 = require("./poller-owner");
52
53
  const push_1 = require("../../lib/push");
53
54
  // Issue #1790: imported by deep path, not through `@/lib/push`. Suites that
54
55
  // replace the barrel to count notifications (e.g. the #1547 escalation test)
@@ -449,6 +450,48 @@ function markOrSettleStructuredHistoryRecheck(pollerKey, recorded) {
449
450
  (0, response_dedup_1.markStructuredHistoryRecheckPending)(pollerKey);
450
451
  }
451
452
  }
453
+ /**
454
+ * Whether this tick may still act for `pollerKey` after the `await` it has just
455
+ * resumed from, logging the refusal when it may not (Issue #3705).
456
+ *
457
+ * #3663, #3684 and #3701 asked {@link mayActForPollerKey} at the writes that had
458
+ * been reported, one at a time. A tick resumes from an `await` with no idea
459
+ * whether its chain was stopped, cut loose past `STALLED_TICK_TIMEOUT` or moved
460
+ * by a worktree-ID migration in the meantime, and every write after that
461
+ * `await` — the prompt sweep, the waiting edge, the completion push, the cursor,
462
+ * the fault notifier, the TUI accumulator, the recheck and skip-streak state —
463
+ * lands under a key it no longer owns, on whatever the successor has put there.
464
+ * So the question is asked after every `await` of the tick, named by the point
465
+ * it resumed from, and the writes below each point run only on a yes.
466
+ *
467
+ * Not asked where a self-stop hands back its verdict (`return await
468
+ * recordCompletedResponse(…)`): `runPollTick` settles the pause from that
469
+ * value, and a stop the tick raised itself must not lose what it recorded.
470
+ *
471
+ * @param ctx - What this tick is keyed by
472
+ * @param after - The `await` the tick has just resumed from, for the log line
473
+ */
474
+ function mayStillActAfter(ctx, after) {
475
+ if ((0, poller_owner_1.mayActForPollerKey)(ctx.pollerKey))
476
+ return true;
477
+ // One line per retired tick, at the first point that turned it away: a tick
478
+ // refused at the capture is asked again after the reader and the log.
479
+ const owner = (0, poller_owner_1.currentPollerOwner)();
480
+ if (owner === undefined || !refusalLogged.has(owner)) {
481
+ if (owner)
482
+ refusalLogged.add(owner);
483
+ logger.info('retired-tick-refused', {
484
+ worktreeId: ctx.worktreeId,
485
+ cliToolId: ctx.cliToolId,
486
+ instanceId: ctx.resolvedInstanceId,
487
+ generation: owner?.generation,
488
+ after,
489
+ });
490
+ }
491
+ return false;
492
+ }
493
+ /** The retired chains {@link mayStillActAfter} has already logged (Issue #3705). */
494
+ const refusalLogged = new WeakSet();
452
495
  /**
453
496
  * Read the frame this tick captured: is there a finished turn on it that the
454
497
  * cursor has not already counted?
@@ -469,17 +512,25 @@ function markOrSettleStructuredHistoryRecheck(pollerKey, recorded) {
469
512
  * @param ctx - What this tick is keyed by
470
513
  * @param output - The capture this tick made
471
514
  * @param lastCapturedLine - Its cursor, 0 when there is none
515
+ * @param owns - Whether the tick still owns its key (Issue #3705); false reads
516
+ * the frame and writes nothing
472
517
  * @returns What the tick goes on with, or `false` when the tick ends here
473
518
  */
474
- function extractCompletedTurn(ctx, output, lastCapturedLine) {
519
+ function extractCompletedTurn(ctx, output, lastCapturedLine, owns) {
475
520
  const { db, worktreeId, cliToolId, instanceId, resolvedInstanceId, pollerKey } = ctx;
476
521
  // Issue #2000: the frame is in hand, so this is the cheapest place to ask
477
522
  // whether the model API has stalled the session. Level in, edge out — see
478
523
  // the helper.
479
- observeUpstreamFaultForPush(worktreeId, cliToolId, instanceId, output);
524
+ //
525
+ // Issue #3705: every write in this function is the caller's `owns` — asked
526
+ // right after the capture, with no `await` since. A retired tick's frame is
527
+ // read, and none of it lands on the fault episode, the accumulator, the skip
528
+ // streaks or the pending prompts of the key it lost.
529
+ if (owns)
530
+ observeUpstreamFaultForPush(worktreeId, cliToolId, instanceId, output);
480
531
  // Layer 2: Accumulate TUI content for full-screen TUI tools, so a turn that
481
532
  // outgrows the alternate-screen pane keeps the head that has scrolled away.
482
- if (cliToolId === 'opencode' || cliToolId === 'copilot') {
533
+ if (owns && (cliToolId === 'opencode' || cliToolId === 'copilot')) {
483
534
  // Issue #1911: opencode is accumulated from the CURRENT TURN'S REGION
484
535
  // rather than the whole frame. Feeding the raw pane seeded the accumulator
485
536
  // with the previous turn's transcript, the echoed prompt and the bottom
@@ -501,7 +552,7 @@ function extractCompletedTurn(ctx, output, lastCapturedLine) {
501
552
  //
502
553
  // Issue #3552: the same frame ends a run of duplicate response ticks for
503
554
  // the same reason — an unfinished frame is not the saved reply.
504
- if (result) {
555
+ if (owns && result) {
505
556
  (0, prompt_dedup_1.resetDuplicatePromptSkipStreak)(pollerKey);
506
557
  (0, response_dedup_1.resetDuplicateResponseSkipStreak)(pollerKey);
507
558
  }
@@ -509,7 +560,7 @@ function extractCompletedTurn(ctx, output, lastCapturedLine) {
509
560
  const { thinkingPattern } = (0, cli_patterns_1.getCliToolPatterns)(cliToolId);
510
561
  const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
511
562
  const tailLines = cleanOutput.split('\n').slice(-thinking_constants_1.THINKING_TAIL_LINE_COUNT).join('\n');
512
- if (thinkingPattern.test(tailLines)) {
563
+ if (owns && thinkingPattern.test(tailLines)) {
513
564
  // Intended twin of the sweep in recordCompletedResponse: this one runs when
514
565
  // working is visible on an unfinished frame, that one when a response is
515
566
  // recorded (Issue #31).
@@ -570,6 +621,21 @@ function extractCompletedTurn(ctx, output, lastCapturedLine) {
570
621
  */
571
622
  function savePromptMessage(ctx, promptDetection, result, isFullScreenTui) {
572
623
  const { db, worktree, worktreeId, cliToolId, instanceId, resolvedInstanceId, pollerKey } = ctx;
624
+ // Issue #3701: the question #3663 / #3684 ask, for the prompt a tick retired
625
+ // during the capture's `await` then finds on the pane — no row, broadcast,
626
+ // task event, push or waiting episode under the key it lost. Asked BEFORE the
627
+ // dedup below, which registers the hash before the save: a refused tick that
628
+ // registered it would make the successor drop the same prompt as a
629
+ // duplicate. No `await` from here to the end of this function.
630
+ if (!(0, poller_owner_1.mayActForPollerKey)(pollerKey)) {
631
+ logger.info('prompt-save-refused', {
632
+ worktreeId,
633
+ cliToolId,
634
+ instanceId: resolvedInstanceId,
635
+ generation: (0, poller_owner_1.currentPollerOwner)()?.generation,
636
+ });
637
+ return false;
638
+ }
573
639
  // Issue #565: Content hash-based duplicate prompt prevention
574
640
  const promptContent = promptDetection.rawContent || promptDetection.cleanContent;
575
641
  const normalizedForDedup = (0, prompt_dedup_1.normalizePromptForDedup)(promptContent, cliToolId);
@@ -686,9 +752,10 @@ function savePromptMessage(ctx, promptDetection, result, isFullScreenTui) {
686
752
  * @param cliToolId - CLI tool identifier
687
753
  * @param result - The complete extraction this tick made
688
754
  * @param pollerKey - Poller key ("worktreeId:instanceId")
755
+ * @param owns - Whether the tick still owns its key (Issue #3705)
689
756
  * @returns The cleaned response
690
757
  */
691
- function cleanCompletedResponse(cliToolId, result, pollerKey) {
758
+ function cleanCompletedResponse(cliToolId, result, pollerKey, owns) {
692
759
  // Clean up responses
693
760
  let cleanedResponse = result.response;
694
761
  if (cliToolId === 'gemini') {
@@ -702,7 +769,10 @@ function cleanCompletedResponse(cliToolId, result, pollerKey) {
702
769
  const sourceContent = accumulatedContent || result.response;
703
770
  cleanedResponse = (0, response_cleaner_1.cleanCopilotResponse)(sourceContent);
704
771
  cleanedResponse = (0, response_cleaner_1.truncateMessage)(cleanedResponse, copilot_constants_1.COPILOT_MAX_MESSAGE_LENGTH, copilot_constants_1.COPILOT_TRUNCATION_MARKER);
705
- (0, tui_accumulator_1.clearTuiAccumulator)(pollerKey);
772
+ // Issue #3705: read, but not cleared, by a tick that lost the key — the
773
+ // successor may already be accumulating its own turn under it.
774
+ if (owns)
775
+ (0, tui_accumulator_1.clearTuiAccumulator)(pollerKey);
706
776
  }
707
777
  else if (cliToolId === 'opencode') {
708
778
  // Issue #1911 defect 3: opencode wrote to the Layer-2 accumulator but never
@@ -724,7 +794,8 @@ function cleanCompletedResponse(cliToolId, result, pollerKey) {
724
794
  ? accumulatedContent
725
795
  : result.response;
726
796
  cleanedResponse = (0, response_cleaner_1.cleanOpenCodeResponse)(sourceContent);
727
- (0, tui_accumulator_1.clearTuiAccumulator)(pollerKey);
797
+ if (owns)
798
+ (0, tui_accumulator_1.clearTuiAccumulator)(pollerKey);
728
799
  }
729
800
  return cleanedResponse;
730
801
  }
@@ -744,6 +815,15 @@ function cleanCompletedResponse(cliToolId, result, pollerKey) {
744
815
  */
745
816
  async function recheckDuplicateResponse(ctx, result, claudeMetadata) {
746
817
  const { db, worktree, worktreeId, cliToolId, instanceId, resolvedInstanceId, pollerKey } = ctx;
818
+ // Issue #3705 (review): a retired tick reaches this function too. The dedup
819
+ // answers "duplicate" on a hash match BEFORE it asks about ownership (it only
820
+ // refuses to REGISTER a hash), and the hash it matches may be the successor's
821
+ // — the same reply, saved by the chain that replaced this one while this
822
+ // tick's capture was in flight. Everything below writes under the key (the
823
+ // skip-log claim, the cursor, the recheck claim, then the reader), so the
824
+ // question is asked first, with no `await` between it and those writes.
825
+ if (!mayStillActAfter(ctx, 'capture'))
826
+ return false;
747
827
  // Issue #1695: this branch used to drop the response silently — the
748
828
  // prompt-side guard above has logged its skip since #565, this one
749
829
  // logged nothing at all, so a reply that never reached History left no
@@ -821,6 +901,11 @@ async function recheckDuplicateResponse(ctx, result, claudeMetadata) {
821
901
  worktreePath: worktree.path,
822
902
  transcriptPathHint: claudeMetadata?.logFilePath ?? null,
823
903
  });
904
+ // Issue #3705: the recheck the successor is owed is not settled, and its
905
+ // hold not dropped, by a tick retired while the reader ran. What the reader
906
+ // answered is still the answer (it asks the same question before it writes).
907
+ if (!mayStillActAfter(ctx, 'recheck-reader'))
908
+ return recaptured;
824
909
  if (recaptured) {
825
910
  (0, response_dedup_1.settleStructuredHistoryRecheck)(pollerKey);
826
911
  // Issue #2436: the turn is now the agent's own Markdown, so a
@@ -868,7 +953,10 @@ function recordOrHoldScrapedResponse(ctx, cleanedResponse, claudeMetadata, decis
868
953
  // width and gutter-prefixed, so no content check could ever recognise them
869
954
  // as the same reply.
870
955
  if (holdScrapedHistory) {
871
- (0, pending_scraped_response_1.holdScrapedResponse)(pollerKey, {
956
+ // Issue #3663: refused (false) to a tick whose chain was retired while it
957
+ // awaited the reader — a stop, a stalled-tick restart or a worktree-ID
958
+ // migration. The store logs that, so this tick does not claim a hold.
959
+ const held = (0, pending_scraped_response_1.holdScrapedResponse)(pollerKey, {
872
960
  worktreeId,
873
961
  cliToolId,
874
962
  instanceId: resolvedInstanceId,
@@ -884,19 +972,37 @@ function recordOrHoldScrapedResponse(ctx, cleanedResponse, claudeMetadata, decis
884
972
  transcriptPathHint: claudeMetadata?.logFilePath ?? null,
885
973
  expiresAt: Date.now() + pending_scraped_response_1.PENDING_SCRAPE_HOLD_MS,
886
974
  });
887
- logger.info('structured-history-scrape-held', {
975
+ if (held) {
976
+ logger.info('structured-history-scrape-held', {
977
+ worktreeId,
978
+ cliToolId,
979
+ instanceId: resolvedInstanceId,
980
+ scrapedLength: cleanedResponse.length,
981
+ holdMs: pending_scraped_response_1.PENDING_SCRAPE_HOLD_MS,
982
+ });
983
+ // The completion edge is announced HERE and not at the flush, because the
984
+ // turn finished now. A relay waiting on this session gets #2401's grace
985
+ // window to find a turn-keyed row, which is exactly the row the hold is
986
+ // waiting for; delaying the announcement by the hold would delay every
987
+ // delivery by it too. Issue #3684: only for a hold that was taken — a
988
+ // refused one is a retired tick, which announces nothing under the key
989
+ // it lost, as in the immediate-save branch below.
990
+ (0, relay_triggers_1.onRelayTurnCompleted)(worktreeId, cliToolId, resolvedInstanceId, false);
991
+ }
992
+ }
993
+ else if (!suppressScrapedHistory && !(0, poller_owner_1.mayActForPollerKey)(pollerKey)) {
994
+ // Issue #3684: the same question the hold asks (#3663), for the reply saved
995
+ // at once — a tick retired while it awaited the reader or the log writes no
996
+ // row, broadcast or relay edge under the key it lost. No `await` between
997
+ // this check and the three below. Issue #3705: the caller now asks the
998
+ // same question for the rest of the tick's writes and the Markdown log for
999
+ // its own, so a retired tick reaches this branch only to be refused here.
1000
+ logger.info('scraped-response-save-refused', {
888
1001
  worktreeId,
889
1002
  cliToolId,
890
1003
  instanceId: resolvedInstanceId,
891
- scrapedLength: cleanedResponse.length,
892
- holdMs: pending_scraped_response_1.PENDING_SCRAPE_HOLD_MS,
1004
+ generation: (0, poller_owner_1.currentPollerOwner)()?.generation,
893
1005
  });
894
- // The completion edge is announced HERE and not at the flush, because the
895
- // turn finished now. A relay waiting on this session gets #2401's grace
896
- // window to find a turn-keyed row, which is exactly the row the hold is
897
- // waiting for; delaying the announcement by the hold would delay every
898
- // delivery by it too.
899
- (0, relay_triggers_1.onRelayTurnCompleted)(worktreeId, cliToolId, resolvedInstanceId, false);
900
1006
  }
901
1007
  else if (!suppressScrapedHistory) {
902
1008
  // Create new CLI tool message in database
@@ -999,7 +1105,13 @@ async function recordCompletedResponse(ctx, turn, claudeMetadata, cleanedRespons
999
1105
  // a return — so unless the fact is written down now, the ask never happens
1000
1106
  // again. A `true` settles it: the turn is recorded and there is nothing left
1001
1107
  // to re-ask about.
1002
- markOrSettleStructuredHistoryRecheck(pollerKey, structuredHistoryLive);
1108
+ //
1109
+ // Issue #3705: by a tick that still owns the key. A retired one goes on to
1110
+ // the scrape decision below only so that the store's and the save branch's
1111
+ // own refusals (#3663, #3684) speak for it; nothing else it would write does.
1112
+ if (mayStillActAfter(ctx, 'reader')) {
1113
+ markOrSettleStructuredHistoryRecheck(pollerKey, structuredHistoryLive);
1114
+ }
1003
1115
  // Issue #2317 Phase D: the scrape is dropped while the geometry is
1004
1116
  // delegated, and the transcript capture above is what makes that safe.
1005
1117
  //
@@ -1030,8 +1142,29 @@ async function recordCompletedResponse(ctx, turn, claudeMetadata, cleanedRespons
1030
1142
  // out. Only the two writes that RECORD THE REPLY wait.
1031
1143
  const holdScrapedHistory = !suppressScrapedHistory && captureReport.outcome === 'not_yet_closed';
1032
1144
  // Create Markdown log file for the conversation pair
1145
+ //
1146
+ // Issue #3705: keyed, so the log asks before its own write (after its own
1147
+ // awaits) whether this tick still owns the key. A held reply confirmed by a
1148
+ // stop is logged by the store with no key (`pending-scraped-response`), and
1149
+ // that log is kept.
1033
1150
  if (cleanedResponse && !suppressScrapedHistory && !holdScrapedHistory) {
1034
- await (0, conversation_logger_1.recordClaudeConversation)(db, worktreeId, cleanedResponse, cliToolId);
1151
+ await (0, conversation_logger_1.recordClaudeConversation)(db, worktreeId, cleanedResponse, cliToolId, { pollerKey });
1152
+ }
1153
+ // Issue #3705: everything from here to the end of the function runs without
1154
+ // an `await`, so this is the tick's last chance to find out that it was
1155
+ // retired while the reader or the log ran. A retired tick sweeps no prompt to
1156
+ // "answered", closes no waiting episode, sends no completion push and moves
1157
+ // no cursor under the key it lost — the successor's, possibly. The scrape
1158
+ // decision still runs: every branch of it asks the same question itself
1159
+ // (#3663 the hold, #3684 the immediate save, the store the discard), and its
1160
+ // refusal lines are what the earlier suites read.
1161
+ if (!mayStillActAfter(ctx, 'conversation-log')) {
1162
+ recordOrHoldScrapedResponse(ctx, cleanedResponse, claudeMetadata, {
1163
+ structuredHistoryLive,
1164
+ suppressScrapedHistory,
1165
+ holdScrapedHistory,
1166
+ });
1167
+ return false;
1035
1168
  }
1036
1169
  // Mark any pending prompts as answered. Intended twin of the sweep in
1037
1170
  // extractCompletedTurn (working visible on an unfinished frame); this one runs
@@ -1103,8 +1236,23 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
1103
1236
  (0, response_poller_core_1.stopPolling)(worktreeId, cliToolId, instanceId);
1104
1237
  return false;
1105
1238
  }
1239
+ const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
1240
+ // Issue #3213: what the steps split out of this function are keyed by.
1241
+ const ctx = {
1242
+ db,
1243
+ worktree,
1244
+ worktreeId,
1245
+ cliToolId,
1246
+ instanceId,
1247
+ resolvedInstanceId,
1248
+ pollerKey,
1249
+ };
1106
1250
  // Check if CLI tool session is running
1107
1251
  const running = await (0, cli_session_1.isSessionRunning)(worktreeId, cliToolId, instanceId);
1252
+ // Issue #3705: the first of the tick's awaits. Every one of them is followed
1253
+ // by the same question; see `mayStillActAfter`.
1254
+ if (!mayStillActAfter(ctx, 'session-check'))
1255
+ return false;
1108
1256
  if (!running) {
1109
1257
  logger.info('session-not-running');
1110
1258
  // `stopPolling` is what confirms a held scrape here; see
@@ -1116,23 +1264,14 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
1116
1264
  (0, response_poller_core_1.stopPolling)(worktreeId, cliToolId, instanceId);
1117
1265
  return false;
1118
1266
  }
1119
- const pollerKey = (0, response_poller_core_1.getPollerKey)(worktreeId, cliToolId, instanceId);
1120
- // Issue #3213: what the steps split out of this function are keyed by.
1121
- const ctx = {
1122
- db,
1123
- worktree,
1124
- worktreeId,
1125
- cliToolId,
1126
- instanceId,
1127
- resolvedInstanceId,
1128
- pollerKey,
1129
- };
1130
1267
  // Issue #2436: a scrape held by an earlier tick, whose transcript has now
1131
1268
  // had its whole budget to close. Ahead of everything below because most
1132
1269
  // ticks of a finished turn never reach the save path — a static frame is a
1133
1270
  // duplicate for an alternate-screen tool and yields no new lines for a
1134
1271
  // scrollback one, and both of those return early.
1135
1272
  await (0, pending_scraped_response_1.settleExpiredPendingScrapedResponse)(pollerKey);
1273
+ if (!mayStillActAfter(ctx, 'pending-scrape-settle'))
1274
+ return false;
1136
1275
  // Issue #2317 Phase D: is a human reading this pane at their own terminal
1137
1276
  // size right now, and did they just stop?
1138
1277
  //
@@ -1144,6 +1283,8 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
1144
1283
  const delegation = (0, tmux_session_surface_1.isLiveAttachEligibleSession)(sessionName)
1145
1284
  ? await (0, geometry_delegation_1.probeGeometryDelegation)(sessionName)
1146
1285
  : { delegated: false, released: false };
1286
+ if (!mayStillActAfter(ctx, 'geometry-probe'))
1287
+ return false;
1147
1288
  if (delegation.released) {
1148
1289
  // The canvas is 1000 rows again, so a cursor recorded against a 44-row
1149
1290
  // frame indexes into a pane that no longer exists. Zero is the only value
@@ -1170,7 +1311,16 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
1170
1311
  // taken from the same constant extractResponse() measures saturation against
1171
1312
  // (Issue #1670) — a literal here would silently decouple the two.
1172
1313
  const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, tmux_capture_cache_1.CACHE_MAX_CAPTURE_LINES, instanceId);
1173
- const turn = extractCompletedTurn(ctx, output, lastCapturedLine);
1314
+ // Issue #3705: there is no `await` from here to the transcript reader
1315
+ // inside the save path, so this one answer covers every write in between —
1316
+ // the fault notifier, the TUI accumulator, the prompt sweep, the skip
1317
+ // streaks, the cursor, the in-progress id. A retired tick still READS the
1318
+ // frame and goes on, because the writes that already ask for themselves
1319
+ // (the prompt row #3701, the response hash #3663, the reader and the
1320
+ // scrape decision) are where its refusal is recorded; it writes none of
1321
+ // the rest.
1322
+ const owns = mayStillActAfter(ctx, 'capture');
1323
+ const turn = extractCompletedTurn(ctx, output, lastCapturedLine, owns);
1174
1324
  if (!turn) {
1175
1325
  return false;
1176
1326
  }
@@ -1197,29 +1347,38 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
1197
1347
  // Issue #3552: a live prompt is not the saved reply, so a run of
1198
1348
  // duplicate response ticks is over; the response hash is kept (a resume
1199
1349
  // after the answer must not re-save the reply underneath, #2230).
1200
- (0, response_dedup_1.resetDuplicateResponseSkipStreak)(pollerKey);
1350
+ if (owns)
1351
+ (0, response_dedup_1.resetDuplicateResponseSkipStreak)(pollerKey);
1352
+ // Asks for itself (#3701), before the prompt dedup.
1201
1353
  return savePromptMessage(ctx, promptDetection, result, isFullScreenTui);
1202
1354
  }
1203
1355
  // Issue #3538: a finished frame without a live prompt ends the run of
1204
1356
  // duplicate prompt ticks, as an unfinished one does in `extractCompletedTurn`.
1205
- (0, prompt_dedup_1.resetDuplicatePromptSkipStreak)(pollerKey);
1357
+ if (owns)
1358
+ (0, prompt_dedup_1.resetDuplicatePromptSkipStreak)(pollerKey);
1206
1359
  // Validate response content is not empty
1207
1360
  if (!result.response || result.response.trim() === '') {
1208
1361
  // Issue #3552: an empty response is not the saved reply either.
1209
- (0, response_dedup_1.resetDuplicateResponseSkipStreak)(pollerKey);
1210
- (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount, resolvedInstanceId);
1362
+ if (owns) {
1363
+ (0, response_dedup_1.resetDuplicateResponseSkipStreak)(pollerKey);
1364
+ (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount, resolvedInstanceId);
1365
+ }
1211
1366
  return false;
1212
1367
  }
1213
1368
  // Parse Claude-specific metadata
1214
1369
  const claudeMetadata = cliToolId === 'claude'
1215
1370
  ? (0, claude_output_1.parseClaudeOutput)(result.response)
1216
1371
  : undefined;
1217
- const cleanedResponse = cleanCompletedResponse(cliToolId, result, pollerKey);
1372
+ const cleanedResponse = cleanCompletedResponse(cliToolId, result, pollerKey, owns);
1218
1373
  // If cleaned response is empty or just "[No content]", skip saving
1219
1374
  if (!cleanedResponse || cleanedResponse.trim() === '' || cleanedResponse === '[No content]') {
1220
1375
  // Issue #3552: nor is one that cleans down to nothing.
1221
- (0, response_dedup_1.resetDuplicateResponseSkipStreak)(pollerKey);
1222
- (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount, resolvedInstanceId);
1376
+ if (owns) {
1377
+ (0, response_dedup_1.resetDuplicateResponseSkipStreak)(pollerKey);
1378
+ (0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount, resolvedInstanceId);
1379
+ }
1380
+ // Left as it was (Issue #3705 sets it aside as harmless): nothing sets the
1381
+ // in-progress id to a non-null value or reads it today.
1223
1382
  (0, db_1.clearInProgressMessageId)(db, worktreeId, cliToolId, resolvedInstanceId);
1224
1383
  return false;
1225
1384
  }
@@ -1234,6 +1393,10 @@ async function checkForResponse(worktreeId, cliToolId, instanceId) {
1234
1393
  // Without this the disabled cursor would leave nothing suppressing re-saves and
1235
1394
  // the poller would append the same finished reply every 2 s.
1236
1395
  if (!lineCountIsCursor) {
1396
+ // A retired tick registers no hash here (#3663, `./response-dedup`), but
1397
+ // it can still MATCH one — the successor's, for the same reply. The
1398
+ // recheck below therefore opens on the ownership question itself before
1399
+ // its claim, its cursor and its read (Issue #3705 review).
1237
1400
  if ((0, response_dedup_1.isDuplicateResponse)(pollerKey, cleanedResponse)) {
1238
1401
  // `await`, so that a rejection still lands in the catch below.
1239
1402
  return await recheckDuplicateResponse(ctx, result, claudeMetadata);
@@ -64,6 +64,7 @@ exports.renameResponseHashCacheKey = renameResponseHashCacheKey;
64
64
  exports.renameResponseHashCacheKeys = renameResponseHashCacheKeys;
65
65
  const crypto_1 = require("crypto");
66
66
  const global_state_1 = require("../global-state");
67
+ const poller_owner_1 = require("./poller-owner");
67
68
  const responseHashCache = (0, global_state_1.getOrInitGlobal)('__responseHashCache', () => new Map());
68
69
  /**
69
70
  * In-memory cache: pollerKey -> ticks still to wait before the transcript reader
@@ -127,6 +128,13 @@ function isDuplicateResponse(pollerKey, content) {
127
128
  if (responseHashCache.get(pollerKey) === hash) {
128
129
  return true;
129
130
  }
131
+ // Issue #3663: a tick of a stopped chain no longer owns its key, so it does
132
+ // not register a hash there — the same line the held scrape draws. Left
133
+ // behind, the hash of a reply that tick never saves (its hold is refused)
134
+ // would be inherited by the next chain on the key, or moved by a migration,
135
+ // and make the successor skip it.
136
+ if (!(0, poller_owner_1.mayActForPollerKey)(pollerKey))
137
+ return false;
130
138
  responseHashCache.set(pollerKey, hash);
131
139
  // Issue #3519: a new response starts a new run of duplicate ticks.
132
140
  duplicateResponseSkipStreak.delete(pollerKey);