commandmate 0.47.2 → 0.49.0

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 (434) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +180 -180
  3. package/.next/app-path-routes-manifest.json +30 -30
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +9 -9
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/api/agents/update/route_client-reference-manifest.js +1 -1
  9. package/.next/server/app/api/agents/versions/route_client-reference-manifest.js +1 -1
  10. package/.next/server/app/api/app/release-notes/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/app/update/route.js +3 -3
  12. package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/capabilities/route.js +1 -1
  18. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/hooks/agent-event/route.js +2 -2
  27. package/.next/server/app/api/hooks/agent-event/route.js.nft.json +1 -1
  28. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/hooks/claude-done/route.js +2 -2
  30. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  31. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  33. package/.next/server/app/api/hooks/permission-request/route.js.nft.json +1 -1
  34. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/push/escalation/route.js +1 -1
  38. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/push/vapid/route.js +1 -1
  41. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/relays/[relayId]/cancel/route.js +1 -1
  43. package/.next/server/app/api/relays/[relayId]/cancel/route.js.nft.json +1 -1
  44. package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/relays/route.js +1 -1
  46. package/.next/server/app/api/relays/route.js.nft.json +1 -1
  47. package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  51. package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
  52. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/repositories/clone/route.js +1 -1
  54. package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
  55. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/repositories/restore/route.js +1 -1
  58. package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -1
  59. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/repositories/route.js +1 -1
  61. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  62. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/repositories/scan/route.js +1 -1
  64. package/.next/server/app/api/repositories/scan/route.js.nft.json +1 -1
  65. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/repositories/sync/route.js +1 -1
  67. package/.next/server/app/api/repositories/sync/route.js.nft.json +1 -1
  68. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/settings/default-agents/route.js +1 -1
  71. package/.next/server/app/api/settings/default-agents/route.js.nft.json +1 -1
  72. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  88. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  89. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/worktrees/[id]/capture/route.js +1 -1
  91. package/.next/server/app/api/worktrees/[id]/capture/route.js.nft.json +1 -1
  92. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js +1 -1
  94. package/.next/server/app/api/worktrees/[id]/clear-composer/route.js.nft.json +1 -1
  95. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  101. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/direct-input/route.js +1 -1
  103. package/.next/server/app/api/worktrees/[id]/direct-input/route.js.nft.json +1 -1
  104. package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/instances/opencode/route.js.nft.json +1 -1
  135. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  138. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  141. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/opencode/session/route.js +1 -1
  150. package/.next/server/app/api/worktrees/[id]/opencode/session/route.js.nft.json +1 -1
  151. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/api/worktrees/[id]/opencode/share/route.js +1 -1
  153. package/.next/server/app/api/worktrees/[id]/opencode/share/route.js.nft.json +1 -1
  154. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  155. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  156. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  157. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route.js +1 -1
  159. package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route.js.nft.json +1 -1
  160. package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route_client-reference-manifest.js +1 -1
  161. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  163. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  164. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  165. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  166. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  167. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  168. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  169. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  170. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  171. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  172. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  173. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  174. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  175. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js +3 -3
  176. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route.js.nft.json +1 -1
  177. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  178. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  179. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  180. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  181. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  182. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  183. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  184. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  185. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  186. package/.next/server/app/api/worktrees/[id]/special-keys/route.js +1 -1
  187. package/.next/server/app/api/worktrees/[id]/special-keys/route.js.nft.json +1 -1
  188. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  189. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  190. package/.next/server/app/api/worktrees/[id]/terminal/route.js +1 -103
  191. package/.next/server/app/api/worktrees/[id]/terminal/route.js.nft.json +1 -1
  192. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  193. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  194. package/.next/server/app/api/worktrees/[id]/timers/route.js +1 -1
  195. package/.next/server/app/api/worktrees/[id]/timers/route.js.nft.json +1 -1
  196. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  197. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  198. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  199. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  200. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  201. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  202. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  203. package/.next/server/app/api/worktrees/[id]/verify/route.js +1 -1
  204. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  205. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route.js +1 -1
  206. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  207. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  208. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  209. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  210. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  211. package/.next/server/app/api/worktrees/route.js +1 -1
  212. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  213. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  214. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  215. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  216. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  217. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  218. package/.next/server/app/page_client-reference-manifest.js +1 -1
  219. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  220. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  221. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  222. package/.next/server/app/sessions/page.js +1 -1
  223. package/.next/server/app/sessions/page.js.nft.json +1 -1
  224. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  225. package/.next/server/app/skills/[skillId]/page.js +1 -1
  226. package/.next/server/app/skills/[skillId]/page.js.nft.json +1 -1
  227. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  228. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  229. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  230. package/.next/server/app/worktrees/[id]/files/[...path]/page.js +2 -2
  231. package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
  232. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  233. package/.next/server/app/worktrees/[id]/page.js +12 -12
  234. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  235. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  236. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  237. package/.next/server/app-paths-manifest.json +30 -30
  238. package/.next/server/chunks/1191.js +11 -4
  239. package/.next/server/chunks/1501.js +1 -1
  240. package/.next/server/chunks/1601.js +2 -2
  241. package/.next/server/chunks/1656.js +2 -2
  242. package/.next/server/chunks/1945.js +3 -3
  243. package/.next/server/chunks/1990.js +103 -0
  244. package/.next/server/chunks/2069.js +1 -0
  245. package/.next/server/chunks/2119.js +1 -1
  246. package/.next/server/chunks/2404.js +1 -1
  247. package/.next/server/chunks/2422.js +1 -1
  248. package/.next/server/chunks/2445.js +1 -1
  249. package/.next/server/chunks/2534.js +1 -1
  250. package/.next/server/chunks/255.js +1 -1
  251. package/.next/server/chunks/3241.js +1 -1
  252. package/.next/server/chunks/3505.js +2 -2
  253. package/.next/server/chunks/4243.js +1 -1
  254. package/.next/server/chunks/4248.js +1 -1
  255. package/.next/server/chunks/4280.js +1 -0
  256. package/.next/server/chunks/5209.js +1 -1
  257. package/.next/server/chunks/6119.js +1 -1
  258. package/.next/server/chunks/6130.js +1 -1
  259. package/.next/server/chunks/6401.js +1 -1
  260. package/.next/server/chunks/6486.js +9 -8
  261. package/.next/server/chunks/6781.js +1 -1
  262. package/.next/server/chunks/720.js +103 -0
  263. package/.next/server/chunks/7604.js +2 -0
  264. package/.next/server/chunks/7905.js +1 -1
  265. package/.next/server/chunks/8581.js +8 -8
  266. package/.next/server/chunks/8728.js +3 -3
  267. package/.next/server/chunks/8791.js +1 -1
  268. package/.next/server/chunks/8807.js +19 -18
  269. package/.next/server/chunks/8918.js +2 -104
  270. package/.next/server/chunks/936.js +1 -1
  271. package/.next/server/chunks/9390.js +1 -1
  272. package/.next/server/chunks/9540.js +1 -1
  273. package/.next/server/chunks/9627.js +5 -5
  274. package/.next/server/chunks/9731.js +2 -2
  275. package/.next/server/middleware-manifest.json +5 -5
  276. package/.next/server/pages/500.html +1 -1
  277. package/.next/server/server-reference-manifest.json +1 -1
  278. package/.next/static/chunks/1128-914c559913546bbc.js +1 -0
  279. package/.next/static/chunks/{2407-a6533805a86d05bd.js → 2407-ab92ae9837c39dce.js} +1 -1
  280. package/.next/static/chunks/{2863-6a07ec82fb52f4dd.js → 2863-7173dc305dffc4f6.js} +1 -1
  281. package/.next/static/chunks/6288-6fadbba6e84393b2.js +1 -0
  282. package/.next/static/chunks/7665-e9ef6e0a85b9fa49.js +1 -0
  283. package/.next/static/chunks/8407-35bbfc84793d7004.js +1 -0
  284. package/.next/static/chunks/9079-987939d231704218.js +1 -0
  285. package/.next/static/chunks/app/{layout-538d8d2746ad1588.js → layout-aa8e66164a4367af.js} +1 -1
  286. package/.next/static/chunks/app/sessions/page-cfd645305e25fd33.js +1 -0
  287. package/.next/static/chunks/app/skills/[skillId]/{page-52ba58712faf3378.js → page-cb6e74fd9a6a571d.js} +1 -1
  288. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-704086656fb45699.js +1 -0
  289. package/.next/static/chunks/app/worktrees/[id]/page-a8d7886703ffc7b4.js +1 -0
  290. package/.next/static/css/{743f79ff19ab91e9.css → db8959555c3d9c96.css} +1 -1
  291. package/.next/trace +13 -13
  292. package/dist/cli/commands/auto-yes.d.ts +24 -0
  293. package/dist/cli/commands/auto-yes.d.ts.map +1 -1
  294. package/dist/cli/commands/auto-yes.js +76 -8
  295. package/dist/cli/commands/capture.d.ts.map +1 -1
  296. package/dist/cli/commands/capture.js +5 -0
  297. package/dist/cli/commands/instances.d.ts.map +1 -1
  298. package/dist/cli/commands/instances.js +10 -1
  299. package/dist/cli/commands/prompt.d.ts.map +1 -1
  300. package/dist/cli/commands/prompt.js +5 -3
  301. package/dist/cli/commands/send.d.ts.map +1 -1
  302. package/dist/cli/commands/send.js +19 -6
  303. package/dist/cli/commands/verify.d.ts.map +1 -1
  304. package/dist/cli/commands/verify.js +8 -1
  305. package/dist/cli/commands/wait.d.ts.map +1 -1
  306. package/dist/cli/commands/wait.js +101 -13
  307. package/dist/cli/config/duration-constants.d.ts +16 -13
  308. package/dist/cli/config/duration-constants.d.ts.map +1 -1
  309. package/dist/cli/config/duration-constants.js +21 -21
  310. package/dist/cli/docs/agent-operations.d.ts +2 -2
  311. package/dist/cli/docs/agent-operations.d.ts.map +1 -1
  312. package/dist/cli/docs/agent-operations.js +29 -1
  313. package/dist/cli/types/api-responses.d.ts +145 -3
  314. package/dist/cli/types/api-responses.d.ts.map +1 -1
  315. package/dist/cli/types/index.d.ts +4 -0
  316. package/dist/cli/types/index.d.ts.map +1 -1
  317. package/dist/cli/utils/resource-resolvers.d.ts +0 -18
  318. package/dist/cli/utils/resource-resolvers.d.ts.map +1 -1
  319. package/dist/cli/utils/resource-resolvers.js +1 -51
  320. package/dist/cli/utils/verify-runner.d.ts +24 -1
  321. package/dist/cli/utils/verify-runner.d.ts.map +1 -1
  322. package/dist/cli/utils/verify-runner.js +54 -1
  323. package/dist/config/auto-yes-duration.d.ts +56 -0
  324. package/dist/config/auto-yes-duration.d.ts.map +1 -0
  325. package/dist/config/auto-yes-duration.js +76 -0
  326. package/dist/lib/polling/auto-yes-suppression-reason.d.ts +54 -2
  327. package/dist/lib/polling/auto-yes-suppression-reason.d.ts.map +1 -1
  328. package/dist/lib/polling/auto-yes-suppression-reason.js +33 -2
  329. package/dist/lib/prompt-observation/types.d.ts +13 -0
  330. package/dist/lib/prompt-observation/types.d.ts.map +1 -1
  331. package/dist/server/locales/en/worktree.json +2 -1
  332. package/dist/server/locales/ja/worktree.json +2 -1
  333. package/dist/server/src/config/auto-yes-config.js +14 -19
  334. package/dist/server/src/config/auto-yes-duration.js +76 -0
  335. package/dist/server/src/lib/auto-yes-poller-answer-target.js +38 -0
  336. package/dist/server/src/lib/auto-yes-poller-keys.js +35 -0
  337. package/dist/server/src/lib/auto-yes-poller-session-presence.js +26 -0
  338. package/dist/server/src/lib/auto-yes-poller-tick.js +233 -0
  339. package/dist/server/src/lib/auto-yes-poller.js +337 -376
  340. package/dist/server/src/lib/auto-yes-state.js +59 -4
  341. package/dist/server/src/lib/cli-tools/submit-verified-sender.js +235 -19
  342. package/dist/server/src/lib/db/chat-db.js +5 -1
  343. package/dist/server/src/lib/db/verification-db.js +23 -2
  344. package/dist/server/src/lib/detection/composer-text.js +215 -11
  345. package/dist/server/src/lib/detection/option-recommendation.js +120 -0
  346. package/dist/server/src/lib/detection/prompt-detect-multiple-choice-approval-context.js +290 -0
  347. package/dist/server/src/lib/detection/prompt-detect-multiple-choice-chrome.js +193 -0
  348. package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +402 -539
  349. package/dist/server/src/lib/detection/prompt-detector.js +92 -21
  350. package/dist/server/src/lib/detection/prompt-screen-key.js +60 -0
  351. package/dist/server/src/lib/detection/tools/antigravity/dialog.js +158 -35
  352. package/dist/server/src/lib/detection/tools/claude/detect.js +111 -10
  353. package/dist/server/src/lib/detection/tools/command-code/dialog.js +67 -0
  354. package/dist/server/src/lib/detection/tools/dialog-block.js +16 -0
  355. package/dist/server/src/lib/detection/upstream-faults.js +19 -2
  356. package/dist/server/src/lib/detection/waiting-screen-reading.js +98 -0
  357. package/dist/server/src/lib/hooks/permission-decision-service.js +7 -0
  358. package/dist/server/src/lib/hooks/structured-decision-response.js +1 -1
  359. package/dist/server/src/lib/polling/auto-yes-confirm-page.js +2 -2
  360. package/dist/server/src/lib/polling/auto-yes-confirmation-ledger-types.js +3 -0
  361. package/dist/server/src/lib/polling/auto-yes-confirmation-ledger.js +34 -3
  362. package/dist/server/src/lib/polling/auto-yes-decision-state.js +195 -0
  363. package/dist/server/src/lib/polling/auto-yes-dialog-gate.js +139 -10
  364. package/dist/server/src/lib/polling/auto-yes-enter-fallback.js +14 -0
  365. package/dist/server/src/lib/polling/auto-yes-manual-answer.js +124 -0
  366. package/dist/server/src/lib/polling/auto-yes-option-effect.js +157 -0
  367. package/dist/server/src/lib/polling/auto-yes-policy.js +11 -5
  368. package/dist/server/src/lib/polling/auto-yes-poller-capture.js +15 -2
  369. package/dist/server/src/lib/polling/auto-yes-poller-generation.js +42 -1
  370. package/dist/server/src/lib/polling/auto-yes-poller-receipt-log.js +42 -5
  371. package/dist/server/src/lib/polling/auto-yes-poller-state.js +147 -49
  372. package/dist/server/src/lib/polling/auto-yes-poller-steps.js +172 -2
  373. package/dist/server/src/lib/polling/auto-yes-prompt-suppression.js +2 -2
  374. package/dist/server/src/lib/polling/auto-yes-question-choice.js +57 -0
  375. package/dist/server/src/lib/polling/auto-yes-resolver.js +96 -21
  376. package/dist/server/src/lib/polling/auto-yes-suppression-reason.js +33 -2
  377. package/dist/server/src/lib/polling/auto-yes-suppression-state.js +25 -1
  378. package/dist/server/src/lib/polling/prompt-screen-reading.js +232 -0
  379. package/dist/server/src/lib/polling/response-checker-dialog-gate.js +39 -0
  380. package/dist/server/src/lib/polling/response-checker.js +145 -58
  381. package/dist/server/src/lib/prompt-answer-sender.js +104 -10
  382. package/dist/server/src/lib/push/notification-dedup.js +15 -2
  383. package/dist/server/src/lib/push/prompt-card-state.js +140 -19
  384. package/dist/server/src/lib/push/prompt-push-gate.js +158 -12
  385. package/dist/server/src/lib/push/push-sender.js +23 -3
  386. package/dist/server/src/lib/push/resolution-push-notifier.js +14 -0
  387. package/dist/server/src/lib/push/waiting-push-notifier.js +139 -17
  388. package/dist/server/src/lib/realtime/terminal-broadcast.js +16 -0
  389. package/dist/server/src/lib/session/agent-event-state.js +25 -5
  390. package/dist/server/src/lib/session/agent-event-turn.js +140 -46
  391. package/dist/server/src/lib/session/cli-session.js +21 -2
  392. package/dist/server/src/lib/session/composer-clear.js +56 -4
  393. package/dist/server/src/lib/session/current-output-builder.js +66 -12
  394. package/dist/server/src/lib/session/send-attempt-state.js +24 -7
  395. package/dist/server/src/lib/session/waiting-episode-binding-observation.js +289 -0
  396. package/dist/server/src/lib/session/waiting-episode-binding-resolve.js +38 -0
  397. package/dist/server/src/lib/session/waiting-episode-binding.js +276 -0
  398. package/dist/server/src/lib/session/waiting-episode-state-ops.js +152 -0
  399. package/dist/server/src/lib/session/waiting-episode-state-store.js +202 -0
  400. package/dist/server/src/lib/session/waiting-episode-state-types.js +6 -0
  401. package/dist/server/src/lib/session/waiting-episode-state.js +68 -91
  402. package/dist/server/src/lib/session/waiting-episode-verdict.js +174 -0
  403. package/dist/server/src/lib/tmux/tmux-capture-cache.js +76 -11
  404. package/dist/server/src/lib/tmux/tmux.js +79 -9
  405. package/dist/server/src/lib/verification/gate-runner.js +172 -16
  406. package/dist/server/src/lib/verification/machine-lock.js +5 -0
  407. package/dist/server/src/lib/ws-server.js +18 -3
  408. package/dist/server/src/types/auto-yes-answer-mode.js +38 -0
  409. package/dist/server/src/types/auto-yes-decision.js +61 -0
  410. package/dist/server/src/types/auto-yes.js +6 -0
  411. package/dist/server/src/types/prompt-reading.js +32 -0
  412. package/dist/types/auto-yes-answer-mode.d.ts +35 -0
  413. package/dist/types/auto-yes-answer-mode.d.ts.map +1 -0
  414. package/dist/types/auto-yes-answer-mode.js +38 -0
  415. package/docs/en/user-guide/webapp-guide.md +47 -13
  416. package/package.json +1 -1
  417. package/public/sw.js +76 -4
  418. package/release-notes/0.48.0.json +57 -0
  419. package/release-notes/0.49.0.json +45 -0
  420. package/.next/server/chunks/1559.js +0 -2
  421. package/.next/server/chunks/2460.js +0 -1
  422. package/.next/server/chunks/5595.js +0 -1
  423. package/.next/server/chunks/610.js +0 -1
  424. package/.next/server/chunks/6722.js +0 -1
  425. package/.next/static/chunks/5371-a549a0d2c2044d41.js +0 -1
  426. package/.next/static/chunks/602-1c1488959215e140.js +0 -1
  427. package/.next/static/chunks/6288-09feba367cc2c65e.js +0 -1
  428. package/.next/static/chunks/8407-155f84e5ba05f1d5.js +0 -1
  429. package/.next/static/chunks/9079-24f9fa549ed298f1.js +0 -1
  430. package/.next/static/chunks/app/sessions/page-e02f1adb1d1c1ccc.js +0 -1
  431. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-9cf1ecc59e36a004.js +0 -1
  432. package/.next/static/chunks/app/worktrees/[id]/page-46aced20b2b1c010.js +0 -1
  433. /package/.next/static/{etorRtpxbQqqOzdUeXdw2 → 3_JtAZaKMJEDFIIfkHv4n}/_buildManifest.js +0 -0
  434. /package/.next/static/{etorRtpxbQqqOzdUeXdw2 → 3_JtAZaKMJEDFIIfkHv4n}/_ssgManifest.js +0 -0
@@ -1,19 +1,27 @@
1
1
  "use strict";
2
2
  /** Multiple choice prompt detection logic extracted from prompt-detector.ts (Issue #575). */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.APPROVAL_TARGET_MAX_LOOKBACK = void 0;
5
- exports.findClaudeTaskPanelLines = findClaudeTaskPanelLines;
6
- exports.findClaudeChrome = findClaudeChrome;
7
- exports.maskClaudeChrome = maskClaudeChrome;
4
+ exports.maskClaudeChrome = exports.findClaudeTaskPanelLines = exports.findClaudeChrome = exports.joinApprovalTarget = exports.findApprovalContextStart = exports.findApprovalContextRange = exports.APPROVAL_TARGET_MAX_LOOKBACK = void 0;
8
5
  exports.isTypedTextFieldOption = isTypedTextFieldOption;
9
- exports.findApprovalContextStart = findApprovalContextStart;
10
- exports.joinApprovalTarget = joinApprovalTarget;
11
6
  exports.buildMultipleChoiceResult = buildMultipleChoiceResult;
12
7
  exports.detectMultipleChoicePrompt = detectMultipleChoicePrompt;
13
8
  const tui_detection_frame_1 = require("./tui-detection-frame");
14
9
  const cli_patterns_1 = require("./cli-patterns");
10
+ const patterns_1 = require("./tools/claude/patterns");
15
11
  const prompt_view_1 = require("../../lib/session/prompt-view");
12
+ const option_recommendation_1 = require("./option-recommendation");
16
13
  const picker_chrome_1 = require("./tools/claude/picker-chrome");
14
+ const prompt_detect_multiple_choice_chrome_1 = require("./prompt-detect-multiple-choice-chrome");
15
+ const prompt_detect_multiple_choice_approval_context_1 = require("./prompt-detect-multiple-choice-approval-context");
16
+ var prompt_detect_multiple_choice_approval_context_2 = require("./prompt-detect-multiple-choice-approval-context");
17
+ Object.defineProperty(exports, "APPROVAL_TARGET_MAX_LOOKBACK", { enumerable: true, get: function () { return prompt_detect_multiple_choice_approval_context_2.APPROVAL_TARGET_MAX_LOOKBACK; } });
18
+ Object.defineProperty(exports, "findApprovalContextRange", { enumerable: true, get: function () { return prompt_detect_multiple_choice_approval_context_2.findApprovalContextRange; } });
19
+ Object.defineProperty(exports, "findApprovalContextStart", { enumerable: true, get: function () { return prompt_detect_multiple_choice_approval_context_2.findApprovalContextStart; } });
20
+ Object.defineProperty(exports, "joinApprovalTarget", { enumerable: true, get: function () { return prompt_detect_multiple_choice_approval_context_2.joinApprovalTarget; } });
21
+ var prompt_detect_multiple_choice_chrome_2 = require("./prompt-detect-multiple-choice-chrome");
22
+ Object.defineProperty(exports, "findClaudeChrome", { enumerable: true, get: function () { return prompt_detect_multiple_choice_chrome_2.findClaudeChrome; } });
23
+ Object.defineProperty(exports, "findClaudeTaskPanelLines", { enumerable: true, get: function () { return prompt_detect_multiple_choice_chrome_2.findClaudeTaskPanelLines; } });
24
+ Object.defineProperty(exports, "maskClaudeChrome", { enumerable: true, get: function () { return prompt_detect_multiple_choice_chrome_2.maskClaudeChrome; } });
17
25
  // ============================================================================
18
26
  // Constants
19
27
  // ============================================================================
@@ -58,158 +66,6 @@ const DEFAULT_OPTION_PATTERN = /^\s*[\u276F\u25CF\u203A][^\d]*(\d+)(?:\.(?!\d)|\
58
66
  * Layer 4 (2+ options), and Layer 5 (SEC-001 question line validation).
59
67
  */
60
68
  const NORMAL_OPTION_PATTERN = /^\s*[^\d]{0,3}(\d+)(?:\.(?!\d)|\s+)\s*(.+)$/;
61
- /**
62
- * [Issue #1708] Header row of Claude Code's bottom-anchored task panel, e.g.
63
- * " 7 tasks (0 done, 1 in progress, 6 open)"
64
- *
65
- * The panel is an overlay pinned to the LAST rows of the pane, structurally
66
- * unrelated to whatever the agent is currently blocked on. In a production pane
67
- * (200x1000, alternate screen) a prompt dialog therefore renders near the top and
68
- * the panel ~880 blank rows below it — rows `compactBlankRows` collapses to one,
69
- * putting the panel squarely inside Pass 2's 50-line reverse-scan window.
70
- *
71
- * That is only harmless while something trims it away. #704/#807 trim at a
72
- * footer, but the AskUserQuestion *confirmation* step ("Ready to submit your
73
- * answers?") renders NEITHER footer, so nothing trims and the panel is the first
74
- * thing the reverse scan meets. Measured on the live capture in
75
- * fixtures/claude-live-1708/askuserquestion-submit-taskpanel.txt, two of its rows
76
- * match NORMAL_OPTION_PATTERN independently:
77
- * - this header → option 7, label "tasks (2 done, 1 in progress, 4 open)"
78
- * - "… +2 completed" → option 2, label "completed"
79
- * Either one alone is enough to make isValidPrecedingOption() reject the real
80
- * `❯ 1. Submit answers` / `2. Cancel` above it, so collectedOptions never reaches
81
- * 2 and the frame is published as "no prompt" — Issue #1708's silent timeout.
82
- *
83
- * Anchored at line start and requires the literal `tasks (` + `<n> done`, which
84
- * no option label produced by any supported CLI carries. ReDoS safe (S4-001):
85
- * bounded repetition, no nesting.
86
- */
87
- const CLAUDE_TASK_PANEL_HEADER_PATTERN = /^\s*\d{1,3}\s+tasks?\s+\(\d+\s+done\b/i;
88
- /**
89
- * [Issue #1708] A single task row of the panel described above, e.g. " ◼ Phase 3".
90
- *
91
- * Today these rows are inert — `isContinuationLine()` already absorbs them — but
92
- * only because none of the observed titles begins with a digit. A title like
93
- * "2. Fix the parser" would render as " ◻ 2. Fix the parser" and be collected as
94
- * option 2, reintroducing the same poisoning from a direction no single-line
95
- * guard anticipates. Skipping the whole panel block closes that off structurally
96
- * instead of one wording at a time.
97
- *
98
- * The glyph set is exactly the two forms measured on a live pane — ◼ (in
99
- * progress) and ◻ (open) — and nothing else. Every extra glyph is a line this
100
- * guard can delete from the frame, so the set is an allowlist of things seen,
101
- * not a guess at what the family might contain:
102
- *
103
- * - ❯ ● › are DEFAULT_OPTION_PATTERN's cursor indicators.
104
- * - ⏺ ⎿ ✔ ✗ are the transcript markers TURN_BOUNDARY_PATTERN reads.
105
- * - ☐ ☑ ☒ are the natural rendering of a MULTI-SELECT choice, and
106
- * AskUserQuestion has a multiSelect mode. Measured: with those in the set,
107
- * `☐ 1. Blue / ☑ 2. Green / ☐ 3. Red` under a panel header is claimed as
108
- * panel content and the prompt above it disappears entirely
109
- * (isPrompt=false, zero options) — Issue #1708's own failure, recreated by
110
- * its fix. See the regression test.
111
- *
112
- * A completed row never appears (the panel collapses those into `… +N
113
- * completed`, which SUMMARY_LINE_PATTERN handles), so no done-marker is needed.
114
- * An unmatched glyph simply ends the block, which is the safe direction: those
115
- * rows go back to being handled exactly as they were before this guard existed.
116
- *
117
- * To widen this, capture a live pane that actually renders the new glyph and add
118
- * it as a fixture. Do not widen it from the shape of the character.
119
- */
120
- const CLAUDE_TASK_PANEL_ROW_PATTERN = /^\s*[◼◻]\s/;
121
- /**
122
- * [Issue #2468] Claude Code 2.1.267's session-diff HUD row, in the two
123
- * spellings measured on a live pane (tests/fixtures/claude-live-2468):
124
- * "No changes this session"
125
- * "+28 files edited before this session (show)"
126
- *
127
- * Chrome like the task panel, but a single row and RIGHT-aligned: on a
128
- * 200-column pane it is ~110 columns of padding and then the text. It is
129
- * therefore matched on the trimmed row — a reader that slices the row from the
130
- * left sees nothing but blanks.
131
- *
132
- * It broke two readers. Pass 2 below parses `+2 files edited …` as option 2,
133
- * which poisons the real `1. / 2.` above it the way `… +2 completed` did in
134
- * #1708. And `findClaudeTranscriptTail` took it for the last transcript row, so
135
- * `detectClaudeDialog` read it as the footer of the AskUserQuestion
136
- * confirmation screen — a dialog that draws no footer — and refused a dialog
137
- * that was open on screen: Auto-Yes, the UI's answer and `commandmate respond`
138
- * all got `prompt_no_longer_active`.
139
- *
140
- * `files?` is the one spelling not seen on a pane: a one-file session was not
141
- * captured, and the singular is accepted so that it is not the case that
142
- * regresses. Everything else is literal, for the same reason the glyph set
143
- * above is an allowlist. Anchored at both ends, no nested quantifiers — ReDoS
144
- * safe (S4-001).
145
- */
146
- const CLAUDE_SESSION_DIFF_HUD_PATTERN = /^(?:No changes this session|\+\d+ files? edited before this session \(show\))$/;
147
- /**
148
- * Line indices belonging to Claude's bottom-pinned panels within [start, end).
149
- *
150
- * Two panels, both chrome rather than transcript or prompt:
151
- * - the task panel (#1708). Walks down from each header row for as long as
152
- * rows keep looking like panel content (task rows or the collapsed `… +N …`
153
- * summary). The first line that is neither ends the block, so a panel that
154
- * abuts other content cannot swallow it.
155
- * - the session-diff HUD (#2468), a single row claimed on its own.
156
- *
157
- * Pass 2 here, `findClaudeTranscriptTail` and `detectClaudeDialog`'s footer
158
- * guard all read this one set — through {@link findClaudeChrome}, which adds an
159
- * AskUserQuestion preview pane to it (Issue #2486) — so they cannot disagree
160
- * about which rows are chrome.
161
- *
162
- * @param lines - Full frame, already ANSI/box stripped
163
- * @param start - First index to consider (inclusive)
164
- * @param end - Last index to consider (exclusive)
165
- * @returns Indices Pass 2 must treat as metadata rather than options
166
- */
167
- function findClaudeTaskPanelLines(lines, start, end) {
168
- const panelLines = new Set();
169
- for (let i = Math.max(0, start); i < Math.min(end, lines.length); i++) {
170
- if (CLAUDE_SESSION_DIFF_HUD_PATTERN.test(lines[i].trim())) {
171
- panelLines.add(i);
172
- continue;
173
- }
174
- if (!CLAUDE_TASK_PANEL_HEADER_PATTERN.test(lines[i]))
175
- continue;
176
- panelLines.add(i);
177
- for (let j = i + 1; j < Math.min(end, lines.length); j++) {
178
- const row = lines[j];
179
- if (!CLAUDE_TASK_PANEL_ROW_PATTERN.test(row) && !SUMMARY_LINE_PATTERN.test(row))
180
- break;
181
- panelLines.add(j);
182
- }
183
- }
184
- return panelLines;
185
- }
186
- /**
187
- * Claude's chrome within [start, end). See {@link ClaudeChrome}.
188
- *
189
- * @param lines - Full frame, ANSI stripped; box drawing optional
190
- * @param start - First index to consider (inclusive)
191
- * @param end - Last index to consider (exclusive)
192
- */
193
- function findClaudeChrome(lines, start, end) {
194
- const rows = findClaudeTaskPanelLines(lines, start, end);
195
- const panes = (0, picker_chrome_1.findClaudePreviewPanes)(lines, start, end);
196
- for (const row of panes.rows)
197
- rows.add(row);
198
- return { rows, cuts: panes.cuts };
199
- }
200
- /**
201
- * The frame with Claude's chrome taken out: chrome rows blank, rows shared with
202
- * the preview pane cut back to the options column. Row indices are unchanged,
203
- * so a reader of the result can report positions in the original frame.
204
- */
205
- function maskClaudeChrome(lines, chrome) {
206
- return lines.map((line, i) => {
207
- if (chrome.rows.has(i))
208
- return '';
209
- const cut = chrome.cuts.get(i);
210
- return cut === undefined ? line : line.slice(0, cut).trimEnd();
211
- });
212
- }
213
69
  /**
214
70
  * [Issue #2486] An indented row in the options column beside a preview pane.
215
71
  *
@@ -220,13 +76,6 @@ function maskClaudeChrome(lines, chrome) {
220
76
  * Anchored, no nested quantifiers — ReDoS safe (S4-001).
221
77
  */
222
78
  const PANE_WRAPPED_LABEL_PATTERN = /^\s{3,}\S/;
223
- /**
224
- * Pattern for separator lines (horizontal rules).
225
- * Matches lines consisting only of dash (-) or em-dash (─) characters.
226
- * Used to skip separator lines in question extraction and non-option line handling.
227
- * Anchored at both ends -- ReDoS safe (S4-001).
228
- */
229
- const SEPARATOR_LINE_PATTERN = /^[-─]+$/;
230
79
  /**
231
80
  * How many lines above `questionEndIndex` the question text is collected from.
232
81
  * Also the point the Issue #1699 approval-context scan starts walking up from,
@@ -241,39 +90,6 @@ const QUESTION_CONTEXT_LINES = 5;
241
90
  * not selectable options and must not be parsed as numbered choices.
242
91
  */
243
92
  const COLLAPSED_OUTPUT_PATTERN = /^\s*\[[^\]]*\d+\s+lines?\]/i;
244
- /**
245
- * [Issue #704] Pattern for Claude Code v2.1.142 prompt summary lines such as
246
- * " … +1 pending"
247
- * " +2 pending"
248
- * " ↑3 more"
249
- * " ↓1 more"
250
- * These lines are rendered BELOW the real options when Claude collapses the
251
- * remaining choices. They are not selectable options and must be skipped by
252
- * Pass 2 — otherwise NORMAL_OPTION_PATTERN matches them as option N with
253
- * label "pending"/"more", and the resulting (number, label) tuple poisons
254
- * isValidPrecedingOption() so that the real 1./2./3. options above are all
255
- * rejected (collectedOptions ends up with length 1 and Layer 4 returns
256
- * no_prompt).
257
- *
258
- * Scope of the pattern (FP suppression is critical):
259
- * - Anchored at line start (^) and requires the trailing keyword to be
260
- * `pending` or `more` followed by a word boundary (\b). This intentionally
261
- * prevents matching legitimate option labels that mention these words
262
- * mid-sentence (e.g. `2. Postpone (will remain pending)`,
263
- * `3. Show me more`) — those still flow through NORMAL_OPTION_PATTERN.
264
- * - Optional leading ellipsis (… U+2026) and optional collapse direction
265
- * marker (+ - ↑ ↓ ⏵ ⏷) accept the variants observed in the wild.
266
- * - [Issue #1708] `completed` joined `pending`/`more` after a live capture
267
- * (fixtures/claude-live-1708) showed the task panel collapsing FINISHED
268
- * rows as `… +2 completed`. That wording was outside the #704 guard, so it
269
- * reached Pass 2 and was collected as `option 2 / label='completed'` —
270
- * poisoning isValidPrecedingOption() exactly as `… +1 pending` used to.
271
- * - The pattern deliberately omits any free `(.+)` capture — it must
272
- * short-circuit, not extract data.
273
- *
274
- * Pattern shape: linear regex with no nested quantifiers — ReDoS safe (S4-001).
275
- */
276
- const SUMMARY_LINE_PATTERN = /^\s*[…]?\s*[+\-↑↓⏵⏷]?\s*\d+\s+(?:pending|more|completed)\b/i;
277
93
  /**
278
94
  * [Issue #704] Pattern for the Claude Code v2.1.142 prompt footer line.
279
95
  * Example: " Esc to cancel · Tab to amend → · Herding…"
@@ -552,7 +368,7 @@ function findQuestionLineInRange(lines, startIndex, scanRange, lowerBound) {
552
368
  for (let i = startIndex - 1; i >= scanLimit; i--) {
553
369
  const candidateLine = lines[i]?.trim() ?? '';
554
370
  // Skip empty lines and separator lines (horizontal rules)
555
- if (!candidateLine || SEPARATOR_LINE_PATTERN.test(candidateLine))
371
+ if (!candidateLine || prompt_detect_multiple_choice_approval_context_1.SEPARATOR_LINE_PATTERN.test(candidateLine))
556
372
  continue;
557
373
  if (isQuestionLikeLine(candidateLine)) {
558
374
  return true;
@@ -649,26 +465,86 @@ function isContinuationLine(rawLine, line) {
649
465
  }
650
466
  return false;
651
467
  }
468
+ /**
469
+ * The user's own message echo (Issue #3832): claude's `❯ msg`, codex's `› msg`.
470
+ * The cursor of an option row (`❯ 1.`) is not one. `>` is left out on purpose:
471
+ * codex's trust dialog opens with `> You are in <dir>` and a here-document's
472
+ * body carries `> ` rows, so a `>` row may be the prompt's own.
473
+ */
474
+ const USER_ECHO_PATTERN = /^[❯›]\s(?!\s*\d+[.)]\s)/;
475
+ /**
476
+ * codex's end-of-turn row (Issue #3832): `Worked for 17s • 19:30`, or the rule
477
+ * form `─ Worked for 1m 2s ─`. claude's is {@link CLAUDE_TURN_COMPLETE_PATTERN}.
478
+ */
479
+ const CODEX_TURN_COMPLETE_PATTERN = /^─*\s*Worked for\s+(?:\d+h\s*)?(?:\d+m\s*)?\d+s\b/;
480
+ /** True when the nearest non-blank row above `row` (down to `floor`) ends a finished turn. */
481
+ function followsFinishedTurn(lines, row, floor) {
482
+ for (let i = row - 1; i >= floor; i--) {
483
+ const line = lines[i];
484
+ if (line === undefined || line.trim() === '')
485
+ continue;
486
+ return patterns_1.CLAUDE_TURN_COMPLETE_PATTERN.test(line) || CODEX_TURN_COMPLETE_PATTERN.test(line.trim());
487
+ }
488
+ return false;
489
+ }
490
+ /**
491
+ * The lowest row in `[from, questionEndIndex)` that is the user's message echo
492
+ * (Issue #3832), or -1.
493
+ *
494
+ * A `❯ ` / `› ` row is taken for the echo only when the screen leaves no doubt:
495
+ * drawn left of the dialog's frame (claude's echo at column 0 against its frame
496
+ * at 1, codex's at 0 against 2), and right under the previous turn's end-of-turn
497
+ * row (`✻ Baked for 3s`, `Worked for 17s`). A body row that happens to start
498
+ * with the glyph — at the frame's column, or under the command it belongs to —
499
+ * is not one, so the rows above it stay in `question` and the deny surface is
500
+ * taken wide.
501
+ */
502
+ function findUserEchoRow(lines, questionEndIndex, from, panelEnd, floor) {
503
+ const frameIndent = (0, prompt_detect_multiple_choice_approval_context_1.panelFrameIndent)(lines, questionEndIndex, panelEnd);
504
+ for (let i = questionEndIndex - 1; i >= from; i--) {
505
+ const line = lines[i];
506
+ if (line === undefined || line.trim() === '')
507
+ continue;
508
+ if ((0, prompt_detect_multiple_choice_approval_context_1.indentOf)(line) < frameIndent && USER_ECHO_PATTERN.test(line.trim())
509
+ && followsFinishedTurn(lines, i, floor)) {
510
+ return i;
511
+ }
512
+ }
513
+ return -1;
514
+ }
652
515
  /**
653
516
  * Extract question text from the lines around questionEndIndex.
654
517
  * Collects non-empty, non-separator lines from up to 5 lines before questionEndIndex
655
518
  * through questionEndIndex itself, joining them with spaces.
656
519
  *
520
+ * [Issue #3832] Not above the user's message echo ({@link findUserEchoRow}):
521
+ * that message opened the current turn, so the rows above it — the previous
522
+ * turn's `⏺ IMPL_COMPLETED` / `✻ Cogitated …`, codex's `• HTTP/2 200` /
523
+ * `Worked for 17s` on the measured screens — are not the prompt's and used to
524
+ * reach the deny surface through `question`. The echo row itself stays. No
525
+ * other row bounds the window: a `⏺` / `●` / `•` row may be the call the prompt
526
+ * asks about (codex's `• Calling …`, agy's `● Bash(…)`), and `question` is what
527
+ * keeps such a row in the deny surface when `approvalTarget` cuts at it.
528
+ *
657
529
  * @param lines - Array of output lines
658
530
  * @param questionEndIndex - Index of the last line before options, or -1 if not found
659
531
  * @param floor - First row the question may start at. Issue #2486: one below an
660
532
  * AskUserQuestion picker's tab row, so neither that row nor the transcript
661
533
  * above it becomes part of the question; 0 when there is none.
534
+ * @param panelEnd - Exclusive end of the dialog's own rows, which set the frame
535
+ * column the echo is drawn at or left of (Issue #3832)
662
536
  * @returns Extracted question text, or generic fallback if questionEndIndex is -1
663
537
  */
664
- function extractQuestionText(lines, questionEndIndex, floor = 0) {
538
+ function extractQuestionText(lines, questionEndIndex, floor = 0, panelEnd = questionEndIndex + 1) {
665
539
  if (questionEndIndex < 0) {
666
540
  return 'Please select an option:';
667
541
  }
542
+ const windowStart = Math.max(floor, questionEndIndex - QUESTION_CONTEXT_LINES);
543
+ const start = Math.max(windowStart, findUserEchoRow(lines, questionEndIndex, windowStart, panelEnd, floor));
668
544
  const questionLines = [];
669
- for (let i = Math.max(floor, questionEndIndex - QUESTION_CONTEXT_LINES); i <= questionEndIndex; i++) {
545
+ for (let i = start; i <= questionEndIndex; i++) {
670
546
  const line = lines[i].trim();
671
- if (line && !SEPARATOR_LINE_PATTERN.test(line)) {
547
+ if (line && !prompt_detect_multiple_choice_approval_context_1.SEPARATOR_LINE_PATTERN.test(line)) {
672
548
  questionLines.push(line);
673
549
  }
674
550
  }
@@ -694,161 +570,6 @@ function extractInstructionText(lines, questionEndIndex, effectiveEnd) {
694
570
  const joined = blockLines.join('\n').trim();
695
571
  return joined.length > 0 ? joined : undefined;
696
572
  }
697
- /**
698
- * Lines that mean "the previous turn ended here" when scanning upward.
699
- *
700
- * Every supported CLI renders its transcript as marker-prefixed rows — Claude's
701
- * tool-use `⏺` and result `⎿`, Codex's activity `•`, its approval receipts `✔`
702
- * / `✗`, the user's own `›` / `❯` / `>` echo, and its output continuation `└`.
703
- * The lines a prompt panel puts above its question (a header such as "Bash
704
- * command", the command itself, a one-line description) never carry one, which
705
- * is what makes this usable as the panel's upper edge after `stripBoxDrawing`
706
- * has erased the real box border.
707
- *
708
- * Horizontal rules count too: Codex draws one between turns.
709
- *
710
- * [Issue #3817] Not inside the panel's own frame: the command a panel shows can
711
- * carry the same glyphs (a here-document's `> quoted` rows), drawn deeper than
712
- * the panel's header, question and option rows. See
713
- * {@link findApprovalContextStart}.
714
- *
715
- * Anchored at the start of the trimmed line — ReDoS safe (no repetition).
716
- */
717
- const TURN_BOUNDARY_PATTERN = /^[⏺⎿●○•✔✓✖✗✘›»❯└├>]/;
718
- /**
719
- * How far above the question the approval context may reach (Issue #1699).
720
- *
721
- * The boundary marker above is the actual fix; this cap only exists so a frame
722
- * that happens to contain no marker at all (a bare shell `(y/n)` prompt under
723
- * plain command output) cannot drag an unbounded scrollback window into the
724
- * deny surface the way the old fixed 19-line lookback did. Sized for the
725
- * tallest real permission panel measured: header + blank + a wrapped multi-line
726
- * command + description + blank.
727
- */
728
- exports.APPROVAL_TARGET_MAX_LOOKBACK = 12;
729
- /**
730
- * A previous tool call's result row (Issue #3817): claude's `⎿`, codex's `└` /
731
- * `├` output continuation. Never the user's echo or a quote (`>`).
732
- */
733
- const RESULT_MARKER_PATTERN = /^[⎿└├]/;
734
- /** Width of a row's leading whitespace. */
735
- function indentOf(line) {
736
- return line.length - line.trimStart().length;
737
- }
738
- /**
739
- * The column the dialog's own frame is drawn at (Issue #3817): the least
740
- * indented non-blank row from the question down to `panelEnd`. Those rows are
741
- * the question, the option block and the footer — claude puts them at column 1
742
- * and the command at column 3, codex its `› 1.` cursor at column 0.
743
- */
744
- function panelFrameIndent(lines, questionLineIndex, panelEnd) {
745
- let indent = Infinity;
746
- for (let i = questionLineIndex; i < Math.min(panelEnd, lines.length); i++) {
747
- const line = lines[i];
748
- if (line === undefined || line.trim() === '')
749
- continue;
750
- indent = Math.min(indent, indentOf(line));
751
- }
752
- return indent === Infinity ? 0 : indent;
753
- }
754
- /** True when this line carries a transcript marker or is a rule. */
755
- function isTurnBoundaryLine(rawLine) {
756
- if (rawLine === undefined)
757
- return false;
758
- const line = rawLine.trim();
759
- if (line === '')
760
- return false;
761
- return TURN_BOUNDARY_PATTERN.test(line) || SEPARATOR_LINE_PATTERN.test(line);
762
- }
763
- /**
764
- * First line index that still belongs to *this* prompt, scanning upward.
765
- *
766
- * Walks up from `questionLineIndex - 1` and stops just below the nearest turn
767
- * boundary; with no boundary in reach it falls back to the lookback cap.
768
- * Everything above the returned index is a previous turn and must never reach a
769
- * deny pattern — that poisoning is Issue #1699.
770
- *
771
- * [Issue #3817] A marker row indented deeper than the dialog's frame
772
- * ({@link panelFrameIndent}) is the body of the command the panel asks about
773
- * when a frame row sits above it — the `> quoted` rows of a here-document
774
- * under a claude panel's ` Bash command` header. Cutting there took the command
775
- * rows above it out of the deny surface. The previous turn's markers are drawn
776
- * at or left of the frame (`⏺`, the user's `> msg` / `❯ msg` echo, a rule), or
777
- * deeper with no frame row above them (claude's ` ⎿` result right under its
778
- * `⏺`), and both still end the scan.
779
- *
780
- * A frame row cannot be told from a body row drawn at the frame's column (a
781
- * shell here-document's `EOF`), so a frame row is taken as an older panel's
782
- * header only in one shape: a tool-use block — a marker row at the frame's
783
- * column, then that row, then deeper rows that are all result markers
784
- * ({@link RESULT_MARKER_PATTERN}). Anything else between two frame rows is read
785
- * as body: when the screen cannot tell, the deny surface is taken wide.
786
- *
787
- * Shared by both detector paths: multiple_choice passes its `questionEndIndex`,
788
- * yes_no the line its pattern matched on.
789
- *
790
- * @param lines - Full frame, already ANSI/box stripped
791
- * @param questionLineIndex - Line the prompt's own question sits on
792
- * @param panelEnd - Exclusive end of the dialog's own rows (Issue #3817); their
793
- * least indented row is the frame. Defaults to the question row alone.
794
- * @returns Index of the first line of the current prompt's own block
795
- */
796
- function findApprovalContextStart(lines, questionLineIndex, panelEnd = questionLineIndex + 1) {
797
- const floor = Math.max(0, questionLineIndex - exports.APPROVAL_TARGET_MAX_LOOKBACK);
798
- const frameIndent = panelFrameIndent(lines, questionLineIndex, panelEnd);
799
- // The lowest deeper marker row with no frame row found above it yet.
800
- let unenclosed = -1;
801
- // The deeper rows since the last frame row: are they all result markers?
802
- let segmentRows = 0;
803
- let segmentAllResults = true;
804
- for (let i = questionLineIndex - 1; i >= floor; i--) {
805
- const line = lines[i];
806
- if (line === undefined || line.trim() === '')
807
- continue;
808
- const atFrame = indentOf(line) <= frameIndent;
809
- const marker = isTurnBoundaryLine(line);
810
- if (marker && atFrame)
811
- return (unenclosed >= 0 ? unenclosed : i) + 1;
812
- if (!atFrame) {
813
- if (marker && unenclosed < 0)
814
- unenclosed = i;
815
- segmentRows++;
816
- segmentAllResults &&= RESULT_MARKER_PATTERN.test(line.trim());
817
- continue;
818
- }
819
- // A frame row: an older tool-use block's header only in that one shape.
820
- if (unenclosed >= 0 && segmentRows > 0 && segmentAllResults && isMarkerRowAtFrameAbove(lines, i, floor, frameIndent)) {
821
- return unenclosed + 1;
822
- }
823
- unenclosed = -1;
824
- segmentRows = 0;
825
- segmentAllResults = true;
826
- }
827
- return unenclosed >= 0 ? unenclosed + 1 : floor;
828
- }
829
- /** True when the nearest non-blank row above `row` is a marker row at the frame's column. */
830
- function isMarkerRowAtFrameAbove(lines, row, floor, frameIndent) {
831
- for (let i = row - 1; i >= floor; i--) {
832
- const line = lines[i];
833
- if (line === undefined || line.trim() === '')
834
- continue;
835
- return indentOf(line) <= frameIndent && isTurnBoundaryLine(line);
836
- }
837
- return false;
838
- }
839
- /**
840
- * Join a slice of the frame into an approval-target string.
841
- *
842
- * @returns The trimmed block, or undefined when it is empty
843
- */
844
- function joinApprovalTarget(lines, start, end) {
845
- const joined = lines
846
- .slice(start, end)
847
- .map(l => l.trimEnd())
848
- .join('\n')
849
- .trim();
850
- return joined.length > 0 ? joined : undefined;
851
- }
852
573
  /**
853
574
  * Extract what this multiple-choice prompt is asking approval for (Issue #1699).
854
575
  *
@@ -864,8 +585,9 @@ function joinApprovalTarget(lines, start, end) {
864
585
  *
865
586
  * Residual, deliberately left alone: `question` itself is built from up to
866
587
  * {@link QUESTION_CONTEXT_LINES} lines above `questionEndIndex` and is also part
867
- * of the deny surface. Narrowing that would change prompt dedup keys and what
868
- * the UI renders, and it is bounded by the options below it either way.
588
+ * of the deny surface. Issue #3832 only stops it at the user's message echo
589
+ * (see {@link extractQuestionText}); a previous turn below no echo still
590
+ * reaches it, which over-suppresses rather than under-suppresses.
869
591
  *
870
592
  * [Issue #2486] One exception to both: neither reaches above an AskUserQuestion
871
593
  * picker's tab row. Everything up there is the transcript — the Bash call Claude
@@ -882,7 +604,19 @@ function extractApprovalTarget(lines, questionEndIndex, effectiveEnd, floor = 0)
882
604
  if (questionEndIndex < 0) {
883
605
  return undefined;
884
606
  }
885
- return joinApprovalTarget(lines, Math.max(floor, findApprovalContextStart(lines, questionEndIndex, effectiveEnd)), effectiveEnd);
607
+ const range = (0, prompt_detect_multiple_choice_approval_context_1.findApprovalContextRange)(lines, questionEndIndex, effectiveEnd);
608
+ // Issue #3881: the picker's tab row is a boundary of its own, so a scan it
609
+ // cuts short is complete whatever the scan said about the rows above.
610
+ const bounded = floor > 0 && floor >= range.start;
611
+ const start = Math.max(floor, range.start);
612
+ return {
613
+ approvalTarget: (0, prompt_detect_multiple_choice_approval_context_1.joinApprovalTarget)(lines, start, effectiveEnd),
614
+ approvalContext: {
615
+ startRow: start,
616
+ endRow: effectiveEnd,
617
+ completeness: bounded ? 'complete' : range.completeness,
618
+ },
619
+ };
886
620
  }
887
621
  /**
888
622
  * Build the final PromptDetectionResult for a multiple choice prompt.
@@ -901,7 +635,7 @@ function extractApprovalTarget(lines, questionEndIndex, effectiveEnd, floor = 0)
901
635
  * @returns PromptDetectionResult with isPrompt: true and multiple_choice data
902
636
  */
903
637
  function buildMultipleChoiceResult(question, collectedOptions, context, output, truncateRawContentFn, submitMode, isAskUserQuestion) {
904
- const { instructionText, approvalTarget } = context ?? {};
638
+ const { instructionText, approvalTarget, approvalContext } = context ?? {};
905
639
  return {
906
640
  isPrompt: true,
907
641
  promptData: {
@@ -919,11 +653,16 @@ function buildMultipleChoiceResult(question, collectedOptions, context, output,
919
653
  status: 'pending',
920
654
  instructionText,
921
655
  ...(approvalTarget !== undefined ? { approvalTarget } : {}),
656
+ // Issue #3881 (H-3): only the case the resolver must act on.
657
+ ...(approvalTarget !== undefined && approvalContext?.completeness === 'truncated'
658
+ ? { approvalTargetCompleteness: 'truncated' }
659
+ : {}),
922
660
  ...(submitMode ? { submitMode } : {}),
923
661
  ...(isAskUserQuestion ? { isAskUserQuestion: true } : {}),
924
662
  },
925
663
  cleanContent: question.trim(),
926
664
  rawContent: truncateRawContentFn(output.trim()), // Issue #235: complete prompt output (truncated) [MF-001]
665
+ ...(approvalContext !== undefined ? { approvalContext } : {}),
927
666
  };
928
667
  }
929
668
  // ============================================================================
@@ -975,6 +714,61 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
975
714
  // C-003: Use ?? true for readability instead of !== false double negation
976
715
  const requireDefault = options?.requireDefaultIndicator ?? true;
977
716
  const lines = output.split('\n');
717
+ const { scanStart, effectiveEnd, hasAskUserQuestionFooter } = findScanWindow(lines);
718
+ const scanWindow = lines.slice(scanStart, effectiveEnd);
719
+ if (hasModelOverlayFooter(scanWindow)) {
720
+ return noPromptResult(output);
721
+ }
722
+ const { hasConfirmationFooter, hasNumberFooter } = findConfirmationFooter(scanWindow);
723
+ // ==========================================================================
724
+ // Pass 1: Check for ❯ indicator existence in scan window
725
+ // If no ❯ lines found and requireDefault is true, there is no multiple_choice prompt.
726
+ // When requireDefault is false, skip this gate entirely to allow ❯-less detection.
727
+ // ==========================================================================
728
+ if (requireDefault) {
729
+ const hasDefaultLine = hasDefaultOptionLine(scanWindow);
730
+ if (!hasDefaultLine && !hasConfirmationFooter) {
731
+ return noPromptResult(output);
732
+ }
733
+ }
734
+ const scan = collectOptions(lines, scanStart, effectiveEnd);
735
+ if (scan === null) {
736
+ return noPromptResult(output);
737
+ }
738
+ const { collectedOptions, optionRows, questionEndIndex } = scan;
739
+ if (!passesOptionValidation(collectedOptions, requireDefault, hasConfirmationFooter, questionEndIndex, lines, scanStart)) {
740
+ return noPromptResult(output);
741
+ }
742
+ // [Issue #2486] An AskUserQuestion picker's tab row is the dialog's top edge:
743
+ // the row is chrome and everything above it is transcript. Found once so
744
+ // `question` and `approvalTarget` start at the same row; 0 on a frame with no
745
+ // tab row, which leaves every other prompt exactly as it was. `instructionText`
746
+ // keeps its wide window — it is the human's view, and on a preview picker the
747
+ // pane it carries is what the question asks the human to look at.
748
+ const pickerTop = questionEndIndex < 0
749
+ ? 0
750
+ : (0, picker_chrome_1.findAskUserQuestionTabRow)(lines, questionEndIndex, prompt_detect_multiple_choice_approval_context_1.APPROVAL_TARGET_MAX_LOOKBACK) + 1;
751
+ const question = extractQuestionText(lines, questionEndIndex, pickerTop, effectiveEnd);
752
+ const instructionText = extractInstructionText(lines, questionEndIndex, effectiveEnd);
753
+ // Issue #1699: the same block, cut at the previous turn's boundary. Display
754
+ // keeps the wide window; deny patterns are judged against this one.
755
+ const approvalBlock = extractApprovalTarget(lines, questionEndIndex, effectiveEnd, pickerTop);
756
+ // Issue #616: Determine submitMode from confirmation footer (detected in single-pass above)
757
+ const submitMode = hasConfirmationFooter
758
+ ? (hasNumberFooter ? 'answer_only' : 'answer_then_enter')
759
+ : undefined;
760
+ const result = buildMultipleChoiceResult(question, collectedOptions, { instructionText, ...approvalBlock }, output, truncateRawContentFn, submitMode, hasAskUserQuestionFooter);
761
+ // [Issue #3863] Only the AskUserQuestion picker: the one this parser reads
762
+ // as a question, whose rows are a title and an indented description.
763
+ if (!hasAskUserQuestionFooter)
764
+ return result;
765
+ const paneColumns = (0, option_recommendation_1.lowerBoundPaneColumns)(lines);
766
+ return {
767
+ ...result,
768
+ optionRecommendations: collectedOptions.map((option, index) => readPickerOptionRecommendation(option.label, optionRows[index], paneColumns)),
769
+ };
770
+ }
771
+ function findScanWindow(lines) {
978
772
  // Strip trailing empty lines (tmux terminal padding) before computing scan window.
979
773
  // tmux buffers often end with many empty padding lines that would shift the
980
774
  // scan window away from the actual prompt content.
@@ -1008,7 +802,9 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
1008
802
  break;
1009
803
  }
1010
804
  }
1011
- const scanWindow = lines.slice(scanStart, effectiveEnd);
805
+ return { scanStart, effectiveEnd, hasAskUserQuestionFooter };
806
+ }
807
+ function hasModelOverlayFooter(scanWindow) {
1012
808
  // [Issue #1495] Claude `/model` local-settings overlay guard.
1013
809
  // The `/model` overlay renders a ❯-marked numbered model list ("1. Default …
1014
810
  // 5. Haiku") under a "Select model" header, which the option-collection logic
@@ -1024,9 +820,12 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
1024
820
  // selection list via CLAUDE_SELECTION_LIST_FOOTER (NavigationButtons + ESC hatch).
1025
821
  for (const rawLine of scanWindow) {
1026
822
  if (cli_patterns_1.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN.test(rawLine)) {
1027
- return noPromptResult(output);
823
+ return true;
1028
824
  }
1029
825
  }
826
+ return false;
827
+ }
828
+ function findConfirmationFooter(scanWindow) {
1030
829
  // Single-pass footer detection: identify both confirmation footer presence and
1031
830
  // specific "press number to confirm" variant in one iteration (Issue #616).
1032
831
  let hasConfirmationFooter = false;
@@ -1041,31 +840,36 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
1041
840
  break; // Both patterns are subsets of CONFIRMATION_FOOTER; one match suffices
1042
841
  }
1043
842
  }
1044
- // ==========================================================================
1045
- // Pass 1: Check for ❯ indicator existence in scan window
1046
- // If no ❯ lines found and requireDefault is true, there is no multiple_choice prompt.
1047
- // When requireDefault is false, skip this gate entirely to allow ❯-less detection.
1048
- // ==========================================================================
1049
- if (requireDefault) {
1050
- let hasDefaultLine = false;
1051
- for (const rawLine of scanWindow) {
1052
- const line = rawLine.trim();
1053
- if (DEFAULT_OPTION_PATTERN.test(line)) {
1054
- hasDefaultLine = true;
1055
- break;
1056
- }
1057
- }
1058
- if (!hasDefaultLine && !hasConfirmationFooter) {
1059
- return noPromptResult(output);
843
+ return { hasConfirmationFooter, hasNumberFooter };
844
+ }
845
+ function hasDefaultOptionLine(scanWindow) {
846
+ for (const rawLine of scanWindow) {
847
+ const line = rawLine.trim();
848
+ if (DEFAULT_OPTION_PATTERN.test(line)) {
849
+ return true;
1060
850
  }
1061
851
  }
852
+ return false;
853
+ }
854
+ function collectOptions(lines, scanStart, effectiveEnd) {
1062
855
  // ==========================================================================
1063
856
  // Pass 2: Collect options (executed when Pass 1 passes or is skipped)
1064
857
  // Scan from end to find options, using both patterns.
1065
858
  // ==========================================================================
1066
- const collectedOptions = [];
1067
- let questionEndIndex = -1;
1068
- let continuationLineCount = 0;
859
+ const state = {
860
+ collectedOptions: [],
861
+ // [Issue #3863] Per collected option (same order): the option row as drawn
862
+ // and the rows under it, for telling its title from its description.
863
+ optionRows: [],
864
+ // Continuation rows met since the option below was collected — the rows
865
+ // UNDER the option the reverse scan reaches next.
866
+ rowsBelow: [],
867
+ questionEndIndex: -1,
868
+ continuationLineCount: 0,
869
+ // [Issue #2486] Wrapped label rows beside a preview pane, held for the option
870
+ // ABOVE them, which the reverse scan has not reached yet.
871
+ paneWrap: [],
872
+ };
1069
873
  // [Issue #1708] Claude's bottom-anchored task panel is an overlay, not part of
1070
874
  // the prompt frame. Resolved as a block up front because the reverse scan meets
1071
875
  // its rows before its header and so cannot recognise them one at a time.
@@ -1073,17 +877,14 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
1073
877
  // [Issue #2486] The same reading carries an AskUserQuestion preview pane, whose
1074
878
  // rows the scan likewise meets before the top border that identifies it — and
1075
879
  // some of which it shares with the options column.
1076
- const chrome = findClaudeChrome(lines, scanStart, effectiveEnd);
1077
- // [Issue #2486] Wrapped label rows beside a preview pane, held for the option
1078
- // ABOVE them, which the reverse scan has not reached yet.
1079
- let paneWrap = [];
880
+ const chrome = (0, prompt_detect_multiple_choice_chrome_1.findClaudeChrome)(lines, scanStart, effectiveEnd);
1080
881
  for (let i = effectiveEnd - 1; i >= scanStart; i--) {
1081
882
  // [Issue #1708] Chrome rows are metadata. Same handling as the
1082
883
  // COLLAPSED_OUTPUT / SUMMARY guards below: skip without ending the scan, so
1083
884
  // the options rendered ABOVE the panel (or beside the pane) are still
1084
885
  // reachable.
1085
886
  if (chrome.rows.has(i)) {
1086
- continuationLineCount++;
887
+ state.continuationLineCount++;
1087
888
  continue;
1088
889
  }
1089
890
  // [Issue #2486] A row the preview pane shares with the options column is
@@ -1093,143 +894,180 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
1093
894
  // Original indentation preserved; only the pane is cut off.
1094
895
  const rawLine = paneCut === undefined ? lines[i] : lines[i].slice(0, paneCut).trimEnd();
1095
896
  const line = rawLine.trim();
1096
- // [Issue #2486] See PANE_WRAPPED_LABEL_PATTERN: the tail of the label above.
1097
- if (paneCut !== undefined && PANE_WRAPPED_LABEL_PATTERN.test(rawLine)) {
1098
- paneWrap.unshift(line);
1099
- continuationLineCount++;
897
+ if (isMetadataRow(state, rawLine, line, paneCut)) {
1100
898
  continue;
1101
899
  }
1102
- // Collapsed preview markers like "[… 12 lines] ctrl + a view all" are
1103
- // metadata, not selectable options. Skip them before option parsing to
1104
- // avoid misclassifying "12" as an option number.
1105
- if (COLLAPSED_OUTPUT_PATTERN.test(line)) {
1106
- continuationLineCount++;
900
+ if (tryCollectOption(state, rawLine, line, paneCut)) {
1107
901
  continue;
1108
902
  }
1109
- // [Issue #704] Claude v2.1.142 renders summary lines like "… +1 pending"
1110
- // BELOW the real options. Without this guard, NORMAL_OPTION_PATTERN below
1111
- // would match the summary line as `option N / label='pending'`, which
1112
- // would then poison isValidPrecedingOption() and cause every real option
1113
- // above (1./2./3.) to be rejected — resulting in collectedOptions.length<2
1114
- // and a spurious no_prompt verdict. Treat the summary line as metadata
1115
- // (same continuation handling as COLLAPSED_OUTPUT_PATTERN above) so the
1116
- // reverse scan continues into the actual option block.
1117
- //
1118
- // Defense-in-depth: works alongside the S2 effectiveEnd trim above. Either
1119
- // S1 or S2 alone is sufficient to fix Issue #704; keeping both protects
1120
- // against future Claude renderings where the footer line text changes
1121
- // (S1 still catches) or the summary line wording changes (S2 still
1122
- // catches via footer trim).
1123
- if (SUMMARY_LINE_PATTERN.test(line)) {
1124
- continuationLineCount++;
1125
- continue;
1126
- }
1127
- // Try DEFAULT_OPTION_PATTERN first (❯ indicator)
1128
- const defaultMatch = line.match(DEFAULT_OPTION_PATTERN);
1129
- if (defaultMatch) {
1130
- const number = parseInt(defaultMatch[1], 10);
1131
- if (number <= 20 && isValidPrecedingOption(number, collectedOptions)) {
1132
- // [Issue #2486] A label that wrapped beside a preview pane is whole again.
1133
- const label = [defaultMatch[2].trim(), ...paneWrap].join(' ');
1134
- paneWrap = [];
1135
- collectedOptions.unshift({ number, label, isDefault: true });
1136
- continuationLineCount = 0;
1137
- continue;
1138
- }
1139
- }
1140
- // Try NORMAL_OPTION_PATTERN (no ❯ indicator)
1141
- const normalMatch = line.match(NORMAL_OPTION_PATTERN);
1142
- if (normalMatch) {
1143
- const number = parseInt(normalMatch[1], 10);
1144
- // Skip unreasonably large option numbers (e.g., "[… 373 lines]" from Codex
1145
- // collapsed output matches as option 373). CLI prompts never exceed 20 options.
1146
- if (number > 20) {
1147
- // Treat as non-option line
1148
- }
1149
- else if (!isValidPrecedingOption(number, collectedOptions)) {
1150
- // Treat as non-option line (e.g., diff line numbers from Codex approval prompts)
1151
- }
1152
- else {
1153
- const label = [normalMatch[2].trim(), ...paneWrap].join(' ');
1154
- paneWrap = [];
1155
- collectedOptions.unshift({ number, label, isDefault: false });
1156
- continuationLineCount = 0;
1157
- continue;
1158
- }
1159
- }
1160
903
  // [Issue #287 Bug3] User input prompt barrier:
1161
904
  // When no options have been collected yet and the line starts with ❯ (U+276F)
1162
905
  // but did NOT match DEFAULT_OPTION_PATTERN above, this line is a Claude Code
1163
906
  // user input prompt (e.g., "❯ 1", "❯ /command") or idle prompt ("❯").
1164
907
  // Anything above this line in the scrollback is historical conversation text,
1165
908
  // not an active prompt. Stop scanning to prevent false positives.
1166
- if (collectedOptions.length === 0 && (line.startsWith('\u276F') || line.startsWith('\u25CF') || line.startsWith('\u203A'))) {
1167
- return noPromptResult(output);
909
+ if (state.collectedOptions.length === 0 && (line.startsWith('❯') || line.startsWith('●') || line.startsWith('›'))) {
910
+ return null;
911
+ }
912
+ // [Issue #3863] An indented row under the bottom option: its wrapped title
913
+ // or its description. Only remembered; the scan goes on as before.
914
+ if (state.collectedOptions.length === 0 && line && /^\s{3,}/.test(rawLine)) {
915
+ state.rowsBelow.unshift(rawLine);
1168
916
  }
1169
917
  // Non-option line handling
1170
- if (collectedOptions.length > 0 && line && !SEPARATOR_LINE_PATTERN.test(line)) {
1171
- // [Issue #460] For deeply indented lines (4+ leading spaces), check
1172
- // continuation BEFORE question keywords. These are option description
1173
- // lines (e.g., " Let Gemini CLI decide the best model...") that may
1174
- // contain keywords like "decide" or "select" but are NOT question lines.
1175
- // The 2-space " Select model" case (Issue #256) is excluded by the 4+ threshold.
1176
- const isDeepIndent = /^\s{4,}/.test(rawLine);
1177
- if (isDeepIndent && isContinuationLine(rawLine, line)) {
1178
- continuationLineCount++;
1179
- if (continuationLineCount > MAX_DEEP_INDENT_CONTINUATION_LINES) {
1180
- questionEndIndex = i;
1181
- break;
1182
- }
1183
- continue;
1184
- }
1185
- // [MF-001 / Issue #256] Check if line is a question-like line BEFORE
1186
- // continuation check. This preserves isContinuationLine()'s SRP by not
1187
- // mixing question detection into it. Without this pre-check, indented
1188
- // question lines (e.g., " Select model") could be misclassified as
1189
- // continuation lines by isContinuationLine()'s hasLeadingSpaces check.
1190
- //
1191
- // [SF-S4-003] Both this pre-check and SEC-001b upward scan use the same
1192
- // isQuestionLikeLine() function intentionally (DRY). If a question line is
1193
- // caught here, SEC-001b upward scan is not needed (questionEndIndex line
1194
- // itself passes isQuestionLikeLine()).
1195
- if (isQuestionLikeLine(line)) {
1196
- questionEndIndex = i;
918
+ if (state.collectedOptions.length > 0 && line && !prompt_detect_multiple_choice_approval_context_1.SEPARATOR_LINE_PATTERN.test(line)) {
919
+ if (scanNonOptionLine(state, rawLine, line, i) === 'break') {
1197
920
  break;
1198
921
  }
1199
- // Check if this is a continuation line (indented line between options,
1200
- // or path/filename fragments from terminal width wrapping - Issue #181)
1201
- if (isContinuationLine(rawLine, line)) {
1202
- continuationLineCount++;
1203
- const maxContinuationLines = /^\s{4,}/.test(rawLine)
1204
- ? MAX_DEEP_INDENT_CONTINUATION_LINES
1205
- : MAX_CONTINUATION_LINES;
1206
- // Issue #372: Codex TUI indents all output with 2 spaces, causing
1207
- // every line to match isContinuationLine(). Limit the scan distance
1208
- // to prevent traversing into body text where numbered lists would be
1209
- // collected as false options.
1210
- if (continuationLineCount > maxContinuationLines) {
1211
- questionEndIndex = i;
1212
- break;
1213
- }
1214
- // Skip continuation lines and continue scanning for more options
1215
- continue;
1216
- }
1217
- // Found a non-empty, non-separator line before options - likely the question
1218
- questionEndIndex = i;
1219
- break;
1220
922
  }
1221
923
  }
924
+ return {
925
+ collectedOptions: state.collectedOptions,
926
+ optionRows: state.optionRows,
927
+ questionEndIndex: state.questionEndIndex,
928
+ };
929
+ }
930
+ /** Rows the reverse scan skips as metadata without ending the scan. */
931
+ function isMetadataRow(state, rawLine, line, paneCut) {
932
+ // [Issue #2486] See PANE_WRAPPED_LABEL_PATTERN: the tail of the label above.
933
+ if (paneCut !== undefined && PANE_WRAPPED_LABEL_PATTERN.test(rawLine)) {
934
+ state.paneWrap.unshift(line);
935
+ state.continuationLineCount++;
936
+ return true;
937
+ }
938
+ // Collapsed preview markers like "[… 12 lines] ctrl + a view all" are
939
+ // metadata, not selectable options. Skip them before option parsing to
940
+ // avoid misclassifying "12" as an option number.
941
+ if (COLLAPSED_OUTPUT_PATTERN.test(line)) {
942
+ state.continuationLineCount++;
943
+ return true;
944
+ }
945
+ // [Issue #704] Claude v2.1.142 renders summary lines like "… +1 pending"
946
+ // BELOW the real options. Without this guard, NORMAL_OPTION_PATTERN below
947
+ // would match the summary line as `option N / label='pending'`, which
948
+ // would then poison isValidPrecedingOption() and cause every real option
949
+ // above (1./2./3.) to be rejected — resulting in collectedOptions.length<2
950
+ // and a spurious no_prompt verdict. Treat the summary line as metadata
951
+ // (same continuation handling as COLLAPSED_OUTPUT_PATTERN above) so the
952
+ // reverse scan continues into the actual option block.
953
+ //
954
+ // Defense-in-depth: works alongside the S2 effectiveEnd trim above. Either
955
+ // S1 or S2 alone is sufficient to fix Issue #704; keeping both protects
956
+ // against future Claude renderings where the footer line text changes
957
+ // (S1 still catches) or the summary line wording changes (S2 still
958
+ // catches via footer trim).
959
+ if (prompt_detect_multiple_choice_chrome_1.SUMMARY_LINE_PATTERN.test(line)) {
960
+ state.continuationLineCount++;
961
+ return true;
962
+ }
963
+ return false;
964
+ }
965
+ /** Collects the row as an option when either option pattern accepts it. */
966
+ function tryCollectOption(state, rawLine, line, paneCut) {
967
+ // Try DEFAULT_OPTION_PATTERN first (❯ indicator)
968
+ const defaultMatch = line.match(DEFAULT_OPTION_PATTERN);
969
+ if (defaultMatch) {
970
+ const number = parseInt(defaultMatch[1], 10);
971
+ if (number <= 20 && isValidPrecedingOption(number, state.collectedOptions)) {
972
+ // [Issue #2486] A label that wrapped beside a preview pane is whole again.
973
+ collectOption(state, rawLine, number, defaultMatch[2], true, paneCut);
974
+ return true;
975
+ }
976
+ }
977
+ // Try NORMAL_OPTION_PATTERN (no ❯ indicator)
978
+ const normalMatch = line.match(NORMAL_OPTION_PATTERN);
979
+ if (normalMatch) {
980
+ const number = parseInt(normalMatch[1], 10);
981
+ // Skip unreasonably large option numbers (e.g., "[… 373 lines]" from Codex
982
+ // collapsed output matches as option 373). CLI prompts never exceed 20 options.
983
+ if (number > 20) {
984
+ // Treat as non-option line
985
+ }
986
+ else if (!isValidPrecedingOption(number, state.collectedOptions)) {
987
+ // Treat as non-option line (e.g., diff line numbers from Codex approval prompts)
988
+ }
989
+ else {
990
+ collectOption(state, rawLine, number, normalMatch[2], false, paneCut);
991
+ return true;
992
+ }
993
+ }
994
+ return false;
995
+ }
996
+ function collectOption(state, rawLine, number, matchedLabel, isDefault, paneCut) {
997
+ const label = [matchedLabel.trim(), ...state.paneWrap].join(' ');
998
+ state.optionRows.unshift({ row: rawLine, besidePane: paneCut !== undefined || state.paneWrap.length > 0, rowsBelow: state.rowsBelow });
999
+ state.rowsBelow = [];
1000
+ state.paneWrap = [];
1001
+ state.collectedOptions.unshift({ number, label, isDefault });
1002
+ state.continuationLineCount = 0;
1003
+ }
1004
+ /** A non-empty, non-separator, non-option row above collected options. */
1005
+ function scanNonOptionLine(state, rawLine, line, i) {
1006
+ // [Issue #460] For deeply indented lines (4+ leading spaces), check
1007
+ // continuation BEFORE question keywords. These are option description
1008
+ // lines (e.g., " Let Gemini CLI decide the best model...") that may
1009
+ // contain keywords like "decide" or "select" but are NOT question lines.
1010
+ // The 2-space " Select model" case (Issue #256) is excluded by the 4+ threshold.
1011
+ const isDeepIndent = /^\s{4,}/.test(rawLine);
1012
+ if (isDeepIndent && isContinuationLine(rawLine, line)) {
1013
+ state.rowsBelow.unshift(rawLine);
1014
+ state.continuationLineCount++;
1015
+ if (state.continuationLineCount > MAX_DEEP_INDENT_CONTINUATION_LINES) {
1016
+ state.questionEndIndex = i;
1017
+ return 'break';
1018
+ }
1019
+ return 'continue';
1020
+ }
1021
+ // [MF-001 / Issue #256] Check if line is a question-like line BEFORE
1022
+ // continuation check. This preserves isContinuationLine()'s SRP by not
1023
+ // mixing question detection into it. Without this pre-check, indented
1024
+ // question lines (e.g., " Select model") could be misclassified as
1025
+ // continuation lines by isContinuationLine()'s hasLeadingSpaces check.
1026
+ //
1027
+ // [SF-S4-003] Both this pre-check and SEC-001b upward scan use the same
1028
+ // isQuestionLikeLine() function intentionally (DRY). If a question line is
1029
+ // caught here, SEC-001b upward scan is not needed (questionEndIndex line
1030
+ // itself passes isQuestionLikeLine()).
1031
+ if (isQuestionLikeLine(line)) {
1032
+ state.questionEndIndex = i;
1033
+ return 'break';
1034
+ }
1035
+ // Check if this is a continuation line (indented line between options,
1036
+ // or path/filename fragments from terminal width wrapping - Issue #181)
1037
+ if (isContinuationLine(rawLine, line)) {
1038
+ state.rowsBelow.unshift(rawLine);
1039
+ state.continuationLineCount++;
1040
+ const maxContinuationLines = /^\s{4,}/.test(rawLine)
1041
+ ? MAX_DEEP_INDENT_CONTINUATION_LINES
1042
+ : MAX_CONTINUATION_LINES;
1043
+ // Issue #372: Codex TUI indents all output with 2 spaces, causing
1044
+ // every line to match isContinuationLine(). Limit the scan distance
1045
+ // to prevent traversing into body text where numbered lists would be
1046
+ // collected as false options.
1047
+ if (state.continuationLineCount > maxContinuationLines) {
1048
+ state.questionEndIndex = i;
1049
+ return 'break';
1050
+ }
1051
+ // Skip continuation lines and continue scanning for more options
1052
+ return 'continue';
1053
+ }
1054
+ // Found a non-empty, non-separator line before options - likely the question
1055
+ state.questionEndIndex = i;
1056
+ return 'break';
1057
+ }
1058
+ /** Layers 3–5: whether the collected options make a prompt. */
1059
+ function passesOptionValidation(collectedOptions, requireDefault, hasConfirmationFooter, questionEndIndex, lines, scanStart) {
1222
1060
  // Layer 3: Consecutive number validation (defensive measure)
1223
1061
  const optionNumbers = collectedOptions.map(opt => opt.number);
1224
1062
  if (!isConsecutiveFromOne(optionNumbers)) {
1225
- return noPromptResult(output);
1063
+ return false;
1226
1064
  }
1227
1065
  // Layer 4: Must have at least 2 options. When requireDefault is true,
1228
1066
  // also require at least one option with ❯ indicator.
1229
1067
  const hasDefaultIndicator = collectedOptions.some(opt => opt.isDefault);
1230
1068
  const allowMissingDefaultIndicator = requireDefault && hasConfirmationFooter;
1231
1069
  if (collectedOptions.length < 2 || (requireDefault && !hasDefaultIndicator && !allowMissingDefaultIndicator)) {
1232
- return noPromptResult(output);
1070
+ return false;
1233
1071
  }
1234
1072
  // Layer 5 [SEC-001]: Enhanced question line validation for requireDefaultIndicator=false.
1235
1073
  // When requireDefault is false, apply stricter validation to prevent false positives
@@ -1244,46 +1082,71 @@ function detectMultipleChoicePrompt(output, options, truncateRawContentFn) {
1244
1082
  // (「実装方針を選んでください」) be detected. Indicator-less frames (#193 capture
1245
1083
  // artifacts) still go through SEC-001a/b unchanged.
1246
1084
  if (!requireDefault && !hasDefaultIndicator) {
1247
- // SEC-001a: No question line found (questionEndIndex === -1) - reject.
1248
- // Prevents generic question fallback from triggering Auto-Yes
1249
- // on plain numbered lists that happen to be consecutive from 1.
1250
- if (questionEndIndex === -1) {
1251
- return noPromptResult(output);
1252
- }
1253
- // SEC-001b: Question line exists but is not actually a question/selection request.
1254
- // Validates that the question line contains a question mark or a selection keyword
1255
- // with colon, distinguishing "Select an option:" from "Recommendations:".
1256
- //
1257
- // [Issue #256] Enhanced with upward scan via findQuestionLineInRange() (SF-003).
1258
- // When questionEndIndex line itself is not a question-like line, scan upward
1259
- // within QUESTION_SCAN_RANGE to find a question line above it. This handles:
1260
- // - Multi-line question wrapping where ? is on a line above questionEndIndex
1261
- // - Model selection prompts where "Select model" is above description lines
1262
- const questionLine = lines[questionEndIndex]?.trim() ?? '';
1263
- if (!isQuestionLikeLine(questionLine)) {
1264
- // Upward scan: look for a question-like line above questionEndIndex
1265
- if (!findQuestionLineInRange(lines, questionEndIndex, QUESTION_SCAN_RANGE, scanStart)) {
1266
- return noPromptResult(output);
1267
- }
1085
+ return passesQuestionLineValidation(questionEndIndex, lines, scanStart);
1086
+ }
1087
+ return true;
1088
+ }
1089
+ function passesQuestionLineValidation(questionEndIndex, lines, scanStart) {
1090
+ // SEC-001a: No question line found (questionEndIndex === -1) - reject.
1091
+ // Prevents generic question fallback from triggering Auto-Yes
1092
+ // on plain numbered lists that happen to be consecutive from 1.
1093
+ if (questionEndIndex === -1) {
1094
+ return false;
1095
+ }
1096
+ // SEC-001b: Question line exists but is not actually a question/selection request.
1097
+ // Validates that the question line contains a question mark or a selection keyword
1098
+ // with colon, distinguishing "Select an option:" from "Recommendations:".
1099
+ //
1100
+ // [Issue #256] Enhanced with upward scan via findQuestionLineInRange() (SF-003).
1101
+ // When questionEndIndex line itself is not a question-like line, scan upward
1102
+ // within QUESTION_SCAN_RANGE to find a question line above it. This handles:
1103
+ // - Multi-line question wrapping where ? is on a line above questionEndIndex
1104
+ // - Model selection prompts where "Select model" is above description lines
1105
+ const questionLine = lines[questionEndIndex]?.trim() ?? '';
1106
+ if (!isQuestionLikeLine(questionLine)) {
1107
+ // Upward scan: look for a question-like line above questionEndIndex
1108
+ if (!findQuestionLineInRange(lines, questionEndIndex, QUESTION_SCAN_RANGE, scanStart)) {
1109
+ return false;
1268
1110
  }
1269
1111
  }
1270
- // [Issue #2486] An AskUserQuestion picker's tab row is the dialog's top edge:
1271
- // the row is chrome and everything above it is transcript. Found once so
1272
- // `question` and `approvalTarget` start at the same row; 0 on a frame with no
1273
- // tab row, which leaves every other prompt exactly as it was. `instructionText`
1274
- // keeps its wide window — it is the human's view, and on a preview picker the
1275
- // pane it carries is what the question asks the human to look at.
1276
- const pickerTop = questionEndIndex < 0
1277
- ? 0
1278
- : (0, picker_chrome_1.findAskUserQuestionTabRow)(lines, questionEndIndex, exports.APPROVAL_TARGET_MAX_LOOKBACK) + 1;
1279
- const question = extractQuestionText(lines, questionEndIndex, pickerTop);
1280
- const instructionText = extractInstructionText(lines, questionEndIndex, effectiveEnd);
1281
- // Issue #1699: the same block, cut at the previous turn's boundary. Display
1282
- // keeps the wide window; deny patterns are judged against this one.
1283
- const approvalTarget = extractApprovalTarget(lines, questionEndIndex, effectiveEnd, pickerTop);
1284
- // Issue #616: Determine submitMode from confirmation footer (detected in single-pass above)
1285
- const submitMode = hasConfirmationFooter
1286
- ? (hasNumberFooter ? 'answer_only' : 'answer_then_enter')
1287
- : undefined;
1288
- return buildMultipleChoiceResult(question, collectedOptions, { instructionText, approvalTarget }, output, truncateRawContentFn, submitMode, hasAskUserQuestionFooter);
1112
+ return true;
1113
+ }
1114
+ /**
1115
+ * [Issue #3863] What an AskUserQuestion option's title says about being
1116
+ * recommended (`option-recommendation`).
1117
+ *
1118
+ * The picker draws a title row and its description indented under it; a title
1119
+ * too long for the pane wraps to the same indent, so the rows alone do not
1120
+ * say which they are. Read:
1121
+ *
1122
+ * - no row under it: the label is the title;
1123
+ * - beside a preview pane (#2486), where the column draws no descriptions:
1124
+ * the label — its wrapped rows already joined — is the title, as long as no
1125
+ * other row is under it;
1126
+ * - the row under it could not be the title's wrap (its first word would have
1127
+ * fit on the option row, `cannotBeWrapOf`): the label is the title;
1128
+ * - the row under it may be the wrap and holds nothing but a marker: the title
1129
+ * wrapped right before its marker — and ends there only if nothing after
1130
+ * the marker continues it: no further row, or a row that could not be the
1131
+ * marker row's wrap (its first word would have fit after the marker). A row
1132
+ * that may still be the title wrapping on (a word too long to fit after the
1133
+ * marker) leaves the title's end, and so the marker's place, unknown;
1134
+ * - otherwise `unknown`.
1135
+ */
1136
+ function readPickerOptionRecommendation(label, reading, paneColumns) {
1137
+ if (reading === undefined)
1138
+ return 'unknown';
1139
+ const [next] = reading.rowsBelow;
1140
+ if (next === undefined)
1141
+ return (0, option_recommendation_1.isRecommendedTitle)(label);
1142
+ if (reading.besidePane)
1143
+ return 'unknown';
1144
+ if ((0, option_recommendation_1.cannotBeWrapOf)(reading.row, next, paneColumns))
1145
+ return (0, option_recommendation_1.isRecommendedTitle)(label);
1146
+ if (!(0, option_recommendation_1.isRecommendationMarker)(next))
1147
+ return 'unknown';
1148
+ const after = reading.rowsBelow[1];
1149
+ if (after === undefined || (0, option_recommendation_1.cannotBeWrapOf)(next, after, paneColumns))
1150
+ return true;
1151
+ return 'unknown';
1289
1152
  }