commandmate 0.48.0 → 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 (269) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +164 -164
  3. package/.next/app-path-routes-manifest.json +30 -30
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +3 -3
  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_client-reference-manifest.js +1 -1
  12. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/capabilities/route.js +1 -1
  17. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  18. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/push/escalation/route.js +1 -1
  31. package/.next/server/app/api/push/escalation/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/push/vapid/route.js +1 -1
  34. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  58. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  63. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  70. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/worktrees/[id]/direct-input/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/worktrees/[id]/instances/notes/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/prompt-response/strict/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  141. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  151. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  153. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  155. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  156. package/.next/server/app/page_client-reference-manifest.js +1 -1
  157. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  159. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  160. package/.next/server/app/sessions/page.js +1 -1
  161. package/.next/server/app/sessions/page.js.nft.json +1 -1
  162. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  163. package/.next/server/app/skills/[skillId]/page.js +1 -1
  164. package/.next/server/app/skills/[skillId]/page.js.nft.json +1 -1
  165. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  166. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  167. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  168. package/.next/server/app/worktrees/[id]/files/[...path]/page.js +2 -2
  169. package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
  170. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  171. package/.next/server/app/worktrees/[id]/page.js +3 -3
  172. package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
  173. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  174. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  175. package/.next/server/app-paths-manifest.json +30 -30
  176. package/.next/server/chunks/1601.js +2 -2
  177. package/.next/server/chunks/1656.js +2 -2
  178. package/.next/server/chunks/1945.js +3 -3
  179. package/.next/server/chunks/2069.js +1 -0
  180. package/.next/server/chunks/2422.js +1 -1
  181. package/.next/server/chunks/2534.js +1 -1
  182. package/.next/server/chunks/3241.js +1 -1
  183. package/.next/server/chunks/4243.js +1 -1
  184. package/.next/server/chunks/4248.js +1 -1
  185. package/.next/server/chunks/4280.js +1 -1
  186. package/.next/server/chunks/6119.js +1 -1
  187. package/.next/server/chunks/6130.js +1 -1
  188. package/.next/server/chunks/6401.js +1 -1
  189. package/.next/server/chunks/6486.js +1 -1
  190. package/.next/server/chunks/720.js +2 -2
  191. package/.next/server/chunks/7604.js +2 -2
  192. package/.next/server/chunks/7905.js +1 -1
  193. package/.next/server/chunks/8581.js +3 -3
  194. package/.next/server/chunks/8728.js +3 -3
  195. package/.next/server/chunks/8791.js +1 -1
  196. package/.next/server/chunks/9390.js +1 -1
  197. package/.next/server/chunks/9627.js +3 -3
  198. package/.next/server/middleware-manifest.json +5 -5
  199. package/.next/server/pages/500.html +1 -1
  200. package/.next/server/server-reference-manifest.json +1 -1
  201. package/.next/static/chunks/1128-914c559913546bbc.js +1 -0
  202. package/.next/static/chunks/{2407-26139a8a2f1adbe8.js → 2407-ab92ae9837c39dce.js} +1 -1
  203. package/.next/static/chunks/{2619-a305ad4810f1393a.js → 2619-04bc32f026a0d946.js} +1 -1
  204. package/.next/static/chunks/{2863-f0121af1cbbedb3b.js → 2863-7173dc305dffc4f6.js} +1 -1
  205. package/.next/static/chunks/6288-6fadbba6e84393b2.js +1 -0
  206. package/.next/static/chunks/7665-e9ef6e0a85b9fa49.js +1 -0
  207. package/.next/static/chunks/{8165-c68b3dc844210e6a.js → 8165-263172c17ae8e8c3.js} +1 -1
  208. package/.next/static/chunks/9079-987939d231704218.js +1 -0
  209. package/.next/static/chunks/app/{layout-46b4cbf07520d9b6.js → layout-aa8e66164a4367af.js} +1 -1
  210. package/.next/static/chunks/app/sessions/{page-fb241bed579c67aa.js → page-cfd645305e25fd33.js} +1 -1
  211. package/.next/static/chunks/app/skills/[skillId]/{page-52ba58712faf3378.js → page-cb6e74fd9a6a571d.js} +1 -1
  212. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-704086656fb45699.js +1 -0
  213. package/.next/static/chunks/app/worktrees/[id]/{page-cbc7749a33450cc5.js → page-a8d7886703ffc7b4.js} +1 -1
  214. package/.next/static/chunks/app/worktrees/[id]/terminal/{page-c601f3b3ece6557e.js → page-373ea37b16d930c7.js} +1 -1
  215. package/.next/static/css/{743f79ff19ab91e9.css → db8959555c3d9c96.css} +1 -1
  216. package/.next/trace +13 -13
  217. package/dist/cli/commands/auto-yes.d.ts.map +1 -1
  218. package/dist/cli/commands/auto-yes.js +3 -5
  219. package/dist/cli/commands/capture.d.ts.map +1 -1
  220. package/dist/cli/commands/capture.js +5 -0
  221. package/dist/cli/commands/send.d.ts.map +1 -1
  222. package/dist/cli/commands/send.js +4 -6
  223. package/dist/cli/commands/verify.d.ts.map +1 -1
  224. package/dist/cli/commands/verify.js +8 -1
  225. package/dist/cli/config/duration-constants.d.ts +16 -13
  226. package/dist/cli/config/duration-constants.d.ts.map +1 -1
  227. package/dist/cli/config/duration-constants.js +21 -21
  228. package/dist/cli/docs/agent-operations.d.ts +1 -1
  229. package/dist/cli/docs/agent-operations.d.ts.map +1 -1
  230. package/dist/cli/docs/agent-operations.js +2 -1
  231. package/dist/cli/types/api-responses.d.ts +29 -1
  232. package/dist/cli/types/api-responses.d.ts.map +1 -1
  233. package/dist/cli/utils/verify-runner.d.ts +24 -1
  234. package/dist/cli/utils/verify-runner.d.ts.map +1 -1
  235. package/dist/cli/utils/verify-runner.js +54 -1
  236. package/dist/config/auto-yes-duration.d.ts +56 -0
  237. package/dist/config/auto-yes-duration.d.ts.map +1 -0
  238. package/dist/config/auto-yes-duration.js +76 -0
  239. package/dist/server/src/config/auto-yes-config.js +14 -19
  240. package/dist/server/src/config/auto-yes-duration.js +76 -0
  241. package/dist/server/src/lib/auto-yes-state.js +9 -2
  242. package/dist/server/src/lib/cli-tools/submit-verified-sender.js +137 -14
  243. package/dist/server/src/lib/detection/composer-text.js +215 -11
  244. package/dist/server/src/lib/detection/prompt-detect-multiple-choice-chrome.js +193 -0
  245. package/dist/server/src/lib/detection/prompt-detect-multiple-choice.js +8 -191
  246. package/dist/server/src/lib/detection/upstream-faults.js +19 -2
  247. package/dist/server/src/lib/hooks/structured-decision-response.js +1 -1
  248. package/dist/server/src/lib/polling/auto-yes-manual-answer.js +4 -2
  249. package/dist/server/src/lib/polling/response-checker.js +12 -15
  250. package/dist/server/src/lib/session/composer-clear.js +56 -4
  251. package/dist/server/src/lib/session/current-output-builder.js +2 -1
  252. package/dist/server/src/lib/session/send-attempt-state.js +2 -1
  253. package/dist/server/src/lib/session/waiting-episode-state-ops.js +152 -0
  254. package/dist/server/src/lib/session/waiting-episode-state-store.js +202 -0
  255. package/dist/server/src/lib/session/waiting-episode-state-types.js +6 -0
  256. package/dist/server/src/lib/session/waiting-episode-state.js +26 -339
  257. package/dist/server/src/lib/tmux/tmux.js +25 -0
  258. package/dist/server/src/lib/verification/gate-runner.js +11 -4
  259. package/docs/en/user-guide/webapp-guide.md +15 -10
  260. package/package.json +1 -1
  261. package/release-notes/0.49.0.json +45 -0
  262. package/.next/server/chunks/6722.js +0 -1
  263. package/.next/static/chunks/2099-c3e0ab70a1b95dd3.js +0 -1
  264. package/.next/static/chunks/5371-a549a0d2c2044d41.js +0 -1
  265. package/.next/static/chunks/6288-09feba367cc2c65e.js +0 -1
  266. package/.next/static/chunks/9079-406b84d3cc891a87.js +0 -1
  267. package/.next/static/chunks/app/worktrees/[id]/files/[...path]/page-9cf1ecc59e36a004.js +0 -1
  268. /package/.next/static/{PTlAswXLq8PAe2vUJ8dVP → 3_JtAZaKMJEDFIIfkHv4n}/_buildManifest.js +0 -0
  269. /package/.next/static/{PTlAswXLq8PAe2vUJ8dVP → 3_JtAZaKMJEDFIIfkHv4n}/_ssgManifest.js +0 -0
@@ -456,6 +456,15 @@ function codexComposerRegion(output) {
456
456
  const box = (0, composer_text_1.findCodexInputBox)(raw);
457
457
  if (box === null)
458
458
  return null;
459
+ return codexRegionFrom(raw, box.firstRow, box.lastRow);
460
+ }
461
+ /**
462
+ * {@link codexComposerRegion} from a known `›` row (Issue #3906): the rows from
463
+ * `firstRow` down to the frame's last block (the footer), ANSI and glyph
464
+ * stripped. `lastRow` is the end of the `›` row's own block, so a composer that
465
+ * is itself the frame's last block is read to the end.
466
+ */
467
+ function codexRegionFrom(raw, firstRow, lastRow) {
459
468
  const isBlank = (row) => (0, cli_patterns_1.stripAnsi)(row).trim() === '';
460
469
  let end = raw.length - 1;
461
470
  while (end >= 0 && isBlank(raw[end]))
@@ -463,18 +472,57 @@ function codexComposerRegion(output) {
463
472
  let lastBlockStart = end;
464
473
  while (lastBlockStart > 0 && !isBlank(raw[lastBlockStart - 1]))
465
474
  lastBlockStart--;
466
- const stop = lastBlockStart > box.lastRow ? lastBlockStart - 1 : end;
467
- const rows = raw.slice(box.firstRow, stop + 1).map((row) => (0, cli_patterns_1.stripAnsi)(row));
475
+ const stop = lastBlockStart > lastRow ? lastBlockStart - 1 : end;
476
+ const rows = raw.slice(firstRow, stop + 1).map((row) => (0, cli_patterns_1.stripAnsi)(row));
468
477
  rows[0] = rows[0].replace(/^\s*\u203A ?/, '');
469
478
  return rows.join('\n');
470
479
  }
471
- function readSubmitComposer(output, cliToolId, reader, lines, windowLines) {
480
+ /** End (inclusive) of the blank-row-delimited block that starts at `row`. */
481
+ function blockEndFrom(raw, row) {
482
+ let end = row;
483
+ while (end + 1 < raw.length && (0, cli_patterns_1.stripAnsi)(raw[end + 1]).trim() !== '')
484
+ end++;
485
+ return end;
486
+ }
487
+ /** The bottom-most row of `windowLines` the marker reader takes as the input line, as a frame index. */
488
+ function markerRowIndex(windowLines, windowStart) {
489
+ for (let i = windowLines.length - 1; i >= 0; i--) {
490
+ if (INPUT_LINE_MARKER.test(windowLines[i]))
491
+ return windowStart + i;
492
+ }
493
+ return -1;
494
+ }
495
+ function readSubmitComposer(output, cliToolId, reader, lines, windowLines, message) {
472
496
  if (cliToolId === 'codex' && reader === 'input-line-marker') {
497
+ const raw = output.split('\n');
498
+ const windowStart = lines.length - windowLines.length;
473
499
  const read = (0, composer_text_1.extractComposerText)(output, cliToolId);
474
500
  if (read.state === 'content') {
501
+ const box = (0, composer_text_1.findCodexInputBox)(raw);
502
+ // Issue #3906: a dialog option drawn below the composer the extractor
503
+ // located means the dialog is what the pane shows now, on every branch
504
+ // below — that composer is one left from before, not this send's.
505
+ if (box !== null && (0, composer_text_1.codexDialogBelow)(raw, box.firstRow))
506
+ return { kind: 'absent' };
507
+ const outsideWindow = box !== null && box.firstRow < windowStart;
475
508
  const region = codexComposerRegion(output) ?? read.text;
476
509
  if (read.text.includes('\n')) {
477
- return { kind: 'text', text: firstNonBlankLine(read.text), rows: read.text.split('\n'), region };
510
+ // Issue #3906: a composer of many rows with no blank line (20 lines) has
511
+ // its `›` row above the window too, and is marked so like the others.
512
+ return {
513
+ kind: 'text',
514
+ text: firstNonBlankLine(read.text),
515
+ rows: read.text.split('\n'),
516
+ region,
517
+ ...(outsideWindow ? { outsideWindow } : {}),
518
+ };
519
+ }
520
+ // Issue #3906 (1): the `›` row sits above the twelve-row window (a one-line
521
+ // paragraph, a blank line, then a tall one). The marker reader would see no
522
+ // input line there — `unseen` before Enter, `submitted` after it — so the
523
+ // composer `extractComposerText` located is read instead, whole.
524
+ if (outsideWindow && (0, composer_text_1.findCodexLiveComposerRow)(raw) === box.firstRow) {
525
+ return { kind: 'text', text: read.text.trim(), rows: [raw[box.firstRow]], region, outsideWindow: true };
478
526
  }
479
527
  // Issue #3880 (N1): a one-row excerpt is read as before for its verdict
480
528
  // after Enter, but still carries the whole region — `head` with the rest of
@@ -483,6 +531,45 @@ function readSubmitComposer(output, cliToolId, reader, lines, windowLines) {
483
531
  const single = readComposer(reader, lines, windowLines);
484
532
  return single.kind === 'text' ? { ...single, region } : single;
485
533
  }
534
+ if (read.state === 'no_composer') {
535
+ // Issue #3906 (2): more paragraphs than the four blocks the extractor
536
+ // searches. Whatever the marker reader takes must carry the whole region,
537
+ // so the composer's first line alone never passes for the body. The row is
538
+ // the live composer only when no dialog option is drawn below it
539
+ // ({@link findCodexLiveComposerRow}, the order `findCodexBottomGlyphRow`
540
+ // reads a frame in): a composer row left above an approval dialog is
541
+ // never this send's composer, however its text reads.
542
+ const composerRow = (0, composer_text_1.findCodexLiveComposerRow)(raw);
543
+ if (composerRow >= 0) {
544
+ const region = codexRegionFrom(raw, composerRow, blockEndFrom(raw, composerRow));
545
+ const text = stripInputMarker((0, cli_patterns_1.stripAnsi)(raw[composerRow]));
546
+ if (composerRow >= windowStart) {
547
+ const single = readComposer(reader, lines, windowLines);
548
+ if (single.kind === 'text' && markerRowIndex(windowLines, windowStart) === composerRow) {
549
+ return { ...single, region };
550
+ }
551
+ return single;
552
+ }
553
+ // Above the window as well, with no dialog below it.
554
+ if (message !== undefined && text.length > 0 && inputMatchesBody(text, message)) {
555
+ return { kind: 'text', text, rows: [raw[composerRow]], region, outsideWindow: true };
556
+ }
557
+ // Not the body's first line. A composer whose own block is empty or
558
+ // placeholder-only is empty; real text is read too — a `/…` command
559
+ // there is `replaced` as it is inside the window, and anything else is
560
+ // not proof that the message left ({@link classifySubmit}: `indeterminate`).
561
+ const held = (0, composer_text_1.readCodexComposerFrom)(raw.slice(0, blockEndFrom(raw, composerRow) + 1), composerRow);
562
+ if (held.state !== 'content')
563
+ return { kind: 'empty' };
564
+ return {
565
+ kind: 'text',
566
+ text: firstNonBlankLine(held.text),
567
+ rows: [raw[composerRow]],
568
+ region,
569
+ outsideWindow: true,
570
+ };
571
+ }
572
+ }
486
573
  }
487
574
  return readComposer(reader, lines, windowLines);
488
575
  }
@@ -497,7 +584,9 @@ function readSubmitComposer(output, cliToolId, reader, lines, windowLines) {
497
584
  * E. A DIFFERENT slash command is in the composer -> replaced (TUI popup
498
585
  * autocompleted the command; clear the line and throw, never resend Enter).
499
586
  * F. Any other non-empty text (idle placeholder/hint) -> submitted (unchanged
500
- * pre-#1501 permissive default, so normal sends never spuriously fail).
587
+ * pre-#1501 permissive default, so normal sends never spuriously fail) —
588
+ * except in a codex composer above the read window -> indeterminate
589
+ * (Issue #3906).
501
590
  *
502
591
  * C–F are scoped to the composer only, so the user-message echo that a TUI
503
592
  * prints into its history above the prompt never causes a false verdict.
@@ -530,7 +619,7 @@ function classifySubmit(output, cliToolId, message, composerSpec = (0, composer_
530
619
  if ((0, cli_patterns_1.detectThinking)(cliToolId, windowStr)) {
531
620
  return 'submitted';
532
621
  }
533
- const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, windowLines);
622
+ const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, windowLines, message);
534
623
  // B. No composer visible (scrolled off / dialog / still starting), or the
535
624
  // composer is empty => the message left the box => submitted.
536
625
  if (composer.kind !== 'text') {
@@ -551,8 +640,11 @@ function classifySubmit(output, cliToolId, message, composerSpec = (0, composer_
551
640
  return 'replaced';
552
641
  }
553
642
  // F. Non-empty, non-command steady-state text (idle placeholder / hint) =>
554
- // submitted, preserving the pre-#1501 permissive default.
555
- return 'submitted';
643
+ // submitted, preserving the pre-#1501 permissive default. Not for a codex
644
+ // composer above the window (Issue #3906): before #3906 that frame was
645
+ // read as no composer at all; text there that is not the body says
646
+ // neither that the message left nor that Enter would send it.
647
+ return composer.outsideWindow ? 'indeterminate' : 'submitted';
556
648
  }
557
649
  /**
558
650
  * Backward-compatible boolean view of {@link classifySubmit}: submitted vs not.
@@ -590,10 +682,15 @@ function isSubmitted(output, cliToolId, message, composerSpec = (0, composer_spe
590
682
  * is checked before placeholders, so a body that itself quotes
591
683
  * `[Pasted Content 600 chars]` is its own text, not codex's placeholder; a real
592
684
  * placeholder is only ever the composer's first row.
685
+ *
686
+ * Issue #3906: a codex composer outside the twelve-row window, or split into
687
+ * more blocks than `extractComposerText` searches, is still read with its whole
688
+ * region ({@link readSubmitComposer}), so neither `unseen` for a body that is
689
+ * there nor `landed` on its first line alone.
593
690
  */
594
691
  function classifyPasteLanded(output, cliToolId, message, composerSpec = (0, composer_spec_1.resolveComposerSpec)(cliToolId)) {
595
692
  const lines = withoutTrailingBlankRows((0, cli_patterns_1.stripAnsi)(output).split('\n'));
596
- const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES));
693
+ const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES), message);
597
694
  if (composer.kind !== 'text')
598
695
  return 'unseen';
599
696
  if (composer.region !== undefined) {
@@ -674,9 +771,9 @@ async function pasteBody(sessionName, message) {
674
771
  }
675
772
  }
676
773
  /** The composer's first row, for an error message a person has to act on. */
677
- function describeComposer(output, cliToolId, composerSpec) {
774
+ function describeComposer(output, cliToolId, composerSpec, message) {
678
775
  const lines = withoutTrailingBlankRows((0, cli_patterns_1.stripAnsi)(output).split('\n'));
679
- const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES));
776
+ const composer = readSubmitComposer(output, cliToolId, composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES), message);
680
777
  if (composer.kind === 'absent')
681
778
  return 'no composer on screen';
682
779
  if (composer.kind === 'empty')
@@ -749,7 +846,7 @@ async function confirmPasteLanded(params) {
749
846
  }
750
847
  const bytes = Buffer.byteLength(message, 'utf8');
751
848
  const lineCount = message.split('\n').length;
752
- const shown = describeComposer(output, cliToolId, composer);
849
+ const shown = describeComposer(output, cliToolId, composer, message);
753
850
  await withdrawUnsentBody(sessionName, cliToolId, composer);
754
851
  (0, tmux_capture_cache_1.invalidateCache)(sessionName);
755
852
  logger.error('paste-not-landed', { sessionName, cliToolId, state, bytes, lines: lineCount, shown });
@@ -781,6 +878,12 @@ function visibleTypedBody(message) {
781
878
  const blank = lines.findIndex((line, index) => index > first && line.trim() === '');
782
879
  return lines.slice(0, blank < 0 ? lines.length : blank).join('\n');
783
880
  }
881
+ /** Whether the whole codex composer, read past the bounded readers (Issue #3906), is exactly `message`. */
882
+ function codexComposerShowsWholeBody(output, message, composerSpec) {
883
+ const lines = withoutTrailingBlankRows((0, cli_patterns_1.stripAnsi)(output).split('\n'));
884
+ const composer = readSubmitComposer(output, 'codex', composerSpec.reader, lines, lines.slice(-VERIFY_WINDOW_LINES), message);
885
+ return composer.kind === 'text' && composer.region !== undefined && compactText(composer.region) === compactText(message);
886
+ }
784
887
  /**
785
888
  * Wait until the composer shows the typed body; only then may Enter be pressed
786
889
  * (Issue #3366).
@@ -794,6 +897,11 @@ function visibleTypedBody(message) {
794
897
  * a prefix is not enough, because codex can draw the first keystrokes before it
795
898
  * decides the rest are a burst.
796
899
  *
900
+ * On codex the verdict is the whole composer against the whole body
901
+ * ({@link codexComposerShowsWholeBody}, Issue #3906): the extractor's text is
902
+ * the body's first paragraph at most, so on its own it would let Enter go on a
903
+ * body whose later paragraphs had not been drawn.
904
+ *
797
905
  * A body that never appears is taken back out and the send THROWS without
798
906
  * Enter — nothing was submitted, so the caller must not print `Message sent.`.
799
907
  *
@@ -808,12 +916,21 @@ async function confirmTypedBodyLanded(params) {
808
916
  for (let attempt = 0; attempt < Math.max(1, attempts); attempt++) {
809
917
  await new Promise((resolve) => setTimeout(resolve, TYPED_BODY_POLL_MS));
810
918
  output = await (0, tmux_1.capturePane)(sessionName, { startLine: -composer.verifyCaptureLines });
919
+ // Issue #3906: on codex the whole composer must show the whole body. The
920
+ // extractor's excerpt stops at the first blank row, so its match is only
921
+ // the first paragraph — a five-paragraph body with one paragraph drawn
922
+ // would pass on it — and it cannot reach a body of more than four blocks.
923
+ if (cliToolId === 'codex') {
924
+ if (codexComposerShowsWholeBody(output, message, composer))
925
+ return;
926
+ continue;
927
+ }
811
928
  const read = (0, composer_text_1.extractComposerText)(output, cliToolId);
812
929
  if (read.state === 'content' && compactText(read.text) === expected)
813
930
  return;
814
931
  }
815
932
  const bytes = Buffer.byteLength(message, 'utf8');
816
- const shown = describeComposer(output, cliToolId, composer);
933
+ const shown = describeComposer(output, cliToolId, composer, message);
817
934
  await withdrawUnsentBody(sessionName, cliToolId, composer);
818
935
  (0, tmux_capture_cache_1.invalidateCache)(sessionName);
819
936
  logger.error('typed-body-not-landed', { sessionName, cliToolId, bytes, attempts, shown });
@@ -1003,9 +1120,15 @@ async function sendMessageWithSubmitVerification(params) {
1003
1120
  logger.error('submit-replaced-by-tui-completion', { sessionName, cliToolId, attempt });
1004
1121
  throw new Error(`Message was replaced by a TUI autocompletion for session ${sessionName}; the input no longer matches the sent text. Cleared the input line without submitting to avoid executing a different command.`);
1005
1122
  }
1006
- // state === 'pending' after the final resend's read-back: give up below.
1123
+ // state === 'pending' / 'indeterminate' after the final read-back: give up below.
1007
1124
  if (attempt === attempts)
1008
1125
  break;
1126
+ // state === 'indeterminate' (Issue #3906) — no Enter: the composer does not
1127
+ // show the body, so another Enter could submit whatever it does show.
1128
+ if (state === 'indeterminate') {
1129
+ logger.warn('submit-not-confirmed:composer-unreadable', { sessionName, cliToolId, attempt });
1130
+ continue;
1131
+ }
1009
1132
  // state === 'pending' — still typed-but-unsent, resend a single Enter and re-check.
1010
1133
  logger.warn('submit-not-confirmed:resending-enter', {
1011
1134
  sessionName,
@@ -93,12 +93,25 @@
93
93
  * server does. That is deliberate — `useTerminalPanePolling` derives the
94
94
  * composer text client-side from the frame it already has, which keeps the
95
95
  * WebSocket push path and the HTTP poll path from disagreeing.
96
+ *
97
+ * That is also why the "a dialog below the composer means no composer" rule
98
+ * (Issue #3927, {@link codexDialogBelow}) lives here rather than next to the
99
+ * clear in `session/composer-clear.ts`: the clear, the published
100
+ * `composerText` and the browser's own reading all go through
101
+ * {@link extractLiveComposerText}, and `tools/codex/cli-patterns.ts`, whose
102
+ * `readCodexGlyphRowKind` the rule was first written on, imports the logger.
96
103
  */
97
104
  Object.defineProperty(exports, "__esModule", { value: true });
98
105
  exports.SUPPORTED_COMPOSER_TOOLS = exports.COMPOSER_TEXT_MAX_CHARS = void 0;
99
106
  exports.findClaudeInputBox = findClaudeInputBox;
107
+ exports.isCodexComposerRow = isCodexComposerRow;
108
+ exports.isCodexBoldGutterRow = isCodexBoldGutterRow;
109
+ exports.findCodexLiveComposerRow = findCodexLiveComposerRow;
110
+ exports.codexDialogBelow = codexDialogBelow;
100
111
  exports.findCodexInputBox = findCodexInputBox;
101
112
  exports.extractComposerText = extractComposerText;
113
+ exports.extractLiveComposerText = extractLiveComposerText;
114
+ exports.readCodexComposerFrom = readCodexComposerFrom;
102
115
  const ansi_1 = require("./ansi");
103
116
  /**
104
117
  * Upper bound on the published composer text.
@@ -208,6 +221,10 @@ function isBlankRow(line) {
208
221
  * A bare `\u203A` with nothing after it is accepted: `capture-pane` trims trailing
209
222
  * whitespace, so that is what a composer with neither text nor placeholder looks
210
223
  * like, and calling it `no_composer` would be a worse answer than `empty`.
224
+ *
225
+ * Exported for the send path (Issue #3906): when {@link findCodexInputBox}'s
226
+ * block bound cannot reach a composer split by many blank rows, the sender
227
+ * still has to tell the real `\u203A` row from these look-alikes.
211
228
  */
212
229
  function isCodexComposerRow(line) {
213
230
  if (line === undefined)
@@ -226,6 +243,120 @@ function isCodexComposerRow(line) {
226
243
  const firstText = chars.slice(2).find(c => c.ch !== ' ');
227
244
  return firstText === undefined || !firstText.bold;
228
245
  }
246
+ /**
247
+ * Whether a codex `\u203A` row has its gutter space inside the glyph's bold span
248
+ * (Issue #3906).
249
+ *
250
+ * codex also draws the transcript echo of a sent message with a bold glyph that
251
+ * is NOT dim — `ESC[1m\u203A ESC[0m` + text (measured on 0.153.2 and 0.160.0:
252
+ * `tests/fixtures/codex-live-2310/turn-submitted-no-status.txt:36`,
253
+ * `codex-multiline-composer-3205/tc104-raw-pane.txt:20`,
254
+ * `codex-dialogs-0157/approval.txt:24`) — which {@link isCodexComposerRow}
255
+ * accepts. Every measured composer resets right after its glyph
256
+ * (`ESC[1m\u203AESC[0m `), and every echo carries the space in the glyph's span,
257
+ * dim or not. {@link findCodexInputBox} does not need this — it only looks at
258
+ * the frame's last blocks, where the live composer is — but a search that climbs
259
+ * past them into the transcript does.
260
+ */
261
+ function isCodexBoldGutterRow(line) {
262
+ if (line === undefined)
263
+ return false;
264
+ const chars = scanAttributedChars(line);
265
+ return chars.length > 1 && chars[0].ch === CODEX_PROMPT_GLYPH && chars[1].ch === ' ' && chars[1].bold;
266
+ }
267
+ /**
268
+ * Classify one raw codex row whose first rendered glyph is `\u203A` (Issue #3927).
269
+ *
270
+ * The same reading as `readCodexGlyphRowKind` in `tools/codex/cli-patterns.ts`,
271
+ * re-stated on this module's SGR scan because that module imports the logger
272
+ * and this one is read by the browser bundle. In order: no attributes at all →
273
+ * null (a stripped capture, where every `\u203A` use reads the same); a dim
274
+ * glyph → the transcript echo; a bold label → a dialog option; a dim label →
275
+ * the composer's placeholder; a coloured glyph whose label is drawn in the
276
+ * same colour → a dialog option (from 0.155.1 the composer's glyph is
277
+ * coloured too, but it resets before its label); anything else → the composer.
278
+ * `tests/unit/lib/detection/composer-text-dialog-below-3927.test.ts` holds the
279
+ * two readings equal over the codex fixtures.
280
+ *
281
+ * @param rawRow - One row of the capture, ANSI intact
282
+ */
283
+ function readCodexGlyphRow(rawRow) {
284
+ if (!rawRow.includes('\x1b'))
285
+ return null;
286
+ const chars = scanAttributedChars(rawRow);
287
+ const glyphIndex = chars.findIndex(c => c.ch.trim() !== '');
288
+ if (glyphIndex < 0 || chars[glyphIndex].ch !== CODEX_PROMPT_GLYPH)
289
+ return null;
290
+ const glyph = chars[glyphIndex];
291
+ if (glyph.dim)
292
+ return 'transcript-echo';
293
+ const label = chars.slice(glyphIndex + 1).find(c => c.ch.trim() !== '');
294
+ if (label?.bold)
295
+ return 'option';
296
+ if (label?.dim)
297
+ return 'composer';
298
+ if (glyph.fg !== null && label?.fg === glyph.fg)
299
+ return 'option';
300
+ return 'composer';
301
+ }
302
+ /**
303
+ * A composer row by every reading: {@link isCodexComposerRow}, and not the
304
+ * bold-not-dim echo ({@link isCodexBoldGutterRow}). The caller has already had
305
+ * {@link readCodexGlyphRow} call it `'composer'`, which also requires ANSI
306
+ * attributes: an attribute-free `\u203A` row (a stripped capture, where a dialog
307
+ * option reads the same as a composer) is never taken (Issue #3906).
308
+ */
309
+ function isLiveComposerRow(row) {
310
+ return isCodexComposerRow(row) && !isCodexBoldGutterRow(row);
311
+ }
312
+ /**
313
+ * The live codex composer's `\u203A` row, wherever it is in the frame (Issue #3906).
314
+ *
315
+ * Read from the bottom, in the order `findCodexBottomGlyphRow` reads a frame:
316
+ * a dialog's highlighted option reached before any composer row means the
317
+ * dialog is what the pane shows now, and a composer row above it is one left
318
+ * there from before (codex-live-1671) — so the answer is -1, not that row. The
319
+ * transcript echo of a sent message is skipped, dim ({@link isCodexComposerRow}
320
+ * rejects it) or not ({@link isCodexBoldGutterRow}).
321
+ *
322
+ * Moved here from `session/composer-clear.ts` by Issue #3927, with
323
+ * {@link codexDialogBelow}, so the browser can read the same rule.
324
+ *
325
+ * @param rows - The capture split into rows, ANSI intact
326
+ * @returns The row's index, or -1 when no live composer row is drawn
327
+ */
328
+ function findCodexLiveComposerRow(rows) {
329
+ for (let i = rows.length - 1; i >= 0; i--) {
330
+ const kind = readCodexGlyphRow(rows[i]);
331
+ if (kind === 'option')
332
+ return -1;
333
+ if (kind === 'composer' && isLiveComposerRow(rows[i]))
334
+ return i;
335
+ }
336
+ return -1;
337
+ }
338
+ /**
339
+ * Whether a dialog, not a composer, is what the pane shows below `row` (Issue #3906).
340
+ *
341
+ * The same bottom-up reading as {@link findCodexLiveComposerRow}, stopped at
342
+ * `row`: a dialog's highlighted option reached first means the composer at
343
+ * `row` — even one {@link findCodexInputBox} located among the frame's last
344
+ * blocks — was left there from before the dialog opened. A live composer row
345
+ * reached first means it was not.
346
+ *
347
+ * @param rows - The capture split into rows, ANSI intact
348
+ * @param row - Index of the composer's `\u203A` row
349
+ */
350
+ function codexDialogBelow(rows, row) {
351
+ for (let i = rows.length - 1; i > row; i--) {
352
+ const kind = readCodexGlyphRow(rows[i]);
353
+ if (kind === 'option')
354
+ return true;
355
+ if (kind === 'composer' && isLiveComposerRow(rows[i]))
356
+ return false;
357
+ }
358
+ return false;
359
+ }
229
360
  /**
230
361
  * Locate codex's composer in a captured pane (Issue #1890).
231
362
  *
@@ -282,7 +413,7 @@ function findCodexInputBox(lines) {
282
413
  */
283
414
  function applySgr(params, state) {
284
415
  const parts = params === '' ? ['0'] : params.split(';');
285
- let { dim, bold } = state;
416
+ let { dim, bold, fg } = state;
286
417
  for (let i = 0; i < parts.length; i++) {
287
418
  const code = parts[i] === '' ? 0 : Number(parts[i]);
288
419
  if (Number.isNaN(code))
@@ -290,15 +421,18 @@ function applySgr(params, state) {
290
421
  if (code === 38 || code === 48 || code === 58) {
291
422
  const mode = parts[i + 1] === '' || parts[i + 1] === undefined ? -1 : Number(parts[i + 1]);
292
423
  // 5 = 256-colour (one argument), 2 = 24-bit RGB (three arguments).
293
- if (mode === 5)
294
- i += 2;
295
- else if (mode === 2)
296
- i += 4;
297
- else
298
- i += 1;
424
+ const argCount = mode === 5 ? 2 : mode === 2 ? 4 : 1;
425
+ if (code === 38)
426
+ fg = parts.slice(i + 1, i + 1 + argCount).join(';');
427
+ i += argCount;
299
428
  continue;
300
429
  }
301
- if (code === 0 || code === 22) {
430
+ if (code === 0) {
431
+ dim = false;
432
+ bold = false;
433
+ fg = null;
434
+ }
435
+ else if (code === 22) {
302
436
  dim = false;
303
437
  bold = false;
304
438
  }
@@ -306,8 +440,12 @@ function applySgr(params, state) {
306
440
  bold = true;
307
441
  else if (code === 2)
308
442
  dim = true;
443
+ else if (code === 39)
444
+ fg = null;
445
+ else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97))
446
+ fg = String(code);
309
447
  }
310
- return { dim, bold };
448
+ return { dim, bold, fg };
311
449
  }
312
450
  /**
313
451
  * Split one raw pane row into rendered characters tagged with their dim state.
@@ -319,12 +457,12 @@ function applySgr(params, state) {
319
457
  */
320
458
  function scanAttributedChars(line) {
321
459
  const out = [];
322
- let state = { dim: false, bold: false };
460
+ let state = { dim: false, bold: false, fg: null };
323
461
  let i = 0;
324
462
  while (i < line.length) {
325
463
  const ch = line[i];
326
464
  if (ch !== '\x1b') {
327
- out.push({ ch, dim: state.dim, bold: state.bold });
465
+ out.push({ ch, dim: state.dim, bold: state.bold, fg: state.fg });
328
466
  i++;
329
467
  continue;
330
468
  }
@@ -430,6 +568,72 @@ function extractComposerText(rawCapture, cliToolId) {
430
568
  const region = locateComposer(lines, cliToolId);
431
569
  if (region === null)
432
570
  return { text: '', state: 'no_composer' };
571
+ return readComposerRegion(lines, region);
572
+ }
573
+ /**
574
+ * The composer the pane shows NOW — {@link extractComposerText}, less a codex
575
+ * composer that a dialog has opened below (Issue #3927).
576
+ *
577
+ * codex draws on the normal screen, so a composer row from before a dialog
578
+ * opened can still sit among the frame's last blocks, above the dialog's
579
+ * highlighted option. {@link extractComposerText} locates it and reports its
580
+ * old text as `content`; this reports `no_composer`, the answer the clear
581
+ * (`session/composer-clear.ts`) has given that frame since #3906. The two
582
+ * display paths — the published `composerText`
583
+ * (`session/current-output-builder.ts`) and the browser's own reading
584
+ * (`hooks/useTerminalPanePolling.ts`) — read through this, so the unsent bar
585
+ * neither shows text its [Clear] cannot remove nor offers a [Run] whose Enter
586
+ * would land in the dialog.
587
+ *
588
+ * {@link extractComposerText} itself keeps its reading. The sender
589
+ * (`cli-tools/submit-verified-sender.ts`) applies {@link codexDialogBelow} to
590
+ * the box it locates on its own branches, and Auto-Yes's Enter fallback and
591
+ * the send-residue check are left reading the box as before.
592
+ *
593
+ * @param rawCapture - A pane capture with ANSI attributes intact
594
+ * @param cliToolId - The CLI whose session was captured
595
+ */
596
+ function extractLiveComposerText(rawCapture, cliToolId) {
597
+ const read = extractComposerText(rawCapture, cliToolId);
598
+ if (cliToolId !== 'codex' || read.state === 'no_composer' || read.state === 'unsupported_tool')
599
+ return read;
600
+ const lines = rawCapture.split('\n');
601
+ const box = findCodexInputBox(lines);
602
+ if (box !== null && codexDialogBelow(lines, box.firstRow))
603
+ return { text: '', state: 'no_composer' };
604
+ return read;
605
+ }
606
+ /**
607
+ * Read a codex composer from a known `\u203A` row (Issue #3906).
608
+ *
609
+ * {@link findCodexInputBox} only searches the frame's last four blocks, and
610
+ * takes the composer's own block alone: a body of several paragraphs puts its
611
+ * `\u203A` row higher up, and its text stops at the first blank row. This reads
612
+ * from `firstRow` down to the frame's last block — codex's model / cwd footer —
613
+ * blank rows included, under the same dim rule as {@link extractComposerText}.
614
+ * When the composer's own block is the frame's last block it is read to the end.
615
+ *
616
+ * Whether `firstRow` is the live composer — not a row left above a dialog — is
617
+ * the caller's question; this only reads it.
618
+ *
619
+ * @param lines - Captured pane lines, ANSI intact
620
+ * @param firstRow - Index of a row {@link isCodexComposerRow} accepts
621
+ */
622
+ function readCodexComposerFrom(lines, firstRow) {
623
+ let end = lines.length - 1;
624
+ while (end >= 0 && isBlankRow(lines[end]))
625
+ end--;
626
+ let lastBlockStart = end;
627
+ while (lastBlockStart > 0 && !isBlankRow(lines[lastBlockStart - 1]))
628
+ lastBlockStart--;
629
+ let ownBlockEnd = firstRow;
630
+ while (ownBlockEnd + 1 < lines.length && !isBlankRow(lines[ownBlockEnd + 1]))
631
+ ownBlockEnd++;
632
+ const lastRow = lastBlockStart > ownBlockEnd ? lastBlockStart - 1 : end;
633
+ return readComposerRegion(lines, { firstRow, lastRow, glyph: CODEX_PROMPT_GLYPH_PATTERN });
634
+ }
635
+ /** The text of a located composer region, under the dim rule. */
636
+ function readComposerRegion(lines, region) {
433
637
  const realRows = [];
434
638
  const renderedRows = [];
435
639
  for (let i = region.firstRow; i <= region.lastRow; i++) {