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
@@ -94,6 +94,7 @@ exports.acceptCodexRolloutPath = acceptCodexRolloutPath;
94
94
  exports.isCodexRolloutFileFor = isCodexRolloutFileFor;
95
95
  exports.findCodexRolloutPath = findCodexRolloutPath;
96
96
  exports.captureCodexTranscriptTurn = captureCodexTranscriptTurn;
97
+ exports.resolveCodexTranscriptPath = resolveCodexTranscriptPath;
97
98
  const promises_1 = require("fs/promises");
98
99
  const os_1 = require("os");
99
100
  const path_1 = require("path");
@@ -296,15 +297,21 @@ async function scanForRollout(dir, sessionId, depth) {
296
297
  return null;
297
298
  }
298
299
  /**
299
- * Read the newest turn out of this instance's rollout and write it.
300
+ * Read this instance's unwritten turns out of its rollout and write them.
300
301
  *
301
- * **Only the newest turn**, and that is the same deliberate difference from
302
- * opencode's backfill that #2121 documents: every earlier turn already has a
303
- * `chat_messages` row the *scraper* wrote, and writing a Markdown row for it too
304
- * would put the same reply in History twice. One row per turn from the scraper,
305
- * one row per turn from here, so the swap is one-for-one.
302
+ * **Every turn in the window that is not already a row**, oldest first, which is
303
+ * the shape Issue #2246 gave all three pull readers and the reasoning for it is
304
+ * written out once on `../claude/history`'s
305
+ * {@link captureClaudeTranscriptTurn}. The short version: this reader only runs
306
+ * when the poller judges a turn finished, so a missed judgement used to lose a
307
+ * turn permanently — by the next judgement "the newest turn" had moved on.
306
308
  *
307
- * The return value is the poller's instruction. **True** means History holds
309
+ * #2121's one-row-per-turn argument is preserved by the **anchor**: the newest
310
+ * turn in the window this reader has already written is where the backfill
311
+ * starts, and a window with no anchor falls back to the newest turn alone.
312
+ *
313
+ * The return value is the poller's instruction, and it is about the **newest**
314
+ * turn whatever else was written on the way. **True** means History holds
308
315
  * this turn as the agent's own Markdown and the scrape must be dropped. **False**
309
316
  * means it does not, for any reason at all — no session pointer, no file, an
310
317
  * unreadable file, a turn codex has not closed yet — and the scrape must be
@@ -350,8 +357,7 @@ async function captureCodexTranscriptTurn(target, capture = {}) {
350
357
  return false;
351
358
  const parsed = (0, transcript_1.parseCodexRollout)(text);
352
359
  const built = (0, transcript_1.buildCodexTurns)(parsed.records, sessionId);
353
- const turn = built.turns.at(-1);
354
- if (!turn) {
360
+ if (built.turns.length === 0) {
355
361
  logger.info('codex-transcript-no-turn', {
356
362
  worktreeId: target.worktreeId,
357
363
  instanceId,
@@ -375,8 +381,33 @@ async function captureCodexTranscriptTurn(target, capture = {}) {
375
381
  duplicateStreamRecords: built.duplicateStreamRecords,
376
382
  });
377
383
  }
378
- const lastUserRow = await recordCodexUserTurns(target, turn);
379
- return await writeCodexTurn(target, turn, (0, transcript_1.renderCodexTurn)(turn), resolveAssistantTimestampMs(turn, lastUserRow), path);
384
+ const pending = await selectUnwrittenCodexTurns(target, built.turns);
385
+ if (pending.turns.length === 0) {
386
+ logger.debug('codex-transcript-turns-already-saved', {
387
+ worktreeId: target.worktreeId,
388
+ instanceId,
389
+ turnsInWindow: built.turns.length,
390
+ });
391
+ return true;
392
+ }
393
+ if (pending.turns.length > 1) {
394
+ logger.info('codex-transcript-backfilling-turns', {
395
+ worktreeId: target.worktreeId,
396
+ instanceId,
397
+ path,
398
+ pendingTurns: pending.turns.length,
399
+ turnsInWindow: built.turns.length,
400
+ anchored: pending.anchored,
401
+ });
402
+ }
403
+ let captured = false;
404
+ for (let index = 0; index < pending.turns.length; index += 1) {
405
+ const turn = pending.turns[index];
406
+ const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
407
+ const lastUserRow = await recordCodexUserTurns(target, turn, previousStartedAt);
408
+ captured = await writeCodexTurn(target, turn, (0, transcript_1.renderCodexTurn)(turn), resolveAssistantTimestampMs(turn, lastUserRow), path);
409
+ }
410
+ return captured;
380
411
  }
381
412
  catch (error) {
382
413
  logger.error('codex-transcript-capture-failed', {
@@ -387,6 +418,58 @@ async function captureCodexTranscriptTurn(target, capture = {}) {
387
418
  return false;
388
419
  }
389
420
  }
421
+ /**
422
+ * The rollout file this instance would be read from, or null (Issue #2246).
423
+ *
424
+ * The same two steps {@link captureCodexTranscriptTurn} opens with, asked
425
+ * without reading or writing anything; see `../claude/history`'s
426
+ * {@link resolveClaudeTranscriptPath} for what the Stop receiver does with it.
427
+ *
428
+ * Never throws.
429
+ */
430
+ async function resolveCodexTranscriptPath(target, capture = {}) {
431
+ try {
432
+ const sessionId = await resolveCodexSessionId(target);
433
+ if (!sessionId)
434
+ return null;
435
+ return await locateCodexRollout(capture.codexHome ?? resolveCodexHome(), sessionId);
436
+ }
437
+ catch {
438
+ return null;
439
+ }
440
+ }
441
+ /**
442
+ * The turns {@link captureCodexTranscriptTurn} still has to write (Issue #2246).
443
+ *
444
+ * The same rule as `../claude/history`'s, on codex's own key: search backwards
445
+ * from the newest turn for one that is already a row, and take everything after
446
+ * it. Record order and never a timestamp, and bounded by the window — a turn
447
+ * whose records fell outside {@link CODEX_TRANSCRIPT_TAIL_BYTES} is not in `turns`.
448
+ *
449
+ * @param turns - Every turn in the window, oldest first
450
+ */
451
+ async function selectUnwrittenCodexTurns(target, turns) {
452
+ const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
453
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
454
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
455
+ ]);
456
+ const db = getDbInstance();
457
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
458
+ const requestId = (0, agent_transcript_1.codexTurnRequestId)(turns[index].turnId);
459
+ if (!findMessageByRequestId(db, target.worktreeId, requestId))
460
+ continue;
461
+ return {
462
+ turns: turns.slice(index + 1),
463
+ previousStartedAt: turns[index].startedAt,
464
+ anchored: true,
465
+ };
466
+ }
467
+ return {
468
+ turns: turns.slice(-1),
469
+ previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
470
+ anchored: false,
471
+ };
472
+ }
390
473
  /**
391
474
  * The rollout file for this session, or null.
392
475
  *
@@ -435,11 +518,15 @@ async function isReadableFile(path) {
435
518
  *
436
519
  * @returns The last row written or found, for the timestamp
437
520
  */
438
- async function recordCodexUserTurns(target, turn) {
521
+ async function recordCodexUserTurns(target, turn, previousStartedAt = 0) {
439
522
  const instanceId = target.instanceId ?? target.cliToolId;
440
523
  let last = { outcome: 'skipped', messageId: null, timestampMs: null };
524
+ // Issue #2246: a turn read late is a turn whose `/send` row may be older than
525
+ // #2196's symmetric window reaches. The previous turn's start is the tightest
526
+ // honest bound, and it only ever widens the search.
527
+ const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
441
528
  for (const prompt of turn.prompts) {
442
- const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.codexPromptRequestId)(prompt.itemId), prompt.text, prompt.timestampMs);
529
+ const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.codexPromptRequestId)(prompt.itemId), prompt.text, prompt.timestampMs, adoption);
443
530
  if (recorded.outcome === 'failed') {
444
531
  logger.warn('codex-transcript-user-turn-failed', {
445
532
  worktreeId: target.worktreeId,
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * Reading the transcript when the agent says it stopped (Issue #2246).
4
+ *
5
+ * ## Why the poller was not enough
6
+ *
7
+ * `lib/polling/structured-history-gate` is asked to record a turn at exactly one
8
+ * moment: the poll on which the scraper decided the turn had finished and was
9
+ * about to save its own copy. That is the right moment — doing the handover at
10
+ * the point of the write is what makes it impossible for both writers to run —
11
+ * but it is the *only* moment, and the decision behind it is a string analysis
12
+ * of a terminal frame.
13
+ *
14
+ * When that analysis misses one completion the cost is not a delay. By the time
15
+ * the next completion is judged, the newest turn is the next one; before #2246
16
+ * the reader wrote only that, so the missed turn belonged to nobody and stayed
17
+ * missing. One misjudged frame, one turn gone (measured 2026-09-02: a reply the
18
+ * transcript held in full and History never showed).
19
+ *
20
+ * The agent itself knows the boundary exactly, and has been telling us: the
21
+ * `stop` hook. This module is that second trigger.
22
+ *
23
+ * ## What it does not do
24
+ *
25
+ * **It does not replace the poller's call.** A session with no hooks — or with
26
+ * hooks the operator never wired up — sends no `stop`, and for it the poller is
27
+ * still the only trigger there is. Two triggers for one turn cost nothing
28
+ * because the readers are idempotent and the gate serialises them per instance;
29
+ * one trigger that is sometimes absent costs a reply.
30
+ *
31
+ * **It does not decide anything about the turn.** The whole judgement stays in
32
+ * the gate and the readers, so the two entry points cannot drift.
33
+ *
34
+ * Never throws: a hook post is fire-and-forget from a CLI's stop handler, and a
35
+ * failure here must not become the agent's problem.
36
+ *
37
+ * @module lib/hooks/stop-history-capture
38
+ */
39
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
40
+ if (k2 === undefined) k2 = k;
41
+ var desc = Object.getOwnPropertyDescriptor(m, k);
42
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
43
+ desc = { enumerable: true, get: function() { return m[k]; } };
44
+ }
45
+ Object.defineProperty(o, k2, desc);
46
+ }) : (function(o, m, k, k2) {
47
+ if (k2 === undefined) k2 = k;
48
+ o[k2] = m[k];
49
+ }));
50
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
51
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
52
+ }) : function(o, v) {
53
+ o["default"] = v;
54
+ });
55
+ var __importStar = (this && this.__importStar) || (function () {
56
+ var ownKeys = function(o) {
57
+ ownKeys = Object.getOwnPropertyNames || function (o) {
58
+ var ar = [];
59
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
60
+ return ar;
61
+ };
62
+ return ownKeys(o);
63
+ };
64
+ return function (mod) {
65
+ if (mod && mod.__esModule) return mod;
66
+ var result = {};
67
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
68
+ __setModuleDefault(result, mod);
69
+ return result;
70
+ };
71
+ })();
72
+ Object.defineProperty(exports, "__esModule", { value: true });
73
+ exports.STOP_TRANSCRIPT_RETRY_DELAY_MS = void 0;
74
+ exports.captureTranscriptTurnOnStop = captureTranscriptTurnOnStop;
75
+ const logger_1 = require("../../lib/logger");
76
+ const logger = (0, logger_1.createLogger)('lib/hooks/stop-history-capture');
77
+ /**
78
+ * How long to wait before asking a second time.
79
+ *
80
+ * The `stop` hook fires when the agent considers the turn over, and the last
81
+ * assistant record is appended to the transcript around — not necessarily
82
+ * before — that instant. A reader that arrives in the gap sees the prompt
83
+ * record and no body, answers false (correctly: an empty row would be a blank
84
+ * reply forever) and, without a retry, hands a turn the agent has definitely
85
+ * finished back to the scraper.
86
+ *
87
+ * Half a second is chosen against what the gap actually is — a file append that
88
+ * has already been issued — rather than against a timeout. One retry and not a
89
+ * loop, because the poller's own trigger is still behind this: if the file is
90
+ * still empty after the retry, the next poll is the right place to notice, and
91
+ * a hook handler that waits on a file is a hook handler that can hang a turn.
92
+ */
93
+ exports.STOP_TRANSCRIPT_RETRY_DELAY_MS = 500;
94
+ function sleep(ms) {
95
+ return new Promise((resolve) => {
96
+ setTimeout(resolve, ms);
97
+ });
98
+ }
99
+ /**
100
+ * Ask the pull-mode reader to record this instance's turn, now.
101
+ *
102
+ * The tool question is asked first and without doing any work
103
+ * (`isPullTranscriptHistory`), so a `stop` from gemini, copilot or opencode
104
+ * costs one map lookup and returns — in particular it never waits out the
105
+ * retry delay. opencode has a second writer too, but a *push* one: its
106
+ * subscription has already received the reply and there is nothing to pull.
107
+ *
108
+ * The import is dynamic so that the poller's module graph — which reaches every
109
+ * transcript reader and the whole opencode client — does not become a static
110
+ * dependency of the hook receiver.
111
+ *
112
+ * @param worktree - The worktree the event resolved to
113
+ * @param cliToolId - The tool that sent the event
114
+ * @param instanceId - The already-resolved instance id
115
+ * @returns Whether History now holds this instance's newest turn as Markdown
116
+ */
117
+ async function captureTranscriptTurnOnStop(worktree, cliToolId, instanceId, options = {}) {
118
+ try {
119
+ const { captureStructuredHistoryTurn, hasStructuredHistoryTranscript, isPullTranscriptHistory } = await Promise.resolve().then(() => __importStar(require('../../lib/polling/structured-history-gate')));
120
+ if (!isPullTranscriptHistory(cliToolId))
121
+ return false;
122
+ // No `transcriptPathHint`: that value is a line the *pane* printed, and the
123
+ // hook receiver has not read the pane. A tool whose transcript can only be
124
+ // found that way has no session pointer either, and therefore sends no stop
125
+ // event this path could act on.
126
+ const capture = { worktreePath: worktree.path, transcriptPathHint: null };
127
+ if (await captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture)) {
128
+ return true;
129
+ }
130
+ const retryDelayMs = options.retryDelayMs ?? exports.STOP_TRANSCRIPT_RETRY_DELAY_MS;
131
+ if (retryDelayMs <= 0)
132
+ return false;
133
+ // The retry is for one failure and one only: the turn's last assistant
134
+ // record had not been appended yet. Every other reason the capture answers
135
+ // false — no session pointer, no transcript, the operator never wired hooks
136
+ // for this tool — is a reason waiting cannot fix, and this handler is on the
137
+ // agent's own stop path, so half a second spent for nothing is half a second
138
+ // added to every turn it is spent on.
139
+ if (!(await hasStructuredHistoryTranscript(worktree.id, cliToolId, instanceId, capture))) {
140
+ return false;
141
+ }
142
+ await sleep(retryDelayMs);
143
+ const captured = await captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture);
144
+ logger.debug('stop-history-capture-retried', {
145
+ worktreeId: worktree.id,
146
+ cliToolId,
147
+ instanceId,
148
+ captured,
149
+ });
150
+ return captured;
151
+ }
152
+ catch (error) {
153
+ logger.warn('stop-history-capture-failed', {
154
+ worktreeId: worktree.id,
155
+ cliToolId,
156
+ instanceId,
157
+ error: error instanceof Error ? error.message : String(error),
158
+ });
159
+ return false;
160
+ }
161
+ }
@@ -434,10 +434,36 @@ function extractResponse(output, lastCapturedLine, cliToolId, captureWindowLines
434
434
  // CRITICAL FIX: Detect and skip Claude Code startup banner/screen
435
435
  if (cliToolId === 'claude') {
436
436
  const cleanResponse = (0, cli_patterns_1.stripAnsi)(response);
437
- const hasBannerArt = /[╭╮╰╯│]/.test(cleanResponse) || /░{3,}/.test(cleanResponse) || /▓{3,}/.test(cleanResponse);
438
- const hasVersionInfo = /Claude Code|claude\/|v\d+\.\d+/.test(cleanResponse);
437
+ // Issue #2247: `│` is what Claude Code draws markdown TABLES with -- the
438
+ // live frame in `tests/fixtures/claude-live-2247/turn-table.txt` is a
439
+ // two-row table and nothing else -- so it identified a reply, not a banner.
440
+ // The banner's own frame glyphs are the rounded corners and the block
441
+ // shading; those stay.
442
+ const hasBannerArt = /[╭╮╰╯]/.test(cleanResponse) || /░{3,}/.test(cleanResponse) || /▓{3,}/.test(cleanResponse);
443
+ // Issue #2247: the bare `v\d+\.\d+` alternative matched any version string a
444
+ // reply happens to mention. The frame that lost a turn on 2026-09-02 was
445
+ // "GitHub Release v0.30.0 を公開しました" (148 chars, well under the 2000
446
+ // below). What the banner actually prints is the tool's own name and
447
+ // version on one row -- `Claude Code v2.1.258` -- so that is what is
448
+ // matched now, plus the `claude/` form older banners used.
449
+ const hasVersionInfo = /Claude Code v\d+\.\d+|claude\//.test(cleanResponse);
439
450
  const hasStartupTips = /Tip:|for shortcuts|\?\s*for help/.test(cleanResponse);
440
451
  const hasProjectInit = /^\s*\/Users\/.*$/m.test(cleanResponse) && cleanResponse.split('\n').length < 30;
452
+ // Issue #2247: the anchors above are only evidence of a banner on a pane
453
+ // that has not had a single turn yet -- the same shape as the #1897 copilot
454
+ // fix below. Claude echoes every prompt into the transcript as `❯ <text>`,
455
+ // and the startup screen has none, so an echo anywhere in the transcript
456
+ // rules the banner out no matter what the reply quotes.
457
+ //
458
+ // The search is `findRecentUserPromptIndex`, deliberately: it is the same
459
+ // `/^[>❯]\s+\S/` this file already anchors extraction on, and it stops at
460
+ // `contentEnd`. That bound is load-bearing rather than incidental -- the
461
+ // footer's composer draws a DIM ghost suggestion (`❯ Try "write a test for
462
+ // <filepath>"`, see `boot-banner.txt`) whose stripped bytes are identical
463
+ // to a real echo (#1879), so a scan over the whole pane would read the
464
+ // startup screen as "already had a turn" and put the banner back in
465
+ // History.
466
+ const hasTurnEcho = findRecentUserPromptIndex(totalLines) >= 0;
441
467
  const userPromptMatch = cleanResponse.match(/^[>❯]\s+(\S.*)$/m);
442
468
  if (userPromptMatch) {
443
469
  const userPromptIndex = cleanResponse.indexOf(userPromptMatch[0]);
@@ -452,7 +478,21 @@ function extractResponse(output, lastCapturedLine, cliToolId, captureWindowLines
452
478
  return incompleteResult(totalLines);
453
479
  }
454
480
  }
455
- else if ((hasBannerArt || hasVersionInfo || hasStartupTips || hasProjectInit) && response.length < 2000) {
481
+ else if (!hasTurnEcho &&
482
+ (hasBannerArt || hasVersionInfo || hasStartupTips || hasProjectInit) &&
483
+ response.length < 2000) {
484
+ // Issue #2247: this branch used to swallow the turn in silence -- the
485
+ // poller kept ticking every 2s and `response-poller` logged nothing at
486
+ // all, so the only way to tell a lost turn from an idle session was to
487
+ // re-run `extractResponse` on a saved pane by hand. It is reached only
488
+ // before the first echo lands, so it cannot become a per-tick flood.
489
+ logger.info('Claude startup banner suppressed, response not saved', {
490
+ responseLength: response.length,
491
+ hasBannerArt,
492
+ hasVersionInfo,
493
+ hasStartupTips,
494
+ hasProjectInit,
495
+ });
456
496
  return incompleteResult(totalLines);
457
497
  }
458
498
  }
@@ -64,8 +64,11 @@
64
64
  * @module lib/polling/structured-history-gate
65
65
  */
66
66
  Object.defineProperty(exports, "__esModule", { value: true });
67
+ exports.resetStructuredHistoryCaptureQueue = resetStructuredHistoryCaptureQueue;
68
+ exports.isPullTranscriptHistory = isPullTranscriptHistory;
67
69
  exports.isStructuredHistoryWriterLive = isStructuredHistoryWriterLive;
68
70
  exports.captureStructuredHistoryTurn = captureStructuredHistoryTurn;
71
+ exports.hasStructuredHistoryTranscript = hasStructuredHistoryTranscript;
69
72
  const logger_1 = require("../../lib/logger");
70
73
  const subscription_1 = require("../../lib/hooks/sources/opencode/subscription");
71
74
  const history_1 = require("../../lib/hooks/sources/claude/history");
@@ -76,6 +79,68 @@ const tool_id_2 = require("../../lib/hooks/sources/codex/tool-id");
76
79
  const tool_id_3 = require("../../lib/hooks/sources/antigravity/tool-id");
77
80
  const registry_1 = require("../../lib/hooks/sources/registry");
78
81
  const logger = (0, logger_1.createLogger)('lib/polling/structured-history-gate');
82
+ /**
83
+ * One in-flight capture per instance (Issue #2246).
84
+ *
85
+ * Until this Issue there was one caller — the poller's save path, whose ticks do
86
+ * not overlap — and the Stop hook receiver is now a second one, arriving at
87
+ * exactly the moment the poller is most likely to be inside the same turn. Two
88
+ * things follow, and it is worth separating them because #2246's own text runs
89
+ * them together.
90
+ *
91
+ * **The duplicate-row race it describes is not open today, and this is what
92
+ * keeps it shut.** The readers are idempotent through `findMessageByRequestId`,
93
+ * which is a check-then-write over a *non-unique* index — but the check and the
94
+ * write are adjacent and synchronous (`better-sqlite3` is), and the only `await`
95
+ * in that stretch is the dynamic import *before* the check. So there is no point
96
+ * for a second caller to interleave at, and two concurrent captures of one turn
97
+ * already end in one row. That is a property of two adjacent statements, which
98
+ * is exactly the kind of property a later refactor removes without noticing: an
99
+ * `await` inserted between them re-opens the window silently, and the row it
100
+ * costs is a duplicated reply nobody sees until an operator reports it.
101
+ *
102
+ * **What it saves today is the work.** Both triggers otherwise read, parse and
103
+ * render the same 4 MiB tail at the same time, and the second one throws all of
104
+ * it away.
105
+ *
106
+ * A queue rather than a shared result: the second caller runs the read again
107
+ * after the first finishes, because it may be asking about a newer turn and
108
+ * because the answer it needs is about the file as it is *now*. Re-running is
109
+ * cheap and, after the first call, finds the row and answers true.
110
+ *
111
+ * On `globalThis` for the reason every shared map in this subsystem is (#1736):
112
+ * under `next dev` the poller's bundle and the hook receiver's bundle would each
113
+ * get a private copy of a module-scoped map, and a lock only one of two bundles
114
+ * can see is not a lock.
115
+ */
116
+ const captureQueue = (globalThis.__structuredHistoryCaptureQueue ??= new Map());
117
+ /** Forget every instance's in-flight capture. Test seam. */
118
+ function resetStructuredHistoryCaptureQueue() {
119
+ captureQueue.clear();
120
+ }
121
+ /** The queue key; the same triple `buildCompositeKey` spells everywhere else. */
122
+ function captureKeyOf(worktreeId, cliToolId, instanceId) {
123
+ return `${worktreeId}\u0000${cliToolId}\u0000${instanceId}`;
124
+ }
125
+ /**
126
+ * Run `work` after whatever is already running for this instance.
127
+ *
128
+ * The stored promise is a settled-only shadow of the real one, so a caller that
129
+ * throws cannot reject the next caller's chain, and the entry is removed only by
130
+ * whoever put it there — a later call that has already replaced it must not have
131
+ * its own lock deleted underneath it.
132
+ */
133
+ function serializePerInstance(key, work) {
134
+ const previous = captureQueue.get(key) ?? Promise.resolve();
135
+ const result = previous.then(work, work);
136
+ const settled = result.then(() => undefined, () => undefined);
137
+ captureQueue.set(key, settled);
138
+ void settled.then(() => {
139
+ if (captureQueue.get(key) === settled)
140
+ captureQueue.delete(key);
141
+ });
142
+ return result;
143
+ }
79
144
  /**
80
145
  * Which reader answers for which tool.
81
146
  *
@@ -91,9 +156,18 @@ const logger = (0, logger_1.createLogger)('lib/polling/structured-history-gate')
91
156
  * {@link captureStructuredHistoryTurn} logs it and falls back to the scraper.
92
157
  */
93
158
  const PULL_TRANSCRIPT_READERS = {
94
- [tool_id_1.CLAUDE_CLI_TOOL_ID]: (target, capture) => (0, history_1.captureClaudeTranscriptTurn)(target, capture),
95
- [tool_id_2.CODEX_CLI_TOOL_ID]: (target, capture) => (0, history_2.captureCodexTranscriptTurn)(target, capture),
96
- [tool_id_3.ANTIGRAVITY_CLI_TOOL_ID]: (target, capture) => (0, history_3.captureAntigravityTranscriptTurn)(target, capture),
159
+ [tool_id_1.CLAUDE_CLI_TOOL_ID]: {
160
+ capture: (target, capture) => (0, history_1.captureClaudeTranscriptTurn)(target, capture),
161
+ locate: (target, capture) => (0, history_1.resolveClaudeTranscriptPath)(target, capture),
162
+ },
163
+ [tool_id_2.CODEX_CLI_TOOL_ID]: {
164
+ capture: (target, capture) => (0, history_2.captureCodexTranscriptTurn)(target, capture),
165
+ locate: (target, capture) => (0, history_2.resolveCodexTranscriptPath)(target, capture),
166
+ },
167
+ [tool_id_3.ANTIGRAVITY_CLI_TOOL_ID]: {
168
+ capture: (target, capture) => (0, history_3.captureAntigravityTranscriptTurn)(target, capture),
169
+ locate: (target, capture) => (0, history_3.resolveAntigravityTranscriptPath)(target, capture),
170
+ },
97
171
  };
98
172
  /**
99
173
  * What this tool's source says about who records its replies.
@@ -114,6 +188,17 @@ function transcriptHistoryModeOf(cliToolId) {
114
188
  return null;
115
189
  }
116
190
  }
191
+ /**
192
+ * Whether this tool keeps a transcript a reader can be asked to pull from.
193
+ *
194
+ * Exported for the Stop receiver (Issue #2246), which has to decide whether the
195
+ * *retry* below is worth waiting for before it answers the agent's hook. It is
196
+ * the same question {@link captureStructuredHistoryTurn} opens with, asked
197
+ * without doing the read — never a tool id comparison, here or there.
198
+ */
199
+ function isPullTranscriptHistory(cliToolId) {
200
+ return transcriptHistoryModeOf(cliToolId) === 'pull';
201
+ }
117
202
  /**
118
203
  * Whether the agent's own server is recording this instance's replies.
119
204
  *
@@ -180,7 +265,7 @@ function isStructuredHistoryWriterLive(worktreeId, cliToolId, instanceId) {
180
265
  * @param capture - Where to look; see {@link StructuredHistoryCapture}
181
266
  */
182
267
  async function captureStructuredHistoryTurn(worktreeId, cliToolId, instanceId, capture) {
183
- if (transcriptHistoryModeOf(cliToolId) !== 'pull')
268
+ if (!isPullTranscriptHistory(cliToolId))
184
269
  return false;
185
270
  const reader = PULL_TRANSCRIPT_READERS[cliToolId];
186
271
  if (!reader) {
@@ -191,11 +276,51 @@ async function captureStructuredHistoryTurn(worktreeId, cliToolId, instanceId, c
191
276
  });
192
277
  return false;
193
278
  }
279
+ const resolvedInstanceId = instanceId ?? cliToolId;
280
+ return serializePerInstance(captureKeyOf(worktreeId, cliToolId, resolvedInstanceId), async () => {
281
+ try {
282
+ return await reader.capture({ worktreeId, cliToolId, instanceId: resolvedInstanceId }, capture);
283
+ }
284
+ catch (error) {
285
+ logger.warn('structured-history-capture-unavailable', {
286
+ worktreeId,
287
+ cliToolId,
288
+ instanceId: resolvedInstanceId,
289
+ error: error instanceof Error ? error.message : String(error),
290
+ });
291
+ return false;
292
+ }
293
+ });
294
+ }
295
+ /**
296
+ * Whether a pull reader can name a transcript for this instance (Issue #2246).
297
+ *
298
+ * Not "is there a turn to write" and not "did anything get written" — only
299
+ * whether the file the reader would open exists right now. The one caller is
300
+ * `lib/hooks/stop-history-capture`, which asks it after a capture answered
301
+ * false, to decide whether waiting half a second and asking again could help.
302
+ * An instance with no transcript will not have one in half a second, and the
303
+ * delay would be paid on every `stop` of every session without hooks.
304
+ *
305
+ * False for every tool that does not declare `transcriptHistory: 'pull'`, and
306
+ * never throws — an unanswerable question is one whose answer is "no file",
307
+ * which costs the retry and nothing else.
308
+ *
309
+ * @param worktreeId - The worktree
310
+ * @param cliToolId - The tool driving the pane
311
+ * @param instanceId - The agent instance; defaults to the primary
312
+ * @param capture - Where to look; see {@link StructuredHistoryCapture}
313
+ */
314
+ async function hasStructuredHistoryTranscript(worktreeId, cliToolId, instanceId, capture) {
315
+ const reader = isPullTranscriptHistory(cliToolId) ? PULL_TRANSCRIPT_READERS[cliToolId] : undefined;
316
+ if (!reader)
317
+ return false;
194
318
  try {
195
- return await reader({ worktreeId, cliToolId, instanceId: instanceId ?? cliToolId }, capture);
319
+ const path = await reader.locate({ worktreeId, cliToolId, instanceId: instanceId ?? cliToolId }, capture);
320
+ return path !== null;
196
321
  }
197
322
  catch (error) {
198
- logger.warn('structured-history-capture-unavailable', {
323
+ logger.warn('structured-history-locate-unavailable', {
199
324
  worktreeId,
200
325
  cliToolId,
201
326
  instanceId: instanceId ?? cliToolId,