commandmate 0.44.1 → 0.44.3

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