commandmate 0.32.0 → 0.33.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 (212) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +159 -159
  3. package/.next/app-path-routes-manifest.json +28 -28
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +12 -12
  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/update/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  12. package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
  18. package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/capabilities/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/hooks/agent-event/route.js +1 -1
  31. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/hooks/claude-done/route.js +2 -2
  33. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/hooks/permission-request/route.js +1 -1
  35. package/.next/server/app/api/hooks/permission-request/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/ollama/models/route_client-reference-manifest.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_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/relays/[relayId]/cancel/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/relays/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/remote/pair/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/settings/default-agents/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/settings/default-surface-mode/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  58. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  60. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/slash-commands/route.js.nft.json +1 -1
  64. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/tasks/[taskId]/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/templates/[id]/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/templates/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
  70. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  72. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/worktrees/[id]/clear-composer/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/worktrees/[id]/cli-reference/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/worktrees/[id]/env/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  82. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  90. package/.next/server/app/api/worktrees/[id]/git/log/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/worktrees/[id]/git/pull/route_client-reference-manifest.js +1 -1
  92. package/.next/server/app/api/worktrees/[id]/git/push/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  94. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  96. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/instances/opencode/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/opencode/diff/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/opencode/session/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/opencode/share/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/resolve-target/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/update-plan/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js.nft.json +1 -1
  136. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  141. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/verify/config/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/cancel/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  151. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  153. package/.next/server/app/api/worktrees/apply-default-agents/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  155. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  156. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  157. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  158. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  159. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  160. package/.next/server/app/page_client-reference-manifest.js +1 -1
  161. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  162. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  163. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  164. package/.next/server/app/sessions/page_client-reference-manifest.js +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_client-reference-manifest.js +1 -1
  169. package/.next/server/app/worktrees/[id]/page.js +11 -11
  170. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  171. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  172. package/.next/server/app-paths-manifest.json +28 -28
  173. package/.next/server/chunks/1210.js +1 -1
  174. package/.next/server/chunks/5043.js +1 -1
  175. package/.next/server/chunks/5164.js +1 -1
  176. package/.next/server/chunks/6491.js +2 -2
  177. package/.next/server/chunks/6838.js +1 -1
  178. package/.next/server/chunks/7875.js +2 -2
  179. package/.next/server/chunks/8918.js +4 -4
  180. package/.next/server/chunks/9130.js +1 -1
  181. package/.next/server/middleware-manifest.json +5 -5
  182. package/.next/server/pages/500.html +1 -1
  183. package/.next/server/server-reference-manifest.json +1 -1
  184. package/.next/static/chunks/{4718-0053d42fb635e700.js → 4718-d97f52cd932dc1ee.js} +1 -1
  185. package/.next/static/chunks/app/worktrees/[id]/page-18a7d9374bd8c218.js +1 -0
  186. package/.next/static/css/{96d94a4ba771efc3.css → fb455b4e308adbaa.css} +1 -1
  187. package/.next/trace +13 -13
  188. package/README.md +6 -2
  189. package/dist/cli/commands/instances.d.ts.map +1 -1
  190. package/dist/cli/commands/instances.js +44 -15
  191. package/dist/cli/commands/peers.d.ts.map +1 -1
  192. package/dist/cli/commands/peers.js +12 -0
  193. package/dist/cli/commands/whoami.d.ts +67 -0
  194. package/dist/cli/commands/whoami.d.ts.map +1 -1
  195. package/dist/cli/commands/whoami.js +106 -0
  196. package/dist/cli/utils/api-client.d.ts +30 -1
  197. package/dist/cli/utils/api-client.d.ts.map +1 -1
  198. package/dist/cli/utils/api-client.js +28 -5
  199. package/dist/server/src/lib/detection/cli-patterns.js +206 -2
  200. package/dist/server/src/lib/hooks/agent-event-service.js +27 -5
  201. package/dist/server/src/lib/hooks/sources/codex/history.js +63 -3
  202. package/dist/server/src/lib/hooks/sources/codex/transcript.js +140 -6
  203. package/dist/server/src/lib/hooks/sources/launch-command.js +112 -15
  204. package/dist/server/src/lib/hooks/sources/turn-body.js +3 -1
  205. package/dist/server/src/lib/hooks/stop-history-capture.js +252 -50
  206. package/dist/server/src/lib/polling/response-checker.js +129 -5
  207. package/dist/server/src/lib/polling/response-dedup.js +136 -0
  208. package/dist/server/src/lib/relay/relay-delivery.js +160 -6
  209. package/package.json +1 -1
  210. package/.next/static/chunks/app/worktrees/[id]/page-06e1ff5ce2d126fd.js +0 -1
  211. /package/.next/static/{UIUPchtWvcEuQgcbGJv4g → eVkZd6vpcQwyJIlbymCUg}/_buildManifest.js +0 -0
  212. /package/.next/static/{UIUPchtWvcEuQgcbGJv4g → eVkZd6vpcQwyJIlbymCUg}/_ssgManifest.js +0 -0
@@ -4,13 +4,15 @@
4
4
  * Shared between response-poller.ts and API routes
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.COPILOT_TOOL_ROW_PATTERN = exports.COPILOT_TOOL_VERBS = exports.COPILOT_TRANSCRIPT_DIVIDER_PATTERN = exports.COPILOT_USER_ECHO_PATTERN = exports.COPILOT_REASONING_HEADER_PATTERN = exports.COPILOT_BOX_ROW_PATTERN = exports.COPILOT_SEPARATOR_PATTERN = exports.COPILOT_IDLE_STATUS_PATTERN = exports.COPILOT_WORKING_STATUS_PATTERN = exports.COPILOT_THINKING_PATTERN = exports.COPILOT_PROMPT_PATTERN = exports.OPENCODE_SKIP_PATTERNS = exports.OPENCODE_SEPARATOR_PATTERN = exports.CLAUDE_SELECTION_LIST_FOOTER = exports.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN = exports.OPENCODE_SELECTION_LIST_PATTERN = exports.OPENCODE_FOOTER_STATUS_PATTERN = exports.OPENCODE_USER_ECHO_PATTERN = exports.OPENCODE_GUTTER_ROW_PATTERN = exports.OPENCODE_COMPOSER_BOTTOM_BORDER = exports.OPENCODE_PROCESSING_INDICATOR = exports.OPENCODE_PERMISSION_PATTERN = exports.OPENCODE_TURN_COMPLETE_PATTERN = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = exports.OPENCODE_IDLE_COMPOSER_PATTERN = exports.OPENCODE_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.CODEX_STATUS_BAR_PATTERN = exports.CODEX_PAGER_FOOTER_PATTERN = exports.CODEX_APPROVAL_FOOTER_PATTERN = exports.CODEX_SELECTION_LIST_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_HOOKS_REVIEW_ANCHORS = exports.CODEX_DIALOG_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_INTERRUPT_HINT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
8
- exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.extractAnsiSequences = exports.stripAnsi = exports.COMMAND_CODE_SKIP_PATTERNS = exports.COMMAND_CODE_HOOK_NOTICE_PATTERN = exports.COMMAND_CODE_BANNER_PATTERNS = exports.COMMAND_CODE_MODE_INDICATOR_PATTERN = exports.COMMAND_CODE_COMPLETION_PATTERN = exports.COMMAND_CODE_RESPONSE_MARKER_PATTERN = exports.COMMAND_CODE_THINKING_PATTERN = exports.COMMAND_CODE_INTERRUPT_HINT_PATTERN = exports.COMMAND_CODE_SEPARATOR_PATTERN = exports.COMMAND_CODE_PROMPT_PATTERN = exports.COMMAND_CODE_SPINNER_CHARS = exports.ANTIGRAVITY_SKIP_PATTERNS = exports.ANTIGRAVITY_SURVEY_PATTERN = exports.ANTIGRAVITY_DIALOG_MAX_ROWS = exports.ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN = exports.ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN = exports.ANTIGRAVITY_NUMBERED_OPTION_PATTERN = exports.ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN = exports.ANTIGRAVITY_SELECTION_LIST_PATTERN = exports.ANTIGRAVITY_SEPARATOR_PATTERN = exports.ANTIGRAVITY_THINKING_PATTERN = exports.ANTIGRAVITY_PROMPT_PATTERN = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.COPILOT_SKIP_PATTERNS = exports.COPILOT_FOLDER_TRUST_ANSWER_KEY = exports.COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN = exports.COPILOT_FOLDER_TRUST_ANCHORS = exports.COPILOT_SELECTION_FOOTER_PATTERN = exports.COPILOT_BOOT_BANNER_ANCHORS = exports.COPILOT_TRANSCRIPT_CONTINUATION_PATTERN = void 0;
7
+ exports.COPILOT_TOOL_VERBS = exports.COPILOT_TRANSCRIPT_DIVIDER_PATTERN = exports.COPILOT_USER_ECHO_PATTERN = exports.COPILOT_REASONING_HEADER_PATTERN = exports.COPILOT_BOX_ROW_PATTERN = exports.COPILOT_SEPARATOR_PATTERN = exports.COPILOT_IDLE_STATUS_PATTERN = exports.COPILOT_WORKING_STATUS_PATTERN = exports.COPILOT_THINKING_PATTERN = exports.COPILOT_PROMPT_PATTERN = exports.OPENCODE_SKIP_PATTERNS = exports.OPENCODE_SEPARATOR_PATTERN = exports.CLAUDE_SELECTION_LIST_FOOTER = exports.CLAUDE_MODEL_OVERLAY_FOOTER_PATTERN = exports.OPENCODE_SELECTION_LIST_PATTERN = exports.OPENCODE_FOOTER_STATUS_PATTERN = exports.OPENCODE_USER_ECHO_PATTERN = exports.OPENCODE_GUTTER_ROW_PATTERN = exports.OPENCODE_COMPOSER_BOTTOM_BORDER = exports.OPENCODE_PROCESSING_INDICATOR = exports.OPENCODE_PERMISSION_PATTERN = exports.OPENCODE_TURN_COMPLETE_PATTERN = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = exports.OPENCODE_IDLE_COMPOSER_PATTERN = exports.OPENCODE_PROMPT_PATTERN = exports.GEMINI_THINKING_PATTERN = exports.GEMINI_PROMPT_PATTERN = exports.MAX_PASTED_TEXT_RETRIES = exports.PASTED_TEXT_DETECT_DELAY = exports.PASTED_TEXT_PATTERN = exports.CODEX_USER_ECHO_PATTERN = exports.CODEX_STATUS_BAR_PATTERN = exports.CODEX_PAGER_FOOTER_PATTERN = exports.CODEX_APPROVAL_FOOTER_PATTERN = exports.CODEX_SELECTION_LIST_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_HOOKS_REVIEW_ANCHORS = exports.CODEX_DIALOG_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_INTERRUPT_HINT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
8
+ exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.extractAnsiSequences = exports.stripAnsi = exports.COMMAND_CODE_SKIP_PATTERNS = exports.COMMAND_CODE_HOOK_NOTICE_PATTERN = exports.COMMAND_CODE_BANNER_PATTERNS = exports.COMMAND_CODE_MODE_INDICATOR_PATTERN = exports.COMMAND_CODE_COMPLETION_PATTERN = exports.COMMAND_CODE_RESPONSE_MARKER_PATTERN = exports.COMMAND_CODE_THINKING_PATTERN = exports.COMMAND_CODE_INTERRUPT_HINT_PATTERN = exports.COMMAND_CODE_SEPARATOR_PATTERN = exports.COMMAND_CODE_PROMPT_PATTERN = exports.COMMAND_CODE_SPINNER_CHARS = exports.ANTIGRAVITY_SKIP_PATTERNS = exports.ANTIGRAVITY_SURVEY_PATTERN = exports.ANTIGRAVITY_DIALOG_MAX_ROWS = exports.ANTIGRAVITY_DIALOG_BOUNDARY_PATTERN = exports.ANTIGRAVITY_SWITCH_MODEL_HEADER_PATTERN = exports.ANTIGRAVITY_NUMBERED_OPTION_PATTERN = exports.ANTIGRAVITY_NAVIGATE_FOOTER_PATTERN = exports.ANTIGRAVITY_SELECTION_LIST_PATTERN = exports.ANTIGRAVITY_SEPARATOR_PATTERN = exports.ANTIGRAVITY_THINKING_PATTERN = exports.ANTIGRAVITY_PROMPT_PATTERN = exports.VIBE_LOCAL_THINKING_PATTERN = exports.VIBE_LOCAL_PROMPT_PATTERN = exports.COPILOT_SKIP_PATTERNS = exports.COPILOT_FOLDER_TRUST_ANSWER_KEY = exports.COPILOT_FOLDER_TRUST_SESSION_OPTION_PATTERN = exports.COPILOT_FOLDER_TRUST_ANCHORS = exports.COPILOT_SELECTION_FOOTER_PATTERN = exports.COPILOT_BOOT_BANNER_ANCHORS = exports.COPILOT_TRANSCRIPT_CONTINUATION_PATTERN = exports.COPILOT_TOOL_ROW_PATTERN = void 0;
9
9
  exports.isCodexTurnActive = isCodexTurnActive;
10
10
  exports.findClaudeChromeStart = findClaudeChromeStart;
11
11
  exports.isCodexPromptReady = isCodexPromptReady;
12
12
  exports.getCodexActiveDialog = getCodexActiveDialog;
13
13
  exports.getCodexLifecycleDialog = getCodexLifecycleDialog;
14
+ exports.findCodexChromeStart = findCodexChromeStart;
15
+ exports.findCodexUserEchoIndex = findCodexUserEchoIndex;
14
16
  exports.findOpenCodeChromeStart = findOpenCodeChromeStart;
15
17
  exports.findOpenCodeUserEchoEnd = findOpenCodeUserEchoEnd;
16
18
  exports.findOpenCodeComposerRows = findOpenCodeComposerRows;
@@ -29,6 +31,7 @@ exports.buildDetectPromptOptions = buildDetectPromptOptions;
29
31
  const logger_1 = require("../../lib/logger");
30
32
  const ansi_1 = require("./ansi");
31
33
  const composer_text_1 = require("./composer-text");
34
+ const cli_patterns_1 = require("./tools/codex/cli-patterns");
32
35
  const thinking_constants_1 = require("../../config/thinking-constants");
33
36
  const logger = (0, logger_1.createLogger)('cli-patterns');
34
37
  /**
@@ -589,6 +592,207 @@ exports.CODEX_PAGER_FOOTER_PATTERN = /(?:↑\/↓|pgup\/pgdn|home\/end)\s+to\s+(
589
592
  * line. No nested quantifiers (ReDoS-safe; adjacent greedy quantifiers only).
590
593
  */
591
594
  exports.CODEX_STATUS_BAR_PATTERN = /^\s*\S.*·\s*~?\/\S*\s*$/;
595
+ /**
596
+ * How far above the last non-blank row {@link findCodexChromeStart} looks for the
597
+ * composer.
598
+ *
599
+ * Same allowance and the same reason as {@link CODEX_COMPOSER_SEARCH_ROWS}: codex
600
+ * pins the composer two to three rows above the bottom in every measured frame
601
+ * (`tests/fixtures/codex-live-2310/`), and a wider band would let the search walk
602
+ * into the transcript and mistake the echoed user message — drawn with the same
603
+ * `›` — for the input box.
604
+ *
605
+ * Wider than the 8 rows of the liveness search because 0.15x can draw notices
606
+ * BELOW the composer (`N background terminal running · /ps to view · /stop to
607
+ * close`) that the liveness search never had to step over.
608
+ */
609
+ const CODEX_CHROME_SEARCH_ROWS = 12;
610
+ /**
611
+ * A row whose first character is codex's `›` glyph, with something after it.
612
+ *
613
+ * Deliberately not {@link CODEX_PROMPT_PATTERN}: that one is multiline and
614
+ * matches a bare `›`, which is right for "is a prompt on screen anywhere?" and
615
+ * wrong for classifying ONE row. Anchored at column 0 because all three of
616
+ * codex's `›` uses are, and an indented `›` in a reply is quoted text.
617
+ */
618
+ const CODEX_CHROME_GLYPH_ROW_PATTERN = /^›(\s|$)/;
619
+ /**
620
+ * Locate the start of codex's bottom-pinned chrome within a captured pane.
621
+ *
622
+ * The fifth reader of this shape, after {@link findClaudeChromeStart} (#1289),
623
+ * {@link findCopilotChromeStart} (#1897), {@link findOpenCodeChromeStart}
624
+ * (#1911) and {@link findCommandCodeChromeStart} (#2250). codex is the tool that
625
+ * never got one, and Issue #2400 is the bill for that.
626
+ *
627
+ * codex renders inline and pins two rows to the bottom of a settled pane:
628
+ *
629
+ * ```text
630
+ * › Ask Codex to do anything ← composer (placeholder or typed text)
631
+ *
632
+ * gpt-6-astra xhigh · ~/share/work/…/CommandMate ← status bar (model · cwd)
633
+ * ```
634
+ *
635
+ * Below the composer codex may also draw its own notices (`N background terminal
636
+ * running · /ps to view`), so the boundary is "the composer row" rather than a
637
+ * list of footer shapes: everything from the composer down is chrome by
638
+ * construction, whatever codex adds there next.
639
+ *
640
+ * ## What went wrong without it (#2400)
641
+ *
642
+ * While the capture window is NOT saturated codex's extraction starts at
643
+ * `lastCapturedLine`, so the composer only ever mattered as the `endIndex` break
644
+ * — which the extraction loop already had. Once the pane outgrows
645
+ * `CACHE_MAX_CAPTURE_LINES` (#1670) the cursor stops being a position in the
646
+ * capture and `resolveExtractionStartIndex` switches to the newest echoed user
647
+ * prompt. With no `contentEnd`, that backwards search started at the very bottom
648
+ * of the pane and the first `›` it met was the COMPOSER. Extraction then began
649
+ * on the row after it, i.e. on the status bar, and the saved "reply" for every
650
+ * turn on a saturated pane was one row:
651
+ *
652
+ * ```text
653
+ * gpt-6-astra xhigh · ~/share/work/github_kewton/CommandAgent-develop
654
+ * ```
655
+ *
656
+ * Identical on every turn, so `isDuplicateResponse` then locked on it and the
657
+ * pane could not record another reply at all. That is #1289's defect verbatim,
658
+ * one tool later — the same reason `findCommandCodeChromeStart` exists.
659
+ *
660
+ * ## Why the attributes and not the placeholder text
661
+ *
662
+ * The codex branch of `findRecentUserPromptIndex` used to exclude the composer by
663
+ * naming its placeholders (`Implement`, `Find and fix`, `Type`, `Summarize`).
664
+ * Those are codex 0.1x wording, and 0.15x draws `Ask Codex to do anything`, so
665
+ * the list silently stopped matching the thing it was written for. Issue #2310
666
+ * measured what actually separates codex's three uses of `›` (U+203A), and it is
667
+ * the SGR attributes, not the text: the composer glyph is bold (`ESC[1m›`), a
668
+ * transcript echo is dim (`ESC[1;2m›`), a dialog option carries a coloured glyph
669
+ * or a bold label. {@link readCodexGlyphRowKind} is that measurement, and this
670
+ * reader is one of its callers.
671
+ *
672
+ * `-1` is returned for a frame whose bottom-most `›` is an option row: codex
673
+ * replaces the composer with the dialog, so there is no chrome to trim and the
674
+ * caller resolves the frame on the prompt path instead.
675
+ *
676
+ * ## The stripped-capture fallback
677
+ *
678
+ * Auto-Yes hands the detection layer a capture that has already been through
679
+ * `stripAnsi`, and there every `›` is the same byte — {@link
680
+ * readCodexGlyphRowKind} answers `null` on purpose rather than guessing. This
681
+ * reader still has to answer for those frames, so it falls back to the one
682
+ * structural landmark codex pins BELOW the composer and nowhere else: the status
683
+ * bar ({@link CODEX_STATUS_BAR_PATTERN}, `model · /path`). Requiring it means a
684
+ * frame with no bar — a pane mid-redraw, an overlay — yields `-1` and the
685
+ * pre-#2400 reading, which is the direction that costs nothing.
686
+ *
687
+ * @param lines - Captured pane lines, ANSI-bearing or not; trailing blanks tolerated
688
+ * @returns Index of the composer row, or -1 when no composer chrome is present
689
+ */
690
+ function findCodexChromeStart(lines) {
691
+ let lastRow = lines.length - 1;
692
+ while (lastRow >= 0 && (0, ansi_1.stripAnsi)(lines[lastRow]).trim() === '')
693
+ lastRow--;
694
+ if (lastRow < 0)
695
+ return -1;
696
+ // Bottom-most `›` row within the band codex reserves for its chrome. Anything
697
+ // further up is transcript, and latching onto an echo there would cut the
698
+ // reply this whole reader exists to keep.
699
+ let glyphRow = -1;
700
+ for (let i = lastRow; i >= Math.max(0, lastRow - CODEX_CHROME_SEARCH_ROWS); i--) {
701
+ if (CODEX_CHROME_GLYPH_ROW_PATTERN.test((0, ansi_1.stripAnsi)(lines[i]))) {
702
+ glyphRow = i;
703
+ break;
704
+ }
705
+ }
706
+ if (glyphRow < 0)
707
+ return -1;
708
+ const kind = (0, cli_patterns_1.readCodexGlyphRowKind)(lines[glyphRow]);
709
+ if (kind === 'composer')
710
+ return glyphRow;
711
+ // A dialog is up (no composer drawn) or the bottom-most `›` is a transcript
712
+ // echo mid-redraw. Neither is chrome to trim.
713
+ if (kind !== null)
714
+ return -1;
715
+ // Stripped capture: no attributes to read. Accept the row as the composer only
716
+ // when codex's status bar is drawn below it, which is where it always sits and
717
+ // where a transcript echo can never be.
718
+ for (let i = glyphRow + 1; i <= lastRow; i++) {
719
+ if (exports.CODEX_STATUS_BAR_PATTERN.test((0, ansi_1.stripAnsi)(lines[i])))
720
+ return glyphRow;
721
+ }
722
+ return -1;
723
+ }
724
+ /**
725
+ * The shape of a codex row that could be the echo of a message the user sent.
726
+ *
727
+ * Shape only — `›` at column 0 with text after it — which all three of codex's
728
+ * `›` uses share. {@link findCodexUserEchoIndex} is what tells them apart.
729
+ */
730
+ exports.CODEX_USER_ECHO_PATTERN = /^›\s+\S/;
731
+ /**
732
+ * Find the newest transcript echo of an operator message in a codex capture.
733
+ *
734
+ * The reader `findRecentUserPromptIndex` anchors codex turns on (Issue #2400).
735
+ * It replaces a negative lookahead over composer placeholder strings
736
+ * (`(?!Implement|Find and fix|Type|Summarize)`) written against codex 0.1x:
737
+ * 0.15x draws `Ask Codex to do anything`, so the guard matched nothing it was
738
+ * written for. The composer became the newest "echo", and on a saturated pane —
739
+ * the one path where this anchor decides where extraction STARTS (#1670) — the
740
+ * reply saved for every turn was the single status-bar row below it.
741
+ *
742
+ * ## What the attributes can and cannot separate
743
+ *
744
+ * #2310 measured the three uses of `›` and this reader adds the fourth reading
745
+ * they left open, captured for #2400 on codex-cli 0.153.4
746
+ * (`tests/fixtures/codex-live-2310/turn-submitted-no-status.txt`): the echo of
747
+ * a message the operator has JUST submitted is drawn `ESC[1m› ESC[0m<text>` —
748
+ * bold glyph, plain label — and only becomes the dim `ESC[1;2m› ` of the
749
+ * measured frames once the turn settles. That shape is indistinguishable from a
750
+ * composer holding typed text, so no per-row attribute rule can separate them.
751
+ *
752
+ * What separates them is position, which codex's layout fixes: the composer is
753
+ * the BOTTOM-MOST `›` row of a frame. So the reader takes the boundary from its
754
+ * caller:
755
+ *
756
+ * - `composerTrimmed` — `findCodexChromeStart` located the chrome and `lines`
757
+ * has already been cut above it, so every `›` row left is transcript and the
758
+ * newest one wins outright.
759
+ * - otherwise — the composer may still be the bottom-most `›` row, so the first
760
+ * candidate is stepped over. This is the structural spelling of the guard the
761
+ * placeholder list used to be, and unlike that list it cannot go stale.
762
+ *
763
+ * Dialog option rows are refused wherever they appear: codex renders inline, so
764
+ * a dialog answered minutes ago is still in the scrollback with its options
765
+ * intact (#1160), and anchoring on one would cut the reply mid-way.
766
+ *
767
+ * @param lines - Captured pane rows, ANSI intact where the caller has it
768
+ * @param contentEnd - Exclusive end of the conversation region
769
+ * @param windowSize - How many rows above `contentEnd` to search
770
+ * @param composerTrimmed - Whether `contentEnd` already excludes the composer
771
+ * @returns Index of the newest echo row, or -1 when none is in the window
772
+ */
773
+ function findCodexUserEchoIndex(lines, contentEnd, windowSize, composerTrimmed) {
774
+ let composerHandled = composerTrimmed;
775
+ for (let i = Math.min(contentEnd, lines.length) - 1; i >= Math.max(0, contentEnd - windowSize); i--) {
776
+ if (!exports.CODEX_USER_ECHO_PATTERN.test((0, ansi_1.stripAnsi)(lines[i])))
777
+ continue;
778
+ const kind = (0, cli_patterns_1.readCodexGlyphRowKind)(lines[i]);
779
+ // An option row of a dialog still sitting in the scrollback.
780
+ if (kind === 'option')
781
+ continue;
782
+ // Positively an echo: codex has settled the row and drawn its glyph dim.
783
+ if (kind === 'transcript-echo')
784
+ return i;
785
+ // `composer` (bold glyph) or `null` (an ANSI-stripped capture, where all
786
+ // three uses are one byte). Either could be the input box, and the input box
787
+ // is always the bottom-most `›` row.
788
+ if (!composerHandled) {
789
+ composerHandled = true;
790
+ continue;
791
+ }
792
+ return i;
793
+ }
794
+ return -1;
795
+ }
592
796
  /**
593
797
  * Pasted text pattern
594
798
  *
@@ -161,16 +161,29 @@ function safeRealpath(target) {
161
161
  * skip it, and after `recordAgentStopEvent` because the reader resolves its
162
162
  * transcript through the session pointer that call refreshes.
163
163
  *
164
- * It is awaited rather than detached. The turn has already ended, so the cost is
165
- * paid by nobody who is waiting for an answer, and a detached promise here would
166
- * be one whose failures nothing observes.
164
+ * ## Exactly one read is awaited (Issue #2398)
165
+ *
166
+ * The comment that stood here said the read could be awaited freely, because
167
+ * "the turn has already ended, so the cost is paid by nobody who is waiting for
168
+ * an answer". That is true of Claude Code, which writes its transcript and then
169
+ * fires the hook, and false of codex, which does not append the record closing
170
+ * the turn until this hook's command exits — so for codex the agent *is* waiting
171
+ * for this answer, and the retries that were waiting for the file were waiting
172
+ * for themselves. Measured 2026-09-07: 105 codex stop events, 0 captured, 1 s of
173
+ * the agent's stop path spent on each.
174
+ *
175
+ * So the reader is asked once here, and anything further happens after this
176
+ * function has returned — detached, and observed through the log rather than
177
+ * through the return value, which is what
178
+ * {@link AgentStopOutcome.structuredHistoryOutcome} exists to say.
167
179
  *
168
180
  * Never throws. A hook is fire-and-forget from a CLI's stop handler, and a
169
181
  * failure here must not become the agent's problem.
170
182
  */
171
183
  async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
172
184
  (0, agent_event_state_1.recordAgentStopEvent)(worktree.id, cliToolId, instanceId);
173
- const structuredHistoryCaptured = await (0, stop_history_capture_1.captureTranscriptTurnOnStop)(worktree, cliToolId, instanceId);
185
+ const { status: structuredHistoryOutcome } = await (0, stop_history_capture_1.resolveStopTranscriptCapture)(worktree, cliToolId, instanceId);
186
+ const structuredHistoryCaptured = structuredHistoryOutcome === 'captured';
174
187
  let task;
175
188
  try {
176
189
  task = (0, tasks_db_1.getActiveTaskForInstance)(db, worktree.id, cliToolId, instanceId);
@@ -185,6 +198,7 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
185
198
  taskEventApplied: false,
186
199
  verificationRunId: null,
187
200
  structuredHistoryCaptured,
201
+ structuredHistoryOutcome,
188
202
  };
189
203
  }
190
204
  if (!task) {
@@ -193,6 +207,7 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
193
207
  taskEventApplied: false,
194
208
  verificationRunId: null,
195
209
  structuredHistoryCaptured,
210
+ structuredHistoryOutcome,
196
211
  };
197
212
  }
198
213
  let taskEventApplied = false;
@@ -212,6 +227,7 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
212
227
  taskEventApplied,
213
228
  verificationRunId: null,
214
229
  structuredHistoryCaptured,
230
+ structuredHistoryOutcome,
215
231
  };
216
232
  }
217
233
  let verificationRunId = null;
@@ -235,5 +251,11 @@ async function applyAgentStopEvent(db, worktree, cliToolId, instanceId) {
235
251
  });
236
252
  }
237
253
  }
238
- return { taskId: task.id, taskEventApplied, verificationRunId, structuredHistoryCaptured };
254
+ return {
255
+ taskId: task.id,
256
+ taskEventApplied,
257
+ verificationRunId,
258
+ structuredHistoryCaptured,
259
+ structuredHistoryOutcome,
260
+ };
239
261
  }
@@ -323,6 +323,13 @@ async function scanForRollout(dir, sessionId, depth) {
323
323
  * attach` is the thing #2196 exists to record, and it is worth recording next to
324
324
  * a *scraped* reply just as much as next to a Markdown one.
325
325
  *
326
+ * A turn whose `task_started` fell outside the window is written too, behind
327
+ * `CODEX_TURN_HEAD_TRUNCATION_MARKER` and beside a
328
+ * `codex-transcript-turn-headless` report (Issue #2402). See `./transcript`'s
329
+ * module docblock for why codex may do that where `../claude/history` may not,
330
+ * and `docs/design/codex-transcript-reader.md` §4.5 for why the prompts that
331
+ * went out of the window with it are left unadopted rather than guessed at.
332
+ *
326
333
  * Never throws.
327
334
  *
328
335
  * @param target - The instance whose turn just ended
@@ -411,11 +418,17 @@ async function captureCodexTranscriptTurn(target, capture = {}) {
411
418
  const previousStartedAt = index === 0 ? pending.previousStartedAt : pending.turns[index - 1].startedAt;
412
419
  userRows.push(await recordCodexUserTurns(target, pending.turns[index], previousStartedAt));
413
420
  }
421
+ const windowFacts = {
422
+ path,
423
+ firstRecordAt: firstCodexRecordAt(parsed.records),
424
+ turnlessRecords: built.turnlessRecords,
425
+ malformedLines: parsed.malformedLines,
426
+ };
414
427
  const lastRecordAt = lastCodexRecordAt(parsed.records);
415
428
  let captured = false;
416
429
  for (let index = 0; index < pending.turns.length; index += 1) {
417
430
  const turn = pending.turns[index];
418
- captured = await writeCodexTurn(target, turn, (0, transcript_1.renderCodexTurn)(turn), resolveAssistantTimestampMs(turn, userRows[index], lastRecordAt.get(turn.turnId) ?? 0, nextTurnOpensAt(pending.turns, userRows, index)), path);
431
+ captured = await writeCodexTurn(target, turn, (0, transcript_1.renderCodexTurn)(turn), resolveAssistantTimestampMs(turn, userRows[index], lastRecordAt.get(turn.turnId) ?? 0, nextTurnOpensAt(pending.turns, userRows, index)), windowFacts);
419
432
  }
420
433
  return captured;
421
434
  }
@@ -448,6 +461,21 @@ async function resolveCodexTranscriptPath(target, capture = {}) {
448
461
  return null;
449
462
  }
450
463
  }
464
+ /**
465
+ * When the window's first timestamped record was written (Issue #2402).
466
+ *
467
+ * The floor on "everything this read could possibly have seen". For a headless
468
+ * turn it is also the only honest thing to say about where the missing head
469
+ * ends: the turn began before this instant, and by how much is not in the file
470
+ * this read opened.
471
+ */
472
+ function firstCodexRecordAt(records) {
473
+ for (const record of records) {
474
+ if (record.timestampMs !== null)
475
+ return record.timestampMs;
476
+ }
477
+ return null;
478
+ }
451
479
  /**
452
480
  * The turns {@link captureCodexTranscriptTurn} still has to write (Issue #2246).
453
481
  *
@@ -642,7 +670,7 @@ function nextTurnOpensAt(turns, userRows, index) {
642
670
  *
643
671
  * @returns Whether History holds this turn as the agent's own Markdown
644
672
  */
645
- async function writeCodexTurn(target, turn, rendered, timestampMs, path) {
673
+ async function writeCodexTurn(target, turn, rendered, timestampMs, windowFacts) {
646
674
  const instanceId = target.instanceId ?? target.cliToolId;
647
675
  if (!turn.closed) {
648
676
  // codex has not written the `task_complete` for this `turn_id` yet, so what
@@ -706,6 +734,38 @@ async function writeCodexTurn(target, turn, rendered, timestampMs, path) {
706
734
  });
707
735
  return true;
708
736
  }
737
+ if (rendered.headless) {
738
+ // Issue #2402. The turn's `task_started` is outside the window, so this row
739
+ // carries `CODEX_TURN_HEAD_TRUNCATION_MARKER` and the operator can see that
740
+ // — but the operator cannot see WHY, and neither can anyone reading the logs
741
+ // afterwards. Everything below is measured: where the window opens, what it
742
+ // held that no turn claimed, and how many of the turn's prompts were inside
743
+ // it. How much fell out is deliberately NOT here. It is not knowable from a
744
+ // read that never saw those bytes, and a guess would be worse than silence.
745
+ //
746
+ // `promptsInWindow` is the one to read when a prompt is missing from the
747
+ // conversation: codex folds a prompt submitted mid-turn into that same turn
748
+ // (23 of 326 archived turns carry more than one), so a headless turn's
749
+ // earlier prompts can be outside the window too — and unlike the body, those
750
+ // are NOT recoverable. `recordCodexUserTurns` only ever sees `turn.prompts`,
751
+ // which is what the window held; a `/send` row for a prompt written before
752
+ // the window stays unkeyed. Adopting it would mean guessing which unkeyed
753
+ // row belongs to this turn with no text to match on — see
754
+ // `docs/design/codex-transcript-reader.md` §4.5.
755
+ logger.info('codex-transcript-turn-headless', {
756
+ worktreeId: target.worktreeId,
757
+ instanceId,
758
+ sessionId: rendered.sessionId,
759
+ turnId: rendered.turnId,
760
+ path: windowFacts.path,
761
+ windowBytes: exports.CODEX_TRANSCRIPT_TAIL_BYTES,
762
+ windowFirstRecordAt: windowFacts.firstRecordAt,
763
+ turnlessRecords: windowFacts.turnlessRecords,
764
+ malformedLines: windowFacts.malformedLines,
765
+ promptsInWindow: turn.prompts.length,
766
+ itemsInWindow: turn.items.length,
767
+ });
768
+ }
709
769
  const message = createMessage(db, {
710
770
  worktreeId: target.worktreeId,
711
771
  role: 'assistant',
@@ -722,7 +782,7 @@ async function writeCodexTurn(target, turn, rendered, timestampMs, path) {
722
782
  instanceId,
723
783
  sessionId: rendered.sessionId,
724
784
  requestId,
725
- path,
785
+ path: windowFacts.path,
726
786
  bodyLength: rendered.body.length,
727
787
  textBlocks: rendered.textBlocks,
728
788
  toolBlocks: rendered.toolBlocks,
@@ -53,6 +53,34 @@
53
53
  * boundary is a field, and a turn is closed by the `task_complete` that carries
54
54
  * its id — measured on 326 of 326 archived turns.
55
55
  *
56
+ * ## A turn can begin outside the window (Issue #2402)
57
+ *
58
+ * `./history` reads the rollout's last `CODEX_TRANSCRIPT_TAIL_BYTES` and nothing
59
+ * more, because the largest rollout on this machine was 273 MB. A long turn
60
+ * outgrows that window: measured on 2026-09-07 on a 30 MB / 7,408-line rollout
61
+ * whose newest turn ran seven hours over 387 items and four compactions, so its
62
+ * `task_started` and its first two prompts sat outside the 4 MiB tail while its
63
+ * later items sat inside it.
64
+ *
65
+ * The `turn_id` stamp is what makes that recoverable. Because it is on **every**
66
+ * `item_completed` and not only on `task_started`, a window that opens in the
67
+ * middle of a turn still reads the turn's real id — so the turn can be rendered
68
+ * AND written under the key a later read will recognise. What is lost is the
69
+ * head of the body, and {@link CodexTurnAccumulator.started} is how that becomes
70
+ * a fact the writer can state: it is the opening mark, exactly as `closed` is
71
+ * the closing one, and a turn with `started: false` is rendered behind
72
+ * {@link CODEX_TURN_HEAD_TRUNCATION_MARKER}.
73
+ *
74
+ * **This is where codex and claude diverge, and the divergence is deliberate.**
75
+ * `../claude/history`'s `collectHeadlessClaudeTurn` renders the same situation
76
+ * and refuses to write it, because claude's transcript carries no link from a
77
+ * reply to the prompt it answers: a turn whose prompt record is outside the
78
+ * window has no key at all, so writing it would mean inventing one, and an
79
+ * invented key is a row no later run can recognise as already written. codex is
80
+ * not under that constraint — the key is a field on the records that ARE in the
81
+ * window — so the argument that stops claude does not reach here. Copying
82
+ * claude's refusal would drop a body codex handed over for free.
83
+ *
56
84
  * ## Pure on purpose
57
85
  *
58
86
  * No filesystem, no database, no `globalThis` — `./history` owns all three, the
@@ -63,7 +91,7 @@
63
91
  * @module lib/hooks/sources/codex/transcript
64
92
  */
65
93
  Object.defineProperty(exports, "__esModule", { value: true });
66
- exports.CODEX_THINKING_LABEL = exports.MAX_CODEX_TOOL_DETAIL_LENGTH = exports.MAX_CODEX_TURN_ITEMS = exports.CODEX_TURN_TRUNCATION_MARKER = exports.MAX_CODEX_TURN_BODY_LENGTH = exports.CODEX_ROLLOUT_EXTENSION = exports.CODEX_SESSIONS_DIR_SEGMENTS = void 0;
94
+ exports.CODEX_COMMENTARY_PHASE = exports.CODEX_FINAL_ANSWER_PHASE = exports.CODEX_THINKING_LABEL = exports.MAX_CODEX_TOOL_DETAIL_LENGTH = exports.MAX_CODEX_TURN_ITEMS = exports.CODEX_TURN_HEAD_TRUNCATION_MARKER = exports.CODEX_TURN_TRUNCATION_MARKER = exports.MAX_CODEX_TURN_BODY_LENGTH = exports.CODEX_ROLLOUT_EXTENSION = exports.CODEX_SESSIONS_DIR_SEGMENTS = void 0;
67
95
  exports.readCodexRolloutItem = readCodexRolloutItem;
68
96
  exports.readCodexRolloutRecord = readCodexRolloutRecord;
69
97
  exports.parseCodexRollout = parseCodexRollout;
@@ -79,6 +107,17 @@ exports.CODEX_ROLLOUT_EXTENSION = '.jsonl';
79
107
  exports.MAX_CODEX_TURN_BODY_LENGTH = 200_000;
80
108
  /** Appended when {@link MAX_CODEX_TURN_BODY_LENGTH} truncates a turn. */
81
109
  exports.CODEX_TURN_TRUNCATION_MARKER = '\n\n_(truncated)_';
110
+ /**
111
+ * Prepended when a turn's opening records fell outside the window (#2402).
112
+ *
113
+ * The mirror of {@link CODEX_TURN_TRUNCATION_MARKER} — same italic parenthetical
114
+ * in the same plain Markdown, on the other end of the body — because the two say
115
+ * the same thing about the same row and a reader who has learned to read one
116
+ * should not have to learn a second notation for the other. Written into
117
+ * `chat_messages.content` at read time like every other string these readers
118
+ * emit, so it is English and outlives any later locale change.
119
+ */
120
+ exports.CODEX_TURN_HEAD_TRUNCATION_MARKER = '_(head truncated)_\n\n';
82
121
  /**
83
122
  * Cap on items kept for one turn.
84
123
  *
@@ -91,6 +130,21 @@ exports.MAX_CODEX_TURN_ITEMS = 2048;
91
130
  exports.MAX_CODEX_TOOL_DETAIL_LENGTH = 200;
92
131
  /** The label a reasoning summary is folded behind. Same word claude uses. */
93
132
  exports.CODEX_THINKING_LABEL = 'Thinking';
133
+ /**
134
+ * `AgentMessage.phase` for the reply codex means the operator to read.
135
+ *
136
+ * Measured 268 of 2,211 archived `AgentMessage` items
137
+ * (`docs/design/codex-transcript-reader.md` §2.3).
138
+ */
139
+ exports.CODEX_FINAL_ANSWER_PHASE = 'final_answer';
140
+ /**
141
+ * `AgentMessage.phase` for the progress narration codex writes while it works.
142
+ *
143
+ * Measured 1,943 of 2,211 — the majority of what codex says in a turn is this.
144
+ * The one phase {@link renderCodexTurn} folds; see its docblock for why the
145
+ * classification is an allow-list of exactly this value and nothing else.
146
+ */
147
+ exports.CODEX_COMMENTARY_PHASE = 'commentary';
94
148
  /**
95
149
  * Read one `item_completed` item.
96
150
  *
@@ -322,11 +376,19 @@ function buildCodexTurns(records, sessionId) {
322
376
  startedAt: record.timestampMs ?? 0,
323
377
  prompts: [],
324
378
  items: [],
379
+ started: false,
325
380
  closed: false,
326
381
  overflowed: false,
327
382
  };
328
383
  turns.set(turnId, turn);
329
384
  }
385
+ if (record.payloadType === 'task_started') {
386
+ // The opening mark (Issue #2402). Recorded rather than assumed from "this
387
+ // is the first record we saw for the id", which is true of every turn in
388
+ // the window — including the one whose real first record is outside it.
389
+ turn.started = true;
390
+ continue;
391
+ }
330
392
  if (record.payloadType === 'task_complete') {
331
393
  turn.closed = true;
332
394
  continue;
@@ -390,14 +452,79 @@ function renderReasoningItem(text) {
390
452
  .join('\n');
391
453
  return `> **${exports.CODEX_THINKING_LABEL}**\n>\n${quoted}`;
392
454
  }
455
+ /**
456
+ * The prose one `AgentMessage` contributes, or `''` when it contributes none.
457
+ *
458
+ * The single definition of "non-empty body" for this reader, shared by
459
+ * {@link hasFinalAnswerProse} and by {@link renderCodexTurn}'s loop on purpose:
460
+ * the fallback asks whether the turn renders an answer, and if the two spelled
461
+ * that test out separately they could disagree about a message that is nothing
462
+ * but whitespace — the exact turn the fallback exists for.
463
+ */
464
+ function agentMessageText(item) {
465
+ return item.text?.trim() ?? '';
466
+ }
467
+ /**
468
+ * Whether this turn renders at least one non-empty `final_answer`.
469
+ *
470
+ * The test is on the **rendered body**, not on the presence of an item: item
471
+ * count and drawn paragraphs are different quantities, because a message whose
472
+ * text is empty or whitespace is dropped before it reaches the body. A turn
473
+ * whose only `final_answer` is blank would, on an item-count test, fold its
474
+ * commentary away and leave an empty bubble.
475
+ */
476
+ function hasFinalAnswerProse(turn) {
477
+ return turn.items.some((item) => item.type === 'AgentMessage' &&
478
+ item.phase === exports.CODEX_FINAL_ANSWER_PHASE &&
479
+ agentMessageText(item).length > 0);
480
+ }
393
481
  /**
394
482
  * Render one turn to Markdown.
395
483
  *
396
484
  * Transcript order within each kind — the same decision #2041 and #2121 took,
397
485
  * and for the same reason: the order is the only record of what happened when,
398
- * and this row is the record. Both `commentary` and `final_answer` messages are
399
- * kept; codex's TUI shows both, and dropping the commentary would remove the
400
- * sentence that explains what the tool line underneath it is for.
486
+ * and this row is the record.
487
+ *
488
+ * ## Why `commentary` is folded now (Issue #2420)
489
+ *
490
+ * #2197 kept `commentary` in the body on the argument that "the sentence that
491
+ * explains what the tool line underneath it is for" would otherwise be lost.
492
+ * **#2234 retired that argument by moving every tool line to a folded section
493
+ * at the end of the body** — there is no line underneath the commentary any
494
+ * more, so what leads the bubble is narration with nothing left to narrate.
495
+ * Measured on one live session (`codex-turn:01a07e37-0658`): 4 commentary
496
+ * blocks / 754 characters in front of a single 495-character answer, so the
497
+ * operator read four paragraphs of progress notes before reaching the reply.
498
+ *
499
+ * ## The classification is an allow-list
500
+ *
501
+ * Exactly `phase === 'commentary'` folds. `final_answer`, a missing `phase` and
502
+ * any value codex has not shipped yet all stay in the body. The deny-list
503
+ * spelling — "fold everything that is not `final_answer`" — is rejected on
504
+ * #2196's discipline: it hands codex the power to hide the body of a bubble by
505
+ * adding a phase name, and the failure would be silent. Every `final_answer` is
506
+ * kept, not just the last: the measured corpus has turns carrying more than one.
507
+ *
508
+ * ## The fallback
509
+ *
510
+ * A turn with no non-empty `final_answer` keeps its commentary in the body —
511
+ * see {@link hasFinalAnswerProse}. Folding is only ever an improvement when
512
+ * there is something better to lead with.
513
+ *
514
+ * ## Why the `Thinking` chip and not a new label
515
+ *
516
+ * The folded commentary rides `../turn-body`'s existing `reasoning` kind, so it
517
+ * lands under the `Thinking (N)` heading the other readers already write, and
518
+ * `splitChatThinking` in `components/worktree/ChatMessageBubble` draws it in
519
+ * the chip that is already there. A second label (`Progress`) would read more
520
+ * precisely, and it was rejected on cost: these labels are baked into
521
+ * `chat_messages.content` **at read time**, so a new one is a string every
522
+ * future reader of the table has to keep folding forever, for rows nothing can
523
+ * re-label. Against that, the chip already means the right thing — "subordinate
524
+ * narration the reader may want and does not want first" — and the operator who
525
+ * reported #2420 called this content "thinking" in their own words. Same
526
+ * reasoning as {@link CODEX_THINKING_LABEL}, which is `Thinking` for the same
527
+ * reason.
401
528
  *
402
529
  * The layout — prose first, the calls folded into one labelled section — is
403
530
  * `../turn-body`'s and is shared with the other three readers (#2234).
@@ -407,12 +534,14 @@ function renderCodexTurn(turn) {
407
534
  const unknown = new Set();
408
535
  let textBlocks = 0;
409
536
  let toolBlocks = 0;
537
+ const foldsCommentary = hasFinalAnswerProse(turn);
410
538
  for (const item of turn.items) {
411
539
  if (item.type === 'AgentMessage') {
412
- const text = item.text?.trim() ?? '';
540
+ const text = agentMessageText(item);
413
541
  if (text.length === 0)
414
542
  continue;
415
- rendered.push({ kind: 'prose', text });
543
+ const folds = foldsCommentary && item.phase === exports.CODEX_COMMENTARY_PHASE;
544
+ rendered.push({ kind: folds ? 'reasoning' : 'prose', text });
416
545
  textBlocks += 1;
417
546
  continue;
418
547
  }
@@ -432,6 +561,10 @@ function renderCodexTurn(turn) {
432
561
  unknown.add(item.type);
433
562
  }
434
563
  let body = (0, turn_body_1.separateTurnBody)(rendered).body;
564
+ // The head mark goes on before the length bound, so a headless turn long
565
+ // enough to be truncated at both ends still says so at both ends.
566
+ if (!turn.started && body.length > 0)
567
+ body = exports.CODEX_TURN_HEAD_TRUNCATION_MARKER + body;
435
568
  if (body.length > exports.MAX_CODEX_TURN_BODY_LENGTH) {
436
569
  body =
437
570
  body.slice(0, exports.MAX_CODEX_TURN_BODY_LENGTH - exports.CODEX_TURN_TRUNCATION_MARKER.length) +
@@ -441,6 +574,7 @@ function renderCodexTurn(turn) {
441
574
  sessionId: turn.sessionId,
442
575
  turnId: turn.turnId,
443
576
  body,
577
+ headless: !turn.started,
444
578
  textBlocks,
445
579
  toolBlocks,
446
580
  unknownBlockTypes: [...unknown],