@sudocode-ai/local-server 0.1.6 → 0.1.8

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 (361) hide show
  1. package/README.md +6 -0
  2. package/dist/errors/agent-errors.d.ts +43 -0
  3. package/dist/errors/agent-errors.d.ts.map +1 -0
  4. package/dist/errors/agent-errors.js +69 -0
  5. package/dist/errors/agent-errors.js.map +1 -0
  6. package/dist/execution/adapters/claude-adapter.d.ts +63 -0
  7. package/dist/execution/adapters/claude-adapter.d.ts.map +1 -0
  8. package/dist/execution/adapters/claude-adapter.js +82 -0
  9. package/dist/execution/adapters/claude-adapter.js.map +1 -0
  10. package/dist/execution/adapters/codex-adapter.d.ts +67 -0
  11. package/dist/execution/adapters/codex-adapter.d.ts.map +1 -0
  12. package/dist/execution/adapters/codex-adapter.js +183 -0
  13. package/dist/execution/adapters/codex-adapter.js.map +1 -0
  14. package/dist/execution/adapters/codex-config-builder.d.ts +30 -0
  15. package/dist/execution/adapters/codex-config-builder.d.ts.map +1 -0
  16. package/dist/execution/adapters/codex-config-builder.js +110 -0
  17. package/dist/execution/adapters/codex-config-builder.js.map +1 -0
  18. package/dist/execution/adapters/copilot-adapter.d.ts +94 -0
  19. package/dist/execution/adapters/copilot-adapter.d.ts.map +1 -0
  20. package/dist/execution/adapters/copilot-adapter.js +163 -0
  21. package/dist/execution/adapters/copilot-adapter.js.map +1 -0
  22. package/dist/execution/adapters/copilot-config-builder.d.ts +48 -0
  23. package/dist/execution/adapters/copilot-config-builder.d.ts.map +1 -0
  24. package/dist/execution/adapters/copilot-config-builder.js +125 -0
  25. package/dist/execution/adapters/copilot-config-builder.js.map +1 -0
  26. package/dist/execution/adapters/cursor-adapter.d.ts +66 -0
  27. package/dist/execution/adapters/cursor-adapter.d.ts.map +1 -0
  28. package/dist/execution/adapters/cursor-adapter.js +121 -0
  29. package/dist/execution/adapters/cursor-adapter.js.map +1 -0
  30. package/dist/execution/adapters/cursor-config-builder.d.ts +29 -0
  31. package/dist/execution/adapters/cursor-config-builder.d.ts.map +1 -0
  32. package/dist/execution/adapters/cursor-config-builder.js +49 -0
  33. package/dist/execution/adapters/cursor-config-builder.js.map +1 -0
  34. package/dist/execution/adapters/shared/config-presets.d.ts +102 -0
  35. package/dist/execution/adapters/shared/config-presets.d.ts.map +1 -0
  36. package/dist/execution/adapters/shared/config-presets.js +205 -0
  37. package/dist/execution/adapters/shared/config-presets.js.map +1 -0
  38. package/dist/execution/adapters/shared/config-utils.d.ts +95 -0
  39. package/dist/execution/adapters/shared/config-utils.d.ts.map +1 -0
  40. package/dist/execution/adapters/shared/config-utils.js +163 -0
  41. package/dist/execution/adapters/shared/config-utils.js.map +1 -0
  42. package/dist/execution/adapters/shared/index.d.ts +8 -0
  43. package/dist/execution/adapters/shared/index.d.ts.map +1 -0
  44. package/dist/execution/adapters/shared/index.js +8 -0
  45. package/dist/execution/adapters/shared/index.js.map +1 -0
  46. package/dist/execution/executors/agent-executor-wrapper.d.ts +153 -0
  47. package/dist/execution/executors/agent-executor-wrapper.d.ts.map +1 -0
  48. package/dist/execution/executors/agent-executor-wrapper.js +652 -0
  49. package/dist/execution/executors/agent-executor-wrapper.js.map +1 -0
  50. package/dist/execution/executors/executor-factory.d.ts +95 -0
  51. package/dist/execution/executors/executor-factory.d.ts.map +1 -0
  52. package/dist/execution/executors/executor-factory.js +120 -0
  53. package/dist/execution/executors/executor-factory.js.map +1 -0
  54. package/dist/execution/output/ag-ui-adapter.d.ts +0 -2
  55. package/dist/execution/output/ag-ui-adapter.d.ts.map +1 -1
  56. package/dist/execution/output/ag-ui-adapter.js +0 -2
  57. package/dist/execution/output/ag-ui-adapter.js.map +1 -1
  58. package/dist/execution/output/index.d.ts +0 -3
  59. package/dist/execution/output/index.d.ts.map +1 -1
  60. package/dist/execution/output/index.js +0 -2
  61. package/dist/execution/output/index.js.map +1 -1
  62. package/dist/execution/output/normalized-to-ag-ui-adapter.d.ts +108 -0
  63. package/dist/execution/output/normalized-to-ag-ui-adapter.d.ts.map +1 -0
  64. package/dist/execution/output/normalized-to-ag-ui-adapter.js +321 -0
  65. package/dist/execution/output/normalized-to-ag-ui-adapter.js.map +1 -0
  66. package/dist/execution/process/builders/claude.d.ts +25 -58
  67. package/dist/execution/process/builders/claude.d.ts.map +1 -1
  68. package/dist/execution/process/builders/claude.js +153 -19
  69. package/dist/execution/process/builders/claude.js.map +1 -1
  70. package/dist/execution/transport/ipc-transport-manager.d.ts +74 -0
  71. package/dist/execution/transport/ipc-transport-manager.d.ts.map +1 -0
  72. package/dist/execution/transport/ipc-transport-manager.js +104 -0
  73. package/dist/execution/transport/ipc-transport-manager.js.map +1 -0
  74. package/dist/execution/transport/transport-manager.d.ts.map +1 -1
  75. package/dist/execution/transport/transport-manager.js +3 -0
  76. package/dist/execution/transport/transport-manager.js.map +1 -1
  77. package/dist/execution/worktree/conflict-detector.d.ts +85 -0
  78. package/dist/execution/worktree/conflict-detector.d.ts.map +1 -0
  79. package/dist/execution/worktree/conflict-detector.js +129 -0
  80. package/dist/execution/worktree/conflict-detector.js.map +1 -0
  81. package/dist/execution/worktree/git-cli.d.ts +9 -0
  82. package/dist/execution/worktree/git-cli.d.ts.map +1 -1
  83. package/dist/execution/worktree/git-cli.js +10 -0
  84. package/dist/execution/worktree/git-cli.js.map +1 -1
  85. package/dist/execution/worktree/git-sync-cli.d.ts +187 -0
  86. package/dist/execution/worktree/git-sync-cli.d.ts.map +1 -0
  87. package/dist/execution/worktree/git-sync-cli.js +350 -0
  88. package/dist/execution/worktree/git-sync-cli.js.map +1 -0
  89. package/dist/execution/worktree/manager.d.ts +18 -0
  90. package/dist/execution/worktree/manager.d.ts.map +1 -1
  91. package/dist/execution/worktree/manager.js +9 -3
  92. package/dist/execution/worktree/manager.js.map +1 -1
  93. package/dist/index.d.ts +1 -3
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +175 -195
  96. package/dist/index.js.map +1 -1
  97. package/dist/middleware/project-context.d.ts +37 -0
  98. package/dist/middleware/project-context.d.ts.map +1 -0
  99. package/dist/middleware/project-context.js +91 -0
  100. package/dist/middleware/project-context.js.map +1 -0
  101. package/dist/public/assets/index-Bb_W5bUr.css +1 -0
  102. package/dist/public/assets/index-CFKL113G.js +710 -0
  103. package/dist/public/assets/index-CFKL113G.js.map +1 -0
  104. package/dist/public/assets/{react-vendor-ByUx1V_q.js → react-vendor-DiL5hC7l.js} +2 -2
  105. package/dist/public/assets/{react-vendor-ByUx1V_q.js.map → react-vendor-DiL5hC7l.js.map} +1 -1
  106. package/dist/public/assets/ui-vendor-B4WMPEfa.js +54 -0
  107. package/dist/public/assets/ui-vendor-B4WMPEfa.js.map +1 -0
  108. package/dist/public/index.html +4 -4
  109. package/dist/routes/agents.d.ts +3 -0
  110. package/dist/routes/agents.d.ts.map +1 -0
  111. package/dist/routes/agents.js +62 -0
  112. package/dist/routes/agents.js.map +1 -0
  113. package/dist/routes/config.d.ts +3 -0
  114. package/dist/routes/config.d.ts.map +1 -0
  115. package/dist/routes/config.js +25 -0
  116. package/dist/routes/config.js.map +1 -0
  117. package/dist/routes/editors.d.ts +15 -0
  118. package/dist/routes/editors.d.ts.map +1 -0
  119. package/dist/routes/editors.js +98 -0
  120. package/dist/routes/editors.js.map +1 -0
  121. package/dist/routes/executions-stream.d.ts +8 -5
  122. package/dist/routes/executions-stream.d.ts.map +1 -1
  123. package/dist/routes/executions-stream.js +10 -6
  124. package/dist/routes/executions-stream.js.map +1 -1
  125. package/dist/routes/executions.d.ts +6 -10
  126. package/dist/routes/executions.d.ts.map +1 -1
  127. package/dist/routes/executions.js +506 -54
  128. package/dist/routes/executions.js.map +1 -1
  129. package/dist/routes/feedback.d.ts +3 -2
  130. package/dist/routes/feedback.d.ts.map +1 -1
  131. package/dist/routes/feedback.js +32 -22
  132. package/dist/routes/feedback.js.map +1 -1
  133. package/dist/routes/files.d.ts +18 -0
  134. package/dist/routes/files.d.ts.map +1 -0
  135. package/dist/routes/files.js +89 -0
  136. package/dist/routes/files.js.map +1 -0
  137. package/dist/routes/issues.d.ts +3 -2
  138. package/dist/routes/issues.d.ts.map +1 -1
  139. package/dist/routes/issues.js +19 -18
  140. package/dist/routes/issues.js.map +1 -1
  141. package/dist/routes/projects.d.ts +11 -0
  142. package/dist/routes/projects.d.ts.map +1 -0
  143. package/dist/routes/projects.js +447 -0
  144. package/dist/routes/projects.js.map +1 -0
  145. package/dist/routes/relationships.d.ts +3 -2
  146. package/dist/routes/relationships.d.ts.map +1 -1
  147. package/dist/routes/relationships.js +12 -10
  148. package/dist/routes/relationships.js.map +1 -1
  149. package/dist/routes/repo-info.d.ts +3 -0
  150. package/dist/routes/repo-info.d.ts.map +1 -0
  151. package/dist/routes/repo-info.js +126 -0
  152. package/dist/routes/repo-info.js.map +1 -0
  153. package/dist/routes/specs.d.ts +3 -2
  154. package/dist/routes/specs.d.ts.map +1 -1
  155. package/dist/routes/specs.js +19 -18
  156. package/dist/routes/specs.js.map +1 -1
  157. package/dist/services/agent-registry.d.ts +140 -0
  158. package/dist/services/agent-registry.d.ts.map +1 -0
  159. package/dist/services/agent-registry.js +272 -0
  160. package/dist/services/agent-registry.js.map +1 -0
  161. package/dist/services/db.d.ts.map +1 -1
  162. package/dist/services/db.js +19 -14
  163. package/dist/services/db.js.map +1 -1
  164. package/dist/services/editor-service.d.ts +57 -0
  165. package/dist/services/editor-service.d.ts.map +1 -0
  166. package/dist/services/editor-service.js +204 -0
  167. package/dist/services/editor-service.js.map +1 -0
  168. package/dist/services/execution-changes-service.d.ts +92 -0
  169. package/dist/services/execution-changes-service.d.ts.map +1 -0
  170. package/dist/services/execution-changes-service.js +546 -0
  171. package/dist/services/execution-changes-service.js.map +1 -0
  172. package/dist/services/execution-lifecycle.d.ts +1 -0
  173. package/dist/services/execution-lifecycle.d.ts.map +1 -1
  174. package/dist/services/execution-lifecycle.js +37 -7
  175. package/dist/services/execution-lifecycle.js.map +1 -1
  176. package/dist/services/execution-logs-store.d.ts +75 -0
  177. package/dist/services/execution-logs-store.d.ts.map +1 -1
  178. package/dist/services/execution-logs-store.js +142 -2
  179. package/dist/services/execution-logs-store.js.map +1 -1
  180. package/dist/services/execution-service.d.ts +50 -58
  181. package/dist/services/execution-service.d.ts.map +1 -1
  182. package/dist/services/execution-service.js +433 -448
  183. package/dist/services/execution-service.js.map +1 -1
  184. package/dist/services/execution-worker-pool.d.ts +116 -0
  185. package/dist/services/execution-worker-pool.d.ts.map +1 -0
  186. package/dist/services/execution-worker-pool.js +326 -0
  187. package/dist/services/execution-worker-pool.js.map +1 -0
  188. package/dist/services/executions.d.ts +3 -0
  189. package/dist/services/executions.d.ts.map +1 -1
  190. package/dist/services/executions.js +11 -2
  191. package/dist/services/executions.js.map +1 -1
  192. package/dist/services/export.d.ts +8 -2
  193. package/dist/services/export.d.ts.map +1 -1
  194. package/dist/services/export.js +29 -23
  195. package/dist/services/export.js.map +1 -1
  196. package/dist/services/feedback.d.ts +16 -0
  197. package/dist/services/feedback.d.ts.map +1 -1
  198. package/dist/services/feedback.js +25 -1
  199. package/dist/services/feedback.js.map +1 -1
  200. package/dist/services/file-search/git-ls-files-strategy.d.ts +72 -0
  201. package/dist/services/file-search/git-ls-files-strategy.d.ts.map +1 -0
  202. package/dist/services/file-search/git-ls-files-strategy.js +176 -0
  203. package/dist/services/file-search/git-ls-files-strategy.js.map +1 -0
  204. package/dist/services/file-search/index.d.ts +9 -0
  205. package/dist/services/file-search/index.d.ts.map +1 -0
  206. package/dist/services/file-search/index.js +10 -0
  207. package/dist/services/file-search/index.js.map +1 -0
  208. package/dist/services/file-search/registry.d.ts +97 -0
  209. package/dist/services/file-search/registry.d.ts.map +1 -0
  210. package/dist/services/file-search/registry.js +140 -0
  211. package/dist/services/file-search/registry.js.map +1 -0
  212. package/dist/services/file-search/strategy.d.ts +58 -0
  213. package/dist/services/file-search/strategy.d.ts.map +1 -0
  214. package/dist/services/file-search/strategy.js +8 -0
  215. package/dist/services/file-search/strategy.js.map +1 -0
  216. package/dist/services/project-context.d.ts +69 -0
  217. package/dist/services/project-context.d.ts.map +1 -0
  218. package/dist/services/project-context.js +113 -0
  219. package/dist/services/project-context.js.map +1 -0
  220. package/dist/services/project-manager.d.ts +95 -0
  221. package/dist/services/project-manager.d.ts.map +1 -0
  222. package/dist/services/project-manager.js +388 -0
  223. package/dist/services/project-manager.js.map +1 -0
  224. package/dist/services/project-registry.d.ts +98 -0
  225. package/dist/services/project-registry.d.ts.map +1 -0
  226. package/dist/services/project-registry.js +289 -0
  227. package/dist/services/project-registry.js.map +1 -0
  228. package/dist/services/prompt-resolver.d.ts +97 -0
  229. package/dist/services/prompt-resolver.d.ts.map +1 -0
  230. package/dist/services/prompt-resolver.js +377 -0
  231. package/dist/services/prompt-resolver.js.map +1 -0
  232. package/dist/services/repo-info.d.ts +29 -0
  233. package/dist/services/repo-info.d.ts.map +1 -0
  234. package/dist/services/repo-info.js +99 -0
  235. package/dist/services/repo-info.js.map +1 -0
  236. package/dist/services/watcher.d.ts +3 -4
  237. package/dist/services/watcher.d.ts.map +1 -1
  238. package/dist/services/watcher.js +18 -35
  239. package/dist/services/watcher.js.map +1 -1
  240. package/dist/services/websocket.d.ts +51 -15
  241. package/dist/services/websocket.d.ts.map +1 -1
  242. package/dist/services/websocket.js +169 -40
  243. package/dist/services/websocket.js.map +1 -1
  244. package/dist/services/worktree-sync-service.d.ts +228 -0
  245. package/dist/services/worktree-sync-service.d.ts.map +1 -0
  246. package/dist/services/worktree-sync-service.js +563 -0
  247. package/dist/services/worktree-sync-service.js.map +1 -0
  248. package/dist/types/editor.d.ts +49 -0
  249. package/dist/types/editor.d.ts.map +1 -0
  250. package/dist/types/editor.js +50 -0
  251. package/dist/types/editor.js.map +1 -0
  252. package/dist/types/project.d.ts +58 -0
  253. package/dist/types/project.d.ts.map +1 -0
  254. package/dist/types/project.js +10 -0
  255. package/dist/types/project.js.map +1 -0
  256. package/dist/utils/executable-check.d.ts +36 -0
  257. package/dist/utils/executable-check.d.ts.map +1 -0
  258. package/dist/utils/executable-check.js +79 -0
  259. package/dist/utils/executable-check.js.map +1 -0
  260. package/dist/workers/execution-worker.d.ts +18 -0
  261. package/dist/workers/execution-worker.d.ts.map +1 -0
  262. package/dist/workers/execution-worker.js +340 -0
  263. package/dist/workers/execution-worker.js.map +1 -0
  264. package/dist/workers/worker-ipc.d.ts +84 -0
  265. package/dist/workers/worker-ipc.d.ts.map +1 -0
  266. package/dist/workers/worker-ipc.js +29 -0
  267. package/dist/workers/worker-ipc.js.map +1 -0
  268. package/package.json +6 -4
  269. package/dist/execution/engine/engine.d.ts +0 -103
  270. package/dist/execution/engine/engine.d.ts.map +0 -1
  271. package/dist/execution/engine/engine.js +0 -10
  272. package/dist/execution/engine/engine.js.map +0 -1
  273. package/dist/execution/engine/simple-engine.d.ts +0 -190
  274. package/dist/execution/engine/simple-engine.d.ts.map +0 -1
  275. package/dist/execution/engine/simple-engine.js +0 -611
  276. package/dist/execution/engine/simple-engine.js.map +0 -1
  277. package/dist/execution/engine/types.d.ts +0 -116
  278. package/dist/execution/engine/types.d.ts.map +0 -1
  279. package/dist/execution/engine/types.js +0 -10
  280. package/dist/execution/engine/types.js.map +0 -1
  281. package/dist/execution/output/ag-ui-integration.d.ts +0 -96
  282. package/dist/execution/output/ag-ui-integration.d.ts.map +0 -1
  283. package/dist/execution/output/ag-ui-integration.js +0 -96
  284. package/dist/execution/output/ag-ui-integration.js.map +0 -1
  285. package/dist/execution/output/claude-code-output-processor.d.ts +0 -321
  286. package/dist/execution/output/claude-code-output-processor.d.ts.map +0 -1
  287. package/dist/execution/output/claude-code-output-processor.js +0 -769
  288. package/dist/execution/output/claude-code-output-processor.js.map +0 -1
  289. package/dist/execution/process/index.d.ts +0 -15
  290. package/dist/execution/process/index.d.ts.map +0 -1
  291. package/dist/execution/process/index.js +0 -15
  292. package/dist/execution/process/index.js.map +0 -1
  293. package/dist/execution/process/manager.d.ts +0 -133
  294. package/dist/execution/process/manager.d.ts.map +0 -1
  295. package/dist/execution/process/manager.js +0 -10
  296. package/dist/execution/process/manager.js.map +0 -1
  297. package/dist/execution/process/simple-manager.d.ts +0 -102
  298. package/dist/execution/process/simple-manager.d.ts.map +0 -1
  299. package/dist/execution/process/simple-manager.js +0 -336
  300. package/dist/execution/process/simple-manager.js.map +0 -1
  301. package/dist/execution/process/types.d.ts +0 -105
  302. package/dist/execution/process/types.d.ts.map +0 -1
  303. package/dist/execution/process/types.js +0 -10
  304. package/dist/execution/process/types.js.map +0 -1
  305. package/dist/execution/process/utils.d.ts +0 -53
  306. package/dist/execution/process/utils.d.ts.map +0 -1
  307. package/dist/execution/process/utils.js +0 -97
  308. package/dist/execution/process/utils.js.map +0 -1
  309. package/dist/execution/resilience/circuit-breaker.d.ts +0 -170
  310. package/dist/execution/resilience/circuit-breaker.d.ts.map +0 -1
  311. package/dist/execution/resilience/circuit-breaker.js +0 -291
  312. package/dist/execution/resilience/circuit-breaker.js.map +0 -1
  313. package/dist/execution/resilience/executor.d.ts +0 -109
  314. package/dist/execution/resilience/executor.d.ts.map +0 -1
  315. package/dist/execution/resilience/executor.js +0 -10
  316. package/dist/execution/resilience/executor.js.map +0 -1
  317. package/dist/execution/resilience/index.d.ts +0 -14
  318. package/dist/execution/resilience/index.d.ts.map +0 -1
  319. package/dist/execution/resilience/index.js +0 -15
  320. package/dist/execution/resilience/index.js.map +0 -1
  321. package/dist/execution/resilience/resilient-executor.d.ts +0 -86
  322. package/dist/execution/resilience/resilient-executor.d.ts.map +0 -1
  323. package/dist/execution/resilience/resilient-executor.js +0 -261
  324. package/dist/execution/resilience/resilient-executor.js.map +0 -1
  325. package/dist/execution/resilience/retry.d.ts +0 -161
  326. package/dist/execution/resilience/retry.d.ts.map +0 -1
  327. package/dist/execution/resilience/retry.js +0 -234
  328. package/dist/execution/resilience/retry.js.map +0 -1
  329. package/dist/execution/resilience/types.d.ts +0 -226
  330. package/dist/execution/resilience/types.d.ts.map +0 -1
  331. package/dist/execution/resilience/types.js +0 -30
  332. package/dist/execution/resilience/types.js.map +0 -1
  333. package/dist/execution/workflow/index.d.ts +0 -13
  334. package/dist/execution/workflow/index.d.ts.map +0 -1
  335. package/dist/execution/workflow/index.js +0 -13
  336. package/dist/execution/workflow/index.js.map +0 -1
  337. package/dist/execution/workflow/linear-orchestrator.d.ts +0 -216
  338. package/dist/execution/workflow/linear-orchestrator.d.ts.map +0 -1
  339. package/dist/execution/workflow/linear-orchestrator.js +0 -683
  340. package/dist/execution/workflow/linear-orchestrator.js.map +0 -1
  341. package/dist/execution/workflow/memory-storage.d.ts +0 -54
  342. package/dist/execution/workflow/memory-storage.d.ts.map +0 -1
  343. package/dist/execution/workflow/memory-storage.js +0 -68
  344. package/dist/execution/workflow/memory-storage.js.map +0 -1
  345. package/dist/execution/workflow/orchestrator.d.ts +0 -158
  346. package/dist/execution/workflow/orchestrator.d.ts.map +0 -1
  347. package/dist/execution/workflow/orchestrator.js +0 -9
  348. package/dist/execution/workflow/orchestrator.js.map +0 -1
  349. package/dist/execution/workflow/types.d.ts +0 -172
  350. package/dist/execution/workflow/types.d.ts.map +0 -1
  351. package/dist/execution/workflow/types.js +0 -9
  352. package/dist/execution/workflow/types.js.map +0 -1
  353. package/dist/execution/workflow/utils.d.ts +0 -89
  354. package/dist/execution/workflow/utils.d.ts.map +0 -1
  355. package/dist/execution/workflow/utils.js +0 -152
  356. package/dist/execution/workflow/utils.js.map +0 -1
  357. package/dist/public/assets/index-BvlblDHK.js +0 -570
  358. package/dist/public/assets/index-BvlblDHK.js.map +0 -1
  359. package/dist/public/assets/index-BwuKdLgN.css +0 -1
  360. package/dist/public/assets/ui-vendor-CotR6bx9.js +0 -54
  361. package/dist/public/assets/ui-vendor-CotR6bx9.js.map +0 -1
@@ -6,140 +6,50 @@
6
6
  *
7
7
  * @module services/execution-service
8
8
  */
9
- import { PromptTemplateEngine } from "./prompt-template-engine.js";
10
9
  import { ExecutionLifecycleService } from "./execution-lifecycle.js";
11
10
  import { createExecution, getExecution, updateExecution, } from "./executions.js";
12
- import { getDefaultTemplate, getTemplateById } from "./prompt-templates.js";
13
11
  import { randomUUID } from "crypto";
14
- import { SimpleProcessManager } from "../execution/process/simple-manager.js";
15
- import { SimpleExecutionEngine } from "../execution/engine/simple-engine.js";
16
- import { ResilientExecutor } from "../execution/resilience/resilient-executor.js";
17
- import { LinearOrchestrator } from "../execution/workflow/linear-orchestrator.js";
18
- import { createAgUiSystem } from "../execution/output/ag-ui-integration.js";
12
+ import { execSync } from "child_process";
19
13
  import { ExecutionLogsStore } from "./execution-logs-store.js";
14
+ import { broadcastExecutionUpdate } from "./websocket.js";
15
+ import { createExecutorForAgent } from "../execution/executors/executor-factory.js";
16
+ import { PromptResolver } from "./prompt-resolver.js";
20
17
  /**
21
18
  * ExecutionService
22
19
  *
23
20
  * Manages the full lifecycle of issue-based executions:
24
- * - Preparing execution with template rendering
25
21
  * - Creating and starting executions with worktree isolation
26
22
  * - Creating follow-up executions that reuse worktrees
27
23
  * - Canceling and cleaning up executions
28
24
  */
29
25
  export class ExecutionService {
30
26
  db;
31
- templateEngine;
27
+ projectId;
32
28
  lifecycleService;
33
29
  repoPath;
34
30
  transportManager;
35
31
  logsStore;
36
- activeOrchestrators = new Map();
32
+ workerPool;
37
33
  /**
38
34
  * Create a new ExecutionService
39
35
  *
40
36
  * @param db - Database instance
37
+ * @param projectId - Project ID for WebSocket broadcasts
41
38
  * @param repoPath - Path to the git repository
42
39
  * @param lifecycleService - Optional execution lifecycle service (creates one if not provided)
43
40
  * @param transportManager - Optional transport manager for SSE streaming
44
41
  * @param logsStore - Optional execution logs store (creates one if not provided)
42
+ * @param workerPool - Optional worker pool for isolated execution processes
45
43
  */
46
- constructor(db, repoPath, lifecycleService, transportManager, logsStore) {
44
+ constructor(db, projectId, repoPath, lifecycleService, transportManager, logsStore, workerPool) {
47
45
  this.db = db;
46
+ this.projectId = projectId;
48
47
  this.repoPath = repoPath;
49
- this.templateEngine = new PromptTemplateEngine();
50
48
  this.lifecycleService =
51
49
  lifecycleService || new ExecutionLifecycleService(db, repoPath);
52
50
  this.transportManager = transportManager;
53
51
  this.logsStore = logsStore || new ExecutionLogsStore(db);
54
- }
55
- /**
56
- * Prepare execution - load issue, render template, return preview
57
- *
58
- * This method loads the issue and related context, renders the template,
59
- * and returns a preview for the user to review before starting execution.
60
- *
61
- * @param issueId - ID of issue to prepare execution for
62
- * @param options - Optional template and config overrides
63
- * @returns Execution prepare result with rendered prompt and context
64
- */
65
- async prepareExecution(issueId, options) {
66
- // 1. Load issue
67
- const issue = this.db
68
- .prepare("SELECT * FROM issues WHERE id = ?")
69
- .get(issueId);
70
- if (!issue) {
71
- throw new Error(`Issue ${issueId} not found`);
72
- }
73
- // 2. Load related specs (via implements/references relationships)
74
- const relatedSpecs = this.db
75
- .prepare(`
76
- SELECT DISTINCT s.id, s.title
77
- FROM specs s
78
- JOIN relationships r ON r.to_id = s.id AND r.to_type = 'spec'
79
- WHERE r.from_id = ? AND r.from_type = 'issue'
80
- AND r.relationship_type IN ('implements', 'references')
81
- ORDER BY s.title
82
- `)
83
- .all(issueId);
84
- // 3. Build context for template rendering
85
- const context = {
86
- issueId: issue.id,
87
- title: issue.title,
88
- description: issue.content,
89
- relatedSpecs: relatedSpecs.length > 0
90
- ? relatedSpecs.map((s) => ({
91
- id: s.id,
92
- title: s.title,
93
- }))
94
- : undefined,
95
- };
96
- // 4. Get template (use custom template if provided, otherwise default)
97
- let template;
98
- if (options?.templateId) {
99
- const customTemplate = getTemplateById(this.db, options.templateId);
100
- if (!customTemplate) {
101
- throw new Error(`Template ${options.templateId} not found`);
102
- }
103
- template = customTemplate.template;
104
- }
105
- else {
106
- const defaultTemplate = getDefaultTemplate(this.db, "issue");
107
- if (!defaultTemplate) {
108
- throw new Error("Default issue template not found");
109
- }
110
- template = defaultTemplate.template;
111
- }
112
- // 5. Render template
113
- const renderedPrompt = this.templateEngine.render(template, context);
114
- // 6. Get default config
115
- const defaultConfig = {
116
- mode: "worktree",
117
- model: "claude-sonnet-4",
118
- baseBranch: "main",
119
- checkpointInterval: 1,
120
- continueOnStepFailure: false,
121
- captureFileChanges: true,
122
- captureToolCalls: true,
123
- ...options?.config,
124
- };
125
- // 7. Validate
126
- const warnings = [];
127
- const errors = [];
128
- if (!renderedPrompt.trim()) {
129
- errors.push("Rendered prompt is empty");
130
- }
131
- return {
132
- renderedPrompt,
133
- issue: {
134
- id: issue.id,
135
- title: issue.title,
136
- content: issue.content,
137
- },
138
- relatedSpecs,
139
- defaultConfig,
140
- warnings,
141
- errors,
142
- };
52
+ this.workerPool = workerPool;
143
53
  }
144
54
  /**
145
55
  * Create and start execution
@@ -151,9 +61,10 @@ export class ExecutionService {
151
61
  * @param issueId - ID of issue to execute
152
62
  * @param config - Execution configuration
153
63
  * @param prompt - Rendered prompt to execute
64
+ * @param agentType - Type of agent to use (defaults to 'claude-code')
154
65
  * @returns Created execution record
155
66
  */
156
- async createExecution(issueId, config, prompt) {
67
+ async createExecution(issueId, config, prompt, agentType = "claude-code") {
157
68
  // 1. Validate
158
69
  if (!prompt.trim()) {
159
70
  throw new Error("Prompt cannot be empty");
@@ -165,23 +76,51 @@ export class ExecutionService {
165
76
  throw new Error(`Issue ${issueId} not found`);
166
77
  }
167
78
  // 2. Determine execution mode and create execution with worktree
79
+ // Store the original (unexpanded) prompt in the database
168
80
  const mode = config.mode || "worktree";
169
81
  let execution;
170
82
  let workDir;
171
83
  if (mode === "worktree") {
172
- // Create execution with isolated worktree
173
- const result = await this.lifecycleService.createExecutionWithWorktree({
174
- issueId,
175
- issueTitle: issue.title,
176
- agentType: "claude-code",
177
- targetBranch: config.baseBranch || "main",
178
- repoPath: this.repoPath,
179
- mode: mode,
180
- prompt: prompt,
181
- config: JSON.stringify(config),
182
- });
183
- execution = result.execution;
184
- workDir = result.worktreePath;
84
+ // Check if we're reusing an existing worktree
85
+ if (config.reuseWorktreeId) {
86
+ // Reuse existing worktree
87
+ const existingExecution = this.db
88
+ .prepare("SELECT * FROM executions WHERE id = ?")
89
+ .get(config.reuseWorktreeId);
90
+ if (!existingExecution || !existingExecution.worktree_path) {
91
+ throw new Error(`Cannot reuse worktree: execution ${config.reuseWorktreeId} not found or has no worktree`);
92
+ }
93
+ // Create execution record with the same worktree path
94
+ const executionId = randomUUID();
95
+ execution = createExecution(this.db, {
96
+ id: executionId,
97
+ issue_id: issueId,
98
+ agent_type: agentType,
99
+ mode: mode,
100
+ prompt: prompt,
101
+ config: JSON.stringify(config),
102
+ target_branch: existingExecution.target_branch,
103
+ branch_name: existingExecution.branch_name,
104
+ worktree_path: existingExecution.worktree_path, // Reuse the same worktree path
105
+ });
106
+ workDir = existingExecution.worktree_path;
107
+ }
108
+ else {
109
+ // Create execution with isolated worktree
110
+ const result = await this.lifecycleService.createExecutionWithWorktree({
111
+ issueId,
112
+ issueTitle: issue.title,
113
+ agentType: agentType,
114
+ targetBranch: config.baseBranch || "main",
115
+ repoPath: this.repoPath,
116
+ mode: mode,
117
+ prompt: prompt, // Store original (unexpanded) prompt
118
+ config: JSON.stringify(config),
119
+ createTargetBranch: config.createBaseBranch || false,
120
+ });
121
+ execution = result.execution;
122
+ workDir = result.worktreePath;
123
+ }
185
124
  }
186
125
  else {
187
126
  // Local mode - create execution without worktree
@@ -189,14 +128,40 @@ export class ExecutionService {
189
128
  execution = createExecution(this.db, {
190
129
  id: executionId,
191
130
  issue_id: issueId,
192
- agent_type: "claude-code",
131
+ agent_type: agentType,
193
132
  mode: mode,
194
- prompt: prompt,
133
+ prompt: prompt, // Store original (unexpanded) prompt
195
134
  config: JSON.stringify(config),
196
135
  target_branch: config.baseBranch || "main",
197
136
  branch_name: config.baseBranch || "main",
198
137
  });
199
138
  workDir = this.repoPath;
139
+ // Capture current commit as before_commit for local mode
140
+ try {
141
+ const beforeCommit = execSync("git rev-parse HEAD", {
142
+ cwd: this.repoPath,
143
+ encoding: "utf-8",
144
+ }).trim();
145
+ updateExecution(this.db, executionId, {
146
+ before_commit: beforeCommit,
147
+ });
148
+ // Reload execution to get updated before_commit
149
+ const updatedExecution = getExecution(this.db, executionId);
150
+ if (updatedExecution) {
151
+ execution = updatedExecution;
152
+ }
153
+ }
154
+ catch (error) {
155
+ console.warn("[ExecutionService] Failed to capture before_commit for local mode:", error instanceof Error ? error.message : String(error));
156
+ // Continue - this is supplementary data
157
+ }
158
+ }
159
+ // 3. Resolve prompt references for execution (done after storing original)
160
+ // Pass the issue ID so the issue content is automatically included even if not explicitly mentioned
161
+ const resolver = new PromptResolver(this.db);
162
+ const { resolvedPrompt, errors } = await resolver.resolve(prompt, new Set(), issueId);
163
+ if (errors.length > 0) {
164
+ console.warn(`[ExecutionService] Prompt resolution warnings:`, errors);
200
165
  }
201
166
  // Initialize empty logs for this execution
202
167
  try {
@@ -209,187 +174,76 @@ export class ExecutionService {
209
174
  });
210
175
  // Don't fail execution creation - logs are nice-to-have
211
176
  }
212
- // 3. Build WorkflowDefinition
213
- const workflow = {
214
- id: `workflow-${execution.id}`,
215
- steps: [
216
- {
217
- id: "execute-issue",
218
- taskType: "issue",
219
- prompt,
220
- taskConfig: {
221
- model: config.model || "claude-sonnet-4",
222
- timeout: config.timeout,
223
- captureFileChanges: config.captureFileChanges ?? true,
224
- captureToolCalls: config.captureToolCalls ?? true,
225
- },
226
- },
227
- ],
177
+ // 3. Start execution (use worker pool if available, otherwise fall back to in-process)
178
+ if (this.workerPool) {
179
+ // Worker pool handles all execution logic in isolated process
180
+ const dbPath = this.db.name;
181
+ await this.workerPool.startExecution(execution, this.repoPath, dbPath);
182
+ // Broadcast execution creation
183
+ broadcastExecutionUpdate(this.projectId, execution.id, "created", execution, execution.issue_id || undefined);
184
+ return execution;
185
+ }
186
+ // 4. In-process execution with executor wrapper (fallback when no worker pool)
187
+ const wrapper = createExecutorForAgent(agentType, { workDir: this.repoPath }, // Agent-specific config (minimal for now)
188
+ {
189
+ workDir: this.repoPath,
190
+ lifecycleService: this.lifecycleService,
191
+ logsStore: this.logsStore,
192
+ projectId: this.projectId,
193
+ db: this.db,
194
+ transportManager: this.transportManager,
195
+ });
196
+ // Build execution task (prompt already resolved above)
197
+ const task = {
198
+ id: execution.id,
199
+ type: "issue",
200
+ entityId: issueId,
201
+ prompt: resolvedPrompt,
202
+ workDir: workDir,
228
203
  config: {
229
- checkpointInterval: config.checkpointInterval ?? 1,
230
- continueOnStepFailure: config.continueOnStepFailure ?? false,
231
204
  timeout: config.timeout,
232
205
  },
233
206
  metadata: {
234
- workDir,
207
+ model: config.model || "claude-sonnet-4",
208
+ captureFileChanges: config.captureFileChanges ?? true,
209
+ captureToolCalls: config.captureToolCalls ?? true,
235
210
  issueId,
236
211
  executionId: execution.id,
237
212
  },
213
+ priority: 0,
214
+ dependencies: [],
215
+ createdAt: new Date(),
238
216
  };
239
- // 4. Create execution engine stack
240
- const processManager = new SimpleProcessManager({
241
- executablePath: "claude",
242
- args: [
243
- "--print",
244
- "--output-format",
245
- "stream-json",
246
- "--dangerously-skip-permissions",
247
- ],
248
- });
249
- let engine = new SimpleExecutionEngine(processManager, {
250
- maxConcurrent: 1, // One task at a time for issue execution
217
+ // Execute with full lifecycle management (non-blocking)
218
+ wrapper.executeWithLifecycle(execution.id, task, workDir).catch((error) => {
219
+ console.error(`[ExecutionService] Execution ${execution.id} failed:`, error);
220
+ // Error is already handled by wrapper (status updated, broadcasts sent)
251
221
  });
252
- let executor = new ResilientExecutor(engine);
253
- // 5. Create AG-UI system (processor + adapter) if transport manager is available
254
- let agUiAdapter;
255
- if (this.transportManager) {
256
- const agUiSystem = createAgUiSystem(execution.id);
257
- agUiAdapter = agUiSystem.adapter;
258
- // Connect adapter to transport for SSE streaming
259
- this.transportManager.connectAdapter(agUiAdapter, execution.id);
260
- // Connect processor to execution engine for real-time output parsing
261
- // Buffer for incomplete lines (stream-json can split mid-line)
262
- let lineBuffer = "";
263
- engine = new SimpleExecutionEngine(processManager, {
264
- maxConcurrent: 1,
265
- // TODO: Factor out this logic for DRY principles.
266
- onOutput: (data, type) => {
267
- if (type === "stdout") {
268
- // Append new data to buffer
269
- lineBuffer += data.toString();
270
- // Process complete lines (ending with \n)
271
- let newlineIndex;
272
- while ((newlineIndex = lineBuffer.indexOf("\n")) !== -1) {
273
- const line = lineBuffer.slice(0, newlineIndex);
274
- lineBuffer = lineBuffer.slice(newlineIndex + 1);
275
- if (line.trim()) {
276
- // 1. Persist raw log immediately (before processing)
277
- try {
278
- this.logsStore.appendRawLog(execution.id, line);
279
- }
280
- catch (err) {
281
- console.error("[ExecutionService] Failed to persist raw log (non-critical):", {
282
- executionId: execution.id,
283
- error: err instanceof Error ? err.message : String(err),
284
- });
285
- // Don't crash execution - logs are nice-to-have
286
- }
287
- // 2. Process through AG-UI pipeline for live clients
288
- agUiSystem.processor.processLine(line).catch((err) => {
289
- console.error("[ExecutionService] Error processing output line:", {
290
- error: err instanceof Error ? err.message : String(err),
291
- line: line.slice(0, 100), // Log first 100 chars for debugging
292
- });
293
- });
294
- }
295
- }
296
- }
297
- },
298
- });
299
- executor = new ResilientExecutor(engine);
300
- }
301
- // 6. Create LinearOrchestrator
302
- const orchestrator = new LinearOrchestrator(executor, undefined, // No storage/checkpointing for now
303
- agUiAdapter, this.lifecycleService);
304
- // 7. Register event handlers to update execution status in database
305
- orchestrator.onWorkflowStart(() => {
306
- try {
307
- updateExecution(this.db, execution.id, {
308
- status: "running",
309
- });
310
- }
311
- catch (error) {
312
- console.error("[ExecutionService] Failed to update execution status to running", {
313
- executionId: execution.id,
314
- error: error instanceof Error ? error.message : String(error),
315
- });
316
- }
317
- });
318
- orchestrator.onWorkflowComplete(() => {
319
- console.log("[ExecutionService] Workflow completed successfully", {
320
- executionId: execution.id,
321
- });
322
- try {
323
- updateExecution(this.db, execution.id, {
324
- status: "completed",
325
- completed_at: new Date().toISOString(),
326
- });
327
- }
328
- catch (error) {
329
- console.error("[ExecutionService] Failed to update execution status to completed", {
330
- executionId: execution.id,
331
- error: error instanceof Error ? error.message : String(error),
332
- note: "Execution may have been deleted (e.g., due to CASCADE DELETE from issue deletion)",
333
- });
334
- }
335
- // Remove orchestrator from active map
336
- this.activeOrchestrators.delete(execution.id);
337
- });
338
- orchestrator.onWorkflowFailed((_executionId, error) => {
339
- console.error("[ExecutionService] Workflow failed", {
340
- executionId: execution.id,
341
- error: error.message,
342
- stack: error.stack,
343
- });
344
- try {
345
- updateExecution(this.db, execution.id, {
346
- status: "failed",
347
- completed_at: new Date().toISOString(),
348
- error_message: error.message,
349
- });
350
- }
351
- catch (updateError) {
352
- console.error("[ExecutionService] Failed to update execution status to failed", {
353
- executionId: execution.id,
354
- error: updateError instanceof Error
355
- ? updateError.message
356
- : String(updateError),
357
- note: "Execution may have been deleted (e.g., due to CASCADE DELETE from issue deletion)",
358
- });
359
- }
360
- // Remove orchestrator from active map
361
- this.activeOrchestrators.delete(execution.id);
362
- });
363
- // 8. Start workflow execution (non-blocking)
364
- orchestrator.startWorkflow(workflow, workDir, {
365
- checkpointInterval: config.checkpointInterval,
366
- executionId: execution.id,
367
- });
368
- // 9. Store orchestrator for later cancellation
369
- this.activeOrchestrators.set(execution.id, orchestrator);
222
+ // Broadcast execution creation
223
+ broadcastExecutionUpdate(this.projectId, execution.id, "created", execution, execution.issue_id || undefined);
370
224
  return execution;
371
225
  }
372
226
  /**
373
- * Create follow-up execution - reuse worktree from previous execution
227
+ * Create follow-up execution
374
228
  *
375
- * Creates a new execution that reuses the worktree from a previous execution,
376
- * appending feedback or additional context to the prompt.
229
+ * For worktree-based executions: reuses the worktree and resumes the session.
230
+ * For local/non-worktree executions: creates a new execution with feedback context.
377
231
  *
378
232
  * @param executionId - ID of previous execution to follow up on
379
233
  * @param feedback - Additional feedback/context to append to prompt
234
+ * @param options - Optional configuration
235
+ * @param options.includeOriginalPrompt - Whether to prepend the original issue content (default: false, assumes session resumption with full history)
380
236
  * @returns Created follow-up execution record
381
237
  */
382
- async createFollowUp(executionId, feedback) {
238
+ async createFollowUp(executionId, feedback, options) {
383
239
  // 1. Get previous execution
384
240
  const prevExecution = getExecution(this.db, executionId);
385
241
  if (!prevExecution) {
386
242
  throw new Error(`Execution ${executionId} not found`);
387
243
  }
388
- if (!prevExecution.worktree_path) {
389
- throw new Error(`Cannot create follow-up: execution ${executionId} has no worktree`);
390
- }
391
- // Check if worktree still exists on filesystem, recreate if needed
392
- if (this.lifecycleService) {
244
+ const hasWorktree = !!prevExecution.worktree_path;
245
+ // For worktree executions, check if worktree still exists on filesystem, recreate if needed
246
+ if (hasWorktree && this.lifecycleService) {
393
247
  const fs = await import("fs");
394
248
  const worktreeExists = fs.existsSync(prevExecution.worktree_path);
395
249
  if (!worktreeExists) {
@@ -405,30 +259,41 @@ export class ExecutionService {
405
259
  });
406
260
  }
407
261
  }
408
- // 2. Validate that previous execution has an issue_id
409
- if (!prevExecution.issue_id) {
410
- throw new Error("Previous execution must have an issue_id for follow-up");
411
- }
412
- // 3. Prepare execution to get rendered prompt
413
- const prepareResult = await this.prepareExecution(prevExecution.issue_id);
414
- // 4. Append feedback to prompt
415
- const followUpPrompt = `${prepareResult.renderedPrompt}
416
-
417
- ## Follow-up Feedback
418
- ${feedback}
262
+ // TODO: Make it so follow-ups don't require an issue id.
263
+ // 2. Build follow-up prompt (default: just feedback, assumes session resumption)
264
+ let followUpPrompt = feedback;
265
+ if (options?.includeOriginalPrompt) {
266
+ // Optional: include original issue content if explicitly requested
267
+ if (!prevExecution.issue_id) {
268
+ throw new Error("Previous execution must have an issue_id to include original prompt");
269
+ }
270
+ // Get issue content directly from database
271
+ const issue = this.db
272
+ .prepare("SELECT content FROM issues WHERE id = ?")
273
+ .get(prevExecution.issue_id);
274
+ if (!issue) {
275
+ throw new Error(`Issue ${prevExecution.issue_id} not found`);
276
+ }
277
+ followUpPrompt = `${issue.content}
419
278
 
420
- Please continue working on this issue, taking into account the feedback above.`;
421
- // 5. Create new execution record that references previous execution
279
+ ${feedback}`;
280
+ }
281
+ // 3. Create new execution record that references previous execution
282
+ // Default to 'claude-code' if agent_type is null (for backwards compatibility)
283
+ const agentType = (prevExecution.agent_type || "claude-code");
284
+ // Determine working directory: worktree path if available, otherwise repo path (local mode)
285
+ const workDir = hasWorktree ? prevExecution.worktree_path : this.repoPath;
422
286
  const newExecutionId = randomUUID();
423
287
  const newExecution = createExecution(this.db, {
424
288
  id: newExecutionId,
425
289
  issue_id: prevExecution.issue_id,
426
- agent_type: "claude-code",
290
+ agent_type: agentType, // Use same agent as previous execution
427
291
  target_branch: prevExecution.target_branch,
428
292
  branch_name: prevExecution.branch_name,
429
- // TODO: Handle case where worktree has been deleted.
430
- worktree_path: prevExecution.worktree_path, // Reuse same worktree
293
+ worktree_path: prevExecution.worktree_path || undefined, // Reuse same worktree (undefined for local)
431
294
  config: prevExecution.config || undefined, // Preserve config (including cleanupMode) from previous execution
295
+ parent_execution_id: executionId, // Link to parent execution for follow-up chain
296
+ prompt: followUpPrompt, // Store original (unexpanded) follow-up prompt
432
297
  });
433
298
  // Initialize empty logs for this execution
434
299
  try {
@@ -441,149 +306,81 @@ Please continue working on this issue, taking into account the feedback above.`;
441
306
  });
442
307
  // Don't fail execution creation - logs are nice-to-have
443
308
  }
444
- // 5. Build WorkflowDefinition
445
- const workflow = {
446
- id: `workflow-${newExecution.id}`,
447
- steps: [
448
- {
449
- id: "execute-followup",
450
- taskType: "issue",
451
- prompt: followUpPrompt,
452
- taskConfig: {
453
- model: "claude-sonnet-4",
454
- captureFileChanges: true,
455
- captureToolCalls: true,
456
- },
457
- },
458
- ],
309
+ // Collect already-expanded entities from parent execution chain
310
+ const alreadyExpandedIds = await this.collectExpandedEntitiesFromChain(executionId);
311
+ // Resolve prompt references for execution (done after storing original)
312
+ // Skip entities that were already expanded in parent executions
313
+ const resolver = new PromptResolver(this.db);
314
+ const { resolvedPrompt, errors } = await resolver.resolve(followUpPrompt, alreadyExpandedIds);
315
+ if (errors.length > 0) {
316
+ console.warn(`[ExecutionService] Follow-up prompt resolution warnings:`, errors);
317
+ }
318
+ // 4. Use executor wrapper with session resumption
319
+ const wrapper = createExecutorForAgent(agentType, { workDir: this.repoPath }, {
320
+ workDir: this.repoPath,
321
+ lifecycleService: this.lifecycleService,
322
+ logsStore: this.logsStore,
323
+ projectId: this.projectId,
324
+ db: this.db,
325
+ transportManager: this.transportManager,
326
+ });
327
+ // Use previous execution's session_id (the actual Claude UUID) if available
328
+ // This enables proper session resumption with Claude Code's --resume-session flag
329
+ // If no session_id was captured, we can't resume - this would start a new session
330
+ const sessionId = prevExecution.session_id;
331
+ if (!sessionId) {
332
+ console.warn(`[ExecutionService] No session_id found for execution ${executionId}, follow-up will start a new session`);
333
+ }
334
+ // Parse config to get model and other settings
335
+ const parsedConfig = prevExecution.config
336
+ ? JSON.parse(prevExecution.config)
337
+ : {};
338
+ // Build execution task for follow-up (use resolved prompt for agent)
339
+ const task = {
340
+ id: newExecution.id,
341
+ type: "issue",
342
+ entityId: prevExecution.issue_id ?? undefined,
343
+ prompt: resolvedPrompt,
344
+ workDir: workDir,
459
345
  config: {
460
- checkpointInterval: 1,
461
- continueOnStepFailure: false,
346
+ timeout: parsedConfig.timeout,
462
347
  },
463
348
  metadata: {
464
- workDir: prevExecution.worktree_path,
465
- issueId: prevExecution.issue_id,
349
+ model: parsedConfig.model || "claude-sonnet-4",
350
+ captureFileChanges: parsedConfig.captureFileChanges ?? true,
351
+ captureToolCalls: parsedConfig.captureToolCalls ?? true,
352
+ issueId: prevExecution.issue_id ?? undefined,
466
353
  executionId: newExecution.id,
467
354
  followUpOf: executionId,
468
355
  },
356
+ priority: 0,
357
+ dependencies: [],
358
+ createdAt: new Date(),
469
359
  };
470
- // 6. Create execution engine stack
471
- const processManager = new SimpleProcessManager({
472
- executablePath: "claude",
473
- args: [
474
- "--print",
475
- "--output-format",
476
- "stream-json",
477
- "--dangerously-skip-permissions",
478
- ],
479
- });
480
- let engine = new SimpleExecutionEngine(processManager, {
481
- maxConcurrent: 1,
482
- });
483
- let executor = new ResilientExecutor(engine);
484
- // 7. Create AG-UI system (processor + adapter) if transport manager is available
485
- let agUiAdapter;
486
- if (this.transportManager) {
487
- const agUiSystem = createAgUiSystem(newExecution.id);
488
- agUiAdapter = agUiSystem.adapter;
489
- this.transportManager.connectAdapter(agUiAdapter, newExecution.id);
490
- // Connect processor to execution engine for real-time output parsing
491
- // Buffer for incomplete lines (stream-json can split mid-line)
492
- let lineBuffer = "";
493
- engine = new SimpleExecutionEngine(processManager, {
494
- maxConcurrent: 1,
495
- // TODO: Factor out this logic for DRY principles.
496
- onOutput: (data, type) => {
497
- if (type === "stdout") {
498
- // Append new data to buffer
499
- lineBuffer += data.toString();
500
- // Process complete lines (ending with \n)
501
- let newlineIndex;
502
- while ((newlineIndex = lineBuffer.indexOf("\n")) !== -1) {
503
- const line = lineBuffer.slice(0, newlineIndex);
504
- lineBuffer = lineBuffer.slice(newlineIndex + 1);
505
- if (line.trim()) {
506
- // 1. Persist raw log immediately (before processing)
507
- try {
508
- this.logsStore.appendRawLog(newExecution.id, line);
509
- }
510
- catch (err) {
511
- console.error("[ExecutionService] Failed to persist raw log (non-critical):", {
512
- executionId: newExecution.id,
513
- error: err instanceof Error ? err.message : String(err),
514
- });
515
- // Don't crash execution - logs are nice-to-have
516
- }
517
- // 2. Process through AG-UI pipeline for live clients
518
- agUiSystem.processor.processLine(line).catch((err) => {
519
- console.error("[ExecutionService] Error processing output line:", {
520
- error: err instanceof Error ? err.message : String(err),
521
- line: line.slice(0, 100), // Log first 100 chars for debugging
522
- });
523
- });
524
- }
525
- }
526
- }
527
- },
360
+ // Execute follow-up (non-blocking)
361
+ // If we have a session ID, resume the session; otherwise start a new one
362
+ if (sessionId) {
363
+ wrapper
364
+ .resumeWithLifecycle(newExecution.id, sessionId, task, workDir)
365
+ .catch((error) => {
366
+ console.error(`[ExecutionService] Follow-up execution ${newExecution.id} failed:`, error);
367
+ // Error is already handled by wrapper (status updated, broadcasts sent)
528
368
  });
529
- executor = new ResilientExecutor(engine);
530
369
  }
531
- // 8. Create LinearOrchestrator
532
- const orchestrator = new LinearOrchestrator(executor, undefined, agUiAdapter, this.lifecycleService);
533
- // 9. Register event handlers
534
- orchestrator.onWorkflowStart(() => {
535
- try {
536
- updateExecution(this.db, newExecution.id, {
537
- status: "running",
538
- });
539
- }
540
- catch (error) {
541
- console.error("[ExecutionService] Failed to update follow-up execution status to running", {
542
- executionId: newExecution.id,
543
- error: error instanceof Error ? error.message : String(error),
544
- });
545
- }
546
- });
547
- orchestrator.onWorkflowComplete(() => {
548
- try {
549
- updateExecution(this.db, newExecution.id, {
550
- status: "completed",
551
- completed_at: new Date().toISOString(),
552
- });
553
- }
554
- catch (error) {
555
- console.error("[ExecutionService] Failed to update follow-up execution status to completed", {
556
- executionId: newExecution.id,
557
- error: error instanceof Error ? error.message : String(error),
558
- });
559
- }
560
- this.activeOrchestrators.delete(newExecution.id);
561
- });
562
- orchestrator.onWorkflowFailed((_execId, error) => {
563
- try {
564
- updateExecution(this.db, newExecution.id, {
565
- status: "failed",
566
- completed_at: new Date().toISOString(),
567
- error_message: error.message,
568
- });
569
- }
570
- catch (updateError) {
571
- console.error("[ExecutionService] Failed to update follow-up execution status to failed", {
572
- executionId: newExecution.id,
573
- error: updateError instanceof Error
574
- ? updateError.message
575
- : String(updateError),
576
- });
577
- }
578
- this.activeOrchestrators.delete(newExecution.id);
579
- });
580
- // 10. Start workflow execution (non-blocking)
581
- orchestrator.startWorkflow(workflow, prevExecution.worktree_path, {
582
- checkpointInterval: 1,
583
- executionId: newExecution.id,
584
- });
585
- // 11. Store orchestrator for later cancellation
586
- this.activeOrchestrators.set(newExecution.id, orchestrator);
370
+ else {
371
+ // No session to resume, start a new execution with the follow-up prompt
372
+ wrapper
373
+ .executeWithLifecycle(newExecution.id, task, workDir)
374
+ .catch((error) => {
375
+ console.error(`[ExecutionService] Follow-up execution ${newExecution.id} failed:`, error);
376
+ });
377
+ }
378
+ // Broadcast execution creation
379
+ broadcastExecutionUpdate(this.projectId, newExecution.id, "created", newExecution, newExecution.issue_id || undefined);
380
+ // Also broadcast to parent execution channel
381
+ if (newExecution.parent_execution_id) {
382
+ broadcastExecutionUpdate(this.projectId, newExecution.parent_execution_id, "updated", newExecution, newExecution.issue_id || undefined);
383
+ }
587
384
  return newExecution;
588
385
  }
589
386
  /**
@@ -602,19 +399,26 @@ Please continue working on this issue, taking into account the feedback above.`;
602
399
  if (execution.status !== "running") {
603
400
  throw new Error(`Cannot cancel execution in ${execution.status} state`);
604
401
  }
605
- // Get orchestrator from active map
606
- const orchestrator = this.activeOrchestrators.get(executionId);
607
- if (orchestrator) {
608
- // Cancel via orchestrator
609
- await orchestrator.cancelWorkflow(executionId);
610
- // Remove from active map
611
- this.activeOrchestrators.delete(executionId);
402
+ // Use worker pool cancellation if available
403
+ if (this.workerPool && this.workerPool.hasWorker(executionId)) {
404
+ await this.workerPool.cancelExecution(executionId);
405
+ return; // Worker pool handles DB updates and broadcasts
612
406
  }
613
- // Update status in database (orchestrator.cancelWorkflow doesn't emit events for DB update)
407
+ // For in-process executions using AgentExecutorWrapper:
408
+ // The wrapper manages its own lifecycle and cancellation.
409
+ // We update the database status, which the wrapper may check,
410
+ // or we rely on process termination to stop execution.
411
+ // TODO: Add cancellation registry in AgentExecutorWrapper for direct process control
412
+ // Update status in database
614
413
  updateExecution(this.db, executionId, {
615
414
  status: "stopped",
616
415
  completed_at: new Date().toISOString(),
617
416
  });
417
+ // Broadcast status change
418
+ const updated = getExecution(this.db, executionId);
419
+ if (updated) {
420
+ broadcastExecutionUpdate(this.projectId, executionId, "status_changed", updated, updated.issue_id || undefined);
421
+ }
618
422
  }
619
423
  /**
620
424
  * Clean up execution resources
@@ -649,9 +453,10 @@ Please continue working on this issue, taking into account the feedback above.`;
649
453
  * when they're configured for manual cleanup.
650
454
  *
651
455
  * @param executionId - ID of execution whose worktree to delete
456
+ * @param deleteBranch - Whether to also delete the execution's branch (default: false)
652
457
  * @throws Error if execution not found, has no worktree, or worktree doesn't exist
653
458
  */
654
- async deleteWorktree(executionId) {
459
+ async deleteWorktree(executionId, deleteBranch = false) {
655
460
  const execution = getExecution(this.db, executionId);
656
461
  if (!execution) {
657
462
  throw new Error(`Execution ${executionId} not found`);
@@ -670,6 +475,146 @@ Please continue working on this issue, taking into account the feedback above.`;
670
475
  const worktreeManager = this.lifecycleService.worktreeManager;
671
476
  // Clean up the worktree
672
477
  await worktreeManager.cleanupWorktree(execution.worktree_path, this.repoPath);
478
+ // Delete branch if requested and it was created by this execution
479
+ if (deleteBranch && execution.branch_name) {
480
+ try {
481
+ // A branch was created for this execution if:
482
+ // - branch_name is DIFFERENT from target_branch (autoCreateBranches was true)
483
+ // - This means a new worktree-specific branch was created
484
+ const wasCreatedByExecution = execution.branch_name !== execution.target_branch &&
485
+ execution.branch_name !== "(detached)";
486
+ if (wasCreatedByExecution) {
487
+ await worktreeManager.git.deleteBranch(this.repoPath, execution.branch_name, true // Force deletion
488
+ );
489
+ console.log(`[ExecutionService] Deleted execution-created branch: ${execution.branch_name}`);
490
+ }
491
+ else {
492
+ console.log(`[ExecutionService] Skipping branch deletion - branch ${execution.branch_name} is the target branch (not created by execution)`);
493
+ }
494
+ }
495
+ catch (err) {
496
+ console.warn(`Failed to delete branch ${execution.branch_name} during worktree deletion:`, err);
497
+ // Continue even if branch deletion fails
498
+ }
499
+ }
500
+ }
501
+ /**
502
+ * Delete an execution and its entire chain
503
+ *
504
+ * Deletes the execution and all its follow-ups (descendants).
505
+ * Optionally deletes the worktree and/or branch.
506
+ *
507
+ * @param executionId - ID of execution to delete (can be root or any execution in chain)
508
+ * @param deleteBranch - Whether to also delete the execution's branch (default: false)
509
+ * @param deleteWorktree - Whether to also delete the execution's worktree (default: false)
510
+ * @throws Error if execution not found
511
+ */
512
+ async deleteExecution(executionId, deleteBranch = false, deleteWorktree = false) {
513
+ const execution = getExecution(this.db, executionId);
514
+ if (!execution) {
515
+ throw new Error(`Execution ${executionId} not found`);
516
+ }
517
+ // Find the root execution by traversing up parent_execution_id
518
+ let rootId = executionId;
519
+ let current = execution;
520
+ while (current.parent_execution_id) {
521
+ rootId = current.parent_execution_id;
522
+ const parent = getExecution(this.db, rootId);
523
+ if (!parent)
524
+ break;
525
+ current = parent;
526
+ }
527
+ // Get all executions in the chain (root + all descendants)
528
+ const chain = this.db
529
+ .prepare(`
530
+ WITH RECURSIVE execution_chain AS (
531
+ -- Base case: the root execution
532
+ SELECT * FROM executions WHERE id = ?
533
+ UNION ALL
534
+ -- Recursive case: children of executions in the chain
535
+ SELECT e.* FROM executions e
536
+ INNER JOIN execution_chain ec ON e.parent_execution_id = ec.id
537
+ )
538
+ SELECT * FROM execution_chain
539
+ `)
540
+ .all(rootId);
541
+ // Cancel any running executions in the chain
542
+ for (const exec of chain) {
543
+ if (exec.status === "running" || exec.status === "pending") {
544
+ try {
545
+ await this.cancelExecution(exec.id);
546
+ }
547
+ catch (err) {
548
+ console.warn(`Failed to cancel execution ${exec.id} during deletion:`, err);
549
+ // Continue with deletion even if cancel fails
550
+ }
551
+ }
552
+ }
553
+ // Delete worktree if requested and it exists (only for root execution)
554
+ const rootExecution = chain.find((e) => e.id === rootId);
555
+ if (deleteWorktree && rootExecution?.worktree_path) {
556
+ try {
557
+ const fs = await import("fs");
558
+ if (fs.existsSync(rootExecution.worktree_path)) {
559
+ await this.deleteWorktree(rootId);
560
+ }
561
+ }
562
+ catch (err) {
563
+ console.warn(`Failed to delete worktree during execution deletion:`, err);
564
+ // Continue with deletion even if worktree cleanup fails
565
+ }
566
+ }
567
+ // Delete branch if requested and it exists
568
+ // IMPORTANT: Only delete branches that were created specifically for this execution
569
+ if (deleteBranch && rootExecution?.branch_name) {
570
+ try {
571
+ // A branch was created for this execution if:
572
+ // - branch_name is DIFFERENT from target_branch (autoCreateBranches was true)
573
+ // - This means a new worktree-specific branch was created
574
+ //
575
+ // A branch was NOT created (reusing existing) if:
576
+ // - branch_name === target_branch (autoCreateBranches was false)
577
+ // - This means the worktree reused the target branch directly
578
+ const wasCreatedByExecution = rootExecution.branch_name !== rootExecution.target_branch &&
579
+ rootExecution.branch_name !== "(detached)";
580
+ if (wasCreatedByExecution) {
581
+ // Get worktree manager from lifecycle service to access git operations
582
+ const worktreeManager = this.lifecycleService
583
+ .worktreeManager;
584
+ await worktreeManager.git.deleteBranch(this.repoPath, rootExecution.branch_name, true // Force deletion
585
+ );
586
+ console.log(`[ExecutionService] Deleted execution-created branch: ${rootExecution.branch_name}`);
587
+ }
588
+ else {
589
+ console.log(`[ExecutionService] Skipping branch deletion - branch ${rootExecution.branch_name} is the target branch (not created by execution)`);
590
+ }
591
+ }
592
+ catch (err) {
593
+ console.warn(`Failed to delete branch ${rootExecution.branch_name} during execution deletion:`, err);
594
+ // Continue with deletion even if branch deletion fails
595
+ }
596
+ }
597
+ // Delete execution logs for all executions in the chain
598
+ for (const exec of chain) {
599
+ try {
600
+ this.logsStore.deleteLogs(exec.id);
601
+ }
602
+ catch (err) {
603
+ console.warn(`Failed to delete logs for execution ${exec.id}:`, err);
604
+ // Continue with deletion even if log cleanup fails
605
+ }
606
+ }
607
+ // Delete all executions in the chain from database
608
+ // Delete in reverse order (children first) to avoid foreign key issues
609
+ const chainIds = chain.map((e) => e.id);
610
+ const placeholders = chainIds.map(() => "?").join(",");
611
+ this.db
612
+ .prepare(`DELETE FROM executions WHERE id IN (${placeholders})`)
613
+ .run(...chainIds);
614
+ // Broadcast deletion event for each execution
615
+ for (const exec of chain) {
616
+ broadcastExecutionUpdate(this.projectId, exec.id, "deleted", { executionId: exec.id }, exec.issue_id || undefined);
617
+ }
673
618
  }
674
619
  /**
675
620
  * Shutdown execution service - cancel all active executions
@@ -678,21 +623,14 @@ Please continue working on this issue, taking into account the feedback above.`;
678
623
  * all running executions before the server exits.
679
624
  */
680
625
  async shutdown() {
681
- const cancelPromises = [];
682
- // Cancel all active orchestrators
683
- for (const [executionId, orchestrator,] of this.activeOrchestrators.entries()) {
684
- cancelPromises.push(orchestrator.cancelWorkflow(executionId).catch((error) => {
685
- console.error("[ExecutionService] Error canceling execution", {
686
- executionId,
687
- error: error.message,
688
- });
689
- }));
626
+ // Shutdown worker pool if available
627
+ if (this.workerPool) {
628
+ await this.workerPool.shutdown();
690
629
  }
691
- // Wait for all cancellations to complete (with timeout)
692
- await Promise.race([
693
- Promise.all(cancelPromises),
694
- new Promise((resolve) => setTimeout(resolve, 5000)), // 5 second timeout
695
- ]);
630
+ // For in-process executions using AgentExecutorWrapper:
631
+ // The wrapper manages its own lifecycle. Processes will be terminated
632
+ // when the Node.js process exits.
633
+ // TODO: Add active execution tracking to AgentExecutorWrapper for graceful shutdown
696
634
  }
697
635
  /**
698
636
  * List all executions for an issue
@@ -722,5 +660,52 @@ Please continue working on this issue, taking into account the feedback above.`;
722
660
  getExecution(executionId) {
723
661
  return getExecution(this.db, executionId);
724
662
  }
663
+ /**
664
+ * Check if there are any active executions
665
+ *
666
+ * @returns true if there are active worker pool executions
667
+ */
668
+ hasActiveExecutions() {
669
+ // Check worker pool for active executions
670
+ if (this.workerPool) {
671
+ return this.workerPool.getActiveWorkerCount() > 0;
672
+ }
673
+ // For in-process executions, we don't track them anymore
674
+ // Query the database for running executions as a fallback
675
+ const runningExecutions = this.db
676
+ .prepare("SELECT COUNT(*) as count FROM executions WHERE status = 'running'")
677
+ .get();
678
+ return runningExecutions.count > 0;
679
+ }
680
+ /**
681
+ * Collect entity IDs that were already expanded in parent executions
682
+ *
683
+ * Walks the execution chain backwards and resolves each prompt to extract
684
+ * which entities were referenced (and thus expanded) in previous executions.
685
+ * This prevents redundant expansion of the same entities in follow-ups.
686
+ *
687
+ * @param executionId - ID of the current execution
688
+ * @returns Set of entity IDs that were already expanded
689
+ */
690
+ async collectExpandedEntitiesFromChain(executionId) {
691
+ const expandedIds = new Set();
692
+ const resolver = new PromptResolver(this.db);
693
+ // Walk backwards through the execution chain
694
+ let currentExecId = executionId;
695
+ while (currentExecId) {
696
+ const execution = getExecution(this.db, currentExecId);
697
+ if (!execution || !execution.prompt)
698
+ break;
699
+ // Resolve the prompt to extract what entities were referenced
700
+ // Pass empty set so we expand everything in this pass (just to collect IDs)
701
+ // Pass the execution's issue_id as implicit to track if it was auto-included
702
+ const { expandedEntityIds } = await resolver.resolve(execution.prompt, new Set(), execution.issue_id || undefined);
703
+ // Add all expanded entity IDs from this execution
704
+ expandedEntityIds.forEach((id) => expandedIds.add(id));
705
+ // Move to parent execution
706
+ currentExecId = execution.parent_execution_id || null;
707
+ }
708
+ return expandedIds;
709
+ }
725
710
  }
726
711
  //# sourceMappingURL=execution-service.js.map