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
@@ -4,1638 +4,156 @@
4
4
  * Shared between response-poller.ts and API routes
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.findOpencodeV2DialogTitle = exports.OPENCODE_V2_DIALOG_TITLE_PATTERN = exports.OPENCODE_V2_THINKING_PATTERN = exports.OPENCODE_V2_FOOTER_PATTERN = exports.OPENCODE_V2_IDLE_COMPOSER_PATTERN = exports.OPENCODE_SKIP_PATTERNS = exports.OPENCODE_SEPARATOR_PATTERN = exports.CLAUDE_SELECTION_LIST_FOOTER = exports.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN = exports.OPENCODE_SELECTION_LIST_PATTERN = exports.OPENCODE_FOOTER_STATUS_PATTERN = exports.OPENCODE_USER_ECHO_PATTERN = exports.OPENCODE_GUTTER_ROW_PATTERN = exports.OPENCODE_COMPOSER_BOTTOM_BORDER = exports.OPENCODE_PROCESSING_INDICATOR = exports.OPENCODE_PERMISSION_PATTERN = exports.OPENCODE_TURN_COMPLETE_PATTERN = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = exports.OPENCODE_IDLE_COMPOSER_PATTERN = exports.OPENCODE_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.CODEX_USER_ECHO_PATTERN = exports.CODEX_TRAILED_STATUS_BAR_PATTERN = exports.CODEX_STATUS_BAR_PATTERN = exports.CODEX_PAGER_FOOTER_PATTERN = exports.CODEX_EFFORT_PICKER_FOOTER_PATTERN = exports.CODEX_PICKER_FOOTER_PATTERN = exports.CODEX_FORM_SUBMIT_FOOTER_PATTERN = exports.CODEX_APPROVAL_FOOTER_PATTERN = exports.CODEX_SELECTION_LIST_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_HOOKS_REVIEW_ANCHORS = exports.CODEX_DIALOG_PATTERN = exports.CODEX_TRUST_QUESTION_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_INTERRUPT_HINT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
8
- exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.extractAnsiSequences = exports.stripAnsi = exports.COMMAND_CODE_SKIP_PATTERNS = exports.COMMAND_CODE_HOOK_NOTICE_PATTERN = exports.COMMAND_CODE_BANNER_PATTERNS = exports.COMMAND_CODE_MODE_INDICATOR_PATTERN = exports.COMMAND_CODE_COMPLETION_PATTERN = exports.COMMAND_CODE_RESPONSE_MARKER_PATTERN = exports.COMMAND_CODE_THINKING_PATTERN = exports.COMMAND_CODE_INTERRUPT_HINT_PATTERN = exports.COMMAND_CODE_SEPARATOR_PATTERN = exports.COMMAND_CODE_PROMPT_PATTERN = exports.COMMAND_CODE_SPINNER_CHARS = exports.ANTIGRAVITY_SKIP_PATTERNS = exports.ANTIGRAVITY_SURVEY_PATTERN = exports.ANTIGRAVITY_DIALOG_MAX_ROWS = exports.ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN = exports.ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN = exports.ANTIGRAVITY_NUMBERED_OPTION_PATTERN = exports.ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN = exports.ANTIGRAVITY_SELECTION_LIST_PATTERN = exports.ANTIGRAVITY_SEPARATOR_PATTERN = exports.ANTIGRAVITY_THINKING_PATTERN = exports.ANTIGRAVITY_PROMPT_PATTERN = exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_PATTERN = exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.COPILOT_SKIP_PATTERNS = exports.COPILOT_FOLDER_TRUST_ANSWER_KEY = exports.COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN = exports.COPILOT_FOLDER_TRUST_ANCHORS = exports.COPILOT_SELECTION_FOOTER_PATTERN = exports.COPILOT_BOOT_BANNER_ANCHORS = exports.COPILOT_TRANSCRIPT_CONTINUATION_PATTERN = exports.COPILOT_TOOL_ROW_PATTERN = exports.COPILOT_TOOL_VERBS = exports.COPILOT_TRANSCRIPT_DIVIDER_PATTERN = exports.COPILOT_USER_ECHO_PATTERN = exports.COPILOT_REASONING_HEADER_PATTERN = exports.COPILOT_BOX_ROW_PATTERN = exports.COPILOT_SEPARATOR_PATTERN = exports.COPILOT_IDLE_STATUS_PATTERN = exports.COPILOT_WORKING_STATUS_PATTERN = exports.COPILOT_THINKING_PATTERN = exports.COPILOT_PROMPT_PATTERN = exports.OPENCODE_V2_SKIP_PATTERNS = void 0;
9
- exports.isCodexTurnActive = isCodexTurnActive;
10
- exports.findClaudeChromeStart = findClaudeChromeStart;
11
- exports.resolveClaudeTrustDialogKeys = resolveClaudeTrustDialogKeys;
12
- exports.isClaudeTrustDialogOpen = isClaudeTrustDialogOpen;
13
- exports.isShellPaneCommand = isShellPaneCommand;
14
- exports.isCodexPromptReady = isCodexPromptReady;
15
- exports.getCodexActiveDialog = getCodexActiveDialog;
16
- exports.getCodexLifecycleDialog = getCodexLifecycleDialog;
17
- exports.findCodexChromeStart = findCodexChromeStart;
18
- exports.findCodexUserEchoIndex = findCodexUserEchoIndex;
19
- exports.findOpenCodeChromeStart = findOpenCodeChromeStart;
20
- exports.findOpenCodeUserEchoEnd = findOpenCodeUserEchoEnd;
21
- exports.findOpenCodeComposerRows = findOpenCodeComposerRows;
22
- exports.stripOpenCodeGutter = stripOpenCodeGutter;
23
- exports.isOpencodeV2ComposerVisible = isOpencodeV2ComposerVisible;
24
- exports.readCopilotStatusBar = readCopilotStatusBar;
25
- exports.findCopilotChromeStart = findCopilotChromeStart;
26
- exports.isCopilotSelectionFrame = isCopilotSelectionFrame;
27
- exports.isCopilotFolderTrustDialog = isCopilotFolderTrustDialog;
28
- exports.locateAntigravityDialogRegion = locateAntigravityDialogRegion;
29
- exports.isAntigravityNumberedDialog = isAntigravityNumberedDialog;
30
- exports.findCommandCodeChromeStart = findCommandCodeChromeStart;
7
+ exports.CLAUDE_SPINNER_CHARS = exports.OPENCODE_V2_SKIP_PATTERNS = exports.isOpencodeV2ComposerVisible = exports.OPENCODE_V2_THINKING_PATTERN = exports.OPENCODE_V2_FOOTER_PATTERN = exports.OPENCODE_V2_IDLE_COMPOSER_PATTERN = exports.OPENCODE_SKIP_PATTERNS = exports.OPENCODE_SEPARATOR_PATTERN = exports.OPENCODE_SELECTION_LIST_PATTERN = exports.stripOpenCodeGutter = exports.findOpenCodeComposerRows = exports.findOpenCodeUserEchoEnd = exports.findOpenCodeChromeStart = exports.OPENCODE_FOOTER_STATUS_PATTERN = exports.OPENCODE_USER_ECHO_PATTERN = exports.OPENCODE_GUTTER_ROW_PATTERN = exports.OPENCODE_COMPOSER_BOTTOM_BORDER = exports.OPENCODE_PROCESSING_INDICATOR = exports.OPENCODE_PERMISSION_PATTERN = exports.OPENCODE_TURN_COMPLETE_PATTERN = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = exports.OPENCODE_IDLE_COMPOSER_PATTERN = exports.OPENCODE_PROMPT_PATTERN = exports.findCodexUserEchoIndex = exports.CODEX_USER_ECHO_PATTERN = exports.findCodexChromeStart = exports.CODEX_TRAILED_STATUS_BAR_PATTERN = exports.CODEX_STATUS_BAR_PATTERN = exports.CODEX_PAGER_FOOTER_PATTERN = exports.CODEX_EFFORT_PICKER_FOOTER_PATTERN = exports.CODEX_PICKER_FOOTER_PATTERN = exports.CODEX_FORM_SUBMIT_FOOTER_PATTERN = exports.CODEX_APPROVAL_FOOTER_PATTERN = exports.CODEX_SELECTION_LIST_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.getCodexLifecycleDialog = exports.CODEX_HOOKS_REVIEW_ANCHORS = exports.getCodexActiveDialog = exports.findCodexComposerRow = exports.isCodexPromptReady = exports.CODEX_GENUINE_PROMPT_LINE = exports.CODEX_DIALOG_PATTERN = exports.CODEX_TRUST_QUESTION_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.isCodexTurnActive = exports.CODEX_INTERRUPT_HINT_PATTERN = exports.CODEX_THINKING_PATTERN = void 0;
8
+ exports.ANTIGRAVITY_SURVEY_PATTERN = exports.ANTIGRAVITY_DIALOG_MAX_ROWS = exports.ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN = exports.ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN = exports.ANTIGRAVITY_NUMBERED_OPTION_PATTERN = exports.ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN = exports.ANTIGRAVITY_SELECTION_LIST_PATTERN = exports.ANTIGRAVITY_SEPARATOR_PATTERN = exports.ANTIGRAVITY_THINKING_PATTERN = exports.ANTIGRAVITY_PROMPT_PATTERN = exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_PATTERN = exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE = exports.COPILOT_SKIP_PATTERNS = exports.isCopilotFolderTrustDialog = exports.COPILOT_FOLDER_TRUST_ANSWER_KEY = exports.COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN = exports.COPILOT_FOLDER_TRUST_ANCHORS = exports.isCopilotSelectionFrame = exports.COPILOT_SELECTION_FOOTER_PATTERN = exports.COPILOT_BOOT_BANNER_ANCHORS = exports.COPILOT_TRANSCRIPT_CONTINUATION_PATTERN = exports.COPILOT_TOOL_ROW_PATTERN = exports.COPILOT_TOOL_VERBS = exports.COPILOT_TRANSCRIPT_DIVIDER_PATTERN = exports.COPILOT_USER_ECHO_PATTERN = exports.COPILOT_REASONING_HEADER_PATTERN = exports.COPILOT_BOX_ROW_PATTERN = exports.findCopilotChromeStart = exports.COPILOT_SEPARATOR_PATTERN = exports.readCopilotStatusBar = exports.COPILOT_IDLE_STATUS_PATTERN = exports.COPILOT_WORKING_STATUS_PATTERN = exports.COPILOT_THINKING_PATTERN = exports.COPILOT_PROMPT_PATTERN = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.CLAUDE_SELECTION_LIST_FOOTER = exports.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN = exports.isClaudeTrustDialogOpen = exports.resolveClaudeTrustDialogKeys = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.findClaudeChromeStart = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = void 0;
9
+ exports.stripBoxDrawing = exports.extractAnsiSequences = exports.stripAnsi = exports.findOpencodeV2DialogTitle = exports.OPENCODE_V2_DIALOG_TITLE_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.isShellPaneCommand = exports.COMMAND_CODE_SKIP_PATTERNS = exports.findCommandCodeChromeStart = exports.COMMAND_CODE_HOOK_NOTICE_PATTERN = exports.COMMAND_CODE_BANNER_PATTERNS = exports.COMMAND_CODE_MODE_INDICATOR_PATTERN = exports.COMMAND_CODE_COMPLETION_PATTERN = exports.COMMAND_CODE_RESPONSE_MARKER_PATTERN = exports.COMMAND_CODE_THINKING_PATTERN = exports.COMMAND_CODE_INTERRUPT_HINT_PATTERN = exports.COMMAND_CODE_SEPARATOR_PATTERN = exports.COMMAND_CODE_PROMPT_PATTERN = exports.COMMAND_CODE_SPINNER_CHARS = exports.ANTIGRAVITY_SKIP_PATTERNS = exports.isAntigravityNumberedDialog = exports.locateAntigravityDialogRegion = void 0;
31
10
  exports.detectThinking = detectThinking;
32
11
  exports.getCliToolPatterns = getCliToolPatterns;
33
- exports.stripBoxDrawing = stripBoxDrawing;
34
12
  exports.buildDetectPromptOptions = buildDetectPromptOptions;
13
+ exports.usesFullFramePrompt = usesFullFramePrompt;
35
14
  const logger_1 = require("../../lib/logger");
36
- const ansi_1 = require("./ansi");
37
- const composer_text_1 = require("./composer-text");
38
- const cli_patterns_1 = require("./tools/codex/cli-patterns");
39
- const thinking_constants_1 = require("../../config/thinking-constants");
15
+ const shell_pane_command_1 = require("./shared/shell-pane-command");
16
+ Object.defineProperty(exports, "isShellPaneCommand", { enumerable: true, get: function () { return shell_pane_command_1.isShellPaneCommand; } });
17
+ const pasted_text_1 = require("./shared/pasted-text");
18
+ Object.defineProperty(exports, "PASTED_TEXT_PATTERN", { enumerable: true, get: function () { return pasted_text_1.PASTED_TEXT_PATTERN; } });
19
+ Object.defineProperty(exports, "PASTED_TEXT_DETECT_DELAY", { enumerable: true, get: function () { return pasted_text_1.PASTED_TEXT_DETECT_DELAY; } });
20
+ Object.defineProperty(exports, "MAX_PASTED_TEXT_RETRIES", { enumerable: true, get: function () { return pasted_text_1.MAX_PASTED_TEXT_RETRIES; } });
21
+ const strip_box_drawing_1 = require("./shared/strip-box-drawing");
22
+ Object.defineProperty(exports, "stripBoxDrawing", { enumerable: true, get: function () { return strip_box_drawing_1.stripBoxDrawing; } });
23
+ const patterns_1 = require("./tools/codex/patterns");
24
+ var patterns_2 = require("./tools/codex/patterns");
25
+ Object.defineProperty(exports, "CODEX_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_THINKING_PATTERN; } });
26
+ Object.defineProperty(exports, "CODEX_INTERRUPT_HINT_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_INTERRUPT_HINT_PATTERN; } });
27
+ Object.defineProperty(exports, "isCodexTurnActive", { enumerable: true, get: function () { return patterns_2.isCodexTurnActive; } });
28
+ Object.defineProperty(exports, "CODEX_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_PROMPT_PATTERN; } });
29
+ Object.defineProperty(exports, "CODEX_TRUST_QUESTION_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_TRUST_QUESTION_PATTERN; } });
30
+ Object.defineProperty(exports, "CODEX_DIALOG_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_DIALOG_PATTERN; } });
31
+ Object.defineProperty(exports, "CODEX_GENUINE_PROMPT_LINE", { enumerable: true, get: function () { return patterns_2.CODEX_GENUINE_PROMPT_LINE; } });
32
+ Object.defineProperty(exports, "isCodexPromptReady", { enumerable: true, get: function () { return patterns_2.isCodexPromptReady; } });
33
+ Object.defineProperty(exports, "findCodexComposerRow", { enumerable: true, get: function () { return patterns_2.findCodexComposerRow; } });
34
+ Object.defineProperty(exports, "getCodexActiveDialog", { enumerable: true, get: function () { return patterns_2.getCodexActiveDialog; } });
35
+ Object.defineProperty(exports, "CODEX_HOOKS_REVIEW_ANCHORS", { enumerable: true, get: function () { return patterns_2.CODEX_HOOKS_REVIEW_ANCHORS; } });
36
+ Object.defineProperty(exports, "getCodexLifecycleDialog", { enumerable: true, get: function () { return patterns_2.getCodexLifecycleDialog; } });
37
+ Object.defineProperty(exports, "CODEX_SEPARATOR_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_SEPARATOR_PATTERN; } });
38
+ Object.defineProperty(exports, "CODEX_SELECTION_LIST_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_SELECTION_LIST_PATTERN; } });
39
+ Object.defineProperty(exports, "CODEX_APPROVAL_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_APPROVAL_FOOTER_PATTERN; } });
40
+ Object.defineProperty(exports, "CODEX_FORM_SUBMIT_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_FORM_SUBMIT_FOOTER_PATTERN; } });
41
+ Object.defineProperty(exports, "CODEX_PICKER_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_PICKER_FOOTER_PATTERN; } });
42
+ Object.defineProperty(exports, "CODEX_EFFORT_PICKER_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_EFFORT_PICKER_FOOTER_PATTERN; } });
43
+ Object.defineProperty(exports, "CODEX_PAGER_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_PAGER_FOOTER_PATTERN; } });
44
+ Object.defineProperty(exports, "CODEX_STATUS_BAR_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_STATUS_BAR_PATTERN; } });
45
+ Object.defineProperty(exports, "CODEX_TRAILED_STATUS_BAR_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_TRAILED_STATUS_BAR_PATTERN; } });
46
+ Object.defineProperty(exports, "findCodexChromeStart", { enumerable: true, get: function () { return patterns_2.findCodexChromeStart; } });
47
+ Object.defineProperty(exports, "CODEX_USER_ECHO_PATTERN", { enumerable: true, get: function () { return patterns_2.CODEX_USER_ECHO_PATTERN; } });
48
+ Object.defineProperty(exports, "findCodexUserEchoIndex", { enumerable: true, get: function () { return patterns_2.findCodexUserEchoIndex; } });
49
+ const patterns_3 = require("./tools/opencode/patterns");
50
+ var patterns_4 = require("./tools/opencode/patterns");
51
+ Object.defineProperty(exports, "OPENCODE_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_PROMPT_PATTERN; } });
52
+ Object.defineProperty(exports, "OPENCODE_IDLE_COMPOSER_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_IDLE_COMPOSER_PATTERN; } });
53
+ Object.defineProperty(exports, "OPENCODE_PROMPT_AFTER_RESPONSE", { enumerable: true, get: function () { return patterns_4.OPENCODE_PROMPT_AFTER_RESPONSE; } });
54
+ Object.defineProperty(exports, "OPENCODE_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_THINKING_PATTERN; } });
55
+ Object.defineProperty(exports, "OPENCODE_LOADING_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_LOADING_PATTERN; } });
56
+ Object.defineProperty(exports, "OPENCODE_RESPONSE_COMPLETE", { enumerable: true, get: function () { return patterns_4.OPENCODE_RESPONSE_COMPLETE; } });
57
+ Object.defineProperty(exports, "OPENCODE_TURN_COMPLETE_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_TURN_COMPLETE_PATTERN; } });
58
+ Object.defineProperty(exports, "OPENCODE_PERMISSION_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_PERMISSION_PATTERN; } });
59
+ Object.defineProperty(exports, "OPENCODE_PROCESSING_INDICATOR", { enumerable: true, get: function () { return patterns_4.OPENCODE_PROCESSING_INDICATOR; } });
60
+ Object.defineProperty(exports, "OPENCODE_COMPOSER_BOTTOM_BORDER", { enumerable: true, get: function () { return patterns_4.OPENCODE_COMPOSER_BOTTOM_BORDER; } });
61
+ Object.defineProperty(exports, "OPENCODE_GUTTER_ROW_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_GUTTER_ROW_PATTERN; } });
62
+ Object.defineProperty(exports, "OPENCODE_USER_ECHO_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_USER_ECHO_PATTERN; } });
63
+ Object.defineProperty(exports, "OPENCODE_FOOTER_STATUS_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_FOOTER_STATUS_PATTERN; } });
64
+ Object.defineProperty(exports, "findOpenCodeChromeStart", { enumerable: true, get: function () { return patterns_4.findOpenCodeChromeStart; } });
65
+ Object.defineProperty(exports, "findOpenCodeUserEchoEnd", { enumerable: true, get: function () { return patterns_4.findOpenCodeUserEchoEnd; } });
66
+ Object.defineProperty(exports, "findOpenCodeComposerRows", { enumerable: true, get: function () { return patterns_4.findOpenCodeComposerRows; } });
67
+ Object.defineProperty(exports, "stripOpenCodeGutter", { enumerable: true, get: function () { return patterns_4.stripOpenCodeGutter; } });
68
+ Object.defineProperty(exports, "OPENCODE_SELECTION_LIST_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_SELECTION_LIST_PATTERN; } });
69
+ Object.defineProperty(exports, "OPENCODE_SEPARATOR_PATTERN", { enumerable: true, get: function () { return patterns_4.OPENCODE_SEPARATOR_PATTERN; } });
70
+ Object.defineProperty(exports, "OPENCODE_SKIP_PATTERNS", { enumerable: true, get: function () { return patterns_4.OPENCODE_SKIP_PATTERNS; } });
71
+ const patterns_5 = require("./tools/opencode-v2/patterns");
72
+ var patterns_6 = require("./tools/opencode-v2/patterns");
73
+ Object.defineProperty(exports, "OPENCODE_V2_IDLE_COMPOSER_PATTERN", { enumerable: true, get: function () { return patterns_6.OPENCODE_V2_IDLE_COMPOSER_PATTERN; } });
74
+ Object.defineProperty(exports, "OPENCODE_V2_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_6.OPENCODE_V2_FOOTER_PATTERN; } });
75
+ Object.defineProperty(exports, "OPENCODE_V2_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_6.OPENCODE_V2_THINKING_PATTERN; } });
76
+ Object.defineProperty(exports, "isOpencodeV2ComposerVisible", { enumerable: true, get: function () { return patterns_6.isOpencodeV2ComposerVisible; } });
77
+ Object.defineProperty(exports, "OPENCODE_V2_SKIP_PATTERNS", { enumerable: true, get: function () { return patterns_6.OPENCODE_V2_SKIP_PATTERNS; } });
78
+ const patterns_7 = require("./tools/claude/patterns");
79
+ var patterns_8 = require("./tools/claude/patterns");
80
+ Object.defineProperty(exports, "CLAUDE_SPINNER_CHARS", { enumerable: true, get: function () { return patterns_8.CLAUDE_SPINNER_CHARS; } });
81
+ Object.defineProperty(exports, "CLAUDE_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_8.CLAUDE_THINKING_PATTERN; } });
82
+ Object.defineProperty(exports, "CLAUDE_INTERRUPT_HINT_PATTERN", { enumerable: true, get: function () { return patterns_8.CLAUDE_INTERRUPT_HINT_PATTERN; } });
83
+ Object.defineProperty(exports, "CLAUDE_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_8.CLAUDE_PROMPT_PATTERN; } });
84
+ Object.defineProperty(exports, "CLAUDE_SEPARATOR_PATTERN", { enumerable: true, get: function () { return patterns_8.CLAUDE_SEPARATOR_PATTERN; } });
85
+ Object.defineProperty(exports, "findClaudeChromeStart", { enumerable: true, get: function () { return patterns_8.findClaudeChromeStart; } });
86
+ Object.defineProperty(exports, "CLAUDE_TRUST_DIALOG_PATTERN", { enumerable: true, get: function () { return patterns_8.CLAUDE_TRUST_DIALOG_PATTERN; } });
87
+ Object.defineProperty(exports, "resolveClaudeTrustDialogKeys", { enumerable: true, get: function () { return patterns_8.resolveClaudeTrustDialogKeys; } });
88
+ Object.defineProperty(exports, "isClaudeTrustDialogOpen", { enumerable: true, get: function () { return patterns_8.isClaudeTrustDialogOpen; } });
89
+ Object.defineProperty(exports, "CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_8.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN; } });
90
+ Object.defineProperty(exports, "CLAUDE_SELECTION_LIST_FOOTER", { enumerable: true, get: function () { return patterns_8.CLAUDE_SELECTION_LIST_FOOTER; } });
91
+ Object.defineProperty(exports, "CLAUDE_SESSION_ERROR_PATTERNS", { enumerable: true, get: function () { return patterns_8.CLAUDE_SESSION_ERROR_PATTERNS; } });
92
+ Object.defineProperty(exports, "CLAUDE_SESSION_ERROR_REGEX_PATTERNS", { enumerable: true, get: function () { return patterns_8.CLAUDE_SESSION_ERROR_REGEX_PATTERNS; } });
93
+ const patterns_9 = require("./tools/gemini/patterns");
94
+ var patterns_10 = require("./tools/gemini/patterns");
95
+ Object.defineProperty(exports, "GEMINI_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_10.GEMINI_PROMPT_PATTERN; } });
96
+ Object.defineProperty(exports, "GEMINI_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_10.GEMINI_THINKING_PATTERN; } });
97
+ const patterns_11 = require("./tools/vibe-local/patterns");
98
+ var patterns_12 = require("./tools/vibe-local/patterns");
99
+ Object.defineProperty(exports, "VIBE_LOCAL_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_12.VIBE_LOCAL_PROMPT_PATTERN; } });
100
+ Object.defineProperty(exports, "VIBE_LOCAL_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_12.VIBE_LOCAL_THINKING_PATTERN; } });
101
+ const patterns_13 = require("./tools/copilot/patterns");
102
+ var patterns_14 = require("./tools/copilot/patterns");
103
+ Object.defineProperty(exports, "COPILOT_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_PROMPT_PATTERN; } });
104
+ Object.defineProperty(exports, "COPILOT_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_THINKING_PATTERN; } });
105
+ Object.defineProperty(exports, "COPILOT_WORKING_STATUS_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_WORKING_STATUS_PATTERN; } });
106
+ Object.defineProperty(exports, "COPILOT_IDLE_STATUS_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_IDLE_STATUS_PATTERN; } });
107
+ Object.defineProperty(exports, "readCopilotStatusBar", { enumerable: true, get: function () { return patterns_14.readCopilotStatusBar; } });
108
+ Object.defineProperty(exports, "COPILOT_SEPARATOR_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_SEPARATOR_PATTERN; } });
109
+ Object.defineProperty(exports, "findCopilotChromeStart", { enumerable: true, get: function () { return patterns_14.findCopilotChromeStart; } });
110
+ Object.defineProperty(exports, "COPILOT_BOX_ROW_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_BOX_ROW_PATTERN; } });
111
+ Object.defineProperty(exports, "COPILOT_REASONING_HEADER_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_REASONING_HEADER_PATTERN; } });
112
+ Object.defineProperty(exports, "COPILOT_USER_ECHO_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_USER_ECHO_PATTERN; } });
113
+ Object.defineProperty(exports, "COPILOT_TRANSCRIPT_DIVIDER_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_TRANSCRIPT_DIVIDER_PATTERN; } });
114
+ Object.defineProperty(exports, "COPILOT_TOOL_VERBS", { enumerable: true, get: function () { return patterns_14.COPILOT_TOOL_VERBS; } });
115
+ Object.defineProperty(exports, "COPILOT_TOOL_ROW_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_TOOL_ROW_PATTERN; } });
116
+ Object.defineProperty(exports, "COPILOT_TRANSCRIPT_CONTINUATION_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_TRANSCRIPT_CONTINUATION_PATTERN; } });
117
+ Object.defineProperty(exports, "COPILOT_BOOT_BANNER_ANCHORS", { enumerable: true, get: function () { return patterns_14.COPILOT_BOOT_BANNER_ANCHORS; } });
118
+ Object.defineProperty(exports, "COPILOT_SELECTION_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_SELECTION_FOOTER_PATTERN; } });
119
+ Object.defineProperty(exports, "isCopilotSelectionFrame", { enumerable: true, get: function () { return patterns_14.isCopilotSelectionFrame; } });
120
+ Object.defineProperty(exports, "COPILOT_FOLDER_TRUST_ANCHORS", { enumerable: true, get: function () { return patterns_14.COPILOT_FOLDER_TRUST_ANCHORS; } });
121
+ Object.defineProperty(exports, "COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN", { enumerable: true, get: function () { return patterns_14.COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN; } });
122
+ Object.defineProperty(exports, "COPILOT_FOLDER_TRUST_ANSWER_KEY", { enumerable: true, get: function () { return patterns_14.COPILOT_FOLDER_TRUST_ANSWER_KEY; } });
123
+ Object.defineProperty(exports, "isCopilotFolderTrustDialog", { enumerable: true, get: function () { return patterns_14.isCopilotFolderTrustDialog; } });
124
+ Object.defineProperty(exports, "COPILOT_SKIP_PATTERNS", { enumerable: true, get: function () { return patterns_14.COPILOT_SKIP_PATTERNS; } });
125
+ const patterns_15 = require("./tools/antigravity/patterns");
126
+ var patterns_16 = require("./tools/antigravity/patterns");
127
+ Object.defineProperty(exports, "ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE; } });
128
+ Object.defineProperty(exports, "ANTIGRAVITY_COMPOSER_MODE_BANNER_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_COMPOSER_MODE_BANNER_PATTERN; } });
129
+ Object.defineProperty(exports, "ANTIGRAVITY_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_PROMPT_PATTERN; } });
130
+ Object.defineProperty(exports, "ANTIGRAVITY_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_THINKING_PATTERN; } });
131
+ Object.defineProperty(exports, "ANTIGRAVITY_SEPARATOR_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_SEPARATOR_PATTERN; } });
132
+ Object.defineProperty(exports, "ANTIGRAVITY_SELECTION_LIST_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_SELECTION_LIST_PATTERN; } });
133
+ Object.defineProperty(exports, "ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN; } });
134
+ Object.defineProperty(exports, "ANTIGRAVITY_NUMBERED_OPTION_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_NUMBERED_OPTION_PATTERN; } });
135
+ Object.defineProperty(exports, "ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN; } });
136
+ Object.defineProperty(exports, "ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN; } });
137
+ Object.defineProperty(exports, "ANTIGRAVITY_DIALOG_MAX_ROWS", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_DIALOG_MAX_ROWS; } });
138
+ Object.defineProperty(exports, "ANTIGRAVITY_SURVEY_PATTERN", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_SURVEY_PATTERN; } });
139
+ Object.defineProperty(exports, "locateAntigravityDialogRegion", { enumerable: true, get: function () { return patterns_16.locateAntigravityDialogRegion; } });
140
+ Object.defineProperty(exports, "isAntigravityNumberedDialog", { enumerable: true, get: function () { return patterns_16.isAntigravityNumberedDialog; } });
141
+ Object.defineProperty(exports, "ANTIGRAVITY_SKIP_PATTERNS", { enumerable: true, get: function () { return patterns_16.ANTIGRAVITY_SKIP_PATTERNS; } });
142
+ const patterns_17 = require("./tools/command-code/patterns");
143
+ var patterns_18 = require("./tools/command-code/patterns");
144
+ Object.defineProperty(exports, "COMMAND_CODE_SPINNER_CHARS", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_SPINNER_CHARS; } });
145
+ Object.defineProperty(exports, "COMMAND_CODE_PROMPT_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_PROMPT_PATTERN; } });
146
+ Object.defineProperty(exports, "COMMAND_CODE_SEPARATOR_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_SEPARATOR_PATTERN; } });
147
+ Object.defineProperty(exports, "COMMAND_CODE_INTERRUPT_HINT_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_INTERRUPT_HINT_PATTERN; } });
148
+ Object.defineProperty(exports, "COMMAND_CODE_THINKING_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_THINKING_PATTERN; } });
149
+ Object.defineProperty(exports, "COMMAND_CODE_RESPONSE_MARKER_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_RESPONSE_MARKER_PATTERN; } });
150
+ Object.defineProperty(exports, "COMMAND_CODE_COMPLETION_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_COMPLETION_PATTERN; } });
151
+ Object.defineProperty(exports, "COMMAND_CODE_MODE_INDICATOR_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_MODE_INDICATOR_PATTERN; } });
152
+ Object.defineProperty(exports, "COMMAND_CODE_BANNER_PATTERNS", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_BANNER_PATTERNS; } });
153
+ Object.defineProperty(exports, "COMMAND_CODE_HOOK_NOTICE_PATTERN", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_HOOK_NOTICE_PATTERN; } });
154
+ Object.defineProperty(exports, "findCommandCodeChromeStart", { enumerable: true, get: function () { return patterns_18.findCommandCodeChromeStart; } });
155
+ Object.defineProperty(exports, "COMMAND_CODE_SKIP_PATTERNS", { enumerable: true, get: function () { return patterns_18.COMMAND_CODE_SKIP_PATTERNS; } });
40
156
  const logger = (0, logger_1.createLogger)('cli-patterns');
41
- /**
42
- * Claude CLI spinner characters (expanded set)
43
- * These are shown when Claude is thinking/processing
44
- */
45
- exports.CLAUDE_SPINNER_CHARS = [
46
- '✻', '✽', '⏺', '·', '∴', '✢', '✳', '✶',
47
- '⦿', '◉', '●', '○', '◌', '◎', '⊙', '⊚',
48
- '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏', // Braille spinner
49
- ];
50
- /**
51
- * Claude thinking pattern
52
- * Matches spinner character followed by activity text ending with …
53
- * The text can contain spaces (e.g., "Verifying implementation (dead code detection)…")
54
- *
55
- * Alternative 2: "esc to interrupt" status bar text (Issue #188)
56
- * Claude Code shows "esc to interrupt" in the terminal status bar during active processing.
57
- * Previous pattern required closing paren `to interrupt\)` matching `(esc to interrupt)`,
58
- * but Claude Code v2.x status bar format uses `· esc to interrupt ·` without parens.
59
- * Updated to match `esc to interrupt` which covers both formats.
60
- */
61
- exports.CLAUDE_THINKING_PATTERN = new RegExp(`[${exports.CLAUDE_SPINNER_CHARS.join('')}]\\s+.+…|esc to interrupt`, 'm');
62
- /**
63
- * Claude status-bar "esc to interrupt" hint (Issue #805)
64
- *
65
- * Claude Code shows "esc to interrupt" in the bottom status bar ONLY while it is
66
- * actively processing. When idle/ready, the status bar shows shortcut hints
67
- * (e.g., "? for shortcuts") instead -- so this token is a reliable "running" signal.
68
- *
69
- * Why this exists separately from CLAUDE_THINKING_PATTERN's "esc to interrupt"
70
- * alternative: status detection evaluates the spinner+ellipsis branch of
71
- * CLAUDE_THINKING_PATTERN within a narrow 5-line window (THINKING_TAIL_LINE_COUNT)
72
- * to avoid mistaking a completed thinking summary in scrollback for active work
73
- * (Issue #188). During /pm-auto-dev + subagent runs, the bottom task panel
74
- * ("⏺ main" / "◯ general-purpose ..." rows) pushes both the "✶ Running…" spinner
75
- * AND the "esc to interrupt" status bar out of that 5-line window, so the session
76
- * was misdetected as Ready (Issue #805). Unlike the spinner+ellipsis summary, the
77
- * status-bar text is repainted live and never lingers in scrollback, so it can be
78
- * matched in a wider footer window without regressing Issue #188.
79
- */
80
- exports.CLAUDE_INTERRUPT_HINT_PATTERN = /esc to interrupt/;
81
- /**
82
- * Codex activity-marker pattern
83
- * Matches activity indicators like "• Planning", "• Searching", etc.
84
- * T1.1: Extended to include "Ran" and "Deciding"
85
- *
86
- * Issue #1671: these are *transcript records*, not a liveness signal. Codex is
87
- * inline-rendered (no alternate screen), so every "• Ran <cmd>" / "• Running
88
- * <cmd>" step it ever printed stays in the pane scrollback forever — measured on
89
- * a live `mcbd-codex-*` pane: 396 "• Ran" and 11 "• Running" rows, all of them
90
- * from finished steps. Matching this pattern against a fixed tail window
91
- * therefore answers "did a step happen recently", not "is Codex working now".
92
- * Use {@link isCodexTurnActive} for the latter.
93
- */
94
- exports.CODEX_THINKING_PATTERN = /•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing|Ran|Deciding)/m;
95
- /**
96
- * Codex live status-line hint (Issue #1671)
97
- *
98
- * While a turn is in flight Codex pins a status row directly above the composer:
99
- *
100
- * • Working (13s • esc to interrupt) · 1 background terminal running · /ps to view
101
- *
102
- * It is repainted in place every tick and erased the moment the turn ends, so —
103
- * unlike the "• Ran"/"• Running" step records — it never lingers in scrollback.
104
- * Measured on a 11,000-line capture of an idle Codex pane: zero occurrences of
105
- * "esc to interrupt", against 396 lingering "• Ran" rows. That makes it the one
106
- * unambiguous "Codex is still generating" token, mirroring Claude's
107
- * {@link CLAUDE_INTERRUPT_HINT_PATTERN}.
108
- */
109
- exports.CODEX_INTERRUPT_HINT_PATTERN = /esc to interrupt/;
110
- /**
111
- * How far above the last content row Codex's composer ("› …") may sit.
112
- *
113
- * Codex pins the composer and the status bar to the bottom of the pane, so the
114
- * composer lands 2-3 rows above the last non-blank row in every observed frame.
115
- * The small allowance keeps the search from walking up into the transcript and
116
- * latching onto the echoed user message, which uses the same "› " marker.
117
- */
118
- const CODEX_COMPOSER_SEARCH_ROWS = 8;
119
- /**
120
- * Decide whether a Codex turn is still in flight (Issue #1671).
121
- *
122
- * Completion detection used to answer this with {@link CODEX_THINKING_PATTERN}
123
- * over a fixed 20-row tail. Because "• Ran <cmd>" is a *past-tense record* that
124
- * never leaves the transcript, a turn that ended with a short final message kept
125
- * that record inside the tail window and was reported as "still thinking"
126
- * forever — so its reply was never saved, while a turn whose final message
127
- * happened to be longer than 20 rows pushed the record out of the window and was
128
- * saved. Whether a reply reached Message History depended on how long it was.
129
- *
130
- * Two signals, both measured against live codex-cli 0.146.0 captures:
131
- *
132
- * 1. The live status line ({@link CODEX_INTERRUPT_HINT_PATTERN}) anywhere in the
133
- * tail window. Present in every generating frame from 1s onwards, absent from
134
- * every idle frame.
135
- * 2. An activity marker in the rows immediately above the composer — the band
136
- * Codex reserves for that status line. Version-agnostic backstop for a Codex
137
- * build whose status row drops the "esc to interrupt" wording; deliberately
138
- * narrow (THINKING_TAIL_LINE_COUNT rows, matching the window status-detector
139
- * already uses) so records further up the transcript cannot reach it.
140
- *
141
- * When no composer can be located the frame is not a normal Codex layout (an
142
- * overlay is up, or the pane is mid-redraw), so signal 2 falls back to the whole
143
- * tail window — the pre-#1671 behaviour, which errs towards "still active".
144
- *
145
- * @param lines - Captured pane lines with trailing blank rows already trimmed
146
- * @param tailLineCount - Size of the tail window completion detection looks at
147
- * @returns True while Codex is still generating
148
- */
149
- function isCodexTurnActive(lines, tailLineCount) {
150
- const tailWindow = (0, ansi_1.stripAnsi)(lines.slice(Math.max(0, lines.length - tailLineCount)).join('\n'));
151
- // 1. Live status line — unambiguous, never survives the end of a turn.
152
- if (exports.CODEX_INTERRUPT_HINT_PATTERN.test(tailWindow))
153
- return true;
154
- // 2. Activity marker in the status-line band directly above the composer.
155
- // Searched bottom-up so the composer wins over the echoed user message.
156
- let composerIndex = -1;
157
- for (let i = lines.length - 1; i >= Math.max(0, lines.length - CODEX_COMPOSER_SEARCH_ROWS); i--) {
158
- if (exports.CODEX_PROMPT_PATTERN.test((0, ansi_1.stripAnsi)(lines[i]))) {
159
- composerIndex = i;
160
- break;
161
- }
162
- }
163
- if (composerIndex < 0)
164
- return exports.CODEX_THINKING_PATTERN.test(tailWindow);
165
- const bandStart = Math.max(0, composerIndex - thinking_constants_1.THINKING_TAIL_LINE_COUNT + 1);
166
- const band = (0, ansi_1.stripAnsi)(lines.slice(bandStart, composerIndex + 1).join('\n'));
167
- return exports.CODEX_THINKING_PATTERN.test(band);
168
- }
169
- /**
170
- * Claude prompt pattern (waiting for input)
171
- * Supports both legacy '>' and new '❯' (U+276F) prompt characters
172
- * Issue #132: Also matches prompts with recommended commands (e.g., "❯ /work-plan")
173
- *
174
- * Matches:
175
- * - Empty prompt: "❯ " or "> "
176
- * - Prompt with command: "❯ /work-plan" or "> npm install"
177
- */
178
- exports.CLAUDE_PROMPT_PATTERN = /^[>❯](\s*$|\s+\S)/m;
179
- /**
180
- * Claude separator pattern
181
- */
182
- exports.CLAUDE_SEPARATOR_PATTERN = /^─{10,}$/m;
183
- /**
184
- * Locate the start of Claude Code's bottom-pinned footer within a captured pane.
185
- *
186
- * Claude Code v2 draws in the alternate screen and reserves the last rows of the
187
- * pane for a footer that is never transcript content:
188
- *
189
- * <hint row> ← "◉ xhigh · /effort", "tmux detected · …", or blank
190
- * ──────────────────── ← separator
191
- * ❯ <input box> ← one or more rows
192
- * ──────────────────── ← separator
193
- * ⏸ manual mode on · ? for shortcuts · ← for agents focus
194
- *
195
- * The hint row rotates every few seconds while the conversation sits idle, so
196
- * keeping the footer in an extracted response makes its content hash change on
197
- * every poll tick. That defeated the content-based dedup added in #1268 and
198
- * re-saved the same reply once per tick (#1289).
199
- *
200
- * The boundary is found structurally rather than by matching hint text: the hint
201
- * strings are Claude Code's to change, and pattern-matching them is what let this
202
- * regression through (`? for shortcuts` was already listed as a skip pattern, but
203
- * the real status bar embeds it mid-line so the anchors never matched). The row
204
- * above the opening separator is reserved by Claude Code's layout and stays blank
205
- * even when a reply fills the whole pane, so it is always safe to drop.
206
- *
207
- * @param lines - Captured pane lines; trailing blank rows are tolerated
208
- * @returns Index of the first footer row, or -1 when no footer is present
209
- */
210
- function findClaudeChromeStart(lines) {
211
- // Issue #1879: the structural search (closing separator → opening separator →
212
- // prompt glyph, including the "is this really the input box and not a reply
213
- // fenced by two horizontal rules?" check) moved to `findClaudeInputBox` so the
214
- // composer reader locates the same box this trimmer does. Behaviour here is
215
- // unchanged; only the caller of the search moved.
216
- const box = (0, composer_text_1.findClaudeInputBox)(lines);
217
- if (box === null)
218
- return -1;
219
- // Include the reserved hint row directly above the opening separator.
220
- return Math.max(0, box.openingSeparator - 1);
221
- }
222
- /**
223
- * Claude trust dialog pattern (Issue #201)
224
- *
225
- * Matches the "Quick safety check" dialog displayed by Claude CLI v2.x
226
- * when accessing a workspace for the first time.
227
- *
228
- * Intentionally uses partial matching (no line-start anchor ^):
229
- * Other pattern constants (CLAUDE_PROMPT_PATTERN, CLAUDE_SEPARATOR_PATTERN, etc.)
230
- * use line-start anchors (^), but this pattern needs to match at any position
231
- * within the tmux output buffer because the dialog text may appear after
232
- * tmux padding or other output. (SF-001)
233
- */
234
- exports.CLAUDE_TRUST_DIALOG_PATTERN = /Yes, I trust this folder/m;
235
- /** The option row of the trust dialog's cursor: `❯` (or legacy `>`) after the dialog's left padding. */
236
- const CLAUDE_TRUST_CURSOR_ROW_PATTERN = /^\s*[>❯]\s+\S/;
237
- /** How far the cursor row may sit from the `Yes, I trust this folder` row (the dialog has two options). */
238
- const CLAUDE_TRUST_MAX_CURSOR_DISTANCE = 3;
239
- /**
240
- * Keys that answer Claude Code's folder-trust dialog with "Yes", or null when
241
- * no answerable dialog is on screen (Issue #3078).
242
- *
243
- * The dialog comes in two layouts, and Enter alone is only right for one:
244
- *
245
- * - default Yes (Issue #201 era, `tests/unit/lib/claude-session.test.ts`):
246
- * ` ❯ 1. Yes, I trust this folder` / ` 2. No, exit` — Enter confirms Yes.
247
- * - default No (2.1.259 `tests/fixtures/chat-dialog-card-2254/claude-trust-2-1-259.txt`,
248
- * 2.1.287 with a permission allow-list
249
- * `tests/fixtures/claude-trust-dialog-3078/allowlist-default-no-2-1-287.txt`):
250
- * ` ❯ No, exit` / ` Yes, I trust this folder` — Enter EXITS Claude Code.
251
- *
252
- * So the answer is read off the screen: the distance from the cursor row to the
253
- * Yes row becomes that many `Up`/`Down` presses, then `Enter`. Only the last
254
- * dialog in the capture counts, and a dialog followed by Claude's input-box
255
- * separator is history in the scrollback, not an open dialog.
256
- *
257
- * @param output - ANSI-stripped pane output
258
- * @returns tmux key names ending in `Enter` (just `['Enter']` when the cursor is
259
- * already on Yes), or null when no open dialog / no readable cursor row
260
- */
261
- function resolveClaudeTrustDialogKeys(output) {
262
- const lines = output.split('\n');
263
- const yesIndex = findOpenClaudeTrustYesRow(lines);
264
- if (yesIndex === -1)
265
- return null;
266
- for (let distance = 0; distance <= CLAUDE_TRUST_MAX_CURSOR_DISTANCE; distance++) {
267
- for (const direction of distance === 0 ? [0] : [-1, 1]) {
268
- const index = yesIndex + direction * distance;
269
- if (index < 0 || index >= lines.length)
270
- continue;
271
- if (!CLAUDE_TRUST_CURSOR_ROW_PATTERN.test(lines[index]))
272
- continue;
273
- // Cursor above Yes → move Down; below → move Up.
274
- const key = direction < 0 ? 'Down' : 'Up';
275
- return [...Array(distance).fill(key), 'Enter'];
276
- }
277
- }
278
- return null;
279
- }
280
- /**
281
- * Whether Claude Code's folder-trust dialog is the open screen (Issue #3078).
282
- *
283
- * The dialog's cursor row (` ❯ No, exit`) has the prompt glyph, so the start
284
- * wait asks this before CLAUDE_PROMPT_PATTERN rather than reading the dialog as
285
- * a ready prompt.
286
- *
287
- * @param output - ANSI-stripped pane output
288
- */
289
- function isClaudeTrustDialogOpen(output) {
290
- return findOpenClaudeTrustYesRow(output.split('\n')) !== -1;
291
- }
292
- /** `#{pane_current_command}` values of an interactive shell, with tmux's `-` login prefix removed. */
293
- const SHELL_PANE_COMMANDS = new Set(['sh', 'bash', 'zsh', 'fish', 'dash', 'ksh', 'tcsh', 'csh']);
294
- /**
295
- * Whether a pane's foreground command is a shell rather than an agent
296
- * (Issue #3089). Claude Code shows up as `claude`, `node` or its version
297
- * string (`2.1.287`), never as one of these, so a shell here after the agent
298
- * was seen running means the agent has exited.
299
- *
300
- * @param command - `#{pane_current_command}` of the pane
301
- */
302
- function isShellPaneCommand(command) {
303
- return SHELL_PANE_COMMANDS.has(command.trim().replace(/^-/, ''));
304
- }
305
- /** Row of the last `Yes, I trust this folder`, or -1 when absent or already answered. */
306
- function findOpenClaudeTrustYesRow(lines) {
307
- for (let i = lines.length - 1; i >= 0; i--) {
308
- if (!exports.CLAUDE_TRUST_DIALOG_PATTERN.test(lines[i]))
309
- continue;
310
- // Claude's input box (separator rows) drawn below it: the dialog is scrollback.
311
- return lines.slice(i + 1).some((line) => exports.CLAUDE_SEPARATOR_PATTERN.test(line)) ? -1 : i;
312
- }
313
- return -1;
314
- }
315
- /**
316
- * Codex prompt pattern
317
- * T1.2: Improved to detect empty prompts as well
318
- */
319
- exports.CODEX_PROMPT_PATTERN = /^›\s*/m;
320
- /**
321
- * The directory/folder trust dialog's question line, across codex builds
322
- * (Issue #2884): `Do you trust the contents of this directory?` (<=0.155.1)
323
- * and `Trust this folder?` (0.157.1, which dropped the "Do you trust" wording
324
- * entirely -- `tests/fixtures/codex-dialogs-0157/trust.txt`). The two variants
325
- * are kept as alternatives of one pattern -- rather than duplicated across
326
- * {@link CODEX_DIALOG_PATTERN}, `getCodexActiveDialog` and
327
- * `getCodexLifecycleDialog` -- so those three cannot drift into disagreeing
328
- * about what counts as the trust dialog.
329
- */
330
- exports.CODEX_TRUST_QUESTION_PATTERN = /Do you trust|Trust this folder\?/;
331
- /**
332
- * Codex INTERACTIVE startup dialog pattern (Issue #890)
333
- *
334
- * Codex shows interactive update-notification and trust dialogs on first launch.
335
- * Their currently-selected option lines render as "› 1. Update now", which ALSO
336
- * matches CODEX_PROMPT_PATTERN (the bare "^›" input-prompt pattern). So "is the
337
- * input prompt ready?" cannot be decided by CODEX_PROMPT_PATTERN alone -- it must
338
- * also confirm no INTERACTIVE dialog is still active. This pattern matches markers
339
- * that appear ONLY in interactive dialogs:
340
- * - Interactive update dialog: "Skip until next version" (the option-3 label)
341
- * - Trust dialog: {@link CODEX_TRUST_QUESTION_PATTERN}
342
- * - Dialog confirm footer: "Press enter to continue"
343
- * - Numbered selection option: "› 1. ..." (leading ›, a digit, a dot)
344
- *
345
- * IMPORTANT (Issue #890 regression): the substring "Update available" is
346
- * deliberately NOT a marker. After the update is skipped, codex keeps a
347
- * non-interactive banner box ("✨ Update available! ... / Run npm install -g
348
- * @openai/codex to update.") rendered ABOVE the genuine "› " prompt. Matching
349
- * "Update available" would make isCodexPromptReady() return false for as long as
350
- * that banner is visible, hanging waitForReady (~30s) and waitForPrompt (15s) on
351
- * exactly the first-launch + update-pending case this fix targets. The interactive
352
- * update dialog is still reliably detected via its other three markers above
353
- * ("› 1. Update now" + "Skip until next version" + "Press enter to continue").
354
- *
355
- * No /g flag (would make .test() stateful); no nested quantifiers (ReDoS-safe).
356
- */
357
- exports.CODEX_DIALOG_PATTERN = new RegExp(`Skip until next version|${exports.CODEX_TRUST_QUESTION_PATTERN.source}|Press enter to continue|^\\s*›\\s*\\d+\\.\\s`, 'm');
358
- /**
359
- * Codex genuine input-prompt line (Issue #892).
360
- *
361
- * A line whose first non-space glyph is "›" but which is NOT a numbered dialog
362
- * option ("› 1. ..."). The selected dialog option renders "›" at column 0 too
363
- * (same column as the live prompt), so the digit-dot negative lookahead is what
364
- * distinguishes the genuine input line from a dialog option line. Single-line
365
- * (no /m, no /g) -- callers test it per line to locate the prompt's position.
366
- */
367
- const CODEX_GENUINE_PROMPT_LINE = /^\s*›(?!\s*\d+\.)/;
368
- /**
369
- * Decide whether Codex output shows a genuine interactive input prompt rather than
370
- * a startup dialog (Issue #890, reworked in Issue #892).
371
- *
372
- * POSITION-based: capturePane(50) returns scrollback, so a dismissed update/trust
373
- * dialog lingers ABOVE the live prompt. The original Issue #890 form
374
- * (`CODEX_PROMPT_PATTERN && !CODEX_DIALOG_PATTERN`) is a whole-window test, so a
375
- * residual dialog line anywhere in the frame keeps it false forever -- hanging
376
- * waitForReady/waitForPrompt and (via the re-firing branches) injecting "222...".
377
- *
378
- * Instead the frame is ready when a genuine input-prompt line sits BELOW every
379
- * interactive dialog marker -- i.e. the prompt is the bottom-most active element.
380
- * CODEX_PROMPT_PATTERN / CODEX_DIALOG_PATTERN are intentionally unchanged here
381
- * (status-detector.ts / response-checker.ts depend on them).
382
- *
383
- * Used by both CodexTool.waitForReady() (startup) and CodexTool.waitForPrompt()
384
- * (before every send) so a residual dialog is never mistaken for "ready" and, just
385
- * as importantly, a genuine prompt below stale dialog scrollback IS detected.
386
- */
387
- function isCodexPromptReady(output) {
388
- const lines = output.split('\n');
389
- let lastDialogMarkerIdx = -1;
390
- let lastPromptIdx = -1;
391
- for (let i = 0; i < lines.length; i++) {
392
- const line = lines[i];
393
- if (exports.CODEX_DIALOG_PATTERN.test(line)) {
394
- // A dialog marker/option line is never itself a genuine prompt.
395
- lastDialogMarkerIdx = i;
396
- continue;
397
- }
398
- if (CODEX_GENUINE_PROMPT_LINE.test(line)) {
399
- lastPromptIdx = i;
400
- }
401
- }
402
- return lastPromptIdx >= 0 && lastPromptIdx > lastDialogMarkerIdx;
403
- }
404
- /**
405
- * Classify the bottom-most active Codex startup dialog (Issue #892).
406
- *
407
- * POSITION-based companion to isCodexPromptReady(): only dialog text appearing
408
- * BELOW the genuine input-prompt line is considered "active". Dialog lines that
409
- * remain in scrollback ABOVE a live prompt are ignored, so a dismissed dialog is
410
- * never re-acted on (this is what stops the update branch from re-sending "2" once
411
- * the dialog has been skipped -- the root cause of the "222..." prefix).
412
- *
413
- * Precedence matches CodexTool.waitForReady()'s historical branch order: the
414
- * update dialog wins over its own "Press enter to continue" footer, because Enter
415
- * on the update dialog could confirm the default "1. Update now" (npm install).
416
- */
417
- /**
418
- * The lines of Codex's ACTIVE region: everything strictly below the bottom-most
419
- * genuine input-prompt line, or the whole frame when there is no prompt line
420
- * (Issue #892).
421
- *
422
- * This is the one rule that keeps every Codex dialog classifier honest.
423
- * capturePane returns scrollback, so a dialog that was answered minutes ago is
424
- * still in the frame; only what sits BELOW the live prompt is still awaiting a
425
- * key. Shared by getCodexActiveDialog and getCodexLifecycleDialog so the two
426
- * cannot drift into disagreeing about what "active" means.
427
- */
428
- function codexActiveRegionLines(output) {
429
- const lines = output.split('\n');
430
- // Index of the bottom-most genuine input-prompt line (-1 if none).
431
- let promptIdx = -1;
432
- for (let i = lines.length - 1; i >= 0; i--) {
433
- if (CODEX_GENUINE_PROMPT_LINE.test(lines[i])) {
434
- promptIdx = i;
435
- break;
436
- }
437
- }
438
- return lines.slice(promptIdx + 1);
439
- }
440
- function getCodexActiveDialog(output) {
441
- // Residual dialog text above a live prompt is excluded, so a dialog lingering
442
- // in scrollback is never treated as active.
443
- const active = codexActiveRegionLines(output).join('\n');
444
- if (active === '') {
445
- return null;
446
- }
447
- if (active.includes('Skip until next version') ||
448
- (active.includes('Update') && active.includes('Skip'))) {
449
- return 'update';
450
- }
451
- if (exports.CODEX_TRUST_QUESTION_PATTERN.test(active)) {
452
- return 'trust';
453
- }
454
- if (active.includes('Press enter to continue')) {
455
- return 'press-enter';
456
- }
457
- return null;
458
- }
459
- /**
460
- * Anchors of codex's "Hooks need review" launch dialog — screen 1 of the three
461
- * it can put in front of a session (Issue #1760, re-measured on codex-cli
462
- * 0.148.0 for Issue #1829):
463
- *
464
- * ```
465
- * Hooks need review
466
- * 4 hooks are new or changed.
467
- * Hooks can run outside the sandbox after you trust them.
468
- *
469
- * > 1. Review hooks
470
- * 2. Trust all and continue
471
- * 3. Continue without trusting (hooks won't run)
472
- * Press enter to confirm or esc to go back
473
- * ```
474
- *
475
- * The hook COUNT is data — 0.147.0 said 5, 0.148.0 said 4 — so neither anchor
476
- * reads it. Both strings are required so a "hooks" mention elsewhere cannot
477
- * select an option on a live prompt.
478
- */
479
- exports.CODEX_HOOKS_REVIEW_ANCHORS = ['Hooks need review', 'Continue without trusting'];
480
- /**
481
- * Footer of screen 2, the hooks LIST, new in codex-cli 0.148.0 (Issue #1829):
482
- * `Press t to trust all; enter to review hooks; esc to close`.
483
- *
484
- * The semicolon is what separates it from screen 3's footer — "trust all;" and
485
- * "trust;" are disjoint — and matching the footer rather than the table above it
486
- * keeps the two screens distinguishable by a single line each.
487
- */
488
- const CODEX_HOOKS_LIST_FOOTER_PATTERN = /press\s+t\s+to\s+trust\s+all\s*;/i;
489
- /**
490
- * Footer of screen 3, the per-hook review DETAIL (Issue #1829):
491
- * `Press t to trust; esc to go back`. Where both live sessions in the Issue were
492
- * found parked.
493
- */
494
- const CODEX_HOOKS_DETAIL_FOOTER_PATTERN = /press\s+t\s+to\s+trust\s*;/i;
495
- /**
496
- * How much of the active region {@link getCodexLifecycleDialog} judges, in
497
- * non-blank lines counted from the bottom (Issue #1829).
498
- *
499
- * `getCodexActiveDialog` searches the whole active region, which is right for
500
- * its caller: `waitForReady` only ever runs during `startSession`, when nothing
501
- * else can be on screen. This classifier runs on every Auto-Yes poll for the
502
- * life of the session, where "active region" alone is not enough — a codex
503
- * approval request renders no `› ` composer line, so an approval that comes up
504
- * while a dismissed hooks screen is still inside the capture window would have
505
- * the whole frame as its active region and would be mistaken for the dialog.
506
- * Requiring the dialog to be in the TAIL is what separates the screen the user
507
- * is looking at from the one they have already left.
508
- *
509
- * 12 lines fits the tallest screen this has to recognise (the review dialog's
510
- * two anchors sit 6 lines apart) and none of the shorter frames below it.
511
- */
512
- const CODEX_LIFECYCLE_TAIL_LINES = 12;
513
- /**
514
- * The interactive update dialog, by its option-3 label or its option-1 line.
515
- *
516
- * Deliberately stricter than `getCodexActiveDialog`'s `Update` AND `Skip`
517
- * fallback: that pair can occur in ordinary agent output, and here a false
518
- * positive silently stops Auto-Yes answering a real prompt. Both anchors below
519
- * are dialog chrome that agent output does not produce.
520
- */
521
- const CODEX_UPDATE_DIALOG_ANCHORS = [
522
- /skip until next version/i,
523
- /^\s*[›❯]?\s*\d+\.\s*Update now/im,
524
- ];
525
- /**
526
- * Classify the bottom-most ACTIVE codex lifecycle screen (Issue #1829).
527
- *
528
- * Position-based, via {@link codexActiveRegionLines}: a dialog left in
529
- * scrollback above a live prompt is not active and returns `null`. That is not
530
- * a detail — the auto-answer guard in the Auto-Yes poller is built on this
531
- * function, and a whole-frame version of it would switch Auto-Yes off for the
532
- * rest of a codex session the moment any launch dialog scrolled past.
533
- *
534
- * The two hooks screens are matched FIRST and bottom-up, because a stuck pane
535
- * holds screen 2 above screen 3 and the way out of each differs. The remaining
536
- * three are region-level substring tests, which is all their anchors allow:
537
- * the review dialog's are on two different lines.
538
- *
539
- * Deliberately NOT used to decide whether a prompt exists. `detectPrompt` still
540
- * reports these screens, so a human still sees them; what this function gates is
541
- * only whether a machine may answer on their behalf.
542
- *
543
- * @param output - ANSI-stripped pane capture
544
- * @returns The active lifecycle screen, or null when none is
545
- */
546
- function getCodexLifecycleDialog(output) {
547
- const activeLines = codexActiveRegionLines(output);
548
- const window = [];
549
- for (let i = activeLines.length - 1; i >= 0 && window.length < CODEX_LIFECYCLE_TAIL_LINES; i--) {
550
- if (activeLines[i].trim() === '')
551
- continue;
552
- window.unshift(activeLines[i]);
553
- }
554
- if (window.length === 0)
555
- return null;
556
- const text = window.join('\n');
557
- // One bottom-up pass, returning on the first line that decides the question --
558
- // including the lines that decide it NEGATIVELY. A stuck pane holds screen 2
559
- // above screen 3, and an approval request can come up with a hooks screen
560
- // still inside the capture window; in both cases the screen the user is
561
- // looking at is the lower one.
562
- for (let i = window.length - 1; i >= 0; i--) {
563
- const line = window[i];
564
- if (CODEX_HOOKS_LIST_FOOTER_PATTERN.test(line))
565
- return 'hooks-list';
566
- if (CODEX_HOOKS_DETAIL_FOOTER_PATTERN.test(line))
567
- return 'hooks-detail';
568
- // The agent asking the human for permission mid-turn (Issue #1628's
569
- // "esc to cancel" footer, which no lifecycle screen wears). This is exactly
570
- // the prompt Auto-Yes exists to answer, so whatever lifecycle text is still
571
- // above it has been left behind and must not withhold the answer.
572
- if (exports.CODEX_APPROVAL_FOOTER_PATTERN.test(line))
573
- return null;
574
- if (CODEX_UPDATE_DIALOG_ANCHORS.some((pattern) => pattern.test(line)))
575
- return 'update';
576
- if (exports.CODEX_TRUST_QUESTION_PATTERN.test(line))
577
- return 'trust';
578
- // Both anchors required, so a stray "hooks" mention cannot claim the screen.
579
- if (line.includes(exports.CODEX_HOOKS_REVIEW_ANCHORS[1]) && text.includes(exports.CODEX_HOOKS_REVIEW_ANCHORS[0])) {
580
- return 'hooks-review';
581
- }
582
- }
583
- return null;
584
- }
585
- /**
586
- * Codex separator pattern
587
- */
588
- exports.CODEX_SEPARATOR_PATTERN = /^─.*Worked for.*─+$/m;
589
- /**
590
- * Codex CLI selection list footer pattern (Issue #619, #622)
591
- * Detects Codex CLI's interactive selection prompts that use arrow key
592
- * navigation (e.g., /model command's model and reasoning level selection steps).
593
- *
594
- * Matches:
595
- * - Step 1 (model selection): "Press enter to select reasoning effort, or esc to dismiss."
596
- * - Step 2 (reasoning level): "Press enter to confirm or esc to go back"
597
- * - Legacy: "press enter to confirm or esc to cancel"
598
- * Does NOT match: "press number to confirm" (handled by detectMultipleChoicePrompt)
599
- *
600
- * The distinction is important: "press enter to confirm/select" indicates an arrow-key
601
- * selection list (NavigationButtons), while "press number to confirm" indicates
602
- * a numbered prompt (PromptPanel with buttons).
603
- */
604
- exports.CODEX_SELECTION_LIST_PATTERN = /press\s+enter\s+to\s+(?:confirm|select)/i;
605
- /**
606
- * Codex CLI approval-request footer pattern (Issue #1628).
607
- *
608
- * Codex renders an approval request ("Would you like to run the following
609
- * command?" / "Would you like to make the following edits?") with the SAME
610
- * "Press enter to confirm" footer as a `/model`-style menu, which is why
611
- * CODEX_SELECTION_LIST_PATTERN swallows it. The two differ in the escape verb:
612
- * an approval request can be *cancelled* (it is the agent asking the human for
613
- * permission), a menu can only be *gone back* from.
614
- *
615
- * Measured on codex-cli 0.146.0 (five consecutive live approval frames captured
616
- * from a real session, plus two live `/model` picker frames):
617
- * - approval : "Press enter to confirm or esc to cancel"
618
- * - /model : "Press enter to confirm or esc to go back"
619
- * - /model : "Press enter to select reasoning effort, or esc to dismiss."
620
- *
621
- * Used only as one of two OR'd approval signals (see isCodexApprovalRequest in
622
- * status-detector.ts); the other is an interrogative question line, so a future
623
- * rewording of either signal alone does not reopen Issue #1628.
624
- *
625
- * No /g flag (keeps .test() stateless), no nested quantifiers (ReDoS-safe).
626
- */
627
- exports.CODEX_APPROVAL_FOOTER_PATTERN = /esc\s+to\s+cancel/i;
628
- /**
629
- * Codex CLI tool-call approval FORM footer (Issue #2609).
630
- *
631
- * Codex asks for some tool calls through a form rather than the classic
632
- * approval list, and closes it with a different sentence. Measured on a live
633
- * Browser use approval (2026-09-17, twice in one session):
634
- *
635
- * ```text
636
- * • Calling 修正前の専用GUIを開く
637
- *
638
- * Field 1/1
639
- * Allow Browser use to access http://127.0.0.1:60311?
640
- *
641
- * origin: http://127.0.0.1:60311
642
- *
643
- * › 1. Allow Run the tool and continue.
644
- * 2. Always allow Run the tool and remember this choice for future tool calls.
645
- * 3. Cancel Cancel this tool call
646
- * enter to submit | esc to cancel
647
- * ```
648
- *
649
- * No `press enter to confirm/select`, so {@link CODEX_SELECTION_LIST_PATTERN}
650
- * misses it and `detectCodexDialog` returned null for a dialog the status
651
- * detector reported as `waiting` — which `/prompt-response` read as
652
- * `prompt_no_longer_active` and Auto-Yes as `unclassified-frame`.
653
- *
654
- * Deliberately a separate constant rather than another alternative in
655
- * CODEX_SELECTION_LIST_PATTERN: that one also drives `detect.ts` branch 0.8, and
656
- * this footer is consumed ONLY by `detectCodexDialog`'s entry gate. It is the
657
- * whole measured row and nothing looser — `/m` + `^…$` against the trimmed
658
- * footer rows `findNumberedOptionBlock` returns, so a sentence that merely
659
- * contains "enter to submit", or "esc to cancel" on its own (the #1928 mutation
660
- * that rewords the approval footer), still does not vouch for a block.
661
- *
662
- * No /g flag (keeps .test() stateless), no nested quantifiers (ReDoS-safe).
663
- */
664
- exports.CODEX_FORM_SUBMIT_FOOTER_PATTERN = /^enter\s+to\s+submit\s*\|\s*esc\s+to\s+cancel$/im;
665
- /**
666
- * Codex CLI 0.157 picker footer pattern (Issue #2868).
667
- *
668
- * codex-cli 0.157.1 retitled `/model` ("Select Model and Effort") and replaced
669
- * its "Press enter to confirm or esc to go back" footer with the terse
670
- * `enter select · esc back` row (measured: `tests/fixtures/codex-dialogs-0157/`).
671
- * CODEX_SELECTION_LIST_PATTERN no longer matched, so branch 0.8 missed the
672
- * picker and the dialog entry gate refused the answer (`prompt_no_longer_active`).
673
- *
674
- * Same construction as CODEX_FORM_SUBMIT_FOOTER_PATTERN: the whole measured row
675
- * (`/m` + `^…$`), tested only against a trimmed single footer row — never a
676
- * window — so a transcript quoting the words does not vouch for anything.
677
- * CODEX_SELECTION_LIST_PATTERN is deliberately left as is (#2774 / #2841).
678
- *
679
- * No /g flag (keeps .test() stateless), no nested quantifiers (ReDoS-safe).
680
- */
681
- exports.CODEX_PICKER_FOOTER_PATTERN = /^enter\s+select\s*·\s*esc\s+back$/im;
682
- /**
683
- * Codex CLI 0.157 effort-picker footer pattern (Issue #2868).
684
- *
685
- * The second `/model` step ("Select Reasoning Level for …") closes with
686
- * `enter default · s session · esc back` (measured:
687
- * `tests/fixtures/codex-dialogs-0157/model-picker-effort.txt`). Same
688
- * construction and rules as CODEX_PICKER_FOOTER_PATTERN: the whole row, tested
689
- * only against a single trimmed footer row.
690
- *
691
- * No /g flag (keeps .test() stateless), no nested quantifiers (ReDoS-safe).
692
- */
693
- exports.CODEX_EFFORT_PICKER_FOOTER_PATTERN = /^enter\s+default\s*·\s*s\s+session\s*·\s*esc\s+back$/im;
694
- /**
695
- * Codex CLI pager / edit-previous (transcript) mode footer pattern (Issue #1017)
696
- *
697
- * When Codex enters its transcript pager / "edit previous message" mode, the
698
- * bottom of the frame shows scroll / edit key hints INSTEAD of the usual
699
- * "model · N% left · path" status bar, e.g.:
700
- * "↑/↓ to scroll pgup/pgdn to page home/end to jump"
701
- * "q to quit esc/← to edit prev → to edit next enter to edit message"
702
- * together with a scroll-percentage separator ("─ N% ─", NOT "N% left ·").
703
- *
704
- * Neither CODEX_SELECTION_LIST_PATTERN (which needs "press enter to
705
- * confirm/select") nor the "N% left ·" status-bar boundary logic in
706
- * status-detector.ts fires here, so the read-only TerminalDisplay is left with no
707
- * way to scroll or escape (the reported bug). This pattern recognizes the pager
708
- * footer directly — independent of the status bar — so the selection window
709
- * (NavigationButtons) can be rendered.
710
- *
711
- * Matches any of the pager-specific hints (either footer line is sufficient):
712
- * - scroll/page/jump hints: "↑/↓ to scroll" / "pgup/pgdn to page" / "home/end to jump"
713
- * - edit-previous hints: "esc/← to edit prev" / "→ to edit next" / "enter to edit message"
714
- * The two branches are independent so a mangled unicode-arrow footer line is still
715
- * caught by the ASCII "to edit prev/next/message" and "pgup/pgdn"/"home/end" hints.
716
- *
717
- * Does NOT match the genuine "/model" selection list ("press enter to select") —
718
- * that footer has no scroll/page/jump or edit-prev/next/message hint — so the
719
- * existing CODEX_SELECTION_LIST_PATTERN path is unaffected (no regression).
720
- *
721
- * No /g flag (S4-5: keeps test() stateless). No nested quantifiers (SEC4-001: ReDoS-safe).
722
- */
723
- exports.CODEX_PAGER_FOOTER_PATTERN = /(?:↑\/↓|pgup\/pgdn|home\/end)\s+to\s+(?:scroll|page|jump)|to\s+edit\s+(?:prev|next|message)/i;
724
- /**
725
- * Codex CLI status-bar line pattern (Issue #1150)
726
- *
727
- * The Codex TUI renders a status bar as the bottom-most content line, just above
728
- * the input area. status-detector.ts uses it as the footer boundary that separates
729
- * the conversation content (thinking indicators / idle "›" prompt) from the input
730
- * area, so both the selection-list check (priority 0.8) and the running/idle check
731
- * (priority 2.7) depend on locating it.
732
- *
733
- * The format drifted across Codex versions — the "N% left ·" token was DROPPED in
734
- * v0.141 (gpt-5.5), which is exactly what broke Issue #1150:
735
- * - v0.141 (gpt-5.5): "gpt-5.5 xhigh · ~/share/work/github_kewton/commandmate-issue-947"
736
- * - legacy (gpt-5.4): "gpt-5.4 high · 21% left · ~/share/work/..."
737
- * - legacy (o4-mini): " o4-mini 50% left · /path/to/project"
738
- *
739
- * The previous pattern required "\d+%\s+left\s+·", so v0.141 bars never matched:
740
- * the footer boundary stayed -1 and the whole Codex running/idle block was skipped,
741
- * leaving generating sessions misreported as `ready` (static green dot, no glow).
742
- *
743
- * Version-independent anchor: a leading model token, a middle-dot "·" separator,
744
- * and a filesystem path ("~/…" or "/…") at the END of the line. Any "N% left ·"
745
- * segment (legacy) is absorbed by ".*·" before the trailing path. Requiring the
746
- * trailing path keeps this Codex-specific (guarded by cliToolId === 'codex' in
747
- * status-detector.ts) and stops ordinary conversation lines that merely contain a
748
- * "·" from being mistaken for the status bar.
749
- *
750
- * Single-line by design (no /m, no /g): status-detector.ts tests it per content
751
- * line. No nested quantifiers (ReDoS-safe; adjacent greedy quantifiers only).
752
- */
753
- exports.CODEX_STATUS_BAR_PATTERN = /^\s*\S.*·\s*~?\/\S*\s*$/;
754
- /**
755
- * Codex status bar with something drawn AFTER the path (Issue #2818) — the
756
- * second shape `findCodexFooterBoundary` in `tools/codex/detect.ts` accepts.
757
- *
758
- * From codex 0.154.0 on, the bar stops ending in the path once the first turn
759
- * has named the thread, and Plan mode adds a right-aligned badge:
760
- *
761
- * ```text
762
- * gpt-5.6-terra low · /private/var/…/repo · Run touch probe.txt
763
- * gpt-6-astra medium · ~/uat3-…/sandbox-repo Plan mode (shift+tab to cycle)
764
- * ```
765
- *
766
- * {@link CODEX_STATUS_BAR_PATTERN} wants the path last, so every such frame had
767
- * no boundary and fell to the detector's bar-independent branch D (#1150's
768
- * safety net). That branch reads the 15-row tail, and on an idle frame the tail
769
- * still holds the finished turn's `• Ran …` record — which is how an idle
770
- * session read `running` (#2808's `idle-after-declined-approval.txt`; #2818
771
- * reproduced it on a turn that simply ran one command and answered).
772
- *
773
- * Kept a SEPARATE pattern rather than a widened {@link CODEX_STATUS_BAR_PATTERN}
774
- * so the change stays on the one reader it was measured for: that pattern is
775
- * also the stripped-capture landmark of {@link findCodexChromeStart} and the
776
- * value reader's first test in `model-info-extractor.ts`, neither of which
777
- * this Issue measured.
778
- *
779
- * The shape is the one `CODEX_STATUS_BAR_WITH_TRAILER_PATTERN`
780
- * (`model-info-extractor.ts`, #2592) reads values off, written out here rather
781
- * than imported: that module's rule is that it must never be the reason this
782
- * boundary moves. Head segment, `·`, a path, then EITHER a further `·`
783
- * segment OR a column gap (two spaces) before right-aligned text. `[^·]*` puts
784
- * the path right after the FIRST `·`, so codex's in-flight row (`• Working (…)
785
- * · 1 background terminal running · /ps to view`) is not a bar — its first `·`
786
- * is followed by a count, not a path.
787
- *
788
- * Single-line, no /g, no nested quantifiers (ReDoS-safe), as above.
789
- */
790
- exports.CODEX_TRAILED_STATUS_BAR_PATTERN = /^\s*\S[^·]*·\s*~?\/\S*(?:\s*·[^\n]*|[^\S\n]{2,}\S[^\n]*)$/;
791
- /**
792
- * How far above the last non-blank row {@link findCodexChromeStart} looks for the
793
- * composer.
794
- *
795
- * Same allowance and the same reason as {@link CODEX_COMPOSER_SEARCH_ROWS}: codex
796
- * pins the composer two to three rows above the bottom in every measured frame
797
- * (`tests/fixtures/codex-live-2310/`), and a wider band would let the search walk
798
- * into the transcript and mistake the echoed user message — drawn with the same
799
- * `›` — for the input box.
800
- *
801
- * Wider than the 8 rows of the liveness search because 0.15x can draw notices
802
- * BELOW the composer (`N background terminal running · /ps to view · /stop to
803
- * close`) that the liveness search never had to step over.
804
- */
805
- const CODEX_CHROME_SEARCH_ROWS = 12;
806
- /**
807
- * A row whose first character is codex's `›` glyph, with something after it.
808
- *
809
- * Deliberately not {@link CODEX_PROMPT_PATTERN}: that one is multiline and
810
- * matches a bare `›`, which is right for "is a prompt on screen anywhere?" and
811
- * wrong for classifying ONE row. Anchored at column 0 because all three of
812
- * codex's `›` uses are, and an indented `›` in a reply is quoted text.
813
- */
814
- const CODEX_CHROME_GLYPH_ROW_PATTERN = /^›(\s|$)/;
815
- /**
816
- * Locate the start of codex's bottom-pinned chrome within a captured pane.
817
- *
818
- * The fifth reader of this shape, after {@link findClaudeChromeStart} (#1289),
819
- * {@link findCopilotChromeStart} (#1897), {@link findOpenCodeChromeStart}
820
- * (#1911) and {@link findCommandCodeChromeStart} (#2250). codex is the tool that
821
- * never got one, and Issue #2400 is the bill for that.
822
- *
823
- * codex renders inline and pins two rows to the bottom of a settled pane:
824
- *
825
- * ```text
826
- * › Ask Codex to do anything ← composer (placeholder or typed text)
827
- *
828
- * gpt-6-astra xhigh · ~/share/work/…/CommandMate ← status bar (model · cwd)
829
- * ```
830
- *
831
- * Below the composer codex may also draw its own notices (`N background terminal
832
- * running · /ps to view`), so the boundary is "the composer row" rather than a
833
- * list of footer shapes: everything from the composer down is chrome by
834
- * construction, whatever codex adds there next.
835
- *
836
- * ## What went wrong without it (#2400)
837
- *
838
- * While the capture window is NOT saturated codex's extraction starts at
839
- * `lastCapturedLine`, so the composer only ever mattered as the `endIndex` break
840
- * — which the extraction loop already had. Once the pane outgrows
841
- * `CACHE_MAX_CAPTURE_LINES` (#1670) the cursor stops being a position in the
842
- * capture and `resolveExtractionStartIndex` switches to the newest echoed user
843
- * prompt. With no `contentEnd`, that backwards search started at the very bottom
844
- * of the pane and the first `›` it met was the COMPOSER. Extraction then began
845
- * on the row after it, i.e. on the status bar, and the saved "reply" for every
846
- * turn on a saturated pane was one row:
847
- *
848
- * ```text
849
- * gpt-6-astra xhigh · ~/share/work/github_kewton/CommandAgent-develop
850
- * ```
851
- *
852
- * Identical on every turn, so `isDuplicateResponse` then locked on it and the
853
- * pane could not record another reply at all. That is #1289's defect verbatim,
854
- * one tool later — the same reason `findCommandCodeChromeStart` exists.
855
- *
856
- * ## Why the attributes and not the placeholder text
857
- *
858
- * The codex branch of `findRecentUserPromptIndex` used to exclude the composer by
859
- * naming its placeholders (`Implement`, `Find and fix`, `Type`, `Summarize`).
860
- * Those are codex 0.1x wording, and 0.15x draws `Ask Codex to do anything`, so
861
- * the list silently stopped matching the thing it was written for. Issue #2310
862
- * measured what actually separates codex's three uses of `›` (U+203A), and it is
863
- * the SGR attributes, not the text: the composer glyph is bold (`ESC[1m›`), a
864
- * transcript echo is dim (`ESC[1;2m›`), a dialog option carries a bold label or
865
- * a label drawn in the glyph's own colour (one span — since #2798 a coloured
866
- * glyph alone is not enough, because 0.155.1 colours the composer's glyph too).
867
- * {@link readCodexGlyphRowKind} is that measurement, and this reader is one of
868
- * its callers.
869
- *
870
- * `-1` is returned for a frame whose bottom-most `›` is an option row: codex
871
- * replaces the composer with the dialog, so there is no chrome to trim and the
872
- * caller resolves the frame on the prompt path instead.
873
- *
874
- * ## The stripped-capture fallback
875
- *
876
- * Auto-Yes hands the detection layer a capture that has already been through
877
- * `stripAnsi`, and there every `›` is the same byte — {@link
878
- * readCodexGlyphRowKind} answers `null` on purpose rather than guessing. This
879
- * reader still has to answer for those frames, so it falls back to the one
880
- * structural landmark codex pins BELOW the composer and nowhere else: the status
881
- * bar ({@link CODEX_STATUS_BAR_PATTERN}, `model · /path`). Requiring it means a
882
- * frame with no bar — a pane mid-redraw, an overlay — yields `-1` and the
883
- * pre-#2400 reading, which is the direction that costs nothing.
884
- *
885
- * @param lines - Captured pane lines, ANSI-bearing or not; trailing blanks tolerated
886
- * @returns Index of the composer row, or -1 when no composer chrome is present
887
- */
888
- function findCodexChromeStart(lines) {
889
- let lastRow = lines.length - 1;
890
- while (lastRow >= 0 && (0, ansi_1.stripAnsi)(lines[lastRow]).trim() === '')
891
- lastRow--;
892
- if (lastRow < 0)
893
- return -1;
894
- // Bottom-most `›` row within the band codex reserves for its chrome. Anything
895
- // further up is transcript, and latching onto an echo there would cut the
896
- // reply this whole reader exists to keep.
897
- let glyphRow = -1;
898
- for (let i = lastRow; i >= Math.max(0, lastRow - CODEX_CHROME_SEARCH_ROWS); i--) {
899
- if (CODEX_CHROME_GLYPH_ROW_PATTERN.test((0, ansi_1.stripAnsi)(lines[i]))) {
900
- glyphRow = i;
901
- break;
902
- }
903
- }
904
- if (glyphRow < 0)
905
- return -1;
906
- const kind = (0, cli_patterns_1.readCodexGlyphRowKind)(lines[glyphRow]);
907
- if (kind === 'composer')
908
- return glyphRow;
909
- // A dialog is up (no composer drawn) or the bottom-most `›` is a transcript
910
- // echo mid-redraw. Neither is chrome to trim.
911
- if (kind !== null)
912
- return -1;
913
- // Stripped capture: no attributes to read. Accept the row as the composer only
914
- // when codex's status bar is drawn below it, which is where it always sits and
915
- // where a transcript echo can never be.
916
- for (let i = glyphRow + 1; i <= lastRow; i++) {
917
- if (exports.CODEX_STATUS_BAR_PATTERN.test((0, ansi_1.stripAnsi)(lines[i])))
918
- return glyphRow;
919
- }
920
- return -1;
921
- }
922
- /**
923
- * The shape of a codex row that could be the echo of a message the user sent.
924
- *
925
- * Shape only — `›` at column 0 with text after it — which all three of codex's
926
- * `›` uses share. {@link findCodexUserEchoIndex} is what tells them apart.
927
- */
928
- exports.CODEX_USER_ECHO_PATTERN = /^›\s+\S/;
929
- /**
930
- * Find the newest transcript echo of an operator message in a codex capture.
931
- *
932
- * The reader `findRecentUserPromptIndex` anchors codex turns on (Issue #2400).
933
- * It replaces a negative lookahead over composer placeholder strings
934
- * (`(?!Implement|Find and fix|Type|Summarize)`) written against codex 0.1x:
935
- * 0.15x draws `Ask Codex to do anything`, so the guard matched nothing it was
936
- * written for. The composer became the newest "echo", and on a saturated pane —
937
- * the one path where this anchor decides where extraction STARTS (#1670) — the
938
- * reply saved for every turn was the single status-bar row below it.
939
- *
940
- * ## What the attributes can and cannot separate
941
- *
942
- * #2310 measured the three uses of `›` and this reader adds the fourth reading
943
- * they left open, captured for #2400 on codex-cli 0.153.4
944
- * (`tests/fixtures/codex-live-2310/turn-submitted-no-status.txt`): the echo of
945
- * a message the operator has JUST submitted is drawn `ESC[1m› ESC[0m<text>` —
946
- * bold glyph, plain label — and only becomes the dim `ESC[1;2m› ` of the
947
- * measured frames once the turn settles. That shape is indistinguishable from a
948
- * composer holding typed text, so no per-row attribute rule can separate them.
949
- *
950
- * What separates them is position, which codex's layout fixes: the composer is
951
- * the BOTTOM-MOST `›` row of a frame. So the reader takes the boundary from its
952
- * caller:
953
- *
954
- * - `composerTrimmed` — `findCodexChromeStart` located the chrome and `lines`
955
- * has already been cut above it, so every `›` row left is transcript and the
956
- * newest one wins outright.
957
- * - otherwise — the composer may still be the bottom-most `›` row, so the first
958
- * candidate is stepped over. This is the structural spelling of the guard the
959
- * placeholder list used to be, and unlike that list it cannot go stale.
960
- *
961
- * Dialog option rows are refused wherever they appear: codex renders inline, so
962
- * a dialog answered minutes ago is still in the scrollback with its options
963
- * intact (#1160), and anchoring on one would cut the reply mid-way.
964
- *
965
- * @param lines - Captured pane rows, ANSI intact where the caller has it
966
- * @param contentEnd - Exclusive end of the conversation region
967
- * @param windowSize - How many rows above `contentEnd` to search
968
- * @param composerTrimmed - Whether `contentEnd` already excludes the composer
969
- * @returns Index of the newest echo row, or -1 when none is in the window
970
- */
971
- function findCodexUserEchoIndex(lines, contentEnd, windowSize, composerTrimmed) {
972
- let composerHandled = composerTrimmed;
973
- for (let i = Math.min(contentEnd, lines.length) - 1; i >= Math.max(0, contentEnd - windowSize); i--) {
974
- if (!exports.CODEX_USER_ECHO_PATTERN.test((0, ansi_1.stripAnsi)(lines[i])))
975
- continue;
976
- const kind = (0, cli_patterns_1.readCodexGlyphRowKind)(lines[i]);
977
- // An option row of a dialog still sitting in the scrollback.
978
- if (kind === 'option')
979
- continue;
980
- // Positively an echo: codex has settled the row and drawn its glyph dim.
981
- if (kind === 'transcript-echo')
982
- return i;
983
- // `composer` (bold glyph) or `null` (an ANSI-stripped capture, where all
984
- // three uses are one byte). Either could be the input box, and the input box
985
- // is always the bottom-most `›` row.
986
- if (!composerHandled) {
987
- composerHandled = true;
988
- continue;
989
- }
990
- return i;
991
- }
992
- return -1;
993
- }
994
- /**
995
- * Pasted text pattern
996
- *
997
- * Claude CLI displays this when it detects multi-line text paste in the
998
- * ink-based TextInput. The pattern matches the folded display format.
999
- *
1000
- * @example "[Pasted text #1 +46 lines]"
1001
- * @see Issue #212, #163
1002
- * @designNote PASTE-001: Pattern matches the start of the indicator only.
1003
- * The line count (+XX lines) is variable, so we match the fixed prefix
1004
- * to minimize false negatives. False positive risk is low because
1005
- * "[Pasted text #" is a unique format generated by Claude CLI's ink renderer.
1006
- * @designNote PASTE-001-FP (SF-S4-002): When used in skipPatterns,
1007
- * line-level matching could filter legitimate response lines if Claude's
1008
- * answer text happens to contain "[Pasted text #". This is unlikely and
1009
- * acceptable -- only the affected line would be lost.
1010
- */
1011
- exports.PASTED_TEXT_PATTERN = /\[Pasted text #\d+/;
1012
- /**
1013
- * Pasted text detection delay (milliseconds)
1014
- *
1015
- * Wait time after sendKeys for tmux buffer to reflect [Pasted text] display.
1016
- *
1017
- * @see Issue #212
1018
- * @designNote PASTE-002: 500ms is the empirically measured time for
1019
- * Claude CLI's ink rendering to complete. capturePane({ startLine: -10 })
1020
- * reads only the last 10 lines since [Pasted text] appears in the most
1021
- * recent few lines.
1022
- */
1023
- exports.PASTED_TEXT_DETECT_DELAY = 500;
1024
- /**
1025
- * Pasted text detection max retries
1026
- *
1027
- * @see Issue #212
1028
- * @designNote PASTE-003: 3 retries x 500ms = max 1500ms additional delay.
1029
- * Typically resolves on the first attempt (+500ms).
1030
- */
1031
- exports.MAX_PASTED_TEXT_RETRIES = 3;
1032
- /**
1033
- * Gemini interactive REPL prompt pattern
1034
- * Gemini CLI shows a `>` or `❯` prompt when waiting for user input in interactive mode.
1035
- *
1036
- * Two branches (Issue #386):
1037
- * - Branch 1: `^[>❯]\s*$` -- bare prompt character (empty input line)
1038
- * - Branch 2: `^\s*[>❯]\s+Type your message.*$` -- new-format prompt with placeholder text
1039
- * (e.g., " > Type your message or @path/to/file"). Leading whitespace is allowed
1040
- * because tmux capture-pane output may include padding.
1041
- *
1042
- * Branch 2 requires "Type your message" after the indicator to avoid false positives
1043
- * on quoted response lines (e.g., "> some quoted text").
1044
- *
1045
- * @see CLAUDE_PROMPT_PATTERN for similar dual-format matching approach
1046
- */
1047
- // [S4-5] /g flag prohibited: would make test() stateful
1048
- exports.GEMINI_PROMPT_PATTERN = /^[>❯]\s*$|^\s*[>❯]\s+Type your message.*$/m;
1049
- /**
1050
- * Gemini thinking/processing pattern
1051
- * Gemini CLI shows braille spinner characters and status text while processing.
1052
- */
1053
- exports.GEMINI_THINKING_PATTERN = /[\u2800-\u28FF]|Thinking\.\.\./;
1054
- /**
1055
- * OpenCode prompt pattern (Issue #379)
1056
- * OpenCode TUI shows "Ask anything..." in the input area when waiting for user input.
1057
- * Unlike Claude/Codex (which use > or ❯), OpenCode uses a text-based prompt indicator.
1058
- */
1059
- exports.OPENCODE_PROMPT_PATTERN = /Ask anything(?:\.\.\.|\u2026)/;
1060
- /**
1061
- * OpenCode idle composer pattern (Issue #1883).
1062
- *
1063
- * The `Ask anything...` placeholder as opencode actually draws it: **inside the
1064
- * input box**, behind the box's own gutter (`\u2503`, or `\u2502` on a lighter
1065
- * border style). Two measured facts make that row positive evidence that the
1066
- * composer is empty, rather than the mere absence of a busy marker (design
1067
- * principle D1 in `docs/design/multi-agent-state-architecture.md`):
1068
- *
1069
- * - opencode paints the placeholder **only while the input buffer is empty**.
1070
- * The first typed character replaces the whole row — measured live on
1071
- * opencode 1.18.20, pane 80x200 (`opencode-live-1883/composer-residual.txt`
1072
- * holds `\u2503 echo PREFILLED` where the idle frame holds the placeholder).
1073
- * - the gutter says the row belongs to the input box. `Ask anything...` printed
1074
- * in a response body has no gutter, and reading that as an idle composer is
1075
- * the "the phrase is on screen somewhere" inference D1 forbids.
1076
- *
1077
- * **Match this against the ANSI-stripped frame BEFORE {@link stripBoxDrawing}**,
1078
- * which strips the very gutter this pattern anchors on.
1079
- *
1080
- * The whitespace runs are `[^\S\n]` (horizontal only) on purpose: plain `\s`
1081
- * crosses newlines under the `m` flag, which let the gutter of one row pair up
1082
- * with the phrase several rows below it and matched frames that hold no
1083
- * composer at all (measured on `phrase-in-response.txt`).
1084
- *
1085
- * {@link OPENCODE_PROMPT_PATTERN} stays as it is: `response-checker` and
1086
- * `OPENCODE_SKIP_PATTERNS` want the bare phrase wherever it lands, because they
1087
- * are deleting the row from an extracted response, not judging a session.
1088
- *
1089
- * opencode 1.18.31 以降は見本文を U+2026(`…`)で描く。ASCII の `...` も実行ファイルに
1090
- * 残っているので両方を受け付ける(Issue #2915、2026-09-28 に実行ファイルから確認)。
1091
- */
1092
- exports.OPENCODE_IDLE_COMPOSER_PATTERN = /^[^\S\n]*[\u2502\u2503][^\S\n]*Ask anything(?:\.\.\.|\u2026)/m;
1093
- /**
1094
- * OpenCode prompt pattern after response completion (Issue #379)
1095
- * Shows "tab agents ctrl+p commands" in the TUI status bar after a response finishes.
1096
- * Used as extraction stop condition in response-poller.ts [D2-003].
1097
- */
1098
- exports.OPENCODE_PROMPT_AFTER_RESPONSE = /tab agents\s+ctrl\+p commands/;
1099
- /**
1100
- * OpenCode thinking/processing pattern (Issue #379)
1101
- * OpenCode TUI shows "Thinking:" prefix while the Ollama model is generating a response.
1102
- * Used by detectThinking() to determine if the tool is actively processing.
1103
- */
1104
- exports.OPENCODE_THINKING_PATTERN = /Thinking:/;
1105
- /**
1106
- * OpenCode loading indicator pattern (Issue #379)
1107
- * Shows a series of 4+ filled square characters (U+2B1D) during initial loading/model warm-up.
1108
- * Filtered from response extraction via OPENCODE_SKIP_PATTERNS.
1109
- */
1110
- exports.OPENCODE_LOADING_PATTERN = /\u2B1D{4,}/;
1111
- /**
1112
- * OpenCode's Build summary LINE, in either of the two forms it is drawn in
1113
- * (Issue #379, corrected by Issue #1893).
1114
- *
1115
- * **This is a line filter, not completion evidence.** It matches
1116
- * `▣ <Action> · <model>` with the duration OPTIONAL, and opencode 1.18 draws
1117
- * that duration-less form on a step that is still in flight -- so a frame this
1118
- * pattern matches may be mid-turn, waiting on a permission dialog, or aborted.
1119
- * Use {@link OPENCODE_TURN_COMPLETE_PATTERN} to decide that a turn has finished.
1120
- *
1121
- * The docstring that stood here until #1893 claimed the opposite ("short
1122
- * responses may omit the timing portion"). Measured against opencode 1.18.21 at
1123
- * the production 80x200 geometry, that is wrong in both directions:
1124
- *
1125
- * - a 2.3-second answer still carries its duration
1126
- * (`▣ Build · GPT-5.6 Luna · 2.3s`, `opencode-live-1893/turn-complete-short.txt`),
1127
- * so no completed turn needs the duration-less branch;
1128
- * - the duration-less form is what opencode leaves on screen while a tool call
1129
- * waits for permission and after a rejected one
1130
- * (`opencode-live-1893/permission-bash.txt`, `…/turn-aborted-no-duration.txt`).
1131
- *
1132
- * Kept loose because three callers want the LINE rather than the verdict:
1133
- * `tui-accumulator.ts`, `response-cleaner.ts` and `polling/response-checker.ts`
1134
- * all use it to drop the summary row from an extracted response, and the
1135
- * mid-step row has to be dropped too. #1911 removed the one caller that used it
1136
- * as a turn BOUNDARY rather than a line filter (the "second-to-last ▣" anchor,
1137
- * replaced by {@link findOpenCodeUserEchoEnd}); the name is left alone because
1138
- * the remaining three callers all want the line.
1139
- */
1140
- exports.OPENCODE_RESPONSE_COMPLETE = /\u25A3\s+\w+\s+·\s+\S+(?:\s+·\s+(?:[\d]+h\s*)?(?:[\d]+m\s*)?[\d.]+s)?/;
1141
- /**
1142
- * OpenCode's finished-turn marker: the Build summary line WITH its duration
1143
- * (Issue #1893).
1144
- *
1145
- * `▣ Build · GPT-5.6 Luna · 5.2s`. This is the one tool-specific completion
1146
- * marker design rule D1 recognises today
1147
- * (`docs/design/multi-agent-state-architecture.md` §4 D1 decision 1, item 1),
1148
- * and the duration is the whole of what makes it positive evidence: opencode
1149
- * prints the same row without a duration while a step is still open, which is
1150
- * how a session parked on a permission dialog was published as
1151
- * `ready`/`opencode_response_complete` (#1893) and how `isOpenCodeComplete`
1152
- * saved the dialog body as if it were an answer.
1153
- *
1154
- * The model segment is `[^·\n]+` rather than `\S+` because real model names
1155
- * carry spaces (`GPT-5.6 Luna`): with `\S+` the optional-duration group of
1156
- * {@link OPENCODE_RESPONSE_COMPLETE} could never reach the duration on a
1157
- * two-word model, so "with duration" and "without duration" were the same match
1158
- * there. Excluding the middle dot rather than allowing anything keeps the
1159
- * quantifier unable to swallow its own delimiter (no nested/ambiguous
1160
- * quantifier -- ReDoS safe), and `.`/`[^·\n]` never cross a line without the
1161
- * `m` flag, so the duration has to be on the marker's own row.
1162
- *
1163
- * Durations observed: `2.3s`, `5.2s`, `45.2s`; the `Nh`/`Nm` prefixes are
1164
- * inherited from the #379 pattern and kept for long turns.
1165
- */
1166
- exports.OPENCODE_TURN_COMPLETE_PATTERN = /\u25A3\s+\w+\s+·\s+[^·\n]+·\s+(?:\d+h\s*)?(?:\d+m\s*)?[\d.]+s/;
1167
- /**
1168
- * OpenCode's permission dialog, anchored on its button row (Issue #1893).
1169
- *
1170
- * opencode 1.18 asks for tool permission with a bottom-anchored box whose last
1171
- * interactive row is a horizontal button strip:
1172
- *
1173
- * ```
1174
- * ┃ △ Permission required
1175
- * ┃ # Shell command
1176
- * ┃ $ ls -la
1177
- * ┃ Allow once Allow always Reject ctrl+f fullscreen ⇆ select enter con
1178
- * ```
1179
- *
1180
- * Nothing in the detection layer saw it before #1893: it carries no number, no
1181
- * `(y/n)`, and no "press enter to confirm" footer, so `detectPrompt` answers
1182
- * `isPrompt: false` and the status detector fell through to the Build marker
1183
- * above it. The row is matched as POSITIVE evidence that a decision is pending
1184
- * (design rule D1) -- it is the affordance itself, not the absence of a busy
1185
- * marker.
1186
- *
1187
- * **Match this against the ANSI-stripped frame BEFORE {@link stripBoxDrawing}**,
1188
- * exactly like {@link OPENCODE_IDLE_COMPOSER_PATTERN}: the leading `┃` (or
1189
- * `│` on a lighter border style) is what says the row belongs to the dialog box
1190
- * rather than to a response body that happens to quote the labels -- the
1191
- * "the phrase is on screen somewhere" inference #1883 had to remove.
1192
- *
1193
- * Deliberately NOT anchored on:
1194
- *
1195
- * - `enter confirm`, which is truncated to `enter con` at opencode's own 80
1196
- * column layout (measured);
1197
- * - `△ Permission required` alone, which is a heading rather than an
1198
- * affordance and survives in the fullscreen (`ctrl+f`) view whose key handling
1199
- * was not measured.
1200
- *
1201
- * The three labels are the same for the `bash` and the `edit` dialog (measured:
1202
- * `permission-bash.txt`, `permission-edit.txt`), and the strip is repainted away
1203
- * the moment the dialog is answered, so a matched row is never scrollback
1204
- * (`turn-aborted-no-duration.txt` holds no `Allow once`).
1205
- *
1206
- * The whitespace runs are `[^\S\n]` (horizontal only) for the reason #1883
1207
- * documents: plain `\s` crosses newlines under the `m` flag and would pair a
1208
- * gutter on one row with labels several rows below it.
1209
- */
1210
- exports.OPENCODE_PERMISSION_PATTERN = /^[^\S\n]*[\u2502\u2503][^\S\n]*Allow once[^\S\n]+Allow always[^\S\n]+Reject\b/m;
1211
- /**
1212
- * OpenCode's busy footer, in BOTH of the spellings it is drawn in (Issue #379,
1213
- * widened by Issue #1894).
1214
- *
1215
- * opencode 1.18 needs Escape TWICE to abort a turn, and the first press does not
1216
- * abort anything -- it re-labels the footer:
1217
- *
1218
- * ```
1219
- * ⬝⬝⬝⬝⬝⬝⬝⬝ esc interrupt 6.5K (1%) · $0.00 ctrl+p commands
1220
- * ⬝■■■■■■⬝ esc again to interrupt 7.2K (1%) · $0.00 ctrl+p commands
1221
- * ```
1222
- *
1223
- * Measured on opencode 1.18.21 at the production 80x200 geometry, sampling the
1224
- * footer every ~360 ms after a single Escape: the second spelling is up from
1225
- * 0.31 s to 4.71 s and the row is back to `esc interrupt` at 5.07 s -- a
1226
- * five-second window, exactly as long as the second-press deadline
1227
- * ({@link OPENCODE_INTERRUPT_SECOND_ESCAPE_DELAY_MS} is sized against it). The
1228
- * generation continues throughout; the turn ran to a natural
1229
- * `▣ Build · GPT-5.6 Luna · 11.3s`, 3 runs out of 3.
1230
- *
1231
- * Before #1894 those five seconds matched nothing at all: `detectSessionStatus`
1232
- * lost branch A and fell through to `running`/`default` while the frame was
1233
- * fresh, and to `ready`/`no_recent_output` once the poller's
1234
- * `lastOutputTimestamp` aged past `STALE_OUTPUT_THRESHOLD_MS` -- both of them
1235
- * `statusEvidence: 'none'`, i.e. a generating session
1236
- * published with no evidence and, on the second path, as FINISHED. That is the
1237
- * "vocabulary changed, so `ready` came back" failure design rule D1 names
1238
- * (`docs/design/multi-agent-state-architecture.md` §4 D1, row #1894), and the
1239
- * row is the same positive busy evidence in either spelling.
1240
- *
1241
- * The optional group is `(?:again to )?` rather than a looser `.*` on purpose:
1242
- * it matches the two measured strings and nothing between an `esc` and an
1243
- * `interrupt` several words apart. Linear, no nested quantifiers -- ReDoS safe.
1244
- *
1245
- * Filtered from response extraction via OPENCODE_SKIP_PATTERNS, which shares
1246
- * this constant: the widened row is dropped from a saved answer for the same
1247
- * reason the narrow one was.
1248
- */
1249
- exports.OPENCODE_PROCESSING_INDICATOR = /esc (?:again to )?interrupt/;
1250
- /**
1251
- * OpenCode's composer bottom border: ` ╹▀▀▀▀▀▀…` (Issue #1911).
1252
- *
1253
- * `╹` (heavy up) is the corner opencode joins the input box's `┃`
1254
- * gutter to, and the `▀` run is the box's bottom edge. It is the one row of
1255
- * the bottom-anchored chrome that can never appear inside a response body, which
1256
- * makes it the anchor {@link findOpenCodeChromeStart} walks up from.
1257
- */
1258
- exports.OPENCODE_COMPOSER_BOTTOM_BORDER = /^[^\S\n]*╹▀{4,}/;
1259
- /**
1260
- * A row that belongs to one of opencode's boxes, matched by its own gutter
1261
- * (Issue #1911).
1262
- *
1263
- * opencode draws three different boxes with the same `┃` gutter (`│`
1264
- * on a lighter border style): the echoed USER PROMPT in the transcript, the
1265
- * COMPOSER pinned to the bottom of the pane, and the PERMISSION DIALOG that
1266
- * replaces the composer. Which one a matched row belongs to is decided by where
1267
- * it sits, not by what it says — see {@link findOpenCodeChromeStart} and
1268
- * {@link findOpenCodeUserEchoEnd}.
1269
- *
1270
- * **Match against the ANSI-stripped frame BEFORE {@link stripBoxDrawing}**,
1271
- * which removes the very gutter this anchors on.
1272
- */
1273
- exports.OPENCODE_GUTTER_ROW_PATTERN = /^[^\S\n]*[│┃]/;
1274
- /**
1275
- * An echoed user prompt row: a gutter row that carries text (Issue #1911).
1276
- *
1277
- * The echo block opencode draws for a submitted message is a blank gutter row,
1278
- * one or more gutter rows holding the message, and another blank gutter row.
1279
- * This matches the middle ones.
1280
- */
1281
- exports.OPENCODE_USER_ECHO_PATTERN = /^[^\S\n]*[│┃][^\S\n]*\S/;
1282
- /**
1283
- * The status cell of opencode's bottom footer (Issue #1911).
1284
- *
1285
- * Measured at the production 80x200 geometry the footer reads
1286
- * `<cwd> 6.4K (1%) · $ctrl+p` / `commands` while idle and
1287
- * `⬝⬝⬝⬝⬝⬝⬝⬝ esc interrupt 6.3K (1%) · $0.00 ctrl+p commands` while running:
1288
- * a context-usage cell followed by the cost sigil. Neither
1289
- * {@link OPENCODE_PROMPT_AFTER_RESPONSE} (`tab agents ctrl+p commands`, which
1290
- * opencode only prints on the FIRST idle frame, before any turn has run) nor
1291
- * {@link OPENCODE_PROCESSING_INDICATOR} covers the idle form, so this row used
1292
- * to be saved as part of the assistant's reply.
1293
- *
1294
- * This is a SECONDARY net. The cwd that shares the row wraps over up to three
1295
- * further rows that carry no signature at all, so the footer is removed
1296
- * structurally by {@link findOpenCodeChromeStart}; this pattern only catches the
1297
- * signed row when that boundary is not available (e.g. a caller holding a
1298
- * fragment rather than a whole pane).
1299
- *
1300
- * Linear, no nested quantifiers — ReDoS safe.
1301
- */
1302
- exports.OPENCODE_FOOTER_STATUS_PATTERN = /\d+(?:\.\d+)?[KMGT]?\s+\(\d+%\)\s+·\s+\$/;
1303
- /**
1304
- * Locate the start of opencode's bottom-anchored chrome within a captured pane
1305
- * (Issue #1911).
1306
- *
1307
- * opencode runs in the alternate screen and reserves the last rows of the pane
1308
- * for chrome that is never transcript content:
1309
- *
1310
- * ```
1311
- * ┃ ← composer box (blank rows + model row),
1312
- * ┃ Build · GPT-5.6 Luna … or the permission dialog that replaces it
1313
- * ╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ← composer bottom border
1314
- * /private/tmp/…-share- 6.4K (1%) · $ctrl+p ← footer, cwd wrapped
1315
- * work-github-kewton-… commands over up to three rows
1316
- * ```
1317
- *
1318
- * Everything from the returned index down is chrome. This is the opencode twin
1319
- * of {@link findClaudeChromeStart} and exists for the same reason (#1289): the
1320
- * footer's cwd rows and the composer's model row were reaching saved responses,
1321
- * which is defect 1 of #1911.
1322
- *
1323
- * Found structurally rather than by matching footer text, because the footer's
1324
- * text is opencode's to change and two of its four rows (the cwd continuations)
1325
- * are an arbitrary filesystem path with no signature at all.
1326
- *
1327
- * @param lines - Captured pane lines, ANSI-stripped, box drawing intact.
1328
- * Trailing blank rows are tolerated.
1329
- * @returns Index of the first chrome row, or -1 when no chrome is recognisable.
1330
- */
1331
- function findOpenCodeChromeStart(lines) {
1332
- let last = lines.length - 1;
1333
- while (last >= 0 && lines[last].trim() === '')
1334
- last--;
1335
- if (last < 0)
1336
- return -1;
1337
- const walkUpGutter = (from) => {
1338
- let top = from;
1339
- while (top - 1 >= 0 && exports.OPENCODE_GUTTER_ROW_PATTERN.test(lines[top - 1]))
1340
- top--;
1341
- return top;
1342
- };
1343
- // The composer's bottom border. Searched from the last row upwards over the
1344
- // WHOLE pane rather than over the few rows the footer occupies: on the boot
1345
- // screen opencode centres the composer under its banner (row ~99 of 200) while
1346
- // the footer stays pinned to the bottom, so a window sized for the footer
1347
- // misses the border entirely (measured, `opencode-live-1883/boot-idle.txt`).
1348
- for (let i = last; i >= 0; i--) {
1349
- if (exports.OPENCODE_COMPOSER_BOTTOM_BORDER.test(lines[i])) {
1350
- return walkUpGutter(i);
1351
- }
1352
- }
1353
- // No border: the permission dialog draws over the composer and its own box
1354
- // runs to the last row of the pane (measured, `opencode-live-1893/permission-*.txt`).
1355
- if (exports.OPENCODE_GUTTER_ROW_PATTERN.test(lines[last])) {
1356
- return walkUpGutter(last);
1357
- }
1358
- return -1;
1359
- }
1360
- /**
1361
- * Locate the last row of the NEWEST echoed user prompt in a captured pane
1362
- * (Issue #1911).
1363
- *
1364
- * The turn currently being answered starts on the row after this one, so it is
1365
- * both the extraction anchor (`resolveExtractionStartIndex`'s opencode branch)
1366
- * and the floor the finished-turn marker has to sit below before a frame counts
1367
- * as a completed turn (`isOpenCodeComplete`).
1368
- *
1369
- * Before #1911 the anchor was "the second-to-last `▣ Build` row", which has two
1370
- * measured failure modes: on the first turn of a session there is no second row,
1371
- * so extraction fell back to line 0 and saved the whole pane; and the row it
1372
- * anchors on belongs to the PREVIOUS turn, so the echoed prompt of the current
1373
- * one was always included in the reply.
1374
- *
1375
- * @param lines - Captured pane lines, ANSI-stripped, box drawing intact.
1376
- * @param chromeStart - Result of {@link findOpenCodeChromeStart}; the search
1377
- * stops above it so the composer's and the permission dialog's own gutter rows
1378
- * are never read as an echoed prompt. Pass -1 when no chrome was found.
1379
- * @returns Index of the echo block's last row, or -1 when no echo is on screen
1380
- * (a turn whose head has scrolled out of the alternate-screen pane).
1381
- */
1382
- function findOpenCodeUserEchoEnd(lines, chromeStart) {
1383
- const limit = chromeStart >= 0 ? chromeStart : lines.length;
1384
- for (let i = limit - 1; i >= 0; i--) {
1385
- if (!exports.OPENCODE_USER_ECHO_PATTERN.test(lines[i]))
1386
- continue;
1387
- // The block's trailing blank gutter row(s) belong to the echo, not to the reply.
1388
- let end = i;
1389
- while (end + 1 < limit && exports.OPENCODE_GUTTER_ROW_PATTERN.test(lines[end + 1]))
1390
- end++;
1391
- return end;
1392
- }
1393
- return -1;
1394
- }
1395
- /**
1396
- * The rows of opencode's composer that hold the INPUT BUFFER (Issue #1906).
1397
- *
1398
- * opencode has no prompt marker — no `>` / `❯` / `›` anywhere near its input —
1399
- * so "is the message still sitting in the composer?" cannot be asked the way it
1400
- * is asked of every other TUI. What opencode has instead is a box, and the box
1401
- * has a fixed shape, measured at the production 80x200 geometry across every
1402
- * frame in `opencode-live-1883/`, `opencode-live-1893/` and
1403
- * `opencode-live-1906/`:
1404
- *
1405
- * ```
1406
- * ┃ ← buffer rows: blank when empty,
1407
- * ┃ Review the send path. one row per (wrapped) line of the
1408
- * ┃ Check newline handling. typed message, or the
1409
- * ┃ Report findings. `Ask anything...` placeholder on a
1410
- * ┃ session that has not answered yet
1411
- * ┃ Build · GPT-5.6 Luna GitHub Copilot ← the agent/model row, ALWAYS last
1412
- * ╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ← the box's bottom border
1413
- * ```
1414
- *
1415
- * The agent/model row is what makes a naive "any guttered row with text" read
1416
- * wrong: it always carries text, so the composer would never look empty. It is
1417
- * identified structurally — the last gutter row before the border — rather than
1418
- * by matching `Build ·` or a model name, both of which are opencode's to change
1419
- * and neither of which is stable across agents (`Build`, `Plan`, a custom agent).
1420
- *
1421
- * Anchored on the border, not on the bottom of the pane, because opencode
1422
- * centres the whole box under its banner before the first turn (row ~100 of 200)
1423
- * and only pins it to the bottom afterwards — see {@link findOpenCodeChromeStart},
1424
- * whose upward walk this shares.
1425
- *
1426
- * Returns `null` when no composer is on screen at all. That is the permission
1427
- * dialog (it replaces the composer and draws no bottom border — measured,
1428
- * `opencode-live-1893/permission-*.txt`), a full-screen overlay, or a session
1429
- * still starting.
1430
- *
1431
- * @param lines - Captured pane lines, ANSI-stripped, box drawing intact. Must be
1432
- * the WHOLE pane: a tail window sized for the other tools does not contain the
1433
- * box on a pre-first-turn frame.
1434
- * @returns The buffer rows with their gutters intact, or `null` when the
1435
- * composer is not on screen. An empty array means a box with no buffer row,
1436
- * which no measured frame produces.
1437
- */
1438
- function findOpenCodeComposerRows(lines) {
1439
- let border = -1;
1440
- for (let i = lines.length - 1; i >= 0; i--) {
1441
- if (exports.OPENCODE_COMPOSER_BOTTOM_BORDER.test(lines[i])) {
1442
- border = i;
1443
- break;
1444
- }
1445
- }
1446
- if (border < 0)
1447
- return null;
1448
- let top = border;
1449
- while (top - 1 >= 0 && exports.OPENCODE_GUTTER_ROW_PATTERN.test(lines[top - 1]))
1450
- top--;
1451
- // `top .. border - 1` are the box's gutter rows; the last of them is the
1452
- // agent/model row, which belongs to the chrome rather than to the buffer.
1453
- const modelRow = border - 1;
1454
- if (modelRow < top)
1455
- return null;
1456
- return lines.slice(top, modelRow);
1457
- }
1458
- /**
1459
- * A composer row with its gutter (and the padding either side) removed
1460
- * (Issue #1906). Blank for an empty buffer row.
1461
- */
1462
- function stripOpenCodeGutter(row) {
1463
- return row.replace(exports.OPENCODE_GUTTER_ROW_PATTERN, '').trim();
1464
- }
1465
- /**
1466
- * OpenCode TUI selection list pattern (Issue #473, narrowed by Issue #1896).
1467
- *
1468
- * Detects the fuzzy-search picker overlay opencode draws for `/models`,
1469
- * `/providers` and `/connect`, anchored on its HEADER ROW COMPLETE WITH the
1470
- * right-aligned `esc` hatch:
1471
- *
1472
- * ```
1473
- * Select model esc
1474
- *
1475
- * Search
1476
- *
1477
- * Recent
1478
- * ● GPT-5.6 Luna GitHub Copilot
1479
- * ```
1480
- *
1481
- * The `esc` is the picker's own dismiss affordance -- positive evidence that an
1482
- * overlay is open (design rule D1), rather than the presence of two English
1483
- * words somewhere on the pane. Until #1896 the pattern was the bare phrase, and
1484
- * `status-detector.ts` tests it against the WHOLE content area (up to ~200 rows,
1485
- * because the header can sit far above the last row when the list is long), so
1486
- * an agent that merely wrote `Select model to continue:` in its answer parked the
1487
- * session on `waiting` / `opencode_selection_list` for the rest of the session
1488
- * -- measured live on opencode 1.18.21,
1489
- * `opencode-live-1896/select-model-in-response.txt`.
1490
- *
1491
- * Requiring two or more spaces before `esc` is what separates the header row
1492
- * from prose: the picker right-aligns the hatch across the overlay's width
1493
- * (37 spaces in the measured frame), while a sentence that happens to end in
1494
- * "esc" would not.
1495
- *
1496
- * NOT additionally anchored on the `Search` row below the header: its distance
1497
- * from the header is unmeasured for the `Connect a provider` variant (that
1498
- * overlay needs an unconfigured provider to open, which the live probe could not
1499
- * produce without touching the operator's real credentials), and the header's
1500
- * own hatch is already the affordance.
1501
- *
1502
- * The header allowlist is deliberately unchanged, and Issue #2112 kept it that
1503
- * way rather than adding the five headings it found missing (`Select agent`,
1504
- * `Sessions`, `Timeline`, `Commands`, and the palette's). #1896 left the
1505
- * question as "a separate change with its own live frames"; #2046 supplied the
1506
- * frames and the answer they gave is that a WIDER WORD LIST is the wrong shape.
1507
- * Three of those dialogs published `ready` / `opencode_response_complete` — the
1508
- * marker of the previous turn, still on the pane behind the overlay — so the
1509
- * damage was a false COMPLETION, not a missing NavigationButtons row, and no
1510
- * heading added here would have changed that ordering. The fix is a gate ahead
1511
- * of the completion branch that reads the overlay's LAYOUT
1512
- * (`lib/detection/opencode-modal-overlay.ts`, branch C2 of
1513
- * `tools/opencode/detect.ts`), which is also what keeps the prose false
1514
- * positives this pattern was narrowed for from coming back through a longer
1515
- * list.
1516
- *
1517
- * This pattern still runs, and still first: it is the reading that survives ANSI
1518
- * stripping, where the layout rule cannot see anything at all.
1519
- *
1520
- * The whitespace runs are `[^\S\n]` (horizontal only) for the reason Issue #1883
1521
- * documents: plain `\s` crosses newlines under the `m` flag, which would let a
1522
- * header on one row pair up with an `esc` several rows below it.
1523
- *
1524
- * Linear pattern, no nested quantifiers -- ReDoS safe (S4-001).
1525
- */
1526
- exports.OPENCODE_SELECTION_LIST_PATTERN = /^[^\S\n]*(?:Select[^\S\n]+(?:model|provider)|Connect[^\S\n]+a[^\S\n]+provider)[^\S\n]{2,}esc[^\S\n]*$/m;
1527
- /**
1528
- * [Issue #1495] Footer signature of Claude Code's `/model` local-settings overlay.
1529
- * Verified against a real Claude Code v2.1.218 capture, the footer reads:
1530
- * "Enter to set as default · s to use this session only · Esc to cancel"
1531
- *
1532
- * The overlay renders a ❯-marked numbered model list ("1. Default … 5. Haiku")
1533
- * under a "Select model" header that detectMultipleChoicePrompt() otherwise
1534
- * matches as a genuine multiple_choice prompt — which let Auto-Yes Enter-confirm
1535
- * a selection and silently change the user's default model. This "set as default"
1536
- * phrasing is unique to the model picker: genuine confirmation prompts never
1537
- * contain it (the trust dialog uses "Enter to confirm · Esc to cancel", Bash-tool
1538
- * approvals use "Esc to cancel · Tab to amend", AskUserQuestion uses
1539
- * "Enter to select · … to navigate"), so it is a safe exclusion signal.
1540
- *
1541
- * Linear pattern, no nested quantifiers — ReDoS safe (S4-001).
1542
- */
1543
- exports.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN = /Enter\s+to\s+set\s+as\s+default\b/i;
1544
- /**
1545
- * Claude CLI selection list footer pattern
1546
- * Detects Claude CLI's interactive selection prompts that require
1547
- * arrow key navigation and Enter to select/toggle.
1548
- *
1549
- * Matches footer instruction lines (known variants):
1550
- * "Enter to select · Tab/Arrow keys to navigate · Esc to cancel"
1551
- * "Enter to select · ↑/↓ to navigate · n to add notes · Esc to cancel"
1552
- * "Enter to confirm · Esc to exit" (legacy /model command footer)
1553
- * "Enter to set as default · s to use this session only · Esc to cancel"
1554
- * (/model command footer as of Claude Code v2.1.218 — Issue #1495)
1555
- *
1556
- * "←/→ to adjust · Enter to confirm · s for this session only · Esc to cancel"
1557
- * (/effort slider footer as of Claude Code v2.1.257 — Issue #3052; before
1558
- * 2.1.257 it was "Enter to confirm · Esc to cancel")
1559
- *
1560
- * The /effort branch keys on "Enter to confirm · s for this session only" rather
1561
- * than "←/→ to adjust": the `s` hint is unique to the pickers (the trust dialog's
1562
- * "Enter to confirm · Esc to cancel" and approval dialogs never carry it), and
1563
- * keeping the match under the `Enter to` prefix leaves the pattern a single
1564
- * linear alternation. Linear, no nested quantifiers — ReDoS safe.
1565
- *
1566
- * The "set as default" branch lets status-detector classify the `/model` overlay
1567
- * as a Claude selection list (NavigationButtons + ESC hatch, hasActivePrompt=false)
1568
- * once detectPrompt() no longer reports it as a prompt (see
1569
- * CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN).
1570
- */
1571
- exports.CLAUDE_SELECTION_LIST_FOOTER = /Enter\s+to\s+(?:select\s+.*to\s+navigate|confirm\s+·\s+(?:Esc|s\s+for\s+this\s+session\s+only)|set\s+as\s+default)/;
1572
- /**
1573
- * OpenCode TUI separator pattern (Issue #379)
1574
- * Matches lines composed entirely of box-drawing / TUI decoration characters.
1575
- * Covers: vertical lines (U+2503), box corners, horizontal lines, and other TUI elements.
1576
- */
1577
- exports.OPENCODE_SEPARATOR_PATTERN = /^[\u2503\u2579\u25A3\u2580\u2500\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C]+$/;
1578
- /**
1579
- * OpenCode skip patterns for response cleaning (Issue #379)
1580
- * Lines matching any of these patterns are filtered from extracted responses.
1581
- * Includes: TUI separators, loading indicators, Build summary prefix,
1582
- * status bar prompts, processing indicators, input prompt, the footer's
1583
- * context/cost status cell (Issue #1911), and pasted text markers.
1584
- */
1585
- exports.OPENCODE_SKIP_PATTERNS = [
1586
- exports.OPENCODE_SEPARATOR_PATTERN,
1587
- exports.OPENCODE_LOADING_PATTERN,
1588
- /^Build\s+/,
1589
- exports.OPENCODE_PROMPT_AFTER_RESPONSE,
1590
- exports.OPENCODE_PROCESSING_INDICATOR,
1591
- exports.OPENCODE_PROMPT_PATTERN,
1592
- exports.OPENCODE_FOOTER_STATUS_PATTERN,
1593
- exports.PASTED_TEXT_PATTERN,
1594
- ];
1595
- // =============================================================================
1596
- // OpenCode V2 (`opencode2`, Issue #2934)
1597
- // =============================================================================
1598
- //
1599
- // A separate tool id from `opencode` (Epic #2370, decision 1). Its TUI is drawn
1600
- // by the same toolkit and many rows look alike, but v1's constants are v1's:
1601
- // they are left exactly as they are, and v2 names its own here so a change on
1602
- // either side cannot move the other. Phase 1 reads only what the send path and
1603
- // the fallback status reader need; the approval dialog (`Always allow`, the
1604
- // reverse of v1's `Allow always`), the completion row and the session tabs are
1605
- // Phase 3's.
1606
- /**
1607
- * OpenCode V2's empty composer: the input box's `┃` gutter with the
1608
- * `Ask anything…` placeholder on the same row (2.0.18 draws U+2026; the ASCII
1609
- * `...` is accepted too, as for v1 since #2915).
1610
- *
1611
- * Measured on 2.0.18 at 80x200 (2026-09-28):
1612
- * ` ┃ Ask anything… "Fix broken tests"`. Match against the ANSI-stripped
1613
- * frame BEFORE `stripBoxDrawing`, which removes the gutter this anchors on.
1614
- */
1615
- exports.OPENCODE_V2_IDLE_COMPOSER_PATTERN = /^[^\S\n]*[\u2502\u2503][^\S\n]*Ask anything(?:\.\.\.|\u2026)/m;
1616
- /**
1617
- * OpenCode V2's footer, which ends in `ctrl+p commands` on every frame the
1618
- * TUI draws (before a turn: `<path>:<branch> shift+tab agents ctrl+p commands`;
1619
- * after one: `<path>:<branch> 8.8K (1%) ctrl+p commands`, 2.0.18).
1620
- *
1621
- * Proof that the TUI — and so its composer — is on screen, NOT that it is idle:
1622
- * a running turn keeps the footer.
1623
- */
1624
- exports.OPENCODE_V2_FOOTER_PATTERN = /ctrl\+p commands/;
1625
- /**
1626
- * OpenCode V2 working indicator: the footer's `esc interrupt` hint, drawn only
1627
- * while a turn is running (2.0.18).
1628
- */
1629
- exports.OPENCODE_V2_THINKING_PATTERN = /esc interrupt/;
1630
- /**
1631
- * Whether OpenCode V2's composer is on screen, so a send may type into it
1632
- * (Issue #2934, D6): the gutter-anchored placeholder, or the footer.
1633
- *
1634
- * @param text - ANSI-stripped capture, box drawing intact
1635
- */
1636
- function isOpencodeV2ComposerVisible(text) {
1637
- return exports.OPENCODE_V2_IDLE_COMPOSER_PATTERN.test(text) || exports.OPENCODE_V2_FOOTER_PATTERN.test(text);
1638
- }
1639
157
  /**
1640
158
  * The OpenCode V2 dialog title row and its reader (Issue #2971) live in a
1641
159
  * browser-safe leaf since Issue #2983, because the chat surface's dialog card
@@ -1645,1192 +163,219 @@ function isOpencodeV2ComposerVisible(text) {
1645
163
  var dialog_title_1 = require("./tools/opencode-v2/dialog-title");
1646
164
  Object.defineProperty(exports, "OPENCODE_V2_DIALOG_TITLE_PATTERN", { enumerable: true, get: function () { return dialog_title_1.OPENCODE_V2_DIALOG_TITLE_PATTERN; } });
1647
165
  Object.defineProperty(exports, "findOpencodeV2DialogTitle", { enumerable: true, get: function () { return dialog_title_1.findOpencodeV2DialogTitle; } });
1648
- /** OpenCode V2 rows that are chrome, never reply content. */
1649
- exports.OPENCODE_V2_SKIP_PATTERNS = [
1650
- exports.OPENCODE_SEPARATOR_PATTERN,
1651
- exports.OPENCODE_V2_FOOTER_PATTERN,
1652
- exports.OPENCODE_V2_THINKING_PATTERN,
1653
- /Ask anything(?:\.\.\.|\u2026)/,
1654
- /^Build\s+·/,
1655
- exports.PASTED_TEXT_PATTERN,
1656
- ];
1657
- /**
1658
- * Copilot prompt pattern (Issue #545)
1659
- * Copilot CLI shows "❯" followed by cursor/text hint:
1660
- * - "❯ [7m [0mType @ to mention files, # for issues/PRs, / for commands, or ? for"
1661
- * - "❯ " (bare prompt)
1662
- * Also matches "? " prefix for question prompts.
1663
- */
1664
- exports.COPILOT_PROMPT_PATTERN = /^[>❯]\s|^\?\s+/m;
1665
- /**
1666
- * Copilot thinking/processing pattern (Issue #545)
1667
- * Copilot CLI shows various action indicators during processing:
1668
- * - "Exploring repo (Esc to cancel · 2.3 KiB)"
1669
- * - "Reasoning ■■■ medium"
1670
- * - "... Thinking"
1671
- * - Tool use: "● Read package.json" / "◉ Mapping structure (Esc to cancel · 8.4 KiB)"
1672
- * Note: "Esc to cancel" alone is not used because trust dialog footer also contains it.
1673
- * Instead, match the action pattern with parenthesized context: "(Esc to cancel ·"
1674
- * Braille spinner characters (U+2800-U+28FF) are also checked.
1675
- *
1676
- * Issue #1897: the bare words `Generating` and `Processing` were dropped. This
1677
- * constant is also a member of {@link COPILOT_SKIP_PATTERNS}, so every
1678
- * alternative here doubles as a "delete this line from the saved response" rule
1679
- * and as the tail-window liveness test in `extractResponse`. Those two words are
1680
- * ordinary English -- a reply whose last line reads "Processing complete." had
1681
- * the line deleted AND pinned the turn to "still thinking" for as long as it was
1682
- * on screen, so the response was never saved at all. The remaining alternatives
1683
- * are all glyph- or punctuation-shaped and cannot occur in prose by accident.
1684
- * Nothing is lost as a running signal either: #1885 measured 0 matches for this
1685
- * whole pattern across 44 live generating frames of copilot 1.0.80, whose turn
1686
- * state is read from the status bar by {@link readCopilotStatusBar} instead.
1687
- */
1688
- exports.COPILOT_THINKING_PATTERN = /[\u2800-\u28FF]|\(Esc to cancel|Reasoning\s+[■▪▮]|\.\.\.\s+Thinking/;
1689
- /**
1690
- * Copilot CLI's bottom status bar while a turn is in flight (Issue #1885).
1691
- *
1692
- * Measured on copilot 1.0.80 at the production 200x1000 geometry
1693
- * (`tests/unit/lib/detection/fixtures/copilot-live-1885/`). The bar is the
1694
- * bottom row of the pane and reads, across 44 captured generating frames:
1695
- *
1696
- * " ● Working esc interrupt GPT-5.6 Terra"
1697
- * " ◉ Working · 1.5 KiB esc interrupt GPT-5.6 Terra"
1698
- *
1699
- * The leading glyph cycles through ● ◉ ◎ ○ and the byte counter appears only
1700
- * once the turn has produced output, so neither is anchored on. `esc interrupt`
1701
- * is the affordance hint copilot draws for as long as the turn can be
1702
- * interrupted -- it is on every generating frame and on every tool-execution
1703
- * frame -- which makes it the same signal opencode's
1704
- * {@link OPENCODE_PROCESSING_INDICATOR} rests on.
1705
- *
1706
- * It is matched against the STATUS BAR ROW ONLY, never a window
1707
- * (see {@link readCopilotStatusBar}). `status-vocabulary-in-response.txt` is a
1708
- * live frame where copilot was asked to print this vocabulary and answered
1709
- * " ● Working esc interrupt" as body text: a window match would have pinned that
1710
- * finished session to `running` for the rest of its life.
1711
- *
1712
- * No /g flag (S4-5: would make test() stateful). No quantifier over a
1713
- * character class that can match its neighbour (SEC4-001: ReDoS safe).
1714
- */
1715
- exports.COPILOT_WORKING_STATUS_PATTERN = /\besc\s+interrupt\b/;
1716
- /**
1717
- * Copilot CLI's bottom status bar while no turn is running (Issue #1885).
1718
- *
1719
- * The same row as {@link COPILOT_WORKING_STATUS_PATTERN}, in the state copilot
1720
- * paints when it is NOT working:
1721
- *
1722
- * " ← open sidebar · / commands · ? help · tab next tab GPT-5.6 Terra"
1723
- *
1724
- * This is copilot's positive completion evidence under design rule D1
1725
- * (`docs/design/multi-agent-state-architecture.md` §4 D1 decision 1, item 2):
1726
- * the key-hint bar and the working bar are two renderings of one row, so seeing
1727
- * the hints is an affirmative observation that the turn is over -- not the
1728
- * absence of a busy marker somewhere on screen. The composer cannot carry that
1729
- * evidence on copilot: `❯` between its two full-width rules is drawn during
1730
- * generation too (measured on every frame of the running fixtures), which is
1731
- * exactly why the always-visible prompt used to win at step 3 of
1732
- * `detectSessionStatus` and report a generating session as ready.
1733
- *
1734
- * Two alternative spellings of one affordance, because copilot has reworded
1735
- * this row before: 1.0.80 shows "? help", and the pre-1.0.79 wording survives
1736
- * in {@link COPILOT_SKIP_PATTERNS} as "? for shortcuts". "/ commands" covers
1737
- * the slash-command hint independently, so a rewording of either half alone
1738
- * does not cost the tool its completion evidence.
1739
- *
1740
- * No /g flag (S4-5). Linear alternation, no nested quantifiers (SEC4-001).
1741
- */
1742
- exports.COPILOT_IDLE_STATUS_PATTERN = /\/\s+commands\b|\?\s+(?:help\b|for\s+shortcuts\b)/;
1743
- /**
1744
- * Read copilot's bottom status bar out of a captured frame (Issue #1885).
1745
- *
1746
- * Takes the whole frame rather than a row so the positional anchor -- "the
1747
- * status bar is the bottom row of the pane" -- cannot be lost at a call site.
1748
- * The scan stops at the first non-blank row from the bottom: if that row is
1749
- * neither state, this returns null and the caller has no evidence, which is the
1750
- * D1-correct answer rather than a guess. Two measured frames rely on it:
1751
- *
1752
- * - a permission dialog replaces the whole bottom of the pane with its box, so
1753
- * the bottom row is `╰───…` and neither pattern matches. The dialog then
1754
- * reaches `detectPrompt` and is reported as `waiting`, unchanged.
1755
- * - the `/model` picker ends in its own footer
1756
- * ("↑/↓ to navigate · … · enter to select · esc to cancel"), which is not the
1757
- * status bar either -- so this reports nothing about it and leaves that
1758
- * screen to the selection-list branch (Issue #1895's subject).
1759
- *
1760
- * @param contentLines - Frame rows, ANSI already stripped, in pane order
1761
- * @returns The state the bottom row announces, or null when it announces neither
1762
- */
1763
- function readCopilotStatusBar(contentLines) {
1764
- for (let i = contentLines.length - 1; i >= 0; i--) {
1765
- const row = contentLines[i];
1766
- if (row.trim() === '')
1767
- continue;
1768
- if (exports.COPILOT_WORKING_STATUS_PATTERN.test(row))
1769
- return 'working';
1770
- if (exports.COPILOT_IDLE_STATUS_PATTERN.test(row))
1771
- return 'idle';
1772
- return null;
1773
- }
1774
- return null;
1775
- }
1776
- /**
1777
- * Copilot separator pattern (Issue #545)
1778
- * Placeholder - to be updated after Phase 1 TUI investigation.
1779
- */
1780
- exports.COPILOT_SEPARATOR_PATTERN = /^─{10,}$/m;
1781
- /** How far above the bottom row copilot's closing rule may sit. */
1782
- const COPILOT_STATUS_BAR_MAX_ROWS = 2;
1783
- /** How many rows copilot's composer may span before the block stops looking like chrome. */
1784
- const COPILOT_COMPOSER_MAX_ROWS = 40;
1785
- /**
1786
- * One of the two rows that fence copilot's composer.
1787
- *
1788
- * Two renderings of one thing, because copilot redrew its composer between the
1789
- * builds this file is measured against (Issue #2269):
1790
- *
1791
- * - **1.0.80** fences it with a full-width horizontal rule, `─` to the pane's
1792
- * width, above and below.
1793
- * - **1.0.82** fences it with a half-block frame instead: `╻` (U+257B) followed
1794
- * by `▄` to the pane's width above, `╹` (U+2579) followed by `▀` below.
1795
- *
1796
- * The corner glyph is REQUIRED on the 1.0.82 forms and that is load-bearing, not
1797
- * decoration. 1.0.82 also boxes the echoed user prompt in the transcript between
1798
- * two dividers of the same half blocks — `tests/unit/lib/detection/fixtures/
1799
- * copilot-live-2269/turn-complete.txt` rows 10 and 12 — and those carry no
1800
- * corner. Accepting a bare `▄`/`▀` run here would let the newest echo's box be
1801
- * read as the composer, which would put `findCopilotChromeStart` ~985 rows too
1802
- * high and delete the reply along with the chrome.
1803
- */
1804
- const COPILOT_RULE_ROW = /^(?:─{10,}|╻▄{10,}|╹▀{10,})$/;
1805
- /**
1806
- * copilot's composer glyph, at the pane's own indent.
1807
- *
1808
- * `>` is the legacy spelling, `❯` is 1.0.80's. 1.0.82 draws no prompt glyph in
1809
- * the composer at all: the row is the frame's left edge, `┃` (U+2503), and the
1810
- * operator's text follows it (`copilot-live-2269/boot-idle.txt` row 998 is the
1811
- * bare glyph on an empty composer). `┃` is safe to accept here even though the
1812
- * reasoning block's rows also start with a vertical — that one is `│` (U+2502,
1813
- * {@link COPILOT_BOX_ROW_PATTERN}) — and in any case this test is only ever
1814
- * applied to the single row between two fence rows, never to a window.
1815
- */
1816
- const COPILOT_COMPOSER_GLYPH = /^ {0,2}[>❯┃]/;
1817
- /**
1818
- * Locate the start of copilot CLI's bottom-pinned chrome within a captured pane
1819
- * (Issue #1897).
1820
- *
1821
- * copilot 1.0.80 runs on the alternate screen and reserves the last five rows of
1822
- * the pane. Measured at the production 200x1000 geometry
1823
- * (`tests/unit/lib/detection/fixtures/copilot-live-1885/`), with the transcript
1824
- * ~970 rows above and nothing but padding in between:
1825
- *
1826
- * <cwd> [⎇ <branch>] Session: N AIC used
1827
- * ──────────────────── ← opening rule
1828
- * ❯ <composer>
1829
- * ──────────────────── ← closing rule
1830
- * ◉ Working · 1.5 KiB esc interrupt GPT-5.6 Terra ← status bar
1831
- *
1832
- * Issue #2269 re-measured the same five rows on 1.0.82, where the fence and the
1833
- * composer are drawn differently but the layout is unchanged:
1834
- *
1835
- * <cwd> Session: N AIC used
1836
- * ╻▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ← opening frame
1837
- * ┃<composer>
1838
- * ╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ← closing frame
1839
- * ← open sidebar · / commands · ? help · tab next tab GPT-5.6 Terra
1840
- *
1841
- * Both spellings live in {@link COPILOT_RULE_ROW} and
1842
- * {@link COPILOT_COMPOSER_GLYPH}; the search below is unchanged. Until they were
1843
- * added this function returned -1 on every 1.0.82 frame, `contentEnd` fell back
1844
- * to the whole pane, and the saved reply opened with 199 `▀` and ended with the
1845
- * `← open sidebar …` footer -- #2269's headline symptom.
1846
- *
1847
- * The boundary is found structurally -- closing rule, opening rule, composer
1848
- * glyph -- and never by matching the status bar's wording. That is the same
1849
- * reasoning {@link findClaudeChromeStart} records (#1289) plus the measurement
1850
- * behind {@link COPILOT_WORKING_STATUS_PATTERN}: copilot will print its own
1851
- * status-bar vocabulary as body text when asked to
1852
- * (`status-vocabulary-in-response.txt` holds ` ● Working esc interrupt` as a
1853
- * reply), so a vocabulary rule strong enough to delete the real bar also deletes
1854
- * that reply. Position is the only thing that separates them.
1855
- *
1856
- * Without this trim the bar is transcript as far as every consumer is concerned:
1857
- * it reached the TUI accumulator on every poll and was saved to History as the
1858
- * agent's answer (#1897's headline symptom, ` Working esc interrupt GPT-5.6
1859
- * Terra`), and its spinner glyph and byte counter change on every tick, so it
1860
- * also defeats the content dedup the alternate-screen tools rely on (#1268).
1861
- *
1862
- * @param lines - Captured pane rows, ANSI-bearing or not; trailing blanks tolerated
1863
- * @returns Index of the first chrome row, or -1 when the pane carries no chrome
1864
- * (a permission dialog draws its box over the whole bottom of the pane, so
1865
- * there is no composer and no bar -- those frames return -1 and are left to
1866
- * `detectPrompt`, exactly as {@link readCopilotStatusBar} leaves them)
1867
- */
1868
- function findCopilotChromeStart(lines) {
1869
- const isRule = (line) => COPILOT_RULE_ROW.test((0, ansi_1.stripAnsi)(line).trim());
1870
- let lastRow = lines.length - 1;
1871
- while (lastRow >= 0 && (0, ansi_1.stripAnsi)(lines[lastRow]).trim() === '')
1872
- lastRow--;
1873
- if (lastRow < 0)
1874
- return -1;
1875
- let closingRule = -1;
1876
- for (let i = lastRow; i >= Math.max(0, lastRow - COPILOT_STATUS_BAR_MAX_ROWS); i--) {
1877
- if (isRule(lines[i])) {
1878
- closingRule = i;
1879
- break;
1880
- }
1881
- }
1882
- if (closingRule < 0)
1883
- return -1;
1884
- let openingRule = -1;
1885
- for (let i = closingRule - 1; i >= Math.max(0, closingRule - COPILOT_COMPOSER_MAX_ROWS); i--) {
1886
- if (isRule(lines[i])) {
1887
- openingRule = i;
1888
- break;
1889
- }
1890
- }
1891
- if (openingRule < 0)
1892
- return -1;
1893
- // Confirm the fenced rows are the composer rather than a reply that happens to
1894
- // sit between two horizontal rules.
1895
- if (!COPILOT_COMPOSER_GLYPH.test((0, ansi_1.stripAnsi)(lines[openingRule + 1] ?? '')))
1896
- return -1;
1897
- // The row above the opening rule is copilot's cwd/branch/session header, which
1898
- // an overlay (the `/model` picker) replaces with a blank rather than with
1899
- // transcript -- measured on all five non-dialog fixtures.
1900
- return Math.max(0, openingRule - 1);
1901
- }
1902
- /**
1903
- * A row copilot draws as part of a box or a collapsed reasoning block
1904
- * (Issue #1897).
1905
- *
1906
- * 1.0.80 renders the model's private reasoning as a `⌄ Thought for 41s` header
1907
- * followed by rows that each begin `│ `, and draws dialogs inside `╭─╮`/`╰─╯`
1908
- * frames. {@link COPILOT_SKIP_PATTERNS} already carries a `[╭╮╰╯│]` rule, but
1909
- * `normalizeCopilotLine` deletes every U+2500..U+257F glyph *before* the skip
1910
- * patterns are applied, so by the time that rule runs the row it is meant to
1911
- * catch reads as ordinary prose -- which is how the TUI accumulator came to save
1912
- * copilot's chain-of-thought as the reply. Matched against the ANSI-stripped row,
1913
- * before normalisation.
1914
- *
1915
- * `─` is deliberately absent: a rule row is {@link COPILOT_SEPARATOR_PATTERN}'s
1916
- * job, and `── heading ──` is content.
1917
- */
1918
- exports.COPILOT_BOX_ROW_PATTERN = /^\s*[│└╰╭╮╯├┤┬┴┼]/;
1919
- /**
1920
- * copilot's collapsed-section header (Issue #1897).
1921
- *
1922
- * Measured on 1.0.80: `⌄ Thinking…` while the reasoning block is live, replaced
1923
- * by `⌄ Thought for 41s` once the turn moves on. Anchored on the `⌄` disclosure
1924
- * glyph rather than on the words, because "Thinking…" also occurs as body text
1925
- * -- `status-vocabulary-in-response.txt` contains exactly that row as a reply,
1926
- * and it must survive cleaning.
1927
- */
1928
- exports.COPILOT_REASONING_HEADER_PATTERN = /^\s*⌄\s/;
1929
- /**
1930
- * copilot's echo of the operator's own prompt in the transcript (Issue #1897).
1931
- *
1932
- * 1.0.80 draws every transcript row at the pane's one-column indent -- ` ❯ Reply
1933
- * with exactly the word: pong` -- while the composer at the bottom of the pane is
1934
- * at column 0. The bare `^[>❯]` form therefore never matched the echo, so
1935
- * `resolveExtractionStartIndex`'s copilot branch found no anchor, fell back to
1936
- * line 0, and handed the launch banner to the cleaner as the turn's reply.
1937
- *
1938
- * Bounded to two leading spaces rather than `\s*` so a quoted `> …` line inside a
1939
- * reply cannot be mistaken for a new turn boundary.
1940
- */
1941
- exports.COPILOT_USER_ECHO_PATTERN = /^ {0,2}[>❯]\s+\S/;
1942
- /**
1943
- * One of the half-block dividers copilot 1.0.82 boxes a transcript row with
1944
- * (Issue #2269).
1945
- *
1946
- * 1.0.80 drew the echoed prompt as a single bare ` ❯ <text>` row. 1.0.82 draws
1947
- * a full-width `▄` run above it and a full-width `▀` run below it
1948
- * (`copilot-live-2269/turn-complete.txt` rows 10 and 12):
1949
- *
1950
- * ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
1951
- * ❯ Reply with exactly the word: uat-run1 00:27
1952
- * ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
1953
- * ● uat-run1
1954
- *
1955
- * {@link COPILOT_SKIP_PATTERNS}' `/[█▘▝▖▗▔▄▌▐]/` rule already dropped the `▄`
1956
- * row. `▀` (U+2580) was not in that class, so the row BELOW the echo survived --
1957
- * and extraction starts one row past the echo, which made a wall of 199 `▀` the
1958
- * first line of every saved 1.0.82 reply.
1959
- *
1960
- * Deliberately not "add `▀` to that character class": this requires a run of ten
1961
- * or more and nothing else on the row, so a reply that happens to use a single
1962
- * half block as a glyph is untouched. The optional leading corner accepts the
1963
- * composer's own fence rows (`╻▄…` / `╹▀…`) as well, which is defence in depth
1964
- * only -- {@link findCopilotChromeStart} cuts those structurally, and this rule
1965
- * is what stops them reaching History if a future build moves the fence again.
1966
- */
1967
- exports.COPILOT_TRANSCRIPT_DIVIDER_PATTERN = /^\s*[╻╹]?[▀▄]{10,}\s*$/;
1968
- /**
1969
- * The verbs copilot prints on a tool row, as a regex alternation source.
1970
- *
1971
- * One list, shared by {@link COPILOT_TOOL_ROW_PATTERN} here and by
1972
- * `COPILOT_TOOL_ACTION_PATTERN` in `response-cleaner.ts`: the two match the same
1973
- * vocabulary behind different markers, and a verb added to one and not the other
1974
- * is a leak nothing fails on.
1975
- *
1976
- * `Asked user` / `Asking user` are Issue #2269's addition. copilot answers a
1977
- * prompt it cannot act on by calling its ask-user tool, and the row it leaves in
1978
- * the transcript (`● Asked user What would you like me to help with?`, measured
1979
- * in `copilot-live-2269/turn-oneword-echo-askuser.txt`) was being saved as the
1980
- * agent's reply -- the "launch screen as the answer" the Issue reports.
1981
- */
1982
- exports.COPILOT_TOOL_VERBS = 'Get|Read|Run|Search|Write|Edit|Delete|List|Create|Fetch|Explore|Execute|Install|Asked user|Asking user|Model changed to:';
1983
- /**
1984
- * copilot 1.0.82's tool row, whose marker is a file-type badge (Issue #2269).
1985
- *
1986
- * 1.0.80 drew every tool row as `● <Verb> …` and `COPILOT_TOOL_ACTION_PATTERN`
1987
- * reads exactly that. 1.0.82 puts a short badge for the file the tool touched in
1988
- * front of the verb instead, and only falls back to `●` for a type it has no
1989
- * badge for. Measured verbatim over
1990
- * `copilot-live-2269/turn-tool-badges.txt` and `turn-tool-rows.txt`:
1991
- *
1992
- * / Search "a.ts" 1 file found
1993
- * MD Read note.md L1:1 (1 line read)
1994
- * TS Read a.ts 1 line read
1995
- * PY Read c.py 1 line read
1996
- * {} Read b.json 1 line read
1997
- * ● Read d.txt 1 line read ← plain text keeps the 1.0.80 marker
1998
- *
1999
- * So the badge is `/`, `{}`, or two to four upper-case characters. Two is the
2000
- * floor on purpose: a one-character class would also match the pronoun in an
2001
- * English sentence ("I Read the file"), and no measured badge is one character.
2002
- * `●` is left to `COPILOT_TOOL_ACTION_PATTERN`, which is the rule that already
2003
- * owns it -- copilot's own prose is `● <text>` too (`● done`, `● 対象ファイルを
2004
- * 確認して内容を読み込みます。`), and only the verb list separates the two.
2005
- *
2006
- * The `$` badge is absent for the same reason: `$ Shell Print requested text 2
2007
- * lines…` opens a BLOCK whose command rows follow, which is
2008
- * `COPILOT_TOOL_INVOCATION_PATTERN`'s job, and `Shell` is not in the verb list
2009
- * anyway.
2010
- *
2011
- * No /g flag (S4-5: would make test() stateful). No quantifier nested inside
2012
- * another (SEC4-001: ReDoS safe).
2013
- */
2014
- exports.COPILOT_TOOL_ROW_PATTERN = new RegExp(`^\\s*(?:\\/|\\{\\}|[A-Z][A-Z0-9+#]{1,3})\\s+(?:${exports.COPILOT_TOOL_VERBS})[\\s:]`);
2015
- /**
2016
- * A wrapped continuation of the copilot transcript row above it (Issue #1897).
2017
- *
2018
- * Marker rows (` ❯ `, ` ● `, ` $ `, ` ⌄ `) carry one leading space; the rows they
2019
- * wrap onto are indented further and carry no marker, and a blank row separates
2020
- * one block from the next (measured on 1.0.80 at 200x1000). Used to walk past the
2021
- * tail of a long *user* prompt so the extracted reply does not open with the
2022
- * second half of the operator's own question.
2023
- */
2024
- exports.COPILOT_TRANSCRIPT_CONTINUATION_PATTERN = /^ {2,}\S/;
2025
- /**
2026
- * Rows that only copilot's first-launch screen draws (Issue #1897).
2027
- *
2028
- * The launch screen is a complete, idle frame: the composer is drawn, the status
2029
- * bar shows key hints, and nothing about it says "no turn has happened yet". So
2030
- * `extractResponse` classified it as a finished response and History opened with
2031
- * the banner as the agent's first message -- before the operator's first prompt.
2032
- *
2033
- * These anchors are only ever consulted for a frame that carries no echoed user
2034
- * prompt at all (see the copilot banner guard in `extractResponse`), which is
2035
- * what keeps a reply that quotes any of this wording from being suppressed.
2036
- */
2037
- exports.COPILOT_BOOT_BANNER_ANCHORS = [
2038
- /No copilot-instructions\.md found/,
2039
- /Copilot uses AI, so always check for mistakes\./,
2040
- /Copilot v\d[\w.]*\s+uses AI/,
2041
- /^\s*(?:●\s+)?GitHub Copilot\s+v\d/m,
2042
- /^\s*(?:●\s+)?Tip:\s*\//m,
2043
- /Describe a task to get started/,
2044
- /Prefer a visual workspace\?/,
2045
- // Issue #2269: copilot's greeting for a prompt it cannot act on. It arrives
2046
- // through the ask-user tool, so the row that reaches the transcript is
2047
- // `● Asked user Hi — what would you like to work on?` and the dialog's own
2048
- // body repeats the question. Both are chrome for a turn that produced no
2049
- // answer, and the operator saw the greeting saved as the agent's reply.
2050
- /what would you like to work on\?/i,
2051
- ];
2052
- /**
2053
- * The key-hint footer copilot draws under a picker, matched one ROW at a time
2054
- * (Issue #547; rewritten against live 1.0.80 frames by Issue #1895).
2055
- *
2056
- * The pattern this replaced --
2057
- * `/Search\s+\w+\.\.\.|Select\s+Model|to (?:navigate|select).*Enter to (?:select|confirm)/`
2058
- * -- matched **none** of the eleven pickers 1.0.80 opens (measured; the frames
2059
- * are in `tests/unit/lib/detection/fixtures/copilot-picker-1895/`, and #1885 /
2060
- * #1886 / #1913 reached the same result independently). `/model` renders
2061
- * `❯ Search models…` with U+2026 rather than three periods, no picker carries
2062
- * the words `Select Model`, and every footer spells its verbs in lower case.
2063
- *
2064
- * What all eleven footers do share is the shape of a key-hint bar: `·`-separated
2065
- * hints carrying either arrow-key navigation or a lower-case dismiss verb.
2066
- * Measured verbatim, bottom-most first:
2067
- *
2068
- * /model ↑/↓ to navigate · ←/→ reasoning effort · tab context window ·
2069
- * shift+tab group: recommended · enter to select · esc to cancel
2070
- * /agent n new agent · ? learn more · esc cancel
2071
- * /theme ↑/↓ to navigate · enter to select · esc to cancel
2072
- * /permissions 1-2 to select · ↑/↓ to navigate · enter to confirm · esc to cancel
2073
- * /skills ↑/↓ to navigate · enter to toggle · esc to close
2074
- * /mcp ↑/↓ to select · enter to show · a to add · esc to close
2075
- * /settings / search · ↑/↓ navigate · tab switch scope · enter edit ·
2076
- * ctrl+r reset · ctrl+e editor · esc close
2077
- * /statusline ↑/↓ nav · enter toggle · esc close
2078
- * /subagents ↑/↓ to navigate · space on/off · r reset · enter to select · esc to cancel
2079
- * /resume / search · ↑/↓ navigate · enter select · ←/→ switch tabs · r refresh ·
2080
- * x delete · s sort:relevance · esc cancel
2081
- * /session / search · ↑/↓ navigate · enter open · n new · tab switch tabs · a filter:all
2082
- *
2083
- * Neither half of the alternation is universal -- `/agent` has no list to walk
2084
- * so it prints no `↑/↓`, and `/session` offers no `esc` -- but every footer has
2085
- * one of them, so the union covers 11/11 while each individual disjunct stays
2086
- * specific enough to be worth requiring. The slash in `↑/↓` is optional only as
2087
- * a rewording tolerance: the synthetic frames pinned since Issue #547 spell it
2088
- * `↑↓`, and whether copilot ever drew it that way is not something this Issue
2089
- * measured. Every 1.0.80 footer above has the slash.
2090
- *
2091
- * Three deliberate narrowings keep this off ordinary text:
2092
- * - the `·` lookahead, so a sentence such as "press esc to cancel" is not a
2093
- * footer unless it is also a hint bar;
2094
- * - lower case, which is what 1.0.80 draws. `(Esc to cancel · 2.3 KiB)` -- the
2095
- * capitalised spelling in {@link COPILOT_THINKING_PATTERN} -- is a *progress*
2096
- * row, and it would otherwise satisfy both halves of this pattern;
2097
- * - `cancel|close` only, so the status bar's `esc interrupt`
2098
- * ({@link COPILOT_WORKING_STATUS_PATTERN}) is not a footer either.
2099
- *
2100
- * The narrowings are defence in depth; the load-bearing guard is positional and
2101
- * lives in {@link isCopilotSelectionFrame} -- copilot's own answer text can and
2102
- * does contain these exact rows (`picker-vocabulary-in-response.txt` is a live
2103
- * frame of it), so no window-scoped match on this vocabulary can be safe.
2104
- *
2105
- * No /g flag (S4-5: would make test() stateful). `[^\n]*` cannot cross a row and
2106
- * no quantifier is nested inside another (SEC4-001: ReDoS safe).
2107
- */
2108
- exports.COPILOT_SELECTION_FOOTER_PATTERN = /^(?=[^\n]*·)[^\n]*(?:↑\/?↓|\besc\s+(?:to\s+)?(?:cancel|close)\b)/m;
2109
- /**
2110
- * How many non-blank rows up from the bottom of the pane may carry the footer.
2111
- *
2112
- * Nine of the eleven pickers put it on the bottom row itself; `/agent` and
2113
- * `/subagents` draw their panel with a closing full-width rule underneath it,
2114
- * which puts the footer two non-blank rows up. Three is that measurement plus
2115
- * one row of slack -- deliberately far short of a window that could reach the
2116
- * transcript, which on the production 200x1000 geometry sits ~950 rows above.
2117
- */
2118
- const COPILOT_SELECTION_FOOTER_SCAN_ROWS = 3;
2119
- /**
2120
- * Whether the pane is sitting on one of copilot's pickers (Issue #1895).
2121
- *
2122
- * Takes the whole frame rather than a window for the same reason
2123
- * {@link readCopilotStatusBar} does: the evidence is positional, and a call site
2124
- * cannot be trusted to preserve "near the bottom of the pane" on its own. Two
2125
- * facts, both measured on 1.0.80, make the position sufficient:
2126
- *
2127
- * - **A picker replaces copilot's chrome.** In the idle and generating states
2128
- * the bottom five rows are cwd / rule / composer / rule / status bar; while a
2129
- * picker is up, none of them are drawn. So a frame whose bottom row is a
2130
- * status bar is not a picker, whatever its transcript says -- which is the
2131
- * whole of the false-positive half of Issue #1895. `detectSessionStatus`
2132
- * reads the same row for the running verdict at step 0.5, so checking it
2133
- * first here also fixes the order between the two branches: the status bar
2134
- * wins, and the picker branch only speaks when copilot has taken it away.
2135
- * - **An answerable dialog is drawn inside a box; a picker is not.** The
2136
- * folder-trust and permission dialogs wear the same lower-case
2137
- * `↑/↓ to navigate · enter to select · esc to cancel` footer, but every one of
2138
- * their rows reads `│ … │` and the bottom row is `╰─…─╯`. Skipping boxed rows
2139
- * keeps those on the prompt branch, where they belong: they are the agent
2140
- * blocked on the human (`hasActivePrompt: true`, exit 10 for `wait`), not a
2141
- * list the operator opened.
2142
- *
2143
- * @param contentLines - Frame rows, ANSI already stripped, in pane order
2144
- * @returns True when the bottom of the pane is a picker's key-hint footer
2145
- */
2146
- function isCopilotSelectionFrame(contentLines) {
2147
- if (readCopilotStatusBar(contentLines) !== null)
2148
- return false;
2149
- let scanned = 0;
2150
- for (let i = contentLines.length - 1; i >= 0; i--) {
2151
- const row = contentLines[i];
2152
- const trimmed = row.trim();
2153
- if (trimmed === '')
2154
- continue;
2155
- if (++scanned > COPILOT_SELECTION_FOOTER_SCAN_ROWS)
2156
- break;
2157
- // A boxed row belongs to a dialog, not a picker (see above).
2158
- if (trimmed.startsWith('│') || trimmed.endsWith('│'))
2159
- continue;
2160
- if (exports.COPILOT_SELECTION_FOOTER_PATTERN.test(row))
2161
- return true;
2162
- }
2163
- return false;
2164
- }
2165
- /**
2166
- * Anchors of Copilot CLI's first-launch "Confirm folder trust" dialog (Issue #1886).
2167
- *
2168
- * Recorded from copilot 1.0.80 (`tests/fixtures/copilot-folder-trust-1080.ts`):
2169
- * copilot asks this once per untrusted git repository, before anything else runs,
2170
- * and the whole dialog is drawn inside a box — every row reads `│ <content>`.
2171
- * That is why `COPILOT_PROMPT_PATTERN` (`^[>❯]\s`) does not match the frame at
2172
- * all and `waitForReady` used to spin its full 30-second window against it.
2173
- *
2174
- * Both anchors are required. One of them alone would also match this dialog's
2175
- * text quoted back inside a model response, and a false positive here does not
2176
- * merely mis-report a status: it sends a bare `1` into a live composer.
2177
- *
2178
- * The anchors live here rather than in `cli-tools/copilot` for the same reason
2179
- * codex's do (Issue #1829): the Auto-Yes poller judges the same screen through
2180
- * `detectPrompt`, and two copies of the wording would be two chances to disagree
2181
- * about what this dialog is.
2182
- */
2183
- exports.COPILOT_FOLDER_TRUST_ANCHORS = [
2184
- 'Confirm folder trust',
2185
- 'Do you trust the files in this folder?',
2186
- ];
2187
- /**
2188
- * The one option CommandMate may answer on the operator's behalf: `1. Yes`,
2189
- * which grants trust for THIS SESSION only.
2190
- *
2191
- * Matching the option text — not just the dialog — is the fail-safe. Option 2
2192
- * ("Yes, and remember this folder for future sessions") writes `trustedFolders`
2193
- * into `~/.copilot/config.json`, one file shared by every checkout on the
2194
- * machine (measured: answering `1` leaves that file byte-identical). If copilot
2195
- * ever reorders the list so that `1` is the remembering variant, this stops
2196
- * matching, nothing is sent, and the launch degrades to the pre-#1886 stall
2197
- * instead of silently persisting a trust grant.
2198
- *
2199
- * Written against the box-stripped frame, where the row reads `❯ 1. Yes`.
2200
- * `[ \t]*$` rather than `\s*$` so the trailing anchor cannot roll onto a later
2201
- * line and accept `1. Yes, and remember ...`.
2202
- */
2203
- exports.COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN = /^[ \t]*(?:[>❯][ \t]*)?1\.[ \t]+Yes[ \t]*$/m;
2204
- /**
2205
- * Key that selects {@link COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN}.
2206
- * Measured on 1.0.80: the digit confirms on its own — sending a trailing Enter
2207
- * would land on the composer that the dialog's dismissal reveals.
2208
- */
2209
- exports.COPILOT_FOLDER_TRUST_ANSWER_KEY = '1';
2210
- /**
2211
- * Whether the pane is sitting on the folder-trust dialog with the session-only
2212
- * option in first position.
2213
- *
2214
- * @param output - ANSI-stripped pane capture (box drawing still present)
2215
- * @returns True when both anchors and the `1. Yes` option row are present
2216
- */
2217
- function isCopilotFolderTrustDialog(output) {
2218
- if (!exports.COPILOT_FOLDER_TRUST_ANCHORS.every((anchor) => output.includes(anchor))) {
2219
- return false;
2220
- }
2221
- return exports.COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN.test(stripBoxDrawing(output));
2222
- }
2223
- /**
2224
- * Copilot skip patterns for response cleaning (Issue #545)
2225
- * Placeholder patterns - to be refined after Phase 1 TUI investigation.
2226
- */
2227
- exports.COPILOT_SKIP_PATTERNS = [
2228
- exports.PASTED_TEXT_PATTERN,
2229
- exports.COPILOT_SEPARATOR_PATTERN,
2230
- exports.COPILOT_THINKING_PATTERN,
2231
- // Issue #1895 replaced COPILOT_SELECTION_LIST_PATTERN with the row-scoped
2232
- // picker footer; the vocabulary it carried (`Search \w+...` / `Select Model`)
2233
- // is copilot's prose, not its chrome.
2234
- exports.COPILOT_SELECTION_FOOTER_PATTERN,
2235
- // Collapsed reasoning header (Issue #1897): "⌄ Thinking…" / "⌄ Thought for 41s"
2236
- exports.COPILOT_REASONING_HEADER_PATTERN,
2237
- // 1.0.82's transcript dividers and badge-marked tool rows (Issue #2269)
2238
- exports.COPILOT_TRANSCRIPT_DIVIDER_PATTERN,
2239
- exports.COPILOT_TOOL_ROW_PATTERN,
2240
- // Issue #2269: the anchored box-row rule, so the raw-row consumer agrees with
2241
- // the accumulator. `[╭╮╰╯│]` below is unanchored and omits `└`, which is the
2242
- // glyph copilot opens a detail row with (` └ Enable all permissions (tools,
2243
- // paths, and URLs)` under the launch screen's tip). `normalizeCopilotLine`
2244
- // deletes every U+2500..U+257F glyph, so by the time the skip patterns run in
2245
- // `cleanCopilotResponse` that row reads as prose -- which is how the ONE row
2246
- // of the 1.0.82 launch screen that no other rule caught became the agent's
2247
- // first reply. `extractCopilotContentLines` already tests this pattern before
2248
- // normalising (#1897); adding it here is what gives `extractResponse`, which
2249
- // never normalises, the same answer.
2250
- exports.COPILOT_BOX_ROW_PATTERN,
2251
- // Logo/banner lines
2252
- /^GitHub Copilot\s+v/,
2253
- /[█▘▝▖▗▔▄▌▐]/,
2254
- /[╭╮╰╯│]/,
2255
- // Status bar (branch + model display)
2256
- /\[⎇\s+\w[^\]]*\]/,
2257
- // Operation guide lines
2258
- /^shift\+tab\s/,
2259
- /^\?\s+for\s+shortcuts/,
2260
- /^ctrl\+[a-z]\s+\w/,
2261
- // Prompt lines
2262
- /^[❯>]\s*(Type\s+@|$)/,
2263
- // Tip/hint lines. Issue #2269 added the `●` marker: 1.0.82 draws the launch
2264
- // screen's tip as `● Tip: /allow-all`, and `cleanCopilotResponse` strips the
2265
- // bullet only AFTER the skip patterns have run, so the bare form never
2266
- // matched and the tip was the one banner row that reached History.
2267
- /^\s*(?:●\s+)?Tip:\s*\//,
2268
- // Issue #2269: copilot's own tab bar, the top row of every 1.0.82 frame. It is
2269
- // the second banner row the launch screen leaked (the logo, the disclaimer and
2270
- // the tip's `└ …` detail row are all caught by the glyph rules above), and
2271
- // between them they are the whole difference between "the launch screen cleans
2272
- // to nothing and cannot be saved" and "History opens with the banner". Spelled
2273
- // out in full rather than as a keyword so a reply that mentions one of these
2274
- // words is untouched.
2275
- /^\s*Current\s+Sessions\s+Issues\s+Pull requests\s+Gists\s*$/,
2276
- // Initial display text
2277
- /^Describe a task to get started/,
2278
- // Issue #571: Disclaimer, initialization message, environment info
2279
- /^Copilot uses AI, so always check for mistakes\.$/, // Disclaimer (full-line match to avoid filtering user content mentioning Copilot)
2280
- /^● 💡/, // Initialization hint message
2281
- /^● Environment loaded:/, // Environment info
2282
- ];
2283
- /**
2284
- * Vibe Local prompt pattern
2285
- * vibe-local (vibe-coder) shows `ctx:N% ❯` prompt when waiting for user input.
2286
- * The prompt line includes a context usage percentage prefix.
2287
- * Examples: "ctx:9% ❯", "ctx:30% ❯", "ctx:9% ❯ /model"
2288
- */
2289
- exports.VIBE_LOCAL_PROMPT_PATTERN = /ctx:\d+%\s*[>❯]/m;
2290
- /**
2291
- * Vibe Local thinking/processing pattern
2292
- * vibe-local shows spinner characters and status text while processing.
2293
- * Matches braille spinners, "Thinking", and tool execution indicators.
2294
- */
2295
- exports.VIBE_LOCAL_THINKING_PATTERN = /[\u2800-\u28FF]|Thinking|⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏|Running|Executing/;
2296
- /**
2297
- * The banner agy paints INTO its empty input box while a non-default permission
2298
- * mode is on (Issue #2592).
2299
- *
2300
- * Measured on agy 1.2.4 in the #2592 UAT
2301
- * (`tests/fixtures/agent-mode-2592/antigravity-{accept-edits,plan}.txt`, row 18,
2302
- * between the box's two rules):
2303
- *
2304
- * `> Accept-edits mode: file edits auto-approved (shift+tab to cycle)`
2305
- * `> Plan mode: research & plan only (shift+tab to cycle)`
2306
- *
2307
- * It takes the place of the bare `>` an empty box shows in default — it is a
2308
- * placeholder, not typed text — so a reader that only accepts the bare glyph
2309
- * reads an idle accept-edits / plan pane as "no input box at all". Before this
2310
- * pattern existed that is exactly what happened: the status detector fell through
2311
- * to its `default` floor (`running` + `isUnclassifiedActive`), `commandmate ls`
2312
- * showed a resting agent as running, `wait` never completed, and
2313
- * `isAntigravityReady` refused every send. #2592 made those two modes one click
2314
- * away, so the hole had to close with it.
2315
- *
2316
- * The structure is matched rather than the two sentences: `<Name> mode: <text>
2317
- * (shift+tab to cycle)`, anchored on the key hint agy ends every banner with, so
2318
- * a third mode needs no edit here. What stays out is anything a user could have
2319
- * typed into the box — the trailing hint is the part nobody types.
2320
- *
2321
- * Whole-row, no `/g`, no nested quantifiers (`[^\n]*` is followed by a literal).
2322
- */
2323
- exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE = '>[^\\S\\n]+[A-Z][A-Za-z-]*[^\\S\\n]mode:[^\\n]*\\(shift\\+tab to cycle\\)[^\\S\\n]*';
2324
- /** {@link ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE} as a single-row pattern. */
2325
- exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_PATTERN = new RegExp(`^${exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE}$`, 'm');
2326
- /**
2327
- * Antigravity (agy) interactive REPL prompt pattern (Issue #988)
2328
- * agy shows a bare ">" input box line when waiting for user input. The input box
2329
- * is always rendered (even while generating), so prompt presence alone does not
2330
- * mean "ready" — running vs idle is resolved together with the thinking pattern /
2331
- * footer status bar in status-detector.ts. (Confirmed on machine: line is "> ".)
2332
- *
2333
- * Issue #2592: or the mode banner that replaces the bare glyph in an EMPTY box
2334
- * while accept-edits / plan is on ({@link ANTIGRAVITY_COMPOSER_MODE_BANNER_PATTERN}).
2335
- * Every consumer of this pattern is asking "is agy's input box drawn and empty?"
2336
- * — the idle rule in `detection/tools/antigravity/detect.ts`, the survey guard
2337
- * beside it, `isAntigravityReady` (the send gate) and the liveness probe — and
2338
- * the answer does not depend on the permission mode, so the banner is folded in
2339
- * here once rather than at each of them.
2340
- */
2341
- exports.ANTIGRAVITY_PROMPT_PATTERN = new RegExp(`^(?:>\\s*|${exports.ANTIGRAVITY_COMPOSER_MODE_BANNER_SOURCE})$`, 'm');
2342
- /**
2343
- * Antigravity (agy) thinking/processing pattern (Issue #988)
2344
- * While generating, agy shows a braille spinner with "Generating..." in the
2345
- * conversation area and an "esc to cancel" hint in the footer status bar. When
2346
- * idle the footer shows "? for shortcuts" instead, so "esc to cancel" is a
2347
- * reliable running signal. Braille spinner chars (U+2800-U+28FF) also matched.
2348
- */
2349
- exports.ANTIGRAVITY_THINKING_PATTERN = /[\u2800-\u28FF]|Generating|esc to cancel/;
2350
- /**
2351
- * Antigravity (agy) separator pattern (Issue #988)
2352
- * agy draws turn separators and the input-box border with runs of U+2500 (─).
2353
- */
2354
- exports.ANTIGRAVITY_SEPARATOR_PATTERN = /^─{3,}$/m;
2355
- /**
2356
- * Antigravity (agy) selection list pattern (Issue #995, broadened in #997)
2357
- * Detects agy's interactive arrow-key selection TUIs (e.g. the "Switch Model"
2358
- * model picker, the "Do you want to proceed?" permission-approval menu). Their
2359
- * footer status bar renders "esc to cancel", which ANTIGRAVITY_THINKING_PATTERN
2360
- * also matches, so this pattern must be checked BEFORE thinking detection in
2361
- * status-detector.ts to keep the selection screen from being misreported as
2362
- * "generating".
2363
- *
2364
- * Matches (either is sufficient):
2365
- * - The "Switch Model" header of the model picker.
2366
- * - The "↑/↓ Navigate" arrow-key navigation hint, common to every agy
2367
- * selection TUI footer. Issue #995 originally required an "enter Select"
2368
- * hint too, but the permission-approval menu footer is
2369
- * "↑/↓ Navigate · tab Amend · ctrl+g … · ctrl+r Review" (no "enter Select"),
2370
- * so #997 relaxes this to the "↑/↓ Navigate" footer alone. This covers the
2371
- * Switch Model picker, permission-approval menus, and future agy selection
2372
- * TUIs in one shot, while staying agy-specific (the cliToolId === 'antigravity'
2373
- * guard in status-detector.ts keeps other tools unaffected).
2374
- *
2375
- * No /g flag (S4-5: would make test() stateful).
2376
- * No `.*` at all (SEC4-001: ReDoS safe — strictly safer than the #995 form).
2377
- */
2378
- exports.ANTIGRAVITY_SELECTION_LIST_PATTERN = /Switch Model|↑\/↓\s*Navigate/m;
2379
- /**
2380
- * The `↑/↓ Navigate` hint agy draws under every arrow-key screen it owns
2381
- * (Issue #2364; measured on agy 1.1.27, pane 200x1000, 2026-09-06).
2382
- *
2383
- * The same token {@link ANTIGRAVITY_SELECTION_LIST_PATTERN} matches, split out
2384
- * because the numbered-dialog reader anchors on THIS row and reads upward from
2385
- * it. It is the one thing all four measured agy dialogs share — the `Do you want
2386
- * to proceed?` command menu, the `Allow creation of this file?` file menu, the
2387
- * folder-trust screen and the `/model` picker all end on it — while the words
2388
- * after it differ on every one (`tab Amend · f full diff`, `tab Amend · ctrl+g
2389
- * edit/expand command`, `enter Confirm`, `←/→ Effort enter Select`).
2390
- */
2391
- exports.ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN = /↑\/↓\s*Navigate/;
2392
- /**
2393
- * One numbered option row of agy's dialogs: `> 1. Yes`, ` 4. No`,
2394
- * ` 2. No, deny creation`.
2395
- *
2396
- * The `>` gutter marks the highlighted row and is optional, because only one
2397
- * of the rows carries it.
2398
- */
2399
- exports.ANTIGRAVITY_NUMBERED_OPTION_PATTERN = /^\s*>?\s*\d+\.\s+\S/m;
2400
- /**
2401
- * The header of agy's `/model` picker (Issue #995).
2402
- *
2403
- * Kept as its own exclusion even though the picker's rows already fail the
2404
- * numbered-row test: the Issue #2364 rule is written as "footer + numbered rows
2405
- * + NOT the Switch Model picker", and the picker is the one agy screen whose
2406
- * misreading has a measured cost (#995: NavigationButtons vanished).
2407
- */
2408
- exports.ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN = /^\s*Switch Model\s*$/;
2409
- /**
2410
- * A row that ends the dialog block when reading UPWARD from the footer
2411
- * (Issue #2364).
2412
- *
2413
- * Everything agy draws above its own dialog panel carries one of these:
2414
- *
2415
- * - a horizontal rule (the turn separator, the input-box border, or the rule
2416
- * under the `Command` / `Create file` panel header);
2417
- * - a `>`-prefixed row that is not a numbered option — the echoed user prompt,
2418
- * the bare composer, the highlighted row of an UNNUMBERED picker (`> Gemini
2419
- * 3.8 Flash`, `> Yes, I trust this folder`) or of the slash-command popup
2420
- * (`> /add-dir Add a directory …`);
2421
- * - a `●` tool-call row, a `⎿` tool-result row or a `▸ Thought for …` row.
2422
- *
2423
- * Bounding the block at the nearest one is what keeps a numbered list in the
2424
- * MODEL'S PROSE, sitting in the transcript above an open picker or popup, from
2425
- * being adopted as that screen's options. Wrapped option labels never start
2426
- * with any of these on the measured frames: agy prints the command text inside
2427
- * the quotes of `… commands that start with '<cmd>'`, indented under the row
2428
- * that opened it.
2429
- *
2430
- * `stripBoxDrawing` blanks the rule rows before the response poller's copy of
2431
- * the frame reaches this rule; the `●` / `>` rows survive it, so the block is
2432
- * bounded the same way on both paths.
2433
- */
2434
- exports.ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN = /^\s*(?:─{3,}\s*$|[●⎿▸]|>(?!\s*\d+\.\s+\S))/;
2435
- /**
2436
- * How many rows above the `↑/↓ Navigate` footer the dialog block may reach.
2437
- *
2438
- * The tallest measured frame (`dialog-bash-wrapped.txt`: header, rule,
2439
- * `Requesting permission for:`, a four-row command, the question and four
2440
- * options of which two wrap onto three rows each) spans 18 rows; the cap leaves
2441
- * room for a longer command without letting a boundary-free frame drag the
2442
- * whole transcript into the block.
2443
- */
2444
- exports.ANTIGRAVITY_DIALOG_MAX_ROWS = 60;
2445
- /**
2446
- * agy's post-answer survey row (Issue #2364).
2447
- *
2448
- * Drawn once, in place of the composer, after some tool decisions:
2449
- *
2450
- * ```
2451
- * How's the CLI experience so far? Help us improve:
2452
- * [1] Good [2] Fine [3] Bad [0] Skip
2453
- *
2454
- * ? for shortcuts …
2455
- * ```
2456
- *
2457
- * Every option sits on ONE row in `[N] label` form and the screen takes a
2458
- * typed digit, not the arrow keys; there is no `↑/↓ Navigate` footer and no bare
2459
- * `>` composer, so before this pattern existed the frame reached the `default`
2460
- * floor — `running`, with the chat surface showing "generating" over a screen
2461
- * that was waiting for a keypress. Anchored to the whole row.
2462
- */
2463
- exports.ANTIGRAVITY_SURVEY_PATTERN = /^\s*\[1\]\s*Good\s+\[2\]\s*Fine\s+\[3\]\s*Bad\s+\[0\]\s*Skip\s*$/m;
2464
- /**
2465
- * Find the rows between the last boundary and the last `↑/↓ Navigate` footer
2466
- * (Issue #2364).
2467
- *
2468
- * The footer is searched from the bottom, so an older dialog still in the
2469
- * scrollback above a newer one is never the one read. Returns null when the
2470
- * frame has no footer at all.
2471
- *
2472
- * Also null when agy's input box is drawn BELOW that footer (Issue #2845): agy
2473
- * paints no `>` composer while a dialog is open — the dialog takes its place —
2474
- * so a footer with a composer under it is the model's reply quoting a dialog,
2475
- * or a dialog left in the scrollback, not a screen waiting for a keypress. It is
2476
- * the same reading `isAntigravitySurveyOpen` gives the survey row (#2364), and
2477
- * the composer is recognised by the same {@link ANTIGRAVITY_PROMPT_PATTERN}.
2478
- *
2479
- * @param lines - ANSI-stripped rows, box drawing optional
2480
- */
2481
- function locateAntigravityDialogRegion(lines) {
2482
- let footer = -1;
2483
- for (let i = lines.length - 1; i >= 0; i--) {
2484
- if (exports.ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN.test(lines[i])) {
2485
- footer = i;
2486
- break;
2487
- }
2488
- }
2489
- if (footer < 0)
2490
- return null;
2491
- for (let i = footer + 1; i < lines.length; i++) {
2492
- if (exports.ANTIGRAVITY_PROMPT_PATTERN.test(lines[i]))
2493
- return null;
2494
- }
2495
- const floor = Math.max(0, footer - exports.ANTIGRAVITY_DIALOG_MAX_ROWS);
2496
- let start = floor;
2497
- for (let i = footer - 1; i >= floor; i--) {
2498
- if (exports.ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN.test(lines[i])) {
2499
- start = i + 1;
2500
- break;
2501
- }
2502
- }
2503
- return { start, footer };
2504
- }
2505
- /**
2506
- * Is this frame one of agy's NUMBERED dialogs rather than one of its
2507
- * arrow-key-only pickers? (Issue #2270, re-measured by Issue #2364)
2508
- *
2509
- * Both kinds of screen share the `↑/↓ Navigate` footer that
2510
- * {@link ANTIGRAVITY_SELECTION_LIST_PATTERN} matches, which is why #997 could
2511
- * widen that pattern to cover the permission menu — and why the menu then
2512
- * resolved as `antigravity_selection_list`, `hasActivePrompt: false`. On the
2513
- * chat surface that reads as "a selection list is open, drive it from the
2514
- * terminal": the arrow buttons can only Enter the highlighted option 1, so
2515
- * options 2-4 became unreachable, while the poller and the push notification
2516
- * described the very same frame as a `multiple_choice` prompt.
2517
- *
2518
- * #2270 told the two apart by the question line, `Do you want to proceed?`,
2519
- * because that was the one dialog it had measured. #2364 measured a second one
2520
- * — agy 1.1.27's file-creation menu asks `Allow creation of this file?` above
2521
- * `1. Yes, allow creation` / `2. No, deny creation` — and it fell straight back
2522
- * into the selection-list reading. So the rule is now about the STRUCTURE the
2523
- * dialogs share and the pickers lack:
2524
- *
2525
- * - the `↑/↓ Navigate` footer,
2526
- * - at least two `N. label` rows between the nearest boundary row and that
2527
- * footer ({@link ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN}), and
2528
- * - no `Switch Model` header in that region.
2529
- *
2530
- * The Switch Model picker, the folder-trust screen and the slash-command popup
2531
- * all draw unnumbered rows (`> Gemini 3.8 Flash`, `> Yes, I trust this folder`,
2532
- * `> /add-dir …`), so they keep the #995 reading — and because a `>` row that
2533
- * is not numbered is itself a boundary, a numbered list in the transcript above
2534
- * one of them cannot be counted.
2535
- *
2536
- * True here means "hand the frame to the agy dialog reader"
2537
- * (`tools/antigravity/dialog.ts`), never "this is a prompt": a frame that
2538
- * passes this test and still fails to read is published as an unclassified
2539
- * frame, not as a selection list and never as "generating".
2540
- *
2541
- * Callers pass the same text they hand {@link ANTIGRAVITY_SELECTION_LIST_PATTERN}.
2542
- */
2543
- function isAntigravityNumberedDialog(text) {
2544
- const lines = text.split('\n');
2545
- const region = locateAntigravityDialogRegion(lines);
2546
- if (region === null)
2547
- return false;
2548
- let numberedRows = 0;
2549
- for (let i = region.start; i < region.footer; i++) {
2550
- const line = lines[i];
2551
- if (exports.ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN.test(line))
2552
- return false;
2553
- if (exports.ANTIGRAVITY_NUMBERED_OPTION_PATTERN.test(line))
2554
- numberedRows++;
2555
- }
2556
- return numberedRows >= 2;
166
+ const CLI_TOOL_PATTERN_TABLE = {
167
+ claude: {
168
+ promptPattern: patterns_7.CLAUDE_PROMPT_PATTERN,
169
+ separatorPattern: patterns_7.CLAUDE_SEPARATOR_PATTERN,
170
+ thinkingPattern: patterns_7.CLAUDE_THINKING_PATTERN,
171
+ skipPatterns: [
172
+ /^─{10,}$/, // Separator lines
173
+ /^[>❯]\s*$/, // Prompt line (legacy '>' and new '❯')
174
+ patterns_7.CLAUDE_THINKING_PATTERN, // Thinking indicators
175
+ /^\s*[⎿⏋]\s+Tip:/, // Tip lines
176
+ /^\s*Tip:/, // Tip lines
177
+ /^\s*\?\s*for shortcuts/, // Shortcuts hint
178
+ /to interrupt\)/, // Part of "esc to interrupt" message
179
+ pasted_text_1.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines] (Issue #212)
180
+ ],
181
+ // Full frame: their multiple-choice prompts with descriptions can exceed 15 lines: Codex
182
+ // approval prompts with long file lists, Claude "Yes, and don't ask again for:
183
+ // git commit -m …" options that embed full commit messages. `detectPrompt`
184
+ // applies its own 50-line window internally.
185
+ promptOptions: { requireDefaultIndicator: false },
186
+ fullFramePrompt: true,
187
+ },
188
+ codex: {
189
+ promptPattern: patterns_1.CODEX_PROMPT_PATTERN,
190
+ separatorPattern: patterns_1.CODEX_SEPARATOR_PATTERN,
191
+ thinkingPattern: patterns_1.CODEX_THINKING_PATTERN,
192
+ skipPatterns: [
193
+ /^─.*─+$/, // Separator lines
194
+ /^›\s*$/, // Empty prompt line
195
+ /^›\s+(Implement|Find and fix|Type)/, // New prompt suggestions
196
+ patterns_1.CODEX_THINKING_PATTERN, // Activity indicators
197
+ /^\s*\d+%\s+context left/, // Context indicator
198
+ /^\s*for shortcuts$/, // Shortcuts hint
199
+ /╭─+╮/, // Box drawing (top)
200
+ /╰─+╯/, // Box drawing (bottom)
201
+ // T1.3: Additional skip patterns for Codex
202
+ /•\s*Ran\s+/, // Command execution lines
203
+ /^\s*└/, // Tree output (completion indicator)
204
+ /^\s*│/, // Continuation lines
205
+ /\(.*esc to interrupt\)/, // Interrupt hint
206
+ pasted_text_1.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines] (Issue #212, defensive)
207
+ ],
208
+ // Full frame: their multiple-choice prompts with descriptions can exceed 15 lines: Codex
209
+ // approval prompts with long file lists, Claude "Yes, and don't ask again for:
210
+ // git commit -m …" options that embed full commit messages. `detectPrompt`
211
+ // applies its own 50-line window internally.
212
+ promptOptions: undefined,
213
+ fullFramePrompt: true,
214
+ },
215
+ gemini: {
216
+ promptPattern: patterns_9.GEMINI_PROMPT_PATTERN,
217
+ separatorPattern: /^[─━]{3,}$/m,
218
+ thinkingPattern: patterns_9.GEMINI_THINKING_PATTERN,
219
+ skipPatterns: [
220
+ patterns_9.GEMINI_PROMPT_PATTERN, // Prompt line (DRY: shared with GEMINI_PROMPT_PATTERN)
221
+ patterns_9.GEMINI_THINKING_PATTERN, // Thinking indicators
222
+ /^\s*$/, // Empty lines
223
+ /Gemini\s+\d+\.\d+/, // Version line
224
+ pasted_text_1.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
225
+ ],
226
+ promptOptions: undefined,
227
+ fullFramePrompt: false,
228
+ },
229
+ 'vibe-local': {
230
+ promptPattern: patterns_11.VIBE_LOCAL_PROMPT_PATTERN,
231
+ separatorPattern: /^[·]{10,}$/m, // vibe-local uses middle dot separators
232
+ thinkingPattern: patterns_11.VIBE_LOCAL_THINKING_PATTERN,
233
+ skipPatterns: [
234
+ patterns_11.VIBE_LOCAL_PROMPT_PATTERN, // Prompt line (ctx:N% ❯)
235
+ patterns_11.VIBE_LOCAL_THINKING_PATTERN, // Thinking indicators
236
+ /^\s*$/, // Empty lines
237
+ /vibe-local|vibe-coder/, // Version/banner lines
238
+ /ctx:\s*\d+%/, // Context usage indicator
239
+ /Model\s+\w/, // Model info line
240
+ /Engine\s+\w/, // Engine info line
241
+ /Mode\s+/, // Mode info line
242
+ /RAM\s+/, // RAM info line
243
+ /CWD\s+/, // Working directory line
244
+ /^[·]{10,}$/, // Middle dot separator lines
245
+ /✦\s*Ready/, // Status bar "Ready" indicator
246
+ /ESC:\s*stop/, // Status bar "ESC: stop" hint
247
+ pasted_text_1.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
248
+ ],
249
+ promptOptions: undefined,
250
+ fullFramePrompt: false,
251
+ },
252
+ opencode: {
253
+ promptPattern: patterns_3.OPENCODE_PROMPT_PATTERN,
254
+ separatorPattern: patterns_3.OPENCODE_SEPARATOR_PATTERN,
255
+ thinkingPattern: patterns_3.OPENCODE_THINKING_PATTERN,
256
+ skipPatterns: patterns_3.OPENCODE_SKIP_PATTERNS,
257
+ // [D2-006] OpenCode prompt "Ask anything..." does not use standard indicators (> / ❯),
258
+ // so requireDefaultIndicator must be false to avoid missing prompt detection.
259
+ //
260
+ // [Issue #1896] `hasNumberedDialogs: false` -- opencode 1.18 renders NO dialog
261
+ // that a typed number drives, so the generic numbered-list inference has
262
+ // nothing to find on its pane and every hit it scored was transcript text.
263
+ // Its two interactive surfaces were both measured at the production 80x200
264
+ // geometry and both are cursor-driven:
265
+ //
266
+ // - the permission dialog is a horizontal button strip
267
+ // ({@link OPENCODE_PERMISSION_PATTERN}, Issue #1893) driven by ←/→ + Enter;
268
+ // typing a number does nothing to it.
269
+ // - the pickers (`/models`, `/providers`, `/connect`, and the ctrl+p command
270
+ // palette) are fuzzy-search lists driven by ↑/↓ + Enter, with no numbers
271
+ // drawn at all. The first three are what
272
+ // {@link OPENCODE_SELECTION_LIST_PATTERN} names; the palette shares the
273
+ // chrome but not the header allowlist, and lands on `running` / `default`.
274
+ //
275
+ // Both keep their own POSITIVE detection in `status-detector.ts`, so `wait`
276
+ // still stops for them (exit 10 via `isSelectionListActive`) and the UI still
277
+ // renders NavigationButtons: nothing that could be answered before stops being
278
+ // answered. What ends is the false positive -- a response whose body ends in
279
+ // `1. / 2. / 3.` + a question was published as
280
+ // `waiting`/`prompt_detected`/`hasActivePrompt: true`, and Auto-Yes typed `1`
281
+ // into the composer and SENT IT as a user utterance (Issue #1896).
282
+ //
283
+ // `requireDefaultIndicator` is kept at its D2-006 value: it is the correct
284
+ // setting for opencode's ❯-less rendering should the numbered path ever be
285
+ // re-enabled, and it still describes the tool.
286
+ // Full frame: their multiple-choice prompts with descriptions can exceed 15 lines: Codex
287
+ // approval prompts with long file lists, Claude "Yes, and don't ask again for:
288
+ // git commit -m …" options that embed full commit messages. `detectPrompt`
289
+ // applies its own 50-line window internally.
290
+ promptOptions: { requireDefaultIndicator: false, hasNumberedDialogs: false },
291
+ fullFramePrompt: true,
292
+ },
293
+ copilot: {
294
+ promptPattern: patterns_13.COPILOT_PROMPT_PATTERN,
295
+ separatorPattern: patterns_13.COPILOT_SEPARATOR_PATTERN,
296
+ thinkingPattern: patterns_13.COPILOT_THINKING_PATTERN,
297
+ skipPatterns: patterns_13.COPILOT_SKIP_PATTERNS,
298
+ // [Issue #545] Copilot prompt pattern may not use standard indicators
299
+ // Full frame: their multiple-choice prompts with descriptions can exceed 15 lines: Codex
300
+ // approval prompts with long file lists, Claude "Yes, and don't ask again for:
301
+ // git commit -m …" options that embed full commit messages. `detectPrompt`
302
+ // applies its own 50-line window internally.
303
+ promptOptions: { requireDefaultIndicator: false },
304
+ fullFramePrompt: true,
305
+ },
306
+ antigravity: {
307
+ promptPattern: patterns_15.ANTIGRAVITY_PROMPT_PATTERN,
308
+ separatorPattern: patterns_15.ANTIGRAVITY_SEPARATOR_PATTERN,
309
+ thinkingPattern: patterns_15.ANTIGRAVITY_THINKING_PATTERN,
310
+ skipPatterns: patterns_15.ANTIGRAVITY_SKIP_PATTERNS,
311
+ // [Issue #999] Antigravity (agy) permission-approval menus highlight the
312
+ // default with an ASCII ">" (0x3E), not the "❯/●/›" indicators that
313
+ // DEFAULT_OPTION_PATTERN recognizes, and their footer is "↑/↓ Navigate"
314
+ // (no "press enter to confirm"). Under the default requireDefaultIndicator=true
315
+ // the Pass 1 gate rejects these menus, so Auto-Yes never responds. Treat agy
316
+ // like claude/opencode/copilot so Pass 2 collects its "1. Yes / … / N. No"
317
+ // options and reports isPrompt=true.
318
+ //
319
+ // [Issue #2364] The `↑/↓ Navigate` dialogs themselves no longer reach
320
+ // `detectPrompt` on either production path: `tools/antigravity/detect.ts`
321
+ // (status) and `detectPromptWithOptions` (response poller) both read them
322
+ // with `detectAntigravityNumberedDialogPrompt` first, because the generic
323
+ // multiple-choice pass reads one row per option and agy wraps a long command
324
+ // across several rows of one label. What this setting still serves is every
325
+ // OTHER numbered agy screen — `/feedback`'s `1-6 Select & Continue` menu is
326
+ // the measured one — which the generic pass reads as before.
327
+ promptOptions: { requireDefaultIndicator: false },
328
+ fullFramePrompt: false,
329
+ },
330
+ // Issue #2250: Command Code's layout is claude-shaped (inline transcript,
331
+ // `❯` composer fenced by two full-width rules) but the constants are its
332
+ // own. Sharing claude's would import the exact defect #2247 had to undo --
333
+ // claude's rules carry a startup-banner reading that keys on `v\d+\.\d+`
334
+ // and `|`, and Command Code prints its version into a `# Command Code
335
+ // v1.40.1` row on every launch.
336
+ 'command-code': {
337
+ promptPattern: patterns_17.COMMAND_CODE_PROMPT_PATTERN,
338
+ separatorPattern: patterns_17.COMMAND_CODE_SEPARATOR_PATTERN,
339
+ thinkingPattern: patterns_17.COMMAND_CODE_THINKING_PATTERN,
340
+ skipPatterns: patterns_17.COMMAND_CODE_SKIP_PATTERNS,
341
+ promptOptions: undefined,
342
+ fullFramePrompt: false,
343
+ },
344
+ // Issue #2934: OpenCode V2's own constants (see OPENCODE_V2_* above). The
345
+ // separator row is the same half-block rule v1 draws, so v1's pattern is
346
+ // reused as a value; nothing of v1's is changed.
347
+ 'opencode-v2': {
348
+ promptPattern: patterns_5.OPENCODE_V2_IDLE_COMPOSER_PATTERN,
349
+ separatorPattern: patterns_3.OPENCODE_SEPARATOR_PATTERN,
350
+ thinkingPattern: patterns_5.OPENCODE_V2_THINKING_PATTERN,
351
+ skipPatterns: patterns_5.OPENCODE_V2_SKIP_PATTERNS,
352
+ promptOptions: undefined,
353
+ fullFramePrompt: false,
354
+ },
355
+ };
356
+ /**
357
+ * The table's own row for an id, or undefined. The table is a plain object, so a
358
+ * name such as 'constructor' or '__proto__' must not reach Object.prototype.
359
+ */
360
+ function ownRowFor(cliToolId) {
361
+ return Object.prototype.hasOwnProperty.call(CLI_TOOL_PATTERN_TABLE, cliToolId)
362
+ ? CLI_TOOL_PATTERN_TABLE[cliToolId]
363
+ : undefined;
2557
364
  }
2558
365
  /**
2559
- * Antigravity (agy) skip patterns for response cleaning (Issue #988)
2560
- * Filters turn/input-box separators, the bare ">" input prompt, the idle status
2561
- * bar ("? for shortcuts ... <model>"), the thinking footer/spinner, banner block
2562
- * art, and pasted-text markers from extracted responses.
2563
- */
2564
- exports.ANTIGRAVITY_SKIP_PATTERNS = [
2565
- exports.ANTIGRAVITY_SEPARATOR_PATTERN, // Turn + input-box separators (─ runs)
2566
- /^>\s*$/, // Bare input prompt line
2567
- /^\?\s+for\s+shortcuts/, // Idle status bar (model name follows on the same line)
2568
- exports.ANTIGRAVITY_THINKING_PATTERN, // Spinner / Generating / "esc to cancel" footer
2569
- /[▄▀█▌▐]/, // Banner block art (defensive; normally above the user-prompt anchor)
2570
- exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
2571
- ];
2572
- /**
2573
- * Command Code status-line spinner glyphs (Issue #2250).
2574
- *
2575
- * The complete `getWaveSymbol` frame set, read off the shipped bundle
2576
- * (`command-code@1.40.1`, `dist/cli.mjs`):
2577
- * `["·","○","◇","☆","✧","☆","◇","○","⌘"]` — six distinct glyphs, cycled.
2578
- *
2579
- * Deliberately NOT folded into {@link CLAUDE_SPINNER_CHARS}. Command Code marks
2580
- * every assistant message with `⠶` (U+2836, see
2581
- * {@link COMMAND_CODE_RESPONSE_MARKER_PATTERN}); adding that braille glyph to a
2582
- * shared spinner class would make each reply's own first row read as "still
2583
- * generating", and adding these glyphs to claude's class would do the same to
2584
- * any claude reply that opens with `·`.
2585
- */
2586
- exports.COMMAND_CODE_SPINNER_CHARS = ['·', '○', '◇', '☆', '✧', '⌘'];
2587
- /**
2588
- * Command Code composer / dialog-cursor prompt pattern (Issue #2250).
2589
- *
2590
- * `❯` (U+276F) only — the ASCII `>` claude also accepts is not a glyph Command
2591
- * Code 1.40.1 draws, and accepting it here would make every shell prompt and
2592
- * every quoted diff line in a reply look like a composer.
2593
- *
2594
- * The row is drawn in two places, which is why prompt presence alone is not
2595
- * "ready": the bottom-pinned composer (`❯ Ask your question...` between two
2596
- * full-width rules) and the highlighted option of a permission dialog
2597
- * (`❯ 1. Yes`). Completion is resolved structurally instead — see
2598
- * {@link findCommandCodeChromeStart}.
2599
- */
2600
- exports.COMMAND_CODE_PROMPT_PATTERN = /^❯(\s*$|\s+\S)/m;
2601
- /**
2602
- * Command Code separator pattern (Issue #2250).
2603
- *
2604
- * Full-pane runs of U+2500 fence the composer above and below, and one more is
2605
- * drawn above a permission dialog. Measured at 200 columns on every fixture in
2606
- * `tests/fixtures/command-code-live-2250/`.
2607
- */
2608
- exports.COMMAND_CODE_SEPARATOR_PATTERN = /^─{10,}$/m;
2609
- /**
2610
- * Command Code status-line "esc to interrupt" hint (Issue #2250).
2611
- *
2612
- * The `Status` component renders `esc to interrupt • <elapsed> • ↓ <tokens>`
2613
- * only in its `"all"` layout, i.e. at a terminal width of 72 columns or more
2614
- * (read off the bundle's `layoutMode` ladder; CommandMate panes are 200 wide, so
2615
- * production always takes that branch). Below 42 columns the whole tail is
2616
- * dropped and only the spinner + verb remain — which is why
2617
- * {@link COMMAND_CODE_THINKING_PATTERN} keeps the spinner branch as well.
2618
- */
2619
- exports.COMMAND_CODE_INTERRUPT_HINT_PATTERN = /esc to interrupt/;
2620
- /**
2621
- * Command Code thinking/processing pattern (Issue #2250).
2622
- *
2623
- * Three measured alternatives:
2624
- *
2625
- * 1. the status row — a spinner glyph, then a single word ending in `…`
2626
- * (`⌘ Planning…`, `· Synthesizing…`; the bundle's verb table is 74
2627
- * single-word entries, all capitalised, but the class is left case-agnostic
2628
- * because the `status` prop is not restricted to that table). Anchored at
2629
- * the start of the line with only leading spaces allowed, so the `·` in the
2630
- * banner's
2631
- * `# models: … · taste-1` row and in the idle footer's
2632
- * `? for shortcuts · taste on` cannot reach it;
2633
- * 2. `✻ Thinking…` — the reasoning block's header WHILE it streams. It becomes
2634
- * `✻ Thought for 1 second [ctrl+o to expand]` once the block is closed, and
2635
- * that past-tense form must NOT match: it sits in the transcript of every
2636
- * finished turn (`turn-version.txt`);
2637
- * 3. {@link COMMAND_CODE_INTERRUPT_HINT_PATTERN}, unanchored, for the same
2638
- * reason claude's pattern carries it.
2639
- */
2640
- exports.COMMAND_CODE_THINKING_PATTERN = new RegExp(`^[^\\S\\n]*[${exports.COMMAND_CODE_SPINNER_CHARS.join('')}][^\\S\\n]+[A-Za-z]+…` +
2641
- `|^[^\\S\\n]*✻ Thinking…` +
2642
- `|${exports.COMMAND_CODE_INTERRUPT_HINT_PATTERN.source}`, 'm');
2643
- /**
2644
- * Command Code assistant-message marker (Issue #2250).
2645
- *
2646
- * `⠶` (U+2836) at column 0, one space, then the reply. Fixed, not a spinner
2647
- * frame: the bundle declares it as `Ct() ? "⠶" : "#"`, i.e. one constant with an
2648
- * ASCII fallback for terminals without unicode support. That answers 親 Issue
2649
- * #2249's 未確定事項 1 — the glyph does not rotate — and it is also why the `#`
2650
- * fallback is deliberately NOT matched here: on such a terminal it is
2651
- * indistinguishable from the `# Command Code v1.40.1` banner rows.
2652
- *
2653
- * Continuation rows of a multi-line reply are indented by two spaces (the marker
2654
- * is one column wide and the body box carries `marginLeft: 1`).
2655
- */
2656
- exports.COMMAND_CODE_RESPONSE_MARKER_PATTERN = /^⠶(?:\s|$)/;
2657
- /**
2658
- * Command Code turn-completion marker (Issue #2250).
2659
- *
2660
- * **Advisory only — never require it to declare a turn finished.** Two measured
2661
- * reasons: `WorkedDurationNote` renders nothing for a turn under 1000 ms, and
2662
- * the row belongs to the live turn's UI rather than to the transcript — it is
2663
- * present in `turn-version.txt` and GONE from `dialog-create-file.txt`, which is
2664
- * the same pane one prompt later.
2665
- */
2666
- exports.COMMAND_CODE_COMPLETION_PATTERN = /^[^\S\n]*✻ Worked for /m;
2667
- /**
2668
- * Command Code footer mode indicator (Issue #2250).
2669
- *
2670
- * The row under the composer's closing rule. `? for shortcuts` is only the
2671
- * DEFAULT-mode spelling: the bundle's `ModeIndicator` swaps it for a mode banner
2672
- * in the other four permission modes, so a rule that keys on `? for shortcuts`
2673
- * alone would stop recognising an idle pane the moment the operator pressed
2674
- * shift+tab. All five spellings are listed.
2675
- */
2676
- exports.COMMAND_CODE_MODE_INDICATOR_PATTERN = /\?\s+for\s+shortcuts|»\s+accept edits on|»\s+permission bypass on|»\s+don't-ask on|^[^\S\n]*plan mode\s/m;
2677
- /**
2678
- * Command Code startup banner rows (Issue #2250).
2679
- *
2680
- * The three-row header under the block-art logo — `# Command Code v1.40.1`,
2681
- * `# models: …`, `# <cwd>` — plus the logo itself.
2682
- *
2683
- * The version row matches the tool's own NAME and version together, which is the
2684
- * shape #2247 had to retreat to on claude: a bare `v\d+\.\d+` matched any reply
2685
- * that mentioned a release, and the turn was silently dropped. Nothing here
2686
- * matches a bare version string, a `│` table glyph, or a `Tip:` line.
2687
- */
2688
- exports.COMMAND_CODE_BANNER_PATTERNS = [
2689
- /^#\s+Command Code v\d/, // Name + version, together
2690
- /^#\s+models:\s/, // Model line
2691
- /^#\s+[~/]/, // Working-directory line
2692
- /^[^\S\n]*[█▀▄▌▐]{3,}/, // Block-art logo rows
2693
- ];
2694
- /**
2695
- * Command Code hook notice row (Issue #2250).
2696
- *
2697
- * `◼ Ran 1 session start hook` — emitted into the transcript when SessionStart
2698
- * hooks fire, so it lands ABOVE the first user echo and is chrome, not a reply.
2699
- * Measured on a hooks-enabled 1.40.1 pane while capturing #2249's evidence.
2700
- * Command Code's hook wiring itself is Phase B (#2251); this row only has to be
2701
- * kept out of History.
2702
- */
2703
- exports.COMMAND_CODE_HOOK_NOTICE_PATTERN = /^[^\S\n]*◼\s+Ran\s+\d+\s+.*hooks?\b/;
2704
- /** How far above the last row Command Code's footer row may sit. */
2705
- const COMMAND_CODE_FOOTER_MAX_ROWS = 4;
2706
- /** How many rows the composer may span before the block stops looking like chrome. */
2707
- const COMMAND_CODE_INPUT_BOX_MAX_ROWS = 40;
2708
- /**
2709
- * Locate the start of Command Code's bottom-pinned chrome within a captured pane.
2710
- *
2711
- * Command Code is inline-rendered, so its transcript grows downwards and the
2712
- * last four rows of a settled pane are always the same four (measured on
2713
- * `boot-idle.txt`, `turn-version.txt` and `turn-tool-write.txt`):
2714
- *
2715
- * ```text
2716
- * ──────────────────── ← opening rule
2717
- * ❯ Ask your question… ← composer (one row per wrapped line)
2718
- * ──────────────────── ← closing rule
2719
- * ? for shortcuts · taste on
2720
- * ```
2721
- *
2722
- * Everything from the opening rule down is terminal furniture. Two things go
2723
- * wrong if it reaches the extractor, and both are regressions this repository
2724
- * has already paid for once: the composer's placeholder is drawn with the same
2725
- * `❯ <text>` shape as a transcript echo, so the turn anchor lands on the FOOTER
2726
- * and the reply extracts as empty (#1289); and the footer row is repainted while
2727
- * the pane sits idle, so keeping it re-hashes the saved response on every poll
2728
- * tick (#1268 / #1289).
2729
- *
2730
- * Structural, like the three readers next to it, and for the reason spelled out
2731
- * on `findClaudeChromeStart`: the hint strings belong to Command Code and a rule
2732
- * that matches them stops working the moment they are reworded. `-1` is the
2733
- * honest answer for a frame with no composer at all — while a permission dialog
2734
- * is up the whole block is replaced by the dialog, which the caller resolves on
2735
- * the prompt path instead.
2736
- *
2737
- * @param lines - Captured pane lines, ANSI-bearing or not; trailing blanks tolerated
2738
- * @returns Index of the opening rule, or -1 when no composer block is present
366
+ * Row for a tool id. An id outside CLI_TOOL_IDS (possible at runtime) falls back
367
+ * to claude's row, as the former `default` branches did.
2739
368
  */
2740
- function findCommandCodeChromeStart(lines) {
2741
- const isSeparator = (line) => /^─{10,}$/.test((0, ansi_1.stripAnsi)(line ?? '').trimEnd());
2742
- let lastRow = lines.length - 1;
2743
- while (lastRow >= 0 && lines[lastRow].trim() === '')
2744
- lastRow--;
2745
- if (lastRow < 0)
2746
- return -1;
2747
- // The closing rule sits just above the mode-indicator row.
2748
- let closingSeparator = -1;
2749
- for (let i = lastRow; i >= Math.max(0, lastRow - COMMAND_CODE_FOOTER_MAX_ROWS); i--) {
2750
- if (isSeparator(lines[i])) {
2751
- closingSeparator = i;
2752
- break;
2753
- }
2754
- }
2755
- if (closingSeparator < 0)
2756
- return -1;
2757
- // Walk up over the composer rows to the opening rule.
2758
- let openingSeparator = -1;
2759
- for (let i = closingSeparator - 1; i >= Math.max(0, closingSeparator - COMMAND_CODE_INPUT_BOX_MAX_ROWS); i--) {
2760
- if (isSeparator(lines[i])) {
2761
- openingSeparator = i;
2762
- break;
2763
- }
2764
- }
2765
- if (openingSeparator < 0)
2766
- return -1;
2767
- // Confirm the fenced rows are the composer and not a reply that happens to
2768
- // contain two horizontal rules.
2769
- if (!/^❯/.test((0, ansi_1.stripAnsi)(lines[openingSeparator + 1] ?? '')))
2770
- return -1;
2771
- return openingSeparator;
369
+ function patternRowFor(cliToolId) {
370
+ return ownRowFor(cliToolId) ?? CLI_TOOL_PATTERN_TABLE.claude;
2772
371
  }
2773
- /**
2774
- * Command Code skip patterns for response cleaning (Issue #2250).
2775
- *
2776
- * The dedicated cleaner Issue #2250 item 8 asks for: the startup banner, the
2777
- * hook notice, the reasoning and turn summaries (`✻ Thought for` / `✻ Worked
2778
- * for`), the composer, the rules and the footer.
2779
- *
2780
- * Nothing here touches the reply body or a tool block: `⠶ <text>`, ` WRITE
2781
- * [probe.txt]`, ` └ Created probe.txt (1 line)` and ` 1 │ hello` all
2782
- * survive (`turn-tool-write.txt`). In particular there is no `^\s*│` rule — the
2783
- * one codex carries — because Command Code renders file previews with it.
2784
- */
2785
- exports.COMMAND_CODE_SKIP_PATTERNS = [
2786
- /^─{10,}$/, // Composer rules and the dialog's rule
2787
- /^❯\s*$/, // Bare composer row
2788
- /^❯\s+Ask your question\.\.\./, // Composer placeholder
2789
- exports.COMMAND_CODE_MODE_INDICATOR_PATTERN, // Footer mode indicator
2790
- exports.COMMAND_CODE_THINKING_PATTERN, // Status row
2791
- /^[^\S\n]*✻\s+(?:Worked|Thought)\s+for\b/, // Turn / reasoning summaries
2792
- exports.COMMAND_CODE_HOOK_NOTICE_PATTERN, // "◼ Ran N session start hook"
2793
- ...exports.COMMAND_CODE_BANNER_PATTERNS, // Startup banner
2794
- exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
2795
- ];
2796
372
  /**
2797
373
  * Detect if CLI tool is showing "thinking" indicator
2798
374
  */
2799
375
  function detectThinking(cliToolId, content) {
2800
376
  const log = logger.withContext({ cliToolId });
2801
377
  log.debug('detectThinking:check', { contentLength: content.length });
2802
- let result;
2803
- switch (cliToolId) {
2804
- case 'claude':
2805
- result = exports.CLAUDE_THINKING_PATTERN.test(content);
2806
- break;
2807
- case 'codex':
2808
- result = exports.CODEX_THINKING_PATTERN.test(content);
2809
- break;
2810
- case 'gemini':
2811
- result = exports.GEMINI_THINKING_PATTERN.test(content);
2812
- break;
2813
- case 'vibe-local':
2814
- result = exports.VIBE_LOCAL_THINKING_PATTERN.test(content);
2815
- break;
2816
- case 'opencode':
2817
- result = exports.OPENCODE_THINKING_PATTERN.test(content);
2818
- break;
2819
- case 'copilot':
2820
- result = exports.COPILOT_THINKING_PATTERN.test(content);
2821
- break;
2822
- case 'antigravity':
2823
- result = exports.ANTIGRAVITY_THINKING_PATTERN.test(content);
2824
- break;
2825
- case 'command-code':
2826
- result = exports.COMMAND_CODE_THINKING_PATTERN.test(content);
2827
- break;
2828
- case 'opencode-v2':
2829
- result = exports.OPENCODE_V2_THINKING_PATTERN.test(content);
2830
- break;
2831
- default:
2832
- result = exports.CLAUDE_THINKING_PATTERN.test(content);
2833
- }
378
+ const result = patternRowFor(cliToolId).thinkingPattern.test(content);
2834
379
  log.debug('detectThinking:result', { isThinking: result });
2835
380
  return result;
2836
381
  }
@@ -2838,190 +383,20 @@ function detectThinking(cliToolId, content) {
2838
383
  * Get CLI tool patterns for response extraction
2839
384
  */
2840
385
  function getCliToolPatterns(cliToolId) {
2841
- switch (cliToolId) {
2842
- case 'claude':
2843
- return {
2844
- promptPattern: exports.CLAUDE_PROMPT_PATTERN,
2845
- separatorPattern: exports.CLAUDE_SEPARATOR_PATTERN,
2846
- thinkingPattern: exports.CLAUDE_THINKING_PATTERN,
2847
- skipPatterns: [
2848
- /^─{10,}$/, // Separator lines
2849
- /^[>❯]\s*$/, // Prompt line (legacy '>' and new '❯')
2850
- exports.CLAUDE_THINKING_PATTERN, // Thinking indicators
2851
- /^\s*[⎿⏋]\s+Tip:/, // Tip lines
2852
- /^\s*Tip:/, // Tip lines
2853
- /^\s*\?\s*for shortcuts/, // Shortcuts hint
2854
- /to interrupt\)/, // Part of "esc to interrupt" message
2855
- exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines] (Issue #212)
2856
- ],
2857
- };
2858
- case 'codex':
2859
- return {
2860
- promptPattern: exports.CODEX_PROMPT_PATTERN,
2861
- separatorPattern: exports.CODEX_SEPARATOR_PATTERN,
2862
- thinkingPattern: exports.CODEX_THINKING_PATTERN,
2863
- skipPatterns: [
2864
- /^─.*─+$/, // Separator lines
2865
- /^›\s*$/, // Empty prompt line
2866
- /^›\s+(Implement|Find and fix|Type)/, // New prompt suggestions
2867
- exports.CODEX_THINKING_PATTERN, // Activity indicators
2868
- /^\s*\d+%\s+context left/, // Context indicator
2869
- /^\s*for shortcuts$/, // Shortcuts hint
2870
- /╭─+╮/, // Box drawing (top)
2871
- /╰─+╯/, // Box drawing (bottom)
2872
- // T1.3: Additional skip patterns for Codex
2873
- /•\s*Ran\s+/, // Command execution lines
2874
- /^\s*└/, // Tree output (completion indicator)
2875
- /^\s*│/, // Continuation lines
2876
- /\(.*esc to interrupt\)/, // Interrupt hint
2877
- exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines] (Issue #212, defensive)
2878
- ],
2879
- };
2880
- case 'gemini':
2881
- return {
2882
- promptPattern: exports.GEMINI_PROMPT_PATTERN,
2883
- separatorPattern: /^[─━]{3,}$/m,
2884
- thinkingPattern: exports.GEMINI_THINKING_PATTERN,
2885
- skipPatterns: [
2886
- exports.GEMINI_PROMPT_PATTERN, // Prompt line (DRY: shared with GEMINI_PROMPT_PATTERN)
2887
- exports.GEMINI_THINKING_PATTERN, // Thinking indicators
2888
- /^\s*$/, // Empty lines
2889
- /Gemini\s+\d+\.\d+/, // Version line
2890
- exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
2891
- ],
2892
- };
2893
- case 'vibe-local':
2894
- return {
2895
- promptPattern: exports.VIBE_LOCAL_PROMPT_PATTERN,
2896
- separatorPattern: /^[·]{10,}$/m, // vibe-local uses middle dot separators
2897
- thinkingPattern: exports.VIBE_LOCAL_THINKING_PATTERN,
2898
- skipPatterns: [
2899
- exports.VIBE_LOCAL_PROMPT_PATTERN, // Prompt line (ctx:N% ❯)
2900
- exports.VIBE_LOCAL_THINKING_PATTERN, // Thinking indicators
2901
- /^\s*$/, // Empty lines
2902
- /vibe-local|vibe-coder/, // Version/banner lines
2903
- /ctx:\s*\d+%/, // Context usage indicator
2904
- /Model\s+\w/, // Model info line
2905
- /Engine\s+\w/, // Engine info line
2906
- /Mode\s+/, // Mode info line
2907
- /RAM\s+/, // RAM info line
2908
- /CWD\s+/, // Working directory line
2909
- /^[·]{10,}$/, // Middle dot separator lines
2910
- /✦\s*Ready/, // Status bar "Ready" indicator
2911
- /ESC:\s*stop/, // Status bar "ESC: stop" hint
2912
- exports.PASTED_TEXT_PATTERN, // [Pasted text #N +XX lines]
2913
- ],
2914
- };
2915
- case 'opencode':
2916
- return {
2917
- promptPattern: exports.OPENCODE_PROMPT_PATTERN,
2918
- separatorPattern: exports.OPENCODE_SEPARATOR_PATTERN,
2919
- thinkingPattern: exports.OPENCODE_THINKING_PATTERN,
2920
- skipPatterns: [...exports.OPENCODE_SKIP_PATTERNS],
2921
- };
2922
- case 'copilot':
2923
- return {
2924
- promptPattern: exports.COPILOT_PROMPT_PATTERN,
2925
- separatorPattern: exports.COPILOT_SEPARATOR_PATTERN,
2926
- thinkingPattern: exports.COPILOT_THINKING_PATTERN,
2927
- skipPatterns: [...exports.COPILOT_SKIP_PATTERNS],
2928
- };
2929
- case 'antigravity':
2930
- return {
2931
- promptPattern: exports.ANTIGRAVITY_PROMPT_PATTERN,
2932
- separatorPattern: exports.ANTIGRAVITY_SEPARATOR_PATTERN,
2933
- thinkingPattern: exports.ANTIGRAVITY_THINKING_PATTERN,
2934
- skipPatterns: [...exports.ANTIGRAVITY_SKIP_PATTERNS],
2935
- };
2936
- // Issue #2250: Command Code's layout is claude-shaped (inline transcript,
2937
- // `❯` composer fenced by two full-width rules) but the constants are its
2938
- // own. Sharing claude's would import the exact defect #2247 had to undo --
2939
- // claude's rules carry a startup-banner reading that keys on `v\d+\.\d+`
2940
- // and `|`, and Command Code prints its version into a `# Command Code
2941
- // v1.40.1` row on every launch.
2942
- case 'command-code':
2943
- return {
2944
- promptPattern: exports.COMMAND_CODE_PROMPT_PATTERN,
2945
- separatorPattern: exports.COMMAND_CODE_SEPARATOR_PATTERN,
2946
- thinkingPattern: exports.COMMAND_CODE_THINKING_PATTERN,
2947
- skipPatterns: [...exports.COMMAND_CODE_SKIP_PATTERNS],
2948
- };
2949
- // Issue #2934: OpenCode V2's own constants (see OPENCODE_V2_* above). The
2950
- // separator row is the same half-block rule v1 draws, so v1's pattern is
2951
- // reused as a value; nothing of v1's is changed.
2952
- case 'opencode-v2':
2953
- return {
2954
- promptPattern: exports.OPENCODE_V2_IDLE_COMPOSER_PATTERN,
2955
- separatorPattern: exports.OPENCODE_SEPARATOR_PATTERN,
2956
- thinkingPattern: exports.OPENCODE_V2_THINKING_PATTERN,
2957
- skipPatterns: [...exports.OPENCODE_V2_SKIP_PATTERNS],
2958
- };
2959
- default:
2960
- // Default to Claude patterns
2961
- return getCliToolPatterns('claude');
2962
- }
386
+ const row = patternRowFor(cliToolId);
387
+ return {
388
+ promptPattern: row.promptPattern,
389
+ separatorPattern: row.separatorPattern,
390
+ thinkingPattern: row.thinkingPattern,
391
+ skipPatterns: [...row.skipPatterns],
392
+ };
2963
393
  }
2964
394
  // ANSI primitives live in a dependency-free leaf module so client components can
2965
395
  // reuse the same tested pattern without pulling this file's server-only imports
2966
396
  // (logger/db) into the browser bundle. Re-exported here for existing importers.
2967
- var ansi_2 = require("./ansi");
2968
- Object.defineProperty(exports, "stripAnsi", { enumerable: true, get: function () { return ansi_2.stripAnsi; } });
2969
- Object.defineProperty(exports, "extractAnsiSequences", { enumerable: true, get: function () { return ansi_2.extractAnsiSequences; } });
2970
- /**
2971
- * Strip box-drawing border characters from CLI output.
2972
- * Gemini CLI wraps Action Required prompts in ╭─╮│╰─╯ borders.
2973
- * Removes │ (U+2502) prefix/suffix and border-only lines (╭╮╰╯─).
2974
- *
2975
- * @param str - Input string (typically after stripAnsi())
2976
- * @returns String with box-drawing borders removed
2977
- */
2978
- function stripBoxDrawing(str) {
2979
- return str.split('\n').map(line => {
2980
- // Remove border-only lines (╭──╮, ╰──╯, │ only, ┃ only, ╹▀▀▀, █ scrollbar, etc.)
2981
- // U+2502 │ (light vertical), U+2503 ┃ (heavy vertical - OpenCode TUI)
2982
- // U+2579 ╹ (heavy up), U+2580 ▀ (upper half block - OpenCode separator)
2983
- // U+2588 █ (full block - OpenCode scrollbar)
2984
- if (/^[\u2502\u2503\u256D\u256E\u256F\u2570\u2500\u2579\u2580\u2588\s]+$/.test(line))
2985
- return '';
2986
- // Strip leading whitespace + │/┃ + optional space, trailing space + │/┃/█
2987
- // OpenCode TUI adds 2-space padding before ┃ borders (e.g., " ┃ content")
2988
- // OpenCode scrollbar █ appears at end of content lines
2989
- return line.replace(/^\s*[\u2502\u2503]\s?/, '').replace(/\s*[\u2502\u2503\u2588]$/, '');
2990
- }).join('\n');
2991
- }
2992
- /**
2993
- * Error patterns that indicate a Claude session failed to start properly
2994
- * Used by isSessionHealthy() to detect broken sessions (MF-001: SRP)
2995
- * Style: readonly + as const for type safety (SF-S2-001: follows response-poller.ts precedent)
2996
- *
2997
- * SEC-SF-004: Pattern maintenance process:
2998
- * - When Claude CLI is updated, verify that error messages still match these patterns.
2999
- * - Test procedure: Intentionally trigger each error condition (e.g., nested session launch)
3000
- * and confirm the error message is captured by the patterns.
3001
- * - If Claude CLI introduces localized error messages, add locale-aware patterns or
3002
- * consider switching to exit code-based detection as a more robust alternative.
3003
- * - Pattern additions should be accompanied by corresponding test cases in
3004
- * claude-session.test.ts.
3005
- *
3006
- * C-S3-001: Codex/Gemini monitoring note:
3007
- * These patterns are currently Claude-specific. If Codex or Gemini exhibit similar
3008
- * "nested session" or startup failure behaviors, analogous error patterns should be
3009
- * added to their respective tool configurations (codex.ts, gemini.ts) rather than
3010
- * extending these arrays, to maintain SRP per CLI tool type.
3011
- */
3012
- exports.CLAUDE_SESSION_ERROR_PATTERNS = [
3013
- 'Claude Code cannot be launched inside another Claude Code session',
3014
- ];
3015
- /**
3016
- * Regex patterns for Claude session errors requiring context matching
3017
- * Used by isSessionHealthy() for multi-condition error detection (MF-001: SRP)
3018
- * Style: readonly + as const for type safety (SF-S2-001: follows response-poller.ts precedent)
3019
- *
3020
- * SEC-SF-004: See CLAUDE_SESSION_ERROR_PATTERNS JSDoc for pattern maintenance process.
3021
- */
3022
- exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = [
3023
- /^Error:.*Claude Code/,
3024
- ];
397
+ var ansi_1 = require("./ansi");
398
+ Object.defineProperty(exports, "stripAnsi", { enumerable: true, get: function () { return ansi_1.stripAnsi; } });
399
+ Object.defineProperty(exports, "extractAnsiSequences", { enumerable: true, get: function () { return ansi_1.extractAnsiSequences; } });
3025
400
  /**
3026
401
  * Build DetectPromptOptions for a given CLI tool.
3027
402
  * Centralizes cliToolId-to-options mapping logic (DRY - MF-001).
@@ -3029,73 +404,25 @@ exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = [
3029
404
  * prompt-detector.ts remains CLI tool independent (Issue #161 principle);
3030
405
  * this function lives in cli-patterns.ts which already depends on CLIToolType.
3031
406
  *
3032
- * [Future extension memo (C-002)]
3033
- * If CLI tool count grows significantly (currently 6), consider migrating
3034
- * to a CLIToolConfig registry pattern where tool-specific settings
3035
- * (including promptDetectionOptions) are managed in a Record<CLIToolType, CLIToolConfig>.
3036
- * Migration threshold: 7th tool addition triggers registry pattern migration [D1-003].
407
+ * [Extension memo (C-002) — carried out]
408
+ * The memo asked for a registry pattern where tool-specific settings
409
+ * (including promptDetectionOptions) are managed in a Record<CLIToolType, ...>,
410
+ * with the 7th tool as the migration threshold [D1-003]. There are 9 tools
411
+ * today, and Issue #3230 made the move: the settings are the `promptOptions`
412
+ * column of CLI_TOOL_PATTERN_TABLE.
3037
413
  *
3038
414
  * @param cliToolId - CLI tool identifier
3039
415
  * @returns DetectPromptOptions for the tool, or undefined for default behavior
3040
416
  */
3041
417
  function buildDetectPromptOptions(cliToolId) {
3042
- if (cliToolId === 'claude') {
3043
- return { requireDefaultIndicator: false };
3044
- }
3045
- // [D2-006] OpenCode prompt "Ask anything..." does not use standard indicators (> / ❯),
3046
- // so requireDefaultIndicator must be false to avoid missing prompt detection.
3047
- //
3048
- // [Issue #1896] `hasNumberedDialogs: false` -- opencode 1.18 renders NO dialog
3049
- // that a typed number drives, so the generic numbered-list inference has
3050
- // nothing to find on its pane and every hit it scored was transcript text.
3051
- // Its two interactive surfaces were both measured at the production 80x200
3052
- // geometry and both are cursor-driven:
3053
- //
3054
- // - the permission dialog is a horizontal button strip
3055
- // ({@link OPENCODE_PERMISSION_PATTERN}, Issue #1893) driven by ←/→ + Enter;
3056
- // typing a number does nothing to it.
3057
- // - the pickers (`/models`, `/providers`, `/connect`, and the ctrl+p command
3058
- // palette) are fuzzy-search lists driven by ↑/↓ + Enter, with no numbers
3059
- // drawn at all. The first three are what
3060
- // {@link OPENCODE_SELECTION_LIST_PATTERN} names; the palette shares the
3061
- // chrome but not the header allowlist, and lands on `running` / `default`.
3062
- //
3063
- // Both keep their own POSITIVE detection in `status-detector.ts`, so `wait`
3064
- // still stops for them (exit 10 via `isSelectionListActive`) and the UI still
3065
- // renders NavigationButtons: nothing that could be answered before stops being
3066
- // answered. What ends is the false positive -- a response whose body ends in
3067
- // `1. / 2. / 3.` + a question was published as
3068
- // `waiting`/`prompt_detected`/`hasActivePrompt: true`, and Auto-Yes typed `1`
3069
- // into the composer and SENT IT as a user utterance (Issue #1896).
3070
- //
3071
- // `requireDefaultIndicator` is kept at its D2-006 value: it is the correct
3072
- // setting for opencode's ❯-less rendering should the numbered path ever be
3073
- // re-enabled, and it still describes the tool.
3074
- if (cliToolId === 'opencode') {
3075
- return { requireDefaultIndicator: false, hasNumberedDialogs: false };
3076
- }
3077
- // [Issue #545] Copilot prompt pattern may not use standard indicators
3078
- if (cliToolId === 'copilot') {
3079
- return { requireDefaultIndicator: false };
3080
- }
3081
- // [Issue #999] Antigravity (agy) permission-approval menus highlight the
3082
- // default with an ASCII ">" (0x3E), not the "❯/●/›" indicators that
3083
- // DEFAULT_OPTION_PATTERN recognizes, and their footer is "↑/↓ Navigate"
3084
- // (no "press enter to confirm"). Under the default requireDefaultIndicator=true
3085
- // the Pass 1 gate rejects these menus, so Auto-Yes never responds. Treat agy
3086
- // like claude/opencode/copilot so Pass 2 collects its "1. Yes / … / N. No"
3087
- // options and reports isPrompt=true.
3088
- //
3089
- // [Issue #2364] The `↑/↓ Navigate` dialogs themselves no longer reach
3090
- // `detectPrompt` on either production path: `tools/antigravity/detect.ts`
3091
- // (status) and `detectPromptWithOptions` (response poller) both read them
3092
- // with `detectAntigravityNumberedDialogPrompt` first, because the generic
3093
- // multiple-choice pass reads one row per option and agy wraps a long command
3094
- // across several rows of one label. What this setting still serves is every
3095
- // OTHER numbered agy screen — `/feedback`'s `1-6 Select & Continue` menu is
3096
- // the measured one — which the generic pass reads as before.
3097
- if (cliToolId === 'antigravity') {
3098
- return { requireDefaultIndicator: false };
3099
- }
3100
- return undefined; // Default behavior (requireDefaultIndicator = true)
418
+ // Fresh object per call (callers may mutate); an id outside the table yields undefined.
419
+ const options = ownRowFor(cliToolId)?.promptOptions;
420
+ return options === undefined ? undefined : { ...options };
421
+ }
422
+ /**
423
+ * Whether a tool hands the FULL frame to `detectPrompt` instead of the 15-line tail.
424
+ * An id outside the table answers false.
425
+ */
426
+ function usesFullFramePrompt(cliToolId) {
427
+ return ownRowFor(cliToolId)?.fullFramePrompt ?? false;
3101
428
  }