@generacy-ai/workflow-engine 0.0.0-preview-20260304013206
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/LICENSE +191 -0
- package/README.md +265 -0
- package/dist/actions/base-action.d.ts +63 -0
- package/dist/actions/base-action.d.ts.map +1 -0
- package/dist/actions/base-action.js +130 -0
- package/dist/actions/base-action.js.map +1 -0
- package/dist/actions/builtin/agent-invoke.d.ts +16 -0
- package/dist/actions/builtin/agent-invoke.d.ts.map +1 -0
- package/dist/actions/builtin/agent-invoke.js +108 -0
- package/dist/actions/builtin/agent-invoke.js.map +1 -0
- package/dist/actions/builtin/humancy-api-handler.d.ts +191 -0
- package/dist/actions/builtin/humancy-api-handler.d.ts.map +1 -0
- package/dist/actions/builtin/humancy-api-handler.js +521 -0
- package/dist/actions/builtin/humancy-api-handler.js.map +1 -0
- package/dist/actions/builtin/humancy-review.d.ts +83 -0
- package/dist/actions/builtin/humancy-review.d.ts.map +1 -0
- package/dist/actions/builtin/humancy-review.js +250 -0
- package/dist/actions/builtin/humancy-review.js.map +1 -0
- package/dist/actions/builtin/pr-create.d.ts +16 -0
- package/dist/actions/builtin/pr-create.d.ts.map +1 -0
- package/dist/actions/builtin/pr-create.js +110 -0
- package/dist/actions/builtin/pr-create.js.map +1 -0
- package/dist/actions/builtin/shell.d.ts +16 -0
- package/dist/actions/builtin/shell.d.ts.map +1 -0
- package/dist/actions/builtin/shell.js +80 -0
- package/dist/actions/builtin/shell.js.map +1 -0
- package/dist/actions/builtin/speckit/index.d.ts +47 -0
- package/dist/actions/builtin/speckit/index.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/index.js +347 -0
- package/dist/actions/builtin/speckit/index.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/feature.d.ts +17 -0
- package/dist/actions/builtin/speckit/lib/feature.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/feature.js +430 -0
- package/dist/actions/builtin/speckit/lib/feature.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/fs.d.ts +62 -0
- package/dist/actions/builtin/speckit/lib/fs.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/fs.js +181 -0
- package/dist/actions/builtin/speckit/lib/fs.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/paths.d.ts +7 -0
- package/dist/actions/builtin/speckit/lib/paths.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/paths.js +161 -0
- package/dist/actions/builtin/speckit/lib/paths.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/prereqs.d.ts +8 -0
- package/dist/actions/builtin/speckit/lib/prereqs.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/prereqs.js +194 -0
- package/dist/actions/builtin/speckit/lib/prereqs.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.d.ts +14 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.js +31 -0
- package/dist/actions/builtin/speckit/lib/stream-batcher.js.map +1 -0
- package/dist/actions/builtin/speckit/lib/templates.d.ts +15 -0
- package/dist/actions/builtin/speckit/lib/templates.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/lib/templates.js +176 -0
- package/dist/actions/builtin/speckit/lib/templates.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.js +22 -0
- package/dist/actions/builtin/speckit/operations/check-prereqs.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/clarify.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/clarify.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/clarify.js +302 -0
- package/dist/actions/builtin/speckit/operations/clarify.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/copy-template.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/copy-template.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/copy-template.js +23 -0
- package/dist/actions/builtin/speckit/operations/copy-template.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/create-feature.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/create-feature.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/create-feature.js +25 -0
- package/dist/actions/builtin/speckit/operations/create-feature.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/get-paths.d.ts +11 -0
- package/dist/actions/builtin/speckit/operations/get-paths.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/get-paths.js +17 -0
- package/dist/actions/builtin/speckit/operations/get-paths.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/implement.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/implement.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/implement.js +246 -0
- package/dist/actions/builtin/speckit/operations/implement.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/plan.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/plan.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/plan.js +253 -0
- package/dist/actions/builtin/speckit/operations/plan.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/specify.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/specify.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/specify.js +178 -0
- package/dist/actions/builtin/speckit/operations/specify.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.d.ts +42 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.js +386 -0
- package/dist/actions/builtin/speckit/operations/tasks-to-issues.js.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks.d.ts +7 -0
- package/dist/actions/builtin/speckit/operations/tasks.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/operations/tasks.js +234 -0
- package/dist/actions/builtin/speckit/operations/tasks.js.map +1 -0
- package/dist/actions/builtin/speckit/types.d.ts +268 -0
- package/dist/actions/builtin/speckit/types.d.ts.map +1 -0
- package/dist/actions/builtin/speckit/types.js +11 -0
- package/dist/actions/builtin/speckit/types.js.map +1 -0
- package/dist/actions/builtin/verification-check.d.ts +16 -0
- package/dist/actions/builtin/verification-check.d.ts.map +1 -0
- package/dist/actions/builtin/verification-check.js +99 -0
- package/dist/actions/builtin/verification-check.js.map +1 -0
- package/dist/actions/builtin/workspace-prepare.d.ts +16 -0
- package/dist/actions/builtin/workspace-prepare.d.ts.map +1 -0
- package/dist/actions/builtin/workspace-prepare.js +108 -0
- package/dist/actions/builtin/workspace-prepare.js.map +1 -0
- package/dist/actions/cli-utils.d.ts +76 -0
- package/dist/actions/cli-utils.d.ts.map +1 -0
- package/dist/actions/cli-utils.js +278 -0
- package/dist/actions/cli-utils.js.map +1 -0
- package/dist/actions/epic/check-completion.d.ts +14 -0
- package/dist/actions/epic/check-completion.d.ts.map +1 -0
- package/dist/actions/epic/check-completion.js +57 -0
- package/dist/actions/epic/check-completion.js.map +1 -0
- package/dist/actions/epic/close.d.ts +18 -0
- package/dist/actions/epic/close.d.ts.map +1 -0
- package/dist/actions/epic/close.js +76 -0
- package/dist/actions/epic/close.js.map +1 -0
- package/dist/actions/epic/create-pr.d.ts +22 -0
- package/dist/actions/epic/create-pr.d.ts.map +1 -0
- package/dist/actions/epic/create-pr.js +140 -0
- package/dist/actions/epic/create-pr.js.map +1 -0
- package/dist/actions/epic/dispatch-children.d.ts +16 -0
- package/dist/actions/epic/dispatch-children.d.ts.map +1 -0
- package/dist/actions/epic/dispatch-children.js +95 -0
- package/dist/actions/epic/dispatch-children.js.map +1 -0
- package/dist/actions/epic/find-children.d.ts +16 -0
- package/dist/actions/epic/find-children.d.ts.map +1 -0
- package/dist/actions/epic/find-children.js +92 -0
- package/dist/actions/epic/find-children.js.map +1 -0
- package/dist/actions/epic/index.d.ts +20 -0
- package/dist/actions/epic/index.d.ts.map +1 -0
- package/dist/actions/epic/index.js +35 -0
- package/dist/actions/epic/index.js.map +1 -0
- package/dist/actions/epic/post-tasks-summary.d.ts +18 -0
- package/dist/actions/epic/post-tasks-summary.d.ts.map +1 -0
- package/dist/actions/epic/post-tasks-summary.js +123 -0
- package/dist/actions/epic/post-tasks-summary.js.map +1 -0
- package/dist/actions/epic/update-status.d.ts +22 -0
- package/dist/actions/epic/update-status.d.ts.map +1 -0
- package/dist/actions/epic/update-status.js +105 -0
- package/dist/actions/epic/update-status.js.map +1 -0
- package/dist/actions/github/add-comment.d.ts +14 -0
- package/dist/actions/github/add-comment.d.ts.map +1 -0
- package/dist/actions/github/add-comment.js +44 -0
- package/dist/actions/github/add-comment.js.map +1 -0
- package/dist/actions/github/client/gh-cli.d.ts +68 -0
- package/dist/actions/github/client/gh-cli.d.ts.map +1 -0
- package/dist/actions/github/client/gh-cli.js +781 -0
- package/dist/actions/github/client/gh-cli.js.map +1 -0
- package/dist/actions/github/client/index.d.ts +12 -0
- package/dist/actions/github/client/index.d.ts.map +1 -0
- package/dist/actions/github/client/index.js +10 -0
- package/dist/actions/github/client/index.js.map +1 -0
- package/dist/actions/github/client/interface.d.ts +270 -0
- package/dist/actions/github/client/interface.d.ts.map +1 -0
- package/dist/actions/github/client/interface.js +2 -0
- package/dist/actions/github/client/interface.js.map +1 -0
- package/dist/actions/github/commit-and-push.d.ts +19 -0
- package/dist/actions/github/commit-and-push.d.ts.map +1 -0
- package/dist/actions/github/commit-and-push.js +86 -0
- package/dist/actions/github/commit-and-push.js.map +1 -0
- package/dist/actions/github/create-draft-pr.d.ts +18 -0
- package/dist/actions/github/create-draft-pr.d.ts.map +1 -0
- package/dist/actions/github/create-draft-pr.js +76 -0
- package/dist/actions/github/create-draft-pr.js.map +1 -0
- package/dist/actions/github/get-context.d.ts +23 -0
- package/dist/actions/github/get-context.d.ts.map +1 -0
- package/dist/actions/github/get-context.js +109 -0
- package/dist/actions/github/get-context.js.map +1 -0
- package/dist/actions/github/index.d.ts +32 -0
- package/dist/actions/github/index.d.ts.map +1 -0
- package/dist/actions/github/index.js +67 -0
- package/dist/actions/github/index.js.map +1 -0
- package/dist/actions/github/label-definitions.d.ts +19 -0
- package/dist/actions/github/label-definitions.d.ts.map +1 -0
- package/dist/actions/github/label-definitions.js +71 -0
- package/dist/actions/github/label-definitions.js.map +1 -0
- package/dist/actions/github/mark-pr-ready.d.ts +14 -0
- package/dist/actions/github/mark-pr-ready.d.ts.map +1 -0
- package/dist/actions/github/mark-pr-ready.js +43 -0
- package/dist/actions/github/mark-pr-ready.js.map +1 -0
- package/dist/actions/github/merge-from-base.d.ts +15 -0
- package/dist/actions/github/merge-from-base.d.ts.map +1 -0
- package/dist/actions/github/merge-from-base.js +120 -0
- package/dist/actions/github/merge-from-base.js.map +1 -0
- package/dist/actions/github/preflight.d.ts +36 -0
- package/dist/actions/github/preflight.d.ts.map +1 -0
- package/dist/actions/github/preflight.js +292 -0
- package/dist/actions/github/preflight.js.map +1 -0
- package/dist/actions/github/read-pr-feedback.d.ts +14 -0
- package/dist/actions/github/read-pr-feedback.d.ts.map +1 -0
- package/dist/actions/github/read-pr-feedback.js +53 -0
- package/dist/actions/github/read-pr-feedback.js.map +1 -0
- package/dist/actions/github/respond-pr-feedback.d.ts +14 -0
- package/dist/actions/github/respond-pr-feedback.d.ts.map +1 -0
- package/dist/actions/github/respond-pr-feedback.js +58 -0
- package/dist/actions/github/respond-pr-feedback.js.map +1 -0
- package/dist/actions/github/review-changes.d.ts +19 -0
- package/dist/actions/github/review-changes.d.ts.map +1 -0
- package/dist/actions/github/review-changes.js +95 -0
- package/dist/actions/github/review-changes.js.map +1 -0
- package/dist/actions/github/sync-labels.d.ts +14 -0
- package/dist/actions/github/sync-labels.d.ts.map +1 -0
- package/dist/actions/github/sync-labels.js +81 -0
- package/dist/actions/github/sync-labels.js.map +1 -0
- package/dist/actions/github/update-pr.d.ts +14 -0
- package/dist/actions/github/update-pr.d.ts.map +1 -0
- package/dist/actions/github/update-pr.js +63 -0
- package/dist/actions/github/update-pr.js.map +1 -0
- package/dist/actions/index.d.ts +109 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +233 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/workflow/check-gate.d.ts +15 -0
- package/dist/actions/workflow/check-gate.d.ts.map +1 -0
- package/dist/actions/workflow/check-gate.js +75 -0
- package/dist/actions/workflow/check-gate.js.map +1 -0
- package/dist/actions/workflow/index.d.ts +21 -0
- package/dist/actions/workflow/index.d.ts.map +1 -0
- package/dist/actions/workflow/index.js +31 -0
- package/dist/actions/workflow/index.js.map +1 -0
- package/dist/actions/workflow/update-phase.d.ts +15 -0
- package/dist/actions/workflow/update-phase.d.ts.map +1 -0
- package/dist/actions/workflow/update-phase.js +143 -0
- package/dist/actions/workflow/update-phase.js.map +1 -0
- package/dist/actions/workflow/update-stage.d.ts +19 -0
- package/dist/actions/workflow/update-stage.d.ts.map +1 -0
- package/dist/actions/workflow/update-stage.js +137 -0
- package/dist/actions/workflow/update-stage.js.map +1 -0
- package/dist/errors/base-workflow-not-found.d.ts +10 -0
- package/dist/errors/base-workflow-not-found.d.ts.map +1 -0
- package/dist/errors/base-workflow-not-found.js +18 -0
- package/dist/errors/base-workflow-not-found.js.map +1 -0
- package/dist/errors/circular-extends.d.ts +9 -0
- package/dist/errors/circular-extends.d.ts.map +1 -0
- package/dist/errors/circular-extends.js +13 -0
- package/dist/errors/circular-extends.js.map +1 -0
- package/dist/errors/correlation-timeout.d.ts +9 -0
- package/dist/errors/correlation-timeout.d.ts.map +1 -0
- package/dist/errors/correlation-timeout.js +13 -0
- package/dist/errors/correlation-timeout.js.map +1 -0
- package/dist/errors/workflow-override.d.ts +9 -0
- package/dist/errors/workflow-override.d.ts.map +1 -0
- package/dist/errors/workflow-override.js +12 -0
- package/dist/errors/workflow-override.js.map +1 -0
- package/dist/executor/events.d.ts +55 -0
- package/dist/executor/events.d.ts.map +1 -0
- package/dist/executor/events.js +72 -0
- package/dist/executor/events.js.map +1 -0
- package/dist/executor/gate-handler.d.ts +55 -0
- package/dist/executor/gate-handler.d.ts.map +1 -0
- package/dist/executor/gate-handler.js +139 -0
- package/dist/executor/gate-handler.js.map +1 -0
- package/dist/executor/index.d.ts +140 -0
- package/dist/executor/index.d.ts.map +1 -0
- package/dist/executor/index.js +681 -0
- package/dist/executor/index.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolation/context.d.ts +117 -0
- package/dist/interpolation/context.d.ts.map +1 -0
- package/dist/interpolation/context.js +256 -0
- package/dist/interpolation/context.js.map +1 -0
- package/dist/interpolation/index.d.ts +76 -0
- package/dist/interpolation/index.d.ts.map +1 -0
- package/dist/interpolation/index.js +264 -0
- package/dist/interpolation/index.js.map +1 -0
- package/dist/loader/extends.d.ts +48 -0
- package/dist/loader/extends.d.ts.map +1 -0
- package/dist/loader/extends.js +140 -0
- package/dist/loader/extends.js.map +1 -0
- package/dist/loader/index.d.ts +50 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/index.js +190 -0
- package/dist/loader/index.js.map +1 -0
- package/dist/loader/schema.d.ts +506 -0
- package/dist/loader/schema.d.ts.map +1 -0
- package/dist/loader/schema.js +63 -0
- package/dist/loader/schema.js.map +1 -0
- package/dist/loader/validator.d.ts +33 -0
- package/dist/loader/validator.d.ts.map +1 -0
- package/dist/loader/validator.js +135 -0
- package/dist/loader/validator.js.map +1 -0
- package/dist/registry/index.d.ts +42 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +77 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/retry/index.d.ts +37 -0
- package/dist/retry/index.d.ts.map +1 -0
- package/dist/retry/index.js +225 -0
- package/dist/retry/index.js.map +1 -0
- package/dist/retry/strategies.d.ts +74 -0
- package/dist/retry/strategies.d.ts.map +1 -0
- package/dist/retry/strategies.js +136 -0
- package/dist/retry/strategies.js.map +1 -0
- package/dist/store/filesystem-store.d.ts +52 -0
- package/dist/store/filesystem-store.d.ts.map +1 -0
- package/dist/store/filesystem-store.js +230 -0
- package/dist/store/filesystem-store.js.map +1 -0
- package/dist/store/index.d.ts +7 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +6 -0
- package/dist/store/index.js.map +1 -0
- package/dist/types/action.d.ts +358 -0
- package/dist/types/action.d.ts.map +1 -0
- package/dist/types/action.js +96 -0
- package/dist/types/action.js.map +1 -0
- package/dist/types/events.d.ts +25 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/execution.d.ts +145 -0
- package/dist/types/execution.d.ts.map +1 -0
- package/dist/types/execution.js +2 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/gate.d.ts +98 -0
- package/dist/types/gate.d.ts.map +1 -0
- package/dist/types/gate.js +41 -0
- package/dist/types/gate.js.map +1 -0
- package/dist/types/github.d.ts +706 -0
- package/dist/types/github.d.ts.map +1 -0
- package/dist/types/github.js +6 -0
- package/dist/types/github.js.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/logger.d.ts +60 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/logger.js +66 -0
- package/dist/types/logger.js.map +1 -0
- package/dist/types/retry.d.ts +36 -0
- package/dist/types/retry.d.ts.map +1 -0
- package/dist/types/retry.js +6 -0
- package/dist/types/retry.js.map +1 -0
- package/dist/types/store.d.ts +88 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/store.js +6 -0
- package/dist/types/store.js.map +1 -0
- package/dist/types/workflow.d.ts +105 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +6 -0
- package/dist/types/workflow.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
import { createLogger } from '../types/logger.js';
|
|
2
|
+
import { getActionHandler, registerBuiltinActions, } from '../actions/index.js';
|
|
3
|
+
import { ExecutionContext, interpolate, interpolateValue } from '../interpolation/index.js';
|
|
4
|
+
import { RetryManager, withTimeout } from '../retry/index.js';
|
|
5
|
+
import { ExecutionEventEmitter } from './events.js';
|
|
6
|
+
import { FilesystemWorkflowStore } from '../store/filesystem-store.js';
|
|
7
|
+
import { simpleGit } from 'simple-git';
|
|
8
|
+
import { getDefaultBranch } from '../actions/builtin/speckit/lib/feature.js';
|
|
9
|
+
// Ensure built-in actions are registered
|
|
10
|
+
let actionsRegistered = false;
|
|
11
|
+
function ensureActionsRegistered() {
|
|
12
|
+
if (!actionsRegistered) {
|
|
13
|
+
registerBuiltinActions();
|
|
14
|
+
actionsRegistered = true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reset the actions registration state (for testing)
|
|
19
|
+
*/
|
|
20
|
+
export function resetActionsRegistration() {
|
|
21
|
+
actionsRegistered = false;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Workflow executor class
|
|
25
|
+
*/
|
|
26
|
+
export class WorkflowExecutor {
|
|
27
|
+
currentExecution;
|
|
28
|
+
eventEmitter = new ExecutionEventEmitter();
|
|
29
|
+
executionContext;
|
|
30
|
+
abortController;
|
|
31
|
+
logger;
|
|
32
|
+
store;
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
ensureActionsRegistered();
|
|
35
|
+
this.logger = options.logger ?? createLogger('WorkflowExecutor');
|
|
36
|
+
this.store = options.store ?? new FilesystemWorkflowStore();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get current execution status
|
|
40
|
+
*/
|
|
41
|
+
getStatus() {
|
|
42
|
+
return this.currentExecution?.status ?? 'idle';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if currently executing
|
|
46
|
+
*/
|
|
47
|
+
isRunning() {
|
|
48
|
+
return this.currentExecution?.status === 'running';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get current execution result
|
|
52
|
+
*/
|
|
53
|
+
getCurrentExecution() {
|
|
54
|
+
return this.currentExecution;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the execution context (for accessing step outputs)
|
|
58
|
+
*/
|
|
59
|
+
getExecutionContext() {
|
|
60
|
+
return this.executionContext;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Add event listener
|
|
64
|
+
*/
|
|
65
|
+
addEventListener(listener) {
|
|
66
|
+
return this.eventEmitter.addEventListener(listener);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute a workflow
|
|
70
|
+
*/
|
|
71
|
+
async execute(workflow, options = { mode: 'normal' }, inputs) {
|
|
72
|
+
// Check if already running
|
|
73
|
+
if (this.isRunning()) {
|
|
74
|
+
throw new Error('A workflow is already running. Cancel it first.');
|
|
75
|
+
}
|
|
76
|
+
// Initialize execution context for variable interpolation
|
|
77
|
+
this.executionContext = new ExecutionContext(inputs, { ...workflow.env, ...options.env });
|
|
78
|
+
// Initialize execution result
|
|
79
|
+
this.currentExecution = {
|
|
80
|
+
workflowName: workflow.name,
|
|
81
|
+
status: 'running',
|
|
82
|
+
mode: options.mode,
|
|
83
|
+
startTime: Date.now(),
|
|
84
|
+
phaseResults: [],
|
|
85
|
+
env: { ...workflow.env, ...options.env },
|
|
86
|
+
};
|
|
87
|
+
// Create abort controller
|
|
88
|
+
this.abortController = new AbortController();
|
|
89
|
+
// Emit start event
|
|
90
|
+
this.eventEmitter.emitEvent('execution:start', workflow.name, {
|
|
91
|
+
message: `Starting workflow: ${workflow.name}`,
|
|
92
|
+
});
|
|
93
|
+
// Log start
|
|
94
|
+
this.logger.info(`Starting workflow: ${workflow.name} (mode: ${options.mode})`);
|
|
95
|
+
if (options.mode === 'dry-run') {
|
|
96
|
+
this.logger.info('DRY-RUN MODE: No actions will be executed');
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
// Find starting phase
|
|
100
|
+
let startPhaseIndex = 0;
|
|
101
|
+
if (options.startPhase) {
|
|
102
|
+
const index = workflow.phases.findIndex(p => p.name === options.startPhase);
|
|
103
|
+
if (index >= 0) {
|
|
104
|
+
startPhaseIndex = index;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Execute phases
|
|
108
|
+
for (let i = startPhaseIndex; i < workflow.phases.length; i++) {
|
|
109
|
+
if (this.abortController.signal.aborted) {
|
|
110
|
+
this.currentExecution.status = 'cancelled';
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
const phase = workflow.phases[i];
|
|
114
|
+
if (!phase) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const phaseResult = await this.executePhase(workflow, phase, i, workflow.phases.length, options, i === startPhaseIndex ? options.startStep : undefined);
|
|
118
|
+
this.currentExecution.phaseResults.push(phaseResult);
|
|
119
|
+
// After setup phase, validate we're on a feature branch (not default)
|
|
120
|
+
if (phase.name === 'setup' && phaseResult.status === 'completed' && options.cwd) {
|
|
121
|
+
await this.validateBranchState(options.cwd);
|
|
122
|
+
}
|
|
123
|
+
// Stop if phase failed and not continuing on error
|
|
124
|
+
if (phaseResult.status === 'failed') {
|
|
125
|
+
this.currentExecution.status = 'failed';
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Set final status if not already set
|
|
130
|
+
if (this.currentExecution.status === 'running') {
|
|
131
|
+
this.currentExecution.status = 'completed';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
this.currentExecution.status = 'failed';
|
|
136
|
+
this.eventEmitter.emitEvent('execution:error', workflow.name, {
|
|
137
|
+
message: error instanceof Error ? error.message : String(error),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// Finalize execution
|
|
141
|
+
this.currentExecution.endTime = Date.now();
|
|
142
|
+
this.currentExecution.duration = this.currentExecution.endTime - this.currentExecution.startTime;
|
|
143
|
+
// Log completion
|
|
144
|
+
this.logger.info(`Workflow ${this.currentExecution.status}: ${workflow.name} (${this.currentExecution.duration}ms)`);
|
|
145
|
+
// Emit completion event
|
|
146
|
+
this.eventEmitter.emitEvent(this.currentExecution.status === 'completed' ? 'execution:complete' : 'execution:error', workflow.name, {
|
|
147
|
+
message: `Workflow ${this.currentExecution.status}: ${workflow.name}`,
|
|
148
|
+
data: this.currentExecution,
|
|
149
|
+
});
|
|
150
|
+
// Cleanup
|
|
151
|
+
this.abortController = undefined;
|
|
152
|
+
return this.currentExecution;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Execute a single phase
|
|
156
|
+
*/
|
|
157
|
+
async executePhase(workflow, phase, phaseIndex, totalPhases, options, startStep) {
|
|
158
|
+
const result = {
|
|
159
|
+
phaseName: phase.name,
|
|
160
|
+
status: 'running',
|
|
161
|
+
startTime: Date.now(),
|
|
162
|
+
stepResults: [],
|
|
163
|
+
};
|
|
164
|
+
// Log phase start
|
|
165
|
+
this.logger.info(`Phase [${phaseIndex + 1}/${totalPhases}]: ${phase.name}`);
|
|
166
|
+
// Emit phase start event
|
|
167
|
+
this.eventEmitter.emitEvent('phase:start', workflow.name, {
|
|
168
|
+
phaseName: phase.name,
|
|
169
|
+
message: `Starting phase: ${phase.name}`,
|
|
170
|
+
});
|
|
171
|
+
// Check phase condition
|
|
172
|
+
if (phase.condition && options.mode !== 'dry-run') {
|
|
173
|
+
const shouldRun = await this.evaluateCondition(phase.condition, options);
|
|
174
|
+
if (!shouldRun) {
|
|
175
|
+
result.status = 'skipped';
|
|
176
|
+
result.endTime = Date.now();
|
|
177
|
+
result.duration = 0;
|
|
178
|
+
this.logger.info(`Phase "${phase.name}" skipped: condition not met`);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
// Find starting step
|
|
184
|
+
let startStepIndex = 0;
|
|
185
|
+
if (startStep) {
|
|
186
|
+
const index = phase.steps.findIndex(s => s.name === startStep);
|
|
187
|
+
if (index >= 0) {
|
|
188
|
+
startStepIndex = index;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Execute steps
|
|
192
|
+
for (let i = startStepIndex; i < phase.steps.length; i++) {
|
|
193
|
+
if (this.abortController?.signal.aborted) {
|
|
194
|
+
result.status = 'failed';
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
const step = phase.steps[i];
|
|
198
|
+
if (!step) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const stepResult = await this.executeStep(workflow, phase, step, i, phase.steps.length, options);
|
|
202
|
+
result.stepResults.push(stepResult);
|
|
203
|
+
// Stop if step failed and not continuing on error
|
|
204
|
+
if (stepResult.status === 'failed' && !step.continueOnError) {
|
|
205
|
+
result.status = 'failed';
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Set final status if not already set
|
|
210
|
+
if (result.status === 'running') {
|
|
211
|
+
result.status = 'completed';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
result.status = 'failed';
|
|
216
|
+
this.eventEmitter.emitEvent('phase:error', workflow.name, {
|
|
217
|
+
phaseName: phase.name,
|
|
218
|
+
message: error instanceof Error ? error.message : String(error),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Finalize phase
|
|
222
|
+
result.endTime = Date.now();
|
|
223
|
+
result.duration = result.endTime - result.startTime;
|
|
224
|
+
// Log phase completion
|
|
225
|
+
this.logger.info(`Phase ${result.status}: ${phase.name} (${result.duration}ms)`);
|
|
226
|
+
// Emit phase completion event
|
|
227
|
+
this.eventEmitter.emitEvent(result.status === 'completed' ? 'phase:complete' : 'phase:error', workflow.name, {
|
|
228
|
+
phaseName: phase.name,
|
|
229
|
+
message: `Phase ${result.status}: ${phase.name}`,
|
|
230
|
+
data: result,
|
|
231
|
+
});
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Execute a single step using action handlers
|
|
236
|
+
*/
|
|
237
|
+
async executeStep(workflow, phase, step, stepIndex, totalSteps, options) {
|
|
238
|
+
const result = {
|
|
239
|
+
stepName: step.name,
|
|
240
|
+
phaseName: phase.name,
|
|
241
|
+
status: 'running',
|
|
242
|
+
startTime: Date.now(),
|
|
243
|
+
};
|
|
244
|
+
// Log step start
|
|
245
|
+
this.logger.info(` Step [${stepIndex + 1}/${totalSteps}]: ${step.name}`);
|
|
246
|
+
// Emit step start event
|
|
247
|
+
this.eventEmitter.emitEvent('step:start', workflow.name, {
|
|
248
|
+
phaseName: phase.name,
|
|
249
|
+
stepName: step.name,
|
|
250
|
+
message: `Starting step: ${step.name}`,
|
|
251
|
+
});
|
|
252
|
+
// Check step condition
|
|
253
|
+
if (step.condition && options.mode !== 'dry-run') {
|
|
254
|
+
const shouldRun = await this.evaluateCondition(step.condition, options);
|
|
255
|
+
if (!shouldRun) {
|
|
256
|
+
result.status = 'skipped';
|
|
257
|
+
result.endTime = Date.now();
|
|
258
|
+
result.duration = 0;
|
|
259
|
+
this.logger.info(` Step "${step.name}" skipped: condition not met`);
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// For dry-run mode, don't actually execute
|
|
264
|
+
if (options.mode === 'dry-run') {
|
|
265
|
+
result.status = 'completed';
|
|
266
|
+
result.output = `[DRY-RUN] Would execute step: ${step.name}`;
|
|
267
|
+
result.endTime = Date.now();
|
|
268
|
+
result.duration = result.endTime - result.startTime;
|
|
269
|
+
this.logger.info(` [DRY-RUN] Would execute: ${step.name}`);
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
// Interpolate step configuration
|
|
274
|
+
const interpolatedStep = this.interpolateStep(step);
|
|
275
|
+
// Get action handler for this step
|
|
276
|
+
const handler = getActionHandler(interpolatedStep);
|
|
277
|
+
if (handler) {
|
|
278
|
+
// Execute using action handler
|
|
279
|
+
const actionResult = await this.executeWithActionHandler(workflow, phase, interpolatedStep, handler, options);
|
|
280
|
+
result.output = typeof actionResult.output === 'string'
|
|
281
|
+
? actionResult.output
|
|
282
|
+
: JSON.stringify(actionResult.output);
|
|
283
|
+
result.exitCode = actionResult.exitCode;
|
|
284
|
+
result.error = actionResult.error;
|
|
285
|
+
result.status = actionResult.success ? 'completed' : 'failed';
|
|
286
|
+
// Store step output for interpolation
|
|
287
|
+
this.storeStepOutput(step.name, actionResult);
|
|
288
|
+
// Emit output if available
|
|
289
|
+
if (actionResult.stdout) {
|
|
290
|
+
this.eventEmitter.emitEvent('step:output', workflow.name, {
|
|
291
|
+
phaseName: phase.name,
|
|
292
|
+
stepName: step.name,
|
|
293
|
+
message: actionResult.stdout,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// No handler found - fail
|
|
299
|
+
result.status = 'failed';
|
|
300
|
+
result.error = `No action handler found for step "${step.name}"`;
|
|
301
|
+
this.logger.error(` No action handler found for step: ${step.name}`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
result.status = 'failed';
|
|
306
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
307
|
+
}
|
|
308
|
+
// Finalize step
|
|
309
|
+
result.endTime = Date.now();
|
|
310
|
+
result.duration = result.endTime - result.startTime;
|
|
311
|
+
// Log step completion
|
|
312
|
+
this.logger.info(` Step ${result.status}: ${step.name} (${result.duration}ms)`);
|
|
313
|
+
// Emit step completion event
|
|
314
|
+
this.eventEmitter.emitEvent(result.status === 'completed' ? 'step:complete' : 'step:error', workflow.name, {
|
|
315
|
+
phaseName: phase.name,
|
|
316
|
+
stepName: step.name,
|
|
317
|
+
message: `Step ${result.status}: ${step.name}`,
|
|
318
|
+
data: result,
|
|
319
|
+
});
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Execute step using action handler with retry logic
|
|
324
|
+
*/
|
|
325
|
+
async executeWithActionHandler(workflow, phase, step, handler, options) {
|
|
326
|
+
if (!handler) {
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
output: null,
|
|
330
|
+
error: 'No action handler found',
|
|
331
|
+
duration: 0,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// Create a step-level AbortController so step timeouts can cancel the operation.
|
|
335
|
+
// Links to the parent (workflow-level) abort controller.
|
|
336
|
+
const stepAbort = new AbortController();
|
|
337
|
+
const parentSignal = this.abortController?.signal;
|
|
338
|
+
if (parentSignal?.aborted) {
|
|
339
|
+
stepAbort.abort();
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
parentSignal?.addEventListener('abort', () => stepAbort.abort(), { once: true });
|
|
343
|
+
}
|
|
344
|
+
// Create action context with step-level signal
|
|
345
|
+
const context = this.createActionContext(workflow, phase, step, options);
|
|
346
|
+
context.signal = stepAbort.signal;
|
|
347
|
+
// Emit action start event
|
|
348
|
+
this.eventEmitter.emitEvent('action:start', workflow.name, {
|
|
349
|
+
phaseName: phase.name,
|
|
350
|
+
stepName: step.name,
|
|
351
|
+
message: `Starting action [${handler.type}]`,
|
|
352
|
+
});
|
|
353
|
+
// Create retry manager with event callback
|
|
354
|
+
const retryManager = RetryManager.fromStep(step, (state, error) => {
|
|
355
|
+
this.eventEmitter.emitEvent('action:retry', workflow.name, {
|
|
356
|
+
phaseName: phase.name,
|
|
357
|
+
stepName: step.name,
|
|
358
|
+
message: `Retrying action (attempt ${state.attempt + 1}): ${error.message}`,
|
|
359
|
+
data: state,
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
// Default timeout: 5 minutes (300000ms)
|
|
363
|
+
const DEFAULT_STEP_TIMEOUT = 300000;
|
|
364
|
+
const stepTimeout = step.timeout ?? DEFAULT_STEP_TIMEOUT;
|
|
365
|
+
// Execute with retry and timeout wrapper.
|
|
366
|
+
// On timeout, abort the step so spawned processes are killed.
|
|
367
|
+
const retryResult = await withTimeout(retryManager.executeWithRetry(handler, step, context), stepTimeout, stepAbort.signal, () => stepAbort.abort());
|
|
368
|
+
// Emit action completion event
|
|
369
|
+
this.eventEmitter.emitEvent(retryResult.result.success ? 'action:complete' : 'action:error', workflow.name, {
|
|
370
|
+
phaseName: phase.name,
|
|
371
|
+
stepName: step.name,
|
|
372
|
+
message: retryResult.result.success
|
|
373
|
+
? `Action [${handler.type}] completed`
|
|
374
|
+
: `Action [${handler.type}] failed: ${retryResult.result.error}`,
|
|
375
|
+
data: {
|
|
376
|
+
attempts: retryResult.attempts,
|
|
377
|
+
totalDuration: retryResult.totalDuration,
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
return retryResult.result;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Create action context for step execution
|
|
384
|
+
*/
|
|
385
|
+
createActionContext(workflow, phase, step, options) {
|
|
386
|
+
// Create logger for actions
|
|
387
|
+
const actionLogger = {
|
|
388
|
+
info: (msg) => this.logger.info(` ${msg}`),
|
|
389
|
+
warn: (msg) => this.logger.warn(` ${msg}`),
|
|
390
|
+
error: (msg) => this.logger.error(` ${msg}`),
|
|
391
|
+
debug: (msg) => this.logger.debug(` ${msg}`),
|
|
392
|
+
};
|
|
393
|
+
return {
|
|
394
|
+
workflow,
|
|
395
|
+
phase,
|
|
396
|
+
step,
|
|
397
|
+
inputs: this.executionContext?.getInputs() ?? {},
|
|
398
|
+
stepOutputs: this.executionContext?.getAllStepOutputs() ?? new Map(),
|
|
399
|
+
env: { ...workflow.env, ...options.env, ...step.env },
|
|
400
|
+
workdir: options.cwd ?? process.cwd(),
|
|
401
|
+
signal: this.abortController?.signal ?? new AbortController().signal,
|
|
402
|
+
logger: actionLogger,
|
|
403
|
+
emitEvent: (event) => {
|
|
404
|
+
this.eventEmitter.emitEvent(event.type, workflow.name, {
|
|
405
|
+
phaseName: phase.name,
|
|
406
|
+
stepName: step.name,
|
|
407
|
+
data: event.data,
|
|
408
|
+
});
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Interpolate variables in step configuration
|
|
414
|
+
*/
|
|
415
|
+
interpolateStep(step) {
|
|
416
|
+
if (!this.executionContext) {
|
|
417
|
+
return step;
|
|
418
|
+
}
|
|
419
|
+
const context = this.executionContext.getInterpolationContext();
|
|
420
|
+
// Deep clone and interpolate
|
|
421
|
+
const interpolated = { ...step };
|
|
422
|
+
if (interpolated.command) {
|
|
423
|
+
interpolated.command = interpolate(interpolated.command, context);
|
|
424
|
+
}
|
|
425
|
+
if (interpolated.script) {
|
|
426
|
+
interpolated.script = interpolate(interpolated.script, context);
|
|
427
|
+
}
|
|
428
|
+
if (interpolated.with) {
|
|
429
|
+
interpolated.with = interpolateValue(interpolated.with, context);
|
|
430
|
+
}
|
|
431
|
+
if (interpolated.env) {
|
|
432
|
+
interpolated.env = interpolateValue(interpolated.env, context);
|
|
433
|
+
}
|
|
434
|
+
return interpolated;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Store step output in execution context
|
|
438
|
+
*/
|
|
439
|
+
storeStepOutput(stepId, result) {
|
|
440
|
+
if (!this.executionContext) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const raw = result.stdout ?? (typeof result.output === 'string' ? result.output : JSON.stringify(result.output));
|
|
444
|
+
// Try to parse output as JSON
|
|
445
|
+
let parsed = null;
|
|
446
|
+
if (typeof result.output === 'object') {
|
|
447
|
+
parsed = result.output;
|
|
448
|
+
}
|
|
449
|
+
else if (typeof result.output === 'string') {
|
|
450
|
+
try {
|
|
451
|
+
parsed = JSON.parse(result.output);
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
parsed = null;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const stepOutput = {
|
|
458
|
+
raw,
|
|
459
|
+
parsed,
|
|
460
|
+
exitCode: result.exitCode ?? (result.success ? 0 : 1),
|
|
461
|
+
completedAt: new Date(),
|
|
462
|
+
};
|
|
463
|
+
this.executionContext.setStepOutput(stepId, stepOutput);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Cancel the current execution
|
|
467
|
+
*/
|
|
468
|
+
cancel() {
|
|
469
|
+
if (this.isRunning()) {
|
|
470
|
+
this.abortController?.abort();
|
|
471
|
+
if (this.currentExecution) {
|
|
472
|
+
this.currentExecution.status = 'cancelled';
|
|
473
|
+
}
|
|
474
|
+
this.eventEmitter.emitEvent('execution:cancel', this.currentExecution?.workflowName ?? 'unknown', {
|
|
475
|
+
message: 'Execution cancelled by user',
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Validate a workflow without executing (dry-run)
|
|
481
|
+
*/
|
|
482
|
+
async validate(workflow, env) {
|
|
483
|
+
return this.execute(workflow, { mode: 'dry-run', env });
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Check if there's a pending workflow state that can be resumed
|
|
487
|
+
*/
|
|
488
|
+
async hasPendingState(workflowId) {
|
|
489
|
+
const state = await this.store.load(workflowId);
|
|
490
|
+
return state !== null && state.pendingReview !== undefined;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Get all pending workflow states
|
|
494
|
+
*/
|
|
495
|
+
async listPendingWorkflows() {
|
|
496
|
+
return this.store.listPending();
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Load a saved workflow state
|
|
500
|
+
*/
|
|
501
|
+
async loadWorkflowState(workflowId) {
|
|
502
|
+
return this.store.load(workflowId);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Resume a paused workflow from saved state.
|
|
506
|
+
* Call this after receiving a human review decision.
|
|
507
|
+
*
|
|
508
|
+
* @param workflow The workflow definition (must match the saved state)
|
|
509
|
+
* @param workflowId The workflow ID to resume
|
|
510
|
+
* @param resumeOptions Options containing the human decision
|
|
511
|
+
* @param executionOptions Standard execution options
|
|
512
|
+
* @returns The execution result
|
|
513
|
+
*/
|
|
514
|
+
async resume(workflow, workflowId, resumeOptions, executionOptions = { mode: 'normal' }) {
|
|
515
|
+
// Load saved state
|
|
516
|
+
const savedState = await this.store.load(workflowId);
|
|
517
|
+
if (!savedState) {
|
|
518
|
+
throw new Error(`No saved workflow state found for ID: ${workflowId}`);
|
|
519
|
+
}
|
|
520
|
+
if (!savedState.pendingReview) {
|
|
521
|
+
throw new Error(`Workflow ${workflowId} does not have a pending review`);
|
|
522
|
+
}
|
|
523
|
+
this.logger.info(`Resuming workflow ${workflowId} from phase: ${savedState.currentPhase}, step: ${savedState.currentStep}`);
|
|
524
|
+
// Initialize execution context with saved state
|
|
525
|
+
this.executionContext = new ExecutionContext(savedState.inputs, { ...workflow.env, ...executionOptions.env });
|
|
526
|
+
// Restore step outputs from saved state
|
|
527
|
+
for (const [stepId, outputData] of Object.entries(savedState.stepOutputs)) {
|
|
528
|
+
const stepOutput = {
|
|
529
|
+
raw: outputData.raw,
|
|
530
|
+
parsed: outputData.parsed,
|
|
531
|
+
exitCode: outputData.exitCode,
|
|
532
|
+
completedAt: new Date(outputData.completedAt),
|
|
533
|
+
};
|
|
534
|
+
this.executionContext.setStepOutput(stepId, stepOutput);
|
|
535
|
+
}
|
|
536
|
+
// If we have a decision, inject it into the context for the review step
|
|
537
|
+
if (resumeOptions.decision) {
|
|
538
|
+
const reviewOutput = {
|
|
539
|
+
raw: JSON.stringify(resumeOptions.decision),
|
|
540
|
+
parsed: {
|
|
541
|
+
approved: resumeOptions.decision.approved,
|
|
542
|
+
comments: resumeOptions.decision.comments,
|
|
543
|
+
respondedBy: resumeOptions.decision.respondedBy,
|
|
544
|
+
respondedAt: resumeOptions.decision.respondedAt,
|
|
545
|
+
reviewId: savedState.pendingReview.reviewId,
|
|
546
|
+
},
|
|
547
|
+
exitCode: 0,
|
|
548
|
+
completedAt: new Date(),
|
|
549
|
+
};
|
|
550
|
+
this.executionContext.setStepOutput(savedState.currentStep, reviewOutput);
|
|
551
|
+
}
|
|
552
|
+
// Clear the pending state
|
|
553
|
+
await this.store.delete(workflowId);
|
|
554
|
+
// Find the next step after the review step
|
|
555
|
+
let nextPhaseIndex = -1;
|
|
556
|
+
let nextStepIndex = -1;
|
|
557
|
+
let foundCurrentStep = false;
|
|
558
|
+
for (let pi = 0; pi < workflow.phases.length; pi++) {
|
|
559
|
+
const phase = workflow.phases[pi];
|
|
560
|
+
if (!phase)
|
|
561
|
+
continue;
|
|
562
|
+
const isCurrentPhase = phase.name === savedState.currentPhase;
|
|
563
|
+
for (let si = 0; si < phase.steps.length; si++) {
|
|
564
|
+
const step = phase.steps[si];
|
|
565
|
+
if (!step)
|
|
566
|
+
continue;
|
|
567
|
+
const isCurrentStep = step.name === savedState.currentStep;
|
|
568
|
+
if (isCurrentPhase && isCurrentStep) {
|
|
569
|
+
foundCurrentStep = true;
|
|
570
|
+
// The next step is after this one
|
|
571
|
+
if (si + 1 < phase.steps.length) {
|
|
572
|
+
nextPhaseIndex = pi;
|
|
573
|
+
nextStepIndex = si + 1;
|
|
574
|
+
}
|
|
575
|
+
else if (pi + 1 < workflow.phases.length) {
|
|
576
|
+
nextPhaseIndex = pi + 1;
|
|
577
|
+
nextStepIndex = 0;
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (foundCurrentStep)
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
if (!foundCurrentStep) {
|
|
586
|
+
throw new Error(`Could not find current step ${savedState.currentStep} in workflow`);
|
|
587
|
+
}
|
|
588
|
+
// If there's no next step, workflow is complete
|
|
589
|
+
if (nextPhaseIndex < 0) {
|
|
590
|
+
this.logger.info('Workflow completed (no more steps after review)');
|
|
591
|
+
return {
|
|
592
|
+
workflowName: workflow.name,
|
|
593
|
+
status: 'completed',
|
|
594
|
+
mode: executionOptions.mode,
|
|
595
|
+
startTime: Date.now(),
|
|
596
|
+
endTime: Date.now(),
|
|
597
|
+
duration: 0,
|
|
598
|
+
phaseResults: [],
|
|
599
|
+
env: { ...workflow.env, ...executionOptions.env },
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
// Resume execution from the next step
|
|
603
|
+
const nextPhase = workflow.phases[nextPhaseIndex];
|
|
604
|
+
const nextStep = nextPhase?.steps[nextStepIndex];
|
|
605
|
+
this.logger.info(`Continuing from phase: ${nextPhase?.name}, step: ${nextStep?.name}`);
|
|
606
|
+
// Execute from the resume point
|
|
607
|
+
return this.execute(workflow, {
|
|
608
|
+
...executionOptions,
|
|
609
|
+
startPhase: nextPhase?.name,
|
|
610
|
+
startStep: nextStep?.name,
|
|
611
|
+
}, savedState.inputs);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Dispose resources
|
|
615
|
+
*/
|
|
616
|
+
dispose() {
|
|
617
|
+
this.cancel();
|
|
618
|
+
this.eventEmitter.removeAllListeners();
|
|
619
|
+
this.currentExecution = undefined;
|
|
620
|
+
this.executionContext = undefined;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Validate that the working directory is not on the default branch.
|
|
624
|
+
* Called after the setup phase to catch failed branch creation early.
|
|
625
|
+
*/
|
|
626
|
+
async validateBranchState(cwd) {
|
|
627
|
+
const git = simpleGit(cwd);
|
|
628
|
+
const currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim();
|
|
629
|
+
const defaultBranch = await getDefaultBranch(git);
|
|
630
|
+
if (currentBranch === defaultBranch) {
|
|
631
|
+
throw new Error(`Branch validation failed: still on default branch "${currentBranch}" after setup phase. ` +
|
|
632
|
+
`The create-feature step likely failed to create/checkout the feature branch.`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Evaluate a condition expression using interpolation context
|
|
637
|
+
*/
|
|
638
|
+
async evaluateCondition(condition, options) {
|
|
639
|
+
try {
|
|
640
|
+
let evaluated = condition;
|
|
641
|
+
// Use interpolation context if available
|
|
642
|
+
if (this.executionContext) {
|
|
643
|
+
const context = this.executionContext.getInterpolationContext();
|
|
644
|
+
evaluated = interpolate(condition, context);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
// Fall back to simple environment variable replacement
|
|
648
|
+
for (const [key, value] of Object.entries(options.env ?? {})) {
|
|
649
|
+
evaluated = evaluated.replace(new RegExp(`\\$\\{?${key}\\}?`, 'g'), value);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Handle function calls
|
|
653
|
+
if (evaluated.includes('success()')) {
|
|
654
|
+
const result = this.executionContext?.getInterpolationContext().functions.success() ?? true;
|
|
655
|
+
evaluated = evaluated.replace(/success\(\)/g, String(result));
|
|
656
|
+
}
|
|
657
|
+
if (evaluated.includes('failure()')) {
|
|
658
|
+
const result = this.executionContext?.getInterpolationContext().functions.failure() ?? false;
|
|
659
|
+
evaluated = evaluated.replace(/failure\(\)/g, String(result));
|
|
660
|
+
}
|
|
661
|
+
if (evaluated.includes('always()')) {
|
|
662
|
+
evaluated = evaluated.replace(/always\(\)/g, 'true');
|
|
663
|
+
}
|
|
664
|
+
// Simple truthy evaluation
|
|
665
|
+
if (evaluated === 'true' || evaluated === '1') {
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
if (evaluated === 'false' || evaluated === '0' || evaluated === '') {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
// Default to true for non-empty strings
|
|
672
|
+
return evaluated.trim().length > 0;
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return true; // Default to running on evaluation error
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// Re-export events
|
|
680
|
+
export { ExecutionEventEmitter, createExecutionEvent } from './events.js';
|
|
681
|
+
//# sourceMappingURL=index.js.map
|