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