commandmate 0.18.0 → 0.19.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 (257) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +131 -131
  3. package/.next/app-path-routes-manifest.json +23 -23
  4. package/.next/build-manifest.json +2 -2
  5. package/.next/prerender-manifest.json +9 -9
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/api/app/update/route_client-reference-manifest.js +1 -1
  9. package/.next/server/app/api/app/update-check/route.js +1 -1
  10. package/.next/server/app/api/app/update-check/route_client-reference-manifest.js +1 -1
  11. package/.next/server/app/api/assistant/conversation/route_client-reference-manifest.js +1 -1
  12. package/.next/server/app/api/assistant/current-output/route_client-reference-manifest.js +1 -1
  13. package/.next/server/app/api/assistant/messages/route_client-reference-manifest.js +1 -1
  14. package/.next/server/app/api/assistant/session/route_client-reference-manifest.js +1 -1
  15. package/.next/server/app/api/assistant/start/route_client-reference-manifest.js +1 -1
  16. package/.next/server/app/api/assistant/terminal/route_client-reference-manifest.js +1 -1
  17. package/.next/server/app/api/assistant/tools/route_client-reference-manifest.js +1 -1
  18. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -1
  19. package/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -1
  20. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/daily-summary/route_client-reference-manifest.js +1 -1
  22. package/.next/server/app/api/daily-summary/status/route_client-reference-manifest.js +1 -1
  23. package/.next/server/app/api/external-apps/[id]/health/route_client-reference-manifest.js +1 -1
  24. package/.next/server/app/api/external-apps/[id]/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/external-apps/route_client-reference-manifest.js +1 -1
  26. package/.next/server/app/api/fs/browse/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/fs/recent-paths/route_client-reference-manifest.js +1 -1
  28. package/.next/server/app/api/hooks/agent-event/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/hooks/claude-done/route_client-reference-manifest.js +1 -1
  30. package/.next/server/app/api/metrics/vibe/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/ollama/models/route_client-reference-manifest.js +1 -1
  32. package/.next/server/app/api/push/subscriptions/route.js +1 -1
  33. package/.next/server/app/api/push/subscriptions/route_client-reference-manifest.js +1 -1
  34. package/.next/server/app/api/push/vapid/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/repositories/[id]/route_client-reference-manifest.js +1 -1
  36. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route.js +1 -1
  37. package/.next/server/app/api/repositories/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  38. package/.next/server/app/api/repositories/[id]/todos/route.js +1 -1
  39. package/.next/server/app/api/repositories/[id]/todos/route_client-reference-manifest.js +1 -1
  40. package/.next/server/app/api/repositories/clone/[jobId]/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/repositories/clone/route_client-reference-manifest.js +1 -1
  42. package/.next/server/app/api/repositories/excluded/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/repositories/restore/route_client-reference-manifest.js +1 -1
  44. package/.next/server/app/api/repositories/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/repositories/scan/route_client-reference-manifest.js +1 -1
  46. package/.next/server/app/api/repositories/sync/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/repositories/validate-path/route_client-reference-manifest.js +1 -1
  48. package/.next/server/app/api/sidebar/group-order/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/skills/[id]/route_client-reference-manifest.js +1 -1
  50. package/.next/server/app/api/skills/installations/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/skills/operations/route_client-reference-manifest.js +1 -1
  52. package/.next/server/app/api/skills/reindex/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  54. package/.next/server/app/api/slash-commands/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/tasks/[taskId]/cancel/route_client-reference-manifest.js +1 -1
  56. package/.next/server/app/api/tasks/[taskId]/route.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/todos/route.js +1 -1
  61. package/.next/server/app/api/todos/route_client-reference-manifest.js +1 -1
  62. package/.next/server/app/api/verification/runs/[runId]/route.js +1 -1
  63. package/.next/server/app/api/verification/runs/[runId]/route_client-reference-manifest.js +1 -1
  64. package/.next/server/app/api/verification/runs/route.js +1 -1
  65. package/.next/server/app/api/verification/runs/route_client-reference-manifest.js +1 -1
  66. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  67. package/.next/server/app/api/worktrees/[id]/auto-yes/route_client-reference-manifest.js +1 -1
  68. package/.next/server/app/api/worktrees/[id]/capture/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
  70. package/.next/server/app/api/worktrees/[id]/cli-tool/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route.js +1 -1
  72. package/.next/server/app/api/worktrees/[id]/cmate/schedules/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/worktrees/[id]/current-output/route_client-reference-manifest.js +1 -1
  74. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -1
  75. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route_client-reference-manifest.js +1 -1
  76. package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +1 -1
  77. package/.next/server/app/api/worktrees/[id]/execution-logs/route_client-reference-manifest.js +1 -1
  78. package/.next/server/app/api/worktrees/[id]/files/[...path]/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/worktrees/[id]/git/branch/create/route_client-reference-manifest.js +1 -1
  80. package/.next/server/app/api/worktrees/[id]/git/branch/delete/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/worktrees/[id]/git/branches/route.js +1 -1
  82. package/.next/server/app/api/worktrees/[id]/git/branches/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/worktrees/[id]/git/checkout/route_client-reference-manifest.js +1 -1
  84. package/.next/server/app/api/worktrees/[id]/git/commit/route.js +1 -1
  85. package/.next/server/app/api/worktrees/[id]/git/commit/route_client-reference-manifest.js +1 -1
  86. package/.next/server/app/api/worktrees/[id]/git/diff/route.js +1 -1
  87. package/.next/server/app/api/worktrees/[id]/git/diff/route_client-reference-manifest.js +1 -1
  88. package/.next/server/app/api/worktrees/[id]/git/fetch/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/worktrees/[id]/git/log/route.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.js +1 -1
  94. package/.next/server/app/api/worktrees/[id]/git/reset/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/worktrees/[id]/git/revert/route.js +1 -1
  96. package/.next/server/app/api/worktrees/[id]/git/revert/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route.js +1 -1
  98. package/.next/server/app/api/worktrees/[id]/git/show/[commitHash]/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/worktrees/[id]/git/stage/route_client-reference-manifest.js +1 -1
  100. package/.next/server/app/api/worktrees/[id]/git/staged/route.js +1 -1
  101. package/.next/server/app/api/worktrees/[id]/git/staged/route_client-reference-manifest.js +1 -1
  102. package/.next/server/app/api/worktrees/[id]/git/stash/[index]/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/worktrees/[id]/git/stash/apply/route_client-reference-manifest.js +1 -1
  104. package/.next/server/app/api/worktrees/[id]/git/stash/pop/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/worktrees/[id]/git/stash/push/route.js +1 -1
  106. package/.next/server/app/api/worktrees/[id]/git/stash/push/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/worktrees/[id]/git/stash/route.js +1 -1
  108. package/.next/server/app/api/worktrees/[id]/git/stash/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/worktrees/[id]/git/status/route.js +1 -1
  110. package/.next/server/app/api/worktrees/[id]/git/status/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/worktrees/[id]/git/unstage/route_client-reference-manifest.js +1 -1
  112. package/.next/server/app/api/worktrees/[id]/git/working-diff/route.js +1 -1
  113. package/.next/server/app/api/worktrees/[id]/git/working-diff/route_client-reference-manifest.js +1 -1
  114. package/.next/server/app/api/worktrees/[id]/interrupt/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/worktrees/[id]/kill-session/route_client-reference-manifest.js +1 -1
  116. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
  117. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route_client-reference-manifest.js +1 -1
  118. package/.next/server/app/api/worktrees/[id]/logs/route.js +1 -1
  119. package/.next/server/app/api/worktrees/[id]/logs/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/worktrees/[id]/marp-render/route.js +1 -1
  121. package/.next/server/app/api/worktrees/[id]/marp-render/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
  123. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route_client-reference-manifest.js +1 -1
  124. package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
  125. package/.next/server/app/api/worktrees/[id]/memos/route_client-reference-manifest.js +1 -1
  126. package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
  127. package/.next/server/app/api/worktrees/[id]/messages/route_client-reference-manifest.js +1 -1
  128. package/.next/server/app/api/worktrees/[id]/prompt-response/route_client-reference-manifest.js +1 -1
  129. package/.next/server/app/api/worktrees/[id]/respond/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/worktrees/[id]/route_client-reference-manifest.js +1 -1
  131. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +1 -1
  132. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route_client-reference-manifest.js +1 -1
  133. package/.next/server/app/api/worktrees/[id]/schedules/active/route.js +1 -1
  134. package/.next/server/app/api/worktrees/[id]/schedules/active/route_client-reference-manifest.js +1 -1
  135. package/.next/server/app/api/worktrees/[id]/schedules/route.js +1 -1
  136. package/.next/server/app/api/worktrees/[id]/schedules/route_client-reference-manifest.js +1 -1
  137. package/.next/server/app/api/worktrees/[id]/search/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  139. package/.next/server/app/api/worktrees/[id]/send/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/git-workflow/route_client-reference-manifest.js +1 -1
  141. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/install/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/plan/route_client-reference-manifest.js +1 -1
  143. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/worktrees/[id]/skills/[skillId]/uninstall-plan/route_client-reference-manifest.js +1 -1
  145. package/.next/server/app/api/worktrees/[id]/skills/route.js +1 -1
  146. package/.next/server/app/api/worktrees/[id]/skills/route_client-reference-manifest.js +1 -1
  147. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  148. package/.next/server/app/api/worktrees/[id]/slash-commands/route_client-reference-manifest.js +1 -1
  149. package/.next/server/app/api/worktrees/[id]/special-keys/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/worktrees/[id]/start-polling/route_client-reference-manifest.js +1 -1
  151. package/.next/server/app/api/worktrees/[id]/tasks/route.js +2 -2
  152. package/.next/server/app/api/worktrees/[id]/tasks/route_client-reference-manifest.js +1 -1
  153. package/.next/server/app/api/worktrees/[id]/terminal/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/[id]/timers/history/route.js +1 -1
  155. package/.next/server/app/api/worktrees/[id]/timers/history/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/api/worktrees/[id]/timers/route_client-reference-manifest.js +1 -1
  157. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route.js +1 -1
  158. package/.next/server/app/api/worktrees/[id]/todos/[todoId]/route_client-reference-manifest.js +1 -1
  159. package/.next/server/app/api/worktrees/[id]/todos/route.js +1 -1
  160. package/.next/server/app/api/worktrees/[id]/todos/route_client-reference-manifest.js +1 -1
  161. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
  162. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route_client-reference-manifest.js +1 -1
  163. package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
  164. package/.next/server/app/api/worktrees/[id]/tree/route_client-reference-manifest.js +1 -1
  165. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route_client-reference-manifest.js +1 -1
  166. package/.next/server/app/api/worktrees/[id]/verify/route.js +1 -1
  167. package/.next/server/app/api/worktrees/[id]/verify/route_client-reference-manifest.js +1 -1
  168. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route.js +1 -1
  169. package/.next/server/app/api/worktrees/[id]/verify/runs/[runId]/route_client-reference-manifest.js +1 -1
  170. package/.next/server/app/api/worktrees/[id]/verify/runs/route.js +1 -1
  171. package/.next/server/app/api/worktrees/[id]/verify/runs/route_client-reference-manifest.js +1 -1
  172. package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
  173. package/.next/server/app/api/worktrees/[id]/viewed/route_client-reference-manifest.js +1 -1
  174. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  175. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  176. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  177. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  178. package/.next/server/app/more/page_client-reference-manifest.js +1 -1
  179. package/.next/server/app/offline/page_client-reference-manifest.js +1 -1
  180. package/.next/server/app/page_client-reference-manifest.js +1 -1
  181. package/.next/server/app/proxy/[...path]/route_client-reference-manifest.js +1 -1
  182. package/.next/server/app/repositories/page_client-reference-manifest.js +1 -1
  183. package/.next/server/app/review/page_client-reference-manifest.js +1 -1
  184. package/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
  185. package/.next/server/app/skills/[skillId]/page_client-reference-manifest.js +1 -1
  186. package/.next/server/app/skills/installed/page_client-reference-manifest.js +1 -1
  187. package/.next/server/app/skills/page_client-reference-manifest.js +1 -1
  188. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  189. package/.next/server/app/worktrees/[id]/page.js +1 -1
  190. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  191. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  192. package/.next/server/app-paths-manifest.json +23 -23
  193. package/.next/server/chunks/1117.js +1 -1
  194. package/.next/server/chunks/3042.js +1 -1
  195. package/.next/server/chunks/3753.js +19 -23
  196. package/.next/server/chunks/5959.js +3 -3
  197. package/.next/server/chunks/6130.js +1 -1
  198. package/.next/server/chunks/6274.js +5 -4
  199. package/.next/server/chunks/6283.js +1 -1
  200. package/.next/server/chunks/8681.js +1 -1
  201. package/.next/server/chunks/8807.js +492 -487
  202. package/.next/server/chunks/8856.js +1 -1
  203. package/.next/server/middleware-manifest.json +5 -5
  204. package/.next/server/pages/500.html +1 -1
  205. package/.next/server/server-reference-manifest.json +1 -1
  206. package/.next/static/chunks/{6488-109e3825c7249e47.js → 6488-2c412c29258637b6.js} +1 -1
  207. package/.next/static/chunks/app/worktrees/[id]/{page-12b3c5f32fc6094e.js → page-f6e09d25cd3b56f9.js} +1 -1
  208. package/.next/trace +12 -12
  209. package/dist/cli/commands/auto-yes.d.ts.map +1 -1
  210. package/dist/cli/commands/auto-yes.js +9 -2
  211. package/dist/cli/commands/capture.d.ts +1 -0
  212. package/dist/cli/commands/capture.d.ts.map +1 -1
  213. package/dist/cli/commands/capture.js +112 -2
  214. package/dist/cli/commands/instances.d.ts +24 -0
  215. package/dist/cli/commands/instances.d.ts.map +1 -1
  216. package/dist/cli/commands/instances.js +44 -0
  217. package/dist/cli/commands/respond.d.ts.map +1 -1
  218. package/dist/cli/commands/respond.js +10 -2
  219. package/dist/cli/commands/send.d.ts.map +1 -1
  220. package/dist/cli/commands/send.js +20 -11
  221. package/dist/cli/commands/wait.d.ts +4 -1
  222. package/dist/cli/commands/wait.d.ts.map +1 -1
  223. package/dist/cli/commands/wait.js +80 -6
  224. package/dist/cli/types/index.d.ts +16 -0
  225. package/dist/cli/types/index.d.ts.map +1 -1
  226. package/dist/cli/utils/pager.d.ts +28 -0
  227. package/dist/cli/utils/pager.d.ts.map +1 -0
  228. package/dist/cli/utils/pager.js +57 -0
  229. package/dist/lib/detection/ansi.d.ts +30 -0
  230. package/dist/lib/detection/ansi.d.ts.map +1 -0
  231. package/dist/lib/detection/ansi.js +39 -0
  232. package/dist/lib/tmux/transcript-squeeze.d.ts +76 -0
  233. package/dist/lib/tmux/transcript-squeeze.d.ts.map +1 -0
  234. package/dist/lib/tmux/transcript-squeeze.js +96 -0
  235. package/dist/server/server.js +18 -0
  236. package/dist/server/src/config/tmux-pane-config.js +33 -1
  237. package/dist/server/src/lib/cli-tools/antigravity.js +2 -1
  238. package/dist/server/src/lib/cli-tools/codex.js +3 -2
  239. package/dist/server/src/lib/cli-tools/copilot.js +2 -2
  240. package/dist/server/src/lib/cli-tools/gemini.js +2 -2
  241. package/dist/server/src/lib/cli-tools/opencode.js +2 -2
  242. package/dist/server/src/lib/cli-tools/vibe-local.js +2 -2
  243. package/dist/server/src/lib/db/agent-instances-db.js +53 -0
  244. package/dist/server/src/lib/db/migrations/index.js +2 -0
  245. package/dist/server/src/lib/db/migrations/runner.js +1 -1
  246. package/dist/server/src/lib/db/migrations/v52-orphaned-worktree-children.js +76 -0
  247. package/dist/server/src/lib/db/migrations/worktree-child-tables.js +119 -0
  248. package/dist/server/src/lib/db/worktree-db.js +65 -42
  249. package/dist/server/src/lib/detection/cli-patterns.js +24 -1
  250. package/dist/server/src/lib/detection/status-detector.js +52 -2
  251. package/dist/server/src/lib/session/claude-session.js +4 -2
  252. package/dist/server/src/lib/tmux/read-mode-pager.js +161 -0
  253. package/dist/server/src/lib/tmux/read-mode.js +327 -0
  254. package/dist/server/src/lib/tmux/tmux.js +43 -5
  255. package/package.json +1 -1
  256. /package/.next/static/{-BSmW0Ae2YsxyCl_ue7_d → -E7I667pJDTBamyG8M_Z-}/_buildManifest.js +0 -0
  257. /package/.next/static/{-BSmW0Ae2YsxyCl_ue7_d → -E7I667pJDTBamyG8M_Z-}/_ssgManifest.js +0 -0
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * Migration v52: delete child rows pointing at a worktree that no longer exists
4
+ * (Issue #1621, Phase 0).
5
+ *
6
+ * `getWorktreeChildTables` used to enumerate child tables from foreign-key
7
+ * metadata alone, so `tasks` (#1548), `verification_runs` (#1544) and
8
+ * `skill_operations` (#1428) — each holding a `worktree_id` with no constraint
9
+ * declared — were invisible to both the rename path and the delete path. No
10
+ * CASCADE fires without a foreign key, so every worktree deleted since those
11
+ * tables landed left their rows behind, and every worktree ID rename left them
12
+ * addressing an ID nothing resolves. The code fix stops new orphans; this
13
+ * migration removes the ones already written.
14
+ *
15
+ * The sweep reuses the shared enumeration rather than naming tables, so the
16
+ * FK-less tables above and the FK-declared ones are treated identically — an
17
+ * orphan in a CASCADE table is equally unreachable, it just needed a
18
+ * `foreign_keys = OFF` window to be created.
19
+ *
20
+ * Three deliberate narrowings:
21
+ *
22
+ * - Append-only ledgers are excluded (`getDeletableWorktreeChildTables`).
23
+ * `skill_operations` holds a `worktree_id` and does have rows referencing
24
+ * worktrees that are gone, but it declares `BEFORE DELETE ... RAISE(ABORT)`
25
+ * (#1234) and is read across every worktree by the audit feed. Deleting from
26
+ * it aborts the statement, which here would roll back the migration and take
27
+ * startup with it (measured). Its rows are history, not orphans.
28
+ * - `worktree_id IS NULL` rows are left alone. Every current child table
29
+ * declares the column NOT NULL, so this cannot fire today; if a future table
30
+ * makes it nullable, a NULL means "not attached", not "attached to something
31
+ * gone", and deleting it would be data loss rather than cleanup.
32
+ * - `verification_gate_results` has no `worktree_id` and is not swept directly.
33
+ * It reaches its worktree through `verification_runs(id) ON DELETE CASCADE`,
34
+ * so deleting the orphaned runs takes its rows with them (better-sqlite3
35
+ * enables `foreign_keys` by default, and db-instance.ts sets it explicitly).
36
+ * `task_events` is *not* in the same position: v51 declares no foreign key to
37
+ * `tasks` on purpose, because history that vanishes with its subject cannot
38
+ * be audited afterwards. That is a design choice, not an orphan, so it stays.
39
+ */
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.v52_migrations = void 0;
42
+ const worktree_child_tables_1 = require("./worktree-child-tables");
43
+ exports.v52_migrations = [
44
+ {
45
+ version: 52,
46
+ name: 'delete-orphaned-worktree-children',
47
+ up: (db) => {
48
+ let deleted = 0;
49
+ for (const { table, column } of (0, worktree_child_tables_1.getDeletableWorktreeChildTables)(db)) {
50
+ // Identifiers come from sqlite_master / PRAGMA, never from user input.
51
+ // The alias is `cm_parent` rather than something short like `w` so it
52
+ // cannot collide with a child table that happens to be named the same.
53
+ const result = db
54
+ .prepare(`DELETE FROM "${table}"
55
+ WHERE "${column}" IS NOT NULL
56
+ AND NOT EXISTS (
57
+ SELECT 1 FROM worktrees AS cm_parent
58
+ WHERE cm_parent.id = "${table}"."${column}"
59
+ )`)
60
+ .run();
61
+ if (result.changes > 0) {
62
+ console.log(`Deleted ${result.changes} orphaned row(s) from ${table}.${column}`);
63
+ deleted += result.changes;
64
+ }
65
+ }
66
+ if (deleted === 0) {
67
+ console.log('No orphaned worktree child rows found');
68
+ }
69
+ },
70
+ down: () => {
71
+ // Deleted rows cannot be resurrected. Defined anyway so rollback tooling
72
+ // (and the tests that rebuild a v51 database) keep working.
73
+ console.log('No rollback for orphaned child row cleanup (rows are gone)');
74
+ },
75
+ },
76
+ ];
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ /**
3
+ * Live-schema introspection for "which tables reference a worktree".
4
+ *
5
+ * Lives under `migrations/` rather than next to `worktree-db.ts` because the
6
+ * dependency has to point this way: migration v52 sweeps orphans with the same
7
+ * enumeration the runtime uses, and a migration that imports `worktree-db.ts`
8
+ * inherits every mock of it — `vi.mock('@/lib/db/worktree-db')` in an unrelated
9
+ * suite made `runMigrations` throw `No "getDeletableWorktreeChildTables" export
10
+ * is defined` (measured across 5 tests). Schema introspection is the more
11
+ * foundational of the two, so it is what the other side imports.
12
+ *
13
+ * Issue #1621.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.getWorktreeChildTables = getWorktreeChildTables;
17
+ exports.getWriteGuardedTables = getWriteGuardedTables;
18
+ exports.getDeletableWorktreeChildTables = getDeletableWorktreeChildTables;
19
+ /** Conventional name of the column holding a worktree reference. */
20
+ const WORKTREE_REFERENCE_COLUMN = 'worktree_id';
21
+ /**
22
+ * Discover every table that references a worktree, along with the referencing
23
+ * column. Derived dynamically from the live schema so it never drifts out of
24
+ * sync with future migrations that add child tables.
25
+ *
26
+ * Two sources are unioned:
27
+ *
28
+ * 1. a `worktree_id` column, **whether or not** the table declares a foreign key
29
+ * 2. a declared foreign key to `worktrees(id)`, whatever the column is named
30
+ *
31
+ * Source 1 is what Issue #1621 adds. FK metadata alone (all this did before)
32
+ * silently skipped `tasks` (#1548), `verification_runs` (#1544) and
33
+ * `skill_operations` (#1428): all three carry a `worktree_id` but declare no
34
+ * constraint, so a rename left them pointing at an ID that no longer exists and
35
+ * a delete left them behind as orphans — no CASCADE fires without a FK.
36
+ *
37
+ * Source 2 is kept so a future table whose FK column is *not* named
38
+ * `worktree_id` keeps being followed; dropping it would be a regression for
39
+ * every table #1151 already handled.
40
+ *
41
+ * Issue #1151, #1621.
42
+ */
43
+ function getWorktreeChildTables(db) {
44
+ const tables = db
45
+ .prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
46
+ .all();
47
+ const children = [];
48
+ const seen = new Set();
49
+ const add = (table, column) => {
50
+ // JSON encoding keeps the pair unambiguous whatever the identifiers contain.
51
+ const key = JSON.stringify([table, column]);
52
+ if (seen.has(key))
53
+ return;
54
+ seen.add(key);
55
+ children.push({ table, column });
56
+ };
57
+ for (const { name: tableName } of tables) {
58
+ if (tableName === 'worktrees')
59
+ continue;
60
+ // PRAGMA identifiers cannot be bound; `tableName` comes from sqlite_master,
61
+ // not user input, so string interpolation here is safe.
62
+ const columns = db.prepare(`PRAGMA table_info("${tableName}")`).all();
63
+ if (columns.some((column) => column.name === WORKTREE_REFERENCE_COLUMN)) {
64
+ add(tableName, WORKTREE_REFERENCE_COLUMN);
65
+ }
66
+ const fks = db.prepare(`PRAGMA foreign_key_list("${tableName}")`).all();
67
+ for (const fk of fks) {
68
+ if (fk.table === 'worktrees' && (fk.to === 'id' || fk.to === null)) {
69
+ add(tableName, fk.from);
70
+ }
71
+ }
72
+ }
73
+ return children;
74
+ }
75
+ /**
76
+ * Tables the schema itself refuses to let us rewrite, per statement kind.
77
+ *
78
+ * An append-only ledger enforces itself with a `BEFORE UPDATE` / `BEFORE DELETE`
79
+ * trigger that calls `RAISE(ABORT, ...)`; `skill_operations` declares both
80
+ * (#1234, migrations v44/v48). Such a table carries a `worktree_id` yet is
81
+ * deliberately *not* owned by its worktree's lifetime: `querySkillOperationAudit`
82
+ * reads it across every worktree for the dashboard feed, exactly like v51 keeps
83
+ * `task_events` alive after its task is gone. Sweeping it is not cleanup, it is
84
+ * destroying audit history — and it does not even fail safely: the trigger
85
+ * aborts the statement, which rolls back the whole enclosing transaction, so a
86
+ * blind sweep breaks worktree deletion outright (measured: `deleteWorktreesByIds`
87
+ * threw `skill_operations is append-only` and the worktree row survived; the
88
+ * same sweep inside migration v52 fails startup).
89
+ *
90
+ * Detected from trigger SQL rather than by table name so a future ledger is
91
+ * protected the moment it declares its guard (Issue #1621).
92
+ */
93
+ function getWriteGuardedTables(db) {
94
+ const triggers = db
95
+ .prepare("SELECT tbl_name, sql FROM sqlite_master WHERE type = 'trigger'")
96
+ .all();
97
+ const noUpdate = new Set();
98
+ const noDelete = new Set();
99
+ for (const trigger of triggers) {
100
+ if (!trigger.sql)
101
+ continue;
102
+ if (!/\bRAISE\s*\(\s*ABORT/i.test(trigger.sql))
103
+ continue;
104
+ if (/\bBEFORE\s+UPDATE\b/i.test(trigger.sql))
105
+ noUpdate.add(trigger.tbl_name);
106
+ if (/\bBEFORE\s+DELETE\b/i.test(trigger.sql))
107
+ noDelete.add(trigger.tbl_name);
108
+ }
109
+ return { noUpdate, noDelete };
110
+ }
111
+ /**
112
+ * Child tables whose rows a worktree actually owns — everything
113
+ * getWorktreeChildTables reports, minus the append-only ledgers that outlive it.
114
+ * This is the set a delete or an orphan sweep may touch (Issue #1621).
115
+ */
116
+ function getDeletableWorktreeChildTables(db) {
117
+ const { noDelete } = getWriteGuardedTables(db);
118
+ return getWorktreeChildTables(db).filter(({ table }) => !noDelete.has(table));
119
+ }
@@ -28,6 +28,7 @@ exports.deleteRepositoryWorktrees = deleteRepositoryWorktrees;
28
28
  exports.deleteWorktreesByIds = deleteWorktreesByIds;
29
29
  const selected_agents_validator_1 = require("../../lib/selected-agents-validator");
30
30
  const chat_db_1 = require("./chat-db");
31
+ const worktree_child_tables_1 = require("./migrations/worktree-child-tables");
31
32
  /**
32
33
  * Get latest user message per CLI tool for multiple worktrees (batch query)
33
34
  * Optimized to avoid N+1 query problem
@@ -269,30 +270,24 @@ function upsertWorktree(db, worktree) {
269
270
  run();
270
271
  }
271
272
  /**
272
- * Discover every table that has a foreign key referencing `worktrees(id)`,
273
- * along with the referencing column. Derived dynamically from the live schema
274
- * so it never drifts out of sync with future migrations that add child tables.
273
+ * Delete every child row pointing at any of `worktreeIds`.
275
274
  *
276
- * Issue #1151.
275
+ * Deliberately covers *all* deletable child tables, not just the FK-less ones:
276
+ * doing so makes deletion correct even when `PRAGMA foreign_keys` is off, and
277
+ * the extra statements against CASCADE tables remove rows that would have been
278
+ * removed anyway. Append-only ledgers are skipped (see getWriteGuardedTables).
279
+ * Must run before the parent row is deleted (Issue #1621).
277
280
  */
278
- function getWorktreeChildTables(db) {
279
- const tables = db
280
- .prepare("SELECT name FROM sqlite_master WHERE type = 'table'")
281
- .all();
282
- const children = [];
283
- for (const { name } of tables) {
284
- if (name === 'worktrees')
281
+ function deleteWorktreeChildRows(db, worktreeIds, children = (0, worktree_child_tables_1.getWorktreeChildTables)(db)) {
282
+ if (worktreeIds.length === 0)
283
+ return;
284
+ const { noDelete } = (0, worktree_child_tables_1.getWriteGuardedTables)(db);
285
+ const placeholders = worktreeIds.map(() => '?').join(',');
286
+ for (const { table, column } of children) {
287
+ if (noDelete.has(table))
285
288
  continue;
286
- // PRAGMA identifiers cannot be bound; `name` comes from sqlite_master, not
287
- // user input, so string interpolation here is safe.
288
- const fks = db.prepare(`PRAGMA foreign_key_list("${name}")`).all();
289
- for (const fk of fks) {
290
- if (fk.table === 'worktrees' && (fk.to === 'id' || fk.to === null)) {
291
- children.push({ table: name, column: fk.from });
292
- }
293
- }
289
+ db.prepare(`DELETE FROM "${table}" WHERE "${column}" IN (${placeholders})`).run(...worktreeIds);
294
290
  }
295
- return children;
296
291
  }
297
292
  /**
298
293
  * Rename a worktree row's primary key from `oldId` to `newId`, re-pointing every
@@ -300,6 +295,18 @@ function getWorktreeChildTables(db) {
300
295
  * same-directory branch switch preserve chat history and all related data
301
296
  * instead of destroying it (Issue #1151).
302
297
  *
298
+ * "Every child table" means every table carrying a worktree reference, not just
299
+ * the ones that declare a foreign key: `tasks`, `verification_runs` and
300
+ * `skill_operations` do not, and used to be left addressing an ID that no
301
+ * longer resolves (Issue #1621).
302
+ *
303
+ * Append-only ledgers are the one exception, and the schema states it itself —
304
+ * `skill_operations` aborts any UPDATE (#1234). An immutable audit row records
305
+ * what happened under the identity in force at the time, and rewriting it is
306
+ * precisely what its trigger exists to prevent; attempting it does not fail
307
+ * quietly either, it aborts the enclosing transaction and takes the rename with
308
+ * it (measured). See getWriteGuardedTables.
309
+ *
303
310
  * Must be called inside a transaction. `PRAGMA defer_foreign_keys` defers FK
304
311
  * enforcement to COMMIT, letting us repoint the parent PK and its children in
305
312
  * any order without needing `ON UPDATE CASCADE` on every constraint.
@@ -314,15 +321,20 @@ function migrateWorktreeIdPreservingChildren(db, oldId, newId) {
314
321
  // independently. Resets automatically at the end of the transaction.
315
322
  db.pragma('defer_foreign_keys = ON');
316
323
  const newExists = db.prepare('SELECT 1 FROM worktrees WHERE id = ?').get(newId);
317
- const children = getWorktreeChildTables(db);
324
+ const { noUpdate } = (0, worktree_child_tables_1.getWriteGuardedTables)(db);
325
+ const allChildren = (0, worktree_child_tables_1.getWorktreeChildTables)(db);
326
+ const children = allChildren.filter(({ table }) => !noUpdate.has(table));
318
327
  if (newExists) {
319
328
  // Collision guard. `path` is UNIQUE so two rows for the same directory
320
329
  // should never coexist, but if the DB is already inconsistent keep the
321
- // destination row's data and fold in the source's non-conflicting children;
322
- // conflicting leftovers are removed by CASCADE when the old row is deleted.
330
+ // destination row's data and fold in the source's non-conflicting children.
323
331
  for (const { table, column } of children) {
324
332
  db.prepare(`UPDATE OR IGNORE "${table}" SET "${column}" = ? WHERE "${column}" = ?`).run(newId, oldId);
325
333
  }
334
+ // Conflicting leftovers are dropped explicitly rather than left to CASCADE:
335
+ // tables without a foreign key never cascade, so relying on the parent
336
+ // delete alone would strand their rows on a dead ID (#1621).
337
+ deleteWorktreeChildRows(db, [oldId], allChildren);
326
338
  db.prepare('DELETE FROM worktrees WHERE id = ?').run(oldId);
327
339
  return;
328
340
  }
@@ -523,29 +535,38 @@ function getWorktreesByRepository(db, repositoryPath) {
523
535
  }
524
536
  /**
525
537
  * Delete all worktrees for a given repository path
526
- * Related data is automatically deleted via ON DELETE CASCADE in every table
527
- * holding a foreign key to worktrees(id) chat_messages, session_states,
528
- * worktree_memos, worktree_todos, agent_instances, scheduled_executions,
529
- * execution_logs, timer_messages and skill_installations (#1430). The live list
530
- * is whatever `PRAGMA foreign_key_list` reports; see getWorktreeChildTables.
538
+ * Child rows are deleted explicitly before the parent, for every table
539
+ * getWorktreeChildTables reports. ON DELETE CASCADE covers only the tables that
540
+ * declare a foreign key; `tasks`, `verification_runs` and `skill_operations`
541
+ * hold a `worktree_id` with no constraint and would otherwise survive their
542
+ * worktree as orphans (#1621). Deleting `verification_runs` still cascades to
543
+ * `verification_gate_results`, which does declare its foreign key.
531
544
  *
532
545
  * @param db - Database instance
533
546
  * @param repositoryPath - Path of the repository to delete
534
547
  * @returns Object containing the count of deleted worktrees
535
548
  */
536
549
  function deleteRepositoryWorktrees(db, repositoryPath) {
537
- const stmt = db.prepare(`
538
- DELETE FROM worktrees WHERE repository_path = ?
539
- `);
540
- const result = stmt.run(repositoryPath);
541
- return { deletedCount: result.changes };
550
+ const run = db.transaction(() => {
551
+ const ids = db
552
+ .prepare('SELECT id FROM worktrees WHERE repository_path = ?')
553
+ .all(repositoryPath)
554
+ .map((row) => row.id)
555
+ .filter((id) => id !== null);
556
+ deleteWorktreeChildRows(db, ids);
557
+ return db
558
+ .prepare('DELETE FROM worktrees WHERE repository_path = ?')
559
+ .run(repositoryPath).changes;
560
+ });
561
+ return { deletedCount: run() };
542
562
  }
543
563
  /**
544
564
  * Delete worktrees by their IDs
545
- * Related data is automatically deleted via ON DELETE CASCADE in every table
546
- * holding a foreign key to worktrees(id) — including skill_installations, whose
547
- * rows used to survive the worktree and make a re-created worktree at the same
548
- * path un-installable (#1430). See getWorktreeChildTables for the live list.
565
+ * Child rows are deleted explicitly before the parent, for every table
566
+ * getWorktreeChildTables reports — including skill_installations, whose rows
567
+ * used to survive the worktree and make a re-created worktree at the same path
568
+ * un-installable (#1430), and the constraint-less `tasks` / `verification_runs`
569
+ * / `skill_operations`, which no CASCADE can reach (#1621).
549
570
  *
550
571
  * @param db - Database instance
551
572
  * @param worktreeIds - Array of worktree IDs to delete
@@ -556,9 +577,11 @@ function deleteWorktreesByIds(db, worktreeIds) {
556
577
  return { deletedCount: 0 };
557
578
  }
558
579
  const placeholders = worktreeIds.map(() => '?').join(',');
559
- const stmt = db.prepare(`
560
- DELETE FROM worktrees WHERE id IN (${placeholders})
561
- `);
562
- const result = stmt.run(...worktreeIds);
563
- return { deletedCount: result.changes };
580
+ const run = db.transaction(() => {
581
+ deleteWorktreeChildRows(db, worktreeIds);
582
+ return db
583
+ .prepare(`DELETE FROM worktrees WHERE id IN (${placeholders})`)
584
+ .run(...worktreeIds).changes;
585
+ });
586
+ return { deletedCount: run() };
564
587
  }
@@ -4,7 +4,7 @@
4
4
  * Shared between response-poller.ts and API routes
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.extractAnsiSequences = exports.stripAnsi = exports.ANTIGRAVITY_SKIP_PATTERNS = 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_SELECTION_LIST_PATTERN = exports.COPILOT_SEPARATOR_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_PROCESSING_INDICATOR = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = 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_SELECTION_LIST_PATTERN = exports.CODEX_SEPARATOR_PATTERN = exports.CODEX_DIALOG_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
7
+ exports.CLAUDE_SESSION_ERROR_REGEX_PATTERNS = exports.CLAUDE_SESSION_ERROR_PATTERNS = exports.extractAnsiSequences = exports.stripAnsi = exports.ANTIGRAVITY_SKIP_PATTERNS = 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_SELECTION_LIST_PATTERN = exports.COPILOT_SEPARATOR_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_PROCESSING_INDICATOR = exports.OPENCODE_RESPONSE_COMPLETE = exports.OPENCODE_LOADING_PATTERN = exports.OPENCODE_THINKING_PATTERN = exports.OPENCODE_PROMPT_AFTER_RESPONSE = 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_DIALOG_PATTERN = exports.CODEX_PROMPT_PATTERN = exports.CLAUDE_TRUST_DIALOG_PATTERN = exports.CLAUDE_SEPARATOR_PATTERN = exports.CLAUDE_PROMPT_PATTERN = exports.CODEX_THINKING_PATTERN = exports.CLAUDE_INTERRUPT_HINT_PATTERN = exports.CLAUDE_THINKING_PATTERN = exports.CLAUDE_SPINNER_CHARS = void 0;
8
8
  exports.findClaudeChromeStart = findClaudeChromeStart;
9
9
  exports.isCodexPromptReady = isCodexPromptReady;
10
10
  exports.getCodexActiveDialog = getCodexActiveDialog;
@@ -295,6 +295,29 @@ exports.CODEX_SEPARATOR_PATTERN = /^─.*Worked for.*─+$/m;
295
295
  * a numbered prompt (PromptPanel with buttons).
296
296
  */
297
297
  exports.CODEX_SELECTION_LIST_PATTERN = /press\s+enter\s+to\s+(?:confirm|select)/i;
298
+ /**
299
+ * Codex CLI approval-request footer pattern (Issue #1628).
300
+ *
301
+ * Codex renders an approval request ("Would you like to run the following
302
+ * command?" / "Would you like to make the following edits?") with the SAME
303
+ * "Press enter to confirm" footer as a `/model`-style menu, which is why
304
+ * CODEX_SELECTION_LIST_PATTERN swallows it. The two differ in the escape verb:
305
+ * an approval request can be *cancelled* (it is the agent asking the human for
306
+ * permission), a menu can only be *gone back* from.
307
+ *
308
+ * Measured on codex-cli 0.146.0 (five consecutive live approval frames captured
309
+ * from a real session, plus two live `/model` picker frames):
310
+ * - approval : "Press enter to confirm or esc to cancel"
311
+ * - /model : "Press enter to confirm or esc to go back"
312
+ * - /model : "Press enter to select reasoning effort, or esc to dismiss."
313
+ *
314
+ * Used only as one of two OR'd approval signals (see isCodexApprovalRequest in
315
+ * status-detector.ts); the other is an interrogative question line, so a future
316
+ * rewording of either signal alone does not reopen Issue #1628.
317
+ *
318
+ * No /g flag (keeps .test() stateless), no nested quantifiers (ReDoS-safe).
319
+ */
320
+ exports.CODEX_APPROVAL_FOOTER_PATTERN = /esc\s+to\s+cancel/i;
298
321
  /**
299
322
  * Codex CLI pager / edit-previous (transcript) mode footer pattern (Issue #1017)
300
323
  *
@@ -100,7 +100,8 @@ const CODEX_NUMBERED_OPTION_PATTERN = /^\s*[❯›●]?\s*\d{1,2}[.)]\s/;
100
100
  * already-answered prompt left in the 50-line scan window rather than an active one.
101
101
  *
102
102
  * Codex keeps the answered "1. Yes / 2. No" block + "press number to confirm" footer in
103
- * its transcript (session historyLimit 50000) instead of repainting it away like Claude,
103
+ * its transcript (it draws on the normal screen, so tmux retains a scrollback for it —
104
+ * TMUX_HISTORY_LIMIT lines deep) instead of repainting it away like Claude,
104
105
  * so detectPrompt() keeps matching it and detectSessionStatus() reports `waiting` even
105
106
  * after the user answered and Codex resumed — the sidebar status dot then stays orange
106
107
  * forever (the reported bug).
@@ -147,6 +148,39 @@ function isCodexStalePrompt(contentLines) {
147
148
  }
148
149
  return lastPromptIdx >= 0 && lastThinkingIdx > lastPromptIdx;
149
150
  }
151
+ /**
152
+ * Issue #1628: decide whether a Codex frame that matched CODEX_SELECTION_LIST_PATTERN
153
+ * is the agent ASKING FOR APPROVAL rather than the user browsing a menu.
154
+ *
155
+ * Why this exists: Codex renders both with a "Press enter to confirm" footer, so the
156
+ * priority-0.8 selection-list branch (added for `/model` in Issue #622) also swallowed
157
+ * every approval request and returned `hasActivePrompt: false`. `isPromptWaiting` is the
158
+ * only blocked-on-a-human signal the current-output payload carries, so `commandmate wait
159
+ * --on-prompt agent` could never raise exit 10 for a Codex worker sitting on
160
+ * "Would you like to run the following command?" — it polled until the timeout while the
161
+ * agent was stopped. Auto-Yes was unaffected because it calls detectPrompt() directly,
162
+ * which parses these frames correctly; only the status-detector layer lost them.
163
+ *
164
+ * Two OR'd signals, both measured against live codex-cli 0.146.0 captures
165
+ * (5 approval frames from one real session + 2 `/model` picker frames):
166
+ * 1. an interrogative question line directly above the options
167
+ * ("Would you like to run the following command?" → the extracted question ends
168
+ * with "?"; a picker's ends with its description, e.g. "…browse all models.")
169
+ * 2. the approval escape verb ("esc to cancel" vs a menu's "esc to go back" /
170
+ * "esc to dismiss")
171
+ * Either alone covers every measured approval frame, so a rewording of one does not
172
+ * reopen the bug.
173
+ *
174
+ * Gated on `promptDetection.isPrompt` so a frame detectPrompt() could not parse into
175
+ * options (e.g. the unnumbered "Select a model" list of Issue #619) can never be
176
+ * promoted to an active prompt.
177
+ */
178
+ function isCodexApprovalRequest(promptDetection, selectionWindow) {
179
+ if (!promptDetection.isPrompt)
180
+ return false;
181
+ const question = promptDetection.promptData?.question?.trim() ?? '';
182
+ return question.endsWith('?') || cli_patterns_1.CODEX_APPROVAL_FOOTER_PATTERN.test(selectionWindow);
183
+ }
150
184
  /**
151
185
  * Detect session status with confidence level
152
186
  *
@@ -286,6 +320,21 @@ function detectSessionStatus(output, cliToolId, lastOutputTimestamp) {
286
320
  if (cli_patterns_1.CODEX_SELECTION_LIST_PATTERN.test(codexSelectionWindow)) {
287
321
  const codexPromptOptions = (0, cli_patterns_1.buildDetectPromptOptions)(cliToolId);
288
322
  const codexPromptDetection = (0, prompt_detector_1.detectPrompt)((0, cli_patterns_1.stripBoxDrawing)(cleanOutput), codexPromptOptions);
323
+ // Issue #1628: an approval request wears the same footer as a menu but is the
324
+ // agent blocked on the human, so it must surface as an active prompt (exit 10
325
+ // for `wait`, PromptPanel in the UI) instead of a navigable list. The #1160
326
+ // staleness guard still applies: an ALREADY-ANSWERED approval whose footer is
327
+ // still inside the window, with Codex running below it, is dead scrollback.
328
+ if (isCodexApprovalRequest(codexPromptDetection, codexSelectionWindow) &&
329
+ !isCodexStalePrompt(contentLines)) {
330
+ return {
331
+ status: 'waiting',
332
+ confidence: 'high',
333
+ reason: exports.STATUS_REASON.PROMPT_DETECTED,
334
+ hasActivePrompt: true,
335
+ promptDetection: codexPromptDetection,
336
+ };
337
+ }
289
338
  return {
290
339
  status: 'waiting',
291
340
  confidence: 'high',
@@ -333,7 +382,8 @@ function detectSessionStatus(output, cliToolId, lastOutputTimestamp) {
333
382
  if (promptDetection.isPrompt) {
334
383
  // Issue #1160: Codex keeps an ANSWERED approval / numbered-choice block
335
384
  // ("1. Yes / 2. No" + "press number to confirm" footer) in its transcript
336
- // (historyLimit 50000) instead of repainting it away like Claude. detectPrompt's
385
+ // (non-alternate-screen, so tmux keeps TMUX_HISTORY_LIMIT lines of scrollback for
386
+ // it) instead of repainting it away like Claude. detectPrompt's
337
387
  // 50-line window then keeps matching that dead prompt, so this priority-1 branch
338
388
  // returns `waiting` even after the user answered and Codex resumed — the sidebar
339
389
  // status dot stays orange forever (the reported bug). Guard with a position check:
@@ -502,11 +502,13 @@ async function startClaudeSession(options) {
502
502
  // (SF-S2-004: Explicit fall-through instead of hidden re-entry)
503
503
  }
504
504
  try {
505
- // Create tmux session with large history buffer for Claude output
505
+ // Create tmux session. Scrollback depth comes from the shared
506
+ // TMUX_HISTORY_LIMIT default (Issue #1624) — do not re-hardcode it here.
507
+ // (Claude itself renders in the alternate screen and keeps history_size at 0,
508
+ // so the limit is inert for this tool; it still applies to the bare shell.)
506
509
  await (0, tmux_1.createSession)({
507
510
  sessionName,
508
511
  workingDirectory: worktreePath,
509
- historyLimit: 50000,
510
512
  });
511
513
  // SF-S2-003: Sanitize environment after createSession, before launching Claude CLI
512
514
  await (0, session_key_sender_1.sanitizeSessionEnvironment)(sessionName);