commandmate 0.44.2 → 0.44.3

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