@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.
- package/README.md +6 -0
- package/dist/errors/agent-errors.d.ts +43 -0
- package/dist/errors/agent-errors.d.ts.map +1 -0
- package/dist/errors/agent-errors.js +69 -0
- package/dist/errors/agent-errors.js.map +1 -0
- package/dist/execution/adapters/claude-adapter.d.ts +63 -0
- package/dist/execution/adapters/claude-adapter.d.ts.map +1 -0
- package/dist/execution/adapters/claude-adapter.js +82 -0
- package/dist/execution/adapters/claude-adapter.js.map +1 -0
- package/dist/execution/adapters/codex-adapter.d.ts +67 -0
- package/dist/execution/adapters/codex-adapter.d.ts.map +1 -0
- package/dist/execution/adapters/codex-adapter.js +183 -0
- package/dist/execution/adapters/codex-adapter.js.map +1 -0
- package/dist/execution/adapters/codex-config-builder.d.ts +30 -0
- package/dist/execution/adapters/codex-config-builder.d.ts.map +1 -0
- package/dist/execution/adapters/codex-config-builder.js +110 -0
- package/dist/execution/adapters/codex-config-builder.js.map +1 -0
- package/dist/execution/adapters/copilot-adapter.d.ts +94 -0
- package/dist/execution/adapters/copilot-adapter.d.ts.map +1 -0
- package/dist/execution/adapters/copilot-adapter.js +163 -0
- package/dist/execution/adapters/copilot-adapter.js.map +1 -0
- package/dist/execution/adapters/copilot-config-builder.d.ts +48 -0
- package/dist/execution/adapters/copilot-config-builder.d.ts.map +1 -0
- package/dist/execution/adapters/copilot-config-builder.js +125 -0
- package/dist/execution/adapters/copilot-config-builder.js.map +1 -0
- package/dist/execution/adapters/cursor-adapter.d.ts +66 -0
- package/dist/execution/adapters/cursor-adapter.d.ts.map +1 -0
- package/dist/execution/adapters/cursor-adapter.js +121 -0
- package/dist/execution/adapters/cursor-adapter.js.map +1 -0
- package/dist/execution/adapters/cursor-config-builder.d.ts +29 -0
- package/dist/execution/adapters/cursor-config-builder.d.ts.map +1 -0
- package/dist/execution/adapters/cursor-config-builder.js +49 -0
- package/dist/execution/adapters/cursor-config-builder.js.map +1 -0
- package/dist/execution/adapters/shared/config-presets.d.ts +102 -0
- package/dist/execution/adapters/shared/config-presets.d.ts.map +1 -0
- package/dist/execution/adapters/shared/config-presets.js +205 -0
- package/dist/execution/adapters/shared/config-presets.js.map +1 -0
- package/dist/execution/adapters/shared/config-utils.d.ts +95 -0
- package/dist/execution/adapters/shared/config-utils.d.ts.map +1 -0
- package/dist/execution/adapters/shared/config-utils.js +163 -0
- package/dist/execution/adapters/shared/config-utils.js.map +1 -0
- package/dist/execution/adapters/shared/index.d.ts +8 -0
- package/dist/execution/adapters/shared/index.d.ts.map +1 -0
- package/dist/execution/adapters/shared/index.js +8 -0
- package/dist/execution/adapters/shared/index.js.map +1 -0
- package/dist/execution/executors/agent-executor-wrapper.d.ts +153 -0
- package/dist/execution/executors/agent-executor-wrapper.d.ts.map +1 -0
- package/dist/execution/executors/agent-executor-wrapper.js +652 -0
- package/dist/execution/executors/agent-executor-wrapper.js.map +1 -0
- package/dist/execution/executors/executor-factory.d.ts +95 -0
- package/dist/execution/executors/executor-factory.d.ts.map +1 -0
- package/dist/execution/executors/executor-factory.js +120 -0
- package/dist/execution/executors/executor-factory.js.map +1 -0
- package/dist/execution/output/ag-ui-adapter.d.ts +0 -2
- package/dist/execution/output/ag-ui-adapter.d.ts.map +1 -1
- package/dist/execution/output/ag-ui-adapter.js +0 -2
- package/dist/execution/output/ag-ui-adapter.js.map +1 -1
- package/dist/execution/output/index.d.ts +0 -3
- package/dist/execution/output/index.d.ts.map +1 -1
- package/dist/execution/output/index.js +0 -2
- package/dist/execution/output/index.js.map +1 -1
- package/dist/execution/output/normalized-to-ag-ui-adapter.d.ts +108 -0
- package/dist/execution/output/normalized-to-ag-ui-adapter.d.ts.map +1 -0
- package/dist/execution/output/normalized-to-ag-ui-adapter.js +321 -0
- package/dist/execution/output/normalized-to-ag-ui-adapter.js.map +1 -0
- package/dist/execution/process/builders/claude.d.ts +24 -57
- package/dist/execution/process/builders/claude.d.ts.map +1 -1
- package/dist/execution/process/builders/claude.js +153 -19
- package/dist/execution/process/builders/claude.js.map +1 -1
- package/dist/execution/transport/ipc-transport-manager.d.ts +74 -0
- package/dist/execution/transport/ipc-transport-manager.d.ts.map +1 -0
- package/dist/execution/transport/ipc-transport-manager.js +104 -0
- package/dist/execution/transport/ipc-transport-manager.js.map +1 -0
- package/dist/execution/transport/transport-manager.d.ts.map +1 -1
- package/dist/execution/transport/transport-manager.js +3 -0
- package/dist/execution/transport/transport-manager.js.map +1 -1
- package/dist/execution/worktree/conflict-detector.d.ts +85 -0
- package/dist/execution/worktree/conflict-detector.d.ts.map +1 -0
- package/dist/execution/worktree/conflict-detector.js +129 -0
- package/dist/execution/worktree/conflict-detector.js.map +1 -0
- package/dist/execution/worktree/git-cli.d.ts +9 -0
- package/dist/execution/worktree/git-cli.d.ts.map +1 -1
- package/dist/execution/worktree/git-cli.js +10 -0
- package/dist/execution/worktree/git-cli.js.map +1 -1
- package/dist/execution/worktree/git-sync-cli.d.ts +198 -0
- package/dist/execution/worktree/git-sync-cli.d.ts.map +1 -0
- package/dist/execution/worktree/git-sync-cli.js +401 -0
- package/dist/execution/worktree/git-sync-cli.js.map +1 -0
- package/dist/execution/worktree/manager.d.ts +18 -0
- package/dist/execution/worktree/manager.d.ts.map +1 -1
- package/dist/execution/worktree/manager.js +9 -3
- package/dist/execution/worktree/manager.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +124 -229
- package/dist/index.js.map +1 -1
- package/dist/middleware/project-context.d.ts +37 -0
- package/dist/middleware/project-context.d.ts.map +1 -0
- package/dist/middleware/project-context.js +91 -0
- package/dist/middleware/project-context.js.map +1 -0
- package/dist/public/assets/index-DV9Tbujb.css +1 -0
- package/dist/public/assets/index-DcDX9-Ad.js +740 -0
- package/dist/public/assets/index-DcDX9-Ad.js.map +1 -0
- package/dist/public/assets/{react-vendor-ByUx1V_q.js → react-vendor-DiL5hC7l.js} +2 -2
- package/dist/public/assets/{react-vendor-ByUx1V_q.js.map → react-vendor-DiL5hC7l.js.map} +1 -1
- package/dist/public/assets/ui-vendor-B4WMPEfa.js +54 -0
- package/dist/public/assets/ui-vendor-B4WMPEfa.js.map +1 -0
- package/dist/public/index.html +4 -4
- package/dist/routes/agents.d.ts +3 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +62 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/config.d.ts +3 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +25 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/editors.d.ts +15 -0
- package/dist/routes/editors.d.ts.map +1 -0
- package/dist/routes/editors.js +98 -0
- package/dist/routes/editors.js.map +1 -0
- package/dist/routes/executions-stream.d.ts +8 -5
- package/dist/routes/executions-stream.d.ts.map +1 -1
- package/dist/routes/executions-stream.js +10 -6
- package/dist/routes/executions-stream.js.map +1 -1
- package/dist/routes/executions.d.ts +6 -10
- package/dist/routes/executions.d.ts.map +1 -1
- package/dist/routes/executions.js +792 -37
- package/dist/routes/executions.js.map +1 -1
- package/dist/routes/feedback.d.ts +3 -2
- package/dist/routes/feedback.d.ts.map +1 -1
- package/dist/routes/feedback.js +12 -10
- package/dist/routes/feedback.js.map +1 -1
- package/dist/routes/files.d.ts +18 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +89 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/issues.d.ts +3 -2
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +19 -18
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/projects.d.ts +11 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +447 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/relationships.d.ts +3 -2
- package/dist/routes/relationships.d.ts.map +1 -1
- package/dist/routes/relationships.js +12 -10
- package/dist/routes/relationships.js.map +1 -1
- package/dist/routes/repo-info.d.ts +3 -0
- package/dist/routes/repo-info.d.ts.map +1 -0
- package/dist/routes/repo-info.js +203 -0
- package/dist/routes/repo-info.js.map +1 -0
- package/dist/routes/specs.d.ts +3 -2
- package/dist/routes/specs.d.ts.map +1 -1
- package/dist/routes/specs.js +19 -18
- package/dist/routes/specs.js.map +1 -1
- package/dist/routes/version.d.ts +3 -0
- package/dist/routes/version.d.ts.map +1 -0
- package/dist/routes/version.js +25 -0
- package/dist/routes/version.js.map +1 -0
- package/dist/services/agent-registry.d.ts +140 -0
- package/dist/services/agent-registry.d.ts.map +1 -0
- package/dist/services/agent-registry.js +272 -0
- package/dist/services/agent-registry.js.map +1 -0
- package/dist/services/editor-service.d.ts +57 -0
- package/dist/services/editor-service.d.ts.map +1 -0
- package/dist/services/editor-service.js +204 -0
- package/dist/services/editor-service.js.map +1 -0
- package/dist/services/execution-changes-service.d.ts +110 -0
- package/dist/services/execution-changes-service.d.ts.map +1 -0
- package/dist/services/execution-changes-service.js +700 -0
- package/dist/services/execution-changes-service.js.map +1 -0
- package/dist/services/execution-lifecycle.d.ts +1 -0
- package/dist/services/execution-lifecycle.d.ts.map +1 -1
- package/dist/services/execution-lifecycle.js +37 -7
- package/dist/services/execution-lifecycle.js.map +1 -1
- package/dist/services/execution-logs-store.d.ts +75 -0
- package/dist/services/execution-logs-store.d.ts.map +1 -1
- package/dist/services/execution-logs-store.js +142 -2
- package/dist/services/execution-logs-store.js.map +1 -1
- package/dist/services/execution-service.d.ts +82 -59
- package/dist/services/execution-service.d.ts.map +1 -1
- package/dist/services/execution-service.js +514 -469
- package/dist/services/execution-service.js.map +1 -1
- package/dist/services/execution-worker-pool.d.ts +116 -0
- package/dist/services/execution-worker-pool.d.ts.map +1 -0
- package/dist/services/execution-worker-pool.js +326 -0
- package/dist/services/execution-worker-pool.js.map +1 -0
- package/dist/services/executions.d.ts +3 -0
- package/dist/services/executions.d.ts.map +1 -1
- package/dist/services/executions.js +11 -17
- package/dist/services/executions.js.map +1 -1
- package/dist/services/export.d.ts +8 -2
- package/dist/services/export.d.ts.map +1 -1
- package/dist/services/export.js +29 -23
- package/dist/services/export.js.map +1 -1
- package/dist/services/file-search/git-ls-files-strategy.d.ts +72 -0
- package/dist/services/file-search/git-ls-files-strategy.d.ts.map +1 -0
- package/dist/services/file-search/git-ls-files-strategy.js +176 -0
- package/dist/services/file-search/git-ls-files-strategy.js.map +1 -0
- package/dist/services/file-search/index.d.ts +9 -0
- package/dist/services/file-search/index.d.ts.map +1 -0
- package/dist/services/file-search/index.js +10 -0
- package/dist/services/file-search/index.js.map +1 -0
- package/dist/services/file-search/registry.d.ts +97 -0
- package/dist/services/file-search/registry.d.ts.map +1 -0
- package/dist/services/file-search/registry.js +140 -0
- package/dist/services/file-search/registry.js.map +1 -0
- package/dist/services/file-search/strategy.d.ts +58 -0
- package/dist/services/file-search/strategy.d.ts.map +1 -0
- package/dist/services/file-search/strategy.js +8 -0
- package/dist/services/file-search/strategy.js.map +1 -0
- package/dist/services/project-context.d.ts +69 -0
- package/dist/services/project-context.d.ts.map +1 -0
- package/dist/services/project-context.js +113 -0
- package/dist/services/project-context.js.map +1 -0
- package/dist/services/project-manager.d.ts +95 -0
- package/dist/services/project-manager.d.ts.map +1 -0
- package/dist/services/project-manager.js +388 -0
- package/dist/services/project-manager.js.map +1 -0
- package/dist/services/project-registry.d.ts +98 -0
- package/dist/services/project-registry.d.ts.map +1 -0
- package/dist/services/project-registry.js +289 -0
- package/dist/services/project-registry.js.map +1 -0
- package/dist/services/prompt-resolver.d.ts +97 -0
- package/dist/services/prompt-resolver.d.ts.map +1 -0
- package/dist/services/prompt-resolver.js +377 -0
- package/dist/services/prompt-resolver.js.map +1 -0
- package/dist/services/repo-info.d.ts +12 -0
- package/dist/services/repo-info.d.ts.map +1 -1
- package/dist/services/repo-info.js +46 -0
- package/dist/services/repo-info.js.map +1 -1
- package/dist/services/version-service.d.ts +14 -0
- package/dist/services/version-service.d.ts.map +1 -0
- package/dist/services/version-service.js +57 -0
- package/dist/services/version-service.js.map +1 -0
- package/dist/services/watcher.d.ts +3 -4
- package/dist/services/watcher.d.ts.map +1 -1
- package/dist/services/watcher.js +18 -35
- package/dist/services/watcher.js.map +1 -1
- package/dist/services/websocket.d.ts +30 -16
- package/dist/services/websocket.d.ts.map +1 -1
- package/dist/services/websocket.js +102 -37
- package/dist/services/websocket.js.map +1 -1
- package/dist/services/worktree-sync-service.d.ts +326 -0
- package/dist/services/worktree-sync-service.d.ts.map +1 -0
- package/dist/services/worktree-sync-service.js +1091 -0
- package/dist/services/worktree-sync-service.js.map +1 -0
- package/dist/types/editor.d.ts +49 -0
- package/dist/types/editor.d.ts.map +1 -0
- package/dist/types/editor.js +50 -0
- package/dist/types/editor.js.map +1 -0
- package/dist/types/project.d.ts +58 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +10 -0
- package/dist/types/project.js.map +1 -0
- package/dist/utils/executable-check.d.ts +36 -0
- package/dist/utils/executable-check.d.ts.map +1 -0
- package/dist/utils/executable-check.js +79 -0
- package/dist/utils/executable-check.js.map +1 -0
- package/dist/workers/execution-worker.d.ts +18 -0
- package/dist/workers/execution-worker.d.ts.map +1 -0
- package/dist/workers/execution-worker.js +340 -0
- package/dist/workers/execution-worker.js.map +1 -0
- package/dist/workers/worker-ipc.d.ts +84 -0
- package/dist/workers/worker-ipc.d.ts.map +1 -0
- package/dist/workers/worker-ipc.js +29 -0
- package/dist/workers/worker-ipc.js.map +1 -0
- package/package.json +6 -5
- package/dist/execution/output/ag-ui-integration.d.ts +0 -96
- package/dist/execution/output/ag-ui-integration.d.ts.map +0 -1
- package/dist/execution/output/ag-ui-integration.js +0 -96
- package/dist/execution/output/ag-ui-integration.js.map +0 -1
- package/dist/execution/output/claude-code-output-processor.d.ts +0 -321
- package/dist/execution/output/claude-code-output-processor.d.ts.map +0 -1
- package/dist/execution/output/claude-code-output-processor.js +0 -769
- package/dist/execution/output/claude-code-output-processor.js.map +0 -1
- package/dist/public/assets/index-B3SEMufD.js +0 -580
- package/dist/public/assets/index-B3SEMufD.js.map +0 -1
- package/dist/public/assets/index-D2YGL3gX.css +0 -1
- package/dist/public/assets/ui-vendor-CotR6bx9.js +0 -54
- 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 {
|
|
8
|
-
import {
|
|
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
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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(
|
|
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
|
-
*
|
|
64
|
+
* GET /api/executions
|
|
26
65
|
*
|
|
27
|
-
*
|
|
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.
|
|
78
|
+
router.get("/executions", (req, res) => {
|
|
30
79
|
try {
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
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("
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
137
|
-
const
|
|
138
|
-
const metadata =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
*
|
|
576
|
+
* POST /api/executions/:executionId/cancel
|
|
236
577
|
*
|
|
237
578
|
* Cancel a running execution
|
|
238
579
|
*/
|
|
239
|
-
router.
|
|
580
|
+
router.post("/executions/:executionId/cancel", async (req, res) => {
|
|
240
581
|
try {
|
|
241
582
|
const { executionId } = req.params;
|
|
242
|
-
await
|
|
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
|
|
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
|
-
|
|
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
|