spec-cat 0.1.62 → 0.1.64

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 (272) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/BOa_3MnO.js +1 -0
  3. package/.output/public/_nuxt/{FfyCVKRU.js → Be7Q-KZm.js} +1 -1
  4. package/.output/public/_nuxt/{DPOGSCeV.js → C0xGK0_5.js} +1 -1
  5. package/.output/public/_nuxt/{Su-Cl_JF.js → C5uvKiQJ.js} +1 -1
  6. package/.output/public/_nuxt/{DIOhMCOf.js → CJOi1pKi.js} +3 -3
  7. package/.output/public/_nuxt/{D4LyIv_t.js → Cqzfu4b1.js} +1 -1
  8. package/.output/public/_nuxt/CrxPmt5A.js +1 -0
  9. package/.output/public/_nuxt/D0ZHasco.js +105 -0
  10. package/.output/public/_nuxt/DIUADCOG.js +1 -0
  11. package/.output/public/_nuxt/DaYnMifu.js +43 -0
  12. package/.output/public/_nuxt/DhK8N2yk.js +58 -0
  13. package/.output/public/_nuxt/{Bz2rU2zs.js → DntGb46R.js} +1 -1
  14. package/.output/public/_nuxt/{DwpuOtAP.js → FCWUBLV4.js} +1 -1
  15. package/.output/public/_nuxt/builds/latest.json +1 -1
  16. package/.output/public/_nuxt/builds/meta/39e9ce1e-ba3f-4066-8a58-66dfecfe2817.json +1 -0
  17. package/.output/public/_nuxt/builds/meta/dev.json +1 -0
  18. package/.output/public/_nuxt/default.DKcvUy2t.css +1 -0
  19. package/.output/public/_nuxt/entry.uBSFaL6A.css +1 -0
  20. package/.output/public/_nuxt/{useTheme.Cny4Z64J.css → useTheme.B4wINCoW.css} +1 -1
  21. package/.output/server/chunks/_/aiProviderRegistry.mjs +1 -2
  22. package/.output/server/chunks/_/aiProviderRegistry.mjs.map +1 -1
  23. package/.output/server/chunks/_/aiProviderSelection.mjs +2 -2
  24. package/.output/server/chunks/_/chatGit.mjs +5 -3
  25. package/.output/server/chunks/_/chatGit.mjs.map +1 -1
  26. package/.output/server/chunks/_/claudeProvider.mjs +1 -215
  27. package/.output/server/chunks/_/claudeProvider.mjs.map +1 -1
  28. package/.output/server/chunks/_/claudeService.mjs +50 -54
  29. package/.output/server/chunks/_/claudeService.mjs.map +1 -1
  30. package/.output/server/chunks/_/codexProvider.mjs +47 -812
  31. package/.output/server/chunks/_/codexProvider.mjs.map +1 -1
  32. package/.output/server/chunks/_/git.mjs +1 -2
  33. package/.output/server/chunks/_/git.mjs.map +1 -1
  34. package/.output/server/chunks/_/gitApiHelpers.mjs +1 -1
  35. package/.output/server/chunks/_/gitBranchOperations.mjs +1 -1
  36. package/.output/server/chunks/_/interactiveProviderQuery.mjs +160 -0
  37. package/.output/server/chunks/_/interactiveProviderQuery.mjs.map +1 -0
  38. package/.output/server/chunks/_/jobPersister.mjs +37 -6
  39. package/.output/server/chunks/_/jobPersister.mjs.map +1 -1
  40. package/.output/server/chunks/_/jobQueue.mjs +430 -278
  41. package/.output/server/chunks/_/jobQueue.mjs.map +1 -1
  42. package/.output/server/chunks/_/terminalSessions.mjs +582 -0
  43. package/.output/server/chunks/_/terminalSessions.mjs.map +1 -0
  44. package/.output/server/chunks/_/worktreeSetup.mjs +98 -0
  45. package/.output/server/chunks/_/worktreeSetup.mjs.map +1 -0
  46. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  47. package/.output/server/chunks/build/client.precomputed.mjs.map +1 -1
  48. package/.output/server/chunks/nitro/nitro.mjs +1198 -584
  49. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  50. package/.output/server/chunks/routes/_ws.mjs +21 -111
  51. package/.output/server/chunks/routes/_ws.mjs.map +1 -1
  52. package/.output/server/chunks/routes/api/ai/providers.get.mjs +3 -1
  53. package/.output/server/chunks/routes/api/ai/providers.get.mjs.map +1 -1
  54. package/.output/server/chunks/routes/api/chat/compare.get.mjs +4 -5
  55. package/.output/server/chunks/routes/api/chat/compare.get.mjs.map +1 -1
  56. package/.output/server/chunks/routes/api/chat/context.get.mjs +4 -2
  57. package/.output/server/chunks/routes/api/chat/context.get.mjs.map +1 -1
  58. package/.output/server/chunks/routes/api/chat/finalize.post.mjs +17 -13
  59. package/.output/server/chunks/routes/api/chat/finalize.post.mjs.map +1 -1
  60. package/.output/server/chunks/routes/api/chat/generate-commit-message.post.mjs +26 -29
  61. package/.output/server/chunks/routes/api/chat/generate-commit-message.post.mjs.map +1 -1
  62. package/.output/server/chunks/routes/api/chat/preview-sync.post.mjs +4 -5
  63. package/.output/server/chunks/routes/api/chat/preview-sync.post.mjs.map +1 -1
  64. package/.output/server/chunks/routes/api/chat/preview.delete.mjs +4 -5
  65. package/.output/server/chunks/routes/api/chat/preview.delete.mjs.map +1 -1
  66. package/.output/server/chunks/routes/api/chat/preview.post.mjs +4 -6
  67. package/.output/server/chunks/routes/api/chat/preview.post.mjs.map +1 -1
  68. package/.output/server/chunks/routes/api/chat/rebase.post.mjs +4 -7
  69. package/.output/server/chunks/routes/api/chat/rebase.post.mjs.map +1 -1
  70. package/.output/server/chunks/routes/api/chat/worktree-commit.post.mjs +4 -6
  71. package/.output/server/chunks/routes/api/chat/worktree-commit.post.mjs.map +1 -1
  72. package/.output/server/chunks/routes/api/chat/worktree.delete.mjs +11 -4
  73. package/.output/server/chunks/routes/api/chat/worktree.delete.mjs.map +1 -1
  74. package/.output/server/chunks/routes/api/chat/worktree.post.mjs +4 -6
  75. package/.output/server/chunks/routes/api/chat/worktree.post.mjs.map +1 -1
  76. package/.output/server/chunks/routes/api/conversations/_conversationId/archive.post.mjs +10 -9
  77. package/.output/server/chunks/routes/api/conversations/_conversationId/archive.post.mjs.map +1 -1
  78. package/.output/server/chunks/routes/api/conversations/archives/_archiveId/restore.post.mjs +4 -7
  79. package/.output/server/chunks/routes/api/conversations/archives/_archiveId/restore.post.mjs.map +1 -1
  80. package/.output/server/chunks/routes/api/conversations/archives/_archiveId_.delete.mjs +4 -7
  81. package/.output/server/chunks/routes/api/conversations/archives/_archiveId_.delete.mjs.map +1 -1
  82. package/.output/server/chunks/routes/api/conversations/archives.delete.mjs +40 -0
  83. package/.output/server/chunks/routes/api/conversations/archives.delete.mjs.map +1 -0
  84. package/.output/server/chunks/routes/api/conversations/archives.get.mjs +4 -7
  85. package/.output/server/chunks/routes/api/conversations/archives.get.mjs.map +1 -1
  86. package/.output/server/chunks/routes/api/conversations/update.post.mjs +4 -7
  87. package/.output/server/chunks/routes/api/conversations/update.post.mjs.map +1 -1
  88. package/.output/server/chunks/routes/api/conversations.get.mjs +4 -7
  89. package/.output/server/chunks/routes/api/conversations.get.mjs.map +1 -1
  90. package/.output/server/chunks/routes/api/conversations.post.mjs +4 -7
  91. package/.output/server/chunks/routes/api/conversations.post.mjs.map +1 -1
  92. package/.output/server/chunks/routes/api/cwd.get.mjs +4 -2
  93. package/.output/server/chunks/routes/api/cwd.get.mjs.map +1 -1
  94. package/.output/server/chunks/routes/api/git/branch-rename.post.mjs +3 -4
  95. package/.output/server/chunks/routes/api/git/branch-rename.post.mjs.map +1 -1
  96. package/.output/server/chunks/routes/api/git/branch.delete.mjs +4 -5
  97. package/.output/server/chunks/routes/api/git/branch.delete.mjs.map +1 -1
  98. package/.output/server/chunks/routes/api/git/branches.get.mjs +4 -6
  99. package/.output/server/chunks/routes/api/git/branches.get.mjs.map +1 -1
  100. package/.output/server/chunks/routes/api/git/checkout.post.mjs +4 -5
  101. package/.output/server/chunks/routes/api/git/checkout.post.mjs.map +1 -1
  102. package/.output/server/chunks/routes/api/git/cherry-pick.post.mjs +3 -4
  103. package/.output/server/chunks/routes/api/git/cherry-pick.post.mjs.map +1 -1
  104. package/.output/server/chunks/routes/api/git/clean.post.mjs +3 -4
  105. package/.output/server/chunks/routes/api/git/clean.post.mjs.map +1 -1
  106. package/.output/server/chunks/routes/api/git/commit/_id_.get.mjs +3 -4
  107. package/.output/server/chunks/routes/api/git/commit/_id_.get.mjs.map +1 -1
  108. package/.output/server/chunks/routes/api/git/commit.post.mjs +4 -5
  109. package/.output/server/chunks/routes/api/git/commit.post.mjs.map +1 -1
  110. package/.output/server/chunks/routes/api/git/diff.get.mjs +4 -5
  111. package/.output/server/chunks/routes/api/git/diff.get.mjs.map +1 -1
  112. package/.output/server/chunks/routes/api/git/fetch.post.mjs +3 -4
  113. package/.output/server/chunks/routes/api/git/fetch.post.mjs.map +1 -1
  114. package/.output/server/chunks/routes/api/git/file-diff.get.mjs +4 -5
  115. package/.output/server/chunks/routes/api/git/file-diff.get.mjs.map +1 -1
  116. package/.output/server/chunks/routes/api/git/graph.get.mjs +4 -5
  117. package/.output/server/chunks/routes/api/git/graph.get.mjs.map +1 -1
  118. package/.output/server/chunks/routes/api/git/log.get.mjs +4 -5
  119. package/.output/server/chunks/routes/api/git/log.get.mjs.map +1 -1
  120. package/.output/server/chunks/routes/api/git/merge-base.get.mjs +4 -5
  121. package/.output/server/chunks/routes/api/git/merge-base.get.mjs.map +1 -1
  122. package/.output/server/chunks/routes/api/git/merge.post.mjs +3 -4
  123. package/.output/server/chunks/routes/api/git/merge.post.mjs.map +1 -1
  124. package/.output/server/chunks/routes/api/git/pull.post.mjs +3 -4
  125. package/.output/server/chunks/routes/api/git/pull.post.mjs.map +1 -1
  126. package/.output/server/chunks/routes/api/git/push.post.mjs +3 -4
  127. package/.output/server/chunks/routes/api/git/push.post.mjs.map +1 -1
  128. package/.output/server/chunks/routes/api/git/rebase.post.mjs +3 -4
  129. package/.output/server/chunks/routes/api/git/rebase.post.mjs.map +1 -1
  130. package/.output/server/chunks/routes/api/git/remote.delete.mjs +3 -4
  131. package/.output/server/chunks/routes/api/git/remote.delete.mjs.map +1 -1
  132. package/.output/server/chunks/routes/api/git/remote.post.mjs +3 -4
  133. package/.output/server/chunks/routes/api/git/remote.post.mjs.map +1 -1
  134. package/.output/server/chunks/routes/api/git/remote.put.mjs +3 -4
  135. package/.output/server/chunks/routes/api/git/remote.put.mjs.map +1 -1
  136. package/.output/server/chunks/routes/api/git/remotes.get.mjs +3 -4
  137. package/.output/server/chunks/routes/api/git/remotes.get.mjs.map +1 -1
  138. package/.output/server/chunks/routes/api/git/reset.post.mjs +3 -4
  139. package/.output/server/chunks/routes/api/git/reset.post.mjs.map +1 -1
  140. package/.output/server/chunks/routes/api/git/revert.post.mjs +3 -4
  141. package/.output/server/chunks/routes/api/git/revert.post.mjs.map +1 -1
  142. package/.output/server/chunks/routes/api/git/show.get.mjs +4 -5
  143. package/.output/server/chunks/routes/api/git/show.get.mjs.map +1 -1
  144. package/.output/server/chunks/routes/api/git/stage.post.mjs +4 -5
  145. package/.output/server/chunks/routes/api/git/stage.post.mjs.map +1 -1
  146. package/.output/server/chunks/routes/api/git/stash-apply.post.mjs +3 -4
  147. package/.output/server/chunks/routes/api/git/stash-apply.post.mjs.map +1 -1
  148. package/.output/server/chunks/routes/api/git/stash-branch.post.mjs +3 -4
  149. package/.output/server/chunks/routes/api/git/stash-branch.post.mjs.map +1 -1
  150. package/.output/server/chunks/routes/api/git/stash-drop.post.mjs +3 -4
  151. package/.output/server/chunks/routes/api/git/stash-drop.post.mjs.map +1 -1
  152. package/.output/server/chunks/routes/api/git/stash-pop.post.mjs +3 -4
  153. package/.output/server/chunks/routes/api/git/stash-pop.post.mjs.map +1 -1
  154. package/.output/server/chunks/routes/api/git/stash.get.mjs +3 -4
  155. package/.output/server/chunks/routes/api/git/stash.get.mjs.map +1 -1
  156. package/.output/server/chunks/routes/api/git/stash.post.mjs +3 -4
  157. package/.output/server/chunks/routes/api/git/stash.post.mjs.map +1 -1
  158. package/.output/server/chunks/routes/api/git/state.get.mjs +3 -4
  159. package/.output/server/chunks/routes/api/git/state.get.mjs.map +1 -1
  160. package/.output/server/chunks/routes/api/git/status.get.mjs +4 -5
  161. package/.output/server/chunks/routes/api/git/status.get.mjs.map +1 -1
  162. package/.output/server/chunks/routes/api/git/tag/_name_.get.mjs +3 -4
  163. package/.output/server/chunks/routes/api/git/tag/_name_.get.mjs.map +1 -1
  164. package/.output/server/chunks/routes/api/git/tag-push.post.mjs +3 -4
  165. package/.output/server/chunks/routes/api/git/tag-push.post.mjs.map +1 -1
  166. package/.output/server/chunks/routes/api/git/tag.delete.mjs +3 -4
  167. package/.output/server/chunks/routes/api/git/tag.delete.mjs.map +1 -1
  168. package/.output/server/chunks/routes/api/git/tag.post.mjs +3 -4
  169. package/.output/server/chunks/routes/api/git/tag.post.mjs.map +1 -1
  170. package/.output/server/chunks/routes/api/git/unstage.post.mjs +4 -5
  171. package/.output/server/chunks/routes/api/git/unstage.post.mjs.map +1 -1
  172. package/.output/server/chunks/routes/api/index.get.mjs +7 -4
  173. package/.output/server/chunks/routes/api/index.get.mjs.map +1 -1
  174. package/.output/server/chunks/routes/api/index.get2.mjs +4 -2
  175. package/.output/server/chunks/routes/api/index.get2.mjs.map +1 -1
  176. package/.output/server/chunks/routes/api/index.get3.mjs +4 -2
  177. package/.output/server/chunks/routes/api/index.get3.mjs.map +1 -1
  178. package/.output/server/chunks/routes/api/index.post.mjs +19 -16
  179. package/.output/server/chunks/routes/api/index.post.mjs.map +1 -1
  180. package/.output/server/chunks/routes/api/index.post2.mjs +4 -6
  181. package/.output/server/chunks/routes/api/index.post2.mjs.map +1 -1
  182. package/.output/server/chunks/routes/api/jobs/_id/cancel.post.mjs +6 -3
  183. package/.output/server/chunks/routes/api/jobs/_id/cancel.post.mjs.map +1 -1
  184. package/.output/server/chunks/routes/api/jobs/_id_.get.mjs +7 -4
  185. package/.output/server/chunks/routes/api/jobs/_id_.get.mjs.map +1 -1
  186. package/.output/server/chunks/routes/api/rebase/abort.post.mjs +4 -2
  187. package/.output/server/chunks/routes/api/rebase/abort.post.mjs.map +1 -1
  188. package/.output/server/chunks/routes/api/rebase/ai-resolve.post.mjs +19 -105
  189. package/.output/server/chunks/routes/api/rebase/ai-resolve.post.mjs.map +1 -1
  190. package/.output/server/chunks/routes/api/rebase/conflicts.get.mjs +3 -1
  191. package/.output/server/chunks/routes/api/rebase/conflicts.get.mjs.map +1 -1
  192. package/.output/server/chunks/routes/api/rebase/continue-sync.post.mjs +4 -2
  193. package/.output/server/chunks/routes/api/rebase/continue-sync.post.mjs.map +1 -1
  194. package/.output/server/chunks/routes/api/rebase/continue.post.mjs +71 -73
  195. package/.output/server/chunks/routes/api/rebase/continue.post.mjs.map +1 -1
  196. package/.output/server/chunks/routes/api/rebase/resolve.put.mjs +3 -1
  197. package/.output/server/chunks/routes/api/rebase/resolve.put.mjs.map +1 -1
  198. package/.output/server/chunks/routes/api/repository/status.get.mjs +4 -5
  199. package/.output/server/chunks/routes/api/repository/status.get.mjs.map +1 -1
  200. package/.output/server/chunks/routes/api/settings.get.mjs +6 -5
  201. package/.output/server/chunks/routes/api/settings.get.mjs.map +1 -1
  202. package/.output/server/chunks/routes/api/settings.post.mjs +5 -3
  203. package/.output/server/chunks/routes/api/settings.post.mjs.map +1 -1
  204. package/.output/server/chunks/routes/api/skills/_skillId/prompt.post.mjs +3 -1
  205. package/.output/server/chunks/routes/api/skills/_skillId/prompt.post.mjs.map +1 -1
  206. package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.get.mjs +3 -1
  207. package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.get.mjs.map +1 -1
  208. package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.put.mjs +3 -1
  209. package/.output/server/chunks/routes/api/specs/_featureId/_...filename_.put.mjs.map +1 -1
  210. package/.output/server/chunks/routes/api/specs/features.get.mjs +3 -1
  211. package/.output/server/chunks/routes/api/specs/features.get.mjs.map +1 -1
  212. package/.output/server/chunks/routes/api/specs/reindex.post.mjs +4 -2
  213. package/.output/server/chunks/routes/api/specs/reindex.post.mjs.map +1 -1
  214. package/.output/server/chunks/routes/api/specs/search.get.mjs +4 -2
  215. package/.output/server/chunks/routes/api/specs/search.get.mjs.map +1 -1
  216. package/.output/server/chunks/routes/api/specs/status.get.mjs +4 -2
  217. package/.output/server/chunks/routes/api/specs/status.get.mjs.map +1 -1
  218. package/.output/server/chunks/routes/api/specs/traceability/_featureId_.get.mjs +4 -2
  219. package/.output/server/chunks/routes/api/specs/traceability/_featureId_.get.mjs.map +1 -1
  220. package/.output/server/chunks/routes/api/worktrees/_name/switch.post.mjs +4 -2
  221. package/.output/server/chunks/routes/api/worktrees/_name/switch.post.mjs.map +1 -1
  222. package/.output/server/chunks/routes/api/worktrees/_name_.delete.mjs +3 -1
  223. package/.output/server/chunks/routes/api/worktrees/_name_.delete.mjs.map +1 -1
  224. package/.output/server/chunks/routes/git-watcher-ws.mjs +4 -2
  225. package/.output/server/chunks/routes/git-watcher-ws.mjs.map +1 -1
  226. package/.output/server/chunks/routes/renderer.mjs +4 -2
  227. package/.output/server/chunks/routes/terminal-ws.mjs +230 -0
  228. package/.output/server/chunks/routes/terminal-ws.mjs.map +1 -0
  229. package/.output/server/index.mjs +4 -2
  230. package/.output/server/index.mjs.map +1 -1
  231. package/.output/server/node_modules/node-pty/build/Makefile +369 -0
  232. package/.output/server/node_modules/node-pty/build/Release/pty.node +0 -0
  233. package/.output/server/node_modules/node-pty/build/binding.Makefile +6 -0
  234. package/.output/server/node_modules/node-pty/build/config.gypi +513 -0
  235. package/.output/server/node_modules/node-pty/build/pty.target.mk +168 -0
  236. package/.output/server/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  237. package/.output/server/node_modules/node-pty/lib/index.js +52 -0
  238. package/.output/server/node_modules/node-pty/lib/shared/conout.js +11 -0
  239. package/.output/server/node_modules/node-pty/lib/terminal.js +190 -0
  240. package/.output/server/node_modules/node-pty/lib/unixTerminal.js +346 -0
  241. package/.output/server/node_modules/node-pty/lib/utils.js +39 -0
  242. package/.output/server/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  243. package/.output/server/node_modules/node-pty/lib/windowsPtyAgent.js +320 -0
  244. package/.output/server/node_modules/node-pty/lib/windowsTerminal.js +199 -0
  245. package/.output/server/node_modules/node-pty/package.json +64 -0
  246. package/.output/server/package.json +2 -1
  247. package/package.json +4 -1
  248. package/.output/public/_nuxt/2zamijve.js +0 -1
  249. package/.output/public/_nuxt/BEjIzkrM.js +0 -85
  250. package/.output/public/_nuxt/BaSsfm-t.js +0 -36
  251. package/.output/public/_nuxt/BjdLSVUw.js +0 -1
  252. package/.output/public/_nuxt/DeqM2TrP.js +0 -1
  253. package/.output/public/_nuxt/builds/meta/df07a2f8-c6d6-420d-a863-ac6e0e7673e0.json +0 -1
  254. package/.output/public/_nuxt/default.DKSPicGu.css +0 -1
  255. package/.output/public/_nuxt/entry.BUDfUMkE.css +0 -1
  256. package/.output/public/_nuxt/zrVRZSxq.js +0 -64
  257. package/.output/server/chunks/_/baseBranch.mjs +0 -137
  258. package/.output/server/chunks/_/baseBranch.mjs.map +0 -1
  259. package/.output/server/chunks/_/claude.mjs +0 -164
  260. package/.output/server/chunks/_/claude.mjs.map +0 -1
  261. package/.output/server/chunks/_/conversationStore.mjs +0 -257
  262. package/.output/server/chunks/_/conversationStore.mjs.map +0 -1
  263. package/.output/server/chunks/_/geminiProvider.mjs +0 -372
  264. package/.output/server/chunks/_/geminiProvider.mjs.map +0 -1
  265. package/.output/server/chunks/_/gitExec.mjs +0 -48
  266. package/.output/server/chunks/_/gitExec.mjs.map +0 -1
  267. package/.output/server/chunks/_/providerProcessError.mjs +0 -163
  268. package/.output/server/chunks/_/providerProcessError.mjs.map +0 -1
  269. package/.output/server/chunks/_/uiAdapter.mjs +0 -421
  270. package/.output/server/chunks/_/uiAdapter.mjs.map +0 -1
  271. package/.output/server/chunks/routes/api/chat.post.mjs +0 -276
  272. package/.output/server/chunks/routes/api/chat.post.mjs.map +0 -1
@@ -1,14 +1,13 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, statSync, openSync, readSync, closeSync } from 'node:fs';
2
2
  import { e as eventBus, G as GLOBAL_CHANNEL } from './eventBus.mjs';
3
- import { e as getProjectDir } from '../nitro/nitro.mjs';
3
+ import { q as readConversationFromStorage, a as getProjectDir, s as updateConversationProviderSessionInStorage } from '../nitro/nitro.mjs';
4
4
  import { exec } from 'node:child_process';
5
5
  import { promisify } from 'node:util';
6
6
  import { i as isSpecCatWorktreePath, b as getChatWorktreeIdFromPath } from './worktreePaths.mjs';
7
7
  import { join } from 'node:path';
8
- import { r as resolveServerProviderSelection, a as guardProviderCapability } from './aiProviderSelection.mjs';
9
- import { s as streamChatWithProvider, h as hasCodexPermissionError, a as hasCodexMissingRolloutPathError, b as summarizeProviderProcessError } from './providerProcessError.mjs';
8
+ import { r as resolveServerProviderSelection } from './aiProviderSelection.mjs';
10
9
  import { g as getProvider } from './aiProviderRegistry.mjs';
11
- import { n as normalizeToolName, e as extractPermissionRequestFromProcessOutput, a as normalizeTools, c as checkForPermissionRequest, i as isRenderableEvent } from './uiAdapter.mjs';
10
+ import { g as getCliHookSpoolPath, p as parseCliHookRecord, d as disposeTerminalSession, b as buildTerminalSessionId, s as subscribeTerminalSession, w as writeTerminalInput, a as getOrCreateTerminalSession } from './terminalSessions.mjs';
12
11
 
13
12
  const execAsync = promisify(exec);
14
13
  async function ensureChatWorktree(projectDir, worktreePath, knownBranch) {
@@ -83,62 +82,179 @@ async function loadSpecContext(projectDir, featureId) {
83
82
  return sections.join("\n");
84
83
  }
85
84
 
86
- function toolStreamKey(event) {
87
- if (typeof event.index === "number") return `index:${event.index}`;
88
- if (event.blockId) return `block:${event.blockId}`;
89
- return null;
85
+ function asRecord(value) {
86
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
90
87
  }
91
- function isUserInputToolName(name) {
92
- const compact = normalizeToolName(name).toLowerCase().replace(/[^a-z0-9]/g, "");
93
- return compact === "askuserquestion" || compact === "requestuserinput";
88
+ function pickString(record, keys) {
89
+ for (const key of keys) {
90
+ const value = record[key];
91
+ if (typeof value === "string" && value.length > 0) return value;
92
+ }
93
+ return void 0;
94
94
  }
95
- function trackStreamingToolInput(event, activeTools) {
96
- const key = toolStreamKey(event);
97
- if (event.type === "block_start") {
98
- if (key && event.blockType === "tool_use" && event.name) {
99
- activeTools.set(key, { name: event.name, inputJson: "" });
100
- }
101
- return null;
95
+ function extractPrompt(payload) {
96
+ const direct = pickString(payload, ["prompt", "message", "user_prompt", "userPrompt", "input"]);
97
+ if (direct) return direct;
98
+ const nestedKeys = ["tool_input", "toolInput", "request", "data"];
99
+ for (const key of nestedKeys) {
100
+ const nested = asRecord(payload[key]);
101
+ const nestedPrompt = pickString(nested, ["prompt", "message", "input"]);
102
+ if (nestedPrompt) return nestedPrompt;
102
103
  }
103
- if (!key) return null;
104
- const tool = activeTools.get(key);
105
- if (!tool) return null;
106
- if (event.type === "block_delta") {
107
- if (event.partialJson) {
108
- tool.inputJson += event.partialJson;
104
+ return void 0;
105
+ }
106
+ function extractToolName(payload) {
107
+ const direct = pickString(payload, ["tool_name", "toolName", "name"]);
108
+ if (direct) return direct;
109
+ const tool = asRecord(payload.tool);
110
+ return pickString(tool, ["name", "tool_name", "toolName"]);
111
+ }
112
+ function isToolFailure(eventName) {
113
+ return eventName === "PostToolUseFailure" || eventName === "PermissionDenied";
114
+ }
115
+ function toEvents(record) {
116
+ const payload = asRecord(record.payload);
117
+ const eventName = record.hookEventName || pickString(payload, ["hook_event_name", "hookEventName"]) || "unknown";
118
+ const base = {
119
+ providerId: record.providerId,
120
+ jobId: record.jobId,
121
+ requestId: record.requestId,
122
+ hookEventName: eventName,
123
+ sessionId: record.sessionId,
124
+ turnId: record.turnId,
125
+ cwd: record.cwd,
126
+ timestamp: record.timestamp
127
+ };
128
+ const events = [
129
+ {
130
+ type: "cli_hook",
131
+ ...base,
132
+ payload: record.payload
109
133
  }
110
- return null;
134
+ ];
135
+ if (eventName === "UserPromptSubmit") {
136
+ events.push({
137
+ type: "cli_prompt_submitted",
138
+ ...base,
139
+ prompt: extractPrompt(payload)
140
+ });
111
141
  }
112
- activeTools.delete(key);
113
- return tool;
142
+ if (eventName === "PostToolUse" || eventName === "PostToolUseFailure") {
143
+ events.push({
144
+ type: "cli_tool_completed",
145
+ ...base,
146
+ toolName: extractToolName(payload),
147
+ failed: isToolFailure(eventName)
148
+ });
149
+ }
150
+ if (eventName === "Stop" || eventName === "SubagentStop") {
151
+ events.push({
152
+ type: eventName === "Stop" ? "cli_turn_stop" : "cli_subagent_stop",
153
+ ...base,
154
+ stopHookActive: payload.stop_hook_active,
155
+ lastAssistantMessage: pickString(payload, ["last_assistant_message", "lastAssistantMessage"])
156
+ });
157
+ }
158
+ return events;
114
159
  }
115
- function parseToolInputJson(inputJson) {
160
+ function startCliHookMonitor(options) {
161
+ const spoolPath = getCliHookSpoolPath(options.conversationId);
162
+ let offset = 0;
163
+ let tail = Buffer.alloc(0);
164
+ let stopped = false;
165
+ let sawStopHook = false;
166
+ let sawArmedPrompt = false;
116
167
  try {
117
- const parsed = JSON.parse(inputJson);
118
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
168
+ offset = existsSync(spoolPath) ? statSync(spoolPath).size : 0;
119
169
  } catch {
120
- return {};
170
+ offset = 0;
121
171
  }
122
- }
123
- function isApprovalMode(mode) {
124
- return mode === "ask" || mode === "plan";
125
- }
126
- function deriveApprovalRequestFromEvent(event, approvedTools, providerId, mode) {
127
- if (!isApprovalMode(mode)) return null;
128
- return checkForPermissionRequest(event, approvedTools, providerId);
129
- }
130
- function deriveApprovalRequestFromProcessOutput(nonJsonOutput, mode) {
131
- if (!isApprovalMode(mode)) return null;
132
- const inferred = extractPermissionRequestFromProcessOutput(nonJsonOutput);
133
- if (!inferred) return null;
134
- const tools = normalizeTools(inferred.tools);
172
+ const readAppendedBytes = (size) => {
173
+ const length = size - offset;
174
+ if (length <= 0) return null;
175
+ let fd = null;
176
+ try {
177
+ fd = openSync(spoolPath, "r");
178
+ const buffer = Buffer.alloc(length);
179
+ const bytesRead = readSync(fd, buffer, 0, length, offset);
180
+ offset += bytesRead;
181
+ return buffer.subarray(0, bytesRead);
182
+ } catch {
183
+ return null;
184
+ } finally {
185
+ if (fd !== null) {
186
+ try {
187
+ closeSync(fd);
188
+ } catch {
189
+ }
190
+ }
191
+ }
192
+ };
193
+ const poll = () => {
194
+ if (stopped || !existsSync(spoolPath)) return;
195
+ let size = 0;
196
+ try {
197
+ size = statSync(spoolPath).size;
198
+ } catch {
199
+ return;
200
+ }
201
+ if (size < offset) {
202
+ offset = 0;
203
+ tail = Buffer.alloc(0);
204
+ }
205
+ const chunk = readAppendedBytes(size);
206
+ if (!chunk || chunk.length === 0) return;
207
+ const combined = tail.length > 0 ? Buffer.concat([tail, chunk]) : chunk;
208
+ const lines = [];
209
+ let lineStart = 0;
210
+ for (let i = 0; i < combined.length; i++) {
211
+ if (combined[i] === 10) {
212
+ lines.push(combined.toString("utf-8", lineStart, i));
213
+ lineStart = i + 1;
214
+ }
215
+ }
216
+ tail = lineStart < combined.length ? Buffer.from(combined.subarray(lineStart)) : Buffer.alloc(0);
217
+ for (const line of lines) {
218
+ const record = parseCliHookRecord(line);
219
+ if (!record) continue;
220
+ if (options.jobId !== void 0 && record.jobId !== options.jobId) continue;
221
+ const armed = options.isArmed ? options.isArmed() : true;
222
+ if (armed && record.hookEventName === "UserPromptSubmit") {
223
+ sawArmedPrompt = true;
224
+ }
225
+ const isStop = record.hookEventName === "Stop";
226
+ const promptGateOpen = sawArmedPrompt;
227
+ const firstStop = isStop && armed && promptGateOpen && !sawStopHook;
228
+ if (firstStop) {
229
+ sawStopHook = true;
230
+ }
231
+ for (const event of toEvents(record)) {
232
+ options.emit(event);
233
+ }
234
+ if (firstStop && options.onStop) {
235
+ const payload = asRecord(record.payload);
236
+ options.onStop(pickString(payload, ["last_assistant_message", "lastAssistantMessage"]));
237
+ }
238
+ }
239
+ };
240
+ const interval = setInterval(poll, 100);
135
241
  return {
136
- type: "permission_request",
137
- tool: tools[0] || "Permission",
138
- tools,
139
- description: inferred.description
242
+ stop: () => {
243
+ poll();
244
+ stopped = true;
245
+ clearInterval(interval);
246
+ },
247
+ sawStop: () => sawStopHook
140
248
  };
141
249
  }
250
+
251
+ function normalizeToolName(tool) {
252
+ if (!tool) return "";
253
+ const trimmed = tool.trim();
254
+ if (!trimmed) return "";
255
+ return trimmed.charAt(0).toUpperCase() + trimmed.slice(1);
256
+ }
257
+
142
258
  function approveTools(approvedTools, tools) {
143
259
  for (const tool of tools) {
144
260
  const normalized = normalizeToolName(tool);
@@ -153,6 +269,8 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
153
269
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
154
270
  const MAX_ATTACHMENT_COUNT = 4;
155
271
  const MAX_ATTACHMENT_SIZE_BYTES = 5 * 1024 * 1024;
272
+ const PTY_TURN_TIMEOUT_MS = 30 * 6e4;
273
+ const PTY_IDLE_FINALIZE_MS = 8e3;
156
274
  const SPECKIT_AUTONOMY_DIRECTIVE = [
157
275
  "Speckit Execution Mode (MANDATORY):",
158
276
  "- Do not ask the user for confirmation, follow-up, or permission to proceed.",
@@ -208,6 +326,24 @@ function buildProviderMessage(baseMessage, attachments) {
208
326
  function generateJobId() {
209
327
  return `job-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
210
328
  }
329
+ function getStoredProviderSessionId(conversation) {
330
+ var _a;
331
+ if (!conversation || typeof conversation !== "object") return null;
332
+ const record = conversation;
333
+ const value = (_a = record.providerSessionId) != null ? _a : record.claudeSessionId;
334
+ return typeof value === "string" && value.length > 0 ? value : null;
335
+ }
336
+ function getStoredProviderSelection(conversation) {
337
+ if (!conversation || typeof conversation !== "object") return null;
338
+ const record = conversation;
339
+ const providerId = record.providerId;
340
+ if (typeof providerId !== "string" || providerId.length === 0) return null;
341
+ const providerModelKey = record.providerModelKey;
342
+ return {
343
+ providerId,
344
+ providerModelKey: typeof providerModelKey === "string" ? providerModelKey : ""
345
+ };
346
+ }
211
347
  class ChatJobQueue {
212
348
  constructor() {
213
349
  __publicField(this, "jobs", /* @__PURE__ */ new Map());
@@ -312,7 +448,7 @@ class ChatJobQueue {
312
448
  source,
313
449
  message: msg.message
314
450
  });
315
- this.runProvider(job, false, false);
451
+ this.runProvider(job, false);
316
452
  return job.id;
317
453
  }
318
454
  /**
@@ -340,7 +476,7 @@ class ChatJobQueue {
340
476
  job.status = "running";
341
477
  const resumeMode = job.message.permissionMode || "ask";
342
478
  job.message = { ...job.message, permissionMode: resumeMode };
343
- this.runProvider(job, false, false);
479
+ this.runProvider(job, false);
344
480
  } else {
345
481
  procState.pendingTools = [];
346
482
  this.setJobStatus(job, "done");
@@ -374,6 +510,35 @@ class ChatJobQueue {
374
510
  this.setJobStatus(job, "done");
375
511
  console.log("[JobQueue] Abort: job status set to done for", job.id);
376
512
  }
513
+ /**
514
+ * Terminate any provider process associated with a conversation that has
515
+ * been finalized. Unlike abort(), this is cleanup for a read-only
516
+ * conversation, so it must not emit a stopped assistant turn.
517
+ */
518
+ finalizeConversation(conversationId) {
519
+ disposeTerminalSession(buildTerminalSessionId(conversationId));
520
+ const convState = this.conversationStates.get(conversationId);
521
+ if (!convState) return;
522
+ if (convState.activeJobId) {
523
+ const job = this.jobs.get(convState.activeJobId);
524
+ const procState = this.jobProcessStates.get(convState.activeJobId);
525
+ if (procState == null ? void 0 : procState.proc) {
526
+ procState.procGeneration++;
527
+ killProc(procState.proc);
528
+ procState.proc = null;
529
+ console.log("[JobQueue] Finalize: process killed for", convState.activeJobId);
530
+ }
531
+ if (procState) {
532
+ procState.pendingTools = [];
533
+ }
534
+ if (job && job.status !== "done" && job.status !== "error") {
535
+ this.setJobStatus(job, "done");
536
+ }
537
+ }
538
+ convState.providerSessionId = null;
539
+ convState.approvedTools.clear();
540
+ convState.activeJobId = null;
541
+ }
377
542
  /**
378
543
  * Reset provider session state for a conversation.
379
544
  */
@@ -415,13 +580,90 @@ class ChatJobQueue {
415
580
  return Array.from(this.jobs.values());
416
581
  }
417
582
  // ── Provider Execution ─────────────────────────────
418
- async runProvider(job, isRetry, forceEphemeral) {
583
+ /**
584
+ * Execute a job through the provider's interactive PTY session. Turn
585
+ * completion is detected via the CLI Stop hook.
586
+ */
587
+ async runProvider(job, isRetry) {
588
+ return this.runProviderViaPty(job, isRetry);
589
+ }
590
+ /**
591
+ * Inject a prompt into a (possibly freshly-spawned) PTY session once its TUI
592
+ * composer is ready, then submit it. The prompt is bracketed-pasted so
593
+ * embedded newlines don't submit line-by-line. Server-side and
594
+ * client-independent — runs whether or not a terminal-ws viewer is attached.
595
+ */
596
+ injectMessage(session, sessionId, text, stillValid, onSubmitted) {
597
+ const READY_IDLE_MS = 1200;
598
+ const REUSED_READY_MS = 800;
599
+ const READY_MAX_MS = 15e3;
600
+ const SUBMIT_IDLE_MS = 600;
601
+ const SUBMIT_MAX_MS = 4e3;
602
+ const reused = session.buffer.length > 0;
603
+ const safeText = text.replace(/\x1b\[20[01]~/g, "");
604
+ const startedAt = Date.now();
605
+ let firstDataAt = reused ? startedAt : 0;
606
+ let lastDataAt = Date.now();
607
+ let injected = false;
608
+ let injectedAt = 0;
609
+ let submitted = false;
610
+ const unsubscribe = subscribeTerminalSession(session, {
611
+ onData: () => {
612
+ const now = Date.now();
613
+ if (!firstDataAt) firstDataAt = now;
614
+ lastDataAt = now;
615
+ },
616
+ onExit: () => {
617
+ }
618
+ });
619
+ const timer = setInterval(() => {
620
+ if (!stillValid()) {
621
+ clearInterval(timer);
622
+ unsubscribe();
623
+ return;
624
+ }
625
+ const now = Date.now();
626
+ if (!injected) {
627
+ const idleMs = reused ? REUSED_READY_MS : READY_IDLE_MS;
628
+ const ready = firstDataAt > 0 && now - lastDataAt >= idleMs;
629
+ const forced = now - startedAt >= READY_MAX_MS;
630
+ if (ready || forced) {
631
+ injected = true;
632
+ injectedAt = now;
633
+ lastDataAt = now;
634
+ writeTerminalInput(sessionId, `\x1B[200~${safeText}\x1B[201~`);
635
+ }
636
+ return;
637
+ }
638
+ if (!submitted) {
639
+ const echoSettled = now - lastDataAt >= SUBMIT_IDLE_MS;
640
+ const submitForced = now - injectedAt >= SUBMIT_MAX_MS;
641
+ if (echoSettled || submitForced) {
642
+ submitted = true;
643
+ writeTerminalInput(sessionId, "\r");
644
+ onSubmitted();
645
+ clearInterval(timer);
646
+ unsubscribe();
647
+ }
648
+ return;
649
+ }
650
+ }, 250);
651
+ }
652
+ async runProviderViaPty(job, isRetry) {
419
653
  const msg = job.message;
420
654
  const convState = this.getConversationState(job.conversationId);
421
655
  const procState = this.jobProcessStates.get(job.id);
422
656
  if (!procState) return;
423
657
  job.status = "running";
424
- const requestedSelection = msg.providerId ? { providerId: msg.providerId, modelKey: msg.providerModelKey || "" } : { providerId: "claude", modelKey: msg.providerModelKey || "" };
658
+ let requestedSelection;
659
+ if (msg.providerId) {
660
+ requestedSelection = { providerId: msg.providerId, modelKey: msg.providerModelKey || "" };
661
+ } else {
662
+ const storedSelection = getStoredProviderSelection(
663
+ await readConversationFromStorage(job.conversationId)
664
+ );
665
+ requestedSelection = storedSelection ? { providerId: storedSelection.providerId, modelKey: msg.providerModelKey || storedSelection.providerModelKey } : { providerId: "claude", modelKey: msg.providerModelKey || "" };
666
+ }
425
667
  const selection = await resolveServerProviderSelection(requestedSelection);
426
668
  const provider = getProvider(selection.providerId);
427
669
  if (!provider) {
@@ -433,39 +675,8 @@ class ChatJobQueue {
433
675
  this.setJobStatus(job, "error");
434
676
  return;
435
677
  }
436
- const providerGuard = await guardProviderCapability(
437
- selection,
438
- "streaming",
439
- "Choose a provider with streaming capability in Settings."
440
- );
441
- if ("failure" in providerGuard) {
442
- this.emitAndBuffer(job, {
443
- type: "error",
444
- error: providerGuard.failure.error,
445
- requestId: msg.requestId
446
- });
447
- this.setJobStatus(job, "error");
448
- return;
449
- }
450
678
  const projectDir = getProjectDir();
451
679
  const workingDirectory = msg.cwd || projectDir;
452
- const mode = msg.permissionMode || "ask";
453
- if (mode === "ask" || mode === "plan") {
454
- const permissionGuard = await guardProviderCapability(
455
- selection,
456
- "permissions",
457
- "Use auto/bypass permission mode or choose a provider that supports permission prompts."
458
- );
459
- if ("failure" in permissionGuard) {
460
- this.emitAndBuffer(job, {
461
- type: "error",
462
- error: permissionGuard.failure.error,
463
- requestId: msg.requestId
464
- });
465
- this.setJobStatus(job, "error");
466
- return;
467
- }
468
- }
469
680
  if (isSpecCatWorktreePath(workingDirectory) && !existsSync(workingDirectory)) {
470
681
  const result = await ensureChatWorktree(projectDir, workingDirectory, msg.worktreeBranch);
471
682
  if (result.recovered) {
@@ -480,224 +691,165 @@ class ChatJobQueue {
480
691
  return;
481
692
  }
482
693
  }
694
+ const speckitCommand = isSpeckitCommand(msg.message);
695
+ if (!isRetry && !speckitCommand && !convState.providerSessionId) {
696
+ const storedSessionId = getStoredProviderSessionId(await readConversationFromStorage(job.conversationId));
697
+ if (storedSessionId) convState.providerSessionId = storedSessionId;
698
+ }
483
699
  const usedResumeFlag = !isRetry && !!convState.providerSessionId;
484
700
  const resumeSessionId = usedResumeFlag ? convState.providerSessionId : void 0;
485
- let systemPrompt;
486
- const speckitCommand = isSpeckitCommand(msg.message);
701
+ let preface = "";
487
702
  if (msg.featureId && !usedResumeFlag) {
488
703
  try {
489
704
  const specContext = await loadSpecContext(projectDir, msg.featureId);
490
- if (specContext) {
491
- systemPrompt = specContext;
492
- }
705
+ if (specContext) preface = specContext;
493
706
  } catch (error) {
494
707
  console.error("[JobQueue] Failed to load spec context:", error);
495
708
  }
496
709
  }
497
710
  if (speckitCommand && !usedResumeFlag) {
498
- systemPrompt = systemPrompt ? `${systemPrompt}
711
+ preface = preface ? `${preface}
499
712
 
500
713
  ${SPECKIT_AUTONOMY_DIRECTIVE}` : SPECKIT_AUTONOMY_DIRECTIVE;
501
714
  }
502
- console.log("[JobQueue] Running provider:", selection.providerId, selection.modelKey, isRetry ? "(retry)" : "");
503
- const generation = procState.procGeneration;
504
- let permissionRequested = false;
505
- let emittedRenderableContent = false;
506
- let emittedTerminalErrorEvent = false;
507
- const activeToolInputs = /* @__PURE__ */ new Map();
508
715
  const attachments = msg.attachments || [];
509
- const providerMessage = buildProviderMessage(msg.message, attachments);
716
+ const baseMessage = buildProviderMessage(msg.message, attachments);
717
+ const fullMessage = preface ? `${preface}
718
+
719
+ ${baseMessage}` : baseMessage;
720
+ console.log("[JobQueue] Running provider via PTY:", selection.providerId, selection.modelKey, isRetry ? "(retry)" : "");
721
+ const generation = procState.procGeneration;
722
+ const sessionId = buildTerminalSessionId(job.conversationId);
723
+ let session;
510
724
  try {
511
- procState.proc = await streamChatWithProvider(
512
- {
513
- message: providerMessage,
514
- selection,
515
- cwd: workingDirectory,
516
- permissionMode: mode,
517
- approvedTools: Array.from(convState.approvedTools),
518
- resumeSessionId,
519
- systemPrompt,
520
- ephemeral: forceEphemeral && selection.providerId === "codex"
725
+ session = getOrCreateTerminalSession({
726
+ sessionId,
727
+ cwd: workingDirectory,
728
+ providerId: selection.providerId,
729
+ modelKey: selection.modelKey,
730
+ resumeSessionId,
731
+ hookContext: {
732
+ enabled: true,
733
+ providerId: selection.providerId,
734
+ conversationId: job.conversationId,
735
+ jobId: job.id,
736
+ requestId: msg.requestId
521
737
  },
522
- {
523
- onProviderJson: (parsed) => {
524
- var _a, _b;
525
- const events = provider.toCanonicalEvents(parsed);
526
- for (const event of events) {
527
- const completedToolInput = event.type === "block_start" || event.type === "block_delta" || event.type === "block_end" ? trackStreamingToolInput(event, activeToolInputs) : null;
528
- if (event.sessionId) {
529
- convState.providerSessionId = event.sessionId;
530
- }
531
- if (isRenderableEvent(event)) {
532
- emittedRenderableContent = true;
533
- }
534
- if (event.type === "error" || event.type === "turn_result" && event.subtype !== "success") {
535
- emittedTerminalErrorEvent = true;
536
- }
537
- if (completedToolInput && isUserInputToolName(completedToolInput.name)) {
538
- permissionRequested = true;
539
- this.setJobStatus(job, "done");
540
- this.emitAndBuffer(job, { type: "ui_event", event });
541
- this.emitAndBuffer(job, {
542
- type: "done",
543
- requestId: msg.requestId,
544
- awaitingUserInput: true,
545
- tool: completedToolInput.name,
546
- input: parseToolInputJson(completedToolInput.inputJson)
547
- });
548
- (_a = procState.proc) == null ? void 0 : _a.kill();
549
- return;
550
- }
551
- if ((mode === "ask" || mode === "plan") && !permissionRequested) {
552
- const permRequest = deriveApprovalRequestFromEvent(
553
- event,
554
- convState.approvedTools,
555
- selection.providerId,
556
- mode
557
- );
558
- if (permRequest) {
559
- permissionRequested = true;
560
- procState.pendingTools = permRequest.tools || [permRequest.tool];
561
- job.status = "waiting_permission";
562
- this.emitAndBuffer(job, {
563
- type: "permission_request",
564
- tool: permRequest.tool,
565
- tools: procState.pendingTools,
566
- description: permRequest.description || `Permission required: ${permRequest.tool}`
567
- });
568
- (_b = procState.proc) == null ? void 0 : _b.kill();
569
- return;
570
- }
571
- }
572
- this.emitAndBuffer(job, { type: "ui_event", event });
573
- }
574
- },
575
- onClose: ({ exitCode, signal, nonJsonOutput }) => {
576
- if (procState.procGeneration !== generation) {
577
- return;
578
- }
579
- try {
580
- if (!permissionRequested) {
581
- if (exitCode !== 0 && exitCode !== null) {
582
- console.error("[JobQueue] Provider exited unexpectedly", {
583
- providerId: selection.providerId,
584
- modelKey: selection.modelKey,
585
- permissionMode: mode,
586
- requestId: msg.requestId,
587
- exitCode,
588
- signal,
589
- nonJsonOutput: nonJsonOutput.slice(-25)
590
- });
591
- const inferred = deriveApprovalRequestFromProcessOutput(nonJsonOutput, mode);
592
- if (inferred) {
593
- permissionRequested = true;
594
- procState.pendingTools = inferred.tools || [inferred.tool];
595
- job.status = "waiting_permission";
596
- this.emitAndBuffer(job, {
597
- type: "permission_request",
598
- tool: inferred.tool,
599
- tools: procState.pendingTools,
600
- description: inferred.description
601
- });
602
- procState.proc = null;
603
- return;
604
- }
605
- const hasPermissionError = hasCodexPermissionError(nonJsonOutput);
606
- const missingRolloutPath = hasCodexMissingRolloutPathError(nonJsonOutput);
607
- if (missingRolloutPath && !hasPermissionError && !isRetry) {
608
- this.emitAndBuffer(job, {
609
- type: "session_reset",
610
- reason: "Codex session state was missing rollout data. Retrying with a fresh ephemeral session."
611
- });
612
- convState.providerSessionId = null;
613
- procState.proc = null;
614
- this.runProvider(job, true, true);
615
- return;
616
- }
617
- if (usedResumeFlag && !isRetry) {
618
- const retryWithEphemeral = selection.providerId === "codex";
619
- this.emitAndBuffer(job, {
620
- type: "session_reset",
621
- reason: retryWithEphemeral ? `Session resume failed (exit code ${exitCode}). Retrying with a fresh ephemeral session.` : `Session resume failed (exit code ${exitCode}). Retrying with a fresh session.`
622
- });
623
- convState.providerSessionId = null;
624
- procState.proc = null;
625
- this.runProvider(job, true, retryWithEphemeral);
626
- return;
627
- }
628
- const summary = summarizeProviderProcessError(nonJsonOutput, 700);
629
- if (!summary && emittedTerminalErrorEvent && emittedRenderableContent) {
630
- this.emitAndBuffer(job, { type: "done", requestId: msg.requestId });
631
- this.setJobStatus(job, "done");
632
- return;
633
- }
634
- const details = summary ? ` \u2014 ${summary}` : "";
635
- this.emitAndBuffer(job, {
636
- type: "error",
637
- error: `Provider process exited unexpectedly (code: ${exitCode}${signal ? ", signal: " + signal : ""})${details}`,
638
- requestId: msg.requestId
639
- });
640
- this.setJobStatus(job, "error");
641
- } else if (exitCode === null && signal) {
642
- const summary = summarizeProviderProcessError(nonJsonOutput, 700);
643
- const details = summary ? ` \u2014 ${summary}` : "";
644
- this.emitAndBuffer(job, {
645
- type: "error",
646
- error: `Provider process was killed by signal ${signal}${details}`,
647
- requestId: msg.requestId
648
- });
649
- this.setJobStatus(job, "error");
650
- console.error("[JobQueue] Provider killed by signal", {
651
- providerId: selection.providerId,
652
- modelKey: selection.modelKey,
653
- permissionMode: mode,
654
- requestId: msg.requestId,
655
- signal,
656
- nonJsonOutput: nonJsonOutput.slice(-25)
657
- });
658
- } else {
659
- if (!emittedRenderableContent) {
660
- const summary = summarizeProviderProcessError(nonJsonOutput, 700);
661
- const fallbackText = summary ? `Provider returned no structured response.
662
-
663
- Raw output:
664
- ${summary}` : "Provider completed without returning visible response content.";
665
- this.emitAssistantText(job, fallbackText, convState.providerSessionId);
666
- }
667
- this.emitAndBuffer(job, { type: "done", requestId: msg.requestId });
668
- this.setJobStatus(job, "done");
669
- }
670
- }
671
- } finally {
672
- procState.proc = null;
673
- }
674
- },
675
- onError: (error) => {
676
- try {
677
- this.emitAndBuffer(job, {
678
- type: "error",
679
- error: `Provider process error: ${error.message}`,
680
- requestId: msg.requestId
681
- });
682
- this.setJobStatus(job, "error");
683
- } finally {
684
- procState.pendingTools = [];
685
- procState.proc = null;
686
- }
687
- }
738
+ onProviderSessionId: (providerSessionId) => {
739
+ convState.providerSessionId = providerSessionId;
740
+ updateConversationProviderSessionInStorage(job.conversationId, providerSessionId).catch((error) => {
741
+ console.warn("[JobQueue] Failed to persist provider session id:", error);
742
+ });
688
743
  }
689
- );
744
+ });
690
745
  } catch (error) {
691
- const errorMsg = error instanceof Error ? error.message : "Failed to start provider process";
692
746
  this.emitAndBuffer(job, {
693
747
  type: "error",
694
- error: errorMsg,
748
+ error: error instanceof Error ? error.message : "Failed to start provider terminal",
695
749
  requestId: msg.requestId
696
750
  });
697
751
  this.setJobStatus(job, "error");
698
- procState.pendingTools = [];
699
- procState.proc = null;
752
+ return;
700
753
  }
754
+ let finished = false;
755
+ let submitted = false;
756
+ let watchdog;
757
+ let idleCheck;
758
+ let unsubscribeExit;
759
+ let lastDataAt = Date.now();
760
+ let sawPostSubmitData = false;
761
+ const cleanup = () => {
762
+ monitor.stop();
763
+ if (watchdog) clearTimeout(watchdog);
764
+ if (idleCheck) clearInterval(idleCheck);
765
+ unsubscribeExit == null ? void 0 : unsubscribeExit();
766
+ };
767
+ const finalize = (lastAssistantMessage) => {
768
+ if (finished) return;
769
+ finished = true;
770
+ cleanup();
771
+ if (procState.procGeneration !== generation) return;
772
+ const text = lastAssistantMessage == null ? void 0 : lastAssistantMessage.trim();
773
+ if (text) this.emitAssistantText(job, text, convState.providerSessionId);
774
+ this.emitAndBuffer(job, { type: "cli_turn_stop_confirmed", requestId: msg.requestId });
775
+ this.emitAndBuffer(job, { type: "done", requestId: msg.requestId });
776
+ this.setJobStatus(job, "done");
777
+ procState.proc = null;
778
+ };
779
+ const finalizeError = (reason) => {
780
+ if (finished) return;
781
+ finished = true;
782
+ cleanup();
783
+ if (procState.procGeneration !== generation) return;
784
+ this.emitAndBuffer(job, { type: "error", error: reason, requestId: msg.requestId });
785
+ this.setJobStatus(job, "error");
786
+ procState.proc = null;
787
+ };
788
+ const monitor = startCliHookMonitor({
789
+ conversationId: job.conversationId,
790
+ emit: (event) => this.emitAndBuffer(job, event),
791
+ onStop: (lastAssistantMessage) => finalize(lastAssistantMessage),
792
+ isArmed: () => submitted,
793
+ // Only finalize on a Stop that follows this turn's own (post-submit)
794
+ // UserPromptSubmit, so a stray Stop from a superseded turn in the reused
795
+ // PTY can never be mistaken for completion. The output-idle watchdog below
796
+ // remains the guaranteed fallback if the prompt hook never lands.
797
+ requireArmedPromptSubmit: true
798
+ });
799
+ const handleExit = () => {
800
+ if (finished) return;
801
+ if (usedResumeFlag && !isRetry) {
802
+ finished = true;
803
+ cleanup();
804
+ if (procState.procGeneration !== generation) return;
805
+ convState.providerSessionId = null;
806
+ this.emitAndBuffer(job, { type: "session_reset", reason: "Resume failed; starting a fresh session" });
807
+ this.runProviderViaPty(job, true).catch((error) => {
808
+ this.emitAndBuffer(job, {
809
+ type: "error",
810
+ error: error instanceof Error ? error.message : "Provider retry failed",
811
+ requestId: msg.requestId
812
+ });
813
+ this.setJobStatus(job, "error");
814
+ });
815
+ return;
816
+ }
817
+ finalizeError("Provider terminal exited before the turn completed");
818
+ };
819
+ unsubscribeExit = subscribeTerminalSession(session, {
820
+ onData: () => {
821
+ lastDataAt = Date.now();
822
+ if (submitted) sawPostSubmitData = true;
823
+ },
824
+ onExit: () => handleExit()
825
+ });
826
+ idleCheck = setInterval(() => {
827
+ if (finished) return;
828
+ if (!submitted || !sawPostSubmitData) return;
829
+ if (Date.now() - lastDataAt >= PTY_IDLE_FINALIZE_MS) {
830
+ console.warn("[JobQueue] PTY turn completed via output-idle fallback (no Stop hook).");
831
+ finalize();
832
+ }
833
+ }, 500);
834
+ watchdog = setTimeout(() => finalizeError("Provider turn timed out"), PTY_TURN_TIMEOUT_MS);
835
+ procState.proc = {
836
+ kill: () => {
837
+ try {
838
+ writeTerminalInput(sessionId, "");
839
+ } catch {
840
+ }
841
+ cleanup();
842
+ }
843
+ };
844
+ this.injectMessage(
845
+ session,
846
+ sessionId,
847
+ fullMessage,
848
+ () => procState.procGeneration === generation && !finished,
849
+ () => {
850
+ submitted = true;
851
+ }
852
+ );
701
853
  }
702
854
  emitAssistantText(job, text, sessionId) {
703
855
  const blockId = `blk-${Date.now()}`;