@sudocode-ai/local-server 0.1.7 → 0.1.9

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 (282) 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 +24 -57
  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 +198 -0
  86. package/dist/execution/worktree/git-sync-cli.d.ts.map +1 -0
  87. package/dist/execution/worktree/git-sync-cli.js +401 -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 +124 -229
  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-DV9Tbujb.css +1 -0
  102. package/dist/public/assets/index-DcDX9-Ad.js +740 -0
  103. package/dist/public/assets/index-DcDX9-Ad.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 +792 -37
  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 +12 -10
  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 +203 -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/routes/version.d.ts +3 -0
  158. package/dist/routes/version.d.ts.map +1 -0
  159. package/dist/routes/version.js +25 -0
  160. package/dist/routes/version.js.map +1 -0
  161. package/dist/services/agent-registry.d.ts +140 -0
  162. package/dist/services/agent-registry.d.ts.map +1 -0
  163. package/dist/services/agent-registry.js +272 -0
  164. package/dist/services/agent-registry.js.map +1 -0
  165. package/dist/services/editor-service.d.ts +57 -0
  166. package/dist/services/editor-service.d.ts.map +1 -0
  167. package/dist/services/editor-service.js +204 -0
  168. package/dist/services/editor-service.js.map +1 -0
  169. package/dist/services/execution-changes-service.d.ts +110 -0
  170. package/dist/services/execution-changes-service.d.ts.map +1 -0
  171. package/dist/services/execution-changes-service.js +700 -0
  172. package/dist/services/execution-changes-service.js.map +1 -0
  173. package/dist/services/execution-lifecycle.d.ts +1 -0
  174. package/dist/services/execution-lifecycle.d.ts.map +1 -1
  175. package/dist/services/execution-lifecycle.js +37 -7
  176. package/dist/services/execution-lifecycle.js.map +1 -1
  177. package/dist/services/execution-logs-store.d.ts +75 -0
  178. package/dist/services/execution-logs-store.d.ts.map +1 -1
  179. package/dist/services/execution-logs-store.js +142 -2
  180. package/dist/services/execution-logs-store.js.map +1 -1
  181. package/dist/services/execution-service.d.ts +82 -59
  182. package/dist/services/execution-service.d.ts.map +1 -1
  183. package/dist/services/execution-service.js +514 -469
  184. package/dist/services/execution-service.js.map +1 -1
  185. package/dist/services/execution-worker-pool.d.ts +116 -0
  186. package/dist/services/execution-worker-pool.d.ts.map +1 -0
  187. package/dist/services/execution-worker-pool.js +326 -0
  188. package/dist/services/execution-worker-pool.js.map +1 -0
  189. package/dist/services/executions.d.ts +3 -0
  190. package/dist/services/executions.d.ts.map +1 -1
  191. package/dist/services/executions.js +11 -17
  192. package/dist/services/executions.js.map +1 -1
  193. package/dist/services/export.d.ts +8 -2
  194. package/dist/services/export.d.ts.map +1 -1
  195. package/dist/services/export.js +29 -23
  196. package/dist/services/export.js.map +1 -1
  197. package/dist/services/file-search/git-ls-files-strategy.d.ts +72 -0
  198. package/dist/services/file-search/git-ls-files-strategy.d.ts.map +1 -0
  199. package/dist/services/file-search/git-ls-files-strategy.js +176 -0
  200. package/dist/services/file-search/git-ls-files-strategy.js.map +1 -0
  201. package/dist/services/file-search/index.d.ts +9 -0
  202. package/dist/services/file-search/index.d.ts.map +1 -0
  203. package/dist/services/file-search/index.js +10 -0
  204. package/dist/services/file-search/index.js.map +1 -0
  205. package/dist/services/file-search/registry.d.ts +97 -0
  206. package/dist/services/file-search/registry.d.ts.map +1 -0
  207. package/dist/services/file-search/registry.js +140 -0
  208. package/dist/services/file-search/registry.js.map +1 -0
  209. package/dist/services/file-search/strategy.d.ts +58 -0
  210. package/dist/services/file-search/strategy.d.ts.map +1 -0
  211. package/dist/services/file-search/strategy.js +8 -0
  212. package/dist/services/file-search/strategy.js.map +1 -0
  213. package/dist/services/project-context.d.ts +69 -0
  214. package/dist/services/project-context.d.ts.map +1 -0
  215. package/dist/services/project-context.js +113 -0
  216. package/dist/services/project-context.js.map +1 -0
  217. package/dist/services/project-manager.d.ts +95 -0
  218. package/dist/services/project-manager.d.ts.map +1 -0
  219. package/dist/services/project-manager.js +388 -0
  220. package/dist/services/project-manager.js.map +1 -0
  221. package/dist/services/project-registry.d.ts +98 -0
  222. package/dist/services/project-registry.d.ts.map +1 -0
  223. package/dist/services/project-registry.js +289 -0
  224. package/dist/services/project-registry.js.map +1 -0
  225. package/dist/services/prompt-resolver.d.ts +97 -0
  226. package/dist/services/prompt-resolver.d.ts.map +1 -0
  227. package/dist/services/prompt-resolver.js +377 -0
  228. package/dist/services/prompt-resolver.js.map +1 -0
  229. package/dist/services/repo-info.d.ts +12 -0
  230. package/dist/services/repo-info.d.ts.map +1 -1
  231. package/dist/services/repo-info.js +46 -0
  232. package/dist/services/repo-info.js.map +1 -1
  233. package/dist/services/version-service.d.ts +14 -0
  234. package/dist/services/version-service.d.ts.map +1 -0
  235. package/dist/services/version-service.js +57 -0
  236. package/dist/services/version-service.js.map +1 -0
  237. package/dist/services/watcher.d.ts +3 -4
  238. package/dist/services/watcher.d.ts.map +1 -1
  239. package/dist/services/watcher.js +18 -35
  240. package/dist/services/watcher.js.map +1 -1
  241. package/dist/services/websocket.d.ts +30 -16
  242. package/dist/services/websocket.d.ts.map +1 -1
  243. package/dist/services/websocket.js +102 -37
  244. package/dist/services/websocket.js.map +1 -1
  245. package/dist/services/worktree-sync-service.d.ts +326 -0
  246. package/dist/services/worktree-sync-service.d.ts.map +1 -0
  247. package/dist/services/worktree-sync-service.js +1091 -0
  248. package/dist/services/worktree-sync-service.js.map +1 -0
  249. package/dist/types/editor.d.ts +49 -0
  250. package/dist/types/editor.d.ts.map +1 -0
  251. package/dist/types/editor.js +50 -0
  252. package/dist/types/editor.js.map +1 -0
  253. package/dist/types/project.d.ts +58 -0
  254. package/dist/types/project.d.ts.map +1 -0
  255. package/dist/types/project.js +10 -0
  256. package/dist/types/project.js.map +1 -0
  257. package/dist/utils/executable-check.d.ts +36 -0
  258. package/dist/utils/executable-check.d.ts.map +1 -0
  259. package/dist/utils/executable-check.js +79 -0
  260. package/dist/utils/executable-check.js.map +1 -0
  261. package/dist/workers/execution-worker.d.ts +18 -0
  262. package/dist/workers/execution-worker.d.ts.map +1 -0
  263. package/dist/workers/execution-worker.js +340 -0
  264. package/dist/workers/execution-worker.js.map +1 -0
  265. package/dist/workers/worker-ipc.d.ts +84 -0
  266. package/dist/workers/worker-ipc.d.ts.map +1 -0
  267. package/dist/workers/worker-ipc.js +29 -0
  268. package/dist/workers/worker-ipc.js.map +1 -0
  269. package/package.json +6 -5
  270. package/dist/execution/output/ag-ui-integration.d.ts +0 -96
  271. package/dist/execution/output/ag-ui-integration.d.ts.map +0 -1
  272. package/dist/execution/output/ag-ui-integration.js +0 -96
  273. package/dist/execution/output/ag-ui-integration.js.map +0 -1
  274. package/dist/execution/output/claude-code-output-processor.d.ts +0 -321
  275. package/dist/execution/output/claude-code-output-processor.d.ts.map +0 -1
  276. package/dist/execution/output/claude-code-output-processor.js +0 -769
  277. package/dist/execution/output/claude-code-output-processor.js.map +0 -1
  278. package/dist/public/assets/index-B3SEMufD.js +0 -580
  279. package/dist/public/assets/index-B3SEMufD.js.map +0 -1
  280. package/dist/public/assets/index-D2YGL3gX.css +0 -1
  281. package/dist/public/assets/ui-vendor-CotR6bx9.js +0 -54
  282. package/dist/public/assets/ui-vendor-CotR6bx9.js.map +0 -1
@@ -2,47 +2,171 @@
2
2
  * Executions API routes (mapped to /api)
3
3
  *
4
4
  * Provides REST API for managing issue executions.
5
+ *
6
+ * Note: All routes require X-Project-ID header via requireProject() middleware
5
7
  */
6
8
  import { Router } from "express";
7
- import { ExecutionService } from "../services/execution-service.js";
8
- import { ExecutionLogsStore } from "../services/execution-logs-store.js";
9
+ import { execSync } from "child_process";
10
+ import { NormalizedEntryToAgUiAdapter } from "../execution/output/normalized-to-ag-ui-adapter.js";
11
+ import { AgUiEventAdapter } from "../execution/output/ag-ui-adapter.js";
12
+ import { agentRegistryService } from "../services/agent-registry.js";
13
+ import { AgentNotFoundError, AgentNotImplementedError, AgentError, } from "../errors/agent-errors.js";
14
+ import { WorktreeSyncService, WorktreeSyncError, WorktreeSyncErrorCode, } from "../services/worktree-sync-service.js";
15
+ import { ExecutionChangesService } from "../services/execution-changes-service.js";
16
+ /**
17
+ * Get WorktreeSyncService instance for a request
18
+ *
19
+ * @param req - Express request with project context
20
+ * @returns WorktreeSyncService instance
21
+ */
22
+ function getWorktreeSyncService(req) {
23
+ const db = req.project.db;
24
+ const repoPath = req.project.path;
25
+ return new WorktreeSyncService(db, repoPath);
26
+ }
27
+ /**
28
+ * Get HTTP status code for WorktreeSyncError
29
+ *
30
+ * @param error - WorktreeSyncError instance
31
+ * @returns HTTP status code
32
+ */
33
+ function getStatusCodeForSyncError(error) {
34
+ switch (error.code) {
35
+ case WorktreeSyncErrorCode.NO_WORKTREE:
36
+ case WorktreeSyncErrorCode.WORKTREE_MISSING:
37
+ case WorktreeSyncErrorCode.BRANCH_MISSING:
38
+ case WorktreeSyncErrorCode.TARGET_BRANCH_MISSING:
39
+ case WorktreeSyncErrorCode.EXECUTION_NOT_FOUND:
40
+ return 404; // Not found
41
+ case WorktreeSyncErrorCode.DIRTY_WORKING_TREE:
42
+ case WorktreeSyncErrorCode.CODE_CONFLICTS:
43
+ case WorktreeSyncErrorCode.NO_COMMON_BASE:
44
+ return 400; // Bad request (user must fix)
45
+ case WorktreeSyncErrorCode.MERGE_FAILED:
46
+ case WorktreeSyncErrorCode.JSONL_RESOLUTION_FAILED:
47
+ case WorktreeSyncErrorCode.DATABASE_SYNC_FAILED:
48
+ return 500; // Internal error
49
+ default:
50
+ return 500;
51
+ }
52
+ }
9
53
  /**
10
54
  * Create executions router
11
55
  *
12
- * @param db - Database instance
13
- * @param repoPath - Path to git repository
14
- * @param transportManager - Optional transport manager for SSE streaming
15
- * @param executionService - Optional execution service instance
16
- * @param logsStore - Optional execution logs store instance
56
+ * Note: ExecutionService and ExecutionLogsStore are accessed via req.project
57
+ * which is injected by the requireProject() middleware
58
+ *
17
59
  * @returns Express router with execution endpoints
18
60
  */
19
- export function createExecutionsRouter(db, repoPath, transportManager, executionService, logsStore) {
61
+ export function createExecutionsRouter() {
20
62
  const router = Router();
21
- const service = executionService ||
22
- new ExecutionService(db, repoPath, undefined, transportManager);
23
- const store = logsStore || new ExecutionLogsStore(db);
24
63
  /**
25
- * POST /api/issues/:issueId/executions/prepare
64
+ * GET /api/executions
26
65
  *
27
- * Prepare an execution - render template and show preview
66
+ * List all executions with filtering and pagination
67
+ *
68
+ * Query parameters:
69
+ * - limit?: number (default: 50)
70
+ * - offset?: number (default: 0)
71
+ * - status?: ExecutionStatus | ExecutionStatus[] (comma-separated for multiple)
72
+ * - issueId?: string
73
+ * - sortBy?: 'created_at' | 'updated_at' (default: 'created_at')
74
+ * - order?: 'asc' | 'desc' (default: 'desc')
75
+ * - since?: ISO date string - only return executions created after this date
76
+ * - includeRunning?: 'true' - when used with 'since', also include running executions regardless of age
28
77
  */
29
- router.post("/issues/:issueId/executions/prepare", async (req, res) => {
78
+ router.get("/executions", (req, res) => {
30
79
  try {
31
- const { issueId } = req.params;
32
- const options = req.body || {};
33
- const result = await service.prepareExecution(issueId, options);
80
+ // Parse query parameters
81
+ const limit = req.query.limit
82
+ ? parseInt(req.query.limit, 10)
83
+ : undefined;
84
+ const offset = req.query.offset
85
+ ? parseInt(req.query.offset, 10)
86
+ : undefined;
87
+ // Parse status (can be single value or comma-separated array)
88
+ let status = undefined;
89
+ if (req.query.status) {
90
+ const statusParam = req.query.status;
91
+ status = statusParam.includes(",")
92
+ ? statusParam.split(",").map((s) => s.trim())
93
+ : statusParam;
94
+ }
95
+ const issueId = req.query.issueId;
96
+ const sortBy = req.query.sortBy || undefined;
97
+ const order = req.query.order || undefined;
98
+ const since = req.query.since;
99
+ const includeRunning = req.query.includeRunning === "true";
100
+ // Validate limit and offset
101
+ if (limit !== undefined && (isNaN(limit) || limit < 0)) {
102
+ res.status(400).json({
103
+ success: false,
104
+ data: null,
105
+ message: "Invalid limit parameter",
106
+ });
107
+ return;
108
+ }
109
+ if (offset !== undefined && (isNaN(offset) || offset < 0)) {
110
+ res.status(400).json({
111
+ success: false,
112
+ data: null,
113
+ message: "Invalid offset parameter",
114
+ });
115
+ return;
116
+ }
117
+ // Validate sortBy
118
+ if (sortBy && sortBy !== "created_at" && sortBy !== "updated_at") {
119
+ res.status(400).json({
120
+ success: false,
121
+ data: null,
122
+ message: "Invalid sortBy parameter. Must be 'created_at' or 'updated_at'",
123
+ });
124
+ return;
125
+ }
126
+ // Validate order
127
+ if (order && order !== "asc" && order !== "desc") {
128
+ res.status(400).json({
129
+ success: false,
130
+ data: null,
131
+ message: "Invalid order parameter. Must be 'asc' or 'desc'",
132
+ });
133
+ return;
134
+ }
135
+ // Validate since (should be valid ISO date)
136
+ if (since) {
137
+ const sinceDate = new Date(since);
138
+ if (isNaN(sinceDate.getTime())) {
139
+ res.status(400).json({
140
+ success: false,
141
+ data: null,
142
+ message: "Invalid since parameter. Must be a valid ISO date string",
143
+ });
144
+ return;
145
+ }
146
+ }
147
+ // Call service method
148
+ const result = req.project.executionService.listAll({
149
+ limit,
150
+ offset,
151
+ status,
152
+ issueId,
153
+ sortBy,
154
+ order,
155
+ since,
156
+ includeRunning,
157
+ });
34
158
  res.json({
35
159
  success: true,
36
160
  data: result,
37
161
  });
38
162
  }
39
163
  catch (error) {
40
- console.error("[API Route] ERROR: Failed to prepare execution:", error);
164
+ console.error("Error listing executions:", error);
41
165
  res.status(500).json({
42
166
  success: false,
43
167
  data: null,
44
168
  error_data: error instanceof Error ? error.message : String(error),
45
- message: "Failed to prepare execution",
169
+ message: "Failed to list executions",
46
170
  });
47
171
  }
48
172
  });
@@ -54,7 +178,7 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
54
178
  router.post("/issues/:issueId/executions", async (req, res) => {
55
179
  try {
56
180
  const { issueId } = req.params;
57
- const { config, prompt } = req.body;
181
+ const { config, prompt, agentType } = req.body;
58
182
  // Validate required fields
59
183
  if (!prompt) {
60
184
  res.status(400).json({
@@ -64,7 +188,22 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
64
188
  });
65
189
  return;
66
190
  }
67
- const execution = await service.createExecution(issueId, config || {}, prompt);
191
+ // Validate agentType if provided
192
+ if (agentType) {
193
+ // Check if agent exists in registry
194
+ if (!agentRegistryService.hasAgent(agentType)) {
195
+ const availableAgents = agentRegistryService
196
+ .getAvailableAgents()
197
+ .map((a) => a.name);
198
+ throw new AgentNotFoundError(agentType, availableAgents);
199
+ }
200
+ // Check if agent is implemented
201
+ if (!agentRegistryService.isAgentImplemented(agentType)) {
202
+ throw new AgentNotImplementedError(agentType);
203
+ }
204
+ }
205
+ const execution = await req.project.executionService.createExecution(issueId, config || {}, prompt, agentType // Optional, defaults to 'claude-code' in service
206
+ );
68
207
  res.status(201).json({
69
208
  success: true,
70
209
  data: execution,
@@ -72,7 +211,39 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
72
211
  }
73
212
  catch (error) {
74
213
  console.error("[API Route] ERROR: Failed to create execution:", error);
75
- // Handle specific error cases
214
+ // Handle agent-specific errors with enhanced error responses
215
+ if (error instanceof AgentNotFoundError) {
216
+ res.status(400).json({
217
+ success: false,
218
+ data: null,
219
+ error: error.message,
220
+ code: error.code,
221
+ details: error.details,
222
+ });
223
+ return;
224
+ }
225
+ if (error instanceof AgentNotImplementedError) {
226
+ res.status(501).json({
227
+ success: false,
228
+ data: null,
229
+ error: error.message,
230
+ code: error.code,
231
+ details: error.details,
232
+ });
233
+ return;
234
+ }
235
+ if (error instanceof AgentError) {
236
+ // Generic agent error (400 by default)
237
+ res.status(400).json({
238
+ success: false,
239
+ data: null,
240
+ error: error.message,
241
+ code: error.code,
242
+ details: error.details,
243
+ });
244
+ return;
245
+ }
246
+ // Handle other errors (backwards compatibility)
76
247
  const errorMessage = error instanceof Error ? error.message : String(error);
77
248
  const statusCode = errorMessage.includes("not found") ? 404 : 500;
78
249
  res.status(statusCode).json({
@@ -91,7 +262,7 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
91
262
  router.get("/executions/:executionId", (req, res) => {
92
263
  try {
93
264
  const { executionId } = req.params;
94
- const execution = service.getExecution(executionId);
265
+ const execution = req.project.executionService.getExecution(executionId);
95
266
  if (!execution) {
96
267
  res.status(404).json({
97
268
  success: false,
@@ -115,16 +286,86 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
115
286
  });
116
287
  }
117
288
  });
289
+ /**
290
+ * GET /api/executions/:executionId/chain
291
+ *
292
+ * Get execution chain (root execution + all follow-ups)
293
+ *
294
+ * Returns the full chain of executions starting from the root.
295
+ * If the requested execution is a follow-up, finds the root and returns the full chain.
296
+ * Executions are ordered chronologically (oldest first).
297
+ */
298
+ router.get("/executions/:executionId/chain", (req, res) => {
299
+ try {
300
+ const { executionId } = req.params;
301
+ const db = req.project.db;
302
+ // Get the requested execution
303
+ const execution = req.project.executionService.getExecution(executionId);
304
+ if (!execution) {
305
+ res.status(404).json({
306
+ success: false,
307
+ data: null,
308
+ message: `Execution not found: ${executionId}`,
309
+ });
310
+ return;
311
+ }
312
+ // Find the root execution by traversing up parent_execution_id
313
+ let rootId = executionId;
314
+ let current = execution;
315
+ while (current.parent_execution_id) {
316
+ rootId = current.parent_execution_id;
317
+ const parent = req.project.executionService.getExecution(rootId);
318
+ if (!parent)
319
+ break;
320
+ current = parent;
321
+ }
322
+ // Get all executions in the chain (root + all descendants)
323
+ // Using recursive CTE to get all descendants
324
+ const chain = db
325
+ .prepare(`
326
+ WITH RECURSIVE execution_chain AS (
327
+ -- Base case: the root execution
328
+ SELECT * FROM executions WHERE id = ?
329
+ UNION ALL
330
+ -- Recursive case: children of executions in the chain
331
+ SELECT e.* FROM executions e
332
+ INNER JOIN execution_chain ec ON e.parent_execution_id = ec.id
333
+ )
334
+ SELECT * FROM execution_chain
335
+ ORDER BY created_at ASC
336
+ `)
337
+ .all(rootId);
338
+ res.json({
339
+ success: true,
340
+ data: {
341
+ rootId,
342
+ executions: chain,
343
+ },
344
+ });
345
+ }
346
+ catch (error) {
347
+ console.error("Error getting execution chain:", error);
348
+ res.status(500).json({
349
+ success: false,
350
+ data: null,
351
+ error_data: error instanceof Error ? error.message : String(error),
352
+ message: "Failed to get execution chain",
353
+ });
354
+ }
355
+ });
118
356
  /**
119
357
  * GET /api/executions/:executionId/logs
120
358
  *
121
- * Get raw execution logs for historical replay
359
+ * Get AG-UI events for historical replay
360
+ *
361
+ * Fetches NormalizedEntry logs from storage and converts them to AG-UI events on-demand.
362
+ * This preserves full structured data in storage while serving UI-ready events to frontend.
122
363
  */
123
- router.get("/executions/:executionId/logs", (req, res) => {
364
+ router.get("/executions/:executionId/logs", async (req, res) => {
124
365
  try {
125
366
  const { executionId } = req.params;
126
367
  // Verify execution exists
127
- const execution = service.getExecution(executionId);
368
+ const execution = req.project.executionService.getExecution(executionId);
128
369
  if (!execution) {
129
370
  res.status(404).json({
130
371
  success: false,
@@ -133,14 +374,27 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
133
374
  });
134
375
  return;
135
376
  }
136
- // Fetch raw logs and metadata
137
- const logs = store.getRawLogs(executionId);
138
- const metadata = store.getLogMetadata(executionId);
377
+ // Fetch normalized entries from storage
378
+ const normalizedEntries = req.project.logsStore.getNormalizedEntries(executionId);
379
+ const metadata = req.project.logsStore.getLogMetadata(executionId);
380
+ // Convert NormalizedEntry to AG-UI events on-demand
381
+ const events = [];
382
+ // Create a temporary AG-UI adapter to collect events
383
+ const agUiAdapter = new AgUiEventAdapter(executionId);
384
+ agUiAdapter.onEvent((event) => {
385
+ events.push(event);
386
+ });
387
+ // Create normalized adapter to transform entries
388
+ const normalizedAdapter = new NormalizedEntryToAgUiAdapter(agUiAdapter);
389
+ // Process all normalized entries through the adapter
390
+ for (const entry of normalizedEntries) {
391
+ await normalizedAdapter.processEntry(entry);
392
+ }
139
393
  res.json({
140
394
  success: true,
141
395
  data: {
142
396
  executionId,
143
- logs,
397
+ events,
144
398
  metadata: metadata
145
399
  ? {
146
400
  lineCount: metadata.line_count,
@@ -167,6 +421,93 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
167
421
  });
168
422
  }
169
423
  });
424
+ /**
425
+ * GET /api/executions/:executionId/changes
426
+ *
427
+ * Get code changes (file list + diff statistics) for an execution
428
+ *
429
+ * Calculates changes on-demand from commit SHAs. Supports:
430
+ * - Committed changes (commit-to-commit diff)
431
+ * - Uncommitted changes (working tree diff)
432
+ * - Unavailable states with clear error reasons
433
+ */
434
+ router.get("/executions/:executionId/changes", async (req, res) => {
435
+ try {
436
+ const { executionId } = req.params;
437
+ const db = req.project.db;
438
+ const repoPath = req.project.path;
439
+ // Create changes service
440
+ const changesService = new ExecutionChangesService(db, repoPath);
441
+ // Get changes
442
+ const result = await changesService.getChanges(executionId);
443
+ res.json({
444
+ success: true,
445
+ data: result,
446
+ });
447
+ }
448
+ catch (error) {
449
+ console.error("[GET /executions/:id/changes] Error:", error);
450
+ res.status(500).json({
451
+ success: false,
452
+ data: null,
453
+ error_data: error instanceof Error ? error.message : String(error),
454
+ message: "Failed to calculate changes",
455
+ });
456
+ }
457
+ });
458
+ /**
459
+ * GET /api/executions/:executionId/changes/file
460
+ *
461
+ * Get diff content for a specific file in an execution
462
+ *
463
+ * Query params:
464
+ * - filePath: Path to the file to get diff for
465
+ */
466
+ router.get("/executions/:executionId/changes/file", async (req, res) => {
467
+ try {
468
+ const { executionId } = req.params;
469
+ const { filePath } = req.query;
470
+ if (!filePath || typeof filePath !== "string") {
471
+ res.status(400).json({
472
+ success: false,
473
+ data: null,
474
+ message: "filePath query parameter is required",
475
+ });
476
+ return;
477
+ }
478
+ const db = req.project.db;
479
+ const repoPath = req.project.path;
480
+ // Create changes service
481
+ const changesService = new ExecutionChangesService(db, repoPath);
482
+ // Get file diff
483
+ const result = await changesService.getFileDiff(executionId, filePath);
484
+ if (!result.success) {
485
+ res.status(400).json({
486
+ success: false,
487
+ data: null,
488
+ message: result.error || "Failed to get file diff",
489
+ });
490
+ return;
491
+ }
492
+ res.json({
493
+ success: true,
494
+ data: {
495
+ filePath,
496
+ oldContent: result.oldContent,
497
+ newContent: result.newContent,
498
+ },
499
+ });
500
+ }
501
+ catch (error) {
502
+ console.error("[GET /executions/:id/changes/file] Error:", error);
503
+ res.status(500).json({
504
+ success: false,
505
+ data: null,
506
+ error_data: error instanceof Error ? error.message : String(error),
507
+ message: "Failed to get file diff",
508
+ });
509
+ }
510
+ });
170
511
  /**
171
512
  * GET /api/issues/:issueId/executions
172
513
  *
@@ -175,7 +516,7 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
175
516
  router.get("/issues/:issueId/executions", (req, res) => {
176
517
  try {
177
518
  const { issueId } = req.params;
178
- const executions = service.listExecutions(issueId);
519
+ const executions = req.project.executionService.listExecutions(issueId);
179
520
  res.json({
180
521
  success: true,
181
522
  data: executions,
@@ -209,7 +550,7 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
209
550
  });
210
551
  return;
211
552
  }
212
- const followUpExecution = await service.createFollowUp(executionId, feedback);
553
+ const followUpExecution = await req.project.executionService.createFollowUp(executionId, feedback);
213
554
  res.status(201).json({
214
555
  success: true,
215
556
  data: followUpExecution,
@@ -232,14 +573,14 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
232
573
  }
233
574
  });
234
575
  /**
235
- * DELETE /api/executions/:executionId
576
+ * POST /api/executions/:executionId/cancel
236
577
  *
237
578
  * Cancel a running execution
238
579
  */
239
- router.delete("/executions/:executionId", async (req, res) => {
580
+ router.post("/executions/:executionId/cancel", async (req, res) => {
240
581
  try {
241
582
  const { executionId } = req.params;
242
- await service.cancelExecution(executionId);
583
+ await req.project.executionService.cancelExecution(executionId);
243
584
  res.json({
244
585
  success: true,
245
586
  data: { executionId },
@@ -259,6 +600,51 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
259
600
  });
260
601
  }
261
602
  });
603
+ /**
604
+ * DELETE /api/executions/:executionId
605
+ *
606
+ * Delete an execution and its entire chain (or cancel if ?cancel=true)
607
+ *
608
+ * Query parameters:
609
+ * - cancel: if "true", cancel the execution instead of deleting it
610
+ * - deleteBranch: if "true", also delete the execution's branch
611
+ * - deleteWorktree: if "true", also delete the execution's worktree
612
+ */
613
+ router.delete("/executions/:executionId", async (req, res) => {
614
+ try {
615
+ const { executionId } = req.params;
616
+ const { cancel, deleteBranch, deleteWorktree } = req.query;
617
+ // If cancel query param is true, cancel the execution
618
+ if (cancel === "true") {
619
+ await req.project.executionService.cancelExecution(executionId);
620
+ res.json({
621
+ success: true,
622
+ data: { executionId },
623
+ message: "Execution cancelled successfully",
624
+ });
625
+ return;
626
+ }
627
+ // Otherwise, delete the execution and its chain
628
+ await req.project.executionService.deleteExecution(executionId, deleteBranch === "true", deleteWorktree === "true");
629
+ res.json({
630
+ success: true,
631
+ data: { executionId },
632
+ message: "Execution deleted successfully",
633
+ });
634
+ }
635
+ catch (error) {
636
+ console.error("Error deleting/cancelling execution:", error);
637
+ // Handle specific error cases
638
+ const errorMessage = error instanceof Error ? error.message : String(error);
639
+ const statusCode = errorMessage.includes("not found") ? 404 : 500;
640
+ res.status(statusCode).json({
641
+ success: false,
642
+ data: null,
643
+ error_data: errorMessage,
644
+ message: "Failed to delete/cancel execution",
645
+ });
646
+ }
647
+ });
262
648
  /**
263
649
  * GET /api/executions/:executionId/worktree
264
650
  *
@@ -267,7 +653,7 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
267
653
  router.get("/executions/:executionId/worktree", async (req, res) => {
268
654
  try {
269
655
  const { executionId } = req.params;
270
- const exists = await service.worktreeExists(executionId);
656
+ const exists = await req.project.executionService.worktreeExists(executionId);
271
657
  res.json({
272
658
  success: true,
273
659
  data: { exists },
@@ -287,11 +673,15 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
287
673
  * DELETE /api/executions/:executionId/worktree
288
674
  *
289
675
  * Delete the worktree for an execution
676
+ *
677
+ * Query parameters:
678
+ * - deleteBranch: if "true", also delete the execution's branch
290
679
  */
291
680
  router.delete("/executions/:executionId/worktree", async (req, res) => {
292
681
  try {
293
682
  const { executionId } = req.params;
294
- await service.deleteWorktree(executionId);
683
+ const { deleteBranch } = req.query;
684
+ await req.project.executionService.deleteWorktree(executionId, deleteBranch === "true");
295
685
  res.json({
296
686
  success: true,
297
687
  data: { executionId },
@@ -318,6 +708,371 @@ export function createExecutionsRouter(db, repoPath, transportManager, execution
318
708
  });
319
709
  }
320
710
  });
711
+ /**
712
+ * GET /api/executions/:executionId/sync/preview
713
+ *
714
+ * Preview sync changes and detect conflicts
715
+ *
716
+ * Returns preview of what would happen if sync is performed,
717
+ * including conflicts, diff, commits, and warnings.
718
+ */
719
+ router.get("/executions/:executionId/sync/preview", async (req, res) => {
720
+ try {
721
+ const { executionId } = req.params;
722
+ // Get worktree sync service
723
+ const syncService = getWorktreeSyncService(req);
724
+ // Preview sync
725
+ const preview = await syncService.previewSync(executionId);
726
+ res.json({
727
+ success: true,
728
+ data: preview,
729
+ });
730
+ }
731
+ catch (error) {
732
+ console.error(`Failed to preview sync for execution ${req.params.executionId}:`, error);
733
+ if (error instanceof WorktreeSyncError) {
734
+ const statusCode = getStatusCodeForSyncError(error);
735
+ res.status(statusCode).json({
736
+ success: false,
737
+ data: null,
738
+ error: error.message,
739
+ code: error.code,
740
+ });
741
+ }
742
+ else {
743
+ res.status(500).json({
744
+ success: false,
745
+ data: null,
746
+ error: "Internal server error",
747
+ message: error instanceof Error ? error.message : String(error),
748
+ });
749
+ }
750
+ }
751
+ });
752
+ /**
753
+ * POST /api/executions/:executionId/sync/squash
754
+ *
755
+ * Perform squash sync operation
756
+ *
757
+ * Combines all worktree changes into a single commit on the target branch.
758
+ * Automatically resolves JSONL conflicts using merge-resolver.
759
+ *
760
+ * Request body:
761
+ * - commitMessage?: string - Optional custom commit message
762
+ */
763
+ router.post("/executions/:executionId/sync/squash", async (req, res) => {
764
+ try {
765
+ const { executionId } = req.params;
766
+ const { commitMessage } = req.body || {};
767
+ // Get worktree sync service
768
+ const syncService = getWorktreeSyncService(req);
769
+ // Check if squashSync method exists
770
+ if (typeof syncService.squashSync !== "function") {
771
+ res.status(501).json({
772
+ success: false,
773
+ data: null,
774
+ error: "Squash sync not yet implemented",
775
+ message: "The squashSync operation is not available yet",
776
+ });
777
+ return;
778
+ }
779
+ // Perform squash sync
780
+ const result = await syncService.squashSync(executionId, commitMessage);
781
+ res.json({
782
+ success: true,
783
+ data: result,
784
+ });
785
+ }
786
+ catch (error) {
787
+ console.error(`Failed to squash sync execution ${req.params.executionId}:`, error);
788
+ if (error instanceof WorktreeSyncError) {
789
+ const statusCode = getStatusCodeForSyncError(error);
790
+ res.status(statusCode).json({
791
+ success: false,
792
+ data: null,
793
+ error: error.message,
794
+ code: error.code,
795
+ });
796
+ }
797
+ else {
798
+ res.status(500).json({
799
+ success: false,
800
+ data: null,
801
+ error: "Internal server error",
802
+ message: error instanceof Error ? error.message : String(error),
803
+ });
804
+ }
805
+ }
806
+ });
807
+ /**
808
+ * POST /api/executions/:executionId/sync/stage
809
+ *
810
+ * Perform stage sync operation
811
+ *
812
+ * Applies committed worktree changes to the working directory without committing.
813
+ * Changes are left staged, ready for the user to commit manually.
814
+ *
815
+ * Request body:
816
+ * - includeUncommitted?: boolean - If true, also copy uncommitted files from worktree
817
+ */
818
+ router.post("/executions/:executionId/sync/stage", async (req, res) => {
819
+ try {
820
+ const { executionId } = req.params;
821
+ const { includeUncommitted } = req.body || {};
822
+ // Get worktree sync service
823
+ const syncService = getWorktreeSyncService(req);
824
+ // Perform stage sync with options
825
+ const result = await syncService.stageSync(executionId, {
826
+ includeUncommitted: includeUncommitted === true,
827
+ });
828
+ res.json({
829
+ success: true,
830
+ data: result,
831
+ });
832
+ }
833
+ catch (error) {
834
+ console.error(`Failed to stage sync execution ${req.params.executionId}:`, error);
835
+ if (error instanceof WorktreeSyncError) {
836
+ const statusCode = getStatusCodeForSyncError(error);
837
+ res.status(statusCode).json({
838
+ success: false,
839
+ data: null,
840
+ error: error.message,
841
+ code: error.code,
842
+ });
843
+ }
844
+ else {
845
+ res.status(500).json({
846
+ success: false,
847
+ data: null,
848
+ error: "Internal server error",
849
+ message: error instanceof Error ? error.message : String(error),
850
+ });
851
+ }
852
+ }
853
+ });
854
+ /**
855
+ * POST /api/executions/:executionId/sync/preserve
856
+ *
857
+ * Perform preserve sync operation
858
+ *
859
+ * Merges all commits from worktree branch to target branch, preserving commit history.
860
+ * Only includes committed changes - uncommitted changes are excluded.
861
+ */
862
+ router.post("/executions/:executionId/sync/preserve", async (req, res) => {
863
+ try {
864
+ const { executionId } = req.params;
865
+ // Get worktree sync service
866
+ const syncService = getWorktreeSyncService(req);
867
+ // Perform preserve sync
868
+ const result = await syncService.preserveSync(executionId);
869
+ res.json({
870
+ success: true,
871
+ data: result,
872
+ });
873
+ }
874
+ catch (error) {
875
+ console.error(`Failed to preserve sync execution ${req.params.executionId}:`, error);
876
+ if (error instanceof WorktreeSyncError) {
877
+ const statusCode = getStatusCodeForSyncError(error);
878
+ res.status(statusCode).json({
879
+ success: false,
880
+ data: null,
881
+ error: error.message,
882
+ code: error.code,
883
+ });
884
+ }
885
+ else {
886
+ res.status(500).json({
887
+ success: false,
888
+ data: null,
889
+ error: "Internal server error",
890
+ message: error instanceof Error ? error.message : String(error),
891
+ });
892
+ }
893
+ }
894
+ });
895
+ /**
896
+ * POST /api/executions/:executionId/commit
897
+ *
898
+ * Commit uncommitted changes for an execution
899
+ *
900
+ * Commits changes to the appropriate branch based on execution mode:
901
+ * - Local mode: Commits to target_branch (current branch)
902
+ * - Worktree mode: Commits to branch_name (temp branch) in worktree
903
+ *
904
+ * Request body:
905
+ * - message: string (required) - Commit message
906
+ */
907
+ router.post("/executions/:executionId/commit", async (req, res) => {
908
+ try {
909
+ const { executionId } = req.params;
910
+ const { message } = req.body;
911
+ // Validate commit message
912
+ if (!message || typeof message !== "string" || !message.trim()) {
913
+ res.status(400).json({
914
+ success: false,
915
+ data: null,
916
+ message: "Commit message is required and must be non-empty",
917
+ });
918
+ return;
919
+ }
920
+ const db = req.project.db;
921
+ const repoPath = req.project.path;
922
+ // Load execution from database
923
+ const execution = db
924
+ .prepare("SELECT * FROM executions WHERE id = ?")
925
+ .get(executionId);
926
+ if (!execution) {
927
+ res.status(404).json({
928
+ success: false,
929
+ data: null,
930
+ message: "Execution not found",
931
+ });
932
+ return;
933
+ }
934
+ // Determine working directory and target branch
935
+ // IMPORTANT: If worktree_path exists, always use it - this is more reliable than the mode field
936
+ // which may not be set correctly on follow-up executions
937
+ const hasWorktree = !!execution.worktree_path;
938
+ const workingDir = hasWorktree ? execution.worktree_path : repoPath;
939
+ const targetBranch = hasWorktree
940
+ ? execution.branch_name
941
+ : execution.target_branch || "main";
942
+ console.log(`[Commit] Execution ${executionId}: hasWorktree=${hasWorktree}, workingDir=${workingDir}, targetBranch=${targetBranch}, mode=${execution.mode}`);
943
+ // Get current uncommitted files from working directory instead of stale database field
944
+ // This ensures we're working with the current state
945
+ let filesChanged = [];
946
+ try {
947
+ // Get modified tracked files
948
+ const modifiedOutput = execSync("git diff --name-only", {
949
+ cwd: workingDir,
950
+ encoding: "utf-8",
951
+ stdio: "pipe",
952
+ });
953
+ // Get staged files
954
+ const stagedOutput = execSync("git diff --cached --name-only", {
955
+ cwd: workingDir,
956
+ encoding: "utf-8",
957
+ stdio: "pipe",
958
+ });
959
+ // Get untracked files
960
+ const untrackedOutput = execSync("git ls-files --others --exclude-standard", {
961
+ cwd: workingDir,
962
+ encoding: "utf-8",
963
+ stdio: "pipe",
964
+ });
965
+ console.log(`[Commit] Git status in ${workingDir}:`, {
966
+ modified: modifiedOutput.trim().split("\n").filter(Boolean),
967
+ staged: stagedOutput.trim().split("\n").filter(Boolean),
968
+ untracked: untrackedOutput.trim().split("\n").filter(Boolean),
969
+ });
970
+ // Combine all files, removing duplicates
971
+ const allFiles = new Set();
972
+ for (const output of [
973
+ modifiedOutput,
974
+ stagedOutput,
975
+ untrackedOutput,
976
+ ]) {
977
+ output
978
+ .split("\n")
979
+ .filter((line) => line.trim())
980
+ .forEach((file) => allFiles.add(file));
981
+ }
982
+ filesChanged = Array.from(allFiles);
983
+ }
984
+ catch (error) {
985
+ console.error("Failed to get uncommitted files:", error);
986
+ }
987
+ // Validate has uncommitted changes
988
+ if (filesChanged.length === 0) {
989
+ res.status(400).json({
990
+ success: false,
991
+ data: null,
992
+ message: "No files to commit",
993
+ });
994
+ return;
995
+ }
996
+ // Execute git operations
997
+ try {
998
+ // Add all changes (more reliable than adding specific files)
999
+ // This catches any files that might have been missed in detection
1000
+ execSync("git add -A", {
1001
+ cwd: workingDir,
1002
+ encoding: "utf-8",
1003
+ stdio: "pipe",
1004
+ });
1005
+ console.log(`[Commit] Staged all changes with git add -A`);
1006
+ // Verify something is staged
1007
+ const stagedAfterAdd = execSync("git diff --cached --name-only", {
1008
+ cwd: workingDir,
1009
+ encoding: "utf-8",
1010
+ stdio: "pipe",
1011
+ }).trim();
1012
+ if (!stagedAfterAdd) {
1013
+ console.log(`[Commit] No files staged after git add -A`);
1014
+ res.status(400).json({
1015
+ success: false,
1016
+ data: null,
1017
+ message: "No files staged for commit after git add",
1018
+ });
1019
+ return;
1020
+ }
1021
+ console.log(`[Commit] Files staged: ${stagedAfterAdd.split("\n").filter(Boolean).join(", ")}`);
1022
+ // Commit using -F - to read message from stdin (safer than shell escaping)
1023
+ const { spawnSync } = await import("child_process");
1024
+ const commitResult = spawnSync("git", ["commit", "-m", message], {
1025
+ cwd: workingDir,
1026
+ encoding: "utf-8",
1027
+ stdio: "pipe",
1028
+ });
1029
+ if (commitResult.status !== 0) {
1030
+ const errorOutput = commitResult.stderr || commitResult.stdout || "Unknown error";
1031
+ console.error(`[Commit] git commit failed:`, errorOutput);
1032
+ throw new Error(`git commit failed: ${errorOutput}`);
1033
+ }
1034
+ console.log(`[Commit] git commit output:`, commitResult.stdout);
1035
+ // Get commit SHA
1036
+ const commitSha = execSync("git rev-parse HEAD", {
1037
+ cwd: workingDir,
1038
+ encoding: "utf-8",
1039
+ stdio: "pipe",
1040
+ }).trim();
1041
+ console.log(`[Commit] Successfully committed ${filesChanged.length} files: ${commitSha}`);
1042
+ // Note: We do NOT update execution.after_commit here
1043
+ // That field represents the state at execution completion time
1044
+ // Manual commits after execution are tracked separately
1045
+ res.json({
1046
+ success: true,
1047
+ data: {
1048
+ commitSha,
1049
+ filesCommitted: filesChanged.length,
1050
+ branch: targetBranch,
1051
+ },
1052
+ message: `Successfully committed ${filesChanged.length} file${filesChanged.length !== 1 ? "s" : ""}`,
1053
+ });
1054
+ }
1055
+ catch (gitError) {
1056
+ console.error("Git operation failed:", gitError);
1057
+ const errorMessage = gitError instanceof Error ? gitError.message : String(gitError);
1058
+ res.status(500).json({
1059
+ success: false,
1060
+ data: null,
1061
+ message: "Git commit failed",
1062
+ error: errorMessage,
1063
+ });
1064
+ }
1065
+ }
1066
+ catch (error) {
1067
+ console.error(`Failed to commit for execution ${req.params.executionId}:`, error);
1068
+ res.status(500).json({
1069
+ success: false,
1070
+ data: null,
1071
+ error: "Internal server error",
1072
+ message: error instanceof Error ? error.message : String(error),
1073
+ });
1074
+ }
1075
+ });
321
1076
  return router;
322
1077
  }
323
1078
  //# sourceMappingURL=executions.js.map