commandmate 0.30.0 → 0.30.2

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 (254) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +158 -158
  3. package/.next/app-path-routes-manifest.json +34 -34
  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 +34 -34
  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/pages-manifest.json +1 -1
  232. package/.next/server/server-reference-manifest.json +1 -1
  233. package/.next/static/chunks/{6381-28d0e95620aa018e.js → 6381-147dde2a967e9458.js} +1 -1
  234. package/.next/static/chunks/app/worktrees/[id]/page-2f73d69187936627.js +1 -0
  235. package/.next/static/css/23f9c40ecc458c77.css +1 -0
  236. package/.next/trace +13 -13
  237. package/dist/server/src/lib/history/user-turn-recorder.js +9 -4
  238. package/dist/server/src/lib/hooks/agent-event-service.js +40 -6
  239. package/dist/server/src/lib/hooks/sources/antigravity/history.js +125 -18
  240. package/dist/server/src/lib/hooks/sources/antigravity/transcript.js +39 -0
  241. package/dist/server/src/lib/hooks/sources/claude/history.js +312 -21
  242. package/dist/server/src/lib/hooks/sources/claude/transcript.js +175 -13
  243. package/dist/server/src/lib/hooks/sources/codex/history.js +100 -13
  244. package/dist/server/src/lib/hooks/stop-history-capture.js +194 -0
  245. package/dist/server/src/lib/polling/response-checker.js +43 -3
  246. package/dist/server/src/lib/polling/structured-history-gate.js +131 -6
  247. package/dist/server/src/lib/session/current-output-builder.js +118 -16
  248. package/package.json +1 -1
  249. package/.next/server/chunks/2827.js +0 -29
  250. package/.next/server/chunks/6781.js +0 -1
  251. package/.next/static/chunks/app/worktrees/[id]/page-d48b95ed554bcd13.js +0 -1
  252. package/.next/static/css/61e10d4b1b11b193.css +0 -1
  253. /package/.next/static/{7K0XXFu_ty4wRSkl4X21E → QXxQbXWoo7ldUyOcv7PCZ}/_buildManifest.js +0 -0
  254. /package/.next/static/{7K0XXFu_ty4wRSkl4X21E → QXxQbXWoo7ldUyOcv7PCZ}/_ssgManifest.js +0 -0
@@ -36,9 +36,12 @@
36
36
  * is therefore keyed on the *prompt* record, never on a `requestId`.
37
37
  * 3. **`type: "user"` is mostly not the user.** Of 19 user records in the
38
38
  * sampled session, 18 were `tool_result` payloads. Tool results, `isMeta`
39
- * placeholders (`[Image: original 1440x2170…]`), and the slash-command
39
+ * placeholders (`[Image: original 1440x2170…]`), and the built-in command
40
40
  * bookkeeping records (`<command-name>`, `<local-command-stdout>`) all have
41
- * `type: "user"` and none of them is a prompt. See {@link isClaudePromptRecord}.
41
+ * `type: "user"` and none of them is a prompt. The one `<command-…>` shape
42
+ * that *is* a prompt — a project command, recorded `<command-message>` first
43
+ * — is Issue #2265; see {@link CLAUDE_SLASH_COMMAND_PREFIX} and
44
+ * {@link isClaudePromptRecord}.
42
45
  * 4. **`thinking` blocks can be empty.** The block arrives with a `signature`
43
46
  * and `thinking: ""` when the text is not retained, so an empty one is
44
47
  * skipped rather than rendered as a blank quote.
@@ -53,13 +56,16 @@
53
56
  * @module lib/hooks/sources/claude/transcript
54
57
  */
55
58
  Object.defineProperty(exports, "__esModule", { value: true });
56
- exports.CLAUDE_HUMAN_PROMPT_SOURCES = exports.CLAUDE_HUMAN_ORIGIN_KIND = exports.CLAUDE_THINKING_LABEL = exports.MAX_CLAUDE_TOOL_DETAIL_LENGTH = exports.MAX_CLAUDE_TURN_BLOCKS = exports.CLAUDE_TURN_TRUNCATION_MARKER = exports.MAX_CLAUDE_TURN_BODY_LENGTH = exports.CLAUDE_PROJECTS_DIR_SEGMENTS = void 0;
59
+ exports.CLAUDE_END_TURN_STOP_REASON = exports.CLAUDE_HUMAN_PROMPT_SOURCES = exports.CLAUDE_HUMAN_ORIGIN_KIND = exports.CLAUDE_SLASH_COMMAND_PREFIX = exports.CLAUDE_THINKING_LABEL = exports.MAX_CLAUDE_TOOL_DETAIL_LENGTH = exports.MAX_CLAUDE_TURN_BLOCKS = exports.CLAUDE_TURN_TRUNCATION_MARKER = exports.MAX_CLAUDE_TURN_BODY_LENGTH = exports.CLAUDE_PROJECTS_DIR_SEGMENTS = void 0;
57
60
  exports.claudeProjectSlug = claudeProjectSlug;
61
+ exports.claudeSlashCommandPrompt = claudeSlashCommandPrompt;
58
62
  exports.readClaudeContentBlock = readClaudeContentBlock;
59
63
  exports.readClaudeTranscriptRecord = readClaudeTranscriptRecord;
60
64
  exports.parseClaudeTranscript = parseClaudeTranscript;
61
65
  exports.isClaudePromptRecord = isClaudePromptRecord;
62
66
  exports.isClaudeOperatorPromptRecord = isClaudeOperatorPromptRecord;
67
+ exports.isClaudeTurnClosingRecord = isClaudeTurnClosingRecord;
68
+ exports.isClaudeTurnWritable = isClaudeTurnWritable;
63
69
  exports.createClaudeTurn = createClaudeTurn;
64
70
  exports.buildClaudeTurns = buildClaudeTurns;
65
71
  exports.renderClaudeTurn = renderClaudeTurn;
@@ -111,18 +117,97 @@ exports.CLAUDE_THINKING_LABEL = 'Thinking';
111
117
  /**
112
118
  * Prefixes that mark a `type: "user"` record as bookkeeping rather than a prompt.
113
119
  *
114
- * All four were observed on real transcripts. `<local-command-caveat>` and the
115
- * `<command-…>` trio are how a slash command is recorded; `<local-command-stdout>`
116
- * is its output. Treating any of them as a prompt would open a turn that the
117
- * operator never typed, and the reply to the *next* real prompt would be filed
118
- * under it.
120
+ * Three since Issue #2265, which took `<command-message>` off the list — see
121
+ * {@link CLAUDE_SLASH_COMMAND_PREFIX} for the census that separated the two
122
+ * shapes the `<command-…>` trio is written in. What is left is the bookkeeping
123
+ * of a **built-in** command: `<command-name>` first is how Claude records
124
+ * `/model`, `/compact`, `/clear`, `/login` and `/plan`, `<local-command-stdout>`
125
+ * is that command's output, and `<local-command-caveat>` is the preamble in
126
+ * front of it. Treating any of them as a prompt would open a turn that the agent
127
+ * never answers, and the reply to the *next* real prompt would be filed under it.
119
128
  */
120
129
  const CLAUDE_SYNTHETIC_PROMPT_PREFIXES = [
121
130
  '<local-command-caveat>',
122
131
  '<local-command-stdout>',
123
132
  '<command-name>',
124
- '<command-message>',
125
133
  ];
134
+ /**
135
+ * The tag a slash command the operator typed opens with (Issue #2265).
136
+ *
137
+ * ## The two shapes, and why the leading tag is the discriminator
138
+ *
139
+ * Claude writes the same three tags for two quite different events, and it
140
+ * writes them in a different **order** for each. Measured over every
141
+ * `type: "user"` record under `~/.claude/projects` on 2026-09-03 whose text
142
+ * opens with one of the `<command-…>` tags — 222 records:
143
+ *
144
+ * | leading tag | count | `<command-name>` values |
145
+ * |---|---|---|
146
+ * | `<command-message>` | 118 | `/orchestrate`, `/release`, `/uat`, `/worktree-cleanup`, `/worktree-new`, `/multi-stage-design-review`, `/cmate-*` |
147
+ * | `<command-name>` | 104 | `/model`, `/compact`, `/clear`, `/login`, `/plan` |
148
+ *
149
+ * The split is exact and it is the split that matters here. The 118 are
150
+ * *project and user commands* — a `.claude/commands/*.md` or a skill, expanded
151
+ * into instructions the agent then works through, which is a turn with a reply
152
+ * in it. The 104 are commands Claude Code executes itself; their whole output is
153
+ * the `<local-command-stdout>` record that follows and the agent never answers
154
+ * them.
155
+ *
156
+ * Before #2265 both were excluded, on the reading that a slash command is not
157
+ * "the operator's text". That cost the 118 their entire turn: the reply was
158
+ * folded into whichever turn came before — already saved, so
159
+ * `selectUnwrittenClaudeTurns` answered "nothing to write", and the answer *this*
160
+ * reader gives is also what suppresses the scraper's copy. The measured loss was
161
+ * a `/release v0.30.1` turn of 7 text blocks and an `/orchestrate` turn of 8,
162
+ * neither of which reached History by any path at all.
163
+ *
164
+ * ## `origin`, and the versions that carry it
165
+ *
166
+ * The 118 also answer {@link isClaudeOperatorPromptRecord}, but only on recent
167
+ * Claude Code: of them, every record from 2.1.238 onwards (46) carries
168
+ * `origin: {"kind": "human"}` and every record before it (72, versions 2.1.220
169
+ * … 2.1.235) carries neither marker — those predate the field. On an old
170
+ * version the turn is still opened and its reply still written; only the `user`
171
+ * row is skipped, which is #2196's stated degradation and not a new one.
172
+ */
173
+ exports.CLAUDE_SLASH_COMMAND_PREFIX = '<command-message>';
174
+ /** `<command-name>/release</command-name>` — the command itself. */
175
+ const CLAUDE_COMMAND_NAME_PATTERN = /<command-name>([\s\S]*?)<\/command-name>/;
176
+ /** `<command-args>v0.30.1</command-args>` — absent on a bare invocation. */
177
+ const CLAUDE_COMMAND_ARGS_PATTERN = /<command-args>([\s\S]*?)<\/command-args>/;
178
+ /**
179
+ * The line the operator typed, rebuilt from a slash-command record (Issue #2265).
180
+ *
181
+ * `<command-name>` and `<command-args>` back into `/release v0.30.1`, which is
182
+ * the string that was in the composer and — for a command CommandMate sent — the
183
+ * string `sendUserMessage` already wrote to `chat_messages`. That equality is
184
+ * what makes the `/send` row *adoptable* instead of duplicated: `recordUserTurn`
185
+ * compares on normalised content, and the raw record text
186
+ * (`<command-message>release</command-message>\n<command-name>…`) matches nothing
187
+ * a person ever sent.
188
+ *
189
+ * Returns null for anything that is not this shape, including a
190
+ * `<command-message>` record with no `<command-name>` in it. Null is what keeps
191
+ * {@link isClaudePromptRecord} from opening a turn it could not name: a record
192
+ * whose command cannot be read would otherwise reach History as its own XML,
193
+ * which is exactly the class of mistake #2196's positive-evidence rule exists to
194
+ * avoid. No such record was observed — all 118 carried both tags.
195
+ *
196
+ * @param text - The record's text, as {@link ClaudeTranscriptRecord.text} holds it
197
+ * @returns `/name args`, `/name` when there are no arguments, or null
198
+ */
199
+ function claudeSlashCommandPrompt(text) {
200
+ const trimmed = text.trim();
201
+ if (!trimmed.startsWith(exports.CLAUDE_SLASH_COMMAND_PREFIX))
202
+ return null;
203
+ const name = CLAUDE_COMMAND_NAME_PATTERN.exec(trimmed)?.[1].trim() ?? '';
204
+ if (name.length === 0)
205
+ return null;
206
+ // Absent on a bare invocation, and empty on a command invoked with no
207
+ // arguments; both mean the prompt is the command name on its own.
208
+ const args = CLAUDE_COMMAND_ARGS_PATTERN.exec(trimmed)?.[1].trim() ?? '';
209
+ return args.length === 0 ? name : `${name} ${args}`;
210
+ }
126
211
  /**
127
212
  * Read one `message.content` element.
128
213
  *
@@ -235,6 +320,7 @@ function readClaudeTranscriptRecord(value) {
235
320
  isCompactSummary: value.isCompactSummary === true,
236
321
  isInterruption: (0, event_mapper_1.readStringField)(value, 'interruptedMessageId') !== null,
237
322
  timestampMs: Number.isFinite(parsed) ? parsed : null,
323
+ stopReason: message ? (0, event_mapper_1.readStringField)(message, 'stop_reason') : null,
238
324
  blocks,
239
325
  text,
240
326
  };
@@ -279,11 +365,19 @@ function parseClaudeTranscript(text) {
279
365
  *
280
366
  * - it carries a `tool_result` block — 18 of the 19 user records in the sampled
281
367
  * session were these;
282
- * - `isMeta` is true — the image placeholders and the local-command caveat;
283
- * - its text opens with one of {@link CLAUDE_SYNTHETIC_PROMPT_PREFIXES} the
284
- * slash-command bookkeeping;
368
+ * - `isMeta` is true — the image placeholders, the local-command caveat, and
369
+ * the body of the `.claude/commands/*.md` or skill a slash command expands
370
+ * to, which arrives on its own record right behind the one below;
371
+ * - its text opens with one of {@link CLAUDE_SYNTHETIC_PROMPT_PREFIXES} — a
372
+ * built-in command and its output;
285
373
  * - it has no text at all, or no `uuid` to name the turn with.
286
374
  *
375
+ * A record opening with {@link CLAUDE_SLASH_COMMAND_PREFIX} is the fifth case
376
+ * and the one Issue #2265 changed: it **is** a prompt, because the operator
377
+ * typed `/release v0.30.1` and the agent answers it at length. It qualifies only
378
+ * when {@link claudeSlashCommandPrompt} can actually read the command out of it,
379
+ * so a shape this reader could not name never opens a turn.
380
+ *
287
381
  * Everything else is treated as a prompt, including `[Request interrupted by
288
382
  * user]`. That is deliberate: an interruption really does end the turn, and the
289
383
  * records after it really do belong to whatever came next.
@@ -300,6 +394,8 @@ function isClaudePromptRecord(record) {
300
394
  const text = record.text.trim();
301
395
  if (text.length === 0)
302
396
  return false;
397
+ if (text.startsWith(exports.CLAUDE_SLASH_COMMAND_PREFIX))
398
+ return claudeSlashCommandPrompt(text) !== null;
303
399
  return !CLAUDE_SYNTHETIC_PROMPT_PREFIXES.some((prefix) => text.startsWith(prefix));
304
400
  }
305
401
  /**
@@ -345,6 +441,14 @@ exports.CLAUDE_HUMAN_PROMPT_SOURCES = ['typed', 'queued'];
345
441
  * is the pre-#2196 behaviour — an orphaned assistant pair, which is untidy, and
346
442
  * not a fabricated user message, which is wrong.
347
443
  *
444
+ * Issue #2265 widened what reaches here by one shape — the project-command
445
+ * record `isClaudePromptRecord` now accepts — and the rule holds on it without
446
+ * change: of the 118 such records under `~/.claude/projects` on 2026-09-03, the
447
+ * 46 written by Claude Code 2.1.238 and later carry `origin: {"kind": "human"}`
448
+ * and answer true, and the 72 written before the field existed carry no marker
449
+ * and answer false. The second group loses its `user` row and keeps its reply,
450
+ * which is this function's designed degradation rather than a new gap.
451
+ *
348
452
  * `isCompactSummary` and `isInterruption` are then checked explicitly even
349
453
  * though nothing observed carries a human marker *and* either flag. They are not
350
454
  * redundant by construction: compaction and interruption are both things the
@@ -362,16 +466,62 @@ function isClaudeOperatorPromptRecord(record) {
362
466
  return record.originKind === exports.CLAUDE_HUMAN_ORIGIN_KIND;
363
467
  return record.promptSource !== null && exports.CLAUDE_HUMAN_PROMPT_SOURCES.includes(record.promptSource);
364
468
  }
469
+ /**
470
+ * `message.stop_reason` on the record that ends a reply (Issue #2264).
471
+ *
472
+ * The other value this reader cares about is `tool_use`, which is not named as
473
+ * a constant because nothing branches on it: everything that is not `end_turn`
474
+ * leaves the turn open.
475
+ */
476
+ exports.CLAUDE_END_TURN_STOP_REASON = 'end_turn';
477
+ /**
478
+ * Whether this assistant record is the one that ended the reply (Issue #2264).
479
+ *
480
+ * `end_turn` **and** prose on the same record. See
481
+ * {@link ClaudeTurnAccumulator.closed} for why neither half alone is the rule,
482
+ * and note that the emptiness test here is the one `renderClaudeTurn` applies to
483
+ * a text block — a block whose `text` trims to nothing is not rendered, so it
484
+ * cannot be what makes a turn look finished either.
485
+ */
486
+ function isClaudeTurnClosingRecord(record) {
487
+ if (record.type !== 'assistant')
488
+ return false;
489
+ if (record.stopReason !== exports.CLAUDE_END_TURN_STOP_REASON)
490
+ return false;
491
+ return record.blocks.some((block) => block.type === 'text' && (block.text ?? '').trim().length > 0);
492
+ }
493
+ /**
494
+ * Whether a writer may record this turn (Issue #2264).
495
+ *
496
+ * Either proof that nothing more is coming: the agent said so
497
+ * ({@link ClaudeTurnAccumulator.closed}), or it has moved on to another prompt
498
+ * ({@link ClaudeTurnAccumulator.superseded}).
499
+ */
500
+ function isClaudeTurnWritable(turn) {
501
+ if (turn.closed || turn.superseded)
502
+ return true;
503
+ // No record of this turn said why it stopped, so there is no evidence to
504
+ // refuse it on. See {@link ClaudeTurnAccumulator.stopReasonObserved}: this is
505
+ // unreachable against every Claude version measured, and it is what keeps a
506
+ // version that stops writing the field from silently disabling the reader.
507
+ return !turn.stopReasonObserved;
508
+ }
365
509
  /** A brand-new accumulator for one turn. */
366
510
  function createClaudeTurn(record, sessionId) {
367
511
  return {
368
512
  sessionId: record.sessionId ?? sessionId,
369
513
  promptUuid: record.uuid,
370
514
  startedAt: record.timestampMs ?? 0,
371
- promptText: record.text,
515
+ // A slash command's record is XML and the operator typed a line; #2265 puts
516
+ // the line on the turn so that the `user` row reads `/release v0.30.1` and
517
+ // the `/send` row holding those same bytes is adopted rather than doubled.
518
+ promptText: claudeSlashCommandPrompt(record.text) ?? record.text,
372
519
  promptIsOperatorInput: isClaudeOperatorPromptRecord(record),
373
520
  blocks: [],
374
521
  assistantRecords: 0,
522
+ closed: false,
523
+ superseded: false,
524
+ stopReasonObserved: false,
375
525
  overflowed: false,
376
526
  };
377
527
  }
@@ -416,6 +566,13 @@ function buildClaudeTurns(records, sessionId) {
416
566
  continue;
417
567
  }
418
568
  current.assistantRecords += 1;
569
+ // Assigned rather than or-ed, so the value left standing is the *last*
570
+ // assistant record's answer. A turn that ends on a `tool_use` record after
571
+ // an earlier `end_turn` — which is what a turn Claude resumed looks like —
572
+ // is open again, and that is the reading the Issue's census supports.
573
+ current.closed = isClaudeTurnClosingRecord(record);
574
+ if (record.stopReason !== null)
575
+ current.stopReasonObserved = true;
419
576
  for (const block of record.blocks) {
420
577
  if (current.blocks.length >= exports.MAX_CLAUDE_TURN_BLOCKS) {
421
578
  current.overflowed = true;
@@ -424,6 +581,11 @@ function buildClaudeTurns(records, sessionId) {
424
581
  current.blocks.push(block);
425
582
  }
426
583
  }
584
+ // Every turn but the last one has a later prompt behind it, and a prompt is
585
+ // proof the agent moved on. See {@link ClaudeTurnAccumulator.superseded}.
586
+ for (let index = 0; index < turns.length - 1; index += 1) {
587
+ turns[index].superseded = true;
588
+ }
427
589
  return { turns, orphanedAssistantRecords, sidechainRecords };
428
590
  }
429
591
  /**
@@ -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,194 @@
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
+ * ## Knowing the boundary is not the same as the file having reached it
24
+ *
25
+ * The `stop` post and the transcript's last append are two events with no
26
+ * ordering between them, and Issue #2264 is what happens when the reader is
27
+ * asked in the gap. The reader now refuses a turn it cannot prove is closed, so
28
+ * the cost of asking too early is a `false` rather than a truncated row — and
29
+ * this module's answer to a `false` is to ask again, up to
30
+ * {@link STOP_TRANSCRIPT_MAX_ATTEMPTS} times, before handing the turn back.
31
+ *
32
+ * ## What it does not do
33
+ *
34
+ * **It does not replace the poller's call.** A session with no hooks — or with
35
+ * hooks the operator never wired up — sends no `stop`, and for it the poller is
36
+ * still the only trigger there is. Two triggers for one turn cost nothing
37
+ * because the readers are idempotent and the gate serialises them per instance;
38
+ * one trigger that is sometimes absent costs a reply.
39
+ *
40
+ * **It does not decide anything about the turn.** The whole judgement stays in
41
+ * the gate and the readers, so the two entry points cannot drift.
42
+ *
43
+ * Never throws: a hook post is fire-and-forget from a CLI's stop handler, and a
44
+ * failure here must not become the agent's problem.
45
+ *
46
+ * @module lib/hooks/stop-history-capture
47
+ */
48
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
49
+ if (k2 === undefined) k2 = k;
50
+ var desc = Object.getOwnPropertyDescriptor(m, k);
51
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
52
+ desc = { enumerable: true, get: function() { return m[k]; } };
53
+ }
54
+ Object.defineProperty(o, k2, desc);
55
+ }) : (function(o, m, k, k2) {
56
+ if (k2 === undefined) k2 = k;
57
+ o[k2] = m[k];
58
+ }));
59
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
60
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
61
+ }) : function(o, v) {
62
+ o["default"] = v;
63
+ });
64
+ var __importStar = (this && this.__importStar) || (function () {
65
+ var ownKeys = function(o) {
66
+ ownKeys = Object.getOwnPropertyNames || function (o) {
67
+ var ar = [];
68
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
69
+ return ar;
70
+ };
71
+ return ownKeys(o);
72
+ };
73
+ return function (mod) {
74
+ if (mod && mod.__esModule) return mod;
75
+ var result = {};
76
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
77
+ __setModuleDefault(result, mod);
78
+ return result;
79
+ };
80
+ })();
81
+ Object.defineProperty(exports, "__esModule", { value: true });
82
+ exports.STOP_TRANSCRIPT_MAX_ATTEMPTS = exports.STOP_TRANSCRIPT_RETRY_DELAY_MS = void 0;
83
+ exports.captureTranscriptTurnOnStop = captureTranscriptTurnOnStop;
84
+ const logger_1 = require("../../lib/logger");
85
+ const logger = (0, logger_1.createLogger)('lib/hooks/stop-history-capture');
86
+ /**
87
+ * How long to wait before asking again.
88
+ *
89
+ * The `stop` hook fires when the agent considers the turn over, and the last
90
+ * assistant record is appended to the transcript around — not necessarily
91
+ * before — that instant. A reader that arrives in the gap sees a turn that is
92
+ * not closed, answers false (correctly: the row it would write is a reply with
93
+ * its last paragraph missing, frozen under the turn key forever) and, without a
94
+ * retry, hands a turn the agent has definitely finished back to the scraper.
95
+ *
96
+ * Half a second is chosen against what the gap actually is — a file append that
97
+ * has already been issued — rather than against a timeout.
98
+ */
99
+ exports.STOP_TRANSCRIPT_RETRY_DELAY_MS = 500;
100
+ /**
101
+ * How many times the reader is asked in total (Issue #2264).
102
+ *
103
+ * #2246 asked twice, on the reading that the only thing a retry waits for is one
104
+ * append that has already been issued. The incident #2264 was reported for says
105
+ * that is not always one append: the measured gap between the `stop` post and
106
+ * the last `text` record was around 100 ms in the fast case, but the turns that
107
+ * were saved short had *tool* records still arriving, and a single 500 ms retry
108
+ * lands inside that run rather than after it.
109
+ *
110
+ * Three and not a loop, because the poller's own trigger is still behind this:
111
+ * if the turn is still open after the third ask, the next poll is the right
112
+ * place to notice — it runs when the pane returns to the composer, by which time
113
+ * the transcript is written — and a hook handler that waits on a file is a hook
114
+ * handler that can hang a turn. The worst case this bounds is 1 s of the agent's
115
+ * stop path, spent only on an instance that has a transcript and an open turn.
116
+ */
117
+ exports.STOP_TRANSCRIPT_MAX_ATTEMPTS = 3;
118
+ function sleep(ms) {
119
+ return new Promise((resolve) => {
120
+ setTimeout(resolve, ms);
121
+ });
122
+ }
123
+ /**
124
+ * Ask the pull-mode reader to record this instance's turn, now.
125
+ *
126
+ * The tool question is asked first and without doing any work
127
+ * (`isPullTranscriptHistory`), so a `stop` from gemini, copilot or opencode
128
+ * costs one map lookup and returns — in particular it never waits out the
129
+ * retry delay. opencode has a second writer too, but a *push* one: its
130
+ * subscription has already received the reply and there is nothing to pull.
131
+ *
132
+ * The import is dynamic so that the poller's module graph — which reaches every
133
+ * transcript reader and the whole opencode client — does not become a static
134
+ * dependency of the hook receiver.
135
+ *
136
+ * @param worktree - The worktree the event resolved to
137
+ * @param cliToolId - The tool that sent the event
138
+ * @param instanceId - The already-resolved instance id
139
+ * @returns Whether History now holds this instance's newest turn as Markdown
140
+ */
141
+ async function captureTranscriptTurnOnStop(worktree, cliToolId, instanceId, options = {}) {
142
+ try {
143
+ const { captureStructuredHistoryTurn, hasStructuredHistoryTranscript, isPullTranscriptHistory } = await Promise.resolve().then(() => __importStar(require('../../lib/polling/structured-history-gate')));
144
+ if (!isPullTranscriptHistory(cliToolId))
145
+ return false;
146
+ // No `transcriptPathHint`: that value is a line the *pane* printed, and the
147
+ // hook receiver has not read the pane. A tool whose transcript can only be
148
+ // found that way has no session pointer either, and therefore sends no stop
149
+ // event this path could act on.
150
+ const capture = { worktreePath: worktree.path, transcriptPathHint: null };
151
+ if (await captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture)) {
152
+ return true;
153
+ }
154
+ const retryDelayMs = options.retryDelayMs ?? exports.STOP_TRANSCRIPT_RETRY_DELAY_MS;
155
+ if (retryDelayMs <= 0)
156
+ return false;
157
+ // The retry is for one failure and one only: the turn is not closed yet
158
+ // because its remaining assistant records have not been appended. Every
159
+ // other reason the capture answers false — no session pointer, no
160
+ // transcript, the operator never wired hooks for this tool — is a reason
161
+ // waiting cannot fix, and this handler is on the agent's own stop path, so
162
+ // half a second spent for nothing is half a second added to every turn it is
163
+ // spent on. Asked once, before the first sleep: a transcript that exists
164
+ // does not stop existing between attempts.
165
+ if (!(await hasStructuredHistoryTranscript(worktree.id, cliToolId, instanceId, capture))) {
166
+ return false;
167
+ }
168
+ const maxAttempts = Math.max(1, options.maxAttempts ?? exports.STOP_TRANSCRIPT_MAX_ATTEMPTS);
169
+ for (let attempt = 2; attempt <= maxAttempts; attempt += 1) {
170
+ await sleep(retryDelayMs);
171
+ const captured = await captureStructuredHistoryTurn(worktree.id, cliToolId, instanceId, capture);
172
+ logger.debug('stop-history-capture-retried', {
173
+ worktreeId: worktree.id,
174
+ cliToolId,
175
+ instanceId,
176
+ attempt,
177
+ maxAttempts,
178
+ captured,
179
+ });
180
+ if (captured)
181
+ return true;
182
+ }
183
+ return false;
184
+ }
185
+ catch (error) {
186
+ logger.warn('stop-history-capture-failed', {
187
+ worktreeId: worktree.id,
188
+ cliToolId,
189
+ instanceId,
190
+ error: error instanceof Error ? error.message : String(error),
191
+ });
192
+ return false;
193
+ }
194
+ }