commandmate 0.30.2 → 0.31.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 (442) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +151 -151
  3. package/.next/app-path-routes-manifest.json +25 -25
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +9 -9
  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 +1 -1
  14. package/.next/server/app/api/assistant/conversation/route.js.nft.json +1 -1
  15. package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/assistant/current-output/route.js +1 -1
  17. package/.next/server/app/api/assistant/current-output/route.js.nft.json +1 -1
  18. package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/assistant/messages/route.js +1 -1
  20. package/.next/server/app/api/assistant/messages/route.js.nft.json +1 -1
  21. package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/assistant/session/route.js +1 -1
  23. package/.next/server/app/api/assistant/session/route.js.nft.json +1 -1
  24. package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/assistant/start/route.js +3 -3
  26. package/.next/server/app/api/assistant/start/route.js.nft.json +1 -1
  27. package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/assistant/terminal/route.js +4 -4
  29. package/.next/server/app/api/assistant/terminal/route.js.nft.json +1 -1
  30. package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/assistant/tools/route.js +1 -1
  32. package/.next/server/app/api/assistant/tools/route.js.nft.json +1 -1
  33. package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/capabilities/route.js +1 -1
  38. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/hooks/agent-event/route.js +1 -1
  47. package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
  48. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/hooks/claude-done/route.js +1 -1
  50. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  51. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  53. package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
  54. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  58. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  65. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
  66. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/repositories/clone/route.js +1 -1
  68. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
  69. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/repositories/restore/route.js +1 -1
  72. package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
  73. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/repositories/route.js +1 -1
  75. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  76. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/repositories/scan/route.js +1 -1
  78. package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
  79. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/repositories/sync/route.js +1 -1
  81. package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
  82. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/settings/default-agents/route.js +1 -1
  85. package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
  86. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/skills/[id]/route.js +1 -1
  90. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/skills/route.js +1 -1
  95. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/slash-commands/route.js +1 -1
  97. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  107. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
  110. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
  113. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  119. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  141. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
  150. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  151. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  152. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  153. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  155. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  156. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  157. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  159. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  160. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  161. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  163. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  165. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  166. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  167. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  168. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  169. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  170. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  171. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  172. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  173. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  174. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  175. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  176. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +4 -3
  177. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  178. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  179. package/.next/server/app/api/worktrees/[id]/schedules/route.js +4 -3
  180. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  181. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  182. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  183. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  184. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  185. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js +2 -2
  186. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
  187. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  188. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  189. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  190. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  191. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  192. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  193. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  194. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  195. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  196. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  197. package/.next/server/app/api/worktrees/[id]/special-keys/route.js +1 -1
  198. package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
  199. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  200. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  201. package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -1
  202. package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
  203. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  204. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  205. package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
  206. package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
  207. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  208. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  209. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  210. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  211. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  212. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  213. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  214. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  215. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  216. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  217. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  218. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  219. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  220. package/.next/server/app/api/worktrees/route.js +1 -1
  221. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  222. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  223. package/.next/server/app/chat/page.js +2 -2
  224. package/.next/server/app/chat/page.js.nft.json +1 -1
  225. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  226. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  227. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  228. package/.next/server/app/more/page.js +2 -2
  229. package/.next/server/app/more/page.js.nft.json +1 -1
  230. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  231. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  232. package/.next/server/app/page.js +1 -1
  233. package/.next/server/app/page.js.nft.json +1 -1
  234. package/.next/server/app/page_client-reference-manifest.js +1 -1
  235. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  236. package/.next/server/app/repositories/page.js +2 -2
  237. package/.next/server/app/repositories/page.js.nft.json +1 -1
  238. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  239. package/.next/server/app/review/page.js +1 -1
  240. package/.next/server/app/review/page.js.nft.json +1 -1
  241. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  242. package/.next/server/app/sessions/page.js +1 -1
  243. package/.next/server/app/sessions/page.js.nft.json +1 -1
  244. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  245. package/.next/server/app/skills/[skillId]/page.js +2 -2
  246. package/.next/server/app/skills/[skillId]/page.js.nft.json +1 -1
  247. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  248. package/.next/server/app/skills/installed/page.js +2 -2
  249. package/.next/server/app/skills/installed/page.js.nft.json +1 -1
  250. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  251. package/.next/server/app/skills/page.js +1 -1
  252. package/.next/server/app/skills/page.js.nft.json +1 -1
  253. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  254. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  255. package/.next/server/app/worktrees/[id]/page.js +11 -11
  256. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  257. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  258. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  259. package/.next/server/app-paths-manifest.json +25 -25
  260. package/.next/server/chunks/1058.js +1 -1
  261. package/.next/server/chunks/1068.js +1 -1
  262. package/.next/server/chunks/1262.js +1 -1
  263. package/.next/server/chunks/1886.js +3 -3
  264. package/.next/server/chunks/1990.js +103 -0
  265. package/.next/server/chunks/2119.js +1 -1
  266. package/.next/server/chunks/2328.js +1 -1
  267. package/.next/server/chunks/2404.js +1 -1
  268. package/.next/server/chunks/2445.js +1 -1
  269. package/.next/server/chunks/261.js +1 -1
  270. package/.next/server/chunks/3123.js +1 -1
  271. package/.next/server/chunks/3241.js +1 -1
  272. package/.next/server/chunks/3480.js +5 -4
  273. package/.next/server/chunks/4299.js +1 -1
  274. package/.next/server/chunks/4393.js +1 -1
  275. package/.next/server/chunks/4443.js +1 -5
  276. package/.next/server/chunks/4802.js +1 -0
  277. package/.next/server/chunks/5017.js +1 -1
  278. package/.next/server/chunks/5043.js +1 -1
  279. package/.next/server/chunks/5164.js +1 -1
  280. package/.next/server/chunks/552.js +2 -2
  281. package/.next/server/chunks/6072.js +1 -1
  282. package/.next/server/chunks/6130.js +1 -1
  283. package/.next/server/chunks/663.js +4 -3
  284. package/.next/server/chunks/6894.js +2 -2
  285. package/.next/server/chunks/7187.js +1 -0
  286. package/.next/server/chunks/7472.js +1 -1
  287. package/.next/server/chunks/{880.js → 7859.js} +6 -6
  288. package/.next/server/chunks/7905.js +1 -1
  289. package/.next/server/chunks/8800.js +2 -2
  290. package/.next/server/chunks/8918.js +7 -0
  291. package/.next/server/chunks/9123.js +2 -2
  292. package/.next/server/chunks/9130.js +4 -4
  293. package/.next/server/chunks/9627.js +1 -1
  294. package/.next/server/chunks/9696.js +1 -1
  295. package/.next/server/chunks/9798.js +1 -1
  296. package/.next/server/middleware-manifest.json +5 -5
  297. package/.next/server/pages/500.html +1 -1
  298. package/.next/server/pages-manifest.json +1 -1
  299. package/.next/server/server-reference-manifest.json +1 -1
  300. package/.next/static/chunks/1783-11c9b4d8a52ebd91.js +1 -0
  301. package/.next/static/chunks/4718-46e1d2f07c8423ba.js +1 -0
  302. package/.next/static/chunks/6022-2fa0b5a82d4ce7b6.js +1 -0
  303. package/.next/static/chunks/{5229-6c89475b61bd2c8b.js → 6170-951b9f5fe6b4b3b5.js} +1 -1
  304. package/.next/static/chunks/app/chat/{page-9aa2011410895b0d.js → page-37d7615d17d7e2a0.js} +1 -1
  305. package/.next/static/chunks/app/layout-4c2f21e3f653ee4c.js +1 -0
  306. package/.next/static/chunks/app/more/{page-333bba1c8bbc9c11.js → page-353a3924e6d7968e.js} +1 -1
  307. package/.next/static/chunks/app/{page-214f7ffa13a0cf10.js → page-c2bf871d953a1d6f.js} +1 -1
  308. package/.next/static/chunks/app/repositories/{page-4951cac905e50224.js → page-7d0f26df0537254c.js} +1 -1
  309. package/.next/static/chunks/app/review/{page-a0899b2d285318c3.js → page-e3f141b2829973e9.js} +1 -1
  310. package/.next/static/chunks/app/sessions/{page-fb36145ec732092a.js → page-53f188ed9ca49e5f.js} +1 -1
  311. package/.next/static/chunks/app/skills/[skillId]/{page-9099e26b60442873.js → page-80569b720aea59cc.js} +1 -1
  312. package/.next/static/chunks/app/skills/installed/{page-182e32368696d824.js → page-dbd2690ad72da95b.js} +1 -1
  313. package/.next/static/chunks/app/skills/{page-d3fdc88239ebe0ab.js → page-dacd36f9c11aa7da.js} +1 -1
  314. package/.next/static/chunks/app/worktrees/[id]/page-64d4ec3051971760.js +1 -0
  315. package/.next/static/css/5c13ea243dfdcb66.css +1 -0
  316. package/.next/trace +13 -13
  317. package/README.md +37 -11
  318. package/dist/cli/commands/attach.d.ts +50 -0
  319. package/dist/cli/commands/attach.d.ts.map +1 -0
  320. package/dist/cli/commands/attach.js +204 -0
  321. package/dist/cli/commands/capture.d.ts.map +1 -1
  322. package/dist/cli/commands/capture.js +114 -0
  323. package/dist/cli/commands/instances.d.ts.map +1 -1
  324. package/dist/cli/commands/instances.js +23 -2
  325. package/dist/cli/commands/ls.d.ts.map +1 -1
  326. package/dist/cli/commands/ls.js +41 -1
  327. package/dist/cli/config/cli-dependencies.d.ts.map +1 -1
  328. package/dist/cli/config/cli-dependencies.js +34 -0
  329. package/dist/cli/program.d.ts.map +1 -1
  330. package/dist/cli/program.js +5 -0
  331. package/dist/cli/types/index.d.ts +36 -0
  332. package/dist/cli/types/index.d.ts.map +1 -1
  333. package/dist/config/tmux-pane-config.d.ts +153 -0
  334. package/dist/config/tmux-pane-config.d.ts.map +1 -0
  335. package/dist/config/tmux-pane-config.js +172 -0
  336. package/dist/lib/cli-tools/session-name.d.ts +44 -0
  337. package/dist/lib/cli-tools/session-name.d.ts.map +1 -0
  338. package/dist/lib/cli-tools/session-name.js +57 -0
  339. package/dist/lib/cli-tools/types.d.ts +1 -1
  340. package/dist/lib/cli-tools/types.d.ts.map +1 -1
  341. package/dist/lib/cli-tools/types.js +6 -1
  342. package/dist/lib/cli-tools/validation.d.ts +34 -0
  343. package/dist/lib/cli-tools/validation.d.ts.map +1 -0
  344. package/dist/lib/cli-tools/validation.js +41 -0
  345. package/dist/lib/detection/status-reason.d.ts +18 -0
  346. package/dist/lib/detection/status-reason.d.ts.map +1 -1
  347. package/dist/lib/detection/status-reason.js +18 -0
  348. package/dist/lib/detection/tools/verified-against.d.ts +57 -7
  349. package/dist/lib/detection/tools/verified-against.d.ts.map +1 -1
  350. package/dist/lib/detection/tools/verified-against.js +59 -8
  351. package/dist/lib/detection/version-probes.d.ts +26 -5
  352. package/dist/lib/detection/version-probes.d.ts.map +1 -1
  353. package/dist/lib/detection/version-probes.js +28 -6
  354. package/dist/lib/session/tmux-session-surface.d.ts +369 -0
  355. package/dist/lib/session/tmux-session-surface.d.ts.map +1 -0
  356. package/dist/lib/session/tmux-session-surface.js +525 -0
  357. package/dist/server/src/config/cli-tool-timing-config.js +12 -1
  358. package/dist/server/src/config/detection-evidence-config.js +5 -0
  359. package/dist/server/src/config/schedule-config.js +28 -1
  360. package/dist/server/src/lib/cli-tools/antigravity.js +2 -2
  361. package/dist/server/src/lib/cli-tools/base.js +13 -8
  362. package/dist/server/src/lib/cli-tools/claude.js +20 -0
  363. package/dist/server/src/lib/cli-tools/codex.js +205 -61
  364. package/dist/server/src/lib/cli-tools/command-code.js +349 -0
  365. package/dist/server/src/lib/cli-tools/composer-spec.js +6 -0
  366. package/dist/server/src/lib/cli-tools/copilot.js +2 -2
  367. package/dist/server/src/lib/cli-tools/gemini.js +2 -2
  368. package/dist/server/src/lib/cli-tools/graceful-exit.js +16 -1
  369. package/dist/server/src/lib/cli-tools/index.js +4 -1
  370. package/dist/server/src/lib/cli-tools/install-hints.js +131 -0
  371. package/dist/server/src/lib/cli-tools/liveness-spec.js +18 -0
  372. package/dist/server/src/lib/cli-tools/manager.js +2 -0
  373. package/dist/server/src/lib/cli-tools/opencode.js +2 -2
  374. package/dist/server/src/lib/cli-tools/start-availability.js +18 -11
  375. package/dist/server/src/lib/cli-tools/types.js +6 -1
  376. package/dist/server/src/lib/cli-tools/vibe-local.js +2 -2
  377. package/dist/server/src/lib/cmate-parser.js +6 -0
  378. package/dist/server/src/lib/detection/cli-patterns.js +441 -8
  379. package/dist/server/src/lib/detection/opencode-modal-overlay.js +66 -100
  380. package/dist/server/src/lib/detection/selection-shape.js +341 -0
  381. package/dist/server/src/lib/detection/sgr-background.js +210 -0
  382. package/dist/server/src/lib/detection/status-detector.js +6 -0
  383. package/dist/server/src/lib/detection/status-reason.js +18 -0
  384. package/dist/server/src/lib/detection/tools/antigravity/detect.js +38 -15
  385. package/dist/server/src/lib/detection/tools/codex/cli-patterns.js +392 -0
  386. package/dist/server/src/lib/detection/tools/codex/detect.js +83 -2
  387. package/dist/server/src/lib/detection/tools/command-code/detect.js +159 -0
  388. package/dist/server/src/lib/detection/tools/registry.js +6 -4
  389. package/dist/server/src/lib/detection/tools/verified-against.js +59 -8
  390. package/dist/server/src/lib/detection/version-probes.js +28 -6
  391. package/dist/server/src/lib/hooks/sources/antigravity/history.js +77 -16
  392. package/dist/server/src/lib/hooks/sources/claude/history.js +94 -18
  393. package/dist/server/src/lib/hooks/sources/codex/history.js +84 -15
  394. package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +130 -17
  395. package/dist/server/src/lib/hooks/sources/codex/relay-install.js +145 -0
  396. package/dist/server/src/lib/hooks/sources/command-code/history.js +865 -0
  397. package/dist/server/src/lib/hooks/sources/command-code/hooks-config.js +390 -0
  398. package/dist/server/src/lib/hooks/sources/command-code/source.js +290 -0
  399. package/dist/server/src/lib/hooks/sources/command-code/tool-id.js +18 -0
  400. package/dist/server/src/lib/hooks/sources/command-code/transcript.js +604 -0
  401. package/dist/server/src/lib/hooks/sources/index.js +3 -1
  402. package/dist/server/src/lib/hooks/sources/opencode/history.js +57 -3
  403. package/dist/server/src/lib/hooks/sources/opencode/transcript.js +24 -28
  404. package/dist/server/src/lib/hooks/sources/registry.js +4 -0
  405. package/dist/server/src/lib/hooks/sources/turn-body.js +77 -19
  406. package/dist/server/src/lib/job-executor.js +12 -0
  407. package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +6 -0
  408. package/dist/server/src/lib/polling/response-checker.js +112 -7
  409. package/dist/server/src/lib/polling/structured-history-gate.js +15 -8
  410. package/dist/server/src/lib/realtime/types.js +15 -5
  411. package/dist/server/src/lib/response-cleaner.js +24 -1
  412. package/dist/server/src/lib/response-extractor.js +11 -0
  413. package/dist/server/src/lib/session/claude-executor.js +223 -4
  414. package/dist/server/src/lib/session/cli-session.js +52 -0
  415. package/dist/server/src/lib/session/tmux-session-surface.js +525 -0
  416. package/dist/server/src/lib/skills/compatibility-matrix.js +117 -1
  417. package/dist/server/src/lib/tmux/geometry-delegation.js +143 -0
  418. package/dist/server/src/lib/tmux/read-mode-pager.js +76 -10
  419. package/dist/server/src/lib/tmux/read-mode.js +44 -2
  420. package/dist/server/src/lib/tmux/session-hook-scripts.js +249 -0
  421. package/dist/server/src/lib/tmux/session-hooks.js +122 -0
  422. package/dist/server/src/lib/tmux/session-status-options.js +213 -0
  423. package/dist/server/src/lib/tmux/tmux.js +79 -2
  424. package/dist/server/src/lib/version-checker.js +110 -0
  425. package/dist/server/src/lib/ws-server.js +16 -10
  426. package/dist/server/src/types/agent-transcript.js +73 -1
  427. package/dist/server/src/types/terminal-keys.js +131 -3
  428. package/dist/types/terminal-keys.d.ts +100 -3
  429. package/dist/types/terminal-keys.d.ts.map +1 -1
  430. package/dist/types/terminal-keys.js +131 -3
  431. package/package.json +1 -1
  432. package/.next/server/chunks/3099.js +0 -5
  433. package/.next/server/chunks/7824.js +0 -1
  434. package/.next/server/chunks/848.js +0 -1
  435. package/.next/static/chunks/3916-0e929d089282dc5d.js +0 -1
  436. package/.next/static/chunks/6022-7a7d1760fc9814c7.js +0 -1
  437. package/.next/static/chunks/6381-147dde2a967e9458.js +0 -1
  438. package/.next/static/chunks/app/layout-6a26ea22d5cf03dc.js +0 -1
  439. package/.next/static/chunks/app/worktrees/[id]/page-2f73d69187936627.js +0 -1
  440. package/.next/static/css/23f9c40ecc458c77.css +0 -1
  441. /package/.next/static/{QXxQbXWoo7ldUyOcv7PCZ → 60mm4_QBuFtFTfo5UMyKU}/_buildManifest.js +0 -0
  442. /package/.next/static/{QXxQbXWoo7ldUyOcv7PCZ → 60mm4_QBuFtFTfo5UMyKU}/_ssgManifest.js +0 -0
@@ -0,0 +1,865 @@
1
+ "use strict";
2
+ /**
3
+ * Writing Command Code's own words into conversation history (Issue #2252,
4
+ * Epic #2249 Phase C).
5
+ *
6
+ * The fifth writer of `chat_messages` for an agent turn, on the terms
7
+ * `../opencode/history` established (#2041) and `../claude/history` generalised
8
+ * (#2121): the first writer is `lib/polling/response-checker`, which captures
9
+ * the pane and cleans it, and the two are mutually exclusive.
10
+ * `lib/polling/structured-history-gate` calls {@link captureCommandCodeTranscriptTurn}
11
+ * at the moment the scraped reply would be saved and saves it only if this
12
+ * returns false.
13
+ *
14
+ * ## Which file is this instance's, when the directory name is not computable
15
+ *
16
+ * This is the one place Command Code differs structurally from claude, and it is
17
+ * why Phase B (#2251) shipped `readCommandCodeTranscriptPath` for this Issue to
18
+ * pick up. claude's project directory is a pure function of `cwd`
19
+ * (`[^A-Za-z0-9] → -`, verified on 512 directories), so `../claude/history` can
20
+ * *compute* the path. Command Code's is `slugify(cwd)` from its own bundle, and
21
+ * the captured pair shows that slugify splits camel case as well —
22
+ * `…/MyCodeBranchDesk/probe` → `…-my-code-branch-desk-probe`. Reimplementing an
23
+ * unpublished slug is a rule that stops matching the day the tool changes it,
24
+ * and Epic #2249 決定 4 says not to.
25
+ *
26
+ * So the file is *found* rather than derived, in two steps:
27
+ *
28
+ * 1. **A hint, if the caller has one** — {@link acceptCommandCodeTranscriptHint}
29
+ * validates it against `~/.commandcode/projects` and `.jsonl` before anything
30
+ * opens it, which is what stops a value the agent printed naming
31
+ * `/etc/passwd`.
32
+ * 2. **A one-level scan of `~/.commandcode/projects/*` for
33
+ * `<session_id>.jsonl`.** The session id is a uuid the hooks already deliver
34
+ * on every event, and the directory holds one file per session, so this is
35
+ * exact rather than heuristic. Memoised per `(home, session)` and re-`stat`ed
36
+ * on every use, which is the shape `../codex/history` uses for its own scan.
37
+ *
38
+ * **The hint is not wired to the hook payload by this Issue, and that is
39
+ * deliberate rather than forgotten.** `transcript_path` arrives on the hook
40
+ * payload, and the only code that sees a raw payload *and* knows which instance
41
+ * sent it is `src/app/api/hooks/agent-event/route.ts`, which is outside this
42
+ * Issue's scope. Phase B says as much on {@link readCommandCodeTranscriptPath}.
43
+ * Rather than leave the reader unable to find its file until a route changes,
44
+ * step 2 makes the pointer sufficient on its own — and step 1 is implemented,
45
+ * tested and used for `capture.transcriptPathHint`, so the day the payload is
46
+ * plumbed the change is one line at the producer.
47
+ *
48
+ * ## What Issue #2304 measured about that seam
49
+ *
50
+ * The two halves are now joined by a test rather than by a paragraph.
51
+ * `tests/unit/hooks/sources/command-code-transcript-path-2304.test.ts` runs the
52
+ * real captured payloads through {@link readCommandCodeTranscriptPath} into
53
+ * `transcriptPathHint` and asserts the turn that comes out — against a **decoy**
54
+ * directory named exactly what claude's `[^A-Za-z0-9] → -` rule would produce
55
+ * for the same `cwd`, holding the same session id and a newer mtime. A reader
56
+ * that computed the slug and a reader that only scanned both write the decoy;
57
+ * only one that follows the hook's own path writes the right turn. The control
58
+ * is asserted too: take the hint away and the scan really does resolve to the
59
+ * decoy.
60
+ *
61
+ * Three live facts from that capture (Command Code 1.49.0, 2026-09-04):
62
+ *
63
+ * - **`transcript_path` is on all four events with one value for the session**,
64
+ * so a receiver may latch it off whichever it sees first. `cwd` is on all
65
+ * four as well — the temptation to compute the slug is *in the payload*, and
66
+ * Epic #2249 決定 4 is the answer to it.
67
+ * - **The slug still splits camel case**: `…/cwd/MyCodeBranchDesk/probe` →
68
+ * `…-cwd-my-code-branch-desk-probe`, where claude's rule would answer
69
+ * `-…-cwd-MyCodeBranchDesk-probe`. Two differences, both pinned by name.
70
+ * - **1.49.0 writes two new files beside the transcript**:
71
+ * `<session_id>.meta.json` and `<session_id>.checkpoints.jsonl`.
72
+ * {@link findCommandCodeTranscriptPath} matches `<session_id>.jsonl`
73
+ * exactly, so neither is a candidate — an `endsWith('.jsonl')` scan would
74
+ * have picked the checkpoints file up. It also passes
75
+ * {@link acceptCommandCodeTranscriptHint} (absolute, under the root,
76
+ * `.jsonl`), and the reason that is still safe is that its rows carry no
77
+ * `type`, so the parser declines all of them and this returns false. No
78
+ * fourth condition on the accept.
79
+ *
80
+ * ## Nothing here throws
81
+ *
82
+ * Same contract as its four siblings, and one more reason: a throw here would
83
+ * propagate into the poller's save path and could cost the scraped reply *as
84
+ * well as* the structured one. The database imports are dynamic so that
85
+ * `better-sqlite3` does not enter the module graph of everything that imports
86
+ * `@/lib/hooks/sources`.
87
+ *
88
+ * @module lib/hooks/sources/command-code/history
89
+ */
90
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
91
+ if (k2 === undefined) k2 = k;
92
+ var desc = Object.getOwnPropertyDescriptor(m, k);
93
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
94
+ desc = { enumerable: true, get: function() { return m[k]; } };
95
+ }
96
+ Object.defineProperty(o, k2, desc);
97
+ }) : (function(o, m, k, k2) {
98
+ if (k2 === undefined) k2 = k;
99
+ o[k2] = m[k];
100
+ }));
101
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
102
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
103
+ }) : function(o, v) {
104
+ o["default"] = v;
105
+ });
106
+ var __importStar = (this && this.__importStar) || (function () {
107
+ var ownKeys = function(o) {
108
+ ownKeys = Object.getOwnPropertyNames || function (o) {
109
+ var ar = [];
110
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
111
+ return ar;
112
+ };
113
+ return ownKeys(o);
114
+ };
115
+ return function (mod) {
116
+ if (mod && mod.__esModule) return mod;
117
+ var result = {};
118
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
119
+ __setModuleDefault(result, mod);
120
+ return result;
121
+ };
122
+ })();
123
+ Object.defineProperty(exports, "__esModule", { value: true });
124
+ exports.COMMAND_CODE_TURN_RECHECK_LIMIT = exports.COMMAND_CODE_TRANSCRIPT_TAIL_BYTES = void 0;
125
+ exports.resetCommandCodeTranscriptSessions = resetCommandCodeTranscriptSessions;
126
+ exports.resolveCommandCodeSessionId = resolveCommandCodeSessionId;
127
+ exports.commandCodeProjectsRoot = commandCodeProjectsRoot;
128
+ exports.acceptCommandCodeTranscriptHint = acceptCommandCodeTranscriptHint;
129
+ exports.findCommandCodeTranscriptPath = findCommandCodeTranscriptPath;
130
+ exports.captureCommandCodeTranscriptTurn = captureCommandCodeTranscriptTurn;
131
+ exports.resolveCommandCodeTranscriptPath = resolveCommandCodeTranscriptPath;
132
+ const promises_1 = require("fs/promises");
133
+ const os_1 = require("os");
134
+ const path_1 = require("path");
135
+ const auto_yes_state_1 = require("../../../../lib/auto-yes-state");
136
+ const transcript_tail_1 = require("../../../../lib/history/transcript-tail");
137
+ const user_turn_recorder_1 = require("../../../../lib/history/user-turn-recorder");
138
+ const logger_1 = require("../../../../lib/logger");
139
+ const agent_transcript_1 = require("../../../../types/agent-transcript");
140
+ const transcript_1 = require("./transcript");
141
+ const logger = (0, logger_1.createLogger)('lib/hooks/sources/command-code/history');
142
+ /**
143
+ * How much of the transcript's tail is read.
144
+ *
145
+ * The shared bound. Command Code's own files are small next to codex's — the
146
+ * captured sessions are single-digit kilobytes — but the file grows for the life
147
+ * of a session and the window is what makes "only turns whose prompt record is
148
+ * inside it are ever written" true, which is what stops a turn the window opened
149
+ * halfway through being written from its middle.
150
+ */
151
+ exports.COMMAND_CODE_TRANSCRIPT_TAIL_BYTES = transcript_tail_1.TRANSCRIPT_TAIL_BYTES;
152
+ /**
153
+ * The shape a Command Code session id has, and the only shape this reader will
154
+ * look up.
155
+ *
156
+ * A uuid, which is what the tool mints (`newSession` calls its uuid generator).
157
+ * The check is not cosmetic: the value reaches a file-name comparison, and an id
158
+ * carrying `/` or `..` would otherwise be a path expression. Everything that
159
+ * fails it is treated as "no pointer", which falls through to the scraper.
160
+ */
161
+ const COMMAND_CODE_SESSION_ID_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
162
+ /**
163
+ * The last session id seen for each instance.
164
+ *
165
+ * On `globalThis` for the reason every shared map in this subsystem is (#1736):
166
+ * under `next dev` the poller's bundle and the hook receiver's bundle would each
167
+ * get a private copy of a module-scoped map, one would write and the other would
168
+ * read, and every lookup would answer null with no error at all.
169
+ *
170
+ * A latch and not a cache: `getLastAgentEvent` holds only the newest event, and
171
+ * Command Code sends `session_id` on all four of its events — but an event that
172
+ * did not would otherwise blank the pointer mid-session.
173
+ */
174
+ const sessionPointers = (globalThis.__commandCodeTranscriptSessions ??= new Map());
175
+ /**
176
+ * Where each session id's transcript was found.
177
+ *
178
+ * Keyed by `<home>\0<sessionId>`, and a memo rather than a source of truth:
179
+ * every hit is re-`stat`ed before it is used. It exists because the lookup is a
180
+ * directory scan — the project directory name is `slugify(cwd)` and not
181
+ * derivable — and a scan on every finished turn is not something to do twice.
182
+ */
183
+ const transcriptPaths = (globalThis.__commandCodeTranscriptPaths ??= new Map());
184
+ function keyOf(target) {
185
+ return (0, auto_yes_state_1.buildCompositeKey)(target.worktreeId, target.cliToolId, target.instanceId);
186
+ }
187
+ /** Forget every instance's pointer and every memoised path. Test seam. */
188
+ function resetCommandCodeTranscriptSessions() {
189
+ sessionPointers.clear();
190
+ transcriptPaths.clear();
191
+ }
192
+ /**
193
+ * The session id this instance's transcript is under, or null.
194
+ *
195
+ * Reads the structured event state first and falls back to the latched value.
196
+ * The import is dynamic so that `agent-event-state`'s module graph does not
197
+ * become a static dependency of the poller.
198
+ *
199
+ * **There is deliberately no fallback below this.** The obvious one — take the
200
+ * newest transcript whose `cwd` is this worktree — is wrong in the case the
201
+ * feature exists for: `command-code` and `command-code-2` in one worktree share
202
+ * a `cwd` and therefore a project directory, so the newest file is whichever of
203
+ * them answered last, and the primary's turn would be filed under the second's
204
+ * conversation. No pointer means the scraper keeps being the only record, which
205
+ * is merely the status quo.
206
+ */
207
+ async function resolveCommandCodeSessionId(target) {
208
+ const key = keyOf(target);
209
+ try {
210
+ const { getLastAgentEvent } = await Promise.resolve().then(() => __importStar(require('../../../../lib/session/agent-event-state')));
211
+ const sessionId = getLastAgentEvent(target.worktreeId, target.cliToolId, target.instanceId)?.sessionId;
212
+ if (typeof sessionId === 'string' && sessionId.length > 0) {
213
+ sessionPointers.set(key, sessionId);
214
+ return sessionId;
215
+ }
216
+ }
217
+ catch (error) {
218
+ // A state module that cannot be reached is one that knows no session id.
219
+ logger.debug('command-code-transcript-session-lookup-failed', {
220
+ worktreeId: target.worktreeId,
221
+ instanceId: target.instanceId ?? target.cliToolId,
222
+ error: error instanceof Error ? error.message : String(error),
223
+ });
224
+ }
225
+ return sessionPointers.get(key) ?? null;
226
+ }
227
+ /** `<home>/.commandcode/projects`. */
228
+ function commandCodeProjectsRoot(homeDir) {
229
+ return (0, path_1.join)(homeDir, ...transcript_1.COMMAND_CODE_PROJECTS_DIR_SEGMENTS);
230
+ }
231
+ /**
232
+ * A transcript path named by something other than this module, accepted only if
233
+ * it really is one.
234
+ *
235
+ * The value this exists for is the hook payload's `transcript_path`, which is a
236
+ * string an agent process chose. Three conditions, all necessary and all the
237
+ * ones `acceptClaudeTranscriptHint` and `acceptCodexRolloutPath` apply: it must
238
+ * be under `~/.commandcode/projects`, so a crafted value cannot make this open
239
+ * `/etc/passwd`; it must end in `.jsonl`; and it must carry no NUL.
240
+ *
241
+ * Containment is checked on the **resolved** path so that `..` cannot climb out,
242
+ * and `resolve` is safe to use here — unlike in `validateHookCwd`, where the
243
+ * value is echoed back — because the resolved string is the only thing that is
244
+ * ever used.
245
+ *
246
+ * @returns The resolved path, or null when it is not acceptable
247
+ */
248
+ function acceptCommandCodeTranscriptHint(homeDir, hint) {
249
+ if (!hint.endsWith(transcript_1.COMMAND_CODE_TRANSCRIPT_EXTENSION))
250
+ return null;
251
+ if (hint.includes('\0'))
252
+ return null;
253
+ const root = (0, path_1.resolve)(commandCodeProjectsRoot(homeDir));
254
+ const resolved = (0, path_1.resolve)(hint);
255
+ if (resolved !== root && !resolved.startsWith(root + path_1.sep))
256
+ return null;
257
+ return resolved;
258
+ }
259
+ /**
260
+ * Find this session's transcript under `~/.commandcode/projects`.
261
+ *
262
+ * One level: the layout is `projects/<slug>/<session_id>.jsonl` and the slug is
263
+ * the only unknown, so every child directory is asked whether it holds this
264
+ * session's file. Directories are visited newest-modified first, because the
265
+ * session being asked about is almost always the one whose project directory was
266
+ * touched last — which makes the common case one `stat` rather than N.
267
+ *
268
+ * Never throws: a root that does not exist is a session this reader cannot see,
269
+ * which is the same answer as a session with no file.
270
+ *
271
+ * @returns The absolute path, or null when nothing under the root matches
272
+ */
273
+ async function findCommandCodeTranscriptPath(homeDir, sessionId) {
274
+ if (!COMMAND_CODE_SESSION_ID_PATTERN.test(sessionId))
275
+ return null;
276
+ const root = commandCodeProjectsRoot(homeDir);
277
+ let entries;
278
+ try {
279
+ entries = await (0, promises_1.readdir)(root, { withFileTypes: true });
280
+ }
281
+ catch {
282
+ return null;
283
+ }
284
+ const directories = [];
285
+ for (const entry of entries) {
286
+ if (!entry.isDirectory())
287
+ continue;
288
+ let modifiedAt = 0;
289
+ try {
290
+ modifiedAt = (await (0, promises_1.stat)((0, path_1.join)(root, entry.name))).mtimeMs;
291
+ }
292
+ catch {
293
+ // A directory that vanished between `readdir` and `stat` is one with no
294
+ // file in it as far as this scan is concerned; it still gets asked below,
295
+ // where the failure costs one `stat` and nothing else.
296
+ }
297
+ directories.push({ name: entry.name, modifiedAt });
298
+ }
299
+ directories.sort((a, b) => b.modifiedAt - a.modifiedAt);
300
+ const fileName = `${sessionId}${transcript_1.COMMAND_CODE_TRANSCRIPT_EXTENSION}`;
301
+ for (const directory of directories) {
302
+ const candidate = (0, path_1.join)(root, directory.name, fileName);
303
+ if (await isReadableFile(candidate))
304
+ return candidate;
305
+ }
306
+ return null;
307
+ }
308
+ /**
309
+ * Read this instance's unwritten turns out of its transcript and write them.
310
+ *
311
+ * **Every turn in the window that is not already a row**, oldest first, which is
312
+ * the shape Issue #2246 gave the pull readers; the reasoning is written out once
313
+ * on `../claude/history`'s `captureClaudeTranscriptTurn` and is not repeated
314
+ * here. The short version: this reader only runs when something judges a turn
315
+ * finished, so a missed judgement used to lose a turn permanently — by the next
316
+ * judgement, "the newest turn" had moved on.
317
+ *
318
+ * The return value is the poller's instruction. **True** means `chat_messages`
319
+ * holds this instance's newest turn as Markdown — because this call wrote it, or
320
+ * because an earlier read of the same finished turn already did — and the scrape
321
+ * must be dropped. **False** means it does not, for any reason at all: no
322
+ * session pointer, no file, an unreadable file, a window with no prompt in it,
323
+ * or a turn the agent has not finished writing. Everything that can go wrong
324
+ * answers false, which is the fail-open the acceptance criteria require.
325
+ *
326
+ * Never throws.
327
+ *
328
+ * @param target - The instance whose turn just ended
329
+ * @returns Whether History now holds this instance's newest turn as Markdown
330
+ */
331
+ async function captureCommandCodeTranscriptTurn(target, capture) {
332
+ const instanceId = target.instanceId ?? target.cliToolId;
333
+ try {
334
+ const homeDir = capture.commandCodeHome ?? (0, os_1.homedir)();
335
+ const sessionId = await resolveCommandCodeSessionId(target);
336
+ const path = await locateCommandCodeTranscript(homeDir, capture, sessionId);
337
+ if (!path) {
338
+ logger.debug('command-code-transcript-unavailable', {
339
+ worktreeId: target.worktreeId,
340
+ instanceId,
341
+ sessionId,
342
+ reason: sessionId ? 'no-file' : 'no-session-pointer',
343
+ });
344
+ return false;
345
+ }
346
+ const text = await (0, transcript_tail_1.readTranscriptTail)(path);
347
+ if (text === null)
348
+ return false;
349
+ const parsed = (0, transcript_1.parseCommandCodeTranscript)(text);
350
+ const built = (0, transcript_1.buildCommandCodeTurns)(parsed.records, parsed.sessionId ?? sessionId ?? '');
351
+ if (built.turns.length === 0) {
352
+ logger.info('command-code-transcript-no-turn', {
353
+ worktreeId: target.worktreeId,
354
+ instanceId,
355
+ path,
356
+ records: parsed.records.length,
357
+ malformedLines: parsed.malformedLines,
358
+ orphanedAssistantRecords: built.orphanedAssistantRecords,
359
+ });
360
+ return false;
361
+ }
362
+ if (parsed.malformedLines > 0 ||
363
+ built.orphanedAssistantRecords > 0 ||
364
+ parsed.parentSession !== null) {
365
+ // All three are expected in small numbers and all three are the kind of
366
+ // thing that must be visible when they stop being small: a fragment at the
367
+ // tail of a file being appended to, the head of the window landing
368
+ // mid-turn, and — the one Issue #2252 scopes out by name — a session that
369
+ // was forked from another, whose records this reader makes no attempt to
370
+ // reattach to their original conversation.
371
+ logger.info('command-code-transcript-partial-read', {
372
+ worktreeId: target.worktreeId,
373
+ instanceId,
374
+ path,
375
+ malformedLines: parsed.malformedLines,
376
+ orphanedAssistantRecords: built.orphanedAssistantRecords,
377
+ unresolvedParentRecords: built.unresolvedParentRecords,
378
+ nonMessageRecords: built.nonMessageRecords,
379
+ parentSession: parsed.parentSession,
380
+ });
381
+ }
382
+ const pending = await selectUnwrittenCommandCodeTurns(target, built.turns);
383
+ // Before anything is written: the rows that are already there (#2264). This
384
+ // is deliberately ahead of the early return below, because "the newest turn
385
+ // is already a row" is the state a row saved from a half-flushed turn is
386
+ // stuck in — the reader answers true and does nothing while the transcript
387
+ // beside it holds the missing paragraph.
388
+ await refreshCommandCodeTurnRows(target, built.turns
389
+ .slice(0, built.turns.length - pending.turns.length)
390
+ .slice(-exports.COMMAND_CODE_TURN_RECHECK_LIMIT), path);
391
+ if (pending.turns.length === 0) {
392
+ logger.debug('command-code-transcript-turns-already-saved', {
393
+ worktreeId: target.worktreeId,
394
+ instanceId,
395
+ turnsInWindow: built.turns.length,
396
+ });
397
+ return true;
398
+ }
399
+ if (pending.turns.length > 1) {
400
+ logger.info('command-code-transcript-backfilling-turns', {
401
+ worktreeId: target.worktreeId,
402
+ instanceId,
403
+ path,
404
+ pendingTurns: pending.turns.length,
405
+ turnsInWindow: built.turns.length,
406
+ anchored: pending.anchored,
407
+ });
408
+ }
409
+ // Oldest first, and the ordering is load-bearing twice over: History sorts
410
+ // by timestamp, and each turn's `/send` row is adopted against the previous
411
+ // turn's start, so a later turn must not get to claim a row an earlier one
412
+ // is about to ask for.
413
+ //
414
+ // Every prompt row is recorded before any reply is written (Issue #2273).
415
+ // That is what lets a reply be dated at its turn's END without overtaking
416
+ // the NEXT turn's prompt: a queued `/send` row is written while this turn is
417
+ // still running, so its instant is only knowable once that row has been
418
+ // recorded. With one turn pending — the ordinary case — this is the single
419
+ // call it always was.
420
+ const userRows = [];
421
+ for (let index = 0; index < pending.turns.length; index += 1) {
422
+ const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
423
+ userRows.push(await recordCommandCodeUserTurn(target, pending.turns[index], previousStartedAt));
424
+ }
425
+ const lastRecordAt = lastCommandCodeAssistantRecordAt(parsed.records);
426
+ let captured = false;
427
+ for (let index = 0; index < pending.turns.length; index += 1) {
428
+ const turn = pending.turns[index];
429
+ captured = await writeCommandCodeTurn(target, turn, (0, transcript_1.renderCommandCodeTurn)(turn), resolveAssistantTimestampMs(turn, userRows[index], lastRecordAt.get(turn.promptId) ?? 0, nextTurnOpensAt(pending.turns, userRows, index)), path);
430
+ }
431
+ return captured;
432
+ }
433
+ catch (error) {
434
+ logger.error('command-code-transcript-capture-failed', {
435
+ worktreeId: target.worktreeId,
436
+ instanceId,
437
+ error: error instanceof Error ? error.message : String(error),
438
+ });
439
+ return false;
440
+ }
441
+ }
442
+ /**
443
+ * The turns {@link captureCommandCodeTranscriptTurn} still has to write (#2246).
444
+ *
445
+ * The search runs **backwards from the newest turn** and stops at the first one
446
+ * that is already a row. That turn is the *anchor*, and everything after it is
447
+ * pending. The comparison is the transcript's own record order and never a
448
+ * timestamp — a turn's assistant row is dated one millisecond after its user row
449
+ * ({@link resolveAssistantTimestampMs}), so the rows carry no ordering that
450
+ * could be trusted for this.
451
+ *
452
+ * A window with no anchor answers with the newest turn alone, which is #2121's
453
+ * behaviour and the right one: the earlier turns really are the scraper's, and
454
+ * there is no evidence in the window that says otherwise.
455
+ */
456
+ async function selectUnwrittenCommandCodeTurns(target, turns) {
457
+ const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
458
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
459
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
460
+ ]);
461
+ const db = getDbInstance();
462
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
463
+ const requestId = (0, agent_transcript_1.commandCodeTurnRequestId)(turns[index].promptId);
464
+ if (!findMessageByRequestId(db, target.worktreeId, requestId))
465
+ continue;
466
+ return {
467
+ turns: turns.slice(index + 1),
468
+ previousStartedAt: turns[index].startedAt,
469
+ anchored: true,
470
+ };
471
+ }
472
+ return {
473
+ turns: turns.slice(-1),
474
+ previousStartedAt: turns.length > 1 ? turns[turns.length - 2].startedAt : 0,
475
+ anchored: false,
476
+ };
477
+ }
478
+ /**
479
+ * The transcript file this instance would be read from, or null (Issue #2246).
480
+ *
481
+ * The same two steps {@link captureCommandCodeTranscriptTurn} opens with, asked
482
+ * without reading or writing anything. The Stop receiver uses it to decide
483
+ * whether waiting half a second and asking again could possibly help: a session
484
+ * with no transcript to name will not have grown one by then.
485
+ *
486
+ * Never throws.
487
+ */
488
+ async function resolveCommandCodeTranscriptPath(target, capture) {
489
+ try {
490
+ const homeDir = capture.commandCodeHome ?? (0, os_1.homedir)();
491
+ return await locateCommandCodeTranscript(homeDir, capture, await resolveCommandCodeSessionId(target));
492
+ }
493
+ catch {
494
+ return null;
495
+ }
496
+ }
497
+ /**
498
+ * The transcript file for this instance, or null.
499
+ *
500
+ * The hint first because it names the file outright, the scan second. Both are
501
+ * checked against the filesystem rather than trusted, and the memo is re-`stat`ed
502
+ * for the same reason: "the path we would use" and "the path that exists" differ
503
+ * for every session that has been deleted, renamed or never persisted at all
504
+ * (`--no-session` writes nothing here, which is measured).
505
+ */
506
+ async function locateCommandCodeTranscript(homeDir, capture, sessionId) {
507
+ const hint = capture.transcriptPathHint;
508
+ if (typeof hint === 'string' && hint.length > 0) {
509
+ const accepted = acceptCommandCodeTranscriptHint(homeDir, hint);
510
+ if (accepted && (await isReadableFile(accepted)))
511
+ return accepted;
512
+ }
513
+ if (!sessionId)
514
+ return null;
515
+ const memoKey = `${homeDir}\0${sessionId}`;
516
+ const memoised = transcriptPaths.get(memoKey);
517
+ if (memoised && (await isReadableFile(memoised)))
518
+ return memoised;
519
+ const found = await findCommandCodeTranscriptPath(homeDir, sessionId);
520
+ if (found)
521
+ transcriptPaths.set(memoKey, found);
522
+ else
523
+ transcriptPaths.delete(memoKey);
524
+ return found;
525
+ }
526
+ async function isReadableFile(path) {
527
+ try {
528
+ return (await (0, promises_1.stat)(path)).isFile();
529
+ }
530
+ catch {
531
+ return false;
532
+ }
533
+ }
534
+ /**
535
+ * Record the prompt this turn answers, before the reply is written (#2196).
536
+ *
537
+ * Ordering is the reason this is a separate call and not a branch inside
538
+ * {@link writeCommandCodeTurn}: the user row has to exist *before* the assistant
539
+ * row so that {@link resolveAssistantTimestampMs} can put the reply after it,
540
+ * and so that a browser watching the broadcast sees a prompt appear and then an
541
+ * answer rather than the other way round.
542
+ *
543
+ * A turn whose prompt was not the operator's — a `steering` or `followup`
544
+ * message the agent loop appended; see `isCommandCodeOperatorPromptRecord` — is
545
+ * skipped and said so in the log. The assistant row is still written: the agent
546
+ * really did reply.
547
+ *
548
+ * Never throws; `recordUserTurn` reports its failures in the return value.
549
+ *
550
+ * @param previousStartedAt - When the turn before this one opened, or 0. Widens
551
+ * `/send` adoption backwards to that instant (#2246); never narrows.
552
+ */
553
+ async function recordCommandCodeUserTurn(target, turn, previousStartedAt = 0) {
554
+ const instanceId = target.instanceId ?? target.cliToolId;
555
+ if (!turn.promptIsOperatorInput) {
556
+ logger.debug('command-code-transcript-user-turn-skipped', {
557
+ worktreeId: target.worktreeId,
558
+ instanceId,
559
+ promptId: turn.promptId,
560
+ reason: 'not-operator-input',
561
+ });
562
+ return { outcome: 'skipped', messageId: null, timestampMs: null };
563
+ }
564
+ const adoption = previousStartedAt > 0 ? { adoptionFromMs: previousStartedAt } : {};
565
+ const recorded = await (0, user_turn_recorder_1.recordUserTurn)(target, (0, agent_transcript_1.commandCodePromptRequestId)(turn.promptId), turn.promptText, turn.startedAt, adoption);
566
+ if (recorded.outcome === 'failed') {
567
+ logger.warn('command-code-transcript-user-turn-failed', {
568
+ worktreeId: target.worktreeId,
569
+ instanceId,
570
+ sessionId: turn.sessionId,
571
+ promptId: turn.promptId,
572
+ });
573
+ }
574
+ return recorded;
575
+ }
576
+ /**
577
+ * When the assistant row for this turn is dated.
578
+ *
579
+ * **The turn's LAST assistant record, not its prompt (Issue #2273).** #2252
580
+ * dated the reply by the prompt record's own clock — `meta.createdAt`, the
581
+ * instant the message was made rather than the instant Command Code's buffered
582
+ * store appended it — and #2196 moved it one millisecond on so that
583
+ * `groupMessagesIntoPairs`, which orders by timestamp and nothing else, could
584
+ * never return the answer above the question. Both properties survive:
585
+ * `earliest` is a floor this never goes under.
586
+ *
587
+ * What neither accounted for is the row that lands BETWEEN the prompt and the
588
+ * reply. A tool approval is written when the dialog appears, seconds into the
589
+ * turn, and the chat surface orders its rows by timestamp — so a reply dated at
590
+ * the turn's start draws ABOVE an approval that really happened before it. The
591
+ * turn's last assistant record is the moment the reply was finished, so
592
+ * everything the turn produced on the way sorts before it.
593
+ *
594
+ * `nextTurnOpensAt` is the ceiling. The next turn's prompt row may be a `/send`
595
+ * row written while THIS turn was still running — a queued prompt — and a reply
596
+ * that overtook it would be paired with the wrong question.
597
+ *
598
+ * @param lastRecordAt - Epoch ms of the turn's last assistant record, or 0
599
+ * @param nextTurnOpensAt - Epoch ms of the next turn's user row, or null when
600
+ * this is the newest turn in the window
601
+ */
602
+ function resolveAssistantTimestampMs(turn, userRow, lastRecordAt = 0, nextTurnOpensAt = null) {
603
+ const earliest = userRow.timestampMs === null
604
+ ? turn.startedAt
605
+ : Math.max(turn.startedAt, userRow.timestampMs + 1);
606
+ const latest = nextTurnOpensAt === null ? Number.POSITIVE_INFINITY : nextTurnOpensAt - 1;
607
+ return Math.max(earliest, Math.min(lastRecordAt, latest));
608
+ }
609
+ /**
610
+ * When each turn's last assistant record was written (Issue #2273).
611
+ *
612
+ * Keyed by `promptId`, which is the turn key {@link buildCommandCodeTurns} uses,
613
+ * and walked over the same records with the same rules — the session header and
614
+ * every non-`message` entry skipped, a prompt record opening a turn — so the two
615
+ * passes cannot disagree about which turn a record belongs to. It is a second
616
+ * pass rather than a field on the accumulator because `./transcript` is not this
617
+ * Issue's to change.
618
+ *
619
+ * Only `assistant` records count. A turn's `user` records are its tool RESULTS,
620
+ * and the last of those is a step the agent took before it had finished
621
+ * answering; the reply is dated by the agent's last word.
622
+ *
623
+ * @returns `promptId` → epoch ms; absent for a turn with no timestamped
624
+ * assistant record
625
+ */
626
+ function lastCommandCodeAssistantRecordAt(records) {
627
+ const at = new Map();
628
+ let key = null;
629
+ for (const record of records) {
630
+ if (record.sessionId)
631
+ continue;
632
+ if (record.type !== transcript_1.COMMAND_CODE_MESSAGE_RECORD_TYPE)
633
+ continue;
634
+ if ((0, transcript_1.isCommandCodePromptRecord)(record)) {
635
+ key = record.id;
636
+ continue;
637
+ }
638
+ if (record.role !== 'assistant' || key === null || record.timestampMs === null)
639
+ continue;
640
+ if (record.timestampMs > (at.get(key) ?? 0))
641
+ at.set(key, record.timestampMs);
642
+ }
643
+ return at;
644
+ }
645
+ /**
646
+ * The instant the next pending turn's prompt row carries, or null (Issue #2273).
647
+ *
648
+ * The user row's own timestamp when there is one, because that is what History
649
+ * sorts on and it can be EARLIER than the turn's start — an adopted `/send` row
650
+ * was written when CommandMate handed the text to the pane, which for a queued
651
+ * prompt is while the previous turn was still running. The turn's start is the
652
+ * fallback for a turn that produced no row at all.
653
+ */
654
+ function nextTurnOpensAt(turns, userRows, index) {
655
+ const next = turns[index + 1];
656
+ if (!next)
657
+ return null;
658
+ return userRows[index + 1]?.timestampMs ?? next.startedAt;
659
+ }
660
+ /**
661
+ * How many already-written turns are re-rendered and compared (Issue #2264).
662
+ *
663
+ * The repair half of #2264 has to look at rows the reader has **already**
664
+ * written, which is the one thing the anchor rule deliberately does not do — so
665
+ * it is bounded here rather than by the window. Three, the same as claude's:
666
+ * the poller runs every two seconds and re-rendering the whole window on every
667
+ * tick would turn a repair into the most expensive thing the poll does, while a
668
+ * row that is going to grow grows within a second of being written.
669
+ */
670
+ exports.COMMAND_CODE_TURN_RECHECK_LIMIT = 3;
671
+ /**
672
+ * Replace a saved row whose body has since grown (Issue #2264).
673
+ *
674
+ * The second half of the fix, and the half that repairs what the first half only
675
+ * stops happening again. A row keyed `command-code-turn:<id>` is written once and
676
+ * every later read answers "already saved", so a row saved from a turn whose last
677
+ * record had not been flushed yet would be frozen short — and the scrape that
678
+ * could have replaced it is suppressed by that same idempotency answer.
679
+ *
680
+ * **Strictly longer, never merely different.** Equality is the ordinary case and
681
+ * must cost nothing, and a body that got *shorter* between two reads is not a
682
+ * turn that grew — it is a window that slid, or a truncation marker — and
683
+ * overwriting a full reply with a shorter one is the one outcome worse than the
684
+ * bug. Longer implies different, so one comparison covers both halves.
685
+ *
686
+ * `message_updated`, never `message`: the row already existed and was already
687
+ * delivered when it was created (#2195), so a client that appended instead of
688
+ * replacing would show the reply twice.
689
+ *
690
+ * @param existing - The row `findMessageByRequestId` answered with
691
+ * @returns Whether the row was replaced
692
+ */
693
+ async function growCommandCodeTurnRow(target, existing, rendered, path) {
694
+ const instanceId = target.instanceId ?? target.cliToolId;
695
+ const previousLength = existing.content.length;
696
+ if (rendered.body.length <= previousLength)
697
+ return false;
698
+ const [{ getDbInstance }, { updateMessageContent }, { broadcastMessage }] = await Promise.all([
699
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
700
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
701
+ Promise.resolve().then(() => __importStar(require('../../../../lib/ws-server'))),
702
+ ]);
703
+ updateMessageContent(getDbInstance(), existing.id, rendered.body);
704
+ broadcastMessage('message_updated', {
705
+ worktreeId: target.worktreeId,
706
+ message: { ...existing, content: rendered.body },
707
+ });
708
+ logger.info('command-code-transcript-turn-updated', {
709
+ worktreeId: target.worktreeId,
710
+ instanceId,
711
+ sessionId: rendered.sessionId,
712
+ requestId: existing.requestId,
713
+ path,
714
+ previousLength,
715
+ bodyLength: rendered.body.length,
716
+ textBlocks: rendered.textBlocks,
717
+ toolBlocks: rendered.toolBlocks,
718
+ });
719
+ return true;
720
+ }
721
+ /**
722
+ * Re-read the newest already-written turns and grow the short ones (#2264).
723
+ *
724
+ * Runs before the pending turns are written and independently of whether there
725
+ * are any — the case it exists for is precisely the one
726
+ * {@link captureCommandCodeTranscriptTurn} would otherwise return `true` from
727
+ * without doing anything.
728
+ *
729
+ * Turns that are still open are skipped rather than compared. Their body is by
730
+ * definition not the final one, and a repair that raced the agent would rewrite
731
+ * the row on every poll of a long turn.
732
+ *
733
+ * The database is asked before the turn is rendered, so a candidate with no row
734
+ * — every candidate, in a session this reader has never written to — costs one
735
+ * indexed lookup and no Markdown.
736
+ *
737
+ * @param candidates - Already-written turns, oldest first, at most
738
+ * {@link COMMAND_CODE_TURN_RECHECK_LIMIT}
739
+ * @returns How many rows were replaced
740
+ */
741
+ async function refreshCommandCodeTurnRows(target, candidates, path) {
742
+ if (candidates.length === 0)
743
+ return 0;
744
+ const [{ getDbInstance }, { findMessageByRequestId }] = await Promise.all([
745
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
746
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
747
+ ]);
748
+ const db = getDbInstance();
749
+ let updated = 0;
750
+ for (const turn of candidates) {
751
+ if (!(0, transcript_1.isCommandCodeTurnWritable)(turn))
752
+ continue;
753
+ const existing = findMessageByRequestId(db, target.worktreeId, (0, agent_transcript_1.commandCodeTurnRequestId)(turn.promptId));
754
+ if (!existing)
755
+ continue;
756
+ if (await growCommandCodeTurnRow(target, existing, (0, transcript_1.renderCommandCodeTurn)(turn), path)) {
757
+ updated += 1;
758
+ }
759
+ }
760
+ return updated;
761
+ }
762
+ /**
763
+ * Write one rendered turn, unless it is already there.
764
+ *
765
+ * `findMessageByRequestId` is both the idempotency check and the reason a repeat
766
+ * read does not duplicate the row: the id is derived from the prompt record's
767
+ * `id`, which does not change between reads of the same file.
768
+ *
769
+ * Answering **true** for a turn that was already saved is deliberate. It means
770
+ * "History holds this turn as Markdown", which is exactly what the poller needs
771
+ * to know — a second poll of the same finished turn must not save the pane's
772
+ * copy on top of the row this path wrote for it.
773
+ *
774
+ * @returns Whether History holds this turn as the agent's own Markdown
775
+ */
776
+ async function writeCommandCodeTurn(target, turn, rendered, timestampMs, path) {
777
+ const instanceId = target.instanceId ?? target.cliToolId;
778
+ if (!(0, transcript_1.isCommandCodeTurnWritable)(turn)) {
779
+ // The agent's last word in this turn reached for a tool and no later prompt
780
+ // has taken over, so what is in the file is a turn in progress. Writing it
781
+ // would put a reply with its last paragraph missing into History
782
+ // **permanently** — the row is keyed on the prompt's `id`, so every later
783
+ // read finds it and answers "already saved" — and the emptiness guard below
784
+ // cannot see the difference, because a turn cut off after its tool calls
785
+ // renders a *non-empty* body. That is Issue #2264, reproduced here before it
786
+ // could be reported: the measured `-p --session` resume leaves exactly this
787
+ // shape on disk, a prompt and a tool call with the reply never flushed.
788
+ //
789
+ // Handing it back to the scraper costs the Markdown rendering for this turn
790
+ // and nothing else: the Stop receiver asks again after a short delay, and
791
+ // the poller asks again when the pane returns to the composer.
792
+ logger.info('command-code-transcript-turn-open', {
793
+ worktreeId: target.worktreeId,
794
+ instanceId,
795
+ sessionId: rendered.sessionId,
796
+ promptId: rendered.promptId,
797
+ assistantRecords: turn.assistantRecords,
798
+ textBlocks: rendered.textBlocks,
799
+ toolBlocks: rendered.toolBlocks,
800
+ });
801
+ return false;
802
+ }
803
+ if (rendered.body.length === 0) {
804
+ // The prompt record is there and the assistant records are not. Answering
805
+ // false hands the turn back to the scraper, which is the only correct
806
+ // answer: an empty row would show as a blank reply forever, and suppressing
807
+ // the scrape would lose the reply outright.
808
+ logger.info('command-code-transcript-turn-empty', {
809
+ worktreeId: target.worktreeId,
810
+ instanceId,
811
+ sessionId: rendered.sessionId,
812
+ promptId: rendered.promptId,
813
+ });
814
+ return false;
815
+ }
816
+ if (rendered.unknownBlockTypes.length > 0) {
817
+ logger.info('command-code-transcript-unknown-blocks', {
818
+ worktreeId: target.worktreeId,
819
+ instanceId,
820
+ sessionId: rendered.sessionId,
821
+ blockTypes: rendered.unknownBlockTypes,
822
+ });
823
+ }
824
+ const requestId = (0, agent_transcript_1.commandCodeTurnRequestId)(rendered.promptId);
825
+ const [{ getDbInstance }, { createMessage, findMessageByRequestId }, { broadcastMessage }] = await Promise.all([
826
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db/db-instance'))),
827
+ Promise.resolve().then(() => __importStar(require('../../../../lib/db'))),
828
+ Promise.resolve().then(() => __importStar(require('../../../../lib/ws-server'))),
829
+ ]);
830
+ const db = getDbInstance();
831
+ const existing = findMessageByRequestId(db, target.worktreeId, requestId);
832
+ if (existing) {
833
+ logger.debug('command-code-transcript-turn-already-saved', {
834
+ worktreeId: target.worktreeId,
835
+ instanceId,
836
+ requestId,
837
+ });
838
+ // The row may still be one of the short ones #2264 was reported for, and
839
+ // this is the one place that knows both the row and the turn.
840
+ await growCommandCodeTurnRow(target, existing, rendered, path);
841
+ return true;
842
+ }
843
+ const message = createMessage(db, {
844
+ worktreeId: target.worktreeId,
845
+ role: 'assistant',
846
+ content: rendered.body,
847
+ messageType: 'normal',
848
+ timestamp: new Date(timestampMs > 0 ? timestampMs : Date.now()),
849
+ cliToolId: target.cliToolId,
850
+ instanceId,
851
+ requestId,
852
+ });
853
+ broadcastMessage('message', { worktreeId: target.worktreeId, message });
854
+ logger.info('command-code-transcript-turn-saved', {
855
+ worktreeId: target.worktreeId,
856
+ instanceId,
857
+ sessionId: rendered.sessionId,
858
+ requestId,
859
+ path,
860
+ bodyLength: rendered.body.length,
861
+ textBlocks: rendered.textBlocks,
862
+ toolBlocks: rendered.toolBlocks,
863
+ });
864
+ return true;
865
+ }