commandmate 0.30.0 → 0.30.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 (251) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +149 -149
  3. package/.next/app-path-routes-manifest.json +28 -28
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
  9. package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
  10. package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/app/update-check/route.js +1 -1
  12. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/assistant/conversation/route.js.nft.json +1 -1
  14. package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/assistant/current-output/route.js.nft.json +1 -1
  16. package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/assistant/messages/route.js +1 -1
  18. package/.next/server/app/api/assistant/messages/route.js.nft.json +1 -1
  19. package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/assistant/session/route.js +1 -1
  21. package/.next/server/app/api/assistant/session/route.js.nft.json +1 -1
  22. package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/assistant/start/route.js +2 -2
  24. package/.next/server/app/api/assistant/start/route.js.nft.json +1 -1
  25. package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/assistant/terminal/route.js +2 -2
  27. package/.next/server/app/api/assistant/terminal/route.js.nft.json +1 -1
  28. package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/assistant/tools/route.js.nft.json +1 -1
  30. package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/capabilities/route.js +1 -1
  35. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/hooks/agent-event/route.js +1 -1
  44. package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
  45. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/hooks/claude-done/route.js +2 -2
  47. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  48. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  50. package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
  51. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  58. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  62. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
  63. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/repositories/clone/route.js +1 -1
  65. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
  66. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/repositories/restore/route.js +1 -1
  69. package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
  70. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/repositories/route.js +1 -1
  72. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  73. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/repositories/scan/route.js +1 -1
  75. package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
  76. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/repositories/sync/route.js +1 -1
  78. package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
  79. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
  82. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  99. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
  101. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
  103. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  108. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
  137. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  139. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  141. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  151. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  152. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  153. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  155. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  156. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  157. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  159. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  160. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  161. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  163. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  165. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  166. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  167. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
  168. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  169. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  170. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  171. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  172. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  173. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  174. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  175. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  176. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  177. package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
  178. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  179. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  180. package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
  181. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  182. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  183. package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
  184. package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
  185. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  186. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  187. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  188. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  189. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  190. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  191. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  192. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  193. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  194. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  195. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  196. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  197. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  198. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  199. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  200. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  201. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  202. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  203. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  204. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  205. package/.next/server/app/page_client-reference-manifest.js +1 -1
  206. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  207. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  208. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  209. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  210. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  211. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  212. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  213. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  214. package/.next/server/app/worktrees/[id]/page.js +12 -12
  215. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  216. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  217. package/.next/server/app-paths-manifest.json +28 -28
  218. package/.next/server/chunks/1058.js +1 -1
  219. package/.next/server/chunks/1886.js +2 -2
  220. package/.next/server/chunks/2119.js +1 -1
  221. package/.next/server/chunks/2445.js +1 -1
  222. package/.next/server/chunks/3099.js +5 -0
  223. package/.next/server/chunks/5017.js +1 -1
  224. package/.next/server/chunks/5043.js +1 -0
  225. package/.next/server/chunks/552.js +25 -0
  226. package/.next/server/chunks/6130.js +1 -1
  227. package/.next/server/chunks/6894.js +1 -1
  228. package/.next/server/chunks/9696.js +2 -2
  229. package/.next/server/middleware-manifest.json +5 -5
  230. package/.next/server/pages/500.html +1 -1
  231. package/.next/server/server-reference-manifest.json +1 -1
  232. package/.next/static/chunks/{6381-28d0e95620aa018e.js → 6381-dbf04a99647f6018.js} +1 -1
  233. package/.next/static/chunks/app/worktrees/[id]/page-aac399392ec8c273.js +1 -0
  234. package/.next/static/css/23f9c40ecc458c77.css +1 -0
  235. package/.next/trace +13 -13
  236. package/dist/server/src/lib/history/user-turn-recorder.js +9 -4
  237. package/dist/server/src/lib/hooks/agent-event-service.js +40 -6
  238. package/dist/server/src/lib/hooks/sources/antigravity/history.js +101 -13
  239. package/dist/server/src/lib/hooks/sources/claude/history.js +161 -18
  240. package/dist/server/src/lib/hooks/sources/codex/history.js +100 -13
  241. package/dist/server/src/lib/hooks/stop-history-capture.js +161 -0
  242. package/dist/server/src/lib/polling/response-checker.js +43 -3
  243. package/dist/server/src/lib/polling/structured-history-gate.js +131 -6
  244. package/dist/server/src/lib/session/current-output-builder.js +118 -16
  245. package/package.json +1 -1
  246. package/.next/server/chunks/2827.js +0 -29
  247. package/.next/server/chunks/6781.js +0 -1
  248. package/.next/static/chunks/app/worktrees/[id]/page-d48b95ed554bcd13.js +0 -1
  249. package/.next/static/css/61e10d4b1b11b193.css +0 -1
  250. /package/.next/static/{7K0XXFu_ty4wRSkl4X21E → pcpB4K6KreMzu2qbhvuRY}/_buildManifest.js +0 -0
  251. /package/.next/static/{7K0XXFu_ty4wRSkl4X21E → pcpB4K6KreMzu2qbhvuRY}/_ssgManifest.js +0 -0
@@ -148,8 +148,9 @@ function describe(target) {
148
148
  * re-read of the log and a `/clear`.
149
149
  * @param content - The prompt, as the agent recorded it
150
150
  * @param timestampMs - When the agent says it was prompted, epoch ms
151
+ * @param options - See {@link RecordUserTurnOptions}; the defaults are #2196's
151
152
  */
152
- async function recordUserTurn(target, key, content, timestampMs) {
153
+ async function recordUserTurn(target, key, content, timestampMs, options = {}) {
153
154
  const instanceId = target.instanceId ?? target.cliToolId;
154
155
  const normalized = normalizeUserTurnContent(content);
155
156
  if (key.length === 0 || normalized.length === 0) {
@@ -171,7 +172,7 @@ async function recordUserTurn(target, key, content, timestampMs) {
171
172
  };
172
173
  }
173
174
  const at = timestampMs > 0 ? timestampMs : Date.now();
174
- const adopted = adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at);
175
+ const adopted = adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at, options);
175
176
  if (adopted)
176
177
  return adopted;
177
178
  const message = chatDb.createMessage(db, {
@@ -217,13 +218,17 @@ async function recordUserTurn(target, key, content, timestampMs) {
217
218
  *
218
219
  * @returns The claimed row, or null when nothing here belongs to this prompt
219
220
  */
220
- function adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at) {
221
+ function adoptExistingRow(chatDb, db, target, instanceId, key, normalized, at, options) {
222
+ // `Math.min` and not a replacement: {@link RecordUserTurnOptions} may widen
223
+ // the search backwards and may never narrow it, so a caller that supplies a
224
+ // bound *inside* the standard window changes nothing.
225
+ const fromMs = Math.min(at - exports.USER_TURN_ADOPTION_WINDOW_MS, options.adoptionFromMs ?? Number.POSITIVE_INFINITY);
221
226
  const candidates = chatDb
222
227
  .findUnkeyedUserMessages(db, {
223
228
  worktreeId: target.worktreeId,
224
229
  cliToolId: target.cliToolId,
225
230
  instanceId,
226
- fromMs: at - exports.USER_TURN_ADOPTION_WINDOW_MS,
231
+ fromMs,
227
232
  toMs: at + exports.USER_TURN_ADOPTION_WINDOW_MS,
228
233
  })
229
234
  .filter((row) => normalizeUserTurnContent(row.content) === normalized)
@@ -30,6 +30,7 @@ const path_validator_1 = require("../../lib/security/path-validator");
30
30
  const task_transition_service_1 = require("../../lib/tasks/task-transition-service");
31
31
  const gate_runner_1 = require("../../lib/verification/gate-runner");
32
32
  const agent_event_state_1 = require("../../lib/session/agent-event-state");
33
+ const stop_history_capture_1 = require("../../lib/hooks/stop-history-capture");
33
34
  const logger_1 = require("../../lib/logger");
34
35
  const logger = (0, logger_1.createLogger)('lib/hooks/agent-event-service');
35
36
  /**
@@ -144,14 +145,32 @@ function safeRealpath(target) {
144
145
  * Apply a `stop` event to whatever the worktree is currently doing.
145
146
  *
146
147
  * The no-op is the load-bearing case. Sessions with no contract, and therefore
147
- * no task, are the overwhelming majority; for them this records the timestamp
148
- * and returns. Nothing about the existing completion path changes.
148
+ * no task, are the overwhelming majority; for them this records the timestamp,
149
+ * asks the transcript reader for the turn, and returns.
150
+ *
151
+ * ## Why the history read is here (Issue #2246)
152
+ *
153
+ * Because it must happen for *every* stop event and not only for the ones that
154
+ * govern a task — the majority case above is exactly the session whose replies
155
+ * this is trying not to lose — and because both callers of this function are
156
+ * stop receivers (`/api/hooks/agent-event` and the compatibility
157
+ * `/api/hooks/claude-done`). Putting it in one of the routes would have left the
158
+ * other one on the old behaviour.
159
+ *
160
+ * It runs **before** the task lookup so that the two early returns below cannot
161
+ * skip it, and after `recordAgentStopEvent` because the reader resolves its
162
+ * transcript through the session pointer that call refreshes.
163
+ *
164
+ * It is awaited rather than detached. The turn has already ended, so the cost is
165
+ * paid by nobody who is waiting for an answer, and a detached promise here would
166
+ * be one whose failures nothing observes.
149
167
  *
150
168
  * Never throws. A hook is fire-and-forget from a CLI's stop handler, and a
151
169
  * failure here must not become the agent's problem.
152
170
  */
153
171
  async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
154
172
  (0, agent_event_state_1.recordAgentStopEvent)(worktree.id, cliToolId, instanceId);
173
+ const structuredHistoryCaptured = await (0, stop_history_capture_1.captureTranscriptTurnOnStop)(worktree, cliToolId, instanceId);
155
174
  let task;
156
175
  try {
157
176
  task = (0, tasks_db_1.getActiveTaskForInstance)(db, worktree.id, cliToolId, instanceId);
@@ -161,10 +180,20 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
161
180
  worktreeId: worktree.id,
162
181
  error: error instanceof Error ? error.message : String(error),
163
182
  });
164
- return { taskId: null, taskEventApplied: false, verificationRunId: null };
183
+ return {
184
+ taskId: null,
185
+ taskEventApplied: false,
186
+ verificationRunId: null,
187
+ structuredHistoryCaptured,
188
+ };
165
189
  }
166
190
  if (!task) {
167
- return { taskId: null, taskEventApplied: false, verificationRunId: null };
191
+ return {
192
+ taskId: null,
193
+ taskEventApplied: false,
194
+ verificationRunId: null,
195
+ structuredHistoryCaptured,
196
+ };
168
197
  }
169
198
  let taskEventApplied = false;
170
199
  try {
@@ -178,7 +207,12 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
178
207
  });
179
208
  }
180
209
  if (!task.contract.success.autoVerifyOnStop) {
181
- return { taskId: task.id, taskEventApplied, verificationRunId: null };
210
+ return {
211
+ taskId: task.id,
212
+ taskEventApplied,
213
+ verificationRunId: null,
214
+ structuredHistoryCaptured,
215
+ };
182
216
  }
183
217
  let verificationRunId = null;
184
218
  try {
@@ -201,5 +235,5 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
201
235
  });
202
236
  }
203
237
  }
204
- return { taskId: task.id, taskEventApplied, verificationRunId };
238
+ return { taskId: task.id, taskEventApplied, verificationRunId, structuredHistoryCaptured };
205
239
  }
@@ -99,6 +99,7 @@ exports.antigravityBrainRoot = antigravityBrainRoot;
99
99
  exports.antigravityTranscriptPath = antigravityTranscriptPath;
100
100
  exports.acceptAntigravityTranscriptPath = acceptAntigravityTranscriptPath;
101
101
  exports.captureAntigravityTranscriptTurn = captureAntigravityTranscriptTurn;
102
+ exports.resolveAntigravityTranscriptPath = resolveAntigravityTranscriptPath;
102
103
  const promises_1 = require("fs/promises");
103
104
  const os_1 = require("os");
104
105
  const path_1 = require("path");
@@ -256,15 +257,21 @@ function acceptAntigravityTranscriptPath(agyHome, candidate) {
256
257
  return resolved;
257
258
  }
258
259
  /**
259
- * Read the newest turn out of this instance's transcript and write it.
260
+ * Read this instance's unwritten turns out of its transcript and write them.
260
261
  *
261
- * **Only the newest turn**, and that is the same deliberate difference from
262
- * opencode's backfill that #2121 documents: every earlier turn already has a
263
- * `chat_messages` row the *scraper* wrote, and writing a Markdown row for it too
264
- * would put the same reply in History twice. One row per turn from the scraper,
265
- * one row per turn from here, so the swap is one-for-one.
262
+ * **Every turn in the window that is not already a row**, oldest first, which is
263
+ * the shape Issue #2246 gave all three pull readers and the reasoning for it is
264
+ * written out once on `../claude/history`'s
265
+ * {@link captureClaudeTranscriptTurn}. The short version: this reader only runs
266
+ * when the poller judges a turn finished, so a missed judgement used to lose a
267
+ * turn permanently — by the next judgement "the newest turn" had moved on.
266
268
  *
267
- * The return value is the poller's instruction. **True** means History holds
269
+ * #2121's one-row-per-turn argument is preserved by the **anchor**: the newest
270
+ * turn in the window this reader has already written is where the backfill
271
+ * starts, and a window with no anchor falls back to the newest turn alone.
272
+ *
273
+ * The return value is the poller's instruction, and it is about the **newest**
274
+ * turn whatever else was written on the way. **True** means History holds
268
275
  * this turn as the agent's own Markdown and the scrape must be dropped. **False**
269
276
  * means it does not, for any reason at all — no conversation pointer, no file, an
270
277
  * unreadable file, a window with no prompt in it — and the scrape must be saved.
@@ -313,8 +320,7 @@ async function captureAntigravityTranscriptTurn(target, capture = {}) {
313
320
  return false;
314
321
  const parsed = (0, transcript_1.parseAntigravityTranscript)(text);
315
322
  const built = (0, transcript_1.buildAntigravityTurns)(parsed.records, conversationId);
316
- const turn = built.turns.at(-1);
317
- if (!turn) {
323
+ if (built.turns.length === 0) {
318
324
  // No `USER_INPUT` in the window. Two ordinary causes, both fail-open: the
319
325
  // 4 MiB window cut mid-conversation, and `transcript_full.jsonl` can hold
320
326
  // less than the whole history — one of the 41 files in the corpus held a
@@ -341,8 +347,33 @@ async function captureAntigravityTranscriptTurn(target, capture = {}) {
341
347
  preludeRecords: built.preludeRecords,
342
348
  });
343
349
  }
344
- const userRow = await recordAntigravityUserTurn(target, turn);
345
- return await writeAntigravityTurn(target, turn, (0, transcript_1.renderAntigravityTurn)(turn), resolveAssistantTimestampMs(turn, userRow), path);
350
+ const pending = await selectUnwrittenAntigravityTurns(target, built.turns);
351
+ if (pending.turns.length === 0) {
352
+ logger.debug('antigravity-transcript-turns-already-saved', {
353
+ worktreeId: target.worktreeId,
354
+ instanceId,
355
+ turnsInWindow: built.turns.length,
356
+ });
357
+ return true;
358
+ }
359
+ if (pending.turns.length > 1) {
360
+ logger.info('antigravity-transcript-backfilling-turns', {
361
+ worktreeId: target.worktreeId,
362
+ instanceId,
363
+ path,
364
+ pendingTurns: pending.turns.length,
365
+ turnsInWindow: built.turns.length,
366
+ anchored: pending.anchored,
367
+ });
368
+ }
369
+ let captured = false;
370
+ for (let index = 0; index < pending.turns.length; index += 1) {
371
+ const turn = pending.turns[index];
372
+ const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
373
+ const userRow = await recordAntigravityUserTurn(target, turn, previousStartedAt);
374
+ captured = await writeAntigravityTurn(target, turn, (0, transcript_1.renderAntigravityTurn)(turn), resolveAssistantTimestampMs(turn, userRow), path);
375
+ }
376
+ return captured;
346
377
  }
347
378
  catch (error) {
348
379
  logger.error('antigravity-transcript-capture-failed', {
@@ -353,6 +384,59 @@ async function captureAntigravityTranscriptTurn(target, capture = {}) {
353
384
  return false;
354
385
  }
355
386
  }
387
+ /**
388
+ * The transcript file this instance would be read from, or null (Issue #2246).
389
+ *
390
+ * The same two steps {@link captureAntigravityTranscriptTurn} opens with, asked
391
+ * without reading or writing anything; see `../claude/history`'s
392
+ * {@link resolveClaudeTranscriptPath} for what the Stop receiver does with it.
393
+ *
394
+ * Never throws.
395
+ */
396
+ async function resolveAntigravityTranscriptPath(target, capture = {}) {
397
+ try {
398
+ const conversationId = await resolveAntigravityConversationId(target);
399
+ if (!conversationId)
400
+ return null;
401
+ return await locateAntigravityTranscript(capture.antigravityHome ?? resolveAntigravityHome(), conversationId);
402
+ }
403
+ catch {
404
+ return null;
405
+ }
406
+ }
407
+ /**
408
+ * The turns {@link captureAntigravityTranscriptTurn} still has to write (#2246).
409
+ *
410
+ * The same rule as `../claude/history`'s, on agy's own key: search backwards
411
+ * from the newest turn for one that is already a row, and take everything after
412
+ * it. Record order and never a timestamp — agy stamps `created_at` at
413
+ * second resolution, so a turn and its own reply routinely share an instant.
414
+ *
415
+ * @param turns - Every turn in the window, oldest first
416
+ */
417
+ async function selectUnwrittenAntigravityTurns(target, turns) {
418
+ const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
419
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
420
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
421
+ ]);
422
+ const db = getDbInstance();
423
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
424
+ const turn = turns[index];
425
+ const requestId = (0, agent_transcript_1.antigravityTurnRequestId)(turn.conversationId, turn.stepIndex);
426
+ if (!findMessageByRequestId(db, target.worktreeId, requestId))
427
+ continue;
428
+ return {
429
+ turns: turns.slice(index + 1),
430
+ previousStartedAt: turn.startedAt,
431
+ anchored: true,
432
+ };
433
+ }
434
+ return {
435
+ turns: turns.slice(-1),
436
+ previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
437
+ anchored: false,
438
+ };
439
+ }
356
440
  /**
357
441
  * The transcript file for this conversation, or null.
358
442
  *
@@ -392,12 +476,16 @@ async function isReadableFile(path) {
392
476
  *
393
477
  * Never throws; `recordUserTurn` reports its failures in the return value.
394
478
  */
395
- async function recordAntigravityUserTurn(target, turn) {
479
+ async function recordAntigravityUserTurn(target, turn, previousStartedAt = 0) {
396
480
  const skipped = { outcome: 'skipped', messageId: null, timestampMs: null };
397
481
  const prompt = turn.prompt;
398
482
  if (!prompt)
399
483
  return skipped;
400
- const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.antigravityPromptRequestId)(turn.conversationId, prompt.stepIndex), prompt.text, prompt.timestampMs);
484
+ // Issue #2246: a turn read late is a turn whose `/send` row may be older than
485
+ // #2196's symmetric window reaches. The previous turn's start is the tightest
486
+ // honest bound, and it only ever widens the search.
487
+ const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
488
+ const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.antigravityPromptRequestId)(turn.conversationId, prompt.stepIndex), prompt.text, prompt.timestampMs, adoption);
401
489
  if (recorded.outcome === 'failed') {
402
490
  logger.warn('antigravity-transcript-user-turn-failed', {
403
491
  worktreeId: target.worktreeId,
@@ -97,6 +97,7 @@ exports.claudeProjectsRoot = claudeProjectsRoot;
97
97
  exports.claudeTranscriptPath = claudeTranscriptPath;
98
98
  exports.acceptClaudeTranscriptHint = acceptClaudeTranscriptHint;
99
99
  exports.captureClaudeTranscriptTurn = captureClaudeTranscriptTurn;
100
+ exports.resolveClaudeTranscriptPath = resolveClaudeTranscriptPath;
100
101
  exports.readClaudeTurnProgress = readClaudeTurnProgress;
101
102
  const promises_1 = require("fs/promises");
102
103
  const os_1 = require("os");
@@ -113,8 +114,9 @@ const logger = (0, logger_1.createLogger)('lib/hooks/sources/claude/history');
113
114
  * The file grows for the life of the session and a long one is tens of
114
115
  * megabytes — the largest on this machine on 2026-08-31 was 23 MB — so reading
115
116
  * it whole on every finished turn would be the most expensive thing the poller
116
- * does. Only the newest turn is ever written (see {@link captureClaudeTranscriptTurn}),
117
- * so only the newest turn has to be in the window.
117
+ * does. Only turns whose prompt record is inside the window are ever written
118
+ * (see {@link captureClaudeTranscriptTurn}), which is also what stops a turn the
119
+ * window opened halfway through being written from its middle.
118
120
  *
119
121
  * 4 MiB is roughly two orders of magnitude above the measured size of a single
120
122
  * turn and still small enough to read and parse in one tick. A turn that
@@ -216,15 +218,38 @@ function acceptClaudeTranscriptHint(homeDir, hint) {
216
218
  return resolved;
217
219
  }
218
220
  /**
219
- * Read the newest turn out of this instance's transcript and write it.
220
- *
221
- * **Only the newest turn**, and that is the deliberate difference from
222
- * opencode's backfill. Every earlier turn of this session already has a
223
- * `chat_messages` row that the *scraper* wrote, and writing a Markdown row for
224
- * it as well would put the same reply in History twice once as prose and once
225
- * as the pane drew it. The scraper wrote exactly one row per turn and this
226
- * writes exactly one row per turn, so the swap is one-for-one and history is
227
- * neither duplicated nor rewritten.
221
+ * Read this instance's unwritten turns out of its transcript and write them.
222
+ *
223
+ * ## Why this is no longer "only the newest turn" (Issue #2246)
224
+ *
225
+ * #2121 wrote `built.turns.at(-1)` and nothing else, for a reason that was
226
+ * sound and is still half true: every earlier turn of this session already has
227
+ * a `chat_messages` row that the *scraper* wrote, and writing a Markdown row
228
+ * for it as well would put the same reply in History twice — once as prose and
229
+ * once as the pane drew it.
230
+ *
231
+ * What that argument did not cover is a turn **no writer recorded at all**. The
232
+ * trigger for this reader is the poller deciding a turn finished, so a poll that
233
+ * misjudges one completion (#2247's launch-banner heuristic, measured on
234
+ * 2026-09-02) does not merely delay the turn: by the time the next completion is
235
+ * judged, "the newest turn" is the *next* one, and the missed turn is nobody's.
236
+ * One dropped completion cost one turn permanently.
237
+ *
238
+ * So the unit of work is now "every turn in the window that is not already a
239
+ * row", written oldest first, and what preserves #2121's argument is the
240
+ * **anchor**: the newest turn in the window that this reader has already written
241
+ * is where the backfill starts. Turns after it are turns this reader was live
242
+ * for, so a scraper row for them exists only in the case the anchor cannot see
243
+ * — the reader answering false once for a transient reason (an empty body) and
244
+ * the scraper saving that turn's pane copy. That trade is deliberate and in the
245
+ * direction every Issue in this subsystem picks: two writers duplicate a reply,
246
+ * no writer loses one.
247
+ *
248
+ * When the window holds **no** anchor — a session the reader has never written
249
+ * for, and equally the first read after a `/clear` — this falls back to #2121's
250
+ * behaviour exactly: the newest turn, and nothing before it. That is the case
251
+ * where the earlier turns really are the scraper's, and there is no evidence in
252
+ * the window that says otherwise.
228
253
  *
229
254
  * The return value is the poller's instruction, so the two failure directions
230
255
  * are worth stating plainly. **True** means this path has recorded the turn and
@@ -234,10 +259,15 @@ function acceptClaudeTranscriptHint(homeDir, hint) {
234
259
  * false, which is the fail-open the acceptance criteria require: two writers
235
260
  * duplicate a reply, no writer loses one.
236
261
  *
262
+ * **It is the newest turn the answer is about**, backfill or not. A run that
263
+ * wrote three missed turns and then found the newest one still empty answers
264
+ * false, because the scrape the poller is holding is the pane's copy of *that*
265
+ * turn and dropping it would lose it.
266
+ *
237
267
  * Never throws.
238
268
  *
239
269
  * @param target - The instance whose turn just ended
240
- * @returns Whether History now holds this turn as the agent's own Markdown
270
+ * @returns Whether History now holds this instance's newest turn as Markdown
241
271
  */
242
272
  async function captureClaudeTranscriptTurn(target, capture) {
243
273
  const instanceId = target.instanceId ?? target.cliToolId;
@@ -262,8 +292,7 @@ async function captureClaudeTranscriptTurn(target, capture) {
262
292
  return false;
263
293
  const parsed = (0, transcript_1.parseClaudeTranscript)(text);
264
294
  const built = (0, transcript_1.buildClaudeTurns)(parsed.records, sessionId ?? '');
265
- const turn = built.turns.at(-1);
266
- if (!turn) {
295
+ if (built.turns.length === 0) {
267
296
  logger.info('claude-transcript-no-turn', {
268
297
  worktreeId: target.worktreeId,
269
298
  instanceId,
@@ -294,8 +323,43 @@ async function captureClaudeTranscriptTurn(target, capture) {
294
323
  sidechainRecords: built.sidechainRecords,
295
324
  });
296
325
  }
297
- const userRow = await recordClaudeUserTurn(target, turn);
298
- return await writeClaudeTurn(target, (0, transcript_1.renderClaudeTurn)(turn), resolveAssistantTimestampMs(turn, userRow), path);
326
+ const pending = await selectUnwrittenClaudeTurns(target, built.turns);
327
+ if (pending.turns.length === 0) {
328
+ // The anchor is the newest turn in the window, so there is nothing to
329
+ // write and the newest turn is a row. True, for the reason
330
+ // {@link writeClaudeTurn} answers true to an already-saved turn: a second
331
+ // poll of one finished turn must not put the pane's copy on top of it.
332
+ logger.debug('claude-transcript-turns-already-saved', {
333
+ worktreeId: target.worktreeId,
334
+ instanceId,
335
+ turnsInWindow: built.turns.length,
336
+ });
337
+ return true;
338
+ }
339
+ if (pending.turns.length > 1) {
340
+ // The #2246 case, and the one worth a line in the log: more than one turn
341
+ // was unwritten, so a completion went unnoticed at the time.
342
+ logger.info('claude-transcript-backfilling-turns', {
343
+ worktreeId: target.worktreeId,
344
+ instanceId,
345
+ path,
346
+ pendingTurns: pending.turns.length,
347
+ turnsInWindow: built.turns.length,
348
+ anchored: pending.anchored,
349
+ });
350
+ }
351
+ // Oldest first, and the ordering is load-bearing twice over: History sorts
352
+ // by timestamp, and each turn's `/send` row is adopted against the previous
353
+ // turn's start, so a later turn must not get to claim a row an earlier one
354
+ // is about to ask for.
355
+ let captured = false;
356
+ for (let index = 0; index < pending.turns.length; index += 1) {
357
+ const turn = pending.turns[index];
358
+ const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
359
+ const userRow = await recordClaudeUserTurn(target, turn, previousStartedAt);
360
+ captured = await writeClaudeTurn(target, (0, transcript_1.renderClaudeTurn)(turn), resolveAssistantTimestampMs(turn, userRow), path);
361
+ }
362
+ return captured;
299
363
  }
300
364
  catch (error) {
301
365
  logger.error('claude-transcript-capture-failed', {
@@ -306,6 +370,78 @@ async function captureClaudeTranscriptTurn(target, capture) {
306
370
  return false;
307
371
  }
308
372
  }
373
+ /**
374
+ * The turns {@link captureClaudeTranscriptTurn} still has to write (Issue #2246).
375
+ *
376
+ * The search runs **backwards from the newest turn** and stops at the first one
377
+ * that is already a row. That turn is the *anchor*, and everything after it is
378
+ * pending — by construction, since the anchor is the newest written turn in the
379
+ * window.
380
+ *
381
+ * Two properties of that rule are worth stating because both were required:
382
+ *
383
+ * - **Order, not time.** The comparison is the transcript's own record order
384
+ * and never a timestamp. A turn's assistant row is dated one millisecond
385
+ * after its user row (`resolveAssistantTimestampMs`), so the rows do not
386
+ * carry an ordering that could be trusted for this.
387
+ * - **The window bounds it.** `buildClaudeTurns` only opens a turn on a prompt
388
+ * record, so a turn whose prompt fell outside
389
+ * {@link CLAUDE_TRANSCRIPT_TAIL_BYTES} is not in `turns` at all and cannot be
390
+ * backfilled from its middle. Its records are counted as
391
+ * `orphanedAssistantRecords` instead, which is what the caller reports.
392
+ *
393
+ * A window with no anchor answers with the newest turn alone. See
394
+ * {@link captureClaudeTranscriptTurn} for why that is #2121's behaviour rather
395
+ * than a degraded one.
396
+ *
397
+ * @param turns - Every turn in the window, oldest first
398
+ */
399
+ async function selectUnwrittenClaudeTurns(target, turns) {
400
+ const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
401
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
402
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
403
+ ]);
404
+ const db = getDbInstance();
405
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
406
+ const requestId = (0, agent_transcript_1.claudeTurnRequestId)(turns[index].promptUuid);
407
+ if (!findMessageByRequestId(db, target.worktreeId, requestId))
408
+ continue;
409
+ return {
410
+ turns: turns.slice(index + 1),
411
+ previousStartedAt: turns[index].startedAt,
412
+ anchored: true,
413
+ };
414
+ }
415
+ return {
416
+ turns: turns.slice(-1),
417
+ previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
418
+ anchored: false,
419
+ };
420
+ }
421
+ /**
422
+ * The transcript file this instance would be read from, or null (Issue #2246).
423
+ *
424
+ * The same two steps {@link captureClaudeTranscriptTurn} opens with — resolve
425
+ * the session pointer, then check the filesystem — asked without reading or
426
+ * writing anything. The Stop receiver uses it to decide whether waiting half a
427
+ * second and asking again could possibly help: a session with no transcript to
428
+ * name will not have grown one by then, and a hook handler that sleeps for
429
+ * nothing is a hook handler that slows every turn of every tool.
430
+ *
431
+ * Never throws.
432
+ */
433
+ async function resolveClaudeTranscriptPath(target, capture) {
434
+ try {
435
+ const homeDir = capture.homeDir ?? (0, os_1.homedir)();
436
+ if (typeof capture.worktreePath !== 'string' || capture.worktreePath.length === 0) {
437
+ return null;
438
+ }
439
+ return await locateClaudeTranscript(homeDir, capture, await resolveClaudeSessionId(target));
440
+ }
441
+ catch {
442
+ return null;
443
+ }
444
+ }
309
445
  /**
310
446
  * The key for a body read with no prompt record in the window.
311
447
  *
@@ -463,8 +599,14 @@ async function readClaudeTurnProgress(target, capture) {
463
599
  * agent really did reply, and #2121's behaviour for those turns is unchanged.
464
600
  *
465
601
  * Never throws; `recordUserTurn` reports its failures in the return value.
602
+ *
603
+ * @param previousStartedAt - When the turn before this one opened, or 0. Widens
604
+ * `/send` adoption backwards to that instant (Issue #2246): a prompt the agent
605
+ * queued for eleven minutes is still the row CommandMate wrote when it sent
606
+ * the text, and #2196's symmetric two-minute window cannot reach it. Never
607
+ * narrows — see {@link RecordUserTurnOptions}.
466
608
  */
467
- async function recordClaudeUserTurn(target, turn) {
609
+ async function recordClaudeUserTurn(target, turn, previousStartedAt = 0) {
468
610
  const instanceId = target.instanceId ?? target.cliToolId;
469
611
  if (!turn.promptIsOperatorInput) {
470
612
  logger.debug('claude-transcript-user-turn-skipped', {
@@ -475,7 +617,8 @@ async function recordClaudeUserTurn(target, turn) {
475
617
  });
476
618
  return { outcome: 'skipped', messageId: null, timestampMs: null };
477
619
  }
478
- const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.claudePromptRequestId)(turn.promptUuid), turn.promptText, turn.startedAt);
620
+ const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
621
+ const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.claudePromptRequestId)(turn.promptUuid), turn.promptText, turn.startedAt, adoption);
479
622
  if (recorded.outcome === 'failed') {
480
623
  logger.warn('claude-transcript-user-turn-failed', {
481
624
  worktreeId: target.worktreeId,