commandmate 0.44.2 → 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 (581) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +149 -149
  3. package/.next/app-path-routes-manifest.json +21 -21
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +12 -12
  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 -1
  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 -1
  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 +2 -2
  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 +21 -21
  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 -1
  294. package/.next/server/chunks/2710.js +1 -1
  295. package/.next/server/chunks/2908.js +1 -1
  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 -1
  308. package/.next/server/chunks/5625.js +25 -0
  309. package/.next/server/chunks/611.js +1 -1
  310. package/.next/server/chunks/6130.js +1 -1
  311. package/.next/server/chunks/6401.js +1 -1
  312. package/.next/server/chunks/6781.js +1 -1
  313. package/.next/server/chunks/6793.js +3 -3
  314. package/.next/server/chunks/7491.js +2 -2
  315. package/.next/server/chunks/7905.js +1 -1
  316. package/.next/server/chunks/850.js +1 -1
  317. package/.next/server/chunks/8728.js +3 -3
  318. package/.next/server/chunks/8791.js +1 -1
  319. package/.next/server/chunks/8800.js +1 -1
  320. package/.next/server/chunks/8918.js +7 -7
  321. package/.next/server/chunks/9130.js +1 -1
  322. package/.next/server/chunks/9390.js +2 -2
  323. package/.next/server/chunks/9584.js +1 -1
  324. package/.next/server/chunks/9627.js +6 -6
  325. package/.next/server/chunks/9731.js +3 -3
  326. package/.next/server/middleware-manifest.json +5 -5
  327. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  328. package/.next/server/pages/500.html +1 -1
  329. package/.next/server/server-reference-manifest.json +1 -1
  330. package/.next/static/chunks/{1642-fb2e176aaadbd137.js → 1642-767aed8e7c61ef7f.js} +1 -1
  331. package/.next/static/chunks/{2407-051f6f84226a9f2c.js → 2407-06d35bb1aa02de80.js} +1 -1
  332. package/.next/static/chunks/{2619-04bc32f026a0d946.js → 2619-a305ad4810f1393a.js} +1 -1
  333. package/.next/static/chunks/4727-5d695d71e3fdf97b.js +1 -0
  334. package/.next/static/chunks/{8165-3853772f6ae1299b.js → 8165-3525fb42f1b4976e.js} +1 -1
  335. package/.next/static/chunks/app/{layout-9da1ab602bcb3051.js → layout-0d1750b833f5cc3e.js} +1 -1
  336. package/.next/static/chunks/app/sessions/page-2af68c63211c751f.js +1 -0
  337. package/.next/static/chunks/app/worktrees/[id]/page-e36898c3a80e7302.js +1 -0
  338. package/.next/static/chunks/app/worktrees/[id]/terminal/{page-373ea37b16d930c7.js → page-c601f3b3ece6557e.js} +1 -1
  339. package/.next/static/css/057f64a65726c439.css +1 -0
  340. package/.next/trace +13 -13
  341. package/dist/cli/commands/ask.d.ts.map +1 -1
  342. package/dist/cli/commands/ask.js +120 -119
  343. package/dist/cli/commands/attach.d.ts +94 -0
  344. package/dist/cli/commands/attach.d.ts.map +1 -1
  345. package/dist/cli/commands/attach.js +145 -7
  346. package/dist/cli/commands/auto-yes.js +1 -1
  347. package/dist/cli/commands/capture.d.ts.map +1 -1
  348. package/dist/cli/commands/capture.js +3 -5
  349. package/dist/cli/commands/command-target.d.ts +23 -0
  350. package/dist/cli/commands/command-target.d.ts.map +1 -0
  351. package/dist/cli/commands/command-target.js +25 -0
  352. package/dist/cli/commands/instances.d.ts.map +1 -1
  353. package/dist/cli/commands/instances.js +41 -2
  354. package/dist/cli/commands/message-dispatch.d.ts +36 -0
  355. package/dist/cli/commands/message-dispatch.d.ts.map +1 -0
  356. package/dist/cli/commands/message-dispatch.js +63 -0
  357. package/dist/cli/commands/reply.d.ts.map +1 -1
  358. package/dist/cli/commands/reply.js +2 -5
  359. package/dist/cli/commands/respond.d.ts.map +1 -1
  360. package/dist/cli/commands/respond.js +3 -6
  361. package/dist/cli/commands/send.d.ts.map +1 -1
  362. package/dist/cli/commands/send.js +149 -146
  363. package/dist/cli/commands/wait.d.ts.map +1 -1
  364. package/dist/cli/commands/wait.js +589 -431
  365. package/dist/cli/types/api-responses.d.ts +159 -27
  366. package/dist/cli/types/api-responses.d.ts.map +1 -1
  367. package/dist/cli/types/api-responses.js +4 -2
  368. package/dist/cli/utils/api-client.d.ts +25 -5
  369. package/dist/cli/utils/api-client.d.ts.map +1 -1
  370. package/dist/cli/utils/api-client.js +87 -55
  371. package/dist/cli/utils/server-url.d.ts +1 -0
  372. package/dist/cli/utils/server-url.d.ts.map +1 -1
  373. package/dist/cli/utils/server-url.js +7 -0
  374. package/dist/config/uat-isolation.d.ts +140 -0
  375. package/dist/config/uat-isolation.d.ts.map +1 -0
  376. package/dist/config/uat-isolation.js +246 -0
  377. package/dist/lib/global-state.d.ts +12 -0
  378. package/dist/lib/global-state.d.ts.map +1 -0
  379. package/dist/lib/global-state.js +16 -0
  380. package/dist/lib/polling/auto-yes-suppression-reason.d.ts +33 -0
  381. package/dist/lib/polling/auto-yes-suppression-reason.d.ts.map +1 -0
  382. package/dist/lib/polling/auto-yes-suppression-reason.js +2 -0
  383. package/dist/lib/session/prompt-view.d.ts +26 -3
  384. package/dist/lib/session/prompt-view.d.ts.map +1 -1
  385. package/dist/lib/session/prompt-view.js +26 -3
  386. package/dist/lib/session/status-evidence-type.d.ts +16 -0
  387. package/dist/lib/session/status-evidence-type.d.ts.map +1 -0
  388. package/dist/lib/session/status-evidence-type.js +2 -0
  389. package/dist/lib/tmux/legacy-session-alias.d.ts.map +1 -1
  390. package/dist/lib/tmux/legacy-session-alias.js +2 -1
  391. package/dist/server/locales/en/worktree.json +3 -1
  392. package/dist/server/locales/ja/worktree.json +3 -1
  393. package/dist/server/src/cli/types/api-responses.js +4 -2
  394. package/dist/server/src/config/cli-tool-timing-config.js +1 -6
  395. package/dist/server/src/config/uat-isolation.js +246 -0
  396. package/dist/server/src/lib/assistant-response-saver.js +62 -3
  397. package/dist/server/src/lib/auto-yes-lifecycle.js +6 -0
  398. package/dist/server/src/lib/auto-yes-poller.js +450 -160
  399. package/dist/server/src/lib/auto-yes-state.js +2 -2
  400. package/dist/server/src/lib/cli-tools/antigravity.js +38 -79
  401. package/dist/server/src/lib/cli-tools/base.js +151 -0
  402. package/dist/server/src/lib/cli-tools/codex.js +44 -80
  403. package/dist/server/src/lib/cli-tools/command-code.js +24 -56
  404. package/dist/server/src/lib/cli-tools/copilot.js +25 -67
  405. package/dist/server/src/lib/cli-tools/direct-input.js +8 -0
  406. package/dist/server/src/lib/cli-tools/gemini.js +23 -73
  407. package/dist/server/src/lib/cli-tools/opencode-pane-width.js +53 -0
  408. package/dist/server/src/lib/cli-tools/opencode-v2.js +9 -7
  409. package/dist/server/src/lib/cli-tools/opencode.js +21 -85
  410. package/dist/server/src/lib/cli-tools/submit-verified-sender.js +127 -4
  411. package/dist/server/src/lib/cli-tools/vibe-local.js +21 -66
  412. package/dist/server/src/lib/detection/cli-patterns.js +374 -3065
  413. package/dist/server/src/lib/detection/idle-evidence-observation.js +2 -2
  414. package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +7 -32
  415. package/dist/server/src/lib/detection/prompt-detector.js +2 -35
  416. package/dist/server/src/lib/detection/selection-shape-patterns.js +69 -0
  417. package/dist/server/src/lib/detection/selection-shape.js +32 -520
  418. package/dist/server/src/lib/detection/shared/pasted-text.js +41 -0
  419. package/dist/server/src/lib/detection/shared/shell-pane-command.js +16 -0
  420. package/dist/server/src/lib/detection/shared/strip-box-drawing.js +25 -0
  421. package/dist/server/src/lib/detection/tools/antigravity/detect.js +5 -29
  422. package/dist/server/src/lib/detection/tools/antigravity/dialog.js +2 -15
  423. package/dist/server/src/lib/detection/tools/antigravity/patterns.js +286 -0
  424. package/dist/server/src/lib/detection/tools/claude/detect.js +3 -14
  425. package/dist/server/src/lib/detection/tools/claude/patterns.js +264 -4
  426. package/dist/server/src/lib/detection/tools/claude/prompt.js +2 -10
  427. package/dist/server/src/lib/detection/tools/codex/detect.js +46 -85
  428. package/dist/server/src/lib/detection/tools/codex/live-region.js +33 -2
  429. package/dist/server/src/lib/detection/tools/codex/patterns.js +855 -0
  430. package/dist/server/src/lib/detection/tools/codex/prompt.js +2 -10
  431. package/dist/server/src/lib/detection/tools/command-code/detect.js +8 -50
  432. package/dist/server/src/lib/detection/tools/command-code/dialog.js +2 -18
  433. package/dist/server/src/lib/detection/tools/command-code/patterns.js +234 -0
  434. package/dist/server/src/lib/detection/tools/command-code/selection-shape.js +461 -0
  435. package/dist/server/src/lib/detection/tools/context-above.js +20 -0
  436. package/dist/server/src/lib/detection/tools/copilot/detect.js +10 -35
  437. package/dist/server/src/lib/detection/tools/copilot/patterns.js +640 -0
  438. package/dist/server/src/lib/detection/tools/copilot/prompt.js +2 -10
  439. package/dist/server/src/lib/detection/tools/gemini/patterns.js +29 -0
  440. package/dist/server/src/lib/detection/tools/live-region-specs.js +1 -1
  441. package/dist/server/src/lib/detection/tools/opencode/detect.js +8 -49
  442. package/dist/server/src/lib/detection/tools/opencode/patterns.js +508 -0
  443. package/dist/server/src/lib/detection/tools/opencode-v2/detect.js +7 -42
  444. package/dist/server/src/lib/detection/tools/opencode-v2/patterns.js +63 -0
  445. package/dist/server/src/lib/detection/tools/run-detection.js +6 -47
  446. package/dist/server/src/lib/detection/tools/verdicts.js +59 -0
  447. package/dist/server/src/lib/detection/tools/vibe-local/patterns.js +20 -0
  448. package/dist/server/src/lib/detection/truncate-raw-content.js +38 -0
  449. package/dist/server/src/lib/detection/turn-abandoned.js +66 -0
  450. package/dist/server/src/lib/detection/unclassified-frame-tracker.js +2 -2
  451. package/dist/server/src/lib/global-state.js +16 -0
  452. package/dist/server/src/lib/hooks/agent-session-telemetry.js +4 -6
  453. package/dist/server/src/lib/hooks/permission-decision-state.js +2 -2
  454. package/dist/server/src/lib/hooks/sources/antigravity/history.js +11 -76
  455. package/dist/server/src/lib/hooks/sources/antigravity/hooks-config.js +63 -1
  456. package/dist/server/src/lib/hooks/sources/antigravity/source.js +16 -0
  457. package/dist/server/src/lib/hooks/sources/antigravity/transcript.js +3 -12
  458. package/dist/server/src/lib/hooks/sources/claude/history.js +17 -156
  459. package/dist/server/src/lib/hooks/sources/claude/queued-notice.js +53 -0
  460. package/dist/server/src/lib/hooks/sources/claude/source.js +33 -3
  461. package/dist/server/src/lib/hooks/sources/claude/transcript.js +3 -13
  462. package/dist/server/src/lib/hooks/sources/codex/history.js +11 -74
  463. package/dist/server/src/lib/hooks/sources/codex/hooks-config.js +147 -11
  464. package/dist/server/src/lib/hooks/sources/codex/relay-install.js +64 -21
  465. package/dist/server/src/lib/hooks/sources/codex/transcript.js +5 -15
  466. package/dist/server/src/lib/hooks/sources/command-code/history.js +20 -121
  467. package/dist/server/src/lib/hooks/sources/command-code/transcript.js +3 -13
  468. package/dist/server/src/lib/hooks/sources/copilot/hook-settings.js +161 -31
  469. package/dist/server/src/lib/hooks/sources/define-source.js +40 -55
  470. package/dist/server/src/lib/hooks/sources/event-mapper.js +2 -1
  471. package/dist/server/src/lib/hooks/sources/opencode/diff.js +3 -4
  472. package/dist/server/src/lib/hooks/sources/opencode/history.js +2 -1
  473. package/dist/server/src/lib/hooks/sources/opencode/launch-settings.js +2 -1
  474. package/dist/server/src/lib/hooks/sources/opencode/payloads.js +2 -1
  475. package/dist/server/src/lib/hooks/sources/opencode/ports.js +2 -1
  476. package/dist/server/src/lib/hooks/sources/opencode/subscription.js +4 -3
  477. package/dist/server/src/lib/hooks/sources/opencode/transcript.js +3 -16
  478. package/dist/server/src/lib/hooks/sources/opencode-v2/history.js +2 -1
  479. package/dist/server/src/lib/hooks/sources/opencode-v2/mappers.js +5 -4
  480. package/dist/server/src/lib/hooks/sources/opencode-v2/ports.js +2 -1
  481. package/dist/server/src/lib/hooks/sources/opencode-v2/subscription.js +3 -7
  482. package/dist/server/src/lib/hooks/sources/opencode-v2/usage.js +2 -1
  483. package/dist/server/src/lib/hooks/sources/pending-decisions.js +2 -1
  484. package/dist/server/src/lib/hooks/sources/registry.js +3 -2
  485. package/dist/server/src/lib/hooks/sources/transcript-history.js +262 -0
  486. package/dist/server/src/lib/hooks/sources/turn-body.js +31 -0
  487. package/dist/server/src/lib/hooks/tool-input-normalization-state.js +2 -2
  488. package/dist/server/src/lib/polling/antigravity-permission-receipts.js +2 -2
  489. package/dist/server/src/lib/polling/auto-yes-enter-fallback-state.js +142 -0
  490. package/dist/server/src/lib/polling/auto-yes-enter-fallback.js +187 -0
  491. package/dist/server/src/lib/polling/auto-yes-resolver.js +9 -3
  492. package/dist/server/src/lib/polling/auto-yes-suppression-reason.js +2 -0
  493. package/dist/server/src/lib/polling/auto-yes-suppression-state.js +2 -2
  494. package/dist/server/src/lib/polling/pending-scraped-response.js +190 -0
  495. package/dist/server/src/lib/polling/prompt-dedup-state.js +2 -2
  496. package/dist/server/src/lib/polling/prompt-dedup.js +2 -2
  497. package/dist/server/src/lib/polling/response-checker-dialog-gate.js +95 -0
  498. package/dist/server/src/lib/polling/response-checker-extraction-result.js +50 -0
  499. package/dist/server/src/lib/polling/response-checker-extraction-steps.js +670 -0
  500. package/dist/server/src/lib/polling/response-checker.js +641 -1142
  501. package/dist/server/src/lib/polling/response-dedup.js +3 -4
  502. package/dist/server/src/lib/polling/response-poller-constants.js +17 -0
  503. package/dist/server/src/lib/polling/response-poller-core.js +54 -20
  504. package/dist/server/src/lib/polling/startup-screen.js +142 -0
  505. package/dist/server/src/lib/polling/structured-history-gate.js +3 -2
  506. package/dist/server/src/lib/prompt-answer-semantic.js +178 -0
  507. package/dist/server/src/lib/prompt-answer-sender.js +5 -4
  508. package/dist/server/src/lib/push/failure-episode-state.js +2 -2
  509. package/dist/server/src/lib/push/notification-dedup.js +3 -4
  510. package/dist/server/src/lib/push/prompt-card-state.js +2 -2
  511. package/dist/server/src/lib/push/waiting-push-notifier.js +3 -4
  512. package/dist/server/src/lib/realtime/publisher-registry.js +3 -2
  513. package/dist/server/src/lib/realtime/terminal-broadcast.js +38 -5
  514. package/dist/server/src/lib/realtime/terminal-session-ownership.js +45 -0
  515. package/dist/server/src/lib/relay/relay-delivery.js +3 -2
  516. package/dist/server/src/lib/relay/relay-readiness.js +38 -1
  517. package/dist/server/src/lib/response-cleaner.js +24 -1
  518. package/dist/server/src/lib/response-extractor.js +16 -0
  519. package/dist/server/src/lib/schedule-manager.js +8 -10
  520. package/dist/server/src/lib/session/agent-event-ask-user-question.js +168 -0
  521. package/dist/server/src/lib/session/agent-event-awaiting-instruction.js +104 -0
  522. package/dist/server/src/lib/session/agent-event-dedup.js +539 -0
  523. package/dist/server/src/lib/session/agent-event-model.js +562 -0
  524. package/dist/server/src/lib/session/agent-event-record.js +10 -0
  525. package/dist/server/src/lib/session/agent-event-state.js +106 -1854
  526. package/dist/server/src/lib/session/agent-event-structured-state.js +109 -0
  527. package/dist/server/src/lib/session/agent-event-turn.js +811 -0
  528. package/dist/server/src/lib/session/agent-session-lifecycle.js +11 -2
  529. package/dist/server/src/lib/session/chat-turn-progress.js +322 -0
  530. package/dist/server/src/lib/session/claude-executor.js +45 -5
  531. package/dist/server/src/lib/session/claude-session.js +21 -65
  532. package/dist/server/src/lib/session/cli-session.js +17 -0
  533. package/dist/server/src/lib/session/current-output-builder.js +450 -1103
  534. package/dist/server/src/lib/session/current-output-history-writers.js +255 -0
  535. package/dist/server/src/lib/session/current-output-types.js +9 -0
  536. package/dist/server/src/lib/session/detection-divergence.js +55 -0
  537. package/dist/server/src/lib/session/global-session-constants.js +2 -13
  538. package/dist/server/src/lib/session/hook-turn-hold.js +102 -0
  539. package/dist/server/src/lib/session/layer-disagreement.js +2 -2
  540. package/dist/server/src/lib/session/opencode-session-store.js +2 -1
  541. package/dist/server/src/lib/session/prompt-view.js +26 -3
  542. package/dist/server/src/lib/session/prompt-waiting-composition.js +3 -2
  543. package/dist/server/src/lib/session/provisional-turn.js +3 -1
  544. package/dist/server/src/lib/session/send-user-message.js +1 -1
  545. package/dist/server/src/lib/session/session-starting-state.js +4 -6
  546. package/dist/server/src/lib/session/status-evidence-type.js +2 -0
  547. package/dist/server/src/lib/session/status-evidence.js +2 -2
  548. package/dist/server/src/lib/session/structured-status-merge.js +335 -0
  549. package/dist/server/src/lib/session/waiting-episode-state.js +3 -4
  550. package/dist/server/src/lib/session-cleanup.js +1 -5
  551. package/dist/server/src/lib/skills/install-plan.js +2 -1
  552. package/dist/server/src/lib/skills/snapshot-store.js +2 -2
  553. package/dist/server/src/lib/skills/uninstall-plan.js +2 -2
  554. package/dist/server/src/lib/timer-manager.js +6 -8
  555. package/dist/server/src/lib/tmux/geometry-delegation.js +3 -4
  556. package/dist/server/src/lib/tmux/legacy-session-alias.js +2 -1
  557. package/dist/server/src/lib/tmux/session-hooks.js +2 -1
  558. package/dist/server/src/lib/tmux/session-status-options.js +3 -3
  559. package/dist/server/src/lib/tmux/tmux-capture-cache.js +3 -2
  560. package/dist/server/src/lib/tmux/tmux.js +33 -59
  561. package/dist/server/src/lib/tui-accumulator.js +2 -2
  562. package/dist/server/src/lib/version-checker.js +6 -6
  563. package/dist/server/src/lib/ws-server.js +47 -11
  564. package/docs/en/user-guide/webapp-guide.md +27 -3
  565. package/package.json +1 -1
  566. package/release-notes/0.44.3.json +82 -0
  567. package/scripts/hooks/cmate-agent-event.sh +18 -0
  568. package/.next/server/chunks/1068.js +0 -1
  569. package/.next/server/chunks/4372.js +0 -5
  570. package/.next/server/chunks/6474.js +0 -14
  571. package/.next/server/chunks/967.js +0 -1
  572. package/.next/server/chunks/9798.js +0 -2
  573. package/.next/server/chunks/9882.js +0 -25
  574. package/.next/static/chunks/591-539fec878c9dec5e.js +0 -1
  575. package/.next/static/chunks/app/sessions/page-049e06335187aa91.js +0 -1
  576. package/.next/static/chunks/app/worktrees/[id]/page-2a81397e60f17e63.js +0 -1
  577. package/.next/static/css/38070d879efa1371.css +0 -1
  578. package/dist/server/src/lib/cli-tools/index.js +0 -41
  579. package/dist/server/src/lib/polling/global-session-poller.js +0 -140
  580. /package/.next/static/{QlCqLjorauDDBXPa-C7TK → tWwHQzmvc-rv9KKUtDWfQ}/_buildManifest.js +0 -0
  581. /package/.next/static/{QlCqLjorauDDBXPa-C7TK → tWwHQzmvc-rv9KKUtDWfQ}/_ssgManifest.js +0 -0
@@ -7,64 +7,20 @@
7
7
  * over WebSocket (terminal streaming), keeping the pull (HTTP) and push (WS)
8
8
  * paths byte-for-byte consistent (DRY).
9
9
  */
10
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- var desc = Object.getOwnPropertyDescriptor(m, k);
13
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
- desc = { enumerable: true, get: function() { return m[k]; } };
15
- }
16
- Object.defineProperty(o, k2, desc);
17
- }) : (function(o, m, k, k2) {
18
- if (k2 === undefined) k2 = k;
19
- o[k2] = m[k];
20
- }));
21
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
- Object.defineProperty(o, "default", { enumerable: true, value: v });
23
- }) : function(o, v) {
24
- o["default"] = v;
25
- });
26
- var __importStar = (this && this.__importStar) || (function () {
27
- var ownKeys = function(o) {
28
- ownKeys = Object.getOwnPropertyNames || function (o) {
29
- var ar = [];
30
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
- return ar;
32
- };
33
- return ownKeys(o);
34
- };
35
- return function (mod) {
36
- if (mod && mod.__esModule) return mod;
37
- var result = {};
38
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
- __setModuleDefault(result, mod);
40
- return result;
41
- };
42
- })();
43
10
  Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.mergeStructuredStatus = mergeStructuredStatus;
11
+ exports.mergeStructuredStatus = exports.resetChatTurnProgressState = exports.emitChatTurnProgress = exports.buildChatTurnProgress = void 0;
45
12
  exports.buildCurrentOutput = buildCurrentOutput;
46
- exports.resetChatTurnProgressState = resetChatTurnProgressState;
47
- exports.buildChatTurnProgress = buildChatTurnProgress;
48
- exports.emitChatTurnProgress = emitChatTurnProgress;
49
13
  const db_1 = require("../../lib/db");
50
- // Issue #2429: the send ledger, read directly from `chat-db` rather than from
51
- // the `@/lib/db` barrel because that barrel does not re-export it. It is the
52
- // SAME row `commandmate wait` asks for over
53
- // `GET /api/worktrees/:id/messages?limit=1&unit=pairs` (see `readNewestPromptAt`
54
- // in `cli/commands/wait.ts`), so the server and the CLI cannot disagree about
55
- // when this instance was last handed a prompt.
56
- const chat_db_1 = require("../../lib/db/chat-db");
57
14
  const unclassified_frame_tracker_1 = require("../../lib/detection/unclassified-frame-tracker");
58
15
  const session_starting_state_1 = require("../../lib/session/session-starting-state");
59
16
  const composer_text_1 = require("../../lib/detection/composer-text");
60
17
  const upstream_faults_1 = require("../../lib/detection/upstream-faults");
61
18
  const opencode_pane_obstruction_1 = require("../../lib/detection/opencode-pane-obstruction");
62
- const types_1 = require("../../lib/realtime/types");
63
- const models_1 = require("../../types/models");
64
19
  const logger_1 = require("../../lib/logger");
65
20
  const manager_1 = require("../../lib/cli-tools/manager");
66
- const types_2 = require("../../lib/cli-tools/types");
21
+ const types_1 = require("../../lib/cli-tools/types");
67
22
  const registry_1 = require("../../lib/hooks/sources/registry");
23
+ const hook_turn_hold_1 = require("../../lib/session/hook-turn-hold");
68
24
  const define_source_1 = require("../../lib/hooks/sources/define-source");
69
25
  const subscription_1 = require("../../lib/hooks/sources/opencode/subscription");
70
26
  const permission_decision_state_1 = require("../../lib/hooks/permission-decision-state");
@@ -77,6 +33,7 @@ const cli_session_1 = require("../../lib/session/cli-session");
77
33
  const status_detector_1 = require("../../lib/detection/status-detector");
78
34
  const auto_yes_manager_1 = require("../../lib/polling/auto-yes-manager");
79
35
  const auto_yes_suppression_state_1 = require("../../lib/polling/auto-yes-suppression-state");
36
+ const auto_yes_enter_fallback_1 = require("../../lib/polling/auto-yes-enter-fallback");
80
37
  const prompt_dedup_state_1 = require("../../lib/polling/prompt-dedup-state");
81
38
  const status_capture_config_1 = require("../../config/status-capture-config");
82
39
  const tmux_capture_cache_1 = require("../../lib/tmux/tmux-capture-cache");
@@ -90,506 +47,21 @@ const prompt_view_1 = require("../../lib/session/prompt-view");
90
47
  const auto_yes_dialog_gate_1 = require("../../lib/polling/auto-yes-dialog-gate");
91
48
  const layer_disagreement_1 = require("../../lib/session/layer-disagreement");
92
49
  const structured_prompt_1 = require("../../lib/session/structured-prompt");
50
+ const chat_turn_progress_1 = require("./chat-turn-progress");
51
+ const structured_status_merge_1 = require("./structured-status-merge");
52
+ const current_output_history_writers_1 = require("./current-output-history-writers");
53
+ const detection_divergence_1 = require("./detection-divergence");
54
+ // Issue #3215: chat turn progress lives in `chat-turn-progress`. Re-exported
55
+ // under the same names so every existing import of this module keeps resolving.
56
+ var chat_turn_progress_2 = require("./chat-turn-progress");
57
+ Object.defineProperty(exports, "buildChatTurnProgress", { enumerable: true, get: function () { return chat_turn_progress_2.buildChatTurnProgress; } });
58
+ Object.defineProperty(exports, "emitChatTurnProgress", { enumerable: true, get: function () { return chat_turn_progress_2.emitChatTurnProgress; } });
59
+ Object.defineProperty(exports, "resetChatTurnProgressState", { enumerable: true, get: function () { return chat_turn_progress_2.resetChatTurnProgressState; } });
60
+ // Issue #3215: the status merge lives in `structured-status-merge`. Re-exported
61
+ // under the same names so every existing import of this module keeps resolving.
62
+ var structured_status_merge_2 = require("./structured-status-merge");
63
+ Object.defineProperty(exports, "mergeStructuredStatus", { enumerable: true, get: function () { return structured_status_merge_2.mergeStructuredStatus; } });
93
64
  const logger = (0, logger_1.createLogger)('current-output-builder');
94
- /**
95
- * Write the "detection failed on this frame" row (Issue #1708).
96
- *
97
- * Stored as a `prompt` message so `capture --prompts` — the audit trail that
98
- * exists precisely to answer "why did this stall?" — lists it alongside the
99
- * prompts that WERE detected. It must never read as one of them, so the
100
- * promptData carries `type: 'unclassified'` and `status: 'unclassified'`; the
101
- * latter is also what keeps it out of `markPendingPromptsAsAnswered()`, whose
102
- * SQL selects `status = 'pending'`. A frame nobody could read must not end up
103
- * stamped "(answered via terminal)" the moment the flag clears.
104
- *
105
- * Not broadcast: this is a record for after the fact, and the prompt-answering
106
- * UI has nothing to render for a frame with no parsed options.
107
- *
108
- * REACH, stated plainly because it is a real limit: this is driven by
109
- * observation, not by the server's own loops. `buildCurrentOutput` has exactly
110
- * two callers — the current-output route and `broadcastTerminalSnapshot`, which
111
- * returns immediately when the room has no subscribers. So a row is written
112
- * while `commandmate wait` is polling (every POLL_INTERVAL_MS), while a browser
113
- * has the terminal open, or on a `capture --json`. A stall that nobody is
114
- * watching at all writes nothing, and `capture --prompts` afterwards will not
115
- * show it. That is tolerable because the stalls this exists to explain are the
116
- * ones something WAS waiting on — but it means the Auto-Yes poller running
117
- * alone is not enough. Feeding the tracker from that loop would need a second
118
- * producer of `isUnclassifiedActive`, i.e. either duplicating its definition or
119
- * adding a detectSessionStatus pass to a hot path; deliberately not done here.
120
- *
121
- * Best effort — a failed insert must never break the payload the caller is
122
- * waiting on. The tracker has already marked the run as recorded, so a failure
123
- * costs this one row, not a retry storm.
124
- */
125
- /**
126
- * The sentence that turns "nothing could read this frame" into "here is what is
127
- * on it, and here is the key that removes it" (Issue #2095).
128
- *
129
- * Empty string when there is no obstruction, so every caller can concatenate it
130
- * unconditionally and the #1708 wording is byte-identical on a frame that has
131
- * none.
132
- *
133
- * English, like the rest of {@link recordUnclassifiedFrame}'s row and unlike the
134
- * UI banner this pairs with. The row is read in `capture --prompts` as often as
135
- * in the history pane, and `commandmate` has no locale to read it in.
136
- */
137
- function describePaneObstruction(obstruction) {
138
- if (!obstruction)
139
- return '';
140
- return (` Cause: opencode's sidebar is sharing rows with the transcript ` +
141
- `(paneObstruction=${obstruction.id}, second column reads ` +
142
- `${JSON.stringify(obstruction.matchedText)}), which covers the marker that ends a ` +
143
- `turn. Press \`${opencode_pane_obstruction_1.OPENCODE_SIDEBAR_RECOVERY_CHORD}\` in the pane to close it.`);
144
- }
145
- /**
146
- * Push a history row this module just wrote to the worktree room (Issue #2214).
147
- *
148
- * Always `'message'`: both callers INSERT a brand-new row, so nothing has been
149
- * delivered for it before and the client appends rather than replaces.
150
- *
151
- * Detached on purpose, and for two reasons that both matter here:
152
- *
153
- * - the `ws-server` import stays dynamic, which is the same bargain
154
- * {@link emitChatTurnProgress} strikes — this module's *other* callers (the
155
- * `/current-output` route and the terminal push) must not pull the WebSocket
156
- * server into their graph just by building a payload;
157
- * - the row is already committed when this runs, so a socket write can never
158
- * turn a written row into a failed one. The two record functions below are
159
- * best-effort by contract and their callers are waiting on a payload.
160
- *
161
- * #2214 recorded a limitation here — a route bundle holding its own empty copy
162
- * of `ws-server`'s `rooms` under `next dev` — and added that "production runs
163
- * one custom-server bundle and is unaffected". **That last clause was wrong.**
164
- * `npm run dev` and `npm start` run the *same* custom server (`tsx server.ts`
165
- * vs `node dist/server/server.js`); what differs is only whether Next evaluates
166
- * a route handler from a dev compilation or from `.next/server`. Either way the
167
- * route handler's copy of this module and of `ws-server` is not the custom
168
- * server's, so this push was silent in production too. #2220 bridged it: the
169
- * `broadcastMessage` below now reaches the socket owner through
170
- * `lib/realtime/publisher-registry` regardless of which graph called it.
171
- */
172
- function broadcastRecordedRow(worktreeId, message) {
173
- void Promise.resolve().then(() => __importStar(require('../../lib/ws-server'))).then(({ broadcastMessage }) => {
174
- broadcastMessage('message', { worktreeId, message });
175
- })
176
- .catch((error) => {
177
- logger.warn('history-row-broadcast-failed', {
178
- worktreeId,
179
- error: error instanceof Error ? error.message : String(error),
180
- });
181
- });
182
- }
183
- function recordUnclassifiedFrame(db, params) {
184
- const dwellSeconds = Math.round(params.dwellMs / 1000);
185
- const statusReason = `${params.sessionStatus}/${params.sessionStatusReason}`;
186
- const question = `Unclassified interactive frame (${statusReason}) held for ${dwellSeconds}s. ` +
187
- `The detection layer could not parse it, so no prompt was published and ` +
188
- `nothing could answer it. Inspect the raw pane with ` +
189
- `\`commandmate capture ${params.worktreeId} --pane\`.` +
190
- // Issue #2095: appended rather than substituted. Everything above is still
191
- // true — the frame really was unreadable — and a caller matching on the
192
- // #1708 wording keeps matching. What follows turns "we could not read it"
193
- // into "here is why, and here is the key that fixes it".
194
- describePaneObstruction(params.obstruction);
195
- const record = {
196
- type: models_1.UNCLASSIFIED_PROMPT_TYPE,
197
- status: 'unclassified',
198
- question,
199
- options: [],
200
- dwellSeconds,
201
- sessionStatusReason: statusReason,
202
- };
203
- try {
204
- const message = (0, db_1.createMessage)(db, {
205
- worktreeId: params.worktreeId,
206
- role: 'assistant',
207
- content: question,
208
- messageType: 'prompt',
209
- // Not a PromptData: nothing may answer this row, which is why the record
210
- // type is kept out of that union (see UnclassifiedFrameRecord). The column
211
- // is shared, so the cast is confined to this one write.
212
- promptData: record,
213
- timestamp: new Date(),
214
- cliToolId: params.cliToolId,
215
- instanceId: params.instanceId,
216
- });
217
- logger.info('unclassified-frame-recorded', {
218
- worktreeId: params.worktreeId,
219
- cliToolId: params.cliToolId,
220
- dwellSeconds,
221
- statusReason,
222
- });
223
- // `cliToolId` / `instanceId` are already the explicit values passed above —
224
- // `createMessage` hands back the caller's own object — so the published row
225
- // addresses the same instance the column does.
226
- broadcastRecordedRow(params.worktreeId, message);
227
- }
228
- catch (error) {
229
- logger.warn('unclassified-frame-record-failed', {
230
- worktreeId: params.worktreeId,
231
- error: error instanceof Error ? error.message : String(error),
232
- });
233
- }
234
- }
235
- /**
236
- * Write the "the agent said a dialog is open and we could not read it" row
237
- * (Issue #1725, continuing #1708's proposal 2).
238
- *
239
- * Written once per waiting episode, and ONLY while the scraper is publishing no
240
- * prompt of its own. Both halves matter:
241
- *
242
- * - once, because `buildCurrentOutput` runs on every poll and a row per poll
243
- * would turn one blocked dialog into a wall of identical history;
244
- * - only when the scraper is blind, because when it is not, the existing
245
- * prompt writers already record that prompt with its options and its answer.
246
- * A second row would double-count the audit trail `capture --prompts` prints
247
- * and put a "nobody could read this" line next to the parsed prompt that
248
- * proves somebody could.
249
- *
250
- * So a row here means exactly one thing, which is the thing #1708 asked to be
251
- * recorded: a prompt existed and the detection layer did not see it.
252
- *
253
- * Best effort, for the same reason as {@link recordUnclassifiedFrame}: the
254
- * caller is waiting on a payload, and a failed insert must cost this row and
255
- * nothing else.
256
- */
257
- function recordStructuredPrompt(db, params) {
258
- const record = (0, structured_prompt_1.buildStructuredPromptHistoryRecord)(params.worktreeId, params.facts);
259
- try {
260
- const message = (0, db_1.createMessage)(db, {
261
- worktreeId: params.worktreeId,
262
- role: 'assistant',
263
- content: record.question,
264
- summary: `structured prompt · source=${params.state.source}${params.state.toolName ? ` · tool=${params.state.toolName}` : ''}`,
265
- messageType: 'prompt',
266
- // Not a PromptData: it has no options and nothing may answer it by
267
- // number. The column is shared, so the cast is confined to this write —
268
- // the same arrangement UnclassifiedFrameRecord uses.
269
- promptData: record,
270
- timestamp: new Date(),
271
- cliToolId: params.cliToolId,
272
- instanceId: params.instanceId,
273
- });
274
- logger.info('structured-prompt-recorded', {
275
- worktreeId: params.worktreeId,
276
- cliToolId: params.cliToolId,
277
- instanceId: params.instanceId,
278
- source: params.state.source,
279
- toolName: params.state.toolName,
280
- });
281
- broadcastRecordedRow(params.worktreeId, message);
282
- }
283
- catch (error) {
284
- logger.warn('structured-prompt-record-failed', {
285
- worktreeId: params.worktreeId,
286
- error: error instanceof Error ? error.message : String(error),
287
- });
288
- }
289
- }
290
- /**
291
- * The `AskUserQuestion` call as the degraded prompt can describe it
292
- * (Issue #1726).
293
- *
294
- * Only the first question, and no option numbers — see
295
- * {@link StructuredAskUserQuestionSummary} for why a layer that cannot see the
296
- * screen must not publish numbers for it.
297
- */
298
- function summarizeAskUserQuestion(episode) {
299
- const first = episode?.spec.questions[0];
300
- if (!first)
301
- return null;
302
- return {
303
- question: first.question,
304
- labels: first.choices.map((choice) => choice.label),
305
- questionCount: episode.spec.questions.length,
306
- // Issue #2951: a question that takes a typed answer, so the panel and the
307
- // phone sheet can offer an input for it.
308
- ...(first.custom ? { custom: true } : {}),
309
- };
310
- }
311
- /**
312
- * Prefer the agent's own account of what it is doing over the screen scrape
313
- * (Issue #1723).
314
- *
315
- * Pure, and exported so the whole precedence table can be tested without a tmux
316
- * session behind it.
317
- *
318
- * ## Why the scraper still wins in two cases
319
- *
320
- * **`waiting` on the scraper's side always wins.** A frame the detector reads
321
- * as a prompt, a selection list or a pager is a frame a human has to act on,
322
- * and this Issue deliberately does not touch `isPromptWaiting` / `promptData` /
323
- * `isSelectionListActive` (they are #1725's). Letting a structured `running`
324
- * overwrite `sessionStatus` while those three still said "answer me" would
325
- * publish a self-contradicting payload. It is also the empirically necessary
326
- * rule: the live capture found that Claude emits **no event at all** while an
327
- * `AskUserQuestion` selection or "Ready to submit your answers?" screen is up
328
- * (`agent-hooks-live-verification.md` §5.6), so the newest structured fact
329
- * there is the `user_prompt_submit` that opened the turn — `running` — while
330
- * the truth on screen is "waiting for a human". That is precisely the #1708
331
- * stall, and the scraper is the only layer that can see it.
332
- *
333
- * **A structured `waiting` is applied only through the prompt-waiting state**
334
- * (Issue #1725). #1723 recorded `Notification(permission_prompt)` and stopped
335
- * there, for a reason that had to be answered before it could be promoted:
336
- * nothing marks a permission prompt as *answered*, so a verdict read off the
337
- * last event alone would stick until the next `Stop` and describe a session
338
- * that went back to work minutes ago. `getStructuredPromptWaiting` is that
339
- * answer — it is released by `Stop` / `user_prompt_submit` / a generation
340
- * change, by the scraper reporting the frame it corroborated has cleared, and
341
- * by expiry. So the `waiting` promoted here is the one that survives all of
342
- * those, passed in explicitly; the raw `structured.status === 'waiting'` still
343
- * decides nothing on its own.
344
- *
345
- * ## `isUnclassifiedActive` is not this function's to move (Issue #2011)
346
- *
347
- * The flag is carried through from the scraper on every path. It answers "could
348
- * the detection layer read this frame?", and no amount of knowledge about the
349
- * TURN changes the answer for the FRAME:
350
- *
351
- * - a static overlay left on screen after a turn (`/help`, #1497) reads as
352
- * `running`/`no_recent_output` while the structured layer says `ready` — the
353
- * structured layer adds nothing about the screen, and clearing the flag would
354
- * take away the navigation hatch the user needs to escape;
355
- * - a frame nobody can classify while the structured layer says `running` is
356
- * still a frame nobody can classify. `wait`'s unclassified dwell (exit 10)
357
- * is the last hatch for a screen that produces no events at all, and #1708
358
- * exists because it was missing. A structured `running` does not prove a
359
- * human is not needed — see the AskUserQuestion case above.
360
- *
361
- * `#1723`'s reported case — `Stop` arrives while the spinner is still painted —
362
- * needs no help from here: that frame reads `running`/`thinking_indicator`, which
363
- * is classified, so the flag was already down and `wait` completes on the
364
- * structured `ready` alone.
365
- *
366
- * What this function DOES move is `evidence`, and only upwards: see
367
- * {@link hookClosedTurn}.
368
- *
369
- * @param turn - The published turn record for this instance, or null. Read only
370
- * for {@link hookClosedTurn}; `structured` remains the status authority.
371
- */
372
- function mergeStructuredStatus(scraper, structured, promptWaiting = null, turn = null, lastPromptAt = null) {
373
- if (scraper.status === 'waiting') {
374
- return { ...scraper, structuredApplied: false };
375
- }
376
- // Issue #1725, the OR rule at the status level: the scraper reads this frame
377
- // as running or ready, and the agent has told us a dialog is in front of it.
378
- // Publishing `running` next to `isPromptWaiting: true` would be a payload
379
- // that contradicts itself, and every consumer of `sessionStatus` — the
380
- // sidebar dot, `deriveSessionStatus`, the worktrees API — would show a worker
381
- // that needs a human as one that is busy.
382
- if (promptWaiting !== null) {
383
- return {
384
- status: 'waiting',
385
- reason: (0, prompt_waiting_composition_1.structuredWaitingReason)(promptWaiting),
386
- thinking: false,
387
- // Untouched: what the scraper could read about this frame does not change
388
- // because the agent told us a dialog is open (Issue #1924).
389
- evidence: scraper.evidence,
390
- isUnclassifiedActive: scraper.isUnclassifiedActive,
391
- structuredApplied: true,
392
- };
393
- }
394
- if (structured === null || structured.status === 'waiting') {
395
- return { ...scraper, structuredApplied: false };
396
- }
397
- // Issue #2429: the structured `ready` is about a turn older than the prompt
398
- // this instance is currently answering, and the screen says so. See
399
- // {@link structuredReadyPredatesPrompt} for why that combination is the only
400
- // one in which the scraper wins a `running`.
401
- if (structuredReadyPredatesPrompt(scraper, structured, turn, lastPromptAt)) {
402
- return { ...scraper, structuredApplied: false };
403
- }
404
- const thinking = structured.status === 'running';
405
- // Issue #2011 (対応 2): the agent's own turn close IS positive evidence.
406
- //
407
- // #1927 (DR2-003) guarded this override with `scraper.evidence === 'positive'`
408
- // to stop a structured `ready` clearing `isUnclassifiedActive` on a frame
409
- // nobody could read. It over-corrected twice over. First, the conjunct made
410
- // the whole expression a no-op: `evidence = cond ? 'positive' : scraper.evidence`
411
- // where `cond` requires `scraper.evidence === 'positive'` returns
412
- // `scraper.evidence` on both arms, so the branch could not change a value in
413
- // any direction. Second, the fix it was reaching for belonged one field over —
414
- // the hatch is held open by {@link ScraperVerdict.isUnclassifiedActive}, which
415
- // is carried through below rather than re-derived, so the evidence no longer
416
- // has to be understated to protect it.
417
- //
418
- // What that leaves is a payload that stopped contradicting itself. A pane
419
- // whose `reason` reads `hook_stop` while `statusEvidence` reads `'none'` is
420
- // denying, in one field, the strongest positive signal this server can get: a
421
- // `Stop` from the agent, for a turn this server watched open. `closedAt >
422
- // openedAt` is what narrows it to that case — a `Stop` that arrived with no
423
- // turn open publishes `openedAt: null` (see `recordAgentEvent`), and a close
424
- // the SCREEN inferred carries one of the other {@link TurnCloseReason} values.
425
- // Freshness needs no bound of its own: `getStructuredSessionState` answered
426
- // null above once the display event passed STRUCTURED_STATE_MAX_AGE_MS.
427
- //
428
- // `scraper.status === 'running'` is deliberately NOT required. The session this
429
- // Issue was reported from sat at `ready`/`input_prompt` with `closedBy: 'stop'`
430
- // — the agent had said it was done and the frame agreed — and the old conjunct
431
- // is what kept publishing `'none'` for it.
432
- const evidence = structured.status === 'ready' && hookClosedTurn(turn) ? 'positive' : scraper.evidence;
433
- return {
434
- status: structured.status,
435
- reason: structured.reason,
436
- thinking,
437
- evidence,
438
- // Issue #2011: what the structured layer knows about the TURN says nothing
439
- // about whether this FRAME could be read. #1708's dwell and the nav hatch
440
- // are the last way out of a pane nothing can drive, and a `Stop` does not
441
- // make an unreadable pane readable — see the `/help` overlay fixture in
442
- // `tests/unit/lib/detection/fixtures/claude-live-2011/`.
443
- isUnclassifiedActive: scraper.isUnclassifiedActive,
444
- structuredApplied: true,
445
- };
446
- }
447
- /**
448
- * Whether the agent's own `Stop` closed a turn this server watched open
449
- * (Issue #2011, 対応 2).
450
- *
451
- * Both halves are required. `closedBy: 'stop'` rules out the five close reasons
452
- * that are the server's inference rather than the agent's word (`stale`,
453
- * `generation`, `session_end`, `scraper_evidence`, `resync_idle`), and
454
- * `closedAt > openedAt` rules out a `Stop` whose turn was never observed
455
- * opening — `recordAgentEvent` publishes `openedAt: null` for that, and a close
456
- * with nothing behind it is not a completion anybody watched.
457
- */
458
- function hookClosedTurn(turn) {
459
- if (turn === null)
460
- return false;
461
- const { closedBy, closedAt, openedAt } = turn;
462
- return closedBy === 'stop' && closedAt !== null && openedAt !== null && closedAt > openedAt;
463
- }
464
- /**
465
- * Whether a structured `ready` could be about a turn that ended before the
466
- * prompt now on screen (Issue #2429).
467
- *
468
- * The cheap half of {@link structuredReadyPredatesPrompt}, split out because
469
- * `buildPayload` asks it FIRST: it is the gate on the one database read this
470
- * rule needs, and a session that is not in this state must not pay for the send
471
- * ledger on every poll. One expression, two callers — the alternative is the
472
- * "two expressions for one fact" the merge's own docblock says is how this
473
- * layer drifts.
474
- *
475
- * Three conjuncts, and all three are about the same instant:
476
- *
477
- * - **the screen is positively generating.** `ScraperVerdict.thinking` is
478
- * `isGeneratingStatus`, i.e. `running` with `thinking_indicator` /
479
- * `opencode_processing_indicator` behind it — a spinner, `✻ Thinking…` or
480
- * `esc to interrupt` that a tool's own pattern matched. It is deliberately
481
- * NOT `scraper.status === 'running'`: the `no_recent_output` and
482
- * `unknown_frame` floors are also `running`, and neither is evidence of
483
- * anything. A frame that merely stopped changing must never retire a `Stop`
484
- * the agent actually sent.
485
- * - **the structured layer says `ready`.** `running` and `waiting` already win
486
- * on their own paths and are not in question here.
487
- * - **that `ready` came from the agent's own `Stop`.**
488
- * `getStructuredSessionState` publishes `ready / hook_stop` exactly when the
489
- * turn record carries `closedBy: 'stop'` with a `closedAt`, so the record is
490
- * read rather than the folded reason.
491
- *
492
- * `openedAt` is deliberately not required, which is what separates this from
493
- * {@link hookClosedTurn}. A `Stop` that arrived with no turn open publishes
494
- * `openedAt: null`, and for the tool this Issue was reported from that is the
495
- * ORDINARY shape: Command Code cannot emit `UserPromptSubmit` (its loader
496
- * validates the event name against a closed list) and emits no
497
- * `PreToolUse` / `PostToolUse` on a turn that calls no tool, so nothing ever
498
- * opens the turn. Requiring an `openedAt` here would exclude precisely the case
499
- * this rule exists for.
500
- */
501
- function staleReadyCandidate(scraper, structured, turn) {
502
- if (!scraper.thinking)
503
- return false;
504
- if (structured === null || structured.status !== 'ready')
505
- return false;
506
- if (turn === null)
507
- return false;
508
- return turn.closedBy === 'stop' && turn.closedAt !== null;
509
- }
510
- /**
511
- * Whether the structured layer's `ready` has been outlived by the newest prompt
512
- * this instance was handed (Issue #2429).
513
- *
514
- * ## The defect this closes
515
- *
516
- * The merge above prefers the agent's own account to the screen, and until this
517
- * Issue the only exception was a scraper `waiting` (#1708). That is correct for
518
- * every tool that can say when a turn BEGINS — claude, codex, copilot, gemini
519
- * and antigravity all post a turn-opening event, so a live turn replaces the
520
- * previous turn's `stop` before the first generating frame is ever read.
521
- *
522
- * Command Code can post neither: no `UserPromptSubmit`, and no `PreToolUse` /
523
- * `PostToolUse` on a turn that calls no tool. The previous turn's `ready /
524
- * hook_stop` therefore stays the newest structured fact for the whole of the
525
- * next turn, and the merge published it over a pane that was visibly generating.
526
- * Measured 2026-09-08 on a 19 s turn: `sessionStatus` read `ready / hook_stop`
527
- * from 4 s to 18 s while the status row said `esc to interrupt`. `commandmate
528
- * wait` reads that field — not the frame — for "is at its composer", so #1975's
529
- * 60 s unanswered-prompt hold was the only thing standing between a long turn
530
- * and a `basis=scraper_ready` completion reported before the reply existed.
531
- *
532
- * ## Why a comparison rather than a timer
533
- *
534
- * The same comparison `wait` already makes (`outstandingPrompt`), against the
535
- * same two server-stamped facts: the turn's `closedAt`, and the timestamp of
536
- * the newest user row in the chat ledger. A `Stop` that POSTDATES the newest
537
- * prompt is this turn's own — the agent finished, the spinner on the frame is a
538
- * repaint that has not settled, and the structured `ready` keeps winning. A
539
- * `Stop` that predates it has reported neither the start nor the end of the
540
- * work now on screen, and about that work it says nothing at all.
541
- *
542
- * Shortening #1975's hold would have been the other repair, and the Issue rules
543
- * it out for a reason worth restating here: the hold is what stops a false
544
- * completion during a turn nobody has reported yet, so a shorter one moves the
545
- * false completion earlier rather than removing it.
546
- *
547
- * @param lastPromptAt - Epoch ms of the newest prompt handed to this instance,
548
- * or null when the ledger was not read or holds none. Null decides nothing:
549
- * an unreadable ledger is not evidence that nothing was sent, so the
550
- * structured verdict stands exactly as it did before this Issue.
551
- */
552
- function structuredReadyPredatesPrompt(scraper, structured, turn, lastPromptAt) {
553
- if (lastPromptAt === null)
554
- return false;
555
- if (!staleReadyCandidate(scraper, structured, turn))
556
- return false;
557
- const closedAt = turn?.closedAt ?? null;
558
- return closedAt !== null && closedAt < lastPromptAt;
559
- }
560
- /**
561
- * When this instance was last handed a prompt, or null (Issue #2429).
562
- *
563
- * The server's half of the comparison `commandmate wait` makes over HTTP. Both
564
- * sides read the same row — the newest `role: 'user'` message scoped to this
565
- * (worktree, tool, instance) — so the field this function feeds and the notice
566
- * `wait` prints cannot describe different sends.
567
- *
568
- * Best effort, like {@link recordUnclassifiedFrame} and
569
- * {@link recordStructuredPrompt}: the caller is building a payload, and a
570
- * ledger that cannot be read must cost this one comparison and nothing else.
571
- * Null is reported rather than a guess, and null leaves the pre-#2429
572
- * precedence in place — an unreadable ledger is not evidence that nothing was
573
- * sent, which is the position `wait` takes on the same read.
574
- */
575
- function readNewestPromptAt(db, worktreeId, cliToolId, instanceId) {
576
- try {
577
- const row = (0, chat_db_1.getLastUserMessageForInstance)(db, worktreeId, cliToolId, instanceId);
578
- if (!row)
579
- return null;
580
- const at = row.timestamp instanceof Date ? row.timestamp.getTime() : NaN;
581
- return Number.isFinite(at) ? at : null;
582
- }
583
- catch (error) {
584
- logger.debug('newest-prompt-read-failed', {
585
- worktreeId,
586
- cliToolId,
587
- instanceId,
588
- error: error instanceof Error ? error.message : String(error),
589
- });
590
- return null;
591
- }
592
- }
593
65
  /**
594
66
  * Build the current-output payload for a worktree session.
595
67
  *
@@ -617,7 +89,7 @@ async function buildCurrentOutput(db, worktreeId, cliToolId, instanceId, resolut
617
89
  // inside, so what is waited for is a worktree row and a Map lookup. What it
618
90
  // buys is that the frame's outcome is logged against the tick that produced
619
91
  // it rather than whenever the event loop got round to the detached tail.
620
- await publishChatTurnProgress(db, worktreeId, cliToolId, instanceId);
92
+ await (0, chat_turn_progress_1.publishChatTurnProgress)(db, worktreeId, cliToolId, instanceId);
621
93
  }
622
94
  if (!resolution)
623
95
  return payload;
@@ -631,17 +103,12 @@ async function buildCurrentOutput(db, worktreeId, cliToolId, instanceId, resolut
631
103
  };
632
104
  }
633
105
  /**
634
- * The payload itself, with no knowledge of how its (tool, instance) pair was
635
- * chosen. Split from {@link buildCurrentOutput} so the resolution fields are
636
- * appended in one place instead of at both of the two return sites below.
106
+ * The agent's own events for this session, read before the pane is looked at
107
+ * (Issue #3215). Split out of {@link buildPayload} in the order it ran there:
108
+ * nothing here awaits, and `structuredEvents` is the object both of its return
109
+ * paths publish.
637
110
  */
638
- async function buildPayload(db, worktreeId, cliToolId, instanceId) {
639
- const resolvedInstanceId = instanceId ?? cliToolId;
640
- const manager = manager_1.CLIToolManager.getInstance();
641
- const cliTool = manager.getTool(cliToolId);
642
- // Issue #2886: read once, independent of `isRunning`, so both return paths
643
- // below publish the same value the pane is (or would be) reached at.
644
- const sessionName = cliTool.getSessionName(worktreeId, instanceId);
111
+ function readAgentEvents(worktreeId, cliToolId, instanceId, resolvedInstanceId) {
645
112
  const stopEventAt = (0, agent_event_state_1.getLastStopEventAt)(worktreeId, cliToolId, instanceId);
646
113
  const lastEvent = (0, agent_event_state_1.getLastAgentEvent)(worktreeId, cliToolId, instanceId);
647
114
  // Issue #1924: the registry answers for every tool — a real implementation
@@ -745,12 +212,377 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
745
212
  // tool's status poll grows a field or a request.
746
213
  sessionDiff: (0, diff_1.ensureOpencodeSessionDiff)({ worktreeId, cliToolId, instanceId }),
747
214
  };
215
+ return { stopEventAt, eventSource, askUserQuestion, structuredEvents };
216
+ }
217
+ /**
218
+ * The prompt this payload publishes and the replies an approval accepts
219
+ * (Issue #3215). Split out of {@link buildPayload} in the order it ran there;
220
+ * every value below is derived from the arguments, and nothing is assigned
221
+ * outside this function.
222
+ */
223
+ function composePromptData({ worktreeId, cliToolId, output, statusResult, startingSince, promptWaiting, askUserQuestion, eventSource, }) {
224
+ const scraperPromptWaiting = statusResult.hasActivePrompt;
225
+ // Issue #1726: the agent's own account of what it asked. It contributes only
226
+ // where some other layer has already established that a dialog is on screen —
227
+ // this record decides no status of its own, because Claude emits nothing at
228
+ // all while an AskUserQuestion picker is up (§5.6) and a record that asserted
229
+ // `waiting` from the invocation would go on asserting it long after a human
230
+ // answered in the terminal.
231
+ // Issue #2040 moved the read to the top of `buildPayload` — `readAgentEvents`
232
+ // takes it there today (the decision entries publish the same episode's
233
+ // choices) — and this line now re-uses it, so the two surfaces cannot describe
234
+ // different instants.
235
+ const scraperPromptData = statusResult.promptDetection.promptData;
236
+ const correctedPromptData = scraperPromptData && askUserQuestion
237
+ ? (0, ask_user_question_prompt_1.applyAskUserQuestion)(scraperPromptData, askUserQuestion.spec)
238
+ : null;
239
+ // The degraded form, for a dialog only the structured layer can see. Enriched
240
+ // with the question text when one is in flight — that turns "a dialog is open
241
+ // and nobody could read it" into "a dialog is open and here is what it asks".
242
+ //
243
+ // Issue #1898 adds the replies the dialog accepts, for the sources that can be
244
+ // answered without touching the pane. The gate is `eventIdentity`: a source
245
+ // that publishes a per-decision id is a source whose approval can be answered
246
+ // by that id, which is what makes an option NUMBER here mean something other
247
+ // than a line on a screen nobody parsed. `source === 'notification'` narrows
248
+ // it to a dialog the agent actually reported — a `permission-request` record
249
+ // is a prediction, and a question is answered with a choice rather than with
250
+ // one of these three verdicts.
251
+ //
252
+ // Issue #2031 adds the fourth conjunct and folds the whole gate into ONE
253
+ // value — today `promptWaiting !== null` here and the other three inside
254
+ // `isApiAnswerableStructuredWait` (Issue #3215).
255
+ // The three verdicts and the id they are delivered to are now derived
256
+ // from the same expression, so they cannot be published apart — and "apart"
257
+ // is not hypothetical, it is the state #1932 shipped: options were published
258
+ // on the capability alone while `decisionId` was published nowhere at all, so
259
+ // the panel drew no buttons and the only way out of an opencode approval in a
260
+ // browser was the arrow-key safety net. Options WITHOUT an id is the worse
261
+ // half of that pair — the numbers would reach the keystroke path, where a
262
+ // bare "1" selects whatever the picker happens to be highlighting (#1681).
263
+ const addressableDecisionId = promptWaiting !== null &&
264
+ (0, prompt_waiting_composition_1.isApiAnswerableStructuredWait)(promptWaiting, eventSource.capabilities.eventIdentity)
265
+ ? promptWaiting.decisionId
266
+ : null;
267
+ // Issue #2100 splits the two halves of #2031's single expression, because an
268
+ // addressable decision is now of two KINDS. The id is published for both — it
269
+ // is what `POST /respond` names, and a question needs it exactly as much as an
270
+ // approval does — but the three verdicts belong to an approval alone.
271
+ //
272
+ // Publishing both for a question is not a cosmetic error: it is precisely what
273
+ // #2039's third gate refuses. `readPromptQuestionChoices` returns null the
274
+ // moment `decisionOptions` is non-empty, so a question carrying them would
275
+ // draw `Allow once / Allow always / Reject` over the agent's own choices, and
276
+ // a verdict sent to a question is refused at the source
277
+ // (`question-needs-answer-verdict`). The kind is recovered from the record the
278
+ // same way `structuredEvents.pendingDecisions[].kind` is, through the one
279
+ // reader in `pending-decision-kind`.
280
+ const addressesQuestion = promptWaiting !== null && (0, pending_decision_kind_1.pendingDecisionKind)(promptWaiting.toolName) === 'question';
281
+ const decisionOptions = addressableDecisionId !== null && !addressesQuestion
282
+ ? // Issue #2951: in the tool's own words (OpenCode V2: `Always allow`).
283
+ (0, structured_prompt_1.structuredDecisionOptionsFor)(cliToolId)
284
+ : null;
285
+ const structuredFacts = promptWaiting === null
286
+ ? null
287
+ : {
288
+ ...promptWaiting,
289
+ askUserQuestion: (0, structured_status_merge_1.summarizeAskUserQuestion)(askUserQuestion),
290
+ decisionOptions,
291
+ // Explicit, though the spread above already carries a `decisionId`
292
+ // off the record: the spread's copy is the raw one, and what may be
293
+ // published is the GATED one. Letting the record's value through
294
+ // would put an id on a payload whose verdicts were withheld, which is
295
+ // the biconditional this Issue exists to hold.
296
+ decisionId: addressableDecisionId,
297
+ // Gated on the same value, for a reason of its own: `patterns` is
298
+ // what the `Allow always` BUTTON grants, so publishing it where no
299
+ // button is drawn adds a rule list to a panel that is telling the
300
+ // user to go and answer in the terminal. Every source but opencode
301
+ // therefore keeps the exact payload it had before this Issue, plus
302
+ // the one `decisionId: null`.
303
+ //
304
+ // Issue #2100 moves the gate onto `decisionOptions` rather than the
305
+ // id, which is the same value for every approval and the honest one
306
+ // for a question: a question draws no `Allow always`, so it may not
307
+ // carry the rules one would have saved. (`reportQuestionPending`
308
+ // records `patterns: null` anyway; the two agree by construction now
309
+ // instead of by coincidence.)
310
+ patterns: decisionOptions !== null ? promptWaiting.patterns : null,
311
+ };
312
+ const promptData = startingSince !== null
313
+ ? null
314
+ : scraperPromptWaiting
315
+ ? correctedPromptData ??
316
+ scraperPromptData ??
317
+ (structuredFacts ? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts) : null)
318
+ : structuredFacts
319
+ ? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts)
320
+ : null;
321
+ // Issue #2870: whether `/prompt-response` would answer the prompt published
322
+ // above, read by the SAME function it re-verifies with. In #2868 `promptData`
323
+ // came off the generic parser alone, the route refused it, and the UI's Send
324
+ // did nothing. Only for a parser-read prompt: the structured form (hook
325
+ // `decisionId`, degraded) is answered by id or not by number at all, so the
326
+ // key is left out there, as it is when no prompt is up. `isPromptWaiting` /
327
+ // `promptData` / `sessionStatus` are untouched — `wait`'s exit 10, Auto-Yes
328
+ // and push notifications keep reading exactly what they read before.
329
+ const promptAnswerable = scraperPromptWaiting && (correctedPromptData ?? scraperPromptData)
330
+ ? (0, auto_yes_dialog_gate_1.assessPromptAnswerability)(cliToolId, output).refusal === null
331
+ : undefined;
332
+ return { structuredFacts, promptData, promptAnswerable };
333
+ }
334
+ /**
335
+ * The scraper's verdict on the frame and the flags read off it (Issue #3272).
336
+ * Split out of {@link buildPayload} in the order it ran there; nothing here
337
+ * awaits.
338
+ */
339
+ function judgeFrame(worktreeId, cliToolId, instanceId, output) {
340
+ // The sequence from here to `startingStatusResult` is the same as in
341
+ // worktree-status-helper.ts; what follows differs on purpose and is not shared
342
+ // (#3215 "not touched").
343
+ // These six expressions are deliberately kept identical to the same sequence in
344
+ // worktree-status-helper.ts, not shared (#3319 item 37 decision).
345
+ const compositeKey = (0, auto_yes_manager_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
346
+ const lastServerResponseTimestamp = (0, auto_yes_manager_1.getLastServerResponseTimestamp)(compositeKey);
347
+ const lastOutputTimestamp = lastServerResponseTimestamp ? new Date(lastServerResponseTimestamp) : undefined;
348
+ const rawStatusResult = (0, status_detector_1.detectSessionStatus)(output, cliToolId, lastOutputTimestamp);
349
+ // Issue #3179: a launch in progress. The frame is a shell prompt and the
350
+ // launch line, or a dialog the launch is about to answer, and none of the
351
+ // flags below may be read off it: the floor verdict raised the Navigate pad,
352
+ // the trust dialog raised the selection list / prompt sheet. A dialog that
353
+ // outstays the launch's own answer releases the record (a login, an unknown
354
+ // dialog), and from then on this is the ordinary verdict again.
355
+ const startingSince = (0, session_starting_state_1.observeSessionStartingFrame)(worktreeId, cliToolId, instanceId, rawStatusResult.status === 'waiting' || rawStatusResult.hasActivePrompt);
356
+ const statusResult = startingSince === null
357
+ ? rawStatusResult
358
+ : (0, session_starting_state_1.startingStatusResult)(rawStatusResult);
359
+ // Issue #1912: every `running` reason that means "the agent is producing
360
+ // output", not just `thinking_indicator`. opencode answers
361
+ // `opencode_processing_indicator` for its `esc interrupt` footer, which is
362
+ // the only signal it gives between the submitted prompt and the first
363
+ // transcript row — on a scraper-only session that stretch showed no
364
+ // thinking indicator at all.
365
+ const thinking = (0, status_detector_1.isGeneratingStatus)(statusResult);
366
+ const scraperPromptWaiting = statusResult.hasActivePrompt;
367
+ const isSelectionListActive = statusResult.status === 'waiting' && status_detector_1.SELECTION_LIST_REASONS.has(statusResult.reason);
368
+ const isPagerActive = statusResult.reason === status_detector_1.STATUS_REASON.CODEX_PAGER;
369
+ // Issue #2369: an overlay whose ONLY exit is the dismiss — Command Code's
370
+ // `/usage` panel, whose last row is `Press Esc to close`.
371
+ //
372
+ // A THIRD flag rather than a member of `SELECTION_LIST_REASONS`, and the
373
+ // separation is the fix rather than a nicety: `isSelectionListActive` is what
374
+ // the arrow pad is drawn from, and this screen has no highlight to move. Nor
375
+ // is it `isUnclassifiedActive` any more — the detector answers `waiting` for
376
+ // it, and `isUnclassifiedFrame` only ever says yes to `running` — which is
377
+ // exactly what stops the chat surface offering the eighteen-button
378
+ // hatch + answer-key card to a panel that accepts one key.
379
+ //
380
+ // `status === 'waiting'` is checked as well as the reason, the same shape
381
+ // `isSelectionListActive` above uses, so a future producer that publishes the
382
+ // token with some other status cannot silently turn the card into an Esc
383
+ // button.
384
+ const isDismissablePanelActive = statusResult.status === 'waiting'
385
+ && statusResult.reason === status_detector_1.STATUS_REASON.COMMAND_CODE_DISMISSABLE_PANEL;
386
+ // Issue #1497: the detection-independent nav hatch (#1017/#1494) is gated on
387
+ // isUnclassifiedActive. A static, unrecognized TUI overlay (e.g. Claude `/help`)
388
+ // whose frame stops changing degrades from `running`/`default` to
389
+ // `no_recent_output` once the Auto-Yes poller has stamped lastOutputTimestamp
390
+ // (its sole writer, auto-yes-poller.ts). That is still an interactive-but-
391
+ // unclassified frame — a real idle prompt (`❯`) is classified earlier as
392
+ // `input_prompt`, never as `no_recent_output` — so treat the timed-out fallback
393
+ // as unclassified too and keep the hatch open instead of stranding the user.
394
+ // Issue #1924, §4 D1 decision 2: stated as evidence. Issue #1927 moved that
395
+ // PRODUCER into the detector, because only the detector knows which rule
396
+ // answered — `input_prompt` is positive for a tool whose idle rule vouched for
397
+ // the frame and `'none'` for one whose rule declined, with the same status and
398
+ // the same reason on the wire.
399
+ const evidence = statusResult.evidence;
400
+ // Issue #2011: the flag is NOT that fact, and deriving it from `evidence` is
401
+ // the regression this Issue fixes. `'none'` is "I could not prove this pane is
402
+ // idle" — an ordinary Claude composer with no completion marker above it
403
+ // qualifies, and 7 of 8 live idle panes did on 2026-08-24. The flag's three
404
+ // consumers all ask the older, narrower question instead: `TerminalEscapeHatch`
405
+ // opens on a frame a human has to drive by hand, `wait` suppresses its
406
+ // completion check and eventually exits 10 on one, and `unclassified_frames`
407
+ // records it as a detection failure worth capturing as a fixture. That is
408
+ // `isUnclassifiedFrame` — the reason vocabulary, not the strength of the
409
+ // evidence behind a readable verdict.
410
+ const isUnclassifiedActive = (0, status_evidence_1.isUnclassifiedFrame)(statusResult.status, statusResult.reason);
411
+ return {
412
+ compositeKey,
413
+ lastServerResponseTimestamp,
414
+ statusResult,
415
+ startingSince,
416
+ thinking,
417
+ scraperPromptWaiting,
418
+ isSelectionListActive,
419
+ isPagerActive,
420
+ isDismissablePanelActive,
421
+ evidence,
422
+ isUnclassifiedActive,
423
+ };
424
+ }
425
+ /**
426
+ * The detection-divergence log lines (Issue #3272). Split out of
427
+ * {@link buildPayload} in the order it ran there; it only logs and tracks.
428
+ */
429
+ function logDetectionDivergence(worktreeId, cliToolId, resolvedInstanceId, statusResult, structured, merged) {
430
+ // Issue #1723 §3: the field data this Epic is being built on. Every line is
431
+ // one poll where the screen and the agent disagreed about what the agent was
432
+ // doing, which is the only way to answer "how wrong was the scraper?" with a
433
+ // number instead of an anecdote. Emitted only on disagreement — a session
434
+ // where the two layers agree is silent — and including the disagreements this
435
+ // merge deliberately does NOT act on (`applied: false`), because those are
436
+ // exactly the cases the next Issues in the Epic have to decide about.
437
+ const diverging = structured !== null && structured.status !== statusResult.status;
438
+ if (diverging) {
439
+ logger.info('detection-divergence', {
440
+ worktreeId,
441
+ cliToolId,
442
+ instanceId: resolvedInstanceId,
443
+ scraperStatus: statusResult.status,
444
+ scraperReason: statusResult.reason,
445
+ structuredStatus: structured.status,
446
+ structuredReason: structured.reason,
447
+ structuredEvent: structured.event,
448
+ structuredEventAt: structured.at,
449
+ applied: merged.structuredApplied,
450
+ });
451
+ }
452
+ // Issue #3311: the length of a disagreement, said once when it ends. The line
453
+ // above is per poll and cannot carry it — see `detection-divergence.ts`.
454
+ const resolvedDivergence = (0, detection_divergence_1.trackDetectionDivergence)(worktreeId, cliToolId, resolvedInstanceId, diverging);
455
+ if (resolvedDivergence !== null) {
456
+ logger.info('detection-divergence-resolved', {
457
+ worktreeId,
458
+ cliToolId,
459
+ instanceId: resolvedInstanceId,
460
+ durationMs: resolvedDivergence.durationMs,
461
+ polls: resolvedDivergence.polls,
462
+ });
463
+ }
464
+ }
465
+ /**
466
+ * The payload of a running session (Issue #3272). Split out of
467
+ * {@link buildPayload} as it stood there, so the keys keep their order and the
468
+ * reads inside it keep theirs.
469
+ */
470
+ function buildRunningPayload({ worktreeId, cliToolId, instanceId, sessionName, published, merged, newContent, output, realtimeSnippet, totalLines, lastCapturedLine, isPromptWaiting, promptData, promptAnswerable, autoYesState, lastEnterFallback, isSelectionListActive, isPagerActive, isDismissablePanelActive, startingSince, lastKnown, lastServerResponseTimestamp, compositeKey, stopEventAt, structuredEvents, model, effort, upstreamFaultMatch, paneObstruction, composer, }) {
471
+ return {
472
+ isRunning: true,
473
+ sessionName,
474
+ cliToolId,
475
+ sessionStatus: published.status,
476
+ sessionStatusReason: published.reason,
477
+ content: newContent,
478
+ fullOutput: output,
479
+ realtimeSnippet,
480
+ lineCount: totalLines,
481
+ lastCapturedLine,
482
+ isComplete: isPromptWaiting,
483
+ isGenerating: published.thinking,
484
+ thinking: published.thinking,
485
+ // Issue #2607: named after the tool actually running. A fixed "Claude" was
486
+ // published for every agent, and `capture --json` readers took it at its word.
487
+ thinkingMessage: published.thinking ? `${(0, types_1.getCliToolDisplayName)(cliToolId)} is thinking...` : null,
488
+ isPromptWaiting,
489
+ promptData,
490
+ ...(promptAnswerable !== undefined ? { promptAnswerable } : {}),
491
+ // Issue #3184: appended next to the value it is derived from, not at the end
492
+ // — it is a reading of `promptData`, and only of it.
493
+ promptView: (0, prompt_view_1.derivePromptView)(promptData),
494
+ autoYes: {
495
+ enabled: autoYesState?.enabled ?? false,
496
+ expiresAt: autoYesState?.enabled ? autoYesState.expiresAt : null,
497
+ stopReason: autoYesState?.stopReason,
498
+ lastSuppression: (0, auto_yes_suppression_state_1.getLastPolicySuppression)(worktreeId, cliToolId, instanceId),
499
+ // Issue #1694: undefined (so the key is absent from the JSON) unless a
500
+ // stop pattern actually fired — the state clears it on every other path.
501
+ stopMatchedText: autoYesState?.stopMatchedText,
502
+ // Issue #3397: appended last, after the published fields above. Null when
503
+ // Auto-Yes never sent its Enter to an unreadable choice screen here.
504
+ lastEnterFallback,
505
+ },
506
+ isSelectionListActive,
507
+ isPagerActive,
508
+ isDismissablePanelActive,
509
+ isUnclassifiedActive: startingSince === null && merged.isUnclassifiedActive,
510
+ startingSince,
511
+ // Issue #1926: evidence strength, named the way §4 D1 names it. Not the same
512
+ // fact as `isUnclassifiedActive` (Issue #2011); published from the merged
513
+ // verdict so it agrees with the status it accompanies.
514
+ statusEvidence: published.evidence,
515
+ lastKnownStatus: lastKnown?.status ?? null,
516
+ lastKnownStatusAt: lastKnown?.at ?? null,
517
+ lastServerResponseTimestamp,
518
+ serverPollerActive: (0, auto_yes_manager_1.isPollerActive)(compositeKey),
519
+ lastStopEventAt: stopEventAt,
520
+ structuredEvents,
521
+ // Issue #1785: straight from the retention layer, unparsed. See the field
522
+ // docs on CurrentOutputPayload for why nothing is normalised on the way out.
523
+ model,
524
+ reasoningEffort: effort,
525
+ // Issue #1695: appended last on purpose — every field above is a published
526
+ // CLI contract and reordering them churns the diff for no reader's benefit.
527
+ promptDedup: (0, prompt_dedup_state_1.getPromptDedupSkips)(worktreeId, cliToolId, instanceId),
528
+ // Issue #1839: read from `realtimeSnippet`, the same rows an operator sees
529
+ // in `capture --json`, so what the field claims can be checked against what
530
+ // is printed next to it.
531
+ upstreamFault: upstreamFaultMatch
532
+ ? {
533
+ id: upstreamFaultMatch.fault.id,
534
+ matchedText: upstreamFaultMatch.matchedText,
535
+ at: Date.now(),
536
+ }
537
+ : null,
538
+ // Issue #2095: the same three keys as `upstreamFault`, read from the same
539
+ // rows, so an operator comparing the two is comparing like with like. The
540
+ // detector's `boxRight` / `rows` stay off the wire — they are how the rule
541
+ // decided, not what a caller acts on, and the payload is already a published
542
+ // contract wide enough to be hard to change.
543
+ paneObstruction: paneObstruction
544
+ ? {
545
+ id: paneObstruction.id,
546
+ matchedText: paneObstruction.matchedText,
547
+ at: Date.now(),
548
+ }
549
+ : null,
550
+ // Issue #1879: read from `output` — the RAW capture, still carrying the SGR
551
+ // attributes `capture-pane -e` fetched. Everything else in this function
552
+ // works on stripped text; this one deliberately does not, because dim is the
553
+ // only thing that separates Claude's ghost suggestion from text a human
554
+ // typed. Do not "tidy" this to read a stripped variable.
555
+ composerText: composer.state === 'content' ? composer.text : null,
556
+ composerState: composer.state,
557
+ };
558
+ }
559
+ /**
560
+ * The payload itself, with no knowledge of how its (tool, instance) pair was
561
+ * chosen. Split from {@link buildCurrentOutput} so the resolution fields are
562
+ * appended in one place instead of at both of the two return sites below.
563
+ */
564
+ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
565
+ const resolvedInstanceId = instanceId ?? cliToolId;
566
+ const manager = manager_1.CLIToolManager.getInstance();
567
+ const cliTool = manager.getTool(cliToolId);
568
+ // Issue #2886: read once, independent of `isRunning`, so both return paths
569
+ // below publish the same value the pane is (or would be) reached at.
570
+ const sessionName = cliTool.getSessionName(worktreeId, instanceId);
571
+ const { stopEventAt, eventSource, askUserQuestion, structuredEvents } = readAgentEvents(worktreeId, cliToolId, instanceId, resolvedInstanceId);
748
572
  const running = await cliTool.isRunning(worktreeId, instanceId);
749
573
  if (!running) {
750
574
  // Issue #1926: the latch describes a process, and this one is gone. Dropped
751
575
  // rather than aged out so the next session on this key starts with no
752
576
  // history instead of inheriting the last one's verdict.
753
577
  (0, status_evidence_1.forgetLastKnownStatus)((0, auto_yes_manager_1.buildCompositeKey)(worktreeId, cliToolId, instanceId));
578
+ // Issue #3300: nothing read off a frame is published here, and that includes
579
+ // `autoYes` — the key is absent, not `enabled: false`. Auto-Yes can still be
580
+ // armed for this instance (it is kept per worktree x instance, not per
581
+ // session); `GET /api/worktrees/:id/auto-yes` answers for it, which is what
582
+ // `commandmate instances` reads and what the browser re-seeds its toggles
583
+ // from (#902). Publishing the key here would make the browser's poll
584
+ // re-seed the toggle, and raise its stop-reason toast, from a session that
585
+ // is not running.
754
586
  return {
755
587
  isRunning: false,
756
588
  // Issue #3179: `beginAgentSession` runs before the pane is created, so a
@@ -822,82 +654,17 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
822
654
  // Issue #1910: saturation of the capture WINDOW is only one of the two ways
823
655
  // the cursor dies, and it is the one that never fires for the alternate-screen
824
656
  // tools — their pane is 1000 rows (claude / copilot) or 200 (opencode), so a
825
- // 10000-line window is never reached and the branch above stayed false
826
- // forever. Meanwhile the poller stores `lastCapturedLine` for them too
827
- // (`updateSessionState` is called unconditionally; only the DEDUP comparison
828
- // is gated on the tool, response-checker.ts), so after one turn the stored
829
- // value is the pane height, `slice()` starts past the last row, and `content`
830
- // — the field `commandmate capture <id>` prints — was an empty string.
831
- // `capturedLineCountIsCursor` states both conditions in one place.
832
- const lineCountIsCursor = (0, types_2.capturedLineCountIsCursor)(cliToolId, captureWindowSaturated);
833
- const newLines = lineCountIsCursor ? lines.slice(Math.max(0, lastCapturedLine)) : lines;
834
- const newContent = newLines.join('\n');
835
- const compositeKey = (0, auto_yes_manager_1.buildCompositeKey)(worktreeId, cliToolId, instanceId);
836
- const lastServerResponseTimestamp = (0, auto_yes_manager_1.getLastServerResponseTimestamp)(compositeKey);
837
- const lastOutputTimestamp = lastServerResponseTimestamp ? new Date(lastServerResponseTimestamp) : undefined;
838
- const rawStatusResult = (0, status_detector_1.detectSessionStatus)(output, cliToolId, lastOutputTimestamp);
839
- // Issue #3179: a launch in progress. The frame is a shell prompt and the
840
- // launch line, or a dialog the launch is about to answer, and none of the
841
- // flags below may be read off it: the floor verdict raised the Navigate pad,
842
- // the trust dialog raised the selection list / prompt sheet. A dialog that
843
- // outstays the launch's own answer releases the record (a login, an unknown
844
- // dialog), and from then on this is the ordinary verdict again.
845
- const startingSince = (0, session_starting_state_1.observeSessionStartingFrame)(worktreeId, cliToolId, instanceId, rawStatusResult.status === 'waiting' || rawStatusResult.hasActivePrompt);
846
- const statusResult = startingSince === null
847
- ? rawStatusResult
848
- : (0, session_starting_state_1.startingStatusResult)(rawStatusResult);
849
- // Issue #1912: every `running` reason that means "the agent is producing
850
- // output", not just `thinking_indicator`. opencode answers
851
- // `opencode_processing_indicator` for its `esc interrupt` footer, which is
852
- // the only signal it gives between the submitted prompt and the first
853
- // transcript row — on a scraper-only session that stretch showed no
854
- // thinking indicator at all.
855
- const thinking = (0, status_detector_1.isGeneratingStatus)(statusResult);
856
- const scraperPromptWaiting = statusResult.hasActivePrompt;
857
- const isSelectionListActive = statusResult.status === 'waiting' && status_detector_1.SELECTION_LIST_REASONS.has(statusResult.reason);
858
- const isPagerActive = statusResult.reason === status_detector_1.STATUS_REASON.CODEX_PAGER;
859
- // Issue #2369: an overlay whose ONLY exit is the dismiss — Command Code's
860
- // `/usage` panel, whose last row is `Press Esc to close`.
861
- //
862
- // A THIRD flag rather than a member of `SELECTION_LIST_REASONS`, and the
863
- // separation is the fix rather than a nicety: `isSelectionListActive` is what
864
- // the arrow pad is drawn from, and this screen has no highlight to move. Nor
865
- // is it `isUnclassifiedActive` any more — the detector answers `waiting` for
866
- // it, and `isUnclassifiedFrame` only ever says yes to `running` — which is
867
- // exactly what stops the chat surface offering the eighteen-button
868
- // hatch + answer-key card to a panel that accepts one key.
869
- //
870
- // `status === 'waiting'` is checked as well as the reason, the same shape
871
- // `isSelectionListActive` above uses, so a future producer that publishes the
872
- // token with some other status cannot silently turn the card into an Esc
873
- // button.
874
- const isDismissablePanelActive = statusResult.status === 'waiting'
875
- && statusResult.reason === status_detector_1.STATUS_REASON.COMMAND_CODE_DISMISSABLE_PANEL;
876
- // Issue #1497: the detection-independent nav hatch (#1017/#1494) is gated on
877
- // isUnclassifiedActive. A static, unrecognized TUI overlay (e.g. Claude `/help`)
878
- // whose frame stops changing degrades from `running`/`default` to
879
- // `no_recent_output` once the Auto-Yes poller has stamped lastOutputTimestamp
880
- // (its sole writer, auto-yes-poller.ts). That is still an interactive-but-
881
- // unclassified frame — a real idle prompt (`❯`) is classified earlier as
882
- // `input_prompt`, never as `no_recent_output` — so treat the timed-out fallback
883
- // as unclassified too and keep the hatch open instead of stranding the user.
884
- // Issue #1924, §4 D1 decision 2: stated as evidence. Issue #1927 moved that
885
- // PRODUCER into the detector, because only the detector knows which rule
886
- // answered — `input_prompt` is positive for a tool whose idle rule vouched for
887
- // the frame and `'none'` for one whose rule declined, with the same status and
888
- // the same reason on the wire.
889
- const evidence = statusResult.evidence;
890
- // Issue #2011: the flag is NOT that fact, and deriving it from `evidence` is
891
- // the regression this Issue fixes. `'none'` is "I could not prove this pane is
892
- // idle" — an ordinary Claude composer with no completion marker above it
893
- // qualifies, and 7 of 8 live idle panes did on 2026-08-24. The flag's three
894
- // consumers all ask the older, narrower question instead: `TerminalEscapeHatch`
895
- // opens on a frame a human has to drive by hand, `wait` suppresses its
896
- // completion check and eventually exits 10 on one, and `unclassified_frames`
897
- // records it as a detection failure worth capturing as a fixture. That is
898
- // `isUnclassifiedFrame` — the reason vocabulary, not the strength of the
899
- // evidence behind a readable verdict.
900
- const isUnclassifiedActive = (0, status_evidence_1.isUnclassifiedFrame)(statusResult.status, statusResult.reason);
657
+ // 10000-line window is never reached and the branch above stayed false
658
+ // forever. Meanwhile the poller stores `lastCapturedLine` for them too
659
+ // (`updateSessionState` is called unconditionally; only the DEDUP comparison
660
+ // is gated on the tool, response-checker.ts), so after one turn the stored
661
+ // value is the pane height, `slice()` starts past the last row, and `content`
662
+ // — the field `commandmate capture <id>` prints — was an empty string.
663
+ // `capturedLineCountIsCursor` states both conditions in one place.
664
+ const lineCountIsCursor = (0, types_1.capturedLineCountIsCursor)(cliToolId, captureWindowSaturated);
665
+ const newLines = lineCountIsCursor ? lines.slice(Math.max(0, lastCapturedLine)) : lines;
666
+ const newContent = newLines.join('\n');
667
+ const { compositeKey, lastServerResponseTimestamp, statusResult, startingSince, thinking, scraperPromptWaiting, isSelectionListActive, isPagerActive, isDismissablePanelActive, evidence, isUnclassifiedActive, } = judgeFrame(worktreeId, cliToolId, instanceId, output);
901
668
  // Issue #1723: the two-layer merge. Everything above this line is the string
902
669
  // analysis, unchanged and still the only source on a machine where no hook
903
670
  // ever fires — `getStructuredSessionState` answers null there, and
@@ -914,8 +681,18 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
914
681
  // the scraper's to describe. What it deliberately does NOT do is complete a
915
682
  // `commandmate wait` — see SCRAPER_COMPLETION_POLLS, and #1839's measurement
916
683
  // of a 529 storm returning Claude to exactly this frame having run nothing.
917
- (0, agent_event_state_1.observeScraperCompletionEvidence)(worktreeId, cliToolId, instanceId, statusResult.status === 'ready' && evidence === 'positive');
918
- const structured = (0, agent_event_state_1.getStructuredSessionState)(worktreeId, cliToolId, instanceId);
684
+ //
685
+ // Issue #3337: and, for codex, not while its hooks speak for the pane,
686
+ // unless the frame shows the turn was interrupted (`■ Conversation
687
+ // interrupted`, after which no `Stop` comes). A misread frame of a live codex
688
+ // turn closed it here and published `ready` mid-turn; the hook's own `Stop`
689
+ // is the end of the turn there, and `stale` stays the bound on a lost `Stop`.
690
+ // Other tools, Claude included, are closed by the screen as before.
691
+ // The rule is `hook-turn-hold`'s, which the relay's readiness check reads too.
692
+ (0, agent_event_state_1.observeScraperCompletionEvidence)(worktreeId, cliToolId, instanceId, statusResult.status === 'ready' && evidence === 'positive', undefined, (0, hook_turn_hold_1.screenMayEndTurn)(structuredEvents.source.kind, cliToolId, output));
693
+ // Issue #3377: on a frame that shows the hook turn was abandoned, the record's
694
+ // `running` is not published — the rule the list reads too.
695
+ const structured = (0, hook_turn_hold_1.structuredStateForFrame)(worktreeId, cliToolId, instanceId, structuredEvents.source.kind, output);
919
696
  // Issue #1725: the open-dialog half of the same merge, resolved before the
920
697
  // status merge because it decides one of its inputs. The rule itself — the
921
698
  // asymmetric release and the OR below — lives in `prompt-waiting-composition`
@@ -948,10 +725,10 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
948
725
  // cannot disagree about when this read is needed — and every other poll (an
949
726
  // idle pane, a turn the agent has opened, a tool that reports its own start)
950
727
  // costs exactly what it did before this Issue, which is nothing.
951
- const lastPromptAt = staleReadyCandidate(scraperVerdict, structured, structuredEvents)
952
- ? readNewestPromptAt(db, worktreeId, cliToolId, resolvedInstanceId)
728
+ const lastPromptAt = (0, structured_status_merge_1.staleReadyCandidate)(scraperVerdict, structured, structuredEvents)
729
+ ? (0, structured_status_merge_1.readNewestPromptAt)(db, worktreeId, cliToolId, resolvedInstanceId)
953
730
  : null;
954
- const merged = mergeStructuredStatus(scraperVerdict, structured, promptWaiting,
731
+ const merged = (0, structured_status_merge_1.mergeStructuredStatus)(scraperVerdict, structured, promptWaiting,
955
732
  // Issue #2011: the same record `structuredEvents` publishes, read once. The
956
733
  // merge needs `closedBy` / `closedAt` / `openedAt`, which
957
734
  // `StructuredSessionState` folds away into `hook_stop` — and the folded form
@@ -966,133 +743,17 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
966
743
  // Issue #3179: not while a launch is in progress. The scraper half is already
967
744
  // neutralised above; this also drops a structured wait the launch inherited.
968
745
  const isPromptWaiting = startingSince === null && promptResolution.waiting;
969
- // Issue #1726: the agent's own account of what it asked. It contributes only
970
- // where some other layer has already established that a dialog is on screen —
971
- // this record decides no status of its own, because Claude emits nothing at
972
- // all while an AskUserQuestion picker is up (§5.6) and a record that asserted
973
- // `waiting` from the invocation would go on asserting it long after a human
974
- // answered in the terminal.
975
- // Issue #2040 moved the read to the top of this function (the decision entries
976
- // publish the same episode's choices) and this line now re-uses it, so the two
977
- // surfaces cannot describe different instants.
978
- const scraperPromptData = statusResult.promptDetection.promptData;
979
- const correctedPromptData = scraperPromptData && askUserQuestion
980
- ? (0, ask_user_question_prompt_1.applyAskUserQuestion)(scraperPromptData, askUserQuestion.spec)
981
- : null;
982
- // The degraded form, for a dialog only the structured layer can see. Enriched
983
- // with the question text when one is in flight — that turns "a dialog is open
984
- // and nobody could read it" into "a dialog is open and here is what it asks".
985
- //
986
- // Issue #1898 adds the replies the dialog accepts, for the sources that can be
987
- // answered without touching the pane. The gate is `eventIdentity`: a source
988
- // that publishes a per-decision id is a source whose approval can be answered
989
- // by that id, which is what makes an option NUMBER here mean something other
990
- // than a line on a screen nobody parsed. `source === 'notification'` narrows
991
- // it to a dialog the agent actually reported — a `permission-request` record
992
- // is a prediction, and a question is answered with a choice rather than with
993
- // one of these three verdicts.
994
- //
995
- // Issue #2031 adds the fourth conjunct and folds the whole gate into ONE
996
- // value. The three verdicts and the id they are delivered to are now derived
997
- // from the same expression, so they cannot be published apart — and "apart"
998
- // is not hypothetical, it is the state #1932 shipped: options were published
999
- // on the capability alone while `decisionId` was published nowhere at all, so
1000
- // the panel drew no buttons and the only way out of an opencode approval in a
1001
- // browser was the arrow-key safety net. Options WITHOUT an id is the worse
1002
- // half of that pair — the numbers would reach the keystroke path, where a
1003
- // bare "1" selects whatever the picker happens to be highlighting (#1681).
1004
- const addressableDecisionId = promptWaiting !== null &&
1005
- promptWaiting.source === 'notification' &&
1006
- eventSource.capabilities.eventIdentity === 'permission-id' &&
1007
- (0, structured_prompt_1.isAddressableDecision)(promptWaiting.decisionId)
1008
- ? promptWaiting.decisionId
1009
- : null;
1010
- // Issue #2100 splits the two halves of #2031's single expression, because an
1011
- // addressable decision is now of two KINDS. The id is published for both — it
1012
- // is what `POST /respond` names, and a question needs it exactly as much as an
1013
- // approval does — but the three verdicts belong to an approval alone.
1014
- //
1015
- // Publishing both for a question is not a cosmetic error: it is precisely what
1016
- // #2039's third gate refuses. `readPromptQuestionChoices` returns null the
1017
- // moment `decisionOptions` is non-empty, so a question carrying them would
1018
- // draw `Allow once / Allow always / Reject` over the agent's own choices, and
1019
- // a verdict sent to a question is refused at the source
1020
- // (`question-needs-answer-verdict`). The kind is recovered from the record the
1021
- // same way `structuredEvents.pendingDecisions[].kind` is, through the one
1022
- // reader in `pending-decision-kind`.
1023
- const addressesQuestion = promptWaiting !== null && (0, pending_decision_kind_1.pendingDecisionKind)(promptWaiting.toolName) === 'question';
1024
- const decisionOptions = addressableDecisionId !== null && !addressesQuestion
1025
- ? // Issue #2951: in the tool's own words (OpenCode V2: `Always allow`).
1026
- (0, structured_prompt_1.structuredDecisionOptionsFor)(cliToolId)
1027
- : null;
1028
- const structuredFacts = promptWaiting === null
1029
- ? null
1030
- : {
1031
- ...promptWaiting,
1032
- askUserQuestion: summarizeAskUserQuestion(askUserQuestion),
1033
- decisionOptions,
1034
- // Explicit, though the spread above already carries a `decisionId`
1035
- // off the record: the spread's copy is the raw one, and what may be
1036
- // published is the GATED one. Letting the record's value through
1037
- // would put an id on a payload whose verdicts were withheld, which is
1038
- // the biconditional this Issue exists to hold.
1039
- decisionId: addressableDecisionId,
1040
- // Gated on the same value, for a reason of its own: `patterns` is
1041
- // what the `Allow always` BUTTON grants, so publishing it where no
1042
- // button is drawn adds a rule list to a panel that is telling the
1043
- // user to go and answer in the terminal. Every source but opencode
1044
- // therefore keeps the exact payload it had before this Issue, plus
1045
- // the one `decisionId: null`.
1046
- //
1047
- // Issue #2100 moves the gate onto `decisionOptions` rather than the
1048
- // id, which is the same value for every approval and the honest one
1049
- // for a question: a question draws no `Allow always`, so it may not
1050
- // carry the rules one would have saved. (`reportQuestionPending`
1051
- // records `patterns: null` anyway; the two agree by construction now
1052
- // instead of by coincidence.)
1053
- patterns: decisionOptions !== null ? promptWaiting.patterns : null,
1054
- };
1055
- const promptData = startingSince !== null
1056
- ? null
1057
- : scraperPromptWaiting
1058
- ? correctedPromptData ??
1059
- scraperPromptData ??
1060
- (structuredFacts ? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts) : null)
1061
- : structuredFacts
1062
- ? (0, structured_prompt_1.buildStructuredPromptData)(worktreeId, structuredFacts)
1063
- : null;
1064
- // Issue #2870: whether `/prompt-response` would answer the prompt published
1065
- // above, read by the SAME function it re-verifies with. In #2868 `promptData`
1066
- // came off the generic parser alone, the route refused it, and the UI's Send
1067
- // did nothing. Only for a parser-read prompt: the structured form (hook
1068
- // `decisionId`, degraded) is answered by id or not by number at all, so the
1069
- // key is left out there, as it is when no prompt is up. `isPromptWaiting` /
1070
- // `promptData` / `sessionStatus` are untouched — `wait`'s exit 10, Auto-Yes
1071
- // and push notifications keep reading exactly what they read before.
1072
- const promptAnswerable = scraperPromptWaiting && (correctedPromptData ?? scraperPromptData)
1073
- ? (0, auto_yes_dialog_gate_1.assessPromptAnswerability)(cliToolId, output).refusal === null
1074
- : undefined;
1075
- // Issue #1723 §3: the field data this Epic is being built on. Every line is
1076
- // one poll where the screen and the agent disagreed about what the agent was
1077
- // doing, which is the only way to answer "how wrong was the scraper?" with a
1078
- // number instead of an anecdote. Emitted only on disagreement — a session
1079
- // where the two layers agree is silent — and including the disagreements this
1080
- // merge deliberately does NOT act on (`applied: false`), because those are
1081
- // exactly the cases the next Issues in the Epic have to decide about.
1082
- if (structured !== null && structured.status !== statusResult.status) {
1083
- logger.info('detection-divergence', {
1084
- worktreeId,
1085
- cliToolId,
1086
- instanceId: resolvedInstanceId,
1087
- scraperStatus: statusResult.status,
1088
- scraperReason: statusResult.reason,
1089
- structuredStatus: structured.status,
1090
- structuredReason: structured.reason,
1091
- structuredEvent: structured.event,
1092
- structuredEventAt: structured.at,
1093
- applied: merged.structuredApplied,
1094
- });
1095
- }
746
+ const { structuredFacts, promptData, promptAnswerable } = composePromptData({
747
+ worktreeId,
748
+ cliToolId,
749
+ output,
750
+ statusResult,
751
+ startingSince,
752
+ promptWaiting,
753
+ askUserQuestion,
754
+ eventSource,
755
+ });
756
+ logDetectionDivergence(worktreeId, cliToolId, resolvedInstanceId, statusResult, structured, merged);
1096
757
  // Issue #1839 defined this window and Issue #2095 reuses it, so it is computed
1097
758
  // once, here, above the first reader. The 100 rows are also what the payload
1098
759
  // publishes, which is the property both fields rest on: what they claim can be
@@ -1136,7 +797,7 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
1136
797
  // the shell and the launch line — so no row, and the run starts afresh after.
1137
798
  const unclassifiedVerdict = (0, unclassified_frame_tracker_1.observeUnclassifiedFrame)(compositeKey, merged.isUnclassifiedActive && !answerableOverAgentApi && startingSince === null);
1138
799
  if (unclassifiedVerdict.shouldRecord) {
1139
- recordUnclassifiedFrame(db, {
800
+ (0, current_output_history_writers_1.recordUnclassifiedFrame)(db, {
1140
801
  worktreeId,
1141
802
  cliToolId,
1142
803
  instanceId: resolvedInstanceId,
@@ -1185,7 +846,7 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
1185
846
  !promptWaiting.recorded &&
1186
847
  !answerableOverAgentApi) {
1187
848
  (0, agent_event_state_1.markStructuredPromptRecorded)(worktreeId, cliToolId, instanceId);
1188
- recordStructuredPrompt(db, {
849
+ (0, current_output_history_writers_1.recordStructuredPrompt)(db, {
1189
850
  worktreeId,
1190
851
  cliToolId,
1191
852
  instanceId: resolvedInstanceId,
@@ -1231,353 +892,39 @@ async function buildPayload(db, worktreeId, cliToolId, instanceId) {
1231
892
  evidence: published.evidence,
1232
893
  });
1233
894
  const lastKnown = (0, status_evidence_1.getLastKnownStatus)(compositeKey);
1234
- return {
1235
- isRunning: true,
1236
- sessionName,
895
+ return buildRunningPayload({
896
+ worktreeId,
1237
897
  cliToolId,
1238
- sessionStatus: published.status,
1239
- sessionStatusReason: published.reason,
1240
- content: newContent,
1241
- fullOutput: output,
898
+ instanceId,
899
+ sessionName,
900
+ published,
901
+ merged,
902
+ newContent,
903
+ output,
1242
904
  realtimeSnippet,
1243
- lineCount: totalLines,
905
+ totalLines,
1244
906
  lastCapturedLine,
1245
- isComplete: isPromptWaiting,
1246
- isGenerating: published.thinking,
1247
- thinking: published.thinking,
1248
- // Issue #2607: named after the tool actually running. A fixed "Claude" was
1249
- // published for every agent, and `capture --json` readers took it at its word.
1250
- thinkingMessage: published.thinking ? `${(0, types_2.getCliToolDisplayName)(cliToolId)} is thinking...` : null,
1251
907
  isPromptWaiting,
1252
908
  promptData,
1253
- ...(promptAnswerable !== undefined ? { promptAnswerable } : {}),
1254
- // Issue #3184: appended next to the value it is derived from, not at the end
1255
- // — it is a reading of `promptData`, and only of it.
1256
- promptView: (0, prompt_view_1.derivePromptView)(promptData),
1257
- autoYes: {
1258
- enabled: autoYesState?.enabled ?? false,
1259
- expiresAt: autoYesState?.enabled ? autoYesState.expiresAt : null,
1260
- stopReason: autoYesState?.stopReason,
1261
- lastSuppression: (0, auto_yes_suppression_state_1.getLastPolicySuppression)(worktreeId, cliToolId, instanceId),
1262
- // Issue #1694: undefined (so the key is absent from the JSON) unless a
1263
- // stop pattern actually fired — the state clears it on every other path.
1264
- stopMatchedText: autoYesState?.stopMatchedText,
1265
- },
909
+ promptAnswerable,
910
+ autoYesState,
911
+ // Issue #3397: judged against the screen-read prompt only — the one
912
+ // `promptAnswerable` is published for, and the one the poller sent its
913
+ // Enter to. A structured (hook / degraded) prompt is never that screen.
914
+ lastEnterFallback: (0, auto_yes_enter_fallback_1.publishEnterFallback)((0, auto_yes_enter_fallback_1.getLastEnterFallback)(worktreeId, cliToolId, instanceId), promptAnswerable !== undefined ? statusResult.promptDetection.promptData : null),
1266
915
  isSelectionListActive,
1267
916
  isPagerActive,
1268
917
  isDismissablePanelActive,
1269
- isUnclassifiedActive: startingSince === null && merged.isUnclassifiedActive,
1270
918
  startingSince,
1271
- // Issue #1926: the same fact `isUnclassifiedActive` carries, named the way
1272
- // §4 D1 names it. Published from the merged verdict so the two cannot
1273
- // disagree on the wire.
1274
- statusEvidence: published.evidence,
1275
- lastKnownStatus: lastKnown?.status ?? null,
1276
- lastKnownStatusAt: lastKnown?.at ?? null,
919
+ lastKnown,
1277
920
  lastServerResponseTimestamp,
1278
- serverPollerActive: (0, auto_yes_manager_1.isPollerActive)(compositeKey),
1279
- lastStopEventAt: stopEventAt,
921
+ compositeKey,
922
+ stopEventAt,
1280
923
  structuredEvents,
1281
- // Issue #1785: straight from the retention layer, unparsed. See the field
1282
- // docs on CurrentOutputPayload for why nothing is normalised on the way out.
1283
924
  model,
1284
- reasoningEffort: effort,
1285
- // Issue #1695: appended last on purpose — every field above is a published
1286
- // CLI contract and reordering them churns the diff for no reader's benefit.
1287
- promptDedup: (0, prompt_dedup_state_1.getPromptDedupSkips)(worktreeId, cliToolId, instanceId),
1288
- // Issue #1839: read from `realtimeSnippet`, the same rows an operator sees
1289
- // in `capture --json`, so what the field claims can be checked against what
1290
- // is printed next to it.
1291
- upstreamFault: upstreamFaultMatch
1292
- ? {
1293
- id: upstreamFaultMatch.fault.id,
1294
- matchedText: upstreamFaultMatch.matchedText,
1295
- at: Date.now(),
1296
- }
1297
- : null,
1298
- // Issue #2095: the same three keys as `upstreamFault`, read from the same
1299
- // rows, so an operator comparing the two is comparing like with like. The
1300
- // detector's `boxRight` / `rows` stay off the wire — they are how the rule
1301
- // decided, not what a caller acts on, and the payload is already a published
1302
- // contract wide enough to be hard to change.
1303
- paneObstruction: paneObstruction
1304
- ? {
1305
- id: paneObstruction.id,
1306
- matchedText: paneObstruction.matchedText,
1307
- at: Date.now(),
1308
- }
1309
- : null,
1310
- // Issue #1879: read from `output` — the RAW capture, still carrying the SGR
1311
- // attributes `capture-pane -e` fetched. Everything else in this function
1312
- // works on stripped text; this one deliberately does not, because dim is the
1313
- // only thing that separates Claude's ghost suggestion from text a human
1314
- // typed. Do not "tidy" this to read a stripped variable.
1315
- composerText: composer.state === 'content' ? composer.text : null,
1316
- composerState: composer.state,
1317
- };
1318
- }
1319
- function newChatTurnProgressState() {
1320
- return {
1321
- version: 0,
1322
- askedAt: Number.NEGATIVE_INFINITY,
1323
- turnKey: null,
1324
- body: null,
1325
- loggedOutcome: null,
1326
- loggedTurnKey: null,
1327
- };
1328
- }
1329
- /**
1330
- * On `globalThis` for the reason every shared map in this subsystem is (#1736):
1331
- * under `next dev` the poller's bundle and the opencode subscription's bundle
1332
- * would each get a private copy, and two producers throttling against two
1333
- * different maps is no throttle at all.
1334
- */
1335
- const chatTurnProgressState = (globalThis.__chatTurnProgressState ??= new Map());
1336
- function chatTurnProgressKey(target) {
1337
- return `${target.worktreeId}:${target.cliToolId}:${target.instanceId ?? target.cliToolId}`;
1338
- }
1339
- /** Forget every instance's progress bookkeeping. Test seam. */
1340
- function resetChatTurnProgressState() {
1341
- chatTurnProgressState.clear();
1342
- }
1343
- /**
1344
- * Say once, at info, what happened to this instance's progress frames (Issue #2248).
1345
- *
1346
- * ## Why info, and why this file had none
1347
- *
1348
- * Every outcome here was `logger.debug`, including the two that mean the reader
1349
- * is watching a blank space: a push that threw, and a room with no subscribers.
1350
- * Issue #2248 was opened after a live session where the body reached the browser
1351
- * — or did not — and the server logs could not answer which, because debug is
1352
- * off in the builds people run. An unobservable push is a feature that can only
1353
- * be debugged by reproducing it.
1354
- *
1355
- * ## Why once
1356
- *
1357
- * The publisher runs on EVERY poll tick of a generating session. Logging each
1358
- * tick at info would put a line per second per agent into the operator's log and
1359
- * make the level useless, so this collapses a run of identical ticks into its
1360
- * first: the outcome and the turn it happened on are the identity, and a repeat
1361
- * of the pair says nothing the first line did not.
1362
- *
1363
- * The consequences of that identity, both deliberate:
1364
- *
1365
- * - **a new turn always logs**, because `turnKey` is part of the pair. That is
1366
- * the acceptance criterion — one info line per turn — and the reason
1367
- * `loggedTurnKey` exists at all rather than a bare boolean;
1368
- * - **a CHANGE of outcome always logs**, so "the subscriber left" and "it
1369
- * started failing" are both visible the tick they happen, without the
1370
- * steady state that follows repeating them.
1371
- *
1372
- * `no-subscribers` and `failed` carry no turn key — neither knows one; the first
1373
- * returns before the source is asked and the second is why there is no frame —
1374
- * so for them the pair is `(outcome, null)` and the run collapses to one line
1375
- * until something else happens.
1376
- */
1377
- function logChatTurnProgressOutcome(target, outcome, turnKey, extra = {}) {
1378
- const key = chatTurnProgressKey(target);
1379
- const state = chatTurnProgressState.get(key) ?? newChatTurnProgressState();
1380
- chatTurnProgressState.set(key, state);
1381
- if (state.loggedOutcome === outcome && state.loggedTurnKey === turnKey)
1382
- return;
1383
- state.loggedOutcome = outcome;
1384
- state.loggedTurnKey = turnKey;
1385
- // Written out rather than interpolated: these strings are what an operator
1386
- // greps the log for, and an interpolated name cannot be found in the source.
1387
- const message = outcome === 'published'
1388
- ? 'chat-turn-progress-published'
1389
- : outcome === 'no-subscribers'
1390
- ? 'chat-turn-progress-no-subscribers'
1391
- : 'chat-turn-progress-failed';
1392
- logger.info(message, {
1393
- worktreeId: target.worktreeId,
1394
- cliToolId: target.cliToolId,
1395
- instanceId: target.instanceId ?? target.cliToolId,
1396
- turnKey,
1397
- ...extra,
925
+ effort,
926
+ upstreamFaultMatch,
927
+ paneObstruction,
928
+ composer,
1398
929
  });
1399
930
  }
1400
- /**
1401
- * Decide whether this instance has a new progress frame, and build it.
1402
- *
1403
- * The single generator both tools go through, on the same argument
1404
- * {@link buildCurrentOutput} makes for the terminal payload: two producers of one
1405
- * wire shape drift, and the drift is invisible because each of them is
1406
- * individually correct. Three rules, in this order, and each of them is a way
1407
- * this feature goes wrong if it is missing:
1408
- *
1409
- * 1. **Throttle.** At most one *ask* per {@link CHAT_TURN_PROGRESS_MIN_INTERVAL_MS}
1410
- * per instance. It gates the ask rather than the send because the source is
1411
- * the expensive half (see {@link ChatTurnProgressSource}); a gate on the send
1412
- * alone would re-read a 4 MiB transcript to discover it had not changed.
1413
- * 2. **No-change suppression.** Same turn, same body → no frame. A reply that
1414
- * has stopped growing must not keep waking every subscribed browser.
1415
- * 3. **Monotonic version**, per instance and NOT per turn. A client drops
1416
- * anything at or below what it has already rendered, and restarting the
1417
- * counter on a new turn would make the first frame of turn N+1 look stale
1418
- * against the last frame of turn N.
1419
- *
1420
- * The body is bounded by {@link truncateChatTurnProgressBody}, whose cut is
1421
- * folded into `partial` together with the reader's own — see
1422
- * {@link ChatTurnProgressEvent.partial} for why one flag answers both.
1423
- *
1424
- * Never throws: a source that throws is a turn with nothing to show, not a
1425
- * broken poll tick.
1426
- *
1427
- * @param target - The instance
1428
- * @param source - Asked only when the throttle allows it
1429
- * @param now - Epoch ms; injected by the tests
1430
- * @returns The frame to broadcast, or null when there is nothing new to say
1431
- */
1432
- async function buildChatTurnProgress(target, source, now = Date.now()) {
1433
- const key = chatTurnProgressKey(target);
1434
- const state = chatTurnProgressState.get(key) ?? newChatTurnProgressState();
1435
- if (now - state.askedAt < types_1.CHAT_TURN_PROGRESS_MIN_INTERVAL_MS)
1436
- return null;
1437
- state.askedAt = now;
1438
- chatTurnProgressState.set(key, state);
1439
- let draft;
1440
- try {
1441
- draft = await source();
1442
- }
1443
- catch {
1444
- // The readers already promise never to throw; this is the belt for the
1445
- // dynamic import that reaches them.
1446
- return null;
1447
- }
1448
- if (!draft || draft.body.length === 0)
1449
- return null;
1450
- const bounded = (0, types_1.truncateChatTurnProgressBody)(draft.body);
1451
- if (draft.turnKey === state.turnKey && bounded.body === state.body)
1452
- return null;
1453
- state.turnKey = draft.turnKey;
1454
- state.body = bounded.body;
1455
- state.version += 1;
1456
- return {
1457
- type: types_1.CHAT_TURN_PROGRESS_EVENT_TYPE,
1458
- worktreeId: target.worktreeId,
1459
- cliToolId: target.cliToolId,
1460
- instanceId: target.instanceId ?? target.cliToolId,
1461
- turnKey: draft.turnKey,
1462
- body: bounded.body,
1463
- partial: bounded.truncated || draft.partial === true,
1464
- version: state.version,
1465
- done: false,
1466
- };
1467
- }
1468
- /**
1469
- * Build a progress frame and push it to the worktree room.
1470
- *
1471
- * Subscribers are checked FIRST, before the throttle state is touched and long
1472
- * before the source is asked, so a session nobody is watching costs one map
1473
- * lookup per tick — the same bargain `broadcastTerminalSnapshot` strikes, and
1474
- * the reason a 4 MiB transcript read does not happen for every claude session on
1475
- * the machine.
1476
- *
1477
- * The `ws-server` import is dynamic so this module's *other* callers — the
1478
- * `/current-output` route and `commandmate capture` — do not pull the WebSocket
1479
- * server into their graph just by building a payload.
1480
- *
1481
- * Never throws; a failed push is a moment of staleness on a surface that has the
1482
- * settled row coming anyway.
1483
- *
1484
- * @returns Whether a frame was broadcast
1485
- */
1486
- async function emitChatTurnProgress(target, source, now = Date.now()) {
1487
- try {
1488
- const { broadcast, hasRoomSubscribers } = await Promise.resolve().then(() => __importStar(require('../../lib/ws-server')));
1489
- if (!hasRoomSubscribers(target.worktreeId)) {
1490
- // Issue #2248. Reported, not silent: "the body never reached the browser"
1491
- // and "nobody was listening" look identical from a screenshot, and this is
1492
- // the only place that can tell them apart.
1493
- logChatTurnProgressOutcome(target, 'no-subscribers', null);
1494
- return false;
1495
- }
1496
- const event = await buildChatTurnProgress(target, source, now);
1497
- // Not logged: null is the throttle, an unchanged body, or a turn with no
1498
- // text yet — the quiet, correct majority of ticks.
1499
- if (!event)
1500
- return false;
1501
- broadcast(target.worktreeId, event);
1502
- logChatTurnProgressOutcome(target, 'published', event.turnKey, {
1503
- version: event.version,
1504
- bodyLength: event.body.length,
1505
- partial: event.partial,
1506
- });
1507
- return true;
1508
- }
1509
- catch (error) {
1510
- logChatTurnProgressOutcome(target, 'failed', null, {
1511
- error: error instanceof Error ? error.message : String(error),
1512
- });
1513
- return false;
1514
- }
1515
- }
1516
- /**
1517
- * Ask the tool that has a transcript for the body of its open turn (Issue #2199).
1518
- *
1519
- * claude only, and that is a statement about which tools have a *pull* reader
1520
- * rather than a list to extend by hand. opencode's body arrives on its own SSE
1521
- * stream and is published from `sources/opencode/history` at the moment a part
1522
- * lands — polling for it here would be a second producer of the same frames.
1523
- * codex and antigravity have neither, and stay on the indicator (#2197 / #2198).
1524
- *
1525
- * The reader is imported dynamically so `sources/claude/history` — and through
1526
- * it `fs/promises`, the session-pointer latch and `user-turn-recorder` — stays
1527
- * out of the module graph of the `/current-output` route and of `commandmate
1528
- * capture`, both of which reach this file for a payload and nothing else.
1529
- *
1530
- * ## Which half the caller waits for (Issue #2248)
1531
- *
1532
- * The caller awaits the CHEAP half — the worktree row and "is anybody watching"
1533
- * — and not the expensive one. That split is what `void` was protecting in the
1534
- * first place: the 4 MiB transcript read, the JSONL parse, the render and the
1535
- * broadcast still run detached, so no poll tick and no WebSocket snapshot waits
1536
- * on them.
1537
- *
1538
- * What awaiting the prelude buys is the reason this Issue exists: the line that
1539
- * says whether anybody received the frame is written while the tick it belongs
1540
- * to is still the tick in progress. Left inside the detached half it landed
1541
- * wherever the event loop happened to get to it, next to some unrelated
1542
- * request's output, which is not a log an operator can read backwards.
1543
- *
1544
- * Never throws: `emitChatTurnProgress` swallows, and so does this.
1545
- */
1546
- async function publishChatTurnProgress(db, worktreeId, cliToolId, instanceId) {
1547
- if (cliToolId !== 'claude')
1548
- return;
1549
- const target = { worktreeId, cliToolId, instanceId };
1550
- try {
1551
- // The slug the transcript directory is named after is a function of the
1552
- // worktree's path, so the row is what makes the file findable at all. Read
1553
- // before the throttle only because it is a single indexed lookup against a
1554
- // connection this function was handed; everything expensive is behind the
1555
- // gate.
1556
- const { getWorktreeById } = await Promise.resolve().then(() => __importStar(require('../../lib/db/worktree-db')));
1557
- const worktreePath = getWorktreeById(db, worktreeId)?.path;
1558
- if (!worktreePath)
1559
- return;
1560
- // Issue #2248. Hoisted out of `emitChatTurnProgress` — which still makes the
1561
- // same check for its other caller — so the answer, and the line that reports
1562
- // it, are settled before this function returns. Costs one Map lookup on the
1563
- // sessions nobody is watching, which is the bargain that was already being
1564
- // struck one frame later.
1565
- const { hasRoomSubscribers } = await Promise.resolve().then(() => __importStar(require('../../lib/ws-server')));
1566
- if (!hasRoomSubscribers(worktreeId)) {
1567
- logChatTurnProgressOutcome(target, 'no-subscribers', null);
1568
- return;
1569
- }
1570
- // Detached on purpose: everything past this point is the transcript read.
1571
- void emitChatTurnProgress(target, async () => {
1572
- const { readClaudeTurnProgress } = await Promise.resolve().then(() => __importStar(require('../../lib/hooks/sources/claude/history')));
1573
- return readClaudeTurnProgress({ worktreeId, cliToolId, instanceId: instanceId ?? cliToolId }, { worktreePath });
1574
- });
1575
- }
1576
- catch (error) {
1577
- // Issue #2248: the reader's half of the same failure — the worktree row or
1578
- // one of the dynamic imports — on the same once-per-run gate as the push's.
1579
- logChatTurnProgressOutcome(target, 'failed', null, {
1580
- error: error instanceof Error ? error.message : String(error),
1581
- });
1582
- }
1583
- }