commandmate 0.44.1 → 0.44.3

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 (604) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +169 -169
  3. package/.next/app-path-routes-manifest.json +35 -35
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/react-loadable-manifest.json +0 -6
  7. package/.next/required-server-files.json +1 -1
  8. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/api/agents/update/route.js +1 -1
  10. package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/agents/versions/route.js +1 -1
  12. package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/app/release-notes/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/app/update/route.js +2 -2
  15. package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/auth/login/route.js +1 -1
  18. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/capabilities/route.js +1 -1
  22. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/daily-summary/route.js +1 -1
  24. package/.next/server/app/api/daily-summary/route.js.nft.json +1 -1
  25. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/daily-summary/status/route.js +1 -1
  27. package/.next/server/app/api/daily-summary/status/route.js.nft.json +1 -1
  28. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/external-apps/[id]/route.js +1 -1
  31. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/external-apps/route.js +1 -1
  33. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/hooks/agent-event/route.js +2 -2
  37. package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
  38. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/hooks/claude-done/route.js +2 -2
  40. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  41. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  43. package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
  44. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/push/escalation/route.js +1 -1
  48. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/push/subscriptions/route.js +2 -2
  50. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/push/vapid/route.js +1 -1
  52. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/relays/[relayId]/cancel/route.js +1 -1
  54. package/.next/server/app/api/relays/[relayId]/cancel/route.js.nft.json +1 -1
  55. package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/relays/route.js +1 -1
  57. package/.next/server/app/api/relays/route.js.nft.json +1 -1
  58. package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/repositories/[id]/route.js +1 -1
  61. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  63. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/repositories/clone/route.js +1 -1
  66. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
  67. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/repositories/restore/route.js +1 -1
  70. package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
  71. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/repositories/route.js +1 -1
  73. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  74. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/repositories/scan/route.js +1 -1
  76. package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
  77. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/repositories/sync/route.js +1 -1
  79. package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
  80. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/settings/default-agents/route.js +1 -1
  83. package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
  84. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/sidebar/group-order/route.js +1 -1
  87. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/skills/[id]/route.js +1 -1
  89. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/skills/route.js +1 -1
  94. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/templates/[id]/route.js +2 -2
  99. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/templates/route.js +2 -2
  101. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  106. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
  109. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
  112. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js +3 -3
  117. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js.nft.json +1 -1
  118. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  121. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/direct-input/route.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/direct-input/route.js.nft.json +1 -1
  124. package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +2 -2
  128. package/.next/server/app/api/worktrees/[id]/execution-logs/route.js.nft.json +1 -1
  129. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/git/branch/create/route.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/git/checkout/route.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/git/commit/route.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  141. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/git/fetch/route.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/git/pull/route.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/git/push/route.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/git/reset/route.js +1 -1
  150. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  151. package/.next/server/app/api/worktrees/[id]/git/revert/route.js +1 -1
  152. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  153. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/[id]/git/stage/route.js +1 -1
  155. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  157. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route.js +1 -1
  159. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  160. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route.js +1 -1
  161. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/api/worktrees/[id]/git/stash/push/route.js +1 -1
  163. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  165. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  166. package/.next/server/app/api/worktrees/[id]/git/unstage/route.js +1 -1
  167. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  168. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  169. package/.next/server/app/api/worktrees/[id]/instances/notes/route.js +1 -1
  170. package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
  171. package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js +1 -1
  172. package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
  173. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  174. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  175. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  176. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  177. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  178. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  179. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.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_client-reference-manifest.js +1 -1
  189. package/.next/server/app/api/worktrees/[id]/opencode/diff/route.js +1 -1
  190. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  191. package/.next/server/app/api/worktrees/[id]/opencode/session/route.js +1 -2
  192. package/.next/server/app/api/worktrees/[id]/opencode/session/route.js.nft.json +1 -1
  193. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  194. package/.next/server/app/api/worktrees/[id]/opencode/share/route.js +1 -2
  195. package/.next/server/app/api/worktrees/[id]/opencode/share/route.js.nft.json +1 -1
  196. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  197. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  198. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  199. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  200. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  201. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  202. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  203. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  204. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  205. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  206. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  207. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +2 -2
  208. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -1
  209. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  210. package/.next/server/app/api/worktrees/[id]/schedules/active/route.js +2 -2
  211. package/.next/server/app/api/worktrees/[id]/schedules/active/route.js.nft.json +1 -1
  212. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  213. package/.next/server/app/api/worktrees/[id]/schedules/route.js +3 -3
  214. package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +1 -1
  215. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  216. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  217. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  218. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  219. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  220. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js +3 -3
  221. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
  222. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  223. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  224. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route.js +1 -1
  225. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  226. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  227. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  228. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  229. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route.js +1 -1
  230. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  231. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  232. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  233. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  234. package/.next/server/app/api/worktrees/[id]/special-keys/route.js +1 -1
  235. package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
  236. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  237. package/.next/server/app/api/worktrees/[id]/tasks/route.js +1 -1
  238. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  239. package/.next/server/app/api/worktrees/[id]/terminal/route.js +2 -2
  240. package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
  241. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.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 +1 -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_client-reference-manifest.js +1 -1
  251. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  252. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  253. package/.next/server/app/api/worktrees/[id]/verify/config/route.js +1 -1
  254. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  255. package/.next/server/app/api/worktrees/[id]/verify/route.js +1 -1
  256. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  257. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route.js +1 -1
  258. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  259. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  260. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  261. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  262. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  263. package/.next/server/app/api/worktrees/route.js +1 -1
  264. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  265. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  266. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  267. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  268. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  269. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  270. package/.next/server/app/page_client-reference-manifest.js +1 -1
  271. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  272. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  273. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  274. package/.next/server/app/sessions/page.js +1 -1
  275. package/.next/server/app/sessions/page.js.nft.json +1 -1
  276. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  277. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  278. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  279. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  280. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  281. package/.next/server/app/worktrees/[id]/page.js +11 -11
  282. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  283. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  284. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  285. package/.next/server/app-paths-manifest.json +35 -35
  286. package/.next/server/chunks/1191.js +2 -2
  287. package/.next/server/chunks/1210.js +1 -1
  288. package/.next/server/chunks/1559.js +2 -0
  289. package/.next/server/chunks/1789.js +1 -0
  290. package/.next/server/chunks/2119.js +1 -1
  291. package/.next/server/chunks/2404.js +1 -1
  292. package/.next/server/chunks/2445.js +1 -1
  293. package/.next/server/chunks/2460.js +1 -0
  294. package/.next/server/chunks/2710.js +1 -1
  295. package/.next/server/chunks/2908.js +1 -0
  296. package/.next/server/chunks/2980.js +1 -1
  297. package/.next/server/chunks/3241.js +1 -1
  298. package/.next/server/chunks/3480.js +1 -1
  299. package/.next/server/chunks/3718.js +1 -1
  300. package/.next/server/chunks/4207.js +2 -2
  301. package/.next/server/chunks/450.js +1 -1
  302. package/.next/server/chunks/4672.js +5 -0
  303. package/.next/server/chunks/{9353.js → 4718.js} +2 -2
  304. package/.next/server/chunks/4837.js +14 -0
  305. package/.next/server/chunks/5048.js +1 -1
  306. package/.next/server/chunks/5209.js +1 -0
  307. package/.next/server/chunks/5595.js +1 -0
  308. package/.next/server/chunks/5625.js +25 -0
  309. package/.next/server/chunks/5845.js +1 -0
  310. package/.next/server/chunks/611.js +1 -1
  311. package/.next/server/chunks/6130.js +1 -1
  312. package/.next/server/chunks/6401.js +1 -1
  313. package/.next/server/chunks/6781.js +1 -1
  314. package/.next/server/chunks/6793.js +3 -3
  315. package/.next/server/chunks/7491.js +2 -2
  316. package/.next/server/chunks/7905.js +1 -1
  317. package/.next/server/chunks/850.js +1 -0
  318. package/.next/server/chunks/8543.js +1 -1
  319. package/.next/server/chunks/8728.js +3 -3
  320. package/.next/server/chunks/8791.js +1 -1
  321. package/.next/server/chunks/8800.js +1 -1
  322. package/.next/server/chunks/8918.js +7 -7
  323. package/.next/server/chunks/9130.js +1 -5
  324. package/.next/server/chunks/9390.js +2 -3
  325. package/.next/server/chunks/9584.js +1 -1
  326. package/.next/server/chunks/9627.js +6 -6
  327. package/.next/server/chunks/9731.js +3 -3
  328. package/.next/server/middleware-manifest.json +5 -5
  329. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  330. package/.next/server/pages/500.html +1 -1
  331. package/.next/server/server-reference-manifest.json +1 -1
  332. package/.next/static/chunks/{1642-fb2e176aaadbd137.js → 1642-767aed8e7c61ef7f.js} +1 -1
  333. package/.next/static/chunks/{2407-f1f258d4a1d92620.js → 2407-06d35bb1aa02de80.js} +1 -1
  334. package/.next/static/chunks/{2619-04bc32f026a0d946.js → 2619-a305ad4810f1393a.js} +1 -1
  335. package/.next/static/chunks/4727-5d695d71e3fdf97b.js +1 -0
  336. package/.next/static/chunks/{8165-3853772f6ae1299b.js → 8165-3525fb42f1b4976e.js} +1 -1
  337. package/.next/static/chunks/app/{layout-da66300cc20244ed.js → layout-0d1750b833f5cc3e.js} +1 -1
  338. package/.next/static/chunks/app/sessions/page-2af68c63211c751f.js +1 -0
  339. package/.next/static/chunks/app/worktrees/[id]/page-e36898c3a80e7302.js +1 -0
  340. package/.next/static/chunks/app/worktrees/[id]/terminal/{page-373ea37b16d930c7.js → page-c601f3b3ece6557e.js} +1 -1
  341. package/.next/static/css/057f64a65726c439.css +1 -0
  342. package/.next/trace +13 -13
  343. package/dist/cli/commands/ask.d.ts.map +1 -1
  344. package/dist/cli/commands/ask.js +120 -119
  345. package/dist/cli/commands/attach.d.ts +94 -0
  346. package/dist/cli/commands/attach.d.ts.map +1 -1
  347. package/dist/cli/commands/attach.js +145 -7
  348. package/dist/cli/commands/auto-yes.js +1 -1
  349. package/dist/cli/commands/capture.d.ts.map +1 -1
  350. package/dist/cli/commands/capture.js +10 -13
  351. package/dist/cli/commands/command-target.d.ts +23 -0
  352. package/dist/cli/commands/command-target.d.ts.map +1 -0
  353. package/dist/cli/commands/command-target.js +25 -0
  354. package/dist/cli/commands/instances.d.ts.map +1 -1
  355. package/dist/cli/commands/instances.js +41 -2
  356. package/dist/cli/commands/message-dispatch.d.ts +36 -0
  357. package/dist/cli/commands/message-dispatch.d.ts.map +1 -0
  358. package/dist/cli/commands/message-dispatch.js +63 -0
  359. package/dist/cli/commands/reply.d.ts.map +1 -1
  360. package/dist/cli/commands/reply.js +2 -5
  361. package/dist/cli/commands/respond.d.ts.map +1 -1
  362. package/dist/cli/commands/respond.js +3 -6
  363. package/dist/cli/commands/send.d.ts.map +1 -1
  364. package/dist/cli/commands/send.js +149 -146
  365. package/dist/cli/commands/wait.d.ts.map +1 -1
  366. package/dist/cli/commands/wait.js +608 -416
  367. package/dist/cli/types/api-responses.d.ts +174 -27
  368. package/dist/cli/types/api-responses.d.ts.map +1 -1
  369. package/dist/cli/types/api-responses.js +4 -2
  370. package/dist/cli/utils/api-client.d.ts +25 -5
  371. package/dist/cli/utils/api-client.d.ts.map +1 -1
  372. package/dist/cli/utils/api-client.js +87 -55
  373. package/dist/cli/utils/server-url.d.ts +1 -0
  374. package/dist/cli/utils/server-url.d.ts.map +1 -1
  375. package/dist/cli/utils/server-url.js +7 -0
  376. package/dist/config/uat-isolation.d.ts +140 -0
  377. package/dist/config/uat-isolation.d.ts.map +1 -0
  378. package/dist/config/uat-isolation.js +246 -0
  379. package/dist/lib/global-state.d.ts +12 -0
  380. package/dist/lib/global-state.d.ts.map +1 -0
  381. package/dist/lib/global-state.js +16 -0
  382. package/dist/lib/polling/auto-yes-suppression-reason.d.ts +33 -0
  383. package/dist/lib/polling/auto-yes-suppression-reason.d.ts.map +1 -0
  384. package/dist/lib/polling/auto-yes-suppression-reason.js +2 -0
  385. package/dist/lib/session/prompt-view.d.ts +228 -0
  386. package/dist/lib/session/prompt-view.d.ts.map +1 -0
  387. package/dist/lib/session/prompt-view.js +290 -0
  388. package/dist/lib/session/status-evidence-type.d.ts +16 -0
  389. package/dist/lib/session/status-evidence-type.d.ts.map +1 -0
  390. package/dist/lib/session/status-evidence-type.js +2 -0
  391. package/dist/lib/tmux/legacy-session-alias.d.ts.map +1 -1
  392. package/dist/lib/tmux/legacy-session-alias.js +2 -1
  393. package/dist/server/locales/en/worktree.json +3 -1
  394. package/dist/server/locales/ja/worktree.json +3 -1
  395. package/dist/server/server.js +5 -3
  396. package/dist/server/src/cli/types/api-responses.js +4 -2
  397. package/dist/server/src/config/cli-tool-timing-config.js +1 -6
  398. package/dist/server/src/config/uat-isolation.js +246 -0
  399. package/dist/server/src/lib/assistant-response-saver.js +62 -3
  400. package/dist/server/src/lib/auto-yes-lifecycle.js +139 -0
  401. package/dist/server/src/lib/auto-yes-poller.js +465 -160
  402. package/dist/server/src/lib/auto-yes-state.js +2 -2
  403. package/dist/server/src/lib/cli-tools/antigravity.js +39 -80
  404. package/dist/server/src/lib/cli-tools/base.js +151 -0
  405. package/dist/server/src/lib/cli-tools/codex.js +44 -80
  406. package/dist/server/src/lib/cli-tools/command-code.js +24 -56
  407. package/dist/server/src/lib/cli-tools/copilot.js +25 -67
  408. package/dist/server/src/lib/cli-tools/direct-input.js +8 -0
  409. package/dist/server/src/lib/cli-tools/gemini.js +23 -73
  410. package/dist/server/src/lib/cli-tools/opencode-pane-width.js +53 -0
  411. package/dist/server/src/lib/cli-tools/opencode-v2.js +9 -7
  412. package/dist/server/src/lib/cli-tools/opencode.js +21 -85
  413. package/dist/server/src/lib/cli-tools/submit-verified-sender.js +127 -4
  414. package/dist/server/src/lib/cli-tools/vibe-local.js +21 -66
  415. package/dist/server/src/lib/detection/cli-patterns.js +374 -3047
  416. package/dist/server/src/lib/detection/idle-evidence-observation.js +2 -2
  417. package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +7 -32
  418. package/dist/server/src/lib/detection/prompt-detector.js +2 -35
  419. package/dist/server/src/lib/detection/selection-shape-patterns.js +69 -0
  420. package/dist/server/src/lib/detection/selection-shape.js +32 -520
  421. package/dist/server/src/lib/detection/shared/pasted-text.js +41 -0
  422. package/dist/server/src/lib/detection/shared/shell-pane-command.js +16 -0
  423. package/dist/server/src/lib/detection/shared/strip-box-drawing.js +25 -0
  424. package/dist/server/src/lib/detection/status-detector.js +1 -1
  425. package/dist/server/src/lib/detection/tools/antigravity/detect.js +12 -60
  426. package/dist/server/src/lib/detection/tools/antigravity/dialog.js +2 -15
  427. package/dist/server/src/lib/detection/tools/antigravity/live-region.js +39 -0
  428. package/dist/server/src/lib/detection/tools/antigravity/patterns.js +286 -0
  429. package/dist/server/src/lib/detection/tools/claude/detect.js +11 -17
  430. package/dist/server/src/lib/detection/tools/claude/live-region.js +26 -0
  431. package/dist/server/src/lib/detection/tools/claude/patterns.js +264 -4
  432. package/dist/server/src/lib/detection/tools/claude/prompt.js +2 -10
  433. package/dist/server/src/lib/detection/tools/codex/cli-patterns.js +1 -1
  434. package/dist/server/src/lib/detection/tools/codex/detect.js +71 -130
  435. package/dist/server/src/lib/detection/tools/codex/live-region.js +104 -0
  436. package/dist/server/src/lib/detection/tools/codex/patterns.js +855 -0
  437. package/dist/server/src/lib/detection/tools/codex/prompt.js +2 -10
  438. package/dist/server/src/lib/detection/tools/command-code/detect.js +19 -50
  439. package/dist/server/src/lib/detection/tools/command-code/dialog.js +2 -18
  440. package/dist/server/src/lib/detection/tools/command-code/live-region.js +36 -0
  441. package/dist/server/src/lib/detection/tools/command-code/patterns.js +234 -0
  442. package/dist/server/src/lib/detection/tools/command-code/selection-shape.js +461 -0
  443. package/dist/server/src/lib/detection/tools/context-above.js +20 -0
  444. package/dist/server/src/lib/detection/tools/copilot/detect.js +13 -35
  445. package/dist/server/src/lib/detection/tools/copilot/live-region.js +52 -0
  446. package/dist/server/src/lib/detection/tools/copilot/patterns.js +640 -0
  447. package/dist/server/src/lib/detection/tools/copilot/prompt.js +2 -10
  448. package/dist/server/src/lib/detection/tools/frame.js +36 -2
  449. package/dist/server/src/lib/detection/tools/gemini/patterns.js +29 -0
  450. package/dist/server/src/lib/detection/tools/live-region-specs.js +31 -0
  451. package/dist/server/src/lib/detection/tools/live-region.js +204 -0
  452. package/dist/server/src/lib/detection/tools/opencode/detect.js +11 -49
  453. package/dist/server/src/lib/detection/tools/opencode/live-region.js +24 -0
  454. package/dist/server/src/lib/detection/tools/opencode/patterns.js +508 -0
  455. package/dist/server/src/lib/detection/tools/opencode-gutter-live-region.js +72 -0
  456. package/dist/server/src/lib/detection/tools/opencode-v2/detect.js +10 -42
  457. package/dist/server/src/lib/detection/tools/opencode-v2/live-region.js +24 -0
  458. package/dist/server/src/lib/detection/tools/opencode-v2/patterns.js +63 -0
  459. package/dist/server/src/lib/detection/tools/run-detection.js +31 -50
  460. package/dist/server/src/lib/detection/tools/verdicts.js +59 -0
  461. package/dist/server/src/lib/detection/tools/vibe-local/patterns.js +20 -0
  462. package/dist/server/src/lib/detection/truncate-raw-content.js +38 -0
  463. package/dist/server/src/lib/detection/turn-abandoned.js +66 -0
  464. package/dist/server/src/lib/detection/unclassified-frame-tracker.js +2 -2
  465. package/dist/server/src/lib/global-state.js +16 -0
  466. package/dist/server/src/lib/hooks/agent-session-telemetry.js +4 -6
  467. package/dist/server/src/lib/hooks/permission-decision-state.js +2 -2
  468. package/dist/server/src/lib/hooks/sources/antigravity/history.js +11 -76
  469. package/dist/server/src/lib/hooks/sources/antigravity/hooks-config.js +63 -1
  470. package/dist/server/src/lib/hooks/sources/antigravity/source.js +16 -0
  471. package/dist/server/src/lib/hooks/sources/antigravity/transcript.js +3 -12
  472. package/dist/server/src/lib/hooks/sources/claude/history.js +17 -156
  473. package/dist/server/src/lib/hooks/sources/claude/queued-notice.js +53 -0
  474. package/dist/server/src/lib/hooks/sources/claude/source.js +33 -3
  475. package/dist/server/src/lib/hooks/sources/claude/transcript.js +3 -13
  476. package/dist/server/src/lib/hooks/sources/codex/history.js +11 -74
  477. package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +147 -11
  478. package/dist/server/src/lib/hooks/sources/codex/relay-install.js +64 -21
  479. package/dist/server/src/lib/hooks/sources/codex/transcript.js +5 -15
  480. package/dist/server/src/lib/hooks/sources/command-code/history.js +20 -121
  481. package/dist/server/src/lib/hooks/sources/command-code/transcript.js +3 -13
  482. package/dist/server/src/lib/hooks/sources/copilot/hook-settings.js +161 -31
  483. package/dist/server/src/lib/hooks/sources/define-source.js +40 -55
  484. package/dist/server/src/lib/hooks/sources/event-mapper.js +2 -1
  485. package/dist/server/src/lib/hooks/sources/opencode/diff.js +3 -4
  486. package/dist/server/src/lib/hooks/sources/opencode/history.js +2 -1
  487. package/dist/server/src/lib/hooks/sources/opencode/launch-settings.js +2 -1
  488. package/dist/server/src/lib/hooks/sources/opencode/payloads.js +2 -1
  489. package/dist/server/src/lib/hooks/sources/opencode/ports.js +2 -1
  490. package/dist/server/src/lib/hooks/sources/opencode/subscription.js +4 -3
  491. package/dist/server/src/lib/hooks/sources/opencode/transcript.js +3 -16
  492. package/dist/server/src/lib/hooks/sources/opencode-v2/history.js +2 -1
  493. package/dist/server/src/lib/hooks/sources/opencode-v2/mappers.js +5 -4
  494. package/dist/server/src/lib/hooks/sources/opencode-v2/ports.js +2 -1
  495. package/dist/server/src/lib/hooks/sources/opencode-v2/subscription.js +3 -7
  496. package/dist/server/src/lib/hooks/sources/opencode-v2/usage.js +2 -1
  497. package/dist/server/src/lib/hooks/sources/pending-decisions.js +2 -1
  498. package/dist/server/src/lib/hooks/sources/registry.js +3 -2
  499. package/dist/server/src/lib/hooks/sources/transcript-history.js +262 -0
  500. package/dist/server/src/lib/hooks/sources/turn-body.js +31 -0
  501. package/dist/server/src/lib/hooks/tool-input-normalization-state.js +2 -2
  502. package/dist/server/src/lib/polling/antigravity-permission-receipts.js +2 -2
  503. package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +16 -4
  504. package/dist/server/src/lib/polling/auto-yes-enter-fallback-state.js +142 -0
  505. package/dist/server/src/lib/polling/auto-yes-enter-fallback.js +187 -0
  506. package/dist/server/src/lib/polling/auto-yes-resolver.js +9 -3
  507. package/dist/server/src/lib/polling/auto-yes-suppression-reason.js +2 -0
  508. package/dist/server/src/lib/polling/auto-yes-suppression-state.js +2 -2
  509. package/dist/server/src/lib/polling/pending-scraped-response.js +190 -0
  510. package/dist/server/src/lib/polling/prompt-dedup-state.js +2 -2
  511. package/dist/server/src/lib/polling/prompt-dedup.js +2 -2
  512. package/dist/server/src/lib/polling/response-checker-dialog-gate.js +95 -0
  513. package/dist/server/src/lib/polling/response-checker-extraction-result.js +50 -0
  514. package/dist/server/src/lib/polling/response-checker-extraction-steps.js +670 -0
  515. package/dist/server/src/lib/polling/response-checker.js +666 -1162
  516. package/dist/server/src/lib/polling/response-dedup.js +3 -4
  517. package/dist/server/src/lib/polling/response-poller-constants.js +17 -0
  518. package/dist/server/src/lib/polling/response-poller-core.js +54 -20
  519. package/dist/server/src/lib/polling/startup-screen.js +142 -0
  520. package/dist/server/src/lib/polling/structured-history-gate.js +3 -2
  521. package/dist/server/src/lib/prompt-answer-semantic.js +178 -0
  522. package/dist/server/src/lib/prompt-answer-sender.js +6 -5
  523. package/dist/server/src/lib/push/failure-episode-state.js +2 -2
  524. package/dist/server/src/lib/push/notification-dedup.js +3 -4
  525. package/dist/server/src/lib/push/prompt-card-state.js +2 -2
  526. package/dist/server/src/lib/push/waiting-push-notifier.js +3 -4
  527. package/dist/server/src/lib/realtime/publisher-registry.js +3 -2
  528. package/dist/server/src/lib/realtime/terminal-broadcast.js +38 -2
  529. package/dist/server/src/lib/realtime/terminal-session-ownership.js +45 -0
  530. package/dist/server/src/lib/relay/relay-delivery.js +6 -2
  531. package/dist/server/src/lib/relay/relay-readiness.js +38 -1
  532. package/dist/server/src/lib/relay/relay-triggers.js +9 -0
  533. package/dist/server/src/lib/resource-cleanup.js +6 -4
  534. package/dist/server/src/lib/response-cleaner.js +24 -1
  535. package/dist/server/src/lib/response-extractor.js +16 -0
  536. package/dist/server/src/lib/schedule-manager.js +8 -10
  537. package/dist/server/src/lib/session/agent-event-ask-user-question.js +168 -0
  538. package/dist/server/src/lib/session/agent-event-awaiting-instruction.js +104 -0
  539. package/dist/server/src/lib/session/agent-event-dedup.js +539 -0
  540. package/dist/server/src/lib/session/agent-event-model.js +562 -0
  541. package/dist/server/src/lib/session/agent-event-record.js +10 -0
  542. package/dist/server/src/lib/session/agent-event-state.js +106 -1854
  543. package/dist/server/src/lib/session/agent-event-structured-state.js +109 -0
  544. package/dist/server/src/lib/session/agent-event-turn.js +811 -0
  545. package/dist/server/src/lib/session/agent-session-lifecycle.js +11 -2
  546. package/dist/server/src/lib/session/chat-turn-progress.js +322 -0
  547. package/dist/server/src/lib/session/claude-executor.js +45 -5
  548. package/dist/server/src/lib/session/claude-session.js +21 -65
  549. package/dist/server/src/lib/session/cli-session.js +17 -0
  550. package/dist/server/src/lib/session/current-output-builder.js +454 -1103
  551. package/dist/server/src/lib/session/current-output-history-writers.js +255 -0
  552. package/dist/server/src/lib/session/current-output-types.js +9 -0
  553. package/dist/server/src/lib/session/detection-divergence.js +55 -0
  554. package/dist/server/src/lib/session/global-session-constants.js +2 -13
  555. package/dist/server/src/lib/session/hook-turn-hold.js +102 -0
  556. package/dist/server/src/lib/session/layer-disagreement.js +2 -2
  557. package/dist/server/src/lib/session/opencode-session-store.js +2 -1
  558. package/dist/server/src/lib/session/prompt-view.js +290 -0
  559. package/dist/server/src/lib/session/prompt-waiting-composition.js +35 -0
  560. package/dist/server/src/lib/session/provisional-turn.js +3 -1
  561. package/dist/server/src/lib/session/send-user-message.js +1 -1
  562. package/dist/server/src/lib/session/session-starting-state.js +4 -6
  563. package/dist/server/src/lib/session/status-evidence-type.js +2 -0
  564. package/dist/server/src/lib/session/status-evidence.js +2 -2
  565. package/dist/server/src/lib/session/structured-status-merge.js +335 -0
  566. package/dist/server/src/lib/session/waiting-episode-state.js +3 -4
  567. package/dist/server/src/lib/session/waiting-kind.js +3 -0
  568. package/dist/server/src/lib/session-cleanup.js +6 -8
  569. package/dist/server/src/lib/skills/install-plan.js +2 -1
  570. package/dist/server/src/lib/skills/snapshot-store.js +2 -2
  571. package/dist/server/src/lib/skills/uninstall-plan.js +2 -2
  572. package/dist/server/src/lib/timer-manager.js +6 -8
  573. package/dist/server/src/lib/tmux/geometry-delegation.js +3 -4
  574. package/dist/server/src/lib/tmux/legacy-session-alias.js +2 -1
  575. package/dist/server/src/lib/tmux/session-hooks.js +2 -1
  576. package/dist/server/src/lib/tmux/session-status-options.js +3 -3
  577. package/dist/server/src/lib/tmux/tmux-capture-cache.js +3 -2
  578. package/dist/server/src/lib/tmux/tmux.js +33 -59
  579. package/dist/server/src/lib/tui-accumulator.js +2 -2
  580. package/dist/server/src/lib/version-checker.js +6 -6
  581. package/dist/server/src/lib/ws-server.js +47 -11
  582. package/docs/en/user-guide/webapp-guide.md +27 -3
  583. package/package.json +1 -1
  584. package/release-notes/0.44.2.json +33 -0
  585. package/release-notes/0.44.3.json +82 -0
  586. package/scripts/hooks/cmate-agent-event.sh +18 -0
  587. package/.next/server/chunks/1068.js +0 -1
  588. package/.next/server/chunks/434.js +0 -1
  589. package/.next/server/chunks/5775.js +0 -1
  590. package/.next/server/chunks/6474.js +0 -14
  591. package/.next/server/chunks/650.js +0 -1
  592. package/.next/server/chunks/6661.js +0 -1
  593. package/.next/server/chunks/8583.js +0 -1
  594. package/.next/server/chunks/964.js +0 -1
  595. package/.next/server/chunks/967.js +0 -1
  596. package/.next/server/chunks/9882.js +0 -25
  597. package/.next/static/chunks/591-308072f285c34787.js +0 -1
  598. package/.next/static/chunks/app/sessions/page-049e06335187aa91.js +0 -1
  599. package/.next/static/chunks/app/worktrees/[id]/page-040c5f0f585e6812.js +0 -1
  600. package/.next/static/css/38070d879efa1371.css +0 -1
  601. package/dist/server/src/lib/cli-tools/index.js +0 -41
  602. package/dist/server/src/lib/polling/global-session-poller.js +0 -140
  603. /package/.next/static/{57lOiz8zSEHdw97XIm0af → tWwHQzmvc-rv9KKUtDWfQ}/_buildManifest.js +0 -0
  604. /package/.next/static/{57lOiz8zSEHdw97XIm0af → tWwHQzmvc-rv9KKUtDWfQ}/_ssgManifest.js +0 -0
@@ -39,604 +39,75 @@
39
39
  * @module lib/session/agent-event-state
40
40
  */
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
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 = exports.AGENT_MODEL_EFFORT_SUFFIX_PATTERN = void 0;
43
- exports.isSameAgentModelName = isSameAgentModelName;
44
- exports.onAgentModelChange = onAgentModelChange;
45
- exports.getAgentModelBaseline = getAgentModelBaseline;
46
- exports.getAgentEventDropCounts = getAgentEventDropCounts;
47
- exports.getPendingDecisions = getPendingDecisions;
48
- exports.getAgentTurn = getAgentTurn;
49
- exports.getPublishedAgentTurn = getPublishedAgentTurn;
50
- exports.closeAgentTurn = closeAgentTurn;
51
- exports.observeScraperCompletionEvidence = observeScraperCompletionEvidence;
42
+ exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = exports.recordAskUserQuestion = exports.getAskUserQuestion = exports.clearAskUserQuestion = exports.isAwaitingInstruction = exports.getAwaitingInstruction = exports.STRUCTURED_STATE_MAX_AGE_MS = exports.getStructuredSessionState = exports.observeScraperCompletionEvidence = exports.joinOpenTurnFromDuplicate = exports.getPublishedAgentTurn = exports.getPendingDecisions = exports.getAgentTurn = exports.closeAgentTurn = exports.recordCapturedModelInfo = exports.recordAgentReportedModel = exports.recordAgentReportedEffort = exports.onAgentModelChange = exports.isSameAgentModelName = exports.getResolvedAgentModelInfo = exports.getLastReportedAgentEffort = exports.getLastKnownAgentModel = exports.getLastKnownAgentEffort = exports.getLastCapturedModelInfo = exports.getAgentModelBaseline = exports.AGENT_MODEL_EFFORT_SUFFIX_PATTERN = exports.shortSessionTag = exports.LIFECYCLE_AGENT_EVENT_TYPES = exports.isDuplicateAgentEvent = exports.getRecentEventKeyCount = exports.getRecentEventIdentityCount = exports.getAgentEventDropCounts = exports.classifyAgentEventDelivery = exports.agentEventKeyClaimedAt = exports.AGENT_EVENT_DEDUP_WINDOW_MS = void 0;
52
43
  exports.recordAgentStopEvent = recordAgentStopEvent;
53
44
  exports.getLastStopEventAt = getLastStopEventAt;
54
45
  exports.recordAgentEvent = recordAgentEvent;
55
- exports.getAwaitingInstruction = getAwaitingInstruction;
56
- exports.isAwaitingInstruction = isAwaitingInstruction;
57
46
  exports.getLastAgentEvent = getLastAgentEvent;
58
- exports.getLastKnownAgentModel = getLastKnownAgentModel;
59
- exports.recordAgentReportedModel = recordAgentReportedModel;
60
- exports.recordCapturedModelInfo = recordCapturedModelInfo;
61
- exports.getLastCapturedModelInfo = getLastCapturedModelInfo;
62
- exports.getResolvedAgentModelInfo = getResolvedAgentModelInfo;
63
- exports.recordAgentReportedEffort = recordAgentReportedEffort;
64
- exports.getLastReportedAgentEffort = getLastReportedAgentEffort;
65
- exports.getLastKnownAgentEffort = getLastKnownAgentEffort;
66
47
  exports.beginAgentEventGeneration = beginAgentEventGeneration;
67
48
  exports.getAgentEventGenerationStartedAt = getAgentEventGenerationStartedAt;
68
49
  exports.discardAgentEventState = discardAgentEventState;
69
- exports.getStructuredSessionState = getStructuredSessionState;
70
50
  exports.reportPermissionRequestPending = reportPermissionRequestPending;
71
51
  exports.reportQuestionPending = reportQuestionPending;
72
52
  exports.getStructuredPromptWaiting = getStructuredPromptWaiting;
73
53
  exports.corroborateStructuredPromptWaiting = corroborateStructuredPromptWaiting;
74
54
  exports.markStructuredPromptRecorded = markStructuredPromptRecorded;
75
55
  exports.clearStructuredPromptWaiting = clearStructuredPromptWaiting;
76
- exports.recordAskUserQuestion = recordAskUserQuestion;
77
- exports.getAskUserQuestion = getAskUserQuestion;
78
- exports.clearAskUserQuestion = clearAskUserQuestion;
79
- exports.isDuplicateAgentEvent = isDuplicateAgentEvent;
80
- exports.getRecentEventKeyCount = getRecentEventKeyCount;
81
- exports.classifyAgentEventDelivery = classifyAgentEventDelivery;
82
- exports.getRecentEventIdentityCount = getRecentEventIdentityCount;
83
56
  exports.clearAgentStopEvents = clearAgentStopEvents;
84
57
  const auto_yes_state_1 = require("../../lib/auto-yes-state");
85
- const agent_event_types_1 = require("../../lib/hooks/agent-event-types");
86
- // Issue #2100: the shared list of tool names that mean "a human is being asked
87
- // to CHOOSE". Pure — its own only import is `permission-request-payload`, which
88
- // this module already reached for `ASK_USER_QUESTION_TOOL` — and importing it
89
- // here rather than repeating `'question'` is what keeps the release rule below
90
- // and `pendingDecisionKind`'s reader from drifting apart.
91
- const pending_decision_kind_1 = require("../../lib/hooks/pending-decision-kind");
92
- // Issue #1784: the terminal-frame half of "which model / effort is this on".
93
- const model_info_extractor_1 = require("../../lib/detection/model-info-extractor");
94
- const status_mapping_1 = require("../../lib/session/status-mapping");
95
- // Issue #1930: the turn model. Pure, so importing it here costs nothing at
96
- // runtime beyond the two bounds it owns.
58
+ const auto_yes_enter_fallback_state_1 = require("../../lib/polling/auto-yes-enter-fallback-state");
59
+ const agent_event_dedup_1 = require("../../lib/session/agent-event-dedup");
60
+ const agent_event_model_1 = require("../../lib/session/agent-event-model");
97
61
  const provisional_turn_1 = require("../../lib/session/provisional-turn");
62
+ const agent_event_turn_1 = require("../../lib/session/agent-event-turn");
63
+ const agent_event_structured_state_1 = require("../../lib/session/agent-event-structured-state");
64
+ const agent_event_awaiting_instruction_1 = require("../../lib/session/agent-event-awaiting-instruction");
65
+ const agent_event_ask_user_question_1 = require("../../lib/session/agent-event-ask-user-question");
66
+ const global_state_1 = require("../global-state");
67
+ var agent_event_dedup_2 = require("../../lib/session/agent-event-dedup");
68
+ Object.defineProperty(exports, "AGENT_EVENT_DEDUP_WINDOW_MS", { enumerable: true, get: function () { return agent_event_dedup_2.AGENT_EVENT_DEDUP_WINDOW_MS; } });
69
+ Object.defineProperty(exports, "agentEventKeyClaimedAt", { enumerable: true, get: function () { return agent_event_dedup_2.agentEventKeyClaimedAt; } });
70
+ Object.defineProperty(exports, "classifyAgentEventDelivery", { enumerable: true, get: function () { return agent_event_dedup_2.classifyAgentEventDelivery; } });
71
+ Object.defineProperty(exports, "getAgentEventDropCounts", { enumerable: true, get: function () { return agent_event_dedup_2.getAgentEventDropCounts; } });
72
+ Object.defineProperty(exports, "getRecentEventIdentityCount", { enumerable: true, get: function () { return agent_event_dedup_2.getRecentEventIdentityCount; } });
73
+ Object.defineProperty(exports, "getRecentEventKeyCount", { enumerable: true, get: function () { return agent_event_dedup_2.getRecentEventKeyCount; } });
74
+ Object.defineProperty(exports, "isDuplicateAgentEvent", { enumerable: true, get: function () { return agent_event_dedup_2.isDuplicateAgentEvent; } });
75
+ Object.defineProperty(exports, "LIFECYCLE_AGENT_EVENT_TYPES", { enumerable: true, get: function () { return agent_event_dedup_2.LIFECYCLE_AGENT_EVENT_TYPES; } });
76
+ Object.defineProperty(exports, "shortSessionTag", { enumerable: true, get: function () { return agent_event_dedup_2.shortSessionTag; } });
77
+ var agent_event_model_2 = require("../../lib/session/agent-event-model");
78
+ Object.defineProperty(exports, "AGENT_MODEL_EFFORT_SUFFIX_PATTERN", { enumerable: true, get: function () { return agent_event_model_2.AGENT_MODEL_EFFORT_SUFFIX_PATTERN; } });
79
+ Object.defineProperty(exports, "getAgentModelBaseline", { enumerable: true, get: function () { return agent_event_model_2.getAgentModelBaseline; } });
80
+ Object.defineProperty(exports, "getLastCapturedModelInfo", { enumerable: true, get: function () { return agent_event_model_2.getLastCapturedModelInfo; } });
81
+ Object.defineProperty(exports, "getLastKnownAgentEffort", { enumerable: true, get: function () { return agent_event_model_2.getLastKnownAgentEffort; } });
82
+ Object.defineProperty(exports, "getLastKnownAgentModel", { enumerable: true, get: function () { return agent_event_model_2.getLastKnownAgentModel; } });
83
+ Object.defineProperty(exports, "getLastReportedAgentEffort", { enumerable: true, get: function () { return agent_event_model_2.getLastReportedAgentEffort; } });
84
+ Object.defineProperty(exports, "getResolvedAgentModelInfo", { enumerable: true, get: function () { return agent_event_model_2.getResolvedAgentModelInfo; } });
85
+ Object.defineProperty(exports, "isSameAgentModelName", { enumerable: true, get: function () { return agent_event_model_2.isSameAgentModelName; } });
86
+ Object.defineProperty(exports, "onAgentModelChange", { enumerable: true, get: function () { return agent_event_model_2.onAgentModelChange; } });
87
+ Object.defineProperty(exports, "recordAgentReportedEffort", { enumerable: true, get: function () { return agent_event_model_2.recordAgentReportedEffort; } });
88
+ Object.defineProperty(exports, "recordAgentReportedModel", { enumerable: true, get: function () { return agent_event_model_2.recordAgentReportedModel; } });
89
+ Object.defineProperty(exports, "recordCapturedModelInfo", { enumerable: true, get: function () { return agent_event_model_2.recordCapturedModelInfo; } });
90
+ var agent_event_turn_2 = require("../../lib/session/agent-event-turn");
91
+ Object.defineProperty(exports, "closeAgentTurn", { enumerable: true, get: function () { return agent_event_turn_2.closeAgentTurn; } });
92
+ Object.defineProperty(exports, "getAgentTurn", { enumerable: true, get: function () { return agent_event_turn_2.getAgentTurn; } });
93
+ Object.defineProperty(exports, "getPendingDecisions", { enumerable: true, get: function () { return agent_event_turn_2.getPendingDecisions; } });
94
+ Object.defineProperty(exports, "getPublishedAgentTurn", { enumerable: true, get: function () { return agent_event_turn_2.getPublishedAgentTurn; } });
95
+ Object.defineProperty(exports, "joinOpenTurnFromDuplicate", { enumerable: true, get: function () { return agent_event_turn_2.joinOpenTurnFromDuplicate; } });
96
+ Object.defineProperty(exports, "observeScraperCompletionEvidence", { enumerable: true, get: function () { return agent_event_turn_2.observeScraperCompletionEvidence; } });
97
+ var agent_event_structured_state_2 = require("../../lib/session/agent-event-structured-state");
98
+ Object.defineProperty(exports, "getStructuredSessionState", { enumerable: true, get: function () { return agent_event_structured_state_2.getStructuredSessionState; } });
99
+ Object.defineProperty(exports, "STRUCTURED_STATE_MAX_AGE_MS", { enumerable: true, get: function () { return agent_event_structured_state_2.STRUCTURED_STATE_MAX_AGE_MS; } });
100
+ var agent_event_awaiting_instruction_2 = require("../../lib/session/agent-event-awaiting-instruction");
101
+ Object.defineProperty(exports, "getAwaitingInstruction", { enumerable: true, get: function () { return agent_event_awaiting_instruction_2.getAwaitingInstruction; } });
102
+ Object.defineProperty(exports, "isAwaitingInstruction", { enumerable: true, get: function () { return agent_event_awaiting_instruction_2.isAwaitingInstruction; } });
103
+ var agent_event_ask_user_question_2 = require("../../lib/session/agent-event-ask-user-question");
104
+ Object.defineProperty(exports, "clearAskUserQuestion", { enumerable: true, get: function () { return agent_event_ask_user_question_2.clearAskUserQuestion; } });
105
+ Object.defineProperty(exports, "getAskUserQuestion", { enumerable: true, get: function () { return agent_event_ask_user_question_2.getAskUserQuestion; } });
106
+ Object.defineProperty(exports, "recordAskUserQuestion", { enumerable: true, get: function () { return agent_event_ask_user_question_2.recordAskUserQuestion; } });
98
107
  /** compositeKey -> epoch ms of the most recent stop event. */
99
- const lastStopEventAt = globalThis.__agentEventLastStopAt ??
100
- (globalThis.__agentEventLastStopAt = new Map());
108
+ const lastStopEventAt = (0, global_state_1.getOrInitGlobal)('__agentEventLastStopAt', () => new Map());
101
109
  /** compositeKey -> the most recent event of any kind. */
102
- const lastAgentEvent = globalThis.__agentEventLast ??
103
- (globalThis.__agentEventLast = new Map());
104
- /** compositeKey -> epoch ms the current generation began. See {@link beginAgentEventGeneration}. */
105
- const generationStartedAt = globalThis.__agentEventGenerationStartedAt ??
106
- (globalThis.__agentEventGenerationStartedAt = new Map());
107
- /**
108
- * compositeKey -> the turn this instance is in, or the last one it was in
109
- * (Issue #1930).
110
- *
111
- * The map that replaced "the newest event is the verdict". `lastAgentEvent`
112
- * above is still written on every delivery and is still what
113
- * `structuredEvents.lastEventType` publishes — it answers "did anything reach
114
- * this server, and for the right instance?", which is a diagnostic question.
115
- * This map answers the state question, and the two deliberately disagree
116
- * whenever an event carries no verdict.
117
- *
118
- * The open dialogs #1725 kept in a map of their own live on the turn now
119
- * ({@link TurnRecord.pendingDecisions}), because a dialog only ever happens
120
- * *inside* a turn and holding them apart is what let a generation bump retire
121
- * one and not the other.
122
- */
123
- const agentTurns = globalThis.__agentEventTurns ??
124
- (globalThis.__agentEventTurns = new Map());
125
- /** compositeKey -> what this instance has had dropped, and why (Issue #1930). */
126
- const dropCounts = globalThis.__agentEventDrops ??
127
- (globalThis.__agentEventDrops = new Map());
128
- /**
129
- * Monotonic suffix for {@link TurnRecord.turnId}.
130
- *
131
- * Two turns can open in the same millisecond — a `post_tool_use` closing one
132
- * agent's turn while another's `user_prompt_submit` lands — and an id built from
133
- * the timestamp alone would then compare equal across instances. `wait` reads a
134
- * change of id as "a new turn began", so the collision would be a missed turn
135
- * boundary rather than a cosmetic clash.
136
- */
137
- const turnSequence = globalThis.__agentEventTurnSeq ??
138
- (globalThis.__agentEventTurnSeq = { value: 0 });
139
- /** compositeKey -> the `AskUserQuestion` call currently in flight (#1726). */
140
- const askUserQuestion = globalThis.__agentEventAskUserQuestion ??
141
- (globalThis.__agentEventAskUserQuestion = new Map());
142
- /** dedup key -> epoch ms it was first seen. See {@link isDuplicateAgentEvent}. */
143
- const recentEventKeys = globalThis.__agentEventRecentKeys ??
144
- (globalThis.__agentEventRecentKeys = new Map());
145
- /**
146
- * compositeKey -> (identity key -> epoch ms it was first seen) (Issue #1899).
147
- *
148
- * Nested rather than flat so the bound is *per instance*, which the flat
149
- * {@link recentEventKeys} is not: one chatty agent must not be able to evict
150
- * another's ids and let a genuine re-delivery through on a quiet pane. See
151
- * {@link claimEventIdentity}.
152
- */
153
- const recentEventIdentities = globalThis.__agentEventRecentIdentities ??
154
- (globalThis.__agentEventRecentIdentities = new Map());
155
- /** compositeKey -> the agent's own "I am waiting for instructions" (#1786). */
156
- const awaitingInstruction = globalThis.__agentEventAwaitingInstruction ??
157
- (globalThis.__agentEventAwaitingInstruction = new Map());
158
- /**
159
- * compositeKey -> the last non-null model this instance reported (Issue #1783).
160
- *
161
- * A *separate* map, not a field read off {@link lastAgentEvent}, and that is the
162
- * whole point of it. `lastAgentEvent` is replaced wholesale on every delivery,
163
- * and Claude puts the model on `SessionStart` and on a `PostModelSwitch`
164
- * (Issue #2363) and on nothing else — so the very next `UserPromptSubmit` would
165
- * overwrite the only record that ever knew it, and
166
- * the UI would show the model for the fraction of a second between session start
167
- * and the first prompt. Keeping the last *non-null* sighting separately is what
168
- * makes "which model is this session on" answerable at all for that tool.
169
- *
170
- * Never written with null: absent means "nothing has ever said", which is the
171
- * honest state for gemini, copilot, and any session that predates this server
172
- * process. See {@link getLastKnownAgentModel}.
173
- */
174
- const lastAgentModel = globalThis.__agentEventLastModel ??
175
- (globalThis.__agentEventLastModel = new Map());
176
- /**
177
- * compositeKey -> epoch ms of the report that last wrote {@link lastAgentModel}
178
- * (Issue #2361).
179
- *
180
- * Kept so the frame can be judged NEWER than the hook. When #2361 was measured
181
- * on 2.1.263, Claude named its model on `SessionStart` and on nothing
182
- * CommandMate registered — `/model` fired none of the seven events, and the
183
- * `SessionStart` a `/clear` emits carries no `model` key — so after a switch
184
- * the hook latch was a true statement about a process that had since changed
185
- * its mind, and the screen was the only channel that heard it. Issue #2363
186
- * registers `PostModelSwitch`, which re-stamps this on every switch; the stamp
187
- * now matters for the sessions where that hook does not arrive. See
188
- * {@link resolveAgentModel}.
189
- */
190
- const lastAgentModelAt = globalThis.__agentEventLastModelAt ??
191
- (globalThis.__agentEventLastModelAt = new Map());
192
- /**
193
- * compositeKey -> what the terminal frame last showed for this instance (#1784).
194
- *
195
- * The second source, and a strictly different kind of fact from
196
- * {@link lastAgentModel}: that one is the agent naming itself over the hook
197
- * channel, this one is the TUI's own chrome read back off the screen. It is
198
- * kept apart rather than folded into the first map because the two have a
199
- * precedence between them ({@link getResolvedAgentModelInfo}) — merging on write
200
- * would let a scraped display name ("Gemini 3.7 Flash") overwrite the exact id
201
- * the agent reported, with nothing left to recover it from.
202
- *
203
- * Latched the same way and for a sharper reason: Claude prints its model in the
204
- * startup banner and nowhere else, and tmux keeps 2000 lines of history, so on
205
- * any session that has been talking for a while the banner is simply gone. The
206
- * screen going quiet is not the model changing, so the last non-null sighting
207
- * stands until the process it described does not (see
208
- * {@link beginAgentEventGeneration} / {@link discardAgentEventState}).
209
- */
210
- const capturedModelInfo = globalThis.__agentCapturedModelInfo ??
211
- (globalThis.__agentCapturedModelInfo = new Map());
212
- /**
213
- * compositeKey -> the effort the *agent itself* last reported (Issue #2048).
214
- *
215
- * The third source, and until #2048 there was no such thing: #1784's whole
216
- * premise was that "no hook payload of any tool carries an effort field", which
217
- * was true of the five push tools and stayed true of opencode's own screen —
218
- * `model-info-extractor` still records that opencode prints no effort anywhere
219
- * in its pane, across 17 live frames.
220
- *
221
- * What changed is the *channel*, not the screen. opencode calls it a **variant**
222
- * and publishes it on the frames CommandMate is subscribed to —
223
- * `Session.model.variant` on `session.updated` and `info.variant` on
224
- * `message.updated`, measured on 1.18.22 in an isolated `HOME`
225
- * (`docs/design/opencode-server-live-verification.md` §20.4). The names in that
226
- * catalogue are `low` / `medium` / `high` / `max` / `minimal` / `none` /
227
- * `xhigh`, and each one's entry carries an `effort` (or `reasoningEffort`) equal
228
- * to itself — so the value is an effort in opencode's own vocabulary as well as
229
- * in this map's.
230
- *
231
- * Kept apart from {@link capturedModelInfo} for the reason that map is kept
232
- * apart from {@link lastAgentModel}: there is a precedence between them
233
- * ({@link getResolvedAgentModelInfo}), and merging on write would lose it.
234
- * Latched the same way, and never written with null — a turn that reports no
235
- * variant is opencode running the model's default, not the previous choice
236
- * being withdrawn.
237
- */
238
- const reportedEffort = globalThis.__agentEventLastEffort ??
239
- (globalThis.__agentEventLastEffort = new Map());
240
- /**
241
- * compositeKey -> the model this instance was last observed on (Issue #2357).
242
- *
243
- * A *fourth* map beside the three latches rather than a comparison against
244
- * them, because the latches are what they are for a reason: each remembers the
245
- * last non-null value its channel produced, and none of them can say "and the
246
- * value before that". This map holds exactly that — the previous answer of
247
- * {@link getResolvedAgentModelInfo} — so a write to either latch can be judged
248
- * as "same model", "first sighting" or "changed" without re-deriving history
249
- * from maps that never kept it.
250
- *
251
- * Cleared wherever the latches are cleared (generation, discard, test seam) and
252
- * on `session_start`, so the first model a new agent process reports is
253
- * recorded as its starting model and announced to nobody.
254
- */
255
- const modelBaseline = globalThis.__agentModelBaseline ??
256
- (globalThis.__agentModelBaseline = new Map());
257
- /**
258
- * Listeners for the model edge, on `globalThis` for the reason the maps above
259
- * are: under `next dev` each route bundle evaluates this module once, and the
260
- * hook route (which writes the hook latch) and the status poll (which writes
261
- * the frame latch) are different bundles. A module-scoped set would let each
262
- * bundle notify only the listeners registered in the same bundle — which is
263
- * none, for the one that receives the hook.
264
- */
265
- const modelChangeListeners = globalThis.__agentModelChangeListeners ??
266
- (globalThis.__agentModelChangeListeners = new Set());
267
- /**
268
- * A trailing reasoning-effort token in a model id (`gemini-3.7-flash-high`).
269
- *
270
- * The same five words `model-info-extractor` recognises, matched only at the
271
- * very end and only after a separator, so `gpt-5-mini` keeps its `mini` and a
272
- * model that merely ends in these letters is untouched. agy encodes the effort
273
- * in the id it reports on every event (`deriveEffortFromModelId`), and a user
274
- * moving the same model from `high` to `low` has not changed model.
275
- */
276
- exports.AGENT_MODEL_EFFORT_SUFFIX_PATTERN = /[-_ ](?:minimal|low|medium|high|xhigh)$/i;
277
- /**
278
- * Tokens a display label carries that the id does not, dropped before comparing.
279
- *
280
- * Measured pairs only: claude's banner reads `Opus 5 (1M context)` for the id
281
- * `claude-opus-5[1m]` — the `1m` is part of the model, the word is not.
282
- */
283
- const MODEL_LABEL_NOISE_PATTERN = /\bcontext\b/gi;
284
- /** Lowercase alphanumerics, effort suffix and label-only words removed. */
285
- function modelNameKey(value) {
286
- return value
287
- .replace(exports.AGENT_MODEL_EFFORT_SUFFIX_PATTERN, '')
288
- .replace(MODEL_LABEL_NOISE_PATTERN, '')
289
- .toLowerCase()
290
- .replace(/[^a-z0-9]/g, '');
291
- }
292
- /**
293
- * Do two model names name the same model? (Issue #2357)
294
- *
295
- * Within one channel the answer is exact on alphanumerics — `GPT-5 mini` and
296
- * `gpt-5-mini` are one model rendered two ways (copilot's bar and its notice,
297
- * both read off the frame), while `gpt-5` and `gpt-5-mini` are two models and
298
- * the second is precisely the downgrade this Issue exists to announce. The
299
- * effort suffix is stripped first, because agy's id carries it and an effort
300
- * change is not a model change.
301
- *
302
- * *Across* channels the rule is widened to containment: the hook reports an
303
- * id, the screen prints a label, and every measured pair is the label's key
304
- * inside the id's (`gemini37flash` ⊂ `gemini37flashhigh`, `opus51m` ⊂
305
- * `claudeopus51m`, `claudesonnet46` ⊂ `anthropicclaudesonnet46`). Containment
306
- * is NOT applied within a channel — it would fold `gpt-5` into `gpt-5-mini` —
307
- * and a caller has to say which case it is in.
308
- *
309
- * Exported for the tests and for anything that has to agree with this module
310
- * about identity; deliberately local rather than the extractor's `sameModel`,
311
- * which is a single-channel exact match and is shared with #2358's edit.
312
- */
313
- function isSameAgentModelName(a, b, options = {}) {
314
- const ka = modelNameKey(a);
315
- const kb = modelNameKey(b);
316
- if (ka === kb)
317
- return true;
318
- if (!options.crossSource)
319
- return false;
320
- if (ka.length === 0 || kb.length === 0)
321
- return false;
322
- return ka.includes(kb) || kb.includes(ka);
323
- }
324
- /**
325
- * Subscribe to the model edge (Issue #2357).
326
- *
327
- * The listener receives every transition {@link observeAgentModel} judged to be
328
- * a change — never a first sighting, never a value going quiet. `#2357`'s
329
- * realtime broadcaster and push notifier are the two subscribers; neither keeps
330
- * a "what was it last time?" of its own, which is the point of this being an
331
- * edge rather than a level.
332
- *
333
- * @returns The unsubscribe function
334
- */
335
- function onAgentModelChange(listener) {
336
- modelChangeListeners.add(listener);
337
- return () => {
338
- modelChangeListeners.delete(listener);
339
- };
340
- }
341
- /** Deliver one change; a listener that throws must not disturb the write that observed it. */
342
- function emitAgentModelChange(change) {
343
- for (const listener of modelChangeListeners) {
344
- try {
345
- listener(change);
346
- }
347
- catch {
348
- // Advisory. The hook route and the status poll are the callers, and a
349
- // notification failure is not a reason to fail either.
350
- }
351
- }
352
- }
353
- /**
354
- * The model this instance was last observed on, or null. Test seam.
355
- */
356
- function getAgentModelBaseline(worktreeId, cliToolId, instanceId) {
357
- return modelBaseline.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
358
- }
359
- /**
360
- * Judge the resolved model after a latch wrote, and announce a change
361
- * (Issue #2357).
362
- *
363
- * Called after every write to {@link lastAgentModel} and
364
- * {@link capturedModelInfo}, with the channel that wrote. What it compares is
365
- * {@link getResolvedAgentModelInfo}'s *model* — the merged value the UI shows —
366
- * against the previous merged value this map remembers. The suppression rules,
367
- * each of which is a measured false positive:
368
- *
369
- * - **`null → value`** (first report, server restart): recorded as the
370
- * starting model, announced to nobody.
371
- * - **`value → null`**: cannot reach here — the latches never write null, so
372
- * a frame that stopped showing the banner leaves the merged value where it
373
- * was. A generation reset clears this map along with the latches, so the
374
- * next value is a first sighting again.
375
- * - **same name, either channel**: `GPT-5 mini` after `gpt-5-mini` is not a
376
- * change. Compared with {@link isSameAgentModelName}; the cross-channel
377
- * widening applies only when the merged value's source actually moved.
378
- * - **`session_start`**: {@link recordAgentEvent} drops the baseline first, so
379
- * the model a relaunched process reports is that process's starting model.
380
- *
381
- * Everything else is a change and is emitted exactly once — the baseline is
382
- * advanced before the listeners run, so a listener that re-enters this module
383
- * cannot see the same edge twice.
384
- */
385
- function observeAgentModel(worktreeId, cliToolId, instanceId, source, at) {
386
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
387
- // Where the merged value came from is decided in one place
388
- // (`resolveAgentModel`, Issue #2361): the hook's, unless the frame overtook
389
- // it — and that is independent of which channel just wrote.
390
- const { info, source: resolvedSource } = resolveAgentModel(key, cliToolId);
391
- const resolved = info.model;
392
- if (resolved === null || resolvedSource === null)
393
- return;
394
- const previous = modelBaseline.get(key);
395
- const next = { model: resolved, source: resolvedSource };
396
- if (previous === undefined) {
397
- modelBaseline.set(key, next);
398
- trimOldestEntries(modelBaseline, MAX_RECENT_EVENT_KEYS);
399
- return;
400
- }
401
- if (isSameAgentModelName(previous.model, resolved, {
402
- crossSource: previous.source !== resolvedSource,
403
- })) {
404
- // Same model, possibly a new spelling or a new channel: follow it so the
405
- // next comparison is against the current representation.
406
- modelBaseline.set(key, next);
407
- return;
408
- }
409
- modelBaseline.set(key, next);
410
- emitAgentModelChange({
411
- worktreeId,
412
- cliToolId,
413
- instanceId: instanceId ?? cliToolId,
414
- from: previous.model,
415
- to: resolved,
416
- source,
417
- at,
418
- });
419
- }
420
- /**
421
- * How long two identical events count as one delivery.
422
- *
423
- * Issue #1722 injects hooks at session start, and `--settings` hooks are
424
- * *concatenated* with the user's own rather than replacing them, so anyone who
425
- * followed the #1549 manual setup now has two `Stop` hooks posting the same
426
- * turn. `applyAgentStopEvent` is idempotent for the timestamp, but
427
- * `applyTaskEvent` is not: each delivery writes its own `agent_idle` row, and a
428
- * reader counting rows would see one turn as two.
429
- *
430
- * A turn cannot end twice in three seconds, and both deliveries carry the same
431
- * `session_id`, so the window is generous relative to the real signal and tight
432
- * relative to anything it could wrongly swallow.
433
- */
434
- exports.AGENT_EVENT_DEDUP_WINDOW_MS = 3000;
435
- /** Cap on retained dedup keys, so a long-lived server cannot grow one per turn. */
436
- const MAX_RECENT_EVENT_KEYS = 512;
437
- /** A zeroed tally. */
438
- function emptyDropCounts() {
439
- return {
440
- dedupDropped: { identity: 0, timeWindow: 0 },
441
- decisionEvicted: 0,
442
- idsDiscarded: 0,
443
- dialogTimedOut: 0,
444
- decisionOverflow: 0,
445
- };
446
- }
447
- /** The live tally for one instance, created on first use. */
448
- function dropsFor(key) {
449
- let counts = dropCounts.get(key);
450
- if (!counts) {
451
- counts = emptyDropCounts();
452
- dropCounts.set(key, counts);
453
- // The composite key is (worktree, tool, instance) and a long-lived server
454
- // accumulates worktrees; bounded like every other map here (DR4-009).
455
- trimOldestEntries(dropCounts, MAX_RECENT_EVENT_KEYS);
456
- }
457
- return counts;
458
- }
459
- /**
460
- * What this instance has had dropped, or a zeroed tally when nothing has.
461
- *
462
- * A copy, not the live object: this is published on the hot path and a caller
463
- * that could mutate it would be able to erase the evidence.
464
- */
465
- function getAgentEventDropCounts(worktreeId, cliToolId, instanceId) {
466
- const counts = dropCounts.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
467
- if (!counts)
468
- return emptyDropCounts();
469
- return { ...counts, dedupDropped: { ...counts.dedupDropped } };
470
- }
471
- /** Epoch ms the current generation began, or null when none was opened. */
472
- function currentGeneration(key) {
473
- return generationStartedAt.get(key) ?? null;
474
- }
475
- /**
476
- * The turn this instance is in, with the generation fence applied.
477
- *
478
- * A turn whose {@link TurnRecord.generationAt} is not the current generation was
479
- * opened by a process that has been replaced. It is not deleted — the fence is
480
- * cheap and deleting would lose the eviction tally — it is simply not this
481
- * instance's turn any more.
482
- */
483
- function fencedTurn(key) {
484
- const turn = agentTurns.get(key);
485
- if (!turn)
486
- return null;
487
- if (turn.generationAt !== currentGeneration(key))
488
- return null;
489
- return turn;
490
- }
491
- /**
492
- * {@link fencedTurn} with the staleness bound applied.
493
- *
494
- * The bound is applied by *closing* the turn rather than by hiding it, so
495
- * `capture --json` says `closedBy: 'stale'` instead of going quiet — "the agent
496
- * never reported the end of this turn" and "nothing has been reported at all"
497
- * are different problems and an operator has to be able to tell them apart.
498
- *
499
- * Writes on read, which this module already does for corroboration, and is
500
- * idempotent: the close is stamped from the event's own clock, so it does not
501
- * move with the reader's `now`.
502
- */
503
- function effectiveTurn(key, now) {
504
- const turn = fencedTurn(key);
505
- if (turn === null)
506
- return null;
507
- if (turn.closedAt === null && now - turn.displayEvent.at >= provisional_turn_1.TURN_STALE_AFTER_MS) {
508
- turn.closedAt = turn.displayEvent.at + provisional_turn_1.TURN_STALE_AFTER_MS;
509
- turn.closedBy = 'stale';
510
- }
511
- return turn;
512
- }
513
- /** Next {@link TurnRecord.turnId}. See {@link turnSequence}. */
514
- function nextTurnId(at) {
515
- turnSequence.value += 1;
516
- return `turn-${at}-${turnSequence.value}`;
517
- }
518
- /**
519
- * Drop the decisions this turn is holding, counting them.
520
- *
521
- * The generation path is the one §4 D3 決定 2 names: a process that has been
522
- * replaced cannot have its approvals answered, and leaving them behind would
523
- * publish `waiting` for a pane whose dialog went away with the process that
524
- * drew it.
525
- */
526
- function evictDecisions(key, turn) {
527
- if (turn.pendingDecisions.length === 0)
528
- return;
529
- dropsFor(key).decisionEvicted += turn.pendingDecisions.length;
530
- turn.pendingDecisions = [];
531
- }
532
- /**
533
- * The decisions still describing something, dropping the ones that are not.
534
- *
535
- * The retention bound of {@link DIALOG_PENDING_MAX_MS}, applied on read and
536
- * counted as `dialogTimedOut` so the release is visible. Kept here rather than
537
- * in a timer for the reason the rest of this module has no timers: the state
538
- * describes a live tmux session, and a reader is the only thing that ever needs
539
- * the answer.
540
- */
541
- function livePendingDecisions(key, turn, now) {
542
- const live = turn.pendingDecisions.filter((decision) => (0, provisional_turn_1.isDecisionLive)(decision, now));
543
- const dropped = turn.pendingDecisions.length - live.length;
544
- if (dropped > 0) {
545
- dropsFor(key).dialogTimedOut += dropped;
546
- turn.pendingDecisions = live;
547
- }
548
- return live;
549
- }
550
- /**
551
- * The approvals this instance is blocked on right now (Issue #1930).
552
- *
553
- * Published as `structuredEvents.pendingDecisions`; `#1932` is what teaches
554
- * `commandmate respond` to name one of these ids.
555
- *
556
- * @param now - Epoch ms; defaults to now
557
- */
558
- function getPendingDecisions(worktreeId, cliToolId, instanceId, now = Date.now()) {
559
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
560
- const turn = effectiveTurn(key, now);
561
- if (turn === null)
562
- return [];
563
- return livePendingDecisions(key, turn, now);
564
- }
565
- /**
566
- * The turn record for one instance, fenced and aged, or null.
567
- *
568
- * The seam `current-output-builder` publishes from and the suites drive. The
569
- * returned object is the live record; nothing outside this module writes to it.
570
- *
571
- * @param now - Epoch ms; defaults to now
572
- */
573
- function getAgentTurn(worktreeId, cliToolId, instanceId, now = Date.now()) {
574
- return effectiveTurn((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), now);
575
- }
576
- /** {@link getAgentTurn} projected onto the four published fields. */
577
- function getPublishedAgentTurn(worktreeId, cliToolId, instanceId, now = Date.now()) {
578
- return (0, provisional_turn_1.derivePublishedTurn)(getAgentTurn(worktreeId, cliToolId, instanceId, now));
579
- }
580
- /**
581
- * Close the open turn on evidence that did not come from the agent (Issue #1930).
582
- *
583
- * The one seam for the two {@link TurnCloseReason} values nothing in the event
584
- * stream can produce:
585
- *
586
- * - `scraper_evidence` — see {@link observeScraperCompletionEvidence}, which is
587
- * the caller in this tree.
588
- * - `resync_idle` — a source whose `capabilities.resync` lets it be re-read
589
- * answering "not busy" after a dropped transport. The reconnect loop that
590
- * would call it lives in `sources/opencode/subscription`, which is #1931's
591
- * file and deliberately untouched here; the value is in the vocabulary and
592
- * the seam accepts it, so wiring it is a call site rather than a new state.
593
- *
594
- * A no-op when no turn is open, which is the ordinary case: neither of these is
595
- * a statement about a session that already reported it finished.
596
- *
597
- * @param at - Epoch ms; defaults to now
598
- * @returns Whether a turn was closed
599
- */
600
- function closeAgentTurn(worktreeId, cliToolId, instanceId, closedBy, at = Date.now()) {
601
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
602
- const turn = fencedTurn(key);
603
- if (turn === null || turn.closedAt !== null)
604
- return false;
605
- turn.closedAt = at;
606
- turn.closedBy = closedBy;
607
- return true;
608
- }
609
- /**
610
- * Feed one poll's reading of the pane into the turn's completion counter
611
- * (Issue #1930).
612
- *
613
- * `completed` is the scraper's own verdict — `ready` with positive evidence —
614
- * NOT the merged one, which would be circular: the merge is what the structured
615
- * layer's `running` overrides, so reading it back would only ever confirm this
616
- * layer's own answer.
617
- *
618
- * See {@link SCRAPER_COMPLETION_POLLS} for why three, and for why closing here
619
- * does not complete a `commandmate wait`.
620
- *
621
- * @param at - Epoch ms; defaults to now
622
- * @returns Whether this poll closed the turn
623
- */
624
- function observeScraperCompletionEvidence(worktreeId, cliToolId, instanceId, completed, at = Date.now()) {
625
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
626
- const turn = fencedTurn(key);
627
- if (turn === null || turn.closedAt !== null)
628
- return false;
629
- if (!completed) {
630
- turn.scraperCompletionPolls = 0;
631
- return false;
632
- }
633
- turn.scraperCompletionPolls += 1;
634
- if (turn.scraperCompletionPolls < provisional_turn_1.SCRAPER_COMPLETION_POLLS)
635
- return false;
636
- turn.closedAt = at;
637
- turn.closedBy = 'scraper_evidence';
638
- return true;
639
- }
110
+ const lastAgentEvent = (0, global_state_1.getOrInitGlobal)('__agentEventLast', () => new Map());
640
111
  /**
641
112
  * Record that `instanceId` reported it stopped.
642
113
  *
@@ -714,7 +185,7 @@ function isLateSessionStart(worktreeId, cliToolId, instanceId, record, options)
714
185
  return false;
715
186
  if (options.sessionStartMayArriveLate !== true)
716
187
  return false;
717
- const openTurn = getStructuredSessionState(worktreeId, cliToolId, instanceId, record.at);
188
+ const openTurn = (0, agent_event_structured_state_1.getStructuredSessionState)(worktreeId, cliToolId, instanceId, record.at);
718
189
  if (openTurn === null || !OPEN_TURN_STATUSES.includes(openTurn.status))
719
190
  return false;
720
191
  const openSessionId = lastAgentEvent.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId))?.sessionId ?? null;
@@ -742,8 +213,8 @@ function recordAgentEvent(worktreeId, cliToolId, instanceId, record, options = {
742
213
  // model would otherwise be the single case where the model is extracted and
743
214
  // then dropped on the floor. copilot reports none today (#1783), which is
744
215
  // exactly why this has to be decided here rather than left to be noticed.
745
- latchAgentModel(key, record);
746
- observeAgentModel(worktreeId, cliToolId, instanceId, 'hook', record.at);
216
+ (0, agent_event_model_1.latchAgentModel)(key, record);
217
+ (0, agent_event_model_1.observeAgentModel)(worktreeId, cliToolId, instanceId, 'hook', record.at);
747
218
  return { recorded: false, skipped: 'late-session-start' };
748
219
  }
749
220
  lastAgentEvent.set(key, record);
@@ -753,567 +224,35 @@ function recordAgentEvent(worktreeId, cliToolId, instanceId, record, options = {
753
224
  // latch so the comparison in `observeAgentModel` has nothing to compare
754
225
  // against. `/clear` (session_end + session_start on the same process, same
755
226
  // model) passes through here unchanged: same name, no edge either way.
756
- modelBaseline.delete(key);
227
+ agent_event_model_1.modelBaseline.delete(key);
757
228
  }
758
- latchAgentModel(key, record);
759
- observeAgentModel(worktreeId, cliToolId, instanceId, 'hook', record.at);
760
- applyAskUserQuestionTransition(key, record);
229
+ (0, agent_event_model_1.latchAgentModel)(key, record);
230
+ (0, agent_event_model_1.observeAgentModel)(worktreeId, cliToolId, instanceId, 'hook', record.at);
231
+ (0, agent_event_ask_user_question_1.applyAskUserQuestionTransition)(key, record);
761
232
  if (record.event === 'session_start') {
762
233
  // The agent restarting inside a pane CommandMate never touched — `claude`
763
234
  // relaunched by hand, or a `/clear` (which emits SessionEnd then
764
235
  // SessionStart on a live session) — is a new generation just as much as a
765
236
  // new tmux session is. Recorded from the event's own timestamp, so the
766
237
  // event that opens a generation is never stale against it.
767
- generationStartedAt.set(key, record.at);
238
+ agent_event_turn_1.generationStartedAt.set(key, record.at);
239
+ // Issue #3397: and Auto-Yes's Enter belonged to the previous process's
240
+ // screen. Only here — past `isLateSessionStart` above, and past the
241
+ // duplicate window the intake applies before calling this — so a late or
242
+ // repeated `SessionStart` never expires the record of the live process.
243
+ (0, auto_yes_enter_fallback_state_1.beginEnterFallbackSession)(worktreeId, cliToolId, instanceId);
768
244
  // Issue #1930: and the turn the previous process was in ends with it. Done
769
245
  // here rather than inside the transition below so the two paths into a new
770
246
  // generation — this one and `beginAgentEventGeneration` — close the turn in
771
247
  // exactly one place.
772
- fenceTurnForNewGeneration(key, record.at);
248
+ (0, agent_event_turn_1.fenceTurnForNewGeneration)(key, record.at);
773
249
  }
774
250
  // Issue #1930: after the generation is settled, so a turn opened by this
775
251
  // event belongs to the generation the event itself opened.
776
- applyTurnTransition(key, record);
777
- applyAwaitingInstructionTransition(key, record);
252
+ (0, agent_event_turn_1.applyTurnTransition)(key, record);
253
+ (0, agent_event_awaiting_instruction_1.applyAwaitingInstructionTransition)(key, record);
778
254
  return { recorded: true };
779
255
  }
780
- /**
781
- * End the open turn because the process that owned it has been replaced
782
- * (Issue #1930).
783
- *
784
- * The turn is closed and re-stamped into the new generation rather than
785
- * deleted, which is the difference between `capture --json` reporting
786
- * `closedBy: 'generation'` and reporting nothing at all. Its approvals go: they
787
- * were raised by a process that no longer exists, and answering one would
788
- * deliver a verdict into a slot nobody is holding.
789
- */
790
- function fenceTurnForNewGeneration(key, at) {
791
- const turn = agentTurns.get(key);
792
- if (!turn)
793
- return;
794
- evictDecisions(key, turn);
795
- // A turn that had ALREADY ended is left where it is, which means the fence in
796
- // `fencedTurn` hides it from here on. That asymmetry is deliberate: the
797
- // previous process's `Stop` is not this process's, and re-stamping it into the
798
- // new generation would publish `ready` — "the agent finished" — for a session
799
- // nobody has typed into yet. Before #1930 the same protection came from
800
- // comparing the event's timestamp against the generation.
801
- if (turn.closedAt !== null)
802
- return;
803
- // An OPEN turn is closed and carried across, so `capture --json` can say
804
- // `closedBy: 'generation'` rather than going quiet. `getStructuredSessionState`
805
- // reads that reason as "nothing is known", so it publishes no verdict either
806
- // way; what it buys is an operator being able to tell "the session was
807
- // restarted under this turn" from "nothing was ever reported".
808
- turn.closedAt = at;
809
- turn.closedBy = 'generation';
810
- turn.generationAt = at;
811
- }
812
- /**
813
- * Issue #1783: latch, never clear. An event without a model is the ordinary
814
- * case (Claude sends one on `SessionStart` and, since #2363, on a model
815
- * switch), and reading it as "the model is now unknown" would blank the display
816
- * on the very next event.
817
- */
818
- function latchAgentModel(key, record) {
819
- if (typeof record.model === 'string' && record.model !== '') {
820
- lastAgentModel.set(key, record.model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
821
- // Issue #2361: stamped on every model-bearing report, not only on a change
822
- // of value — a hook that names the same model again is the agent
823
- // re-affirming it, and a frame has to be newer than THAT to overtake.
824
- lastAgentModelAt.set(key, record.at);
825
- }
826
- }
827
- /**
828
- * The third state: "this turn is over and nobody has told me what to do next"
829
- * (Issue #1786).
830
- *
831
- * | event | effect on awaiting_instruction |
832
- * |-----------------------------------|--------------------------------|
833
- * | `notification(idle_prompt)` | **set** |
834
- * | `user_prompt_submit` | release |
835
- * | `session_start` / `session_end` | release |
836
- * | everything else | unchanged |
837
- *
838
- * It is a boolean beside the status rather than a fifth `SessionStatus`, because
839
- * `idle_prompt` already maps to `ready` (`agentEventToSessionStatus`) and
840
- * `ready` means "you can send a message" everywhere in this codebase — the
841
- * sidebar, `deriveCliStatus`, `getNextAction`, `commandmate wait`. Widening that
842
- * vocabulary to carry "and it is *asking* you to" would have every consumer of
843
- * the four values re-decide what they mean; the boolean asks nothing of anyone
844
- * who does not want it.
845
- *
846
- * `stop` deliberately does NOT set it. `Stop` fires when the turn ends, which is
847
- * most of the time the agent has simply finished a step of its own plan;
848
- * `Notification(idle_prompt)` is Claude's separate, later "waiting for your
849
- * input" signal, and using the turn boundary instead would mark every
850
- * intermediate stop as a request for instructions.
851
- *
852
- * `pre_tool_use` / `post_tool_use` / `notification(permission_prompt)` leave it
853
- * alone, and that is not a gap: a tool call or a dialog can only happen inside a
854
- * turn, and the turn's own `user_prompt_submit` has already released it. Adding
855
- * them would only paper over a `UserPromptSubmit` that never arrived — and an
856
- * operator whose `UserPromptSubmit` hook is missing has no `Notification` hook
857
- * either, so there would be nothing to release.
858
- *
859
- * There is deliberately no age bound here, unlike every other state in this
860
- * module. Those bound the damage of an event that may never arrive (a lost
861
- * `Stop`, a dialog answered with no event to say so). This one is released by
862
- * events that cannot be missed while the fact is still true: typing into the
863
- * composer — from the app or straight into the tmux pane — raises
864
- * `UserPromptSubmit`, and ending the session raises `SessionEnd`. An agent that
865
- * has been idle for six hours genuinely is still awaiting instructions, and
866
- * expiring the flag would erase the notification #1790 exists to send. The
867
- * generation fence in {@link isAwaitingInstruction} still applies, so the flag
868
- * never survives the process that reported it.
869
- */
870
- function applyAwaitingInstructionTransition(key, record) {
871
- switch (record.event) {
872
- case 'notification':
873
- if (record.detail === 'idle_prompt') {
874
- awaitingInstruction.set(key, { at: record.at, message: record.message ?? null });
875
- }
876
- return;
877
- case 'user_prompt_submit':
878
- case 'session_start':
879
- case 'session_end':
880
- awaitingInstruction.delete(key);
881
- return;
882
- case 'stop':
883
- case 'pre_tool_use':
884
- case 'post_tool_use':
885
- return;
886
- default:
887
- // exhaustive check: a new AgentEventType must decide its transition here
888
- record.event;
889
- return;
890
- }
891
- }
892
- /**
893
- * The agent's own "waiting for your input", or null (Issue #1786).
894
- *
895
- * Fenced by generation like the rest of this module: an `idle_prompt` from the
896
- * Claude process that used to live in this pane is not this one's.
897
- */
898
- function getAwaitingInstruction(worktreeId, cliToolId, instanceId) {
899
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
900
- const record = awaitingInstruction.get(key);
901
- if (!record)
902
- return null;
903
- const generation = generationStartedAt.get(key);
904
- if (generation !== undefined && record.at < generation)
905
- return null;
906
- return record;
907
- }
908
- /** {@link getAwaitingInstruction} as the boolean the list API publishes. */
909
- function isAwaitingInstruction(worktreeId, cliToolId, instanceId) {
910
- return getAwaitingInstruction(worktreeId, cliToolId, instanceId) !== null;
911
- }
912
- /**
913
- * The turn transition: what one delivery does to {@link TurnRecord}
914
- * (Issue #1930, §4 D3 決定 2).
915
- *
916
- * | event | turn | dialog | display |
917
- * |------------------------------------|-------------------------|----------|---------|
918
- * | `user_prompt_submit` | **opens a new one** | release | yes |
919
- * | `pre_tool_use` | continues, else opens | unchanged| yes |
920
- * | `post_tool_use` | continues, else opens | release | yes |
921
- * | `stop` (this session) | **closes** `stop` | release | yes |
922
- * | `stop` (another session) | unchanged | unchanged| no |
923
- * | `session_end` | **closes** `session_end`| release | no |
924
- * | `session_start` | new generation (above) | evicted | no |
925
- * | `notification(permission_prompt)` | unchanged | **open** | only to bootstrap |
926
- * | `notification(permission_replied)` | unchanged | release | no |
927
- * | `notification(idle_prompt)` | unchanged | release | yes |
928
- * | `notification(anything else)` | unchanged | unchanged| no |
929
- *
930
- * Four rows are the whole point of the Issue, and each of them was a defect
931
- * under "the newest event is the verdict":
932
- *
933
- * - **`user_prompt_submit` opens a new turn, the tool-call events continue the
934
- * open one.** That is what makes {@link TurnRecord.turnId} an identity: #1926
935
- * re-stamped it on every `pre_tool_use`, so a consumer reading a changed id
936
- * as "a new turn began" false-positived several times inside one turn. `wait`
937
- * reads it that way now.
938
- * - **A `stop` naming another session changes nothing.** opencode publishes
939
- * `session.idle` for every session its server holds, other processes'
940
- * included (#1758 §5.6).
941
- * - **`session_start` / `session_end` / an unknown notification /
942
- * `permission_replied` do not become the displayed event.** An event carrying
943
- * no verdict must not erase one. #1903 fixed the single measured instance
944
- * (copilot's late `SessionStart`) by holding the delivery; the rule is
945
- * general here, and the capability it reads is still the only reason a
946
- * `session_start` is held rather than recorded.
947
- * - **`notification(permission_prompt)` does not become the displayed event
948
- * when a turn already exists.** The dialog is a fact about the *pane*, and
949
- * it is published by the decision ledger below. Letting it overwrite the
950
- * display is how the pre-#1898 `waiting` outlived the approval that caused
951
- * it: released, the record still said `waiting`, because the only thing it
952
- * could read was the event that opened the dialog.
953
- *
954
- * `idle_prompt` is the one row that publishes `ready` **without** closing the
955
- * turn, and that is a measurement rather than an oversight: #1839 caught Claude
956
- * emitting it 62 s into a turn that ran nothing, so it cannot be a turn
957
- * boundary. `wait`'s gate stays armed through it; what changes is only what the
958
- * pane displays.
959
- */
960
- function applyTurnTransition(key, record) {
961
- const generation = currentGeneration(key);
962
- // An event stamped before the current generation was produced by a process
963
- // that has been replaced. Refusing it here is what makes re-recording a stale
964
- // `user_prompt_submit` at its original timestamp a no-op.
965
- if (generation !== null && record.at < generation)
966
- return;
967
- switch (record.event) {
968
- case 'user_prompt_submit':
969
- openTurn(key, record, generation, { continueOpen: false });
970
- releaseAllDecisions(key);
971
- return;
972
- case 'post_tool_use':
973
- openTurn(key, record, generation, { continueOpen: true });
974
- // The tool call the dialog was gating has finished, so somebody answered
975
- // it (Issue #1726). This is the release #1725 could not have: it had no
976
- // event meaning "the human answered", only `Stop` meaning "the turn
977
- // ended", which can be minutes later.
978
- releaseAllDecisions(key);
979
- return;
980
- case 'pre_tool_use':
981
- // Deliberately leaves the dialog alone (Issue #1726). It is the
982
- // `AskUserQuestion` invocation, and a picker being *about to be drawn* is
983
- // not a fact this state can carry.
984
- openTurn(key, record, generation, { continueOpen: true });
985
- return;
986
- case 'stop':
987
- applyStopToTurn(key, record, generation);
988
- return;
989
- case 'session_end':
990
- applySessionEndToTurn(key, record);
991
- releaseAllDecisions(key);
992
- return;
993
- case 'session_start':
994
- // The generation was opened by `recordAgentEvent` before this ran, and
995
- // `fenceTurnForNewGeneration` closed the turn with it. Nothing further:
996
- // the frame carries no verdict, so it is not the displayed event either.
997
- return;
998
- case 'notification':
999
- applyNotificationToTurn(key, record, generation);
1000
- return;
1001
- default:
1002
- // exhaustive check: a new AgentEventType must decide its transition here
1003
- record.event;
1004
- return;
1005
- }
1006
- }
1007
- /** The three fields the turn keeps from a record. */
1008
- function displayOf(record) {
1009
- return { event: record.event, at: record.at, detail: record.detail };
1010
- }
1011
- /**
1012
- * Open a turn for this event, or move the display onto an open one.
1013
- *
1014
- * @param continueOpen - Whether an already-open turn of the same session is
1015
- * this event's turn. False for `user_prompt_submit`, which IS a new turn.
1016
- */
1017
- function openTurn(key, record, generation, { continueOpen }) {
1018
- const turn = fencedTurn(key);
1019
- const openHere = turn !== null &&
1020
- turn.closedAt === null &&
1021
- turn.openedAt !== null &&
1022
- (0, provisional_turn_1.closesTurn)(turn, record.sessionId);
1023
- if (continueOpen && openHere) {
1024
- turn.displayEvent = displayOf(record);
1025
- // A hand-configured relay posts no session id, so a turn can learn one
1026
- // partway through. Learning it is what lets a later `stop` be matched.
1027
- turn.sessionId ??= record.sessionId;
1028
- turn.scraperCompletionPolls = 0;
1029
- return;
1030
- }
1031
- // A dialog-only record (openedAt null) is not a turn; its decisions belong to
1032
- // the turn that is opening now, because that is when they were raised.
1033
- const carried = turn !== null && turn.openedAt === null && turn.closedAt === null
1034
- ? turn.pendingDecisions
1035
- : [];
1036
- agentTurns.set(key, {
1037
- turnId: nextTurnId(record.at),
1038
- sessionId: record.sessionId,
1039
- openedAt: record.at,
1040
- closedAt: null,
1041
- closedBy: null,
1042
- generationAt: generation,
1043
- displayEvent: displayOf(record),
1044
- pendingDecisions: carried,
1045
- scraperCompletionPolls: 0,
1046
- });
1047
- boundTurnMap();
1048
- }
1049
- /**
1050
- * Apply a `stop` — the only event that is the agent saying its turn is over.
1051
- *
1052
- * A `stop` for another session leaves everything alone; see {@link closesTurn}.
1053
- * A `stop` with no turn open still records one, because "this instance last
1054
- * reported it stopped at T" is the fact `ready` is published from, and the
1055
- * opening it never saw is published as null rather than guessed.
1056
- */
1057
- function applyStopToTurn(key, record, generation) {
1058
- const turn = fencedTurn(key);
1059
- if (turn !== null && turn.closedAt === null && turn.openedAt !== null) {
1060
- if (!(0, provisional_turn_1.closesTurn)(turn, record.sessionId))
1061
- return;
1062
- turn.closedAt = record.at;
1063
- turn.closedBy = 'stop';
1064
- turn.displayEvent = displayOf(record);
1065
- releaseAllDecisions(key);
1066
- return;
1067
- }
1068
- agentTurns.set(key, {
1069
- turnId: nextTurnId(record.at),
1070
- sessionId: record.sessionId,
1071
- openedAt: null,
1072
- closedAt: record.at,
1073
- closedBy: 'stop',
1074
- generationAt: generation,
1075
- displayEvent: displayOf(record),
1076
- pendingDecisions: [],
1077
- scraperCompletionPolls: 0,
1078
- });
1079
- boundTurnMap();
1080
- }
1081
- /**
1082
- * `session_end`: the agent session this instance was talking to is gone.
1083
- *
1084
- * **Overwrites an existing close reason**, which is the one place in this
1085
- * module where a later event rewrites an earlier verdict, and #1723's contract
1086
- * is why. `/clear` emits `SessionEnd(reason=clear)` on a session that is alive
1087
- * and about to keep going, and the turn before it may perfectly well have ended
1088
- * with a `Stop`. Leaving that `Stop` standing would keep publishing "the agent
1089
- * finished" about a conversation that no longer exists — the integration pin is
1090
- * `current-output-structured-status-1723`, which asserts the session goes back
1091
- * to the scraper the moment `SessionEnd` lands.
1092
- *
1093
- * `closedBy: 'session_end'` publishes no verdict of its own (see
1094
- * {@link getStructuredSessionState}), so this is a retirement rather than a
1095
- * different answer.
1096
- */
1097
- function applySessionEndToTurn(key, record) {
1098
- const turn = fencedTurn(key);
1099
- if (turn === null)
1100
- return;
1101
- if (!(0, provisional_turn_1.closesTurn)(turn, record.sessionId))
1102
- return;
1103
- turn.closedAt = record.at;
1104
- turn.closedBy = 'session_end';
1105
- }
1106
- /**
1107
- * The notification rows of the transition table.
1108
- *
1109
- * Matched on `notification_type` ({@link AgentEventRecord.detail}), never on the
1110
- * human-facing `message` (D3): the observed messages are English prose the agent
1111
- * is free to reword.
1112
- */
1113
- function applyNotificationToTurn(key, record, generation) {
1114
- if (record.detail === 'permission_prompt') {
1115
- if (record.promptSettled === true) {
1116
- // Issue #1898: adjudicated before it was recorded, and the verdict
1117
- // reached the agent. There is no dialog and there never was one for a
1118
- // human to answer — opening a record for it published `waiting` for the
1119
- // whole of the tool call that followed (measured at eight seconds).
1120
- releaseSettledDecision(key, record);
1121
- return;
1122
- }
1123
- openDecision(key, generation, {
1124
- source: 'notification',
1125
- at: record.at,
1126
- message: record.message ?? null,
1127
- // Issue #2031. Was a hard `null`, which is what left an opencode approval
1128
- // reaching the browser with no statement of what it was for: the tool
1129
- // name is not in `permission.asked` and has to be carried on the record
1130
- // by whoever correlated it. Absent still means "this source had nothing
1131
- // to say", and `openDecision` leaves an earlier name in place for that.
1132
- toolName: record.toolName ?? null,
1133
- patterns: record.decisionPatterns ?? null,
1134
- decisionId: record.decisionId ?? null,
1135
- bootstrapDisplay: displayOf(record),
1136
- });
1137
- return;
1138
- }
1139
- if (record.detail === agent_event_types_1.PERMISSION_REPLIED_DETAIL) {
1140
- // Issue #1898. The agent's own statement that the dialog is gone — whoever
1141
- // answered it. Not a word this build reads a status from, so it decides
1142
- // nothing; all it does is retire the record.
1143
- if (record.promptSettled === true)
1144
- releaseSettledDecision(key, record);
1145
- return;
1146
- }
1147
- if (record.detail === 'idle_prompt') {
1148
- // The agent reporting it is sitting at the composer waiting for input is the
1149
- // agent saying nothing is in front of that composer.
1150
- releaseAllDecisions(key);
1151
- const turn = fencedTurn(key);
1152
- if (turn !== null && turn.closedAt === null) {
1153
- turn.displayEvent = displayOf(record);
1154
- return;
1155
- }
1156
- if (turn !== null)
1157
- return;
1158
- // Nothing to attach it to — the first thing this instance ever said, or the
1159
- // first since a generation. It still carries a verdict (`ready`), and #1723
1160
- // publishes it, so a display-only record is opened to hold it. `openedAt`
1161
- // stays null: an agent sitting at its composer is not in a turn, and a
1162
- // record that claimed otherwise would gate `wait` on a turn nobody opened.
1163
- agentTurns.set(key, {
1164
- turnId: nextTurnId(record.at),
1165
- sessionId: record.sessionId,
1166
- openedAt: null,
1167
- closedAt: null,
1168
- closedBy: null,
1169
- generationAt: generation,
1170
- displayEvent: displayOf(record),
1171
- pendingDecisions: [],
1172
- scraperCompletionPolls: 0,
1173
- });
1174
- boundTurnMap();
1175
- return;
1176
- }
1177
- // An unrecognised notification type is not evidence of anything, and guessing
1178
- // would be worse than the scraper.
1179
- }
1180
- /**
1181
- * Open, or refresh, a dialog record on this instance's turn.
1182
- *
1183
- * `bootstrapDisplay` is used only when there is no record at all to hang the
1184
- * decision on. That record is deliberately **not** an open turn — its
1185
- * `openedAt` is null — so releasing the decision hands the pane back to the
1186
- * scraper rather than asserting `running` for a turn nobody ever saw open.
1187
- */
1188
- function openDecision(key, generation, input) {
1189
- let turn = fencedTurn(key);
1190
- if (turn === null) {
1191
- turn = {
1192
- turnId: nextTurnId(input.at),
1193
- sessionId: null,
1194
- openedAt: null,
1195
- closedAt: null,
1196
- closedBy: null,
1197
- generationAt: generation,
1198
- displayEvent: input.bootstrapDisplay,
1199
- pendingDecisions: [],
1200
- scraperCompletionPolls: 0,
1201
- };
1202
- agentTurns.set(key, turn);
1203
- boundTurnMap();
1204
- }
1205
- else if (turn.closedAt !== null) {
1206
- // The dialog is being raised after the turn it would have belonged to
1207
- // ended — a `PermissionRequest` racing a `Stop`, or a re-check landing
1208
- // late. Re-open a dialog-only record rather than resurrecting the turn.
1209
- turn = {
1210
- turnId: nextTurnId(input.at),
1211
- sessionId: null,
1212
- openedAt: null,
1213
- closedAt: null,
1214
- closedBy: null,
1215
- generationAt: generation,
1216
- displayEvent: input.bootstrapDisplay,
1217
- pendingDecisions: [],
1218
- scraperCompletionPolls: 0,
1219
- };
1220
- agentTurns.set(key, turn);
1221
- boundTurnMap();
1222
- }
1223
- // Issue #1930 / S1: an id that fails validation is DISCARDED, not truncated —
1224
- // a truncated id compares equal to a different id sharing its prefix, and the
1225
- // reply to one approval would then retire another's record.
1226
- const decisionId = input.decisionId === null ? null : (0, provisional_turn_1.acceptExternalId)(input.decisionId);
1227
- if (input.decisionId !== null && decisionId === null)
1228
- dropsFor(key).idsDiscarded += 1;
1229
- const confirmed = input.source === 'notification';
1230
- // Which record, if any, this report is a second sighting of.
1231
- //
1232
- // - an id matches its own record;
1233
- // - an id with no record of its own may still be *confirming* the anonymous
1234
- // prediction that forecast it. That is the measured pair: a
1235
- // `PermissionRequest` this server declined to decide fires ~6 s before the
1236
- // `Notification(permission_prompt)` that proves the dialog, and the
1237
- // request carries no id;
1238
- // - an anonymous report merges only with another anonymous one. A source
1239
- // that publishes no ids therefore keeps exactly one dialog record per
1240
- // instance, which is what #1725 did and the honest limit for it — matching
1241
- // an anonymous report against an *identified* record would let one
1242
- // approval's forecast confirm a different approval's dialog.
1243
- const existing = decisionId !== null
1244
- ? (turn.pendingDecisions.find((decision) => decision.decisionId === decisionId) ??
1245
- turn.pendingDecisions.find((decision) => decision.decisionId === null && decision.source === 'permission-request'))
1246
- : turn.pendingDecisions.find((decision) => decision.decisionId === null);
1247
- if (existing) {
1248
- // The same dialog, reported twice: `PermissionRequest` predicted it and the
1249
- // `Notification` then proved it. Keep the earliest `at` — that is when the
1250
- // human was first blocked, and it is what the scraper-release grace and the
1251
- // age bound are measured from — and take the confirmation.
1252
- existing.message = (0, provisional_turn_1.boundDecisionMessage)(input.message) ?? existing.message;
1253
- existing.toolName = (0, provisional_turn_1.boundDecisionToolName)(input.toolName) ?? existing.toolName;
1254
- // Issue #2031, same rule as the two lines above it: a second sighting that
1255
- // knows the rules fills them in, one that does not leaves the earlier
1256
- // answer alone. The measured pair is exactly this — the `PermissionRequest`
1257
- // forecast carries no patterns, the `Notification` that confirms it does.
1258
- existing.patterns = (0, provisional_turn_1.boundDecisionPatterns)(input.patterns) ?? existing.patterns;
1259
- existing.decisionId ??= decisionId;
1260
- if (confirmed) {
1261
- existing.source = 'notification';
1262
- existing.confirmedAt ??= input.at;
1263
- }
1264
- return;
1265
- }
1266
- if (turn.pendingDecisions.length >= provisional_turn_1.MAX_PENDING_DECISIONS_PER_TURN) {
1267
- // S14(d). Counted rather than dropped in silence: an agent raising more
1268
- // approvals than this in one turn is a fact an operator has to be able to
1269
- // see, and the oldest is the one a human has been looking at.
1270
- dropsFor(key).decisionOverflow += 1;
1271
- return;
1272
- }
1273
- turn.pendingDecisions.push({
1274
- decisionId,
1275
- at: input.at,
1276
- source: input.source,
1277
- message: (0, provisional_turn_1.boundDecisionMessage)(input.message),
1278
- toolName: (0, provisional_turn_1.boundDecisionToolName)(input.toolName),
1279
- patterns: (0, provisional_turn_1.boundDecisionPatterns)(input.patterns),
1280
- confirmedAt: confirmed ? input.at : null,
1281
- scraperCorroborated: false,
1282
- recorded: false,
1283
- });
1284
- }
1285
- /** Retire every dialog this instance was holding. */
1286
- function releaseAllDecisions(key) {
1287
- const turn = fencedTurn(key);
1288
- if (turn !== null)
1289
- turn.pendingDecisions = [];
1290
- }
1291
- /**
1292
- * Retire the record a verdict was delivered for (Issue #1898).
1293
- *
1294
- * Matched on {@link AgentEventRecord.decisionId} when both sides have one: an
1295
- * agent that can run two approvals at once would otherwise have the reply to
1296
- * the first retire the record for the second. When either side is anonymous the
1297
- * release is unconditional — an unmatched id is a source that publishes none,
1298
- * and refusing to act there would leave the pre-#1898 stall in place for it.
1299
- */
1300
- function releaseSettledDecision(key, record) {
1301
- const turn = fencedTurn(key);
1302
- if (turn === null || turn.pendingDecisions.length === 0)
1303
- return;
1304
- const settledId = record.decisionId === null || record.decisionId === undefined
1305
- ? null
1306
- : (0, provisional_turn_1.acceptExternalId)(record.decisionId);
1307
- if (settledId === null) {
1308
- turn.pendingDecisions = [];
1309
- return;
1310
- }
1311
- turn.pendingDecisions = turn.pendingDecisions.filter((decision) => decision.decisionId !== null && decision.decisionId !== settledId);
1312
- }
1313
- /** Keep the turn map bounded the way every other map in this module is. */
1314
- function boundTurnMap() {
1315
- trimOldestEntries(agentTurns, MAX_RECENT_EVENT_KEYS);
1316
- }
1317
256
  /**
1318
257
  * @returns The last structured event reported by this instance, or null when it
1319
258
  * has reported none.
@@ -1321,277 +260,6 @@ function boundTurnMap() {
1321
260
  function getLastAgentEvent(worktreeId, cliToolId, instanceId) {
1322
261
  return lastAgentEvent.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
1323
262
  }
1324
- /**
1325
- * The last model this instance reported running, or null (Issue #1783).
1326
- *
1327
- * "Last **non-null**", which is the only useful reading: three of the four tools
1328
- * that publish a model publish it on some events and not others, and Claude
1329
- * publishes it on two (`SessionStart`, and each switch since #2363). Reading
1330
- * `getLastAgentEvent()?.model` would therefore answer null for almost every
1331
- * moment of almost every session.
1332
- *
1333
- * Deliberately **not** bounded by {@link STRUCTURED_STATE_MAX_AGE_MS}, unlike
1334
- * every other reader in this module. That bound exists because a *status* that
1335
- * nothing has refreshed is a claim about right now that may have expired — a
1336
- * lost `Stop` leaving the layer asserting `running` forever. A model is not that
1337
- * kind of claim: an eight-hour turn is on the same model at the end as at the
1338
- * start, and expiring it would blank the display on precisely the long-running
1339
- * sessions this is most useful for. What *is* honoured is identity: a new
1340
- * generation or a discarded session drops the value, because the process that
1341
- * reported it is gone. See {@link beginAgentEventGeneration} /
1342
- * {@link discardAgentEventState}.
1343
- *
1344
- * In-memory only — nothing is written to `session_states`. After a server
1345
- * restart codex and antigravity repopulate on their next event; Claude stays
1346
- * null until its next `SessionStart`, a gap Phase 2 (#1784) closes from the
1347
- * terminal frame.
1348
- */
1349
- function getLastKnownAgentModel(worktreeId, cliToolId, instanceId) {
1350
- return lastAgentModel.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
1351
- }
1352
- /**
1353
- * Latch the model the agent named for itself outside an agent event
1354
- * (Issue #2964).
1355
- *
1356
- * The hook channel's latch — the one {@link recordAgentEvent} writes from
1357
- * `record.model` — for a source whose model arrives on something that is not
1358
- * one of the seven event words. OpenCode V2 is that source: its `session.*`
1359
- * frames carry no model, and the model it actually answered with is read off
1360
- * the assistant messages of `GET /api/session/{id}/message` after the turn.
1361
- *
1362
- * Latch, never clear ({@link latchAgentModel}'s rule), and judged for a change
1363
- * exactly as a hook-borne model is, so #2357's broadcast and push fire for it.
1364
- *
1365
- * @param model - The model name, or null/empty to leave the latch alone
1366
- */
1367
- function recordAgentReportedModel(worktreeId, cliToolId, instanceId, model, at = Date.now()) {
1368
- if (typeof model !== 'string' || model === '')
1369
- return;
1370
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1371
- lastAgentModel.set(key, model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
1372
- lastAgentModelAt.set(key, at);
1373
- observeAgentModel(worktreeId, cliToolId, instanceId, 'hook', at);
1374
- }
1375
- /**
1376
- * Latch what a terminal capture showed for this instance (Issue #1784).
1377
- *
1378
- * Called from the status-detection poll with the text that poll already
1379
- * captured — no `capture-pane` is issued for this, so the feature costs nothing
1380
- * in tmux round-trips.
1381
- *
1382
- * **Each half latches independently.** A Codex footer carries a model on every
1383
- * frame but an effort only on some formats; a Claude banner carries both and
1384
- * then scrolls away entirely. Writing `{model, effort}` wholesale would let the
1385
- * frame that stopped showing one of them blank a value the other frame proved.
1386
- * Nothing is ever written as null: absent means "no frame has ever shown this",
1387
- * which is the honest state for gemini/copilot and for any session whose chrome
1388
- * this module has no rule for.
1389
- *
1390
- * **The one exception is an effort the frame shows but cannot name** (Issue
1391
- * #2835, `info.effortUnreadable`). A codex bar that draws `default` or a
1392
- * model-defined value where `xhigh` used to be is not "not showing" the effort
1393
- * — it is showing a different one. Keeping `xhigh` would publish a value the
1394
- * session is no longer running at, so the effort half is dropped to null and
1395
- * the model half latches as usual.
1396
- *
1397
- * @param info - {@link import('../../lib/detection/model-info-extractor').extractModelInfo}'s answer
1398
- */
1399
- function recordCapturedModelInfo(worktreeId, cliToolId, instanceId, info, at = Date.now()) {
1400
- if (!info.model && !info.effort)
1401
- return;
1402
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1403
- const previous = capturedModelInfo.get(key);
1404
- const model = info.model ? info.model.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH) : (previous?.model ?? null);
1405
- capturedModelInfo.set(key, {
1406
- model,
1407
- effort: info.effortUnreadable ? null : (info.effort ?? previous?.effort ?? null),
1408
- // Issue #2361: the change stamp moves only when the model half takes a
1409
- // different value. A banner re-read on every poll keeps the stamp where the
1410
- // first sighting put it, so it never out-dates a hook by mere repetition.
1411
- modelChangedAt: model === null
1412
- ? null
1413
- : model === (previous?.model ?? null)
1414
- ? (previous?.modelChangedAt ?? at)
1415
- : at,
1416
- });
1417
- // Issue #2357: an effort-only frame leaves the model half where it was, so
1418
- // there is nothing to judge; a frame that named a model is the screen's
1419
- // report and is compared against what this instance was last seen on.
1420
- if (info.model)
1421
- observeAgentModel(worktreeId, cliToolId, instanceId, 'frame', at);
1422
- }
1423
- /**
1424
- * The last model/effort a terminal capture showed, both halves possibly null.
1425
- *
1426
- * The raw scraped value, before precedence is applied — {@link
1427
- * getResolvedAgentModelInfo} is what callers publishing to the UI or the API
1428
- * want. Exported for the tests and for anything that needs to tell "the screen
1429
- * said" apart from "the agent said".
1430
- */
1431
- function getLastCapturedModelInfo(worktreeId, cliToolId, instanceId) {
1432
- const record = capturedModelInfo.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
1433
- return { model: record?.model ?? null, effort: record?.effort ?? null };
1434
- }
1435
- /**
1436
- * The model and reasoning effort to publish for this instance (Issue #1784).
1437
- *
1438
- * The single answer both surfaces should read: it folds the hook channel
1439
- * (#1783) together with the screen under the precedence documented on
1440
- * {@link mergeModelInfo} — hooks win for the model, the screen is the only
1441
- * source of effort for codex/claude, and antigravity's effort is derived from
1442
- * the id it reports rather than from its (renderer-truncated) status bar —
1443
- * with the one exception {@link resolveAgentModel} documents (Issue #2361): a
1444
- * claude frame that started naming a different model after the hook last
1445
- * spoke is the newer statement, and wins.
1446
- *
1447
- * Both halves may be null, and routinely are: no tool publishes an effort over
1448
- * hooks, and most tools publish neither. Callers omit the key rather than
1449
- * emitting null — see `CliToolSessionStatus`.
1450
- */
1451
- function getResolvedAgentModelInfo(worktreeId, cliToolId, instanceId) {
1452
- return resolveAgentModel((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), cliToolId).info;
1453
- }
1454
- /**
1455
- * Tools whose hook channel may name the model at session start and never again
1456
- * (Issue #2361).
1457
- *
1458
- * For these, a frame that starts naming a DIFFERENT model after the hook last
1459
- * spoke may be the only report of a mid-session switch there is, and it
1460
- * overtakes the hook in {@link resolveAgentModel}. For every other tool the
1461
- * rule stays #1784's — hooks win — because their hook channel re-reports the
1462
- * model on later events (codex and antigravity on every one, opencode on every
1463
- * `message.updated`), so a switch reaches the hook latch by itself and a frame
1464
- * that disagrees with a hook that is still re-affirming its value is a
1465
- * misread, not news.
1466
- *
1467
- * Measured on claude 2.1.263 for #2361: `SessionStart` alone carried `model`
1468
- * among the events then registered; `/model`, `/fast` and `/effort` fired none
1469
- * of them; the `SessionStart` that `/clear` emits carries no `model`.
1470
- *
1471
- * Issue #2363 registers `PostModelSwitch`, so on 2.1.263 and later the hook
1472
- * DOES re-report every `/model` and `/fast` that changes the model, in the
1473
- * `SessionStart` spelling (`claude-sonnet-5`, `claude-opus-5[1m]`). Claude
1474
- * stays on this list all the same, as the **fallback** rather than the rule,
1475
- * and the two cases the rule exists for were weighed against each other:
1476
- *
1477
- * - *The hook re-reports.* The switch lands here as a hook write, stamping
1478
- * {@link lastAgentModelAt}; the pane re-reads the rewritten banner or the
1479
- * confirmation line moments later and names the same model in its label
1480
- * spelling, which {@link isSameAgentModelName} (cross-channel) folds into
1481
- * the hook's exact id. The frame does not overtake, the hook's id is what
1482
- * is published, and the edge fires once. Nothing in this rule had to change
1483
- * for that — it was written to defer to a hook that re-affirms.
1484
- * - *The hook does not arrive.* A claude older than the hook, a session
1485
- * launched without injection (`CM_AGENT_HOOKS_INJECT=0`, a hand-written
1486
- * `--settings`), a delivery lost to a wedged server or the 3 s
1487
- * de-duplication window: the latch is stale, the frame is the only channel
1488
- * that heard the switch, and this rule is what publishes it. Removing the
1489
- * tool from this set would take that back to the #2361 symptom — a stale
1490
- * id shown with no edge — on precisely the sessions that have no other way
1491
- * of reporting.
1492
- *
1493
- * The price of keeping it is the case #2361 already carried: a frame that
1494
- * starts showing a stale confirmation line AFTER the hook spoke (the banner
1495
- * scrolls away, an older `Set model to …` row becomes the fallback the
1496
- * extractor reads) overtakes a hook that was right. That was possible before
1497
- * #2363 — with a staler hook value underneath — and is no wider now; a switch
1498
- * whose confirmation line the extractor does read leaves the newer row on the
1499
- * pane, so the stale row is reachable only after a `/fast` or an automatic
1500
- * fallback, and only until the next `/model` line or hook.
1501
- */
1502
- const FRAME_OVERTAKES_HOOK_MODEL_TOOLS = new Set(['claude']);
1503
- /**
1504
- * The merged model/effort for one instance, and which channel the model half
1505
- * came from (Issue #2361).
1506
- *
1507
- * {@link mergeModelInfo}'s precedence — hooks win the model — with one
1508
- * exception layered on top for {@link FRAME_OVERTAKES_HOOK_MODEL_TOOLS}: when
1509
- * the frame's model half CHANGED after the hook last named a model
1510
- * ({@link CapturedModelRecord.modelChangedAt} > {@link lastAgentModelAt}) and
1511
- * the two do not name the same model ({@link isSameAgentModelName}, cross
1512
- * channel), the frame's value is the newer statement and is published in the
1513
- * hook's place. Every other case is unchanged:
1514
- *
1515
- * - the frame names the same model as the hook (the startup banner after
1516
- * `SessionStart`, or `/model` back to the model the hook named): the hook's
1517
- * exact id is published, as before;
1518
- * - the frame's value predates the hook (a switch line still on screen when a
1519
- * relaunched process reports its starting model): the hook wins;
1520
- * - no hook has spoken: the frame is the only source, as before.
1521
- *
1522
- * `source` is null when neither channel has a model.
1523
- */
1524
- function resolveAgentModel(key, cliToolId) {
1525
- const hooksModel = lastAgentModel.get(key) ?? null;
1526
- const captured = capturedModelInfo.get(key);
1527
- const capturedInfo = { model: captured?.model ?? null, effort: captured?.effort ?? null };
1528
- // Issue #2048: the third source. Null for every tool but opencode and for
1529
- // every opencode session running on a model's default, so every other
1530
- // surface's string is byte-identical to pre-#2048.
1531
- const hooksEffort = reportedEffort.get(key) ?? null;
1532
- const frameChangedAt = captured?.modelChangedAt ?? null;
1533
- const frameOvertakes = hooksModel !== null &&
1534
- capturedInfo.model !== null &&
1535
- frameChangedAt !== null &&
1536
- FRAME_OVERTAKES_HOOK_MODEL_TOOLS.has(cliToolId) &&
1537
- frameChangedAt > (lastAgentModelAt.get(key) ?? 0) &&
1538
- !isSameAgentModelName(hooksModel, capturedInfo.model, { crossSource: true });
1539
- const info = (0, model_info_extractor_1.mergeModelInfo)(cliToolId, frameOvertakes ? null : hooksModel, capturedInfo, hooksEffort);
1540
- if (info.model === null)
1541
- return { info, source: null };
1542
- return { info, source: hooksModel !== null && !frameOvertakes ? 'hook' : 'frame' };
1543
- }
1544
- /**
1545
- * Latch the effort (opencode's *variant*) the agent named for itself (#2048).
1546
- *
1547
- * Latch, never clear — {@link latchAgentModel}'s rule, and for the same reason:
1548
- * `session.updated` omits `Session.model.variant` entirely when the session is
1549
- * on a model's default, and `message.updated` omits `info.variant` on the
1550
- * assistant message a turn opens with. Reading either absence as "the variant
1551
- * is now unknown" would blank the display between frames of the same turn.
1552
- *
1553
- * Called by the tool's own reader — `lib/hooks/sources/opencode/subscription` —
1554
- * rather than from the generic event path, because the value lives on frames
1555
- * that map to none of the seven event words.
1556
- *
1557
- * @param effort - The variant name, or null/empty to leave the latch alone
1558
- */
1559
- function recordAgentReportedEffort(worktreeId, cliToolId, instanceId, effort) {
1560
- if (typeof effort !== 'string' || effort === '')
1561
- return;
1562
- reportedEffort.set((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), effort.slice(0, agent_event_types_1.MAX_EVENT_DETAIL_LENGTH));
1563
- }
1564
- /**
1565
- * The effort the agent last reported for this instance, or null (#2048).
1566
- *
1567
- * The raw latched value, before precedence — {@link getResolvedAgentModelInfo}
1568
- * is what callers publishing to the UI or the API want. Exported so a test can
1569
- * tell "the agent said" apart from "the screen said", the same way
1570
- * {@link getLastCapturedModelInfo} lets it.
1571
- */
1572
- function getLastReportedAgentEffort(worktreeId, cliToolId, instanceId) {
1573
- return reportedEffort.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
1574
- }
1575
- /**
1576
- * The reasoning effort this instance is running at, or null (Issue #1784).
1577
- *
1578
- * Convenience reader over {@link getResolvedAgentModelInfo} for callers that
1579
- * want only the effort — `capture --json` and `instances` (#1785) among them.
1580
- */
1581
- function getLastKnownAgentEffort(worktreeId, cliToolId, instanceId) {
1582
- return getResolvedAgentModelInfo(worktreeId, cliToolId, instanceId).effort;
1583
- }
1584
- /**
1585
- * How long a structured verdict is trusted after the event that produced it
1586
- * (Issue #1723; one expression since #1930).
1587
- *
1588
- * The turn model owns the number now — see `provisional-turn`'s
1589
- * {@link TURN_STALE_AFTER_MS}, which documents why it is 30 minutes. This name
1590
- * is kept because it is what `status-evidence` and the #1723/#1725/#1903 suites
1591
- * read, and because "the age bound on a verdict" and "how long a turn may run
1592
- * unheard-from" really are the same fact rather than two that happen to agree.
1593
- */
1594
- exports.STRUCTURED_STATE_MAX_AGE_MS = provisional_turn_1.TURN_STALE_AFTER_MS;
1595
263
  /**
1596
264
  * Open a new generation for this instance, invalidating everything reported
1597
265
  * before now (Issue #1723).
@@ -1611,43 +279,43 @@ exports.STRUCTURED_STATE_MAX_AGE_MS = provisional_turn_1.TURN_STALE_AFTER_MS;
1611
279
  */
1612
280
  function beginAgentEventGeneration(worktreeId, cliToolId, instanceId, at = Date.now()) {
1613
281
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1614
- generationStartedAt.set(key, at);
282
+ agent_event_turn_1.generationStartedAt.set(key, at);
1615
283
  // Issue #1930: the turn the previous process was in ends with that process,
1616
284
  // and the approvals it was holding go with it — they were raised against a
1617
285
  // slot nobody is holding open any more. The turn record is kept, closed as
1618
286
  // `closedBy: 'generation'`, so `capture --json` can say why it ended instead
1619
287
  // of going quiet. The dialogs #1725 kept in a map of their own live on that
1620
288
  // record now, which is what makes this one call retire both.
1621
- fenceTurnForNewGeneration(key, at);
289
+ (0, agent_event_turn_1.fenceTurnForNewGeneration)(key, at);
1622
290
  // Same reasoning for the question that dialog was asking (Issue #1726).
1623
- askUserQuestion.delete(key);
291
+ agent_event_ask_user_question_1.askUserQuestion.delete(key);
1624
292
  // And for "waiting for your input" (Issue #1786): a new process has not asked
1625
293
  // for anything yet.
1626
- awaitingInstruction.delete(key);
294
+ agent_event_awaiting_instruction_1.awaitingInstruction.delete(key);
1627
295
  // And for the model (Issue #1783): a new generation is a new agent process,
1628
296
  // which may have been launched on a different model entirely. Latching across
1629
297
  // one would show the *previous* process's model with nothing to correct it —
1630
298
  // Claude only re-reports on `SessionStart`, which lands moments later anyway.
1631
299
  // A `/clear` is deliberately not affected: it reaches `recordAgentEvent` as
1632
300
  // `session_end` + `session_start`, never this function.
1633
- lastAgentModel.delete(key);
1634
- lastAgentModelAt.delete(key);
301
+ agent_event_model_1.lastAgentModel.delete(key);
302
+ agent_event_model_1.lastAgentModelAt.delete(key);
1635
303
  // Issue #1784: same argument for what the screen showed. The latch exists to
1636
304
  // survive the banner scrolling away *within* one process; carrying it across
1637
305
  // a relaunch would show the old process's effort with no frame left that
1638
306
  // could contradict it. The next poll re-reads a live footer immediately.
1639
- capturedModelInfo.delete(key);
307
+ agent_event_model_1.capturedModelInfo.delete(key);
1640
308
  // Issue #2048: and the variant the agent named, for exactly #1783's reason —
1641
309
  // the new process may have been launched with a different one, or with none.
1642
- reportedEffort.delete(key);
310
+ agent_event_model_1.reportedEffort.delete(key);
1643
311
  // Issue #2357: and the model this instance was last seen on. The next value
1644
312
  // is a new process's starting model (`null → value`), not a change from the
1645
313
  // process that was replaced.
1646
- modelBaseline.delete(key);
314
+ agent_event_model_1.modelBaseline.delete(key);
1647
315
  // Issue #1899: the ids claimed for this key were issued by the process that
1648
316
  // has just been replaced. Unlike the time-window keys, they never expire on
1649
317
  // their own, so a generation is the only thing that retires them.
1650
- recentEventIdentities.delete(key);
318
+ agent_event_dedup_1.recentEventIdentities.delete(key);
1651
319
  }
1652
320
  /**
1653
321
  * @returns Epoch ms the current generation began, or null when no generation
@@ -1655,7 +323,7 @@ function beginAgentEventGeneration(worktreeId, cliToolId, instanceId, at = Date.
1655
323
  * server process, whose events are then judged on age alone.
1656
324
  */
1657
325
  function getAgentEventGenerationStartedAt(worktreeId, cliToolId, instanceId) {
1658
- return generationStartedAt.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
326
+ return agent_event_turn_1.generationStartedAt.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId)) ?? null;
1659
327
  }
1660
328
  /**
1661
329
  * Discard the structured state for one instance — the session it described is
@@ -1669,105 +337,24 @@ function getAgentEventGenerationStartedAt(worktreeId, cliToolId, instanceId) {
1669
337
  function discardAgentEventState(worktreeId, cliToolId, instanceId) {
1670
338
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1671
339
  lastAgentEvent.delete(key);
1672
- generationStartedAt.delete(key);
340
+ agent_event_turn_1.generationStartedAt.delete(key);
1673
341
  // Issue #1930: the turn, and with it the dialogs it was holding. Not counted
1674
342
  // as an eviction — the session is gone, so there is nobody left to tell.
1675
- agentTurns.delete(key);
1676
- dropCounts.delete(key);
1677
- askUserQuestion.delete(key);
1678
- awaitingInstruction.delete(key);
343
+ agent_event_turn_1.agentTurns.delete(key);
344
+ agent_event_dedup_1.dropCounts.delete(key);
345
+ agent_event_ask_user_question_1.askUserQuestion.delete(key);
346
+ agent_event_awaiting_instruction_1.awaitingInstruction.delete(key);
1679
347
  // Issue #1783: the session that was on this model no longer exists.
1680
- lastAgentModel.delete(key);
1681
- lastAgentModelAt.delete(key);
348
+ agent_event_model_1.lastAgentModel.delete(key);
349
+ agent_event_model_1.lastAgentModelAt.delete(key);
1682
350
  // Issue #1784: nor does the pane its footer was read from.
1683
- capturedModelInfo.delete(key);
351
+ agent_event_model_1.capturedModelInfo.delete(key);
1684
352
  // Issue #2048: nor the variant that session was running at.
1685
- reportedEffort.delete(key);
353
+ agent_event_model_1.reportedEffort.delete(key);
1686
354
  // Issue #2357: nor the model it was last seen on.
1687
- modelBaseline.delete(key);
355
+ agent_event_model_1.modelBaseline.delete(key);
1688
356
  // Issue #1899: nor do the frame ids that session issued.
1689
- recentEventIdentities.delete(key);
1690
- }
1691
- /**
1692
- * The status this instance's turn implies, or null when it implies nothing
1693
- * (Issue #1723, re-derived from the turn model in #1930).
1694
- *
1695
- * Null is the answer for every session on a machine where hooks never fire,
1696
- * which is what keeps the unconfigured environment on exactly the behaviour it
1697
- * had before #1723. It is also the answer when:
1698
- *
1699
- * - the turn belongs to a previous generation, i.e. to an agent process that
1700
- * used to live in this pane;
1701
- * - nothing has been heard about it for {@link STRUCTURED_STATE_MAX_AGE_MS};
1702
- * - the turn ended for a reason that says nothing about whether the pane is
1703
- * free — `session_end` (a `/clear` mid-turn), `stale`, `generation`, or the
1704
- * two the *screen* closed it on (`scraper_evidence`, `resync_idle`). Only
1705
- * the agent's own `Stop` publishes `ready` over this channel; everything
1706
- * else hands the pane back to the scraper, which is the layer those closures
1707
- * came from in the first place;
1708
- * - no decision stands and the displayed event carries no verdict.
1709
- *
1710
- * ## The derivation, in the order it is applied
1711
- *
1712
- * | condition | verdict |
1713
- * |---------------------------------------------|-------------------------------|
1714
- * | an unanswered decision is live | `waiting` |
1715
- * | the turn was closed by `stop` | `ready` / `hook_stop` |
1716
- * | the turn was closed by anything else | null |
1717
- * | the displayed event carries a verdict | that verdict |
1718
- * | a turn is open and it does not | `running` / `hook_prompt_submit` |
1719
- * | otherwise | null |
1720
- *
1721
- * §4 D3's `running ⟺ open turn ∧ 未裁定なし ∧ 現世代` falls out of the first,
1722
- * fourth and fifth rows together with the generation fence — with one measured
1723
- * exception written into the table above it: `notification(idle_prompt)`
1724
- * publishes `ready` **without** closing the turn (#1839 caught Claude emitting
1725
- * it 62 s into a turn that ran nothing, so it cannot be a boundary). A reader
1726
- * that needs the boundary rather than the display reads `closedAt`.
1727
- *
1728
- * Whether the tmux session is alive is NOT checked here — the caller
1729
- * (`buildCurrentOutput`) has already answered that with the CLI tool's own
1730
- * `isRunning()` and returned early, and asking twice would mean a second tmux
1731
- * round-trip on the hot path for an answer it is holding.
1732
- *
1733
- * @param now - Epoch ms; defaults to now
1734
- */
1735
- function getStructuredSessionState(worktreeId, cliToolId, instanceId, now = Date.now()) {
1736
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1737
- const turn = effectiveTurn(key, now);
1738
- if (turn === null)
1739
- return null;
1740
- const display = turn.displayEvent;
1741
- if (now - display.at >= exports.STRUCTURED_STATE_MAX_AGE_MS)
1742
- return null;
1743
- const seen = { event: display.event, at: display.at, detail: display.detail };
1744
- const pending = livePendingDecisions(key, turn, now);
1745
- if (pending.length > 0) {
1746
- // The two kinds of evidence stay apart in the reason token, exactly as
1747
- // `structuredWaitingReason` keeps them apart for the payload: a
1748
- // `Notification` is proof a dialog exists, a `PermissionRequest` this
1749
- // server declined to decide is the prediction that one is about to.
1750
- const reason = pending[0].source === 'notification'
1751
- ? status_mapping_1.HOOK_STATUS_REASON.PERMISSION_PROMPT
1752
- : status_mapping_1.HOOK_STATUS_REASON.PERMISSION_REQUEST;
1753
- return { status: 'waiting', reason, ...seen };
1754
- }
1755
- if (turn.closedAt !== null) {
1756
- if (turn.closedBy === 'stop') {
1757
- return { status: 'ready', reason: status_mapping_1.HOOK_STATUS_REASON.STOP, ...seen };
1758
- }
1759
- return null;
1760
- }
1761
- const verdict = (0, status_mapping_1.agentEventToSessionStatus)(display.event, display.detail);
1762
- // A `waiting` read off the displayed event is a dialog nothing is holding any
1763
- // more — the ledger above is what answers that question now. The turn is
1764
- // still open, so the agent is still working.
1765
- if (verdict === null || verdict.status === 'waiting') {
1766
- if (turn.openedAt === null)
1767
- return null;
1768
- return { status: 'running', reason: status_mapping_1.HOOK_STATUS_REASON.PROMPT_SUBMIT, ...seen };
1769
- }
1770
- return { ...verdict, ...seen };
357
+ agent_event_dedup_1.recentEventIdentities.delete(key);
1771
358
  }
1772
359
  /**
1773
360
  * How long a `permission-request`-sourced record survives without
@@ -1793,7 +380,7 @@ exports.STRUCTURED_PROMPT_PROVISIONAL_MAX_AGE_MS = provisional_turn_1.DIALOG_PEN
1793
380
  */
1794
381
  function reportPermissionRequestPending(worktreeId, cliToolId, instanceId, toolName, at = Date.now()) {
1795
382
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1796
- openDecision(key, currentGeneration(key), {
383
+ (0, agent_event_turn_1.openDecision)(key, (0, agent_event_turn_1.currentGeneration)(key), {
1797
384
  source: 'permission-request',
1798
385
  at,
1799
386
  message: null,
@@ -1849,7 +436,7 @@ function reportPermissionRequestPending(worktreeId, cliToolId, instanceId, toolN
1849
436
  */
1850
437
  function reportQuestionPending(worktreeId, cliToolId, instanceId, input, at = Date.now()) {
1851
438
  const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
1852
- openDecision(key, currentGeneration(key), {
439
+ (0, agent_event_turn_1.openDecision)(key, (0, agent_event_turn_1.currentGeneration)(key), {
1853
440
  source: 'notification',
1854
441
  at,
1855
442
  // The question TEXT is not the dialog's `message`: it is published as
@@ -1880,7 +467,7 @@ function reportQuestionPending(worktreeId, cliToolId, instanceId, input, at = Da
1880
467
  * @param now - Epoch ms; defaults to now
1881
468
  */
1882
469
  function getStructuredPromptWaiting(worktreeId, cliToolId, instanceId, now = Date.now()) {
1883
- return getPendingDecisions(worktreeId, cliToolId, instanceId, now)[0] ?? null;
470
+ return (0, agent_event_turn_1.getPendingDecisions)(worktreeId, cliToolId, instanceId, now)[0] ?? null;
1884
471
  }
1885
472
  /**
1886
473
  * Record that the scraper has seen a blocking frame while this dialog is open
@@ -1917,368 +504,33 @@ function markStructuredPromptRecorded(worktreeId, cliToolId, instanceId) {
1917
504
  * for a frame the scraper has just said is clear.
1918
505
  */
1919
506
  function clearStructuredPromptWaiting(worktreeId, cliToolId, instanceId) {
1920
- releaseAllDecisions((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
1921
- }
1922
- /**
1923
- * When the in-flight question is released (Issue #1726).
1924
- *
1925
- * | event | effect on the question |
1926
- * |-----------------------------------|------------------------|
1927
- * | `pre_tool_use(AskUserQuestion)` | unchanged (this IS it) |
1928
- * | `pre_tool_use(any other tool)` | release |
1929
- * | `post_tool_use` | release |
1930
- * | `notification(permission_prompt)` | **unchanged** |
1931
- * | `notification(idle_prompt)` | release |
1932
- * | `notification(other)` | unchanged |
1933
- * | `stop` | release |
1934
- * | `user_prompt_submit` | release |
1935
- * | `session_start` / `session_end` | release |
1936
- *
1937
- * `PostToolUse` is the precise release — "this tool call is over" — and `Stop`
1938
- * is the backstop for a delivery that never arrives. Issue #1726's text proposed
1939
- * `PostToolUse` and the #1721 spike recorded it as never observed, so this Issue
1940
- * measured it directly on a live v2.1.223 session (2026-08-06):
1941
- *
1942
- * ```
1943
- * 15:36:04.112 PreToolUse AskUserQuestion
1944
- * 15:36:28.643 PostToolUse AskUserQuestion <- the human answered
1945
- * 15:36:29.992 Stop
1946
- * ```
1947
- *
1948
- * It fires, 1.3 s ahead of `Stop` here — and much further ahead whenever the
1949
- * agent keeps working after the answer, which is the case that matters: `Stop`
1950
- * alone would leave a finished question in place for the whole rest of the turn.
1951
- *
1952
- * A `PostToolUse` for any other tool releases as well: the agent could not have
1953
- * finished another tool call while this question was still on screen.
1954
- *
1955
- * **`Notification(permission_prompt)` keeping the question is a live
1956
- * measurement, not a guess.** The #1721 report says the picker emits no events
1957
- * while it is displayed (§5.6), and a first cut of this module read that as "any
1958
- * event means the picker is gone". Driving a real v2.1.223 session through the
1959
- * server on 2026-08-06 disproved it:
1960
- *
1961
- * ```
1962
- * 15:29:18.099 PreToolUse(AskUserQuestion)
1963
- * 15:29:18.109 PermissionRequest(AskUserQuestion) -> no decision
1964
- * 15:29:24.128 Notification(permission_prompt) <- picker still on screen
1965
- * ```
1966
- *
1967
- * The notification lands ~6 s after the dialog is drawn (§5.5's timing exactly),
1968
- * which is *inside* the window §5.6 was counting rather than outside it. Under
1969
- * the first rule it deleted the question six seconds after it arrived, and the
1970
- * options went back to being the screen's — which is the whole feature, silently
1971
- * off, on every real session.
1972
- *
1973
- * `idle_prompt` still releases: the agent reporting it is sitting at the
1974
- * composer is the agent saying no picker is in front of it. An unrecognised
1975
- * notification type changes nothing, because nothing is known about it.
1976
- */
1977
- function applyAskUserQuestionTransition(key, record) {
1978
- switch (record.event) {
1979
- case 'pre_tool_use':
1980
- // A `PreToolUse` for anything else means the agent has moved on to another
1981
- // tool, so whatever question was in flight has been answered. Only
1982
- // reachable when the operator's own settings.json registers a wider
1983
- // matcher than the injected `AskUserQuestion` one — the two files are
1984
- // concatenated, not substituted (#1722).
1985
- //
1986
- // Issue #2100: the exemption is the whole QUESTION vocabulary, not
1987
- // Claude's one spelling. opencode names its question tool `question`, and
1988
- // it publishes a tool part for the same call: measured on 1.18.23 in an
1989
- // isolated HOME, `question.asked` and
1990
- // `message.part.updated(tool=question, status=running)` arrive **in the
1991
- // same millisecond**, in that order (§27.3). Under the old test the
1992
- // second frame read as "the agent moved on to another tool" and deleted
1993
- // the episode 1 ms after the ingest recorded it, which is why
1994
- // `pendingDecisions[].questionOptions` and `promptData.askUserQuestion`
1995
- // were null for every opencode question. Claude is unaffected —
1996
- // `AskUserQuestion` is the first member of the same list — and the cost
1997
- // is that a hook tool literally named `question` no longer releases the
1998
- // episode, which is the same name `pendingDecisionKind` already reads as
1999
- // "this record is a question" for every tool.
2000
- if (!pending_decision_kind_1.QUESTION_DECISION_TOOL_NAMES.includes(record.detail ?? '')) {
2001
- askUserQuestion.delete(key);
2002
- }
2003
- return;
2004
- case 'notification':
2005
- if (record.detail === 'idle_prompt')
2006
- askUserQuestion.delete(key);
2007
- return;
2008
- case 'post_tool_use':
2009
- case 'stop':
2010
- case 'user_prompt_submit':
2011
- case 'session_start':
2012
- case 'session_end':
2013
- askUserQuestion.delete(key);
2014
- return;
2015
- default:
2016
- // exhaustive check: a new AgentEventType must decide its transition here
2017
- record.event;
2018
- return;
2019
- }
2020
- }
2021
- /**
2022
- * Record the `AskUserQuestion` invocation reported for one instance.
2023
- *
2024
- * Idempotent by construction: the same call is reported twice on every session
2025
- * — once by `PreToolUse` and once by the `PermissionRequest` that
2026
- * `AskUserQuestion` also raises with a byte-identical `tool_input` — and the
2027
- * second delivery simply overwrites the first with the same content.
2028
- *
2029
- * @param at - Epoch ms; defaults to now
2030
- */
2031
- function recordAskUserQuestion(worktreeId, cliToolId, instanceId, spec, at = Date.now()) {
2032
- askUserQuestion.set((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId), { at, spec });
2033
- }
2034
- /**
2035
- * The `AskUserQuestion` call in flight for this instance, or null.
2036
- *
2037
- * Bounded exactly like {@link getStructuredSessionState}: a record from a
2038
- * previous generation belongs to a Claude process that no longer exists, and one
2039
- * older than {@link STRUCTURED_STATE_MAX_AGE_MS} has outlived the screen it
2040
- * describes. There is no provisional bound — unlike a `PermissionRequest`, a
2041
- * `PreToolUse(AskUserQuestion)` is not a prediction that a dialog *might*
2042
- * appear: allowing the permission request does not dismiss the picker (§5.6), so
2043
- * the picker is drawn unconditionally.
2044
- *
2045
- * @param now - Epoch ms; defaults to now
2046
- */
2047
- function getAskUserQuestion(worktreeId, cliToolId, instanceId, now = Date.now()) {
2048
- const key = (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
2049
- const episode = askUserQuestion.get(key);
2050
- if (!episode)
2051
- return null;
2052
- const generation = generationStartedAt.get(key);
2053
- if (generation !== undefined && episode.at < generation)
2054
- return null;
2055
- if (now - episode.at >= exports.STRUCTURED_STATE_MAX_AGE_MS)
2056
- return null;
2057
- return episode;
2058
- }
2059
- /** Drop the in-flight question for one instance. */
2060
- function clearAskUserQuestion(worktreeId, cliToolId, instanceId) {
2061
- askUserQuestion.delete((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
2062
- }
2063
- /**
2064
- * Whether this event is a second copy of one already handled, and should be
2065
- * dropped.
2066
- *
2067
- * Only events that name a `sessionId` can be deduplicated, and calling this
2068
- * *claims* the key: a first call answers false and marks it, so the caller must
2069
- * ask once per request and act on the answer. Events with no session id are
2070
- * never suppressed — a caller that omits it (the #1549 relay run without a hook
2071
- * payload, a hand-rolled `curl`) has given us nothing to tell two deliveries of
2072
- * one turn from two genuine turns, and inventing a match there would silently
2073
- * drop real events.
2074
- *
2075
- * The subtype is part of the key (Issue #1726). It has to be, now that
2076
- * `pre_tool_use` exists: that event's subtype is the tool name, several tool
2077
- * calls a second is ordinary, and a key without it would read a `Bash` call and
2078
- * the `AskUserQuestion` that follows it as one delivery and drop the second. The
2079
- * same correction applies to two `Notification`s of different types inside the
2080
- * window, which were previously collapsed as well.
2081
- *
2082
- * @param at - Epoch ms; defaults to now
2083
- * @param detail - The event's subtype, when it has one
2084
- */
2085
- function isDuplicateAgentEvent(worktreeId, cliToolId, instanceId, event, sessionId, at = Date.now(), detail = null) {
2086
- if (!sessionId)
2087
- return false;
2088
- const key = [
2089
- (0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId),
2090
- event,
2091
- detail ?? '',
2092
- sessionId,
2093
- ].join(' ');
2094
- const seenAt = recentEventKeys.get(key);
2095
- if (seenAt !== undefined && at - seenAt < exports.AGENT_EVENT_DEDUP_WINDOW_MS) {
2096
- return true;
2097
- }
2098
- recentEventKeys.set(key, at);
2099
- pruneRecentEventKeys(at);
2100
- return false;
2101
- }
2102
- /** Drop keys past the window, then the oldest survivors if still over the cap. */
2103
- function pruneRecentEventKeys(now) {
2104
- for (const [key, seenAt] of recentEventKeys) {
2105
- if (now - seenAt >= exports.AGENT_EVENT_DEDUP_WINDOW_MS) {
2106
- recentEventKeys.delete(key);
2107
- }
2108
- }
2109
- // Map iterates in insertion order, so the head is the oldest.
2110
- while (recentEventKeys.size > MAX_RECENT_EVENT_KEYS) {
2111
- const oldest = recentEventKeys.keys().next();
2112
- if (oldest.done)
2113
- break;
2114
- recentEventKeys.delete(oldest.value);
2115
- }
2116
- }
2117
- /** How many dedup keys are currently retained. Test seam for the bound above. */
2118
- function getRecentEventKeyCount() {
2119
- return recentEventKeys.size;
2120
- }
2121
- // =============================================================================
2122
- // Identity de-duplication (Issue #1899)
2123
- // =============================================================================
2124
- /**
2125
- * Event words whose repeat is a fact about the session, not a re-delivery
2126
- * (Issue #1899; design §4 D3 decision 2).
2127
- *
2128
- * These are the two words that end something, and neither of them carries an
2129
- * id on any source measured so far. `session.idle` — opencode's `stop` — is
2130
- * `{ "sessionID": "ses_…" }` and nothing else, which is exactly why
2131
- * `sources/opencode/turn-gate` exists. With no id, a generic deduper has only
2132
- * the clock, and the clock cannot tell a second turn from a second delivery:
2133
- * #1899 measured a real `stop` 2.5 s after the previous one being dropped,
2134
- * which leaves the newest event at `user_prompt_submit`, the instance reading
2135
- * `running`, and `commandmate wait` blocked until the 30-minute staleness
2136
- * bound.
2137
- *
2138
- * **The exemption is conditional on the source declaring an identity**, and
2139
- * that condition is the whole safety argument. A source that declares one is a
2140
- * source with a real deduper elsewhere — on the SSE path, `TurnGate`, which
2141
- * arms on `session.status(busy)` and completes on the first `session.idle`
2142
- * after arming, so the abort double-idle (19 ms apart, §5.3.2) never reaches
2143
- * the ingest at all. Push hooks have no such gate: #1722's concatenated
2144
- * settings really do post two `Stop`s for one turn, and
2145
- * {@link isDuplicateAgentEvent} — which is what every hook receiver still
2146
- * calls — is left untouched for them.
2147
- */
2148
- exports.LIFECYCLE_AGENT_EVENT_TYPES = ['stop', 'session_end'];
2149
- /**
2150
- * Whether this delivery is a second copy of one already handled (Issue #1899).
2151
- *
2152
- * The tool-agnostic replacement for calling {@link isDuplicateAgentEvent}
2153
- * directly, and it branches on the source's declared capability rather than on
2154
- * its name — flip opencode's `eventIdentity` to `null` and every case below
2155
- * falls back to the 3-second window it used before this Issue.
2156
- *
2157
- * Three rules, in order:
2158
- *
2159
- * 1. **The frame has an id** — key on it, with no time bound at all. An id is
2160
- * a claim about identity that a clock cannot improve on: two approvals
2161
- * 1 s apart are two approvals, and the same approval replayed by
2162
- * `resyncPending` four minutes later is still one approval. That second
2163
- * half is what the ingest's window was reaching for ("a frame delivered
2164
- * twice by a re-sync racing the live stream") and never actually covered,
2165
- * since a re-sync is not obliged to land within three seconds.
2166
- * 2. **No id, but the word ends something** — never suppressed. See
2167
- * {@link LIFECYCLE_AGENT_EVENT_TYPES}.
2168
- * 3. **Anything else** — the time window, unchanged. That is every push
2169
- * source, and the identity-declaring source's `session.created` /
2170
- * `session.error`, which publish no id either but are not turn boundaries.
2171
- *
2172
- * Calling this *claims* the key, exactly as {@link isDuplicateAgentEvent} does:
2173
- * ask once per delivery and act on the answer.
2174
- */
2175
- function classifyAgentEventDelivery(delivery) {
2176
- const composite = (0, auto_yes_state_1.buildCompositeKey)(delivery.worktreeId, delivery.cliToolId, delivery.instanceId);
2177
- if (delivery.identityKind !== null) {
2178
- // Issue #1930 / S1: the id is the agent's, and it becomes a Map key. One
2179
- // that fails validation is DISCARDED — the delivery then takes the
2180
- // no-id path below, which is a path this function already has — rather than
2181
- // truncated, because a truncated id compares equal to a different id that
2182
- // shares its prefix and would drop a real event as a repeat.
2183
- const identity = delivery.identity === null ? null : (0, provisional_turn_1.acceptExternalId)(delivery.identity);
2184
- if (delivery.identity !== null && identity === null)
2185
- dropsFor(composite).idsDiscarded += 1;
2186
- if (identity !== null) {
2187
- if (claimEventIdentity(delivery, identity)) {
2188
- dropsFor(composite).dedupDropped.identity += 1;
2189
- return { duplicate: true, by: 'identity' };
2190
- }
2191
- return { duplicate: false };
2192
- }
2193
- if (exports.LIFECYCLE_AGENT_EVENT_TYPES.includes(delivery.event)) {
2194
- return { duplicate: false };
2195
- }
2196
- }
2197
- const duplicate = isDuplicateAgentEvent(delivery.worktreeId, delivery.cliToolId, delivery.instanceId, delivery.event, delivery.sessionId, delivery.at, delivery.detail);
2198
- if (!duplicate)
2199
- return { duplicate: false };
2200
- dropsFor(composite).dedupDropped.timeWindow += 1;
2201
- return { duplicate: true, by: 'time-window' };
2202
- }
2203
- /**
2204
- * Claim `(event, detail, identity)` for one instance, answering whether it was
2205
- * already claimed.
2206
- *
2207
- * `event` and `detail` are in the key and are not decoration: opencode asks an
2208
- * approval under `properties.id` and answers it under `properties.requestID`
2209
- * with **the same `per_…` value** (#1898), so a key made of the identity alone
2210
- * would read `permission.replied` as a repeat of `permission.asked` and drop
2211
- * the one frame that releases the dialog. The same shape covers
2212
- * `pre_tool_use` / `post_tool_use`, which share a `callID`.
2213
- *
2214
- * `sessionId` is deliberately *not* in the key. An id is unique within the
2215
- * agent that issued it, and folding in a field that is null on some deliveries
2216
- * would let the same frame through twice.
2217
- */
2218
- function claimEventIdentity(delivery, identity) {
2219
- const composite = (0, auto_yes_state_1.buildCompositeKey)(delivery.worktreeId, delivery.cliToolId, delivery.instanceId);
2220
- let claimed = recentEventIdentities.get(composite);
2221
- if (!claimed) {
2222
- claimed = new Map();
2223
- recentEventIdentities.set(composite, claimed);
2224
- // Bound the instances as well as the entries per instance: the composite
2225
- // key is (worktree, tool, instance) and a long-lived server accumulates
2226
- // worktrees (DR4-009).
2227
- trimOldestEntries(recentEventIdentities, MAX_RECENT_EVENT_KEYS);
2228
- }
2229
- const key = [delivery.event, delivery.detail ?? '', identity].join(' ');
2230
- if (claimed.has(key))
2231
- return true;
2232
- claimed.set(key, delivery.at);
2233
- trimOldestEntries(claimed, MAX_RECENT_EVENT_KEYS);
2234
- return false;
2235
- }
2236
- /** Drop the oldest entries until the map fits. Maps iterate in insertion order. */
2237
- function trimOldestEntries(entries, max) {
2238
- while (entries.size > max) {
2239
- const oldest = entries.keys().next();
2240
- if (oldest.done)
2241
- break;
2242
- entries.delete(oldest.value);
2243
- }
2244
- }
2245
- /**
2246
- * How many identities are retained for one instance, or across all of them when
2247
- * no instance is named. Test seam for the bound above.
2248
- */
2249
- function getRecentEventIdentityCount(worktreeId, cliToolId, instanceId) {
2250
- if (worktreeId === undefined || cliToolId === undefined) {
2251
- let total = 0;
2252
- for (const claimed of recentEventIdentities.values())
2253
- total += claimed.size;
2254
- return total;
2255
- }
2256
- return recentEventIdentities.get((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId))?.size ?? 0;
507
+ (0, agent_event_turn_1.releaseAllDecisions)((0, auto_yes_state_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
2257
508
  }
2258
509
  /** Drop every recorded event. Test seam. */
2259
510
  function clearAgentStopEvents() {
2260
511
  lastStopEventAt.clear();
2261
512
  lastAgentEvent.clear();
2262
- recentEventKeys.clear();
2263
- recentEventIdentities.clear();
2264
- generationStartedAt.clear();
513
+ agent_event_dedup_1.recentEventKeys.clear();
514
+ agent_event_dedup_1.recentEventIdentities.clear();
515
+ agent_event_turn_1.generationStartedAt.clear();
2265
516
  // Issue #1930: the turns, the dialogs they hold, and the tally of what was
2266
517
  // dropped from them.
2267
- agentTurns.clear();
2268
- dropCounts.clear();
2269
- askUserQuestion.clear();
2270
- awaitingInstruction.clear();
518
+ agent_event_turn_1.agentTurns.clear();
519
+ agent_event_turn_1.reopenedTurns.clear();
520
+ agent_event_dedup_1.dropCounts.clear();
521
+ agent_event_ask_user_question_1.askUserQuestion.clear();
522
+ agent_event_awaiting_instruction_1.awaitingInstruction.clear();
2271
523
  // Issue #1783. CI runs with `fileParallelism: false`, so every suite in the
2272
524
  // repo shares this process — a model latched by one test would otherwise be
2273
525
  // read by another, in file order, and only in CI.
2274
- lastAgentModel.clear();
2275
- lastAgentModelAt.clear();
526
+ agent_event_model_1.lastAgentModel.clear();
527
+ agent_event_model_1.lastAgentModelAt.clear();
2276
528
  // Issue #1784: and the same for the scraped half.
2277
- capturedModelInfo.clear();
529
+ agent_event_model_1.capturedModelInfo.clear();
2278
530
  // Issue #2048: and for the variant the agent reported.
2279
- reportedEffort.clear();
531
+ agent_event_model_1.reportedEffort.clear();
2280
532
  // Issue #2357: and for the model each instance was last seen on. The
2281
533
  // listener set is deliberately NOT cleared — a subscription belongs to the
2282
534
  // process, not to a session, and a suite that armed one expects it to stay.
2283
- modelBaseline.clear();
535
+ agent_event_model_1.modelBaseline.clear();
2284
536
  }