commandmate 0.26.0 → 0.27.0

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 (838) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +431 -403
  3. package/.next/app-path-routes-manifest.json +26 -22
  4. package/.next/build-manifest.json +5 -5
  5. package/.next/prerender-manifest.json +9 -9
  6. package/.next/react-loadable-manifest.json +6 -6
  7. package/.next/required-server-files.json +1 -1
  8. package/.next/routes-manifest.json +24 -0
  9. package/.next/server/app/_not-found/page_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 +1 -1
  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 +2 -2
  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 -0
  38. package/.next/server/app/api/capabilities/route.js.nft.json +1 -0
  39. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -0
  40. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/fs/browse/route.js +2 -2
  46. package/.next/server/app/api/fs/browse/route.js.nft.json +1 -1
  47. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/fs/recent-paths/route.js +2 -2
  49. package/.next/server/app/api/fs/recent-paths/route.js.nft.json +1 -1
  50. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/hooks/agent-event/route.js +1 -1
  52. package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
  53. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/hooks/claude-done/route.js +19 -1
  55. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  56. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  58. package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
  59. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/push/escalation/route.js +1 -1
  63. package/.next/server/app/api/push/escalation/route.js.nft.json +1 -1
  64. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/push/subscriptions/route.js +1 -1
  66. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/push/vapid/route.js +1 -1
  68. package/.next/server/app/api/push/vapid/route.js.nft.json +1 -1
  69. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route.js +1 -1
  72. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/repositories/[id]/todos/route.js +1 -1
  74. package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  76. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
  77. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/repositories/clone/route.js +1 -1
  79. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
  80. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/repositories/restore/route.js +1 -1
  83. package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
  84. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/repositories/route.js +1 -1
  86. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  87. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/repositories/scan/route.js +1 -1
  89. package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
  90. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/repositories/sync/route.js +1 -1
  92. package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
  93. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/repositories/validate-path/route.js +1 -1
  95. package/.next/server/app/api/repositories/validate-path/route.js.nft.json +1 -1
  96. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/tasks/[taskId]/route.js +1 -1
  106. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/todos/route.js +1 -1
  110. package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/verification/runs/[runId]/route.js +1 -1
  112. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/verification/runs/route.js +1 -1
  114. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  117. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
  120. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -0
  122. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -0
  123. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -0
  124. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js.nft.json +1 -1
  128. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/current-output/route.js +2 -1
  130. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  131. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/env/route.js +2 -0
  133. package/.next/server/app/api/worktrees/[id]/env/route.js.nft.json +1 -0
  134. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -0
  135. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js.nft.json +1 -1
  141. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/git/branches/route.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/git/commit/route.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  151. package/.next/server/app/api/worktrees/[id]/git/log/route.js +1 -1
  152. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  153. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  155. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  157. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  159. package/.next/server/app/api/worktrees/[id]/git/staged/route.js +1 -1
  160. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  161. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  163. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/api/worktrees/[id]/git/stash/push/route.js +1 -1
  165. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  166. package/.next/server/app/api/worktrees/[id]/git/stash/route.js +1 -1
  167. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  168. package/.next/server/app/api/worktrees/[id]/git/status/route.js +1 -1
  169. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  170. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  171. package/.next/server/app/api/worktrees/[id]/git/working-diff/route.js +1 -1
  172. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  173. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  174. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  175. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  176. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  177. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  178. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  179. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
  180. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  181. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  182. package/.next/server/app/api/worktrees/[id]/marp-render/route.js +1 -1
  183. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  184. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
  185. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  186. package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
  187. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  188. package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
  189. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  190. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  191. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  192. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  193. package/.next/server/app/api/worktrees/[id]/resolve-target/route.js +1 -0
  194. package/.next/server/app/api/worktrees/[id]/resolve-target/route.js.nft.json +1 -0
  195. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -0
  196. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  197. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  198. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  199. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  200. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  201. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  202. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +1 -5
  203. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -1
  204. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  205. package/.next/server/app/api/worktrees/[id]/schedules/active/route.js +1 -1
  206. package/.next/server/app/api/worktrees/[id]/schedules/active/route.js.nft.json +1 -1
  207. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  208. package/.next/server/app/api/worktrees/[id]/schedules/route.js +2 -6
  209. package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +1 -1
  210. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  211. package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
  212. package/.next/server/app/api/worktrees/[id]/search/route.js.nft.json +1 -1
  213. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  214. package/.next/server/app/api/worktrees/[id]/send/route.js +3 -1
  215. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  216. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  217. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js +1 -1
  218. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
  219. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  220. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  221. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  222. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  223. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  224. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  225. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  226. package/.next/server/app/api/worktrees/[id]/skills/route.js +1 -1
  227. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  228. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  229. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  230. package/.next/server/app/api/worktrees/[id]/special-keys/route.js +1 -1
  231. package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
  232. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  233. package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
  234. package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
  235. package/.next/server/app/api/worktrees/[id]/start-polling/route_client-reference-manifest.js +1 -1
  236. package/.next/server/app/api/worktrees/[id]/tasks/route.js +1 -1
  237. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  238. package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -1
  239. package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
  240. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  241. package/.next/server/app/api/worktrees/[id]/timers/history/route.js +1 -1
  242. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  243. package/.next/server/app/api/worktrees/[id]/timers/route.js +3 -1
  244. package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
  245. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  246. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route.js +1 -1
  247. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  248. package/.next/server/app/api/worktrees/[id]/todos/route.js +1 -1
  249. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  250. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
  251. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js.nft.json +1 -1
  252. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  253. package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
  254. package/.next/server/app/api/worktrees/[id]/tree/route.js.nft.json +1 -1
  255. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  256. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
  257. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js.nft.json +1 -1
  258. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  259. package/.next/server/app/api/worktrees/[id]/verify/route.js +1 -1
  260. package/.next/server/app/api/worktrees/[id]/verify/route.js.nft.json +1 -1
  261. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  262. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route.js +1 -1
  263. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  264. package/.next/server/app/api/worktrees/[id]/verify/runs/route.js +1 -1
  265. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  266. package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
  267. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  268. package/.next/server/app/api/worktrees/route.js +1 -1
  269. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  270. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  271. package/.next/server/app/chat/page.js +2 -2
  272. package/.next/server/app/chat/page.js.nft.json +1 -1
  273. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  274. package/.next/server/app/login/page.js +2 -2
  275. package/.next/server/app/login/page.js.nft.json +1 -1
  276. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  277. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  278. package/.next/server/app/more/page.js +2 -2
  279. package/.next/server/app/more/page.js.nft.json +1 -1
  280. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  281. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  282. package/.next/server/app/page.js +1 -1
  283. package/.next/server/app/page.js.nft.json +1 -1
  284. package/.next/server/app/page_client-reference-manifest.js +1 -1
  285. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  286. package/.next/server/app/repositories/page.js +2 -2
  287. package/.next/server/app/repositories/page.js.nft.json +1 -1
  288. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  289. package/.next/server/app/review/page.js +1 -1
  290. package/.next/server/app/review/page.js.nft.json +1 -1
  291. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  292. package/.next/server/app/sessions/page.js +1 -1
  293. package/.next/server/app/sessions/page.js.nft.json +1 -1
  294. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  295. package/.next/server/app/skills/[skillId]/page.js +2 -2
  296. package/.next/server/app/skills/[skillId]/page.js.nft.json +1 -1
  297. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  298. package/.next/server/app/skills/installed/page.js +2 -2
  299. package/.next/server/app/skills/installed/page.js.nft.json +1 -1
  300. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  301. package/.next/server/app/skills/page.js +2 -2
  302. package/.next/server/app/skills/page.js.nft.json +1 -1
  303. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  304. package/.next/server/app/worktrees/[id]/files/[...path]/page.js +2 -2
  305. package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
  306. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  307. package/.next/server/app/worktrees/[id]/page.js +9 -7
  308. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  309. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  310. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  311. package/.next/server/app-paths-manifest.json +26 -22
  312. package/.next/server/chunks/1029.js +1 -1
  313. package/.next/server/chunks/1068.js +3 -1
  314. package/.next/server/chunks/1296.js +1 -1
  315. package/.next/server/chunks/1538.js +7 -0
  316. package/.next/server/chunks/1601.js +2 -2
  317. package/.next/server/chunks/2119.js +1 -1
  318. package/.next/server/chunks/2404.js +3 -2
  319. package/.next/server/chunks/2445.js +1 -1
  320. package/.next/server/chunks/2546.js +1 -1
  321. package/.next/server/chunks/2689.js +13 -0
  322. package/.next/server/chunks/2759.js +8 -0
  323. package/.next/server/chunks/2839.js +1 -0
  324. package/.next/server/chunks/3241.js +1 -1
  325. package/.next/server/chunks/3292.js +1 -0
  326. package/.next/server/chunks/3417.js +1 -1
  327. package/.next/server/chunks/3480.js +1 -17
  328. package/.next/server/chunks/4393.js +1 -1
  329. package/.next/server/chunks/4423.js +1 -0
  330. package/.next/server/chunks/4444.js +22 -8
  331. package/.next/server/chunks/4565.js +3 -0
  332. package/.next/server/chunks/467.js +1 -0
  333. package/.next/server/chunks/4905.js +5 -0
  334. package/.next/server/chunks/5017.js +1 -0
  335. package/.next/server/chunks/5089.js +1 -0
  336. package/.next/server/chunks/5164.js +1 -1
  337. package/.next/server/chunks/5220.js +1 -0
  338. package/.next/server/chunks/5354.js +1 -0
  339. package/.next/server/chunks/552.js +19 -0
  340. package/.next/server/chunks/5571.js +1 -1
  341. package/.next/server/chunks/5998.js +3 -3
  342. package/.next/server/chunks/6130.js +1 -1
  343. package/.next/server/chunks/6274.js +11 -11
  344. package/.next/server/chunks/6285.js +1 -1
  345. package/.next/server/chunks/6401.js +1 -1
  346. package/.next/server/chunks/6781.js +1 -1
  347. package/.next/server/chunks/7472.js +1 -0
  348. package/.next/server/chunks/7506.js +1 -0
  349. package/.next/server/chunks/848.js +1 -1
  350. package/.next/server/chunks/8536.js +1 -1
  351. package/.next/server/chunks/880.js +1 -1
  352. package/.next/server/chunks/8807.js +5 -5
  353. package/.next/server/chunks/9067.js +1 -1
  354. package/.next/server/chunks/9284.js +1 -0
  355. package/.next/server/chunks/9358.js +1 -1
  356. package/.next/server/chunks/9508.js +1 -0
  357. package/.next/server/chunks/9938.js +1 -0
  358. package/.next/server/middleware-build-manifest.js +1 -1
  359. package/.next/server/middleware-manifest.json +5 -5
  360. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  361. package/.next/server/pages/500.html +1 -1
  362. package/.next/server/server-reference-manifest.json +1 -1
  363. package/.next/static/chunks/{2053-dfac2d49227cc942.js → 2053-c0e931eb4391baa1.js} +1 -1
  364. package/.next/static/chunks/3361-5d6e93fba8a92bc1.js +1 -0
  365. package/.next/static/chunks/3922.0d2e64daf9f187da.js +1 -0
  366. package/.next/static/chunks/{5229-35f9acab97a553c5.js → 5229-1f5f19740a8d6c11.js} +1 -1
  367. package/.next/static/chunks/{6381-9303430933084b56.js → 6381-b7250e8ca7a44c6c.js} +1 -1
  368. package/.next/static/chunks/{8649-f551b0010dba4986.js → 8521-c43994fb58501d13.js} +1 -1
  369. package/.next/static/chunks/app/api/app/update/route-6add0d88e2828c23.js +1 -0
  370. package/.next/static/chunks/app/api/app/update-check/route-6add0d88e2828c23.js +1 -0
  371. package/.next/static/chunks/app/api/assistant/conversation/route-6add0d88e2828c23.js +1 -0
  372. package/.next/static/chunks/app/api/assistant/current-output/route-6add0d88e2828c23.js +1 -0
  373. package/.next/static/chunks/app/api/assistant/messages/route-6add0d88e2828c23.js +1 -0
  374. package/.next/static/chunks/app/api/assistant/session/route-6add0d88e2828c23.js +1 -0
  375. package/.next/static/chunks/app/api/assistant/start/route-6add0d88e2828c23.js +1 -0
  376. package/.next/static/chunks/app/api/assistant/terminal/route-6add0d88e2828c23.js +1 -0
  377. package/.next/static/chunks/app/api/assistant/tools/route-6add0d88e2828c23.js +1 -0
  378. package/.next/static/chunks/app/api/auth/login/route-6add0d88e2828c23.js +1 -0
  379. package/.next/static/chunks/app/api/auth/logout/route-6add0d88e2828c23.js +1 -0
  380. package/.next/static/chunks/app/api/auth/status/route-6add0d88e2828c23.js +1 -0
  381. package/.next/static/chunks/app/api/capabilities/route-6add0d88e2828c23.js +1 -0
  382. package/.next/static/chunks/app/api/daily-summary/route-6add0d88e2828c23.js +1 -0
  383. package/.next/static/chunks/app/api/daily-summary/status/route-6add0d88e2828c23.js +1 -0
  384. package/.next/static/chunks/app/api/external-apps/[id]/health/route-6add0d88e2828c23.js +1 -0
  385. package/.next/static/chunks/app/api/external-apps/[id]/route-6add0d88e2828c23.js +1 -0
  386. package/.next/static/chunks/app/api/external-apps/route-6add0d88e2828c23.js +1 -0
  387. package/.next/static/chunks/app/api/fs/browse/route-6add0d88e2828c23.js +1 -0
  388. package/.next/static/chunks/app/api/fs/recent-paths/route-6add0d88e2828c23.js +1 -0
  389. package/.next/static/chunks/app/api/hooks/agent-event/route-6add0d88e2828c23.js +1 -0
  390. package/.next/static/chunks/app/api/hooks/claude-done/route-6add0d88e2828c23.js +1 -0
  391. package/.next/static/chunks/app/api/hooks/permission-request/route-6add0d88e2828c23.js +1 -0
  392. package/.next/static/chunks/app/api/metrics/vibe/route-6add0d88e2828c23.js +1 -0
  393. package/.next/static/chunks/app/api/ollama/models/route-6add0d88e2828c23.js +1 -0
  394. package/.next/static/chunks/app/api/push/escalation/route-6add0d88e2828c23.js +1 -0
  395. package/.next/static/chunks/app/api/push/subscriptions/route-6add0d88e2828c23.js +1 -0
  396. package/.next/static/chunks/app/api/push/vapid/route-6add0d88e2828c23.js +1 -0
  397. package/.next/static/chunks/app/api/repositories/[id]/route-6add0d88e2828c23.js +1 -0
  398. package/.next/static/chunks/app/api/repositories/[id]/todos/[todoId]/route-6add0d88e2828c23.js +1 -0
  399. package/.next/static/chunks/app/api/repositories/[id]/todos/route-6add0d88e2828c23.js +1 -0
  400. package/.next/static/chunks/app/api/repositories/clone/[jobId]/route-6add0d88e2828c23.js +1 -0
  401. package/.next/static/chunks/app/api/repositories/clone/route-6add0d88e2828c23.js +1 -0
  402. package/.next/static/chunks/app/api/repositories/excluded/route-6add0d88e2828c23.js +1 -0
  403. package/.next/static/chunks/app/api/repositories/restore/route-6add0d88e2828c23.js +1 -0
  404. package/.next/static/chunks/app/api/repositories/route-6add0d88e2828c23.js +1 -0
  405. package/.next/static/chunks/app/api/repositories/scan/route-6add0d88e2828c23.js +1 -0
  406. package/.next/static/chunks/app/api/repositories/sync/route-6add0d88e2828c23.js +1 -0
  407. package/.next/static/chunks/app/api/repositories/validate-path/route-6add0d88e2828c23.js +1 -0
  408. package/.next/static/chunks/app/api/sidebar/group-order/route-6add0d88e2828c23.js +1 -0
  409. package/.next/static/chunks/app/api/skills/[id]/route-6add0d88e2828c23.js +1 -0
  410. package/.next/static/chunks/app/api/skills/installations/route-6add0d88e2828c23.js +1 -0
  411. package/.next/static/chunks/app/api/skills/operations/route-6add0d88e2828c23.js +1 -0
  412. package/.next/static/chunks/app/api/skills/reindex/route-6add0d88e2828c23.js +1 -0
  413. package/.next/static/chunks/app/api/skills/route-6add0d88e2828c23.js +1 -0
  414. package/.next/static/chunks/app/api/slash-commands/route-6add0d88e2828c23.js +1 -0
  415. package/.next/static/chunks/app/api/tasks/[taskId]/cancel/route-6add0d88e2828c23.js +1 -0
  416. package/.next/static/chunks/app/api/tasks/[taskId]/route-6add0d88e2828c23.js +1 -0
  417. package/.next/static/chunks/app/api/templates/[id]/route-6add0d88e2828c23.js +1 -0
  418. package/.next/static/chunks/app/api/templates/route-6add0d88e2828c23.js +1 -0
  419. package/.next/static/chunks/app/api/todos/route-6add0d88e2828c23.js +1 -0
  420. package/.next/static/chunks/app/api/verification/runs/[runId]/route-6add0d88e2828c23.js +1 -0
  421. package/.next/static/chunks/app/api/verification/runs/route-6add0d88e2828c23.js +1 -0
  422. package/.next/static/chunks/app/api/worktrees/[id]/auto-yes/route-6add0d88e2828c23.js +1 -0
  423. package/.next/static/chunks/app/api/worktrees/[id]/capture/route-6add0d88e2828c23.js +1 -0
  424. package/.next/static/chunks/app/api/worktrees/[id]/clear-composer/route-6add0d88e2828c23.js +1 -0
  425. package/.next/static/chunks/app/api/worktrees/[id]/cli-tool/route-6add0d88e2828c23.js +1 -0
  426. package/.next/static/chunks/app/api/worktrees/[id]/cmate/schedules/route-6add0d88e2828c23.js +1 -0
  427. package/.next/static/chunks/app/api/worktrees/[id]/current-output/route-6add0d88e2828c23.js +1 -0
  428. package/.next/static/chunks/app/api/worktrees/[id]/env/route-6add0d88e2828c23.js +1 -0
  429. package/.next/static/chunks/app/api/worktrees/[id]/execution-logs/[logId]/route-6add0d88e2828c23.js +1 -0
  430. package/.next/static/chunks/app/api/worktrees/[id]/execution-logs/route-6add0d88e2828c23.js +1 -0
  431. package/.next/static/chunks/app/api/worktrees/[id]/files/[...path]/route-6add0d88e2828c23.js +1 -0
  432. package/.next/static/chunks/app/api/worktrees/[id]/git/branch/create/route-6add0d88e2828c23.js +1 -0
  433. package/.next/static/chunks/app/api/worktrees/[id]/git/branch/delete/route-6add0d88e2828c23.js +1 -0
  434. package/.next/static/chunks/app/api/worktrees/[id]/git/branches/route-6add0d88e2828c23.js +1 -0
  435. package/.next/static/chunks/app/api/worktrees/[id]/git/checkout/route-6add0d88e2828c23.js +1 -0
  436. package/.next/static/chunks/app/api/worktrees/[id]/git/commit/route-6add0d88e2828c23.js +1 -0
  437. package/.next/static/chunks/app/api/worktrees/[id]/git/diff/route-6add0d88e2828c23.js +1 -0
  438. package/.next/static/chunks/app/api/worktrees/[id]/git/fetch/route-6add0d88e2828c23.js +1 -0
  439. package/.next/static/chunks/app/api/worktrees/[id]/git/log/route-6add0d88e2828c23.js +1 -0
  440. package/.next/static/chunks/app/api/worktrees/[id]/git/pull/route-6add0d88e2828c23.js +1 -0
  441. package/.next/static/chunks/app/api/worktrees/[id]/git/push/route-6add0d88e2828c23.js +1 -0
  442. package/.next/static/chunks/app/api/worktrees/[id]/git/reset/route-6add0d88e2828c23.js +1 -0
  443. package/.next/static/chunks/app/api/worktrees/[id]/git/revert/route-6add0d88e2828c23.js +1 -0
  444. package/.next/static/chunks/app/api/worktrees/[id]/git/show/[commitHash]/route-6add0d88e2828c23.js +1 -0
  445. package/.next/static/chunks/app/api/worktrees/[id]/git/stage/route-6add0d88e2828c23.js +1 -0
  446. package/.next/static/chunks/app/api/worktrees/[id]/git/staged/route-6add0d88e2828c23.js +1 -0
  447. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/[index]/route-6add0d88e2828c23.js +1 -0
  448. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/apply/route-6add0d88e2828c23.js +1 -0
  449. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/pop/route-6add0d88e2828c23.js +1 -0
  450. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/push/route-6add0d88e2828c23.js +1 -0
  451. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/route-6add0d88e2828c23.js +1 -0
  452. package/.next/static/chunks/app/api/worktrees/[id]/git/status/route-6add0d88e2828c23.js +1 -0
  453. package/.next/static/chunks/app/api/worktrees/[id]/git/unstage/route-6add0d88e2828c23.js +1 -0
  454. package/.next/static/chunks/app/api/worktrees/[id]/git/working-diff/route-6add0d88e2828c23.js +1 -0
  455. package/.next/static/chunks/app/api/worktrees/[id]/interrupt/route-6add0d88e2828c23.js +1 -0
  456. package/.next/static/chunks/app/api/worktrees/[id]/kill-session/route-6add0d88e2828c23.js +1 -0
  457. package/.next/static/chunks/app/api/worktrees/[id]/logs/[filename]/route-6add0d88e2828c23.js +1 -0
  458. package/.next/static/chunks/app/api/worktrees/[id]/logs/route-6add0d88e2828c23.js +1 -0
  459. package/.next/static/chunks/app/api/worktrees/[id]/marp-render/route-6add0d88e2828c23.js +1 -0
  460. package/.next/static/chunks/app/api/worktrees/[id]/memos/[memoId]/route-6add0d88e2828c23.js +1 -0
  461. package/.next/static/chunks/app/api/worktrees/[id]/memos/route-6add0d88e2828c23.js +1 -0
  462. package/.next/static/chunks/app/api/worktrees/[id]/messages/route-6add0d88e2828c23.js +1 -0
  463. package/.next/static/chunks/app/api/worktrees/[id]/prompt-response/route-6add0d88e2828c23.js +1 -0
  464. package/.next/static/chunks/app/api/worktrees/[id]/resolve-target/route-6add0d88e2828c23.js +1 -0
  465. package/.next/static/chunks/app/api/worktrees/[id]/respond/route-6add0d88e2828c23.js +1 -0
  466. package/.next/static/chunks/app/api/worktrees/[id]/route-6add0d88e2828c23.js +1 -0
  467. package/.next/static/chunks/app/api/worktrees/[id]/schedules/[scheduleId]/route-6add0d88e2828c23.js +1 -0
  468. package/.next/static/chunks/app/api/worktrees/[id]/schedules/active/route-6add0d88e2828c23.js +1 -0
  469. package/.next/static/chunks/app/api/worktrees/[id]/schedules/route-6add0d88e2828c23.js +1 -0
  470. package/.next/static/chunks/app/api/worktrees/[id]/search/route-6add0d88e2828c23.js +1 -0
  471. package/.next/static/chunks/app/api/worktrees/[id]/send/route-6add0d88e2828c23.js +1 -0
  472. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route-6add0d88e2828c23.js +1 -0
  473. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/install/route-6add0d88e2828c23.js +1 -0
  474. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/plan/route-6add0d88e2828c23.js +1 -0
  475. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/uninstall/route-6add0d88e2828c23.js +1 -0
  476. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route-6add0d88e2828c23.js +1 -0
  477. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/update/route-6add0d88e2828c23.js +1 -0
  478. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/update-plan/route-6add0d88e2828c23.js +1 -0
  479. package/.next/static/chunks/app/api/worktrees/[id]/skills/route-6add0d88e2828c23.js +1 -0
  480. package/.next/static/chunks/app/api/worktrees/[id]/slash-commands/route-6add0d88e2828c23.js +1 -0
  481. package/.next/static/chunks/app/api/worktrees/[id]/special-keys/route-6add0d88e2828c23.js +1 -0
  482. package/.next/static/chunks/app/api/worktrees/[id]/start-polling/route-6add0d88e2828c23.js +1 -0
  483. package/.next/static/chunks/app/api/worktrees/[id]/tasks/route-6add0d88e2828c23.js +1 -0
  484. package/.next/static/chunks/app/api/worktrees/[id]/terminal/route-6add0d88e2828c23.js +1 -0
  485. package/.next/static/chunks/app/api/worktrees/[id]/timers/history/route-6add0d88e2828c23.js +1 -0
  486. package/.next/static/chunks/app/api/worktrees/[id]/timers/route-6add0d88e2828c23.js +1 -0
  487. package/.next/static/chunks/app/api/worktrees/[id]/todos/[todoId]/route-6add0d88e2828c23.js +1 -0
  488. package/.next/static/chunks/app/api/worktrees/[id]/todos/route-6add0d88e2828c23.js +1 -0
  489. package/.next/static/chunks/app/api/worktrees/[id]/tree/[...path]/route-6add0d88e2828c23.js +1 -0
  490. package/.next/static/chunks/app/api/worktrees/[id]/tree/route-6add0d88e2828c23.js +1 -0
  491. package/.next/static/chunks/app/api/worktrees/[id]/upload/[...path]/route-6add0d88e2828c23.js +1 -0
  492. package/.next/static/chunks/app/api/worktrees/[id]/verify/route-6add0d88e2828c23.js +1 -0
  493. package/.next/static/chunks/app/api/worktrees/[id]/verify/runs/[runId]/route-6add0d88e2828c23.js +1 -0
  494. package/.next/static/chunks/app/api/worktrees/[id]/verify/runs/route-6add0d88e2828c23.js +1 -0
  495. package/.next/static/chunks/app/api/worktrees/[id]/viewed/route-6add0d88e2828c23.js +1 -0
  496. package/.next/static/chunks/app/api/worktrees/route-6add0d88e2828c23.js +1 -0
  497. package/.next/static/chunks/app/chat/{page-439b35c9cb90dbe0.js → page-231df45127e876c2.js} +1 -1
  498. package/.next/static/chunks/app/login/{page-eefd515d8c37e5ee.js → page-adc51f0f7cbb7ac6.js} +1 -1
  499. package/.next/static/chunks/app/manifest.webmanifest/route-6add0d88e2828c23.js +1 -0
  500. package/.next/static/chunks/app/more/{page-889b7546a8a553dc.js → page-3266dfa945972649.js} +1 -1
  501. package/.next/static/chunks/app/{page-a8d04ac0e3e4c9fc.js → page-0305431f43195431.js} +1 -1
  502. package/.next/static/chunks/app/proxy/[...path]/route-6add0d88e2828c23.js +1 -0
  503. package/.next/static/chunks/app/repositories/{page-f3c4d8839b1ca203.js → page-ae8fa55749976f79.js} +1 -1
  504. package/.next/static/chunks/app/review/{page-6dc88649fe88ac16.js → page-8f63aff862fee421.js} +1 -1
  505. package/.next/static/chunks/app/sessions/{page-2c9ed9e969097688.js → page-4d5382b257077c14.js} +1 -1
  506. package/.next/static/chunks/app/skills/[skillId]/{page-19beef6121c4c982.js → page-aaf0bcb1dfb558b9.js} +1 -1
  507. package/.next/static/chunks/app/skills/installed/{page-d5b9cef6d21fabda.js → page-dafc486410e839b7.js} +1 -1
  508. package/.next/static/chunks/app/skills/{page-06ef637e07dd9377.js → page-9e3c91da31514893.js} +1 -1
  509. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/{page-5fed3984fafcd910.js → page-90f96977c741bbf8.js} +1 -1
  510. package/.next/static/chunks/app/worktrees/[id]/layout-6add0d88e2828c23.js +1 -0
  511. package/.next/static/chunks/app/worktrees/[id]/page-e4f28966b4fc0ce1.js +1 -0
  512. package/.next/static/chunks/{webpack-9d892a9ff07c289d.js → webpack-6a9668cf54a26eba.js} +1 -1
  513. package/.next/static/css/928e5a8830fbf997.css +1 -0
  514. package/.next/static/t0BoV4H46ZgKmTjSi7GIz/_buildManifest.js +1 -0
  515. package/.next/trace +13 -12
  516. package/.next/types/app/api/capabilities/route.ts +347 -0
  517. package/.next/types/app/api/worktrees/[id]/clear-composer/route.ts +347 -0
  518. package/.next/types/app/api/worktrees/[id]/env/route.ts +347 -0
  519. package/.next/types/app/api/worktrees/[id]/resolve-target/route.ts +347 -0
  520. package/.next/types/routes.d.ts +5 -1
  521. package/.next/types/validator.ts +36 -0
  522. package/dist/cli/commands/auto-yes.d.ts.map +1 -1
  523. package/dist/cli/commands/auto-yes.js +40 -2
  524. package/dist/cli/commands/capture.d.ts.map +1 -1
  525. package/dist/cli/commands/capture.js +28 -13
  526. package/dist/cli/commands/instances.d.ts +30 -18
  527. package/dist/cli/commands/instances.d.ts.map +1 -1
  528. package/dist/cli/commands/instances.js +32 -36
  529. package/dist/cli/commands/ls.d.ts.map +1 -1
  530. package/dist/cli/commands/ls.js +73 -1
  531. package/dist/cli/commands/respond.d.ts.map +1 -1
  532. package/dist/cli/commands/respond.js +18 -1
  533. package/dist/cli/commands/send.d.ts.map +1 -1
  534. package/dist/cli/commands/send.js +21 -15
  535. package/dist/cli/commands/status.d.ts.map +1 -1
  536. package/dist/cli/commands/status.js +38 -0
  537. package/dist/cli/commands/wait.d.ts +26 -0
  538. package/dist/cli/commands/wait.d.ts.map +1 -1
  539. package/dist/cli/commands/wait.js +390 -43
  540. package/dist/cli/config/cli-dependencies.d.ts.map +1 -1
  541. package/dist/cli/config/cli-dependencies.js +16 -0
  542. package/dist/cli/config/cli-tool-ids.d.ts +11 -0
  543. package/dist/cli/config/cli-tool-ids.d.ts.map +1 -1
  544. package/dist/cli/config/cli-tool-ids.js +12 -1
  545. package/dist/cli/types/api-responses.d.ts +443 -2
  546. package/dist/cli/types/api-responses.d.ts.map +1 -1
  547. package/dist/cli/utils/api-client.d.ts +18 -0
  548. package/dist/cli/utils/api-client.d.ts.map +1 -1
  549. package/dist/cli/utils/api-client.js +22 -0
  550. package/dist/cli/utils/server-capabilities.d.ts +56 -0
  551. package/dist/cli/utils/server-capabilities.d.ts.map +1 -0
  552. package/dist/cli/utils/server-capabilities.js +166 -0
  553. package/dist/cli/utils/session-target.d.ts +70 -0
  554. package/dist/cli/utils/session-target.d.ts.map +1 -0
  555. package/dist/cli/utils/session-target.js +118 -0
  556. package/dist/lib/cli-tools/copilot-executable.d.ts +79 -0
  557. package/dist/lib/cli-tools/copilot-executable.d.ts.map +1 -0
  558. package/dist/lib/cli-tools/copilot-executable.js +155 -0
  559. package/dist/lib/cli-tools/types.d.ts +61 -0
  560. package/dist/lib/cli-tools/types.d.ts.map +1 -1
  561. package/dist/lib/cli-tools/types.js +41 -0
  562. package/dist/lib/detection/ansi.d.ts +2 -1
  563. package/dist/lib/detection/ansi.d.ts.map +1 -1
  564. package/dist/lib/detection/ansi.js +3 -2
  565. package/dist/lib/detection/tools/verified-against.d.ts +84 -0
  566. package/dist/lib/detection/tools/verified-against.d.ts.map +1 -0
  567. package/dist/lib/detection/tools/verified-against.js +88 -0
  568. package/dist/lib/detection/version-probes.d.ts +207 -0
  569. package/dist/lib/detection/version-probes.d.ts.map +1 -0
  570. package/dist/lib/detection/version-probes.js +290 -0
  571. package/dist/lib/security/env-sanitizer.d.ts +198 -0
  572. package/dist/lib/security/env-sanitizer.d.ts.map +1 -0
  573. package/dist/lib/security/env-sanitizer.js +232 -0
  574. package/dist/server/locales/en/notifications.json +17 -6
  575. package/dist/server/locales/ja/notifications.json +17 -6
  576. package/dist/server/src/config/activity-bar-config.js +4 -0
  577. package/dist/server/src/config/cli-tool-timing-config.js +59 -2
  578. package/dist/server/src/config/copilot-constants.js +11 -1
  579. package/dist/server/src/config/detection-evidence-config.js +129 -0
  580. package/dist/server/src/config/schedule-config.js +56 -7
  581. package/dist/server/src/config/tmux-pane-config.js +18 -1
  582. package/dist/server/src/lib/auto-yes-poller.js +36 -0
  583. package/dist/server/src/lib/cli-tools/antigravity.js +4 -2
  584. package/dist/server/src/lib/cli-tools/base.js +131 -10
  585. package/dist/server/src/lib/cli-tools/capture-spec.js +56 -0
  586. package/dist/server/src/lib/cli-tools/claude.js +1 -1
  587. package/dist/server/src/lib/cli-tools/codex.js +6 -2
  588. package/dist/server/src/lib/cli-tools/composer-spec.js +96 -0
  589. package/dist/server/src/lib/cli-tools/copilot-executable.js +155 -0
  590. package/dist/server/src/lib/cli-tools/copilot.js +307 -81
  591. package/dist/server/src/lib/cli-tools/gemini.js +8 -6
  592. package/dist/server/src/lib/cli-tools/graceful-exit.js +181 -0
  593. package/dist/server/src/lib/cli-tools/index.js +11 -1
  594. package/dist/server/src/lib/cli-tools/manager.js +56 -4
  595. package/dist/server/src/lib/cli-tools/opencode-config.js +195 -24
  596. package/dist/server/src/lib/cli-tools/opencode.js +233 -30
  597. package/dist/server/src/lib/cli-tools/session-name.js +57 -0
  598. package/dist/server/src/lib/cli-tools/submit-verified-sender.js +213 -30
  599. package/dist/server/src/lib/cli-tools/types.js +41 -0
  600. package/dist/server/src/lib/cli-tools/vibe-local.js +7 -3
  601. package/dist/server/src/lib/cmate-cli-tool-parser.js +29 -1
  602. package/dist/server/src/lib/cmate-parser.js +12 -4
  603. package/dist/server/src/lib/db/db.js +5 -1
  604. package/dist/server/src/lib/db/migrations/v41-push-subscriptions.js +11 -0
  605. package/dist/server/src/lib/db/push-subscriptions-db.js +60 -3
  606. package/dist/server/src/lib/detection/ansi.js +3 -2
  607. package/dist/server/src/lib/detection/cli-patterns.js +901 -67
  608. package/dist/server/src/lib/detection/composer-text.js +455 -0
  609. package/dist/server/src/lib/detection/idle-evidence-observation.js +51 -0
  610. package/dist/server/src/lib/detection/model-info-extractor.js +195 -4
  611. package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +19 -0
  612. package/dist/server/src/lib/detection/status-detector.js +76 -675
  613. package/dist/server/src/lib/detection/status-reason.js +57 -0
  614. package/dist/server/src/lib/detection/tools/antigravity/detect.js +75 -0
  615. package/dist/server/src/lib/detection/tools/claude/detect.js +144 -0
  616. package/dist/server/src/lib/detection/tools/claude/patterns.js +98 -0
  617. package/dist/server/src/lib/detection/tools/claude/prompt.js +124 -0
  618. package/dist/server/src/lib/detection/tools/codex/detect.js +343 -0
  619. package/dist/server/src/lib/detection/tools/codex/prompt.js +104 -0
  620. package/dist/server/src/lib/detection/tools/copilot/detect.js +185 -0
  621. package/dist/server/src/lib/detection/tools/copilot/prompt.js +89 -0
  622. package/dist/server/src/lib/detection/tools/dialog-block.js +159 -0
  623. package/dist/server/src/lib/detection/tools/frame.js +47 -0
  624. package/dist/server/src/lib/detection/tools/opencode/detect.js +242 -0
  625. package/dist/server/src/lib/detection/tools/opencode/prompt.js +85 -0
  626. package/dist/server/src/lib/detection/tools/registry.js +58 -0
  627. package/dist/server/src/lib/detection/tools/run-detection.js +202 -0
  628. package/dist/server/src/lib/detection/tools/types.js +34 -0
  629. package/dist/server/src/lib/detection/tools/verified-against.js +88 -0
  630. package/dist/server/src/lib/hooks/agent-event-types.js +19 -1
  631. package/dist/server/src/lib/hooks/permission-adjudication.js +160 -0
  632. package/dist/server/src/lib/hooks/permission-decision-service.js +81 -14
  633. package/dist/server/src/lib/hooks/permission-decision-state.js +62 -0
  634. package/dist/server/src/lib/hooks/permission-request-payload.js +18 -1
  635. package/dist/server/src/lib/hooks/sources/antigravity/source.js +10 -0
  636. package/dist/server/src/lib/hooks/sources/claude/source.js +20 -0
  637. package/dist/server/src/lib/hooks/sources/codex/source.js +10 -0
  638. package/dist/server/src/lib/hooks/sources/copilot/hook-settings.js +403 -50
  639. package/dist/server/src/lib/hooks/sources/copilot/source.js +40 -4
  640. package/dist/server/src/lib/hooks/sources/define-source.js +23 -1
  641. package/dist/server/src/lib/hooks/sources/event-mapper.js +29 -1
  642. package/dist/server/src/lib/hooks/sources/gemini/source.js +10 -0
  643. package/dist/server/src/lib/hooks/sources/index.js +3 -1
  644. package/dist/server/src/lib/hooks/sources/launch-command.js +96 -1
  645. package/dist/server/src/lib/hooks/sources/legacy-relay.js +12 -0
  646. package/dist/server/src/lib/hooks/sources/opencode/client.js +295 -32
  647. package/dist/server/src/lib/hooks/sources/opencode/ingest.js +120 -42
  648. package/dist/server/src/lib/hooks/sources/opencode/mappers.js +95 -3
  649. package/dist/server/src/lib/hooks/sources/opencode/ports.js +70 -9
  650. package/dist/server/src/lib/hooks/sources/opencode/runtime.js +62 -4
  651. package/dist/server/src/lib/hooks/sources/opencode/source.js +54 -3
  652. package/dist/server/src/lib/hooks/sources/opencode/subscription.js +254 -26
  653. package/dist/server/src/lib/hooks/sources/opencode/turn-gate.js +119 -4
  654. package/dist/server/src/lib/hooks/sources/pending-decisions.js +40 -2
  655. package/dist/server/src/lib/hooks/tool-input-normalization-state.js +67 -0
  656. package/dist/server/src/lib/hooks/tool-input-normalization.js +157 -0
  657. package/dist/server/src/lib/job-executor.js +16 -4
  658. package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +184 -0
  659. package/dist/server/src/lib/polling/response-checker.js +243 -26
  660. package/dist/server/src/lib/push/failure-episode-state.js +127 -0
  661. package/dist/server/src/lib/push/failure-push-notifier.js +344 -0
  662. package/dist/server/src/lib/push/index.js +35 -1
  663. package/dist/server/src/lib/push/prompt-card-state.js +93 -0
  664. package/dist/server/src/lib/push/prompt-push-gate.js +130 -0
  665. package/dist/server/src/lib/push/push-sender.js +82 -7
  666. package/dist/server/src/lib/push/resolution-push-notifier.js +193 -0
  667. package/dist/server/src/lib/push/waiting-push-notifier.js +42 -0
  668. package/dist/server/src/lib/response-cleaner.js +93 -20
  669. package/dist/server/src/lib/response-extractor.js +98 -5
  670. package/dist/server/src/lib/security/env-sanitizer.js +179 -5
  671. package/dist/server/src/lib/session/agent-event-state.js +1038 -189
  672. package/dist/server/src/lib/session/agent-session-lifecycle.js +8 -0
  673. package/dist/server/src/lib/session/claude-executor.js +23 -3
  674. package/dist/server/src/lib/session/claude-session.js +11 -1
  675. package/dist/server/src/lib/session/composer-clear.js +95 -0
  676. package/dist/server/src/lib/session/current-output-builder.js +267 -25
  677. package/dist/server/src/lib/session/provisional-turn.js +310 -0
  678. package/dist/server/src/lib/session/resolve-session-target.js +156 -0
  679. package/dist/server/src/lib/session/send-user-message.js +14 -18
  680. package/dist/server/src/lib/session/session-start-error.js +53 -1
  681. package/dist/server/src/lib/session/status-evidence.js +217 -0
  682. package/dist/server/src/lib/session/structured-prompt.js +46 -4
  683. package/dist/server/src/lib/session/waiting-episode-state.js +24 -0
  684. package/dist/server/src/lib/tmux/key-sequence.js +120 -0
  685. package/dist/server/src/lib/tmux/tmux.js +91 -19
  686. package/dist/server/src/lib/tui-accumulator.js +68 -1
  687. package/dist/server/src/lib/verification/gate-runner.js +84 -1
  688. package/dist/server/src/lib/ws-server.js +8 -5
  689. package/dist/server/src/types/cli-tool-contracts.js +75 -0
  690. package/dist/server/src/types/terminal-keys.js +37 -0
  691. package/dist/types/cli-tool-contracts.d.ts +235 -0
  692. package/dist/types/cli-tool-contracts.d.ts.map +1 -0
  693. package/dist/types/cli-tool-contracts.js +75 -0
  694. package/package.json +3 -2
  695. package/public/sw.js +65 -2
  696. package/.next/server/chunks/1841.js +0 -1
  697. package/.next/server/chunks/233.js +0 -1
  698. package/.next/server/chunks/3022.js +0 -1
  699. package/.next/server/chunks/3270.js +0 -1
  700. package/.next/server/chunks/4455.js +0 -1
  701. package/.next/server/chunks/5959.js +0 -27
  702. package/.next/server/chunks/7458.js +0 -6
  703. package/.next/server/chunks/8730.js +0 -1
  704. package/.next/server/chunks/9058.js +0 -1
  705. package/.next/server/chunks/9687.js +0 -3
  706. package/.next/static/chunks/3898-ef488bfa804507d9.js +0 -1
  707. package/.next/static/chunks/9263.f2034ec525abcf72.js +0 -1
  708. package/.next/static/chunks/app/api/app/update/route-fae89f8d4cc3ff97.js +0 -1
  709. package/.next/static/chunks/app/api/app/update-check/route-fae89f8d4cc3ff97.js +0 -1
  710. package/.next/static/chunks/app/api/assistant/conversation/route-fae89f8d4cc3ff97.js +0 -1
  711. package/.next/static/chunks/app/api/assistant/current-output/route-fae89f8d4cc3ff97.js +0 -1
  712. package/.next/static/chunks/app/api/assistant/messages/route-fae89f8d4cc3ff97.js +0 -1
  713. package/.next/static/chunks/app/api/assistant/session/route-fae89f8d4cc3ff97.js +0 -1
  714. package/.next/static/chunks/app/api/assistant/start/route-fae89f8d4cc3ff97.js +0 -1
  715. package/.next/static/chunks/app/api/assistant/terminal/route-fae89f8d4cc3ff97.js +0 -1
  716. package/.next/static/chunks/app/api/assistant/tools/route-fae89f8d4cc3ff97.js +0 -1
  717. package/.next/static/chunks/app/api/auth/login/route-fae89f8d4cc3ff97.js +0 -1
  718. package/.next/static/chunks/app/api/auth/logout/route-fae89f8d4cc3ff97.js +0 -1
  719. package/.next/static/chunks/app/api/auth/status/route-fae89f8d4cc3ff97.js +0 -1
  720. package/.next/static/chunks/app/api/daily-summary/route-fae89f8d4cc3ff97.js +0 -1
  721. package/.next/static/chunks/app/api/daily-summary/status/route-fae89f8d4cc3ff97.js +0 -1
  722. package/.next/static/chunks/app/api/external-apps/[id]/health/route-fae89f8d4cc3ff97.js +0 -1
  723. package/.next/static/chunks/app/api/external-apps/[id]/route-fae89f8d4cc3ff97.js +0 -1
  724. package/.next/static/chunks/app/api/external-apps/route-fae89f8d4cc3ff97.js +0 -1
  725. package/.next/static/chunks/app/api/fs/browse/route-fae89f8d4cc3ff97.js +0 -1
  726. package/.next/static/chunks/app/api/fs/recent-paths/route-fae89f8d4cc3ff97.js +0 -1
  727. package/.next/static/chunks/app/api/hooks/agent-event/route-fae89f8d4cc3ff97.js +0 -1
  728. package/.next/static/chunks/app/api/hooks/claude-done/route-fae89f8d4cc3ff97.js +0 -1
  729. package/.next/static/chunks/app/api/hooks/permission-request/route-fae89f8d4cc3ff97.js +0 -1
  730. package/.next/static/chunks/app/api/metrics/vibe/route-fae89f8d4cc3ff97.js +0 -1
  731. package/.next/static/chunks/app/api/ollama/models/route-fae89f8d4cc3ff97.js +0 -1
  732. package/.next/static/chunks/app/api/push/escalation/route-fae89f8d4cc3ff97.js +0 -1
  733. package/.next/static/chunks/app/api/push/subscriptions/route-fae89f8d4cc3ff97.js +0 -1
  734. package/.next/static/chunks/app/api/push/vapid/route-fae89f8d4cc3ff97.js +0 -1
  735. package/.next/static/chunks/app/api/repositories/[id]/route-fae89f8d4cc3ff97.js +0 -1
  736. package/.next/static/chunks/app/api/repositories/[id]/todos/[todoId]/route-fae89f8d4cc3ff97.js +0 -1
  737. package/.next/static/chunks/app/api/repositories/[id]/todos/route-fae89f8d4cc3ff97.js +0 -1
  738. package/.next/static/chunks/app/api/repositories/clone/[jobId]/route-fae89f8d4cc3ff97.js +0 -1
  739. package/.next/static/chunks/app/api/repositories/clone/route-fae89f8d4cc3ff97.js +0 -1
  740. package/.next/static/chunks/app/api/repositories/excluded/route-fae89f8d4cc3ff97.js +0 -1
  741. package/.next/static/chunks/app/api/repositories/restore/route-fae89f8d4cc3ff97.js +0 -1
  742. package/.next/static/chunks/app/api/repositories/route-fae89f8d4cc3ff97.js +0 -1
  743. package/.next/static/chunks/app/api/repositories/scan/route-fae89f8d4cc3ff97.js +0 -1
  744. package/.next/static/chunks/app/api/repositories/sync/route-fae89f8d4cc3ff97.js +0 -1
  745. package/.next/static/chunks/app/api/repositories/validate-path/route-fae89f8d4cc3ff97.js +0 -1
  746. package/.next/static/chunks/app/api/sidebar/group-order/route-fae89f8d4cc3ff97.js +0 -1
  747. package/.next/static/chunks/app/api/skills/[id]/route-fae89f8d4cc3ff97.js +0 -1
  748. package/.next/static/chunks/app/api/skills/installations/route-fae89f8d4cc3ff97.js +0 -1
  749. package/.next/static/chunks/app/api/skills/operations/route-fae89f8d4cc3ff97.js +0 -1
  750. package/.next/static/chunks/app/api/skills/reindex/route-fae89f8d4cc3ff97.js +0 -1
  751. package/.next/static/chunks/app/api/skills/route-fae89f8d4cc3ff97.js +0 -1
  752. package/.next/static/chunks/app/api/slash-commands/route-fae89f8d4cc3ff97.js +0 -1
  753. package/.next/static/chunks/app/api/tasks/[taskId]/cancel/route-fae89f8d4cc3ff97.js +0 -1
  754. package/.next/static/chunks/app/api/tasks/[taskId]/route-fae89f8d4cc3ff97.js +0 -1
  755. package/.next/static/chunks/app/api/templates/[id]/route-fae89f8d4cc3ff97.js +0 -1
  756. package/.next/static/chunks/app/api/templates/route-fae89f8d4cc3ff97.js +0 -1
  757. package/.next/static/chunks/app/api/todos/route-fae89f8d4cc3ff97.js +0 -1
  758. package/.next/static/chunks/app/api/verification/runs/[runId]/route-fae89f8d4cc3ff97.js +0 -1
  759. package/.next/static/chunks/app/api/verification/runs/route-fae89f8d4cc3ff97.js +0 -1
  760. package/.next/static/chunks/app/api/worktrees/[id]/auto-yes/route-fae89f8d4cc3ff97.js +0 -1
  761. package/.next/static/chunks/app/api/worktrees/[id]/capture/route-fae89f8d4cc3ff97.js +0 -1
  762. package/.next/static/chunks/app/api/worktrees/[id]/cli-tool/route-fae89f8d4cc3ff97.js +0 -1
  763. package/.next/static/chunks/app/api/worktrees/[id]/cmate/schedules/route-fae89f8d4cc3ff97.js +0 -1
  764. package/.next/static/chunks/app/api/worktrees/[id]/current-output/route-fae89f8d4cc3ff97.js +0 -1
  765. package/.next/static/chunks/app/api/worktrees/[id]/execution-logs/[logId]/route-fae89f8d4cc3ff97.js +0 -1
  766. package/.next/static/chunks/app/api/worktrees/[id]/execution-logs/route-fae89f8d4cc3ff97.js +0 -1
  767. package/.next/static/chunks/app/api/worktrees/[id]/files/[...path]/route-fae89f8d4cc3ff97.js +0 -1
  768. package/.next/static/chunks/app/api/worktrees/[id]/git/branch/create/route-fae89f8d4cc3ff97.js +0 -1
  769. package/.next/static/chunks/app/api/worktrees/[id]/git/branch/delete/route-fae89f8d4cc3ff97.js +0 -1
  770. package/.next/static/chunks/app/api/worktrees/[id]/git/branches/route-fae89f8d4cc3ff97.js +0 -1
  771. package/.next/static/chunks/app/api/worktrees/[id]/git/checkout/route-fae89f8d4cc3ff97.js +0 -1
  772. package/.next/static/chunks/app/api/worktrees/[id]/git/commit/route-fae89f8d4cc3ff97.js +0 -1
  773. package/.next/static/chunks/app/api/worktrees/[id]/git/diff/route-fae89f8d4cc3ff97.js +0 -1
  774. package/.next/static/chunks/app/api/worktrees/[id]/git/fetch/route-fae89f8d4cc3ff97.js +0 -1
  775. package/.next/static/chunks/app/api/worktrees/[id]/git/log/route-fae89f8d4cc3ff97.js +0 -1
  776. package/.next/static/chunks/app/api/worktrees/[id]/git/pull/route-fae89f8d4cc3ff97.js +0 -1
  777. package/.next/static/chunks/app/api/worktrees/[id]/git/push/route-fae89f8d4cc3ff97.js +0 -1
  778. package/.next/static/chunks/app/api/worktrees/[id]/git/reset/route-fae89f8d4cc3ff97.js +0 -1
  779. package/.next/static/chunks/app/api/worktrees/[id]/git/revert/route-fae89f8d4cc3ff97.js +0 -1
  780. package/.next/static/chunks/app/api/worktrees/[id]/git/show/[commitHash]/route-fae89f8d4cc3ff97.js +0 -1
  781. package/.next/static/chunks/app/api/worktrees/[id]/git/stage/route-fae89f8d4cc3ff97.js +0 -1
  782. package/.next/static/chunks/app/api/worktrees/[id]/git/staged/route-fae89f8d4cc3ff97.js +0 -1
  783. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/[index]/route-fae89f8d4cc3ff97.js +0 -1
  784. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/apply/route-fae89f8d4cc3ff97.js +0 -1
  785. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/pop/route-fae89f8d4cc3ff97.js +0 -1
  786. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/push/route-fae89f8d4cc3ff97.js +0 -1
  787. package/.next/static/chunks/app/api/worktrees/[id]/git/stash/route-fae89f8d4cc3ff97.js +0 -1
  788. package/.next/static/chunks/app/api/worktrees/[id]/git/status/route-fae89f8d4cc3ff97.js +0 -1
  789. package/.next/static/chunks/app/api/worktrees/[id]/git/unstage/route-fae89f8d4cc3ff97.js +0 -1
  790. package/.next/static/chunks/app/api/worktrees/[id]/git/working-diff/route-fae89f8d4cc3ff97.js +0 -1
  791. package/.next/static/chunks/app/api/worktrees/[id]/interrupt/route-fae89f8d4cc3ff97.js +0 -1
  792. package/.next/static/chunks/app/api/worktrees/[id]/kill-session/route-fae89f8d4cc3ff97.js +0 -1
  793. package/.next/static/chunks/app/api/worktrees/[id]/logs/[filename]/route-fae89f8d4cc3ff97.js +0 -1
  794. package/.next/static/chunks/app/api/worktrees/[id]/logs/route-fae89f8d4cc3ff97.js +0 -1
  795. package/.next/static/chunks/app/api/worktrees/[id]/marp-render/route-fae89f8d4cc3ff97.js +0 -1
  796. package/.next/static/chunks/app/api/worktrees/[id]/memos/[memoId]/route-fae89f8d4cc3ff97.js +0 -1
  797. package/.next/static/chunks/app/api/worktrees/[id]/memos/route-fae89f8d4cc3ff97.js +0 -1
  798. package/.next/static/chunks/app/api/worktrees/[id]/messages/route-fae89f8d4cc3ff97.js +0 -1
  799. package/.next/static/chunks/app/api/worktrees/[id]/prompt-response/route-fae89f8d4cc3ff97.js +0 -1
  800. package/.next/static/chunks/app/api/worktrees/[id]/respond/route-fae89f8d4cc3ff97.js +0 -1
  801. package/.next/static/chunks/app/api/worktrees/[id]/route-fae89f8d4cc3ff97.js +0 -1
  802. package/.next/static/chunks/app/api/worktrees/[id]/schedules/[scheduleId]/route-fae89f8d4cc3ff97.js +0 -1
  803. package/.next/static/chunks/app/api/worktrees/[id]/schedules/active/route-fae89f8d4cc3ff97.js +0 -1
  804. package/.next/static/chunks/app/api/worktrees/[id]/schedules/route-fae89f8d4cc3ff97.js +0 -1
  805. package/.next/static/chunks/app/api/worktrees/[id]/search/route-fae89f8d4cc3ff97.js +0 -1
  806. package/.next/static/chunks/app/api/worktrees/[id]/send/route-fae89f8d4cc3ff97.js +0 -1
  807. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route-fae89f8d4cc3ff97.js +0 -1
  808. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/install/route-fae89f8d4cc3ff97.js +0 -1
  809. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/plan/route-fae89f8d4cc3ff97.js +0 -1
  810. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/uninstall/route-fae89f8d4cc3ff97.js +0 -1
  811. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route-fae89f8d4cc3ff97.js +0 -1
  812. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/update/route-fae89f8d4cc3ff97.js +0 -1
  813. package/.next/static/chunks/app/api/worktrees/[id]/skills/[skillId]/update-plan/route-fae89f8d4cc3ff97.js +0 -1
  814. package/.next/static/chunks/app/api/worktrees/[id]/skills/route-fae89f8d4cc3ff97.js +0 -1
  815. package/.next/static/chunks/app/api/worktrees/[id]/slash-commands/route-fae89f8d4cc3ff97.js +0 -1
  816. package/.next/static/chunks/app/api/worktrees/[id]/special-keys/route-fae89f8d4cc3ff97.js +0 -1
  817. package/.next/static/chunks/app/api/worktrees/[id]/start-polling/route-fae89f8d4cc3ff97.js +0 -1
  818. package/.next/static/chunks/app/api/worktrees/[id]/tasks/route-fae89f8d4cc3ff97.js +0 -1
  819. package/.next/static/chunks/app/api/worktrees/[id]/terminal/route-fae89f8d4cc3ff97.js +0 -1
  820. package/.next/static/chunks/app/api/worktrees/[id]/timers/history/route-fae89f8d4cc3ff97.js +0 -1
  821. package/.next/static/chunks/app/api/worktrees/[id]/timers/route-fae89f8d4cc3ff97.js +0 -1
  822. package/.next/static/chunks/app/api/worktrees/[id]/todos/[todoId]/route-fae89f8d4cc3ff97.js +0 -1
  823. package/.next/static/chunks/app/api/worktrees/[id]/todos/route-fae89f8d4cc3ff97.js +0 -1
  824. package/.next/static/chunks/app/api/worktrees/[id]/tree/[...path]/route-fae89f8d4cc3ff97.js +0 -1
  825. package/.next/static/chunks/app/api/worktrees/[id]/tree/route-fae89f8d4cc3ff97.js +0 -1
  826. package/.next/static/chunks/app/api/worktrees/[id]/upload/[...path]/route-fae89f8d4cc3ff97.js +0 -1
  827. package/.next/static/chunks/app/api/worktrees/[id]/verify/route-fae89f8d4cc3ff97.js +0 -1
  828. package/.next/static/chunks/app/api/worktrees/[id]/verify/runs/[runId]/route-fae89f8d4cc3ff97.js +0 -1
  829. package/.next/static/chunks/app/api/worktrees/[id]/verify/runs/route-fae89f8d4cc3ff97.js +0 -1
  830. package/.next/static/chunks/app/api/worktrees/[id]/viewed/route-fae89f8d4cc3ff97.js +0 -1
  831. package/.next/static/chunks/app/api/worktrees/route-fae89f8d4cc3ff97.js +0 -1
  832. package/.next/static/chunks/app/manifest.webmanifest/route-fae89f8d4cc3ff97.js +0 -1
  833. package/.next/static/chunks/app/proxy/[...path]/route-fae89f8d4cc3ff97.js +0 -1
  834. package/.next/static/chunks/app/worktrees/[id]/layout-fae89f8d4cc3ff97.js +0 -1
  835. package/.next/static/chunks/app/worktrees/[id]/page-fbdf4621a2911ac1.js +0 -1
  836. package/.next/static/css/e865c4979a41aa03.css +0 -1
  837. package/.next/static/hNdNiFes2xLls68SWhAU9/_buildManifest.js +0 -1
  838. /package/.next/static/{hNdNiFes2xLls68SWhAU9 → t0BoV4H46ZgKmTjSi7GIz}/_ssgManifest.js +0 -0
@@ -39,7 +39,13 @@
39
39
  * @module lib/session/agent-event-state
40
40
  */
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = exports.STRUCTURED_STATE_MAX_AGE_MS = exports.AGENT_EVENT_DEDUP_WINDOW_MS = void 0;
42
+ exports.LIFECYCLE_AGENT_EVENT_TYPES = exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = exports.STRUCTURED_STATE_MAX_AGE_MS = exports.AGENT_EVENT_DEDUP_WINDOW_MS = void 0;
43
+ exports.getAgentEventDropCounts = getAgentEventDropCounts;
44
+ exports.getPendingDecisions = getPendingDecisions;
45
+ exports.getAgentTurn = getAgentTurn;
46
+ exports.getPublishedAgentTurn = getPublishedAgentTurn;
47
+ exports.closeAgentTurn = closeAgentTurn;
48
+ exports.observeScraperCompletionEvidence = observeScraperCompletionEvidence;
43
49
  exports.recordAgentStopEvent = recordAgentStopEvent;
44
50
  exports.getLastStopEventAt = getLastStopEventAt;
45
51
  exports.recordAgentEvent = recordAgentEvent;
@@ -65,6 +71,8 @@ exports.getAskUserQuestion = getAskUserQuestion;
65
71
  exports.clearAskUserQuestion = clearAskUserQuestion;
66
72
  exports.isDuplicateAgentEvent = isDuplicateAgentEvent;
67
73
  exports.getRecentEventKeyCount = getRecentEventKeyCount;
74
+ exports.classifyAgentEventDelivery = classifyAgentEventDelivery;
75
+ exports.getRecentEventIdentityCount = getRecentEventIdentityCount;
68
76
  exports.clearAgentStopEvents = clearAgentStopEvents;
69
77
  const auto_yes_state_1 = require("../../lib/auto-yes-state");
70
78
  const agent_event_types_1 = require("../../lib/hooks/agent-event-types");
@@ -72,7 +80,9 @@ const permission_request_payload_1 = require("../../lib/hooks/permission-request
72
80
  // Issue #1784: the terminal-frame half of "which model / effort is this on".
73
81
  const model_info_extractor_1 = require("../../lib/detection/model-info-extractor");
74
82
  const status_mapping_1 = require("../../lib/session/status-mapping");
75
- const structured_prompt_1 = require("../../lib/session/structured-prompt");
83
+ // Issue #1930: the turn model. Pure, so importing it here costs nothing at
84
+ // runtime beyond the two bounds it owns.
85
+ const provisional_turn_1 = require("../../lib/session/provisional-turn");
76
86
  /** compositeKey -> epoch ms of the most recent stop event. */
77
87
  const lastStopEventAt = globalThis.__agentEventLastStopAt ??
78
88
  (globalThis.__agentEventLastStopAt = new Map());
@@ -82,15 +92,54 @@ const lastAgentEvent = globalThis.__agentEventLast ??
82
92
  /** compositeKey -> epoch ms the current generation began. See {@link beginAgentEventGeneration}. */
83
93
  const generationStartedAt = globalThis.__agentEventGenerationStartedAt ??
84
94
  (globalThis.__agentEventGenerationStartedAt = new Map());
85
- /** compositeKey -> the open dialog the structured layer knows about (#1725). */
86
- const promptWaiting = globalThis.__agentEventPromptWaiting ??
87
- (globalThis.__agentEventPromptWaiting = new Map());
95
+ /**
96
+ * compositeKey -> the turn this instance is in, or the last one it was in
97
+ * (Issue #1930).
98
+ *
99
+ * The map that replaced "the newest event is the verdict". `lastAgentEvent`
100
+ * above is still written on every delivery and is still what
101
+ * `structuredEvents.lastEventType` publishes — it answers "did anything reach
102
+ * this server, and for the right instance?", which is a diagnostic question.
103
+ * This map answers the state question, and the two deliberately disagree
104
+ * whenever an event carries no verdict.
105
+ *
106
+ * The open dialogs #1725 kept in a map of their own live on the turn now
107
+ * ({@link TurnRecord.pendingDecisions}), because a dialog only ever happens
108
+ * *inside* a turn and holding them apart is what let a generation bump retire
109
+ * one and not the other.
110
+ */
111
+ const agentTurns = globalThis.__agentEventTurns ??
112
+ (globalThis.__agentEventTurns = new Map());
113
+ /** compositeKey -> what this instance has had dropped, and why (Issue #1930). */
114
+ const dropCounts = globalThis.__agentEventDrops ??
115
+ (globalThis.__agentEventDrops = new Map());
116
+ /**
117
+ * Monotonic suffix for {@link TurnRecord.turnId}.
118
+ *
119
+ * Two turns can open in the same millisecond — a `post_tool_use` closing one
120
+ * agent's turn while another's `user_prompt_submit` lands — and an id built from
121
+ * the timestamp alone would then compare equal across instances. `wait` reads a
122
+ * change of id as "a new turn began", so the collision would be a missed turn
123
+ * boundary rather than a cosmetic clash.
124
+ */
125
+ const turnSequence = globalThis.__agentEventTurnSeq ??
126
+ (globalThis.__agentEventTurnSeq = { value: 0 });
88
127
  /** compositeKey -> the `AskUserQuestion` call currently in flight (#1726). */
89
128
  const askUserQuestion = globalThis.__agentEventAskUserQuestion ??
90
129
  (globalThis.__agentEventAskUserQuestion = new Map());
91
130
  /** dedup key -> epoch ms it was first seen. See {@link isDuplicateAgentEvent}. */
92
131
  const recentEventKeys = globalThis.__agentEventRecentKeys ??
93
132
  (globalThis.__agentEventRecentKeys = new Map());
133
+ /**
134
+ * compositeKey -> (identity key -> epoch ms it was first seen) (Issue #1899).
135
+ *
136
+ * Nested rather than flat so the bound is *per instance*, which the flat
137
+ * {@link recentEventKeys} is not: one chatty agent must not be able to evict
138
+ * another's ids and let a genuine re-delivery through on a quiet pane. See
139
+ * {@link claimEventIdentity}.
140
+ */
141
+ const recentEventIdentities = globalThis.__agentEventRecentIdentities ??
142
+ (globalThis.__agentEventRecentIdentities = new Map());
94
143
  /** compositeKey -> the agent's own "I am waiting for instructions" (#1786). */
95
144
  const awaitingInstruction = globalThis.__agentEventAwaitingInstruction ??
96
145
  (globalThis.__agentEventAwaitingInstruction = new Map());
@@ -148,6 +197,209 @@ const capturedModelInfo = globalThis.__agentCapturedModelInfo ??
148
197
  exports.AGENT_EVENT_DEDUP_WINDOW_MS = 3000;
149
198
  /** Cap on retained dedup keys, so a long-lived server cannot grow one per turn. */
150
199
  const MAX_RECENT_EVENT_KEYS = 512;
200
+ /** A zeroed tally. */
201
+ function emptyDropCounts() {
202
+ return {
203
+ dedupDropped: { identity: 0, timeWindow: 0 },
204
+ decisionEvicted: 0,
205
+ idsDiscarded: 0,
206
+ dialogTimedOut: 0,
207
+ decisionOverflow: 0,
208
+ };
209
+ }
210
+ /** The live tally for one instance, created on first use. */
211
+ function dropsFor(key) {
212
+ let counts = dropCounts.get(key);
213
+ if (!counts) {
214
+ counts = emptyDropCounts();
215
+ dropCounts.set(key, counts);
216
+ // The composite key is (worktree, tool, instance) and a long-lived server
217
+ // accumulates worktrees; bounded like every other map here (DR4-009).
218
+ trimOldestEntries(dropCounts, MAX_RECENT_EVENT_KEYS);
219
+ }
220
+ return counts;
221
+ }
222
+ /**
223
+ * What this instance has had dropped, or a zeroed tally when nothing has.
224
+ *
225
+ * A copy, not the live object: this is published on the hot path and a caller
226
+ * that could mutate it would be able to erase the evidence.
227
+ */
228
+ function getAgentEventDropCounts(worktreeId, cliToolId, instanceId) {
229
+ const counts = dropCounts.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
230
+ if (!counts)
231
+ return emptyDropCounts();
232
+ return { ...counts, dedupDropped: { ...counts.dedupDropped } };
233
+ }
234
+ /** Epoch ms the current generation began, or null when none was opened. */
235
+ function currentGeneration(key) {
236
+ return generationStartedAt.get(key) ?? null;
237
+ }
238
+ /**
239
+ * The turn this instance is in, with the generation fence applied.
240
+ *
241
+ * A turn whose {@link TurnRecord.generationAt} is not the current generation was
242
+ * opened by a process that has been replaced. It is not deleted — the fence is
243
+ * cheap and deleting would lose the eviction tally — it is simply not this
244
+ * instance's turn any more.
245
+ */
246
+ function fencedTurn(key) {
247
+ const turn = agentTurns.get(key);
248
+ if (!turn)
249
+ return null;
250
+ if (turn.generationAt !== currentGeneration(key))
251
+ return null;
252
+ return turn;
253
+ }
254
+ /**
255
+ * {@link fencedTurn} with the staleness bound applied.
256
+ *
257
+ * The bound is applied by *closing* the turn rather than by hiding it, so
258
+ * `capture --json` says `closedBy: 'stale'` instead of going quiet — "the agent
259
+ * never reported the end of this turn" and "nothing has been reported at all"
260
+ * are different problems and an operator has to be able to tell them apart.
261
+ *
262
+ * Writes on read, which this module already does for corroboration, and is
263
+ * idempotent: the close is stamped from the event's own clock, so it does not
264
+ * move with the reader's `now`.
265
+ */
266
+ function effectiveTurn(key, now) {
267
+ const turn = fencedTurn(key);
268
+ if (turn === null)
269
+ return null;
270
+ if (turn.closedAt === null && now - turn.displayEvent.at >= provisional_turn_1.TURN_STALE_AFTER_MS) {
271
+ turn.closedAt = turn.displayEvent.at + provisional_turn_1.TURN_STALE_AFTER_MS;
272
+ turn.closedBy = 'stale';
273
+ }
274
+ return turn;
275
+ }
276
+ /** Next {@link TurnRecord.turnId}. See {@link turnSequence}. */
277
+ function nextTurnId(at) {
278
+ turnSequence.value += 1;
279
+ return `turn-${at}-${turnSequence.value}`;
280
+ }
281
+ /**
282
+ * Drop the decisions this turn is holding, counting them.
283
+ *
284
+ * The generation path is the one §4 D3 決定 2 names: a process that has been
285
+ * replaced cannot have its approvals answered, and leaving them behind would
286
+ * publish `waiting` for a pane whose dialog went away with the process that
287
+ * drew it.
288
+ */
289
+ function evictDecisions(key, turn) {
290
+ if (turn.pendingDecisions.length === 0)
291
+ return;
292
+ dropsFor(key).decisionEvicted += turn.pendingDecisions.length;
293
+ turn.pendingDecisions = [];
294
+ }
295
+ /**
296
+ * The decisions still describing something, dropping the ones that are not.
297
+ *
298
+ * The retention bound of {@link DIALOG_PENDING_MAX_MS}, applied on read and
299
+ * counted as `dialogTimedOut` so the release is visible. Kept here rather than
300
+ * in a timer for the reason the rest of this module has no timers: the state
301
+ * describes a live tmux session, and a reader is the only thing that ever needs
302
+ * the answer.
303
+ */
304
+ function livePendingDecisions(key, turn, now) {
305
+ const live = turn.pendingDecisions.filter((decision) => (0, provisional_turn_1.isDecisionLive)(decision, now));
306
+ const dropped = turn.pendingDecisions.length - live.length;
307
+ if (dropped > 0) {
308
+ dropsFor(key).dialogTimedOut += dropped;
309
+ turn.pendingDecisions = live;
310
+ }
311
+ return live;
312
+ }
313
+ /**
314
+ * The approvals this instance is blocked on right now (Issue #1930).
315
+ *
316
+ * Published as `structuredEvents.pendingDecisions`; `#1932` is what teaches
317
+ * `commandmate respond` to name one of these ids.
318
+ *
319
+ * @param now - Epoch ms; defaults to now
320
+ */
321
+ function getPendingDecisions(worktreeId, cliToolId, instanceId, now = Date.now()) {
322
+ const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
323
+ const turn = effectiveTurn(key, now);
324
+ if (turn === null)
325
+ return [];
326
+ return livePendingDecisions(key, turn, now);
327
+ }
328
+ /**
329
+ * The turn record for one instance, fenced and aged, or null.
330
+ *
331
+ * The seam `current-output-builder` publishes from and the suites drive. The
332
+ * returned object is the live record; nothing outside this module writes to it.
333
+ *
334
+ * @param now - Epoch ms; defaults to now
335
+ */
336
+ function getAgentTurn(worktreeId, cliToolId, instanceId, now = Date.now()) {
337
+ return effectiveTurn((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), now);
338
+ }
339
+ /** {@link getAgentTurn} projected onto the four published fields. */
340
+ function getPublishedAgentTurn(worktreeId, cliToolId, instanceId, now = Date.now()) {
341
+ return (0, provisional_turn_1.derivePublishedTurn)(getAgentTurn(worktreeId, cliToolId, instanceId, now));
342
+ }
343
+ /**
344
+ * Close the open turn on evidence that did not come from the agent (Issue #1930).
345
+ *
346
+ * The one seam for the two {@link TurnCloseReason} values nothing in the event
347
+ * stream can produce:
348
+ *
349
+ * - `scraper_evidence` — see {@link observeScraperCompletionEvidence}, which is
350
+ * the caller in this tree.
351
+ * - `resync_idle` — a source whose `capabilities.resync` lets it be re-read
352
+ * answering "not busy" after a dropped transport. The reconnect loop that
353
+ * would call it lives in `sources/opencode/subscription`, which is #1931's
354
+ * file and deliberately untouched here; the value is in the vocabulary and
355
+ * the seam accepts it, so wiring it is a call site rather than a new state.
356
+ *
357
+ * A no-op when no turn is open, which is the ordinary case: neither of these is
358
+ * a statement about a session that already reported it finished.
359
+ *
360
+ * @param at - Epoch ms; defaults to now
361
+ * @returns Whether a turn was closed
362
+ */
363
+ function closeAgentTurn(worktreeId, cliToolId, instanceId, closedBy, at = Date.now()) {
364
+ const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
365
+ const turn = fencedTurn(key);
366
+ if (turn === null || turn.closedAt !== null)
367
+ return false;
368
+ turn.closedAt = at;
369
+ turn.closedBy = closedBy;
370
+ return true;
371
+ }
372
+ /**
373
+ * Feed one poll's reading of the pane into the turn's completion counter
374
+ * (Issue #1930).
375
+ *
376
+ * `completed` is the scraper's own verdict — `ready` with positive evidence —
377
+ * NOT the merged one, which would be circular: the merge is what the structured
378
+ * layer's `running` overrides, so reading it back would only ever confirm this
379
+ * layer's own answer.
380
+ *
381
+ * See {@link SCRAPER_COMPLETION_POLLS} for why three, and for why closing here
382
+ * does not complete a `commandmate wait`.
383
+ *
384
+ * @param at - Epoch ms; defaults to now
385
+ * @returns Whether this poll closed the turn
386
+ */
387
+ function observeScraperCompletionEvidence(worktreeId, cliToolId, instanceId, completed, at = Date.now()) {
388
+ const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
389
+ const turn = fencedTurn(key);
390
+ if (turn === null || turn.closedAt !== null)
391
+ return false;
392
+ if (!completed) {
393
+ turn.scraperCompletionPolls = 0;
394
+ return false;
395
+ }
396
+ turn.scraperCompletionPolls += 1;
397
+ if (turn.scraperCompletionPolls < provisional_turn_1.SCRAPER_COMPLETION_POLLS)
398
+ return false;
399
+ turn.closedAt = at;
400
+ turn.closedBy = 'scraper_evidence';
401
+ return true;
402
+ }
151
403
  /**
152
404
  * Record that `instanceId` reported it stopped.
153
405
  *
@@ -164,22 +416,100 @@ function recordAgentStopEvent(worktreeId, cliToolId, instanceId, at = Date.now()
164
416
  function getLastStopEventAt(worktreeId, cliToolId, instanceId) {
165
417
  return lastStopEventAt.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
166
418
  }
419
+ /**
420
+ * The verdicts that mean this instance is inside a turn (Issue #1903).
421
+ *
422
+ * `waiting` is in the list because a dialog only happens *during* a turn: the
423
+ * agent asked for permission in the middle of the work it was doing, and the
424
+ * turn it belongs to is as open as one reading `running`. Leaving it out would
425
+ * fix the measured copilot window (`UserPromptSubmit` -> `SessionStart`) and
426
+ * leave the same hole one event further in.
427
+ *
428
+ * `ready` (`stop` / `idle_prompt`) and null (no event, a previous generation, a
429
+ * stale one, or an event with no verdict at all) both mean no turn is open.
430
+ */
431
+ const OPEN_TURN_STATUSES = ['running', 'waiting'];
432
+ /**
433
+ * Whether this `session_start` is the current turn's own, arriving late
434
+ * (Issue #1903).
435
+ *
436
+ * copilot 1.0.80 fires `UserPromptSubmit` and *then* `SessionStart`, 12-15 s
437
+ * later on a first turn — measured twice, and the payload says so itself: the
438
+ * captured `SessionStart` carries `initial_prompt` with the text of the prompt
439
+ * that was already submitted. Under the "newest event is the verdict" model
440
+ * that arrival erased `running / hook_prompt_submit`, because
441
+ * `agentEventToSessionStatus` answers null for `session_start`; the pane fell
442
+ * back to the scraper, which reads a generating copilot frame as `ready`
443
+ * (#1885), and a `commandmate wait` started inside that window exited 0 with
444
+ * `basis=scraper_ready` while the agent was still thinking.
445
+ *
446
+ * The rule is the design policy's (§4 D3 decision 2): *an event carrying no
447
+ * verdict does not close an open turn*. Here that is expressed as "it does not
448
+ * replace the event the verdict is read from either", because this model has
449
+ * one record where the turn model has two fields.
450
+ *
451
+ * Three conditions, and each one is load-bearing:
452
+ *
453
+ * 1. **The source declares it.** Not a tool id — flip copilot's capability to
454
+ * `false` and the late frame overwrites again, flip claude's to `true` and
455
+ * claude's would be held. #1901 reads `permissionHookPredictsDialog` the
456
+ * same way.
457
+ * 2. **A turn is open**, judged by {@link getStructuredSessionState} *as of the
458
+ * arriving event's own timestamp* — which is how this inherits the
459
+ * generation fence and the {@link STRUCTURED_STATE_MAX_AGE_MS} bound rather
460
+ * than growing a second copy of either. A `session_start` on an idle
461
+ * instance, after a `stop`, or as the first event of a session is recorded
462
+ * exactly as it always was, generation bump included. That is what keeps
463
+ * `/clear` working: it arrives as `session_end` (verdict null, so the turn
464
+ * is no longer open) followed by `session_start`.
465
+ * 3. **It does not name a different agent session.** A genuine restart inside
466
+ * the pane is a different `session_id`, and holding *that* frame would be
467
+ * the real cost of this rule — the instance would keep publishing the dead
468
+ * process's `running`. When either side is null the two are treated as the
469
+ * same session, because "no id" is the shape a hand-configured #1549 hook
470
+ * posts and the fix has to survive it; that residue is bounded by
471
+ * {@link STRUCTURED_STATE_MAX_AGE_MS}, after which the scraper takes the
472
+ * session back, and by `beginAgentEventGeneration` on every session
473
+ * CommandMate itself (re)starts.
474
+ */
475
+ function isLateSessionStart(worktreeId, cliToolId, instanceId, record, options) {
476
+ if (record.event !== 'session_start')
477
+ return false;
478
+ if (options.sessionStartMayArriveLate !== true)
479
+ return false;
480
+ const openTurn = getStructuredSessionState(worktreeId, cliToolId, instanceId, record.at);
481
+ if (openTurn === null || !OPEN_TURN_STATUSES.includes(openTurn.status))
482
+ return false;
483
+ const openSessionId = lastAgentEvent.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId))?.sessionId ?? null;
484
+ if (openSessionId === null || record.sessionId == null)
485
+ return true;
486
+ return record.sessionId === openSessionId;
487
+ }
167
488
  /**
168
489
  * Record any structured event against an instance (Issue #1722).
169
490
  *
170
491
  * Deliberately does not touch `lastStopEventAt`: that timestamp belongs to
171
492
  * `applyAgentStopEvent`, which writes it alongside the task transition it drives
172
493
  * so the two cannot disagree.
494
+ *
495
+ * @param options - What the delivering source declares about itself (#1903)
496
+ * @returns Whether the delivery was applied, so the caller can log a held one.
497
+ * Callers that do not care may ignore it; every pre-#1903 caller does.
173
498
  */
174
- function recordAgentEvent(worktreeId, cliToolId, instanceId, record) {
499
+ function recordAgentEvent(worktreeId, cliToolId, instanceId, record, options = {}) {
175
500
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
176
- lastAgentEvent.set(key, record);
177
- // Issue #1783: latch, never clear. An event without a model is the ordinary
178
- // case (Claude sends one on `SessionStart` alone), and reading it as "the
179
- // model is now unknown" would blank the display on the very next event.
180
- if (typeof record.model === 'string' && record.model !== '') {
181
- lastAgentModel.set(key, record.model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
501
+ if (isLateSessionStart(worktreeId, cliToolId, instanceId, record, options)) {
502
+ // Issue #1903. Held, not discarded: the model latch below is not part of
503
+ // the turn and never was. `SessionStart` is the one event Claude puts a
504
+ // model on, so a source that both declares this capability and reports a
505
+ // model would otherwise be the single case where the model is extracted and
506
+ // then dropped on the floor. copilot reports none today (#1783), which is
507
+ // exactly why this has to be decided here rather than left to be noticed.
508
+ latchAgentModel(key, record);
509
+ return { recorded: false, skipped: 'late-session-start' };
182
510
  }
511
+ lastAgentEvent.set(key, record);
512
+ latchAgentModel(key, record);
183
513
  applyAskUserQuestionTransition(key, record);
184
514
  if (record.event === 'session_start') {
185
515
  // The agent restarting inside a pane CommandMate never touched — `claude`
@@ -188,9 +518,59 @@ function recordAgentEvent(worktreeId, cliToolId, instanceId, record) {
188
518
  // new tmux session is. Recorded from the event's own timestamp, so the
189
519
  // event that opens a generation is never stale against it.
190
520
  generationStartedAt.set(key, record.at);
521
+ // Issue #1930: and the turn the previous process was in ends with it. Done
522
+ // here rather than inside the transition below so the two paths into a new
523
+ // generation — this one and `beginAgentEventGeneration` — close the turn in
524
+ // exactly one place.
525
+ fenceTurnForNewGeneration(key, record.at);
191
526
  }
192
- applyPromptWaitingTransition(key, record);
527
+ // Issue #1930: after the generation is settled, so a turn opened by this
528
+ // event belongs to the generation the event itself opened.
529
+ applyTurnTransition(key, record);
193
530
  applyAwaitingInstructionTransition(key, record);
531
+ return { recorded: true };
532
+ }
533
+ /**
534
+ * End the open turn because the process that owned it has been replaced
535
+ * (Issue #1930).
536
+ *
537
+ * The turn is closed and re-stamped into the new generation rather than
538
+ * deleted, which is the difference between `capture --json` reporting
539
+ * `closedBy: 'generation'` and reporting nothing at all. Its approvals go: they
540
+ * were raised by a process that no longer exists, and answering one would
541
+ * deliver a verdict into a slot nobody is holding.
542
+ */
543
+ function fenceTurnForNewGeneration(key, at) {
544
+ const turn = agentTurns.get(key);
545
+ if (!turn)
546
+ return;
547
+ evictDecisions(key, turn);
548
+ // A turn that had ALREADY ended is left where it is, which means the fence in
549
+ // `fencedTurn` hides it from here on. That asymmetry is deliberate: the
550
+ // previous process's `Stop` is not this process's, and re-stamping it into the
551
+ // new generation would publish `ready` — "the agent finished" — for a session
552
+ // nobody has typed into yet. Before #1930 the same protection came from
553
+ // comparing the event's timestamp against the generation.
554
+ if (turn.closedAt !== null)
555
+ return;
556
+ // An OPEN turn is closed and carried across, so `capture --json` can say
557
+ // `closedBy: 'generation'` rather than going quiet. `getStructuredSessionState`
558
+ // reads that reason as "nothing is known", so it publishes no verdict either
559
+ // way; what it buys is an operator being able to tell "the session was
560
+ // restarted under this turn" from "nothing was ever reported".
561
+ turn.closedAt = at;
562
+ turn.closedBy = 'generation';
563
+ turn.generationAt = at;
564
+ }
565
+ /**
566
+ * Issue #1783: latch, never clear. An event without a model is the ordinary
567
+ * case (Claude sends one on `SessionStart` alone), and reading it as "the model
568
+ * is now unknown" would blank the display on the very next event.
569
+ */
570
+ function latchAgentModel(key, record) {
571
+ if (typeof record.model === 'string' && record.model !== '') {
572
+ lastAgentModel.set(key, record.model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
573
+ }
194
574
  }
195
575
  /**
196
576
  * The third state: "this turn is over and nobody has told me what to do next"
@@ -278,77 +658,93 @@ function isAwaitingInstruction(worktreeId, cliToolId, instanceId) {
278
658
  return getAwaitingInstruction(worktreeId, cliToolId, instanceId) !== null;
279
659
  }
280
660
  /**
281
- * The `prompt_waiting` half of the state machine, driven by the same events
282
- * (Issue #1725).
283
- *
284
- * | event | effect on prompt_waiting |
285
- * |-----------------------------------|--------------------------|
286
- * | `notification(permission_prompt)` | **open**, confirmed |
287
- * | `notification(idle_prompt)` | release |
288
- * | `notification(other/none)` | unchanged |
289
- * | `stop` | release |
290
- * | `user_prompt_submit` | release |
291
- * | `session_start` / `session_end` | release |
292
- * | `pre_tool_use` | unchanged |
293
- * | `post_tool_use` | release |
294
- *
295
- * `pre_tool_use` leaves this half alone on purpose (Issue #1726). It is the
296
- * `AskUserQuestion` invocation, and a picker being *about to be drawn* is not
297
- * the kind of fact this state can carry: nothing marks the picker as answered
298
- * (§5.6 measured total silence through the selection and confirmation screens),
299
- * so an open record from this source would keep asserting `waiting` for up to
300
- * {@link STRUCTURED_STATE_MAX_AGE_MS} after a human answered in the terminal.
301
- * Whether that screen is up stays the scraper's question; what Issue #1726 adds
302
- * is the *content* of the question, kept in a separate record that decides no
303
- * status. The `PermissionRequest` `AskUserQuestion` also raises still opens a
304
- * provisional record here, exactly as it did in #1725 unchanged.
305
- *
306
- * The release set is the one the spike could actually justify. `Stop` is
307
- * *measured*: answering an `AskUserQuestion` resumed the turn and delivered a
308
- * `Stop` (§5.6, received 23 → 24). `PostToolUse` — which Issue #1725's text
309
- * proposed was **not** observed at all in the spike, so it is not wired up
310
- * here; the route does not even map it to a lifecycle event.
311
- * `user_prompt_submit` is release by construction: a prompt was typed at the
312
- * composer, so no dialog was in front of it. `session_start` / `session_end`
313
- * are generation changes, and a dialog does not survive one.
314
- *
315
- * `idle_prompt` releases for the same reason it maps to `ready`: the agent
316
- * reporting it is sitting at the composer waiting for input is the agent saying
317
- * nothing is in front of that composer.
318
- *
319
- * What is deliberately absent is a timer that closes it. There is no event for
320
- * "the human answered the dialog" Claude emits none (§5.6) — so the other
321
- * half of the release rule lives in `current-output-builder`, where the scraper
322
- * can be asked whether the frame it saw a moment ago is still blocking.
323
- */
324
- function applyPromptWaitingTransition(key, record) {
661
+ * The turn transition: what one delivery does to {@link TurnRecord}
662
+ * (Issue #1930, §4 D3 決定 2).
663
+ *
664
+ * | event | turn | dialog | display |
665
+ * |------------------------------------|-------------------------|----------|---------|
666
+ * | `user_prompt_submit` | **opens a new one** | release | yes |
667
+ * | `pre_tool_use` | continues, else opens | unchanged| yes |
668
+ * | `post_tool_use` | continues, else opens | release | yes |
669
+ * | `stop` (this session) | **closes** `stop` | release | yes |
670
+ * | `stop` (another session) | unchanged | unchanged| no |
671
+ * | `session_end` | **closes** `session_end`| release | no |
672
+ * | `session_start` | new generation (above) | evicted | no |
673
+ * | `notification(permission_prompt)` | unchanged | **open** | only to bootstrap |
674
+ * | `notification(permission_replied)` | unchanged | release | no |
675
+ * | `notification(idle_prompt)` | unchanged | release | yes |
676
+ * | `notification(anything else)` | unchanged | unchanged| no |
677
+ *
678
+ * Four rows are the whole point of the Issue, and each of them was a defect
679
+ * under "the newest event is the verdict":
680
+ *
681
+ * - **`user_prompt_submit` opens a new turn, the tool-call events continue the
682
+ * open one.** That is what makes {@link TurnRecord.turnId} an identity: #1926
683
+ * re-stamped it on every `pre_tool_use`, so a consumer reading a changed id
684
+ * as "a new turn began" false-positived several times inside one turn. `wait`
685
+ * reads it that way now.
686
+ * - **A `stop` naming another session changes nothing.** opencode publishes
687
+ * `session.idle` for every session its server holds, other processes'
688
+ * included (#1758 §5.6).
689
+ * - **`session_start` / `session_end` / an unknown notification /
690
+ * `permission_replied` do not become the displayed event.** An event carrying
691
+ * no verdict must not erase one. #1903 fixed the single measured instance
692
+ * (copilot's late `SessionStart`) by holding the delivery; the rule is
693
+ * general here, and the capability it reads is still the only reason a
694
+ * `session_start` is held rather than recorded.
695
+ * - **`notification(permission_prompt)` does not become the displayed event
696
+ * when a turn already exists.** The dialog is a fact about the *pane*, and
697
+ * it is published by the decision ledger below. Letting it overwrite the
698
+ * display is how the pre-#1898 `waiting` outlived the approval that caused
699
+ * it: released, the record still said `waiting`, because the only thing it
700
+ * could read was the event that opened the dialog.
701
+ *
702
+ * `idle_prompt` is the one row that publishes `ready` **without** closing the
703
+ * turn, and that is a measurement rather than an oversight: #1839 caught Claude
704
+ * emitting it 62 s into a turn that ran nothing, so it cannot be a turn
705
+ * boundary. `wait`'s gate stays armed through it; what changes is only what the
706
+ * pane displays.
707
+ */
708
+ function applyTurnTransition(key, record) {
709
+ const generation = currentGeneration(key);
710
+ // An event stamped before the current generation was produced by a process
711
+ // that has been replaced. Refusing it here is what makes re-recording a stale
712
+ // `user_prompt_submit` at its original timestamp a no-op.
713
+ if (generation !== null && record.at < generation)
714
+ return;
325
715
  switch (record.event) {
326
- case 'notification':
327
- if (record.detail === 'permission_prompt') {
328
- openPromptWaiting(key, {
329
- source: 'notification',
330
- message: record.message ?? null,
331
- at: record.at,
332
- });
333
- }
334
- else if (record.detail === 'idle_prompt') {
335
- promptWaiting.delete(key);
336
- }
337
- return;
338
- case 'stop':
339
716
  case 'user_prompt_submit':
340
- case 'session_start':
341
- case 'session_end':
342
- promptWaiting.delete(key);
717
+ openTurn(key, record, generation, { continueOpen: false });
718
+ releaseAllDecisions(key);
343
719
  return;
344
720
  case 'post_tool_use':
721
+ openTurn(key, record, generation, { continueOpen: true });
345
722
  // The tool call the dialog was gating has finished, so somebody answered
346
723
  // it (Issue #1726). This is the release #1725 could not have: it had no
347
724
  // event meaning "the human answered", only `Stop` meaning "the turn
348
725
  // ended", which can be minutes later.
349
- promptWaiting.delete(key);
726
+ releaseAllDecisions(key);
350
727
  return;
351
728
  case 'pre_tool_use':
729
+ // Deliberately leaves the dialog alone (Issue #1726). It is the
730
+ // `AskUserQuestion` invocation, and a picker being *about to be drawn* is
731
+ // not a fact this state can carry.
732
+ openTurn(key, record, generation, { continueOpen: true });
733
+ return;
734
+ case 'stop':
735
+ applyStopToTurn(key, record, generation);
736
+ return;
737
+ case 'session_end':
738
+ applySessionEndToTurn(key, record);
739
+ releaseAllDecisions(key);
740
+ return;
741
+ case 'session_start':
742
+ // The generation was opened by `recordAgentEvent` before this ran, and
743
+ // `fenceTurnForNewGeneration` closed the turn with it. Nothing further:
744
+ // the frame carries no verdict, so it is not the displayed event either.
745
+ return;
746
+ case 'notification':
747
+ applyNotificationToTurn(key, record, generation);
352
748
  return;
353
749
  default:
354
750
  // exhaustive check: a new AgentEventType must decide its transition here
@@ -356,6 +752,304 @@ function applyPromptWaitingTransition(key, record) {
356
752
  return;
357
753
  }
358
754
  }
755
+ /** The three fields the turn keeps from a record. */
756
+ function displayOf(record) {
757
+ return { event: record.event, at: record.at, detail: record.detail };
758
+ }
759
+ /**
760
+ * Open a turn for this event, or move the display onto an open one.
761
+ *
762
+ * @param continueOpen - Whether an already-open turn of the same session is
763
+ * this event's turn. False for `user_prompt_submit`, which IS a new turn.
764
+ */
765
+ function openTurn(key, record, generation, { continueOpen }) {
766
+ const turn = fencedTurn(key);
767
+ const openHere = turn !== null &&
768
+ turn.closedAt === null &&
769
+ turn.openedAt !== null &&
770
+ (0, provisional_turn_1.closesTurn)(turn, record.sessionId);
771
+ if (continueOpen && openHere) {
772
+ turn.displayEvent = displayOf(record);
773
+ // A hand-configured relay posts no session id, so a turn can learn one
774
+ // partway through. Learning it is what lets a later `stop` be matched.
775
+ turn.sessionId ??= record.sessionId;
776
+ turn.scraperCompletionPolls = 0;
777
+ return;
778
+ }
779
+ // A dialog-only record (openedAt null) is not a turn; its decisions belong to
780
+ // the turn that is opening now, because that is when they were raised.
781
+ const carried = turn !== null && turn.openedAt === null && turn.closedAt === null
782
+ ? turn.pendingDecisions
783
+ : [];
784
+ agentTurns.set(key, {
785
+ turnId: nextTurnId(record.at),
786
+ sessionId: record.sessionId,
787
+ openedAt: record.at,
788
+ closedAt: null,
789
+ closedBy: null,
790
+ generationAt: generation,
791
+ displayEvent: displayOf(record),
792
+ pendingDecisions: carried,
793
+ scraperCompletionPolls: 0,
794
+ });
795
+ boundTurnMap();
796
+ }
797
+ /**
798
+ * Apply a `stop` — the only event that is the agent saying its turn is over.
799
+ *
800
+ * A `stop` for another session leaves everything alone; see {@link closesTurn}.
801
+ * A `stop` with no turn open still records one, because "this instance last
802
+ * reported it stopped at T" is the fact `ready` is published from, and the
803
+ * opening it never saw is published as null rather than guessed.
804
+ */
805
+ function applyStopToTurn(key, record, generation) {
806
+ const turn = fencedTurn(key);
807
+ if (turn !== null && turn.closedAt === null && turn.openedAt !== null) {
808
+ if (!(0, provisional_turn_1.closesTurn)(turn, record.sessionId))
809
+ return;
810
+ turn.closedAt = record.at;
811
+ turn.closedBy = 'stop';
812
+ turn.displayEvent = displayOf(record);
813
+ releaseAllDecisions(key);
814
+ return;
815
+ }
816
+ agentTurns.set(key, {
817
+ turnId: nextTurnId(record.at),
818
+ sessionId: record.sessionId,
819
+ openedAt: null,
820
+ closedAt: record.at,
821
+ closedBy: 'stop',
822
+ generationAt: generation,
823
+ displayEvent: displayOf(record),
824
+ pendingDecisions: [],
825
+ scraperCompletionPolls: 0,
826
+ });
827
+ boundTurnMap();
828
+ }
829
+ /**
830
+ * `session_end`: the agent session this instance was talking to is gone.
831
+ *
832
+ * **Overwrites an existing close reason**, which is the one place in this
833
+ * module where a later event rewrites an earlier verdict, and #1723's contract
834
+ * is why. `/clear` emits `SessionEnd(reason=clear)` on a session that is alive
835
+ * and about to keep going, and the turn before it may perfectly well have ended
836
+ * with a `Stop`. Leaving that `Stop` standing would keep publishing "the agent
837
+ * finished" about a conversation that no longer exists — the integration pin is
838
+ * `current-output-structured-status-1723`, which asserts the session goes back
839
+ * to the scraper the moment `SessionEnd` lands.
840
+ *
841
+ * `closedBy: 'session_end'` publishes no verdict of its own (see
842
+ * {@link getStructuredSessionState}), so this is a retirement rather than a
843
+ * different answer.
844
+ */
845
+ function applySessionEndToTurn(key, record) {
846
+ const turn = fencedTurn(key);
847
+ if (turn === null)
848
+ return;
849
+ if (!(0, provisional_turn_1.closesTurn)(turn, record.sessionId))
850
+ return;
851
+ turn.closedAt = record.at;
852
+ turn.closedBy = 'session_end';
853
+ }
854
+ /**
855
+ * The notification rows of the transition table.
856
+ *
857
+ * Matched on `notification_type` ({@link AgentEventRecord.detail}), never on the
858
+ * human-facing `message` (D3): the observed messages are English prose the agent
859
+ * is free to reword.
860
+ */
861
+ function applyNotificationToTurn(key, record, generation) {
862
+ if (record.detail === 'permission_prompt') {
863
+ if (record.promptSettled === true) {
864
+ // Issue #1898: adjudicated before it was recorded, and the verdict
865
+ // reached the agent. There is no dialog and there never was one for a
866
+ // human to answer — opening a record for it published `waiting` for the
867
+ // whole of the tool call that followed (measured at eight seconds).
868
+ releaseSettledDecision(key, record);
869
+ return;
870
+ }
871
+ openDecision(key, generation, {
872
+ source: 'notification',
873
+ at: record.at,
874
+ message: record.message ?? null,
875
+ toolName: null,
876
+ decisionId: record.decisionId ?? null,
877
+ bootstrapDisplay: displayOf(record),
878
+ });
879
+ return;
880
+ }
881
+ if (record.detail === agent_event_types_1.PERMISSION_REPLIED_DETAIL) {
882
+ // Issue #1898. The agent's own statement that the dialog is gone — whoever
883
+ // answered it. Not a word this build reads a status from, so it decides
884
+ // nothing; all it does is retire the record.
885
+ if (record.promptSettled === true)
886
+ releaseSettledDecision(key, record);
887
+ return;
888
+ }
889
+ if (record.detail === 'idle_prompt') {
890
+ // The agent reporting it is sitting at the composer waiting for input is the
891
+ // agent saying nothing is in front of that composer.
892
+ releaseAllDecisions(key);
893
+ const turn = fencedTurn(key);
894
+ if (turn !== null && turn.closedAt === null) {
895
+ turn.displayEvent = displayOf(record);
896
+ return;
897
+ }
898
+ if (turn !== null)
899
+ return;
900
+ // Nothing to attach it to — the first thing this instance ever said, or the
901
+ // first since a generation. It still carries a verdict (`ready`), and #1723
902
+ // publishes it, so a display-only record is opened to hold it. `openedAt`
903
+ // stays null: an agent sitting at its composer is not in a turn, and a
904
+ // record that claimed otherwise would gate `wait` on a turn nobody opened.
905
+ agentTurns.set(key, {
906
+ turnId: nextTurnId(record.at),
907
+ sessionId: record.sessionId,
908
+ openedAt: null,
909
+ closedAt: null,
910
+ closedBy: null,
911
+ generationAt: generation,
912
+ displayEvent: displayOf(record),
913
+ pendingDecisions: [],
914
+ scraperCompletionPolls: 0,
915
+ });
916
+ boundTurnMap();
917
+ return;
918
+ }
919
+ // An unrecognised notification type is not evidence of anything, and guessing
920
+ // would be worse than the scraper.
921
+ }
922
+ /**
923
+ * Open, or refresh, a dialog record on this instance's turn.
924
+ *
925
+ * `bootstrapDisplay` is used only when there is no record at all to hang the
926
+ * decision on. That record is deliberately **not** an open turn — its
927
+ * `openedAt` is null — so releasing the decision hands the pane back to the
928
+ * scraper rather than asserting `running` for a turn nobody ever saw open.
929
+ */
930
+ function openDecision(key, generation, input) {
931
+ let turn = fencedTurn(key);
932
+ if (turn === null) {
933
+ turn = {
934
+ turnId: nextTurnId(input.at),
935
+ sessionId: null,
936
+ openedAt: null,
937
+ closedAt: null,
938
+ closedBy: null,
939
+ generationAt: generation,
940
+ displayEvent: input.bootstrapDisplay,
941
+ pendingDecisions: [],
942
+ scraperCompletionPolls: 0,
943
+ };
944
+ agentTurns.set(key, turn);
945
+ boundTurnMap();
946
+ }
947
+ else if (turn.closedAt !== null) {
948
+ // The dialog is being raised after the turn it would have belonged to
949
+ // ended — a `PermissionRequest` racing a `Stop`, or a re-check landing
950
+ // late. Re-open a dialog-only record rather than resurrecting the turn.
951
+ turn = {
952
+ turnId: nextTurnId(input.at),
953
+ sessionId: null,
954
+ openedAt: null,
955
+ closedAt: null,
956
+ closedBy: null,
957
+ generationAt: generation,
958
+ displayEvent: input.bootstrapDisplay,
959
+ pendingDecisions: [],
960
+ scraperCompletionPolls: 0,
961
+ };
962
+ agentTurns.set(key, turn);
963
+ boundTurnMap();
964
+ }
965
+ // Issue #1930 / S1: an id that fails validation is DISCARDED, not truncated —
966
+ // a truncated id compares equal to a different id sharing its prefix, and the
967
+ // reply to one approval would then retire another's record.
968
+ const decisionId = input.decisionId === null ? null : (0, provisional_turn_1.acceptExternalId)(input.decisionId);
969
+ if (input.decisionId !== null && decisionId === null)
970
+ dropsFor(key).idsDiscarded += 1;
971
+ const confirmed = input.source === 'notification';
972
+ // Which record, if any, this report is a second sighting of.
973
+ //
974
+ // - an id matches its own record;
975
+ // - an id with no record of its own may still be *confirming* the anonymous
976
+ // prediction that forecast it. That is the measured pair: a
977
+ // `PermissionRequest` this server declined to decide fires ~6 s before the
978
+ // `Notification(permission_prompt)` that proves the dialog, and the
979
+ // request carries no id;
980
+ // - an anonymous report merges only with another anonymous one. A source
981
+ // that publishes no ids therefore keeps exactly one dialog record per
982
+ // instance, which is what #1725 did and the honest limit for it — matching
983
+ // an anonymous report against an *identified* record would let one
984
+ // approval's forecast confirm a different approval's dialog.
985
+ const existing = decisionId !== null
986
+ ? (turn.pendingDecisions.find((decision) => decision.decisionId === decisionId) ??
987
+ turn.pendingDecisions.find((decision) => decision.decisionId === null && decision.source === 'permission-request'))
988
+ : turn.pendingDecisions.find((decision) => decision.decisionId === null);
989
+ if (existing) {
990
+ // The same dialog, reported twice: `PermissionRequest` predicted it and the
991
+ // `Notification` then proved it. Keep the earliest `at` — that is when the
992
+ // human was first blocked, and it is what the scraper-release grace and the
993
+ // age bound are measured from — and take the confirmation.
994
+ existing.message = (0, provisional_turn_1.boundDecisionMessage)(input.message) ?? existing.message;
995
+ existing.toolName = (0, provisional_turn_1.boundDecisionToolName)(input.toolName) ?? existing.toolName;
996
+ existing.decisionId ??= decisionId;
997
+ if (confirmed) {
998
+ existing.source = 'notification';
999
+ existing.confirmedAt ??= input.at;
1000
+ }
1001
+ return;
1002
+ }
1003
+ if (turn.pendingDecisions.length >= provisional_turn_1.MAX_PENDING_DECISIONS_PER_TURN) {
1004
+ // S14(d). Counted rather than dropped in silence: an agent raising more
1005
+ // approvals than this in one turn is a fact an operator has to be able to
1006
+ // see, and the oldest is the one a human has been looking at.
1007
+ dropsFor(key).decisionOverflow += 1;
1008
+ return;
1009
+ }
1010
+ turn.pendingDecisions.push({
1011
+ decisionId,
1012
+ at: input.at,
1013
+ source: input.source,
1014
+ message: (0, provisional_turn_1.boundDecisionMessage)(input.message),
1015
+ toolName: (0, provisional_turn_1.boundDecisionToolName)(input.toolName),
1016
+ confirmedAt: confirmed ? input.at : null,
1017
+ scraperCorroborated: false,
1018
+ recorded: false,
1019
+ });
1020
+ }
1021
+ /** Retire every dialog this instance was holding. */
1022
+ function releaseAllDecisions(key) {
1023
+ const turn = fencedTurn(key);
1024
+ if (turn !== null)
1025
+ turn.pendingDecisions = [];
1026
+ }
1027
+ /**
1028
+ * Retire the record a verdict was delivered for (Issue #1898).
1029
+ *
1030
+ * Matched on {@link AgentEventRecord.decisionId} when both sides have one: an
1031
+ * agent that can run two approvals at once would otherwise have the reply to
1032
+ * the first retire the record for the second. When either side is anonymous the
1033
+ * release is unconditional — an unmatched id is a source that publishes none,
1034
+ * and refusing to act there would leave the pre-#1898 stall in place for it.
1035
+ */
1036
+ function releaseSettledDecision(key, record) {
1037
+ const turn = fencedTurn(key);
1038
+ if (turn === null || turn.pendingDecisions.length === 0)
1039
+ return;
1040
+ const settledId = record.decisionId === null || record.decisionId === undefined
1041
+ ? null
1042
+ : (0, provisional_turn_1.acceptExternalId)(record.decisionId);
1043
+ if (settledId === null) {
1044
+ turn.pendingDecisions = [];
1045
+ return;
1046
+ }
1047
+ turn.pendingDecisions = turn.pendingDecisions.filter((decision) => decision.decisionId !== null && decision.decisionId !== settledId);
1048
+ }
1049
+ /** Keep the turn map bounded the way every other map in this module is. */
1050
+ function boundTurnMap() {
1051
+ trimOldestEntries(agentTurns, MAX_RECENT_EVENT_KEYS);
1052
+ }
359
1053
  /**
360
1054
  * @returns The last structured event reported by this instance, or null when it
361
1055
  * has reported none.
@@ -456,22 +1150,15 @@ function getLastKnownAgentEffort(worktreeId, cliToolId, instanceId) {
456
1150
  }
457
1151
  /**
458
1152
  * How long a structured verdict is trusted after the event that produced it
459
- * (Issue #1723).
1153
+ * (Issue #1723; one expression since #1930).
460
1154
  *
461
- * Hooks are fail-open by design: a timeout or a refused connection costs the
462
- * event and never the agent's turn (`agent-hooks-live-verification.md` §1.1).
463
- * A *lost* `Stop` is therefore possible, and without a bound it would leave
464
- * this layer asserting `running` for a session that finished `commandmate
465
- * wait` would then poll until `--timeout` on a session the scraper could have
466
- * called done. The bound converts "forever" into "at most this long", after
467
- * which the scraper takes the session back.
468
- *
469
- * Deliberately generous. A single agent turn running past 30 minutes is
470
- * ordinary for the workloads this tool exists to babysit, and expiring a live
471
- * verdict mid-turn costs the whole benefit of the two-layer split. Expiry is
472
- * not a failure mode: it is exactly the pre-#1723 behaviour.
1155
+ * The turn model owns the number now see `provisional-turn`'s
1156
+ * {@link TURN_STALE_AFTER_MS}, which documents why it is 30 minutes. This name
1157
+ * is kept because it is what `status-evidence` and the #1723/#1725/#1903 suites
1158
+ * read, and because "the age bound on a verdict" and "how long a turn may run
1159
+ * unheard-from" really are the same fact rather than two that happen to agree.
473
1160
  */
474
- exports.STRUCTURED_STATE_MAX_AGE_MS = 30 * 60 * 1000;
1161
+ exports.STRUCTURED_STATE_MAX_AGE_MS = provisional_turn_1.TURN_STALE_AFTER_MS;
475
1162
  /**
476
1163
  * Open a new generation for this instance, invalidating everything reported
477
1164
  * before now (Issue #1723).
@@ -492,11 +1179,13 @@ exports.STRUCTURED_STATE_MAX_AGE_MS = 30 * 60 * 1000;
492
1179
  function beginAgentEventGeneration(worktreeId, cliToolId, instanceId, at = Date.now()) {
493
1180
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
494
1181
  generationStartedAt.set(key, at);
495
- // A dialog belongs to the process that drew it (Issue #1725). The generation
496
- // bound in `getStructuredPromptWaiting` would already hide it, but leaving the
497
- // record behind means a later `corroborate`/`markRecorded` would mutate a
498
- // dead episode.
499
- promptWaiting.delete(key);
1182
+ // Issue #1930: the turn the previous process was in ends with that process,
1183
+ // and the approvals it was holding go with it they were raised against a
1184
+ // slot nobody is holding open any more. The turn record is kept, closed as
1185
+ // `closedBy: 'generation'`, so `capture --json` can say why it ended instead
1186
+ // of going quiet. The dialogs #1725 kept in a map of their own live on that
1187
+ // record now, which is what makes this one call retire both.
1188
+ fenceTurnForNewGeneration(key, at);
500
1189
  // Same reasoning for the question that dialog was asking (Issue #1726).
501
1190
  askUserQuestion.delete(key);
502
1191
  // And for "waiting for your input" (Issue #1786): a new process has not asked
@@ -514,6 +1203,10 @@ function beginAgentEventGeneration(worktreeId, cliToolId, instanceId, at = Date.
514
1203
  // a relaunch would show the old process's effort with no frame left that
515
1204
  // could contradict it. The next poll re-reads a live footer immediately.
516
1205
  capturedModelInfo.delete(key);
1206
+ // Issue #1899: the ids claimed for this key were issued by the process that
1207
+ // has just been replaced. Unlike the time-window keys, they never expire on
1208
+ // their own, so a generation is the only thing that retires them.
1209
+ recentEventIdentities.delete(key);
517
1210
  }
518
1211
  /**
519
1212
  * @returns Epoch ms the current generation began, or null when no generation
@@ -536,143 +1229,153 @@ function discardAgentEventState(worktreeId, cliToolId, instanceId) {
536
1229
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
537
1230
  lastAgentEvent.delete(key);
538
1231
  generationStartedAt.delete(key);
539
- promptWaiting.delete(key);
1232
+ // Issue #1930: the turn, and with it the dialogs it was holding. Not counted
1233
+ // as an eviction — the session is gone, so there is nobody left to tell.
1234
+ agentTurns.delete(key);
1235
+ dropCounts.delete(key);
540
1236
  askUserQuestion.delete(key);
541
1237
  awaitingInstruction.delete(key);
542
1238
  // Issue #1783: the session that was on this model no longer exists.
543
1239
  lastAgentModel.delete(key);
544
1240
  // Issue #1784: nor does the pane its footer was read from.
545
1241
  capturedModelInfo.delete(key);
1242
+ // Issue #1899: nor do the frame ids that session issued.
1243
+ recentEventIdentities.delete(key);
546
1244
  }
547
1245
  /**
548
- * The status this instance's structured events imply, or null when they imply
549
- * nothing (Issue #1723).
1246
+ * The status this instance's turn implies, or null when it implies nothing
1247
+ * (Issue #1723, re-derived from the turn model in #1930).
550
1248
  *
551
1249
  * Null is the answer for every session on a machine where hooks never fire,
552
1250
  * which is what keeps the unconfigured environment on exactly the behaviour it
553
- * had before this Issue. It is also the answer when:
554
- *
555
- * - the last event carries no verdict (`session_start`, `session_end`, a
556
- * `Notification` of an unrecognised type) — see `agentEventToSessionStatus`;
557
- * - the event predates the current generation, i.e. it belongs to a previous
558
- * Claude process in a reused pane;
559
- * - the event is older than {@link STRUCTURED_STATE_MAX_AGE_MS}.
1251
+ * had before #1723. It is also the answer when:
1252
+ *
1253
+ * - the turn belongs to a previous generation, i.e. to an agent process that
1254
+ * used to live in this pane;
1255
+ * - nothing has been heard about it for {@link STRUCTURED_STATE_MAX_AGE_MS};
1256
+ * - the turn ended for a reason that says nothing about whether the pane is
1257
+ * free `session_end` (a `/clear` mid-turn), `stale`, `generation`, or the
1258
+ * two the *screen* closed it on (`scraper_evidence`, `resync_idle`). Only
1259
+ * the agent's own `Stop` publishes `ready` over this channel; everything
1260
+ * else hands the pane back to the scraper, which is the layer those closures
1261
+ * came from in the first place;
1262
+ * - no decision stands and the displayed event carries no verdict.
1263
+ *
1264
+ * ## The derivation, in the order it is applied
1265
+ *
1266
+ * | condition | verdict |
1267
+ * |---------------------------------------------|-------------------------------|
1268
+ * | an unanswered decision is live | `waiting` |
1269
+ * | the turn was closed by `stop` | `ready` / `hook_stop` |
1270
+ * | the turn was closed by anything else | null |
1271
+ * | the displayed event carries a verdict | that verdict |
1272
+ * | a turn is open and it does not | `running` / `hook_prompt_submit` |
1273
+ * | otherwise | null |
1274
+ *
1275
+ * §4 D3's `running ⟺ open turn ∧ 未裁定なし ∧ 現世代` falls out of the first,
1276
+ * fourth and fifth rows together with the generation fence — with one measured
1277
+ * exception written into the table above it: `notification(idle_prompt)`
1278
+ * publishes `ready` **without** closing the turn (#1839 caught Claude emitting
1279
+ * it 62 s into a turn that ran nothing, so it cannot be a boundary). A reader
1280
+ * that needs the boundary rather than the display reads `closedAt`.
560
1281
  *
561
1282
  * Whether the tmux session is alive is NOT checked here — the caller
562
- * (`buildCurrentOutput`) has already answered that question with the CLI tool's
563
- * own `isRunning()` and returned early, and asking twice would mean a second
564
- * tmux round-trip on the hot path for an answer it is holding.
1283
+ * (`buildCurrentOutput`) has already answered that with the CLI tool's own
1284
+ * `isRunning()` and returned early, and asking twice would mean a second tmux
1285
+ * round-trip on the hot path for an answer it is holding.
565
1286
  *
566
1287
  * @param now - Epoch ms; defaults to now
567
1288
  */
568
1289
  function getStructuredSessionState(worktreeId, cliToolId, instanceId, now = Date.now()) {
569
1290
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
570
- const record = lastAgentEvent.get(key);
571
- if (!record)
1291
+ const turn = effectiveTurn(key, now);
1292
+ if (turn === null)
572
1293
  return null;
573
- const generation = generationStartedAt.get(key);
574
- if (generation !== undefined && record.at < generation)
1294
+ const display = turn.displayEvent;
1295
+ if (now - display.at >= exports.STRUCTURED_STATE_MAX_AGE_MS)
575
1296
  return null;
576
- if (now - record.at >= exports.STRUCTURED_STATE_MAX_AGE_MS)
577
- return null;
578
- const verdict = (0, status_mapping_1.agentEventToSessionStatus)(record.event, record.detail);
579
- if (verdict === null)
1297
+ const seen = { event: display.event, at: display.at, detail: display.detail };
1298
+ const pending = livePendingDecisions(key, turn, now);
1299
+ if (pending.length > 0) {
1300
+ // The two kinds of evidence stay apart in the reason token, exactly as
1301
+ // `structuredWaitingReason` keeps them apart for the payload: a
1302
+ // `Notification` is proof a dialog exists, a `PermissionRequest` this
1303
+ // server declined to decide is the prediction that one is about to.
1304
+ const reason = pending[0].source === 'notification'
1305
+ ? status_mapping_1.HOOK_STATUS_REASON.PERMISSION_PROMPT
1306
+ : status_mapping_1.HOOK_STATUS_REASON.PERMISSION_REQUEST;
1307
+ return { status: 'waiting', reason, ...seen };
1308
+ }
1309
+ if (turn.closedAt !== null) {
1310
+ if (turn.closedBy === 'stop') {
1311
+ return { status: 'ready', reason: status_mapping_1.HOOK_STATUS_REASON.STOP, ...seen };
1312
+ }
580
1313
  return null;
581
- return { ...verdict, event: record.event, at: record.at, detail: record.detail };
1314
+ }
1315
+ const verdict = (0, status_mapping_1.agentEventToSessionStatus)(display.event, display.detail);
1316
+ // A `waiting` read off the displayed event is a dialog nothing is holding any
1317
+ // more — the ledger above is what answers that question now. The turn is
1318
+ // still open, so the agent is still working.
1319
+ if (verdict === null || verdict.status === 'waiting') {
1320
+ if (turn.openedAt === null)
1321
+ return null;
1322
+ return { status: 'running', reason: status_mapping_1.HOOK_STATUS_REASON.PROMPT_SUBMIT, ...seen };
1323
+ }
1324
+ return { ...verdict, ...seen };
582
1325
  }
583
1326
  /**
584
1327
  * How long a `permission-request`-sourced record survives without
585
- * corroboration (Issue #1725).
586
- *
587
- * The two sources differ in kind. `Notification(permission_prompt)` fires ~6 s
588
- * *after* the dialog is drawn and only when one is (§5.5): its arrival is proof.
589
- * A no-decision `PermissionRequest` fires *before* the dialog, so it is a
590
- * prediction — an accurate one (D5 measured `{}` landing in the ordinary TUI
591
- * approval flow), but a prediction about a version of Claude, a permission mode
592
- * and a rule set this server does not get to see in full.
593
- *
594
- * A prediction that nothing confirms must expire, because the cost of it
595
- * sticking is a `wait --on-prompt agent` that exits 10 on a session with no
596
- * dialog in it — a worker aborted for nothing. 20 s is generous against both
597
- * confirmations: the `Notification` lands at ~6 s, and the scraper reads the
598
- * pane through a 5 s capture cache on a 5 s poll. Expiring is not a failure
599
- * mode; it is the pre-#1725 behaviour for a dialog neither layer ever saw.
600
- */
601
- exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = 20_000;
602
- /** Open, or refresh, the prompt-waiting record for one instance. */
603
- function openPromptWaiting(key, input) {
604
- const existing = promptWaiting.get(key);
605
- const confirmed = input.source === 'notification';
606
- if (existing) {
607
- // The same dialog, reported twice: `PermissionRequest` predicted it and the
608
- // `Notification` then proved it. Keep the earliest `at` — that is when the
609
- // human was first blocked, and it is what the scraper-release grace and the
610
- // age bound are measured from — and take the confirmation.
611
- existing.message = input.message ?? existing.message;
612
- existing.toolName = input.toolName ?? existing.toolName;
613
- if (confirmed) {
614
- existing.source = 'notification';
615
- existing.confirmedAt ??= input.at;
616
- }
617
- return;
618
- }
619
- promptWaiting.set(key, {
620
- at: input.at,
621
- source: input.source,
622
- message: input.message
623
- ? input.message.slice(0, structured_prompt_1.MAX_STRUCTURED_PROMPT_MESSAGE_LENGTH)
624
- : null,
625
- toolName: input.toolName ?? null,
626
- confirmedAt: confirmed ? input.at : null,
627
- scraperCorroborated: false,
628
- recorded: false,
629
- });
630
- }
1328
+ * corroboration (Issue #1725; one expression since #1930).
1329
+ *
1330
+ * The turn model owns the number — see `provisional-turn`'s
1331
+ * {@link DIALOG_PENDING_MAX_MS}, whose doc has the measurement. Kept under this
1332
+ * name for `permission-decision-service`, which cites it, and for the #1725
1333
+ * suite.
1334
+ */
1335
+ exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = provisional_turn_1.DIALOG_PENDING_MAX_MS.predicted;
631
1336
  /**
632
1337
  * Report that a dialog is open because the agent asked us to adjudicate one and
633
1338
  * we declined to (Issue #1725, Auto-Yes v2's no-decision path).
634
1339
  *
635
1340
  * Provisional: see {@link STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS}.
636
1341
  *
1342
+ * Called by `permission-decision-service` and by opencode's ingest, both of
1343
+ * which have already applied the `permissionHookPredictsDialog` capability gate
1344
+ * (#1901) — this function is told, it does not decide.
1345
+ *
637
1346
  * @param at - Epoch ms; defaults to now
638
1347
  */
639
1348
  function reportPermissionRequestPending(worktreeId, cliToolId, instanceId, toolName, at = Date.now()) {
640
- openPromptWaiting((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), {
1349
+ const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1350
+ openDecision(key, currentGeneration(key), {
641
1351
  source: 'permission-request',
1352
+ at,
642
1353
  message: null,
643
1354
  toolName,
644
- at,
1355
+ decisionId: null,
1356
+ // A forecast carries no event of its own, so the record it bootstraps is
1357
+ // described by the thing it is forecasting. `structuredEvents.lastEventType`
1358
+ // is unaffected — that reads `getLastAgentEvent`, which nothing here writes.
1359
+ bootstrapDisplay: { event: 'notification', at, detail: 'permission_prompt' },
645
1360
  });
646
1361
  }
647
1362
  /**
648
1363
  * The open dialog this instance's structured events imply, or null.
649
1364
  *
650
1365
  * Bounded exactly like {@link getStructuredSessionState}: a record from a
651
- * previous generation is not this session's, and one older than
652
- * {@link STRUCTURED_STATE_MAX_AGE_MS} has outlived the fact it describes. An
653
- * unconfirmed `permission-request` record expires far sooner — see
654
- * {@link STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS}.
1366
+ * previous generation is not this session's, and one past its retention bound
1367
+ * has outlived the fact it describes. An unconfirmed `permission-request`
1368
+ * record expires far sooner — see {@link DIALOG_PENDING_MAX_MS}.
655
1369
  *
656
- * The returned object is the live record, not a copy: `current-output-builder`
657
- * marks corroboration and the history write on it.
1370
+ * The oldest live decision, because that is the one a human has been looking
1371
+ * at, and because it is the record #1725 published when there could only ever
1372
+ * be one. The returned object is the live record, not a copy:
1373
+ * `prompt-waiting-composition` marks corroboration and the history write on it.
658
1374
  *
659
1375
  * @param now - Epoch ms; defaults to now
660
1376
  */
661
1377
  function getStructuredPromptWaiting(worktreeId, cliToolId, instanceId, now = Date.now()) {
662
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
663
- const state = promptWaiting.get(key);
664
- if (!state)
665
- return null;
666
- const generation = generationStartedAt.get(key);
667
- if (generation !== undefined && state.at < generation)
668
- return null;
669
- if (now - state.at >= exports.STRUCTURED_STATE_MAX_AGE_MS)
670
- return null;
671
- if (state.confirmedAt === null &&
672
- now - state.at >= exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS) {
673
- return null;
674
- }
675
- return state;
1378
+ return getPendingDecisions(worktreeId, cliToolId, instanceId, now)[0] ?? null;
676
1379
  }
677
1380
  /**
678
1381
  * Record that the scraper has seen a blocking frame while this dialog is open
@@ -680,12 +1383,12 @@ function getStructuredPromptWaiting(worktreeId, cliToolId, instanceId, now = Dat
680
1383
  *
681
1384
  * Two effects, both needed: it confirms a provisional record, and it arms the
682
1385
  * only release rule the scraper is entitled to apply. See
683
- * {@link StructuredPromptWaitingState.scraperCorroborated}.
1386
+ * {@link StructuredPendingDecision.scraperCorroborated}.
684
1387
  *
685
1388
  * @param at - Epoch ms; defaults to now
686
1389
  */
687
1390
  function corroborateStructuredPromptWaiting(worktreeId, cliToolId, instanceId, at = Date.now()) {
688
- const state = promptWaiting.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
1391
+ const state = getStructuredPromptWaiting(worktreeId, cliToolId, instanceId, at);
689
1392
  if (!state)
690
1393
  return;
691
1394
  state.scraperCorroborated = true;
@@ -693,18 +1396,23 @@ function corroborateStructuredPromptWaiting(worktreeId, cliToolId, instanceId, a
693
1396
  }
694
1397
  /** Note that this episode's prompt-history row has been written. */
695
1398
  function markStructuredPromptRecorded(worktreeId, cliToolId, instanceId) {
696
- const state = promptWaiting.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
1399
+ const state = getStructuredPromptWaiting(worktreeId, cliToolId, instanceId);
697
1400
  if (state)
698
1401
  state.recorded = true;
699
1402
  }
700
1403
  /**
701
1404
  * Release the prompt-waiting record — the dialog is gone (Issue #1725).
702
1405
  *
703
- * Called from the event transitions above and from `current-output-builder`
1406
+ * Called from the turn transition above and from `prompt-waiting-composition`
704
1407
  * when the scraper reports that the frame it corroborated has cleared.
1408
+ *
1409
+ * Releases every decision this instance is holding, not only the first. The
1410
+ * scraper's statement is about the *pane*, and a pane with no dialog on it has
1411
+ * no dialogs on it — retiring one and leaving the rest would publish `waiting`
1412
+ * for a frame the scraper has just said is clear.
705
1413
  */
706
1414
  function clearStructuredPromptWaiting(worktreeId, cliToolId, instanceId) {
707
- promptWaiting.delete((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
1415
+ releaseAllDecisions((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
708
1416
  }
709
1417
  /**
710
1418
  * When the in-flight question is released (Issue #1726).
@@ -889,13 +1597,154 @@ function pruneRecentEventKeys(now) {
889
1597
  function getRecentEventKeyCount() {
890
1598
  return recentEventKeys.size;
891
1599
  }
1600
+ // =============================================================================
1601
+ // Identity de-duplication (Issue #1899)
1602
+ // =============================================================================
1603
+ /**
1604
+ * Event words whose repeat is a fact about the session, not a re-delivery
1605
+ * (Issue #1899; design §4 D3 decision 2).
1606
+ *
1607
+ * These are the two words that end something, and neither of them carries an
1608
+ * id on any source measured so far. `session.idle` — opencode's `stop` — is
1609
+ * `{ "sessionID": "ses_…" }` and nothing else, which is exactly why
1610
+ * `sources/opencode/turn-gate` exists. With no id, a generic deduper has only
1611
+ * the clock, and the clock cannot tell a second turn from a second delivery:
1612
+ * #1899 measured a real `stop` 2.5 s after the previous one being dropped,
1613
+ * which leaves the newest event at `user_prompt_submit`, the instance reading
1614
+ * `running`, and `commandmate wait` blocked until the 30-minute staleness
1615
+ * bound.
1616
+ *
1617
+ * **The exemption is conditional on the source declaring an identity**, and
1618
+ * that condition is the whole safety argument. A source that declares one is a
1619
+ * source with a real deduper elsewhere — on the SSE path, `TurnGate`, which
1620
+ * arms on `session.status(busy)` and completes on the first `session.idle`
1621
+ * after arming, so the abort double-idle (19 ms apart, §5.3.2) never reaches
1622
+ * the ingest at all. Push hooks have no such gate: #1722's concatenated
1623
+ * settings really do post two `Stop`s for one turn, and
1624
+ * {@link isDuplicateAgentEvent} — which is what every hook receiver still
1625
+ * calls — is left untouched for them.
1626
+ */
1627
+ exports.LIFECYCLE_AGENT_EVENT_TYPES = ['stop', 'session_end'];
1628
+ /**
1629
+ * Whether this delivery is a second copy of one already handled (Issue #1899).
1630
+ *
1631
+ * The tool-agnostic replacement for calling {@link isDuplicateAgentEvent}
1632
+ * directly, and it branches on the source's declared capability rather than on
1633
+ * its name — flip opencode's `eventIdentity` to `null` and every case below
1634
+ * falls back to the 3-second window it used before this Issue.
1635
+ *
1636
+ * Three rules, in order:
1637
+ *
1638
+ * 1. **The frame has an id** — key on it, with no time bound at all. An id is
1639
+ * a claim about identity that a clock cannot improve on: two approvals
1640
+ * 1 s apart are two approvals, and the same approval replayed by
1641
+ * `resyncPending` four minutes later is still one approval. That second
1642
+ * half is what the ingest's window was reaching for ("a frame delivered
1643
+ * twice by a re-sync racing the live stream") and never actually covered,
1644
+ * since a re-sync is not obliged to land within three seconds.
1645
+ * 2. **No id, but the word ends something** — never suppressed. See
1646
+ * {@link LIFECYCLE_AGENT_EVENT_TYPES}.
1647
+ * 3. **Anything else** — the time window, unchanged. That is every push
1648
+ * source, and the identity-declaring source's `session.created` /
1649
+ * `session.error`, which publish no id either but are not turn boundaries.
1650
+ *
1651
+ * Calling this *claims* the key, exactly as {@link isDuplicateAgentEvent} does:
1652
+ * ask once per delivery and act on the answer.
1653
+ */
1654
+ function classifyAgentEventDelivery(delivery) {
1655
+ const composite = (0, auto_yes_state_1.buildCompositeKey)(delivery.worktreeId, delivery.cliToolId, delivery.instanceId);
1656
+ if (delivery.identityKind !== null) {
1657
+ // Issue #1930 / S1: the id is the agent's, and it becomes a Map key. One
1658
+ // that fails validation is DISCARDED — the delivery then takes the
1659
+ // no-id path below, which is a path this function already has — rather than
1660
+ // truncated, because a truncated id compares equal to a different id that
1661
+ // shares its prefix and would drop a real event as a repeat.
1662
+ const identity = delivery.identity === null ? null : (0, provisional_turn_1.acceptExternalId)(delivery.identity);
1663
+ if (delivery.identity !== null && identity === null)
1664
+ dropsFor(composite).idsDiscarded += 1;
1665
+ if (identity !== null) {
1666
+ if (claimEventIdentity(delivery, identity)) {
1667
+ dropsFor(composite).dedupDropped.identity += 1;
1668
+ return { duplicate: true, by: 'identity' };
1669
+ }
1670
+ return { duplicate: false };
1671
+ }
1672
+ if (exports.LIFECYCLE_AGENT_EVENT_TYPES.includes(delivery.event)) {
1673
+ return { duplicate: false };
1674
+ }
1675
+ }
1676
+ const duplicate = isDuplicateAgentEvent(delivery.worktreeId, delivery.cliToolId, delivery.instanceId, delivery.event, delivery.sessionId, delivery.at, delivery.detail);
1677
+ if (!duplicate)
1678
+ return { duplicate: false };
1679
+ dropsFor(composite).dedupDropped.timeWindow += 1;
1680
+ return { duplicate: true, by: 'time-window' };
1681
+ }
1682
+ /**
1683
+ * Claim `(event, detail, identity)` for one instance, answering whether it was
1684
+ * already claimed.
1685
+ *
1686
+ * `event` and `detail` are in the key and are not decoration: opencode asks an
1687
+ * approval under `properties.id` and answers it under `properties.requestID`
1688
+ * with **the same `per_…` value** (#1898), so a key made of the identity alone
1689
+ * would read `permission.replied` as a repeat of `permission.asked` and drop
1690
+ * the one frame that releases the dialog. The same shape covers
1691
+ * `pre_tool_use` / `post_tool_use`, which share a `callID`.
1692
+ *
1693
+ * `sessionId` is deliberately *not* in the key. An id is unique within the
1694
+ * agent that issued it, and folding in a field that is null on some deliveries
1695
+ * would let the same frame through twice.
1696
+ */
1697
+ function claimEventIdentity(delivery, identity) {
1698
+ const composite = (0, auto_yes_state_1.buildCompositeKey)(delivery.worktreeId, delivery.cliToolId, delivery.instanceId);
1699
+ let claimed = recentEventIdentities.get(composite);
1700
+ if (!claimed) {
1701
+ claimed = new Map();
1702
+ recentEventIdentities.set(composite, claimed);
1703
+ // Bound the instances as well as the entries per instance: the composite
1704
+ // key is (worktree, tool, instance) and a long-lived server accumulates
1705
+ // worktrees (DR4-009).
1706
+ trimOldestEntries(recentEventIdentities, MAX_RECENT_EVENT_KEYS);
1707
+ }
1708
+ const key = [delivery.event, delivery.detail ?? '', identity].join(' ');
1709
+ if (claimed.has(key))
1710
+ return true;
1711
+ claimed.set(key, delivery.at);
1712
+ trimOldestEntries(claimed, MAX_RECENT_EVENT_KEYS);
1713
+ return false;
1714
+ }
1715
+ /** Drop the oldest entries until the map fits. Maps iterate in insertion order. */
1716
+ function trimOldestEntries(entries, max) {
1717
+ while (entries.size > max) {
1718
+ const oldest = entries.keys().next();
1719
+ if (oldest.done)
1720
+ break;
1721
+ entries.delete(oldest.value);
1722
+ }
1723
+ }
1724
+ /**
1725
+ * How many identities are retained for one instance, or across all of them when
1726
+ * no instance is named. Test seam for the bound above.
1727
+ */
1728
+ function getRecentEventIdentityCount(worktreeId, cliToolId, instanceId) {
1729
+ if (worktreeId === undefined || cliToolId === undefined) {
1730
+ let total = 0;
1731
+ for (const claimed of recentEventIdentities.values())
1732
+ total += claimed.size;
1733
+ return total;
1734
+ }
1735
+ return recentEventIdentities.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId))?.size ?? 0;
1736
+ }
892
1737
  /** Drop every recorded event. Test seam. */
893
1738
  function clearAgentStopEvents() {
894
1739
  lastStopEventAt.clear();
895
1740
  lastAgentEvent.clear();
896
1741
  recentEventKeys.clear();
1742
+ recentEventIdentities.clear();
897
1743
  generationStartedAt.clear();
898
- promptWaiting.clear();
1744
+ // Issue #1930: the turns, the dialogs they hold, and the tally of what was
1745
+ // dropped from them.
1746
+ agentTurns.clear();
1747
+ dropCounts.clear();
899
1748
  askUserQuestion.clear();
900
1749
  awaitingInstruction.clear();
901
1750
  // Issue #1783. CI runs with `fileParallelism: false`, so every suite in the