agentweaver 0.1.15 → 0.1.17
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 +76 -19
- package/dist/artifact-manifest.js +219 -0
- package/dist/artifacts.js +88 -3
- package/dist/doctor/checks/env-diagnostics.js +25 -0
- package/dist/doctor/checks/executors.js +2 -2
- package/dist/doctor/checks/flow-readiness.js +15 -18
- package/dist/flow-state.js +212 -15
- package/dist/index.js +539 -209
- package/dist/interactive/blessed-session.js +361 -0
- package/dist/interactive/controller.js +1326 -0
- package/dist/interactive/create-interactive-session.js +5 -0
- package/dist/interactive/ink/index.js +597 -0
- package/dist/interactive/progress.js +245 -0
- package/dist/interactive/selectors.js +14 -0
- package/dist/interactive/session.js +1 -0
- package/dist/interactive/state.js +34 -0
- package/dist/interactive/tree.js +155 -0
- package/dist/interactive/types.js +1 -0
- package/dist/interactive/view-model.js +1 -0
- package/dist/interactive-ui.js +159 -194
- package/dist/pipeline/auto-flow.js +9 -6
- package/dist/pipeline/context.js +7 -5
- package/dist/pipeline/declarative-flow-runner.js +212 -6
- package/dist/pipeline/declarative-flows.js +63 -17
- package/dist/pipeline/execution-routing-config.js +15 -0
- package/dist/pipeline/flow-catalog.js +50 -12
- package/dist/pipeline/flow-run-resume.js +29 -0
- package/dist/pipeline/flow-specs/auto-common.json +90 -360
- package/dist/pipeline/flow-specs/auto-golang.json +81 -360
- package/dist/pipeline/flow-specs/auto-simple.json +141 -0
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
- package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +316 -0
- package/dist/pipeline/flow-specs/design-review.json +10 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
- package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
- package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
- package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
- package/dist/pipeline/flow-specs/implement.json +13 -6
- package/dist/pipeline/flow-specs/instant-task.json +177 -0
- package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
- package/dist/pipeline/flow-specs/plan-revise.json +7 -1
- package/dist/pipeline/flow-specs/plan.json +51 -71
- package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
- package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
- package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
- package/dist/pipeline/flow-specs/review/review-project.json +12 -0
- package/dist/pipeline/flow-specs/review/review.json +37 -31
- package/dist/pipeline/flow-specs/task-describe.json +2 -0
- package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
- package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
- package/dist/pipeline/launch-profile-config.js +30 -18
- package/dist/pipeline/node-contract.js +1 -0
- package/dist/pipeline/node-registry.js +115 -6
- package/dist/pipeline/node-runner.js +3 -2
- package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
- package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
- package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
- package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
- package/dist/pipeline/nodes/ensure-summary-json-node.js +13 -2
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
- package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
- package/dist/pipeline/nodes/flow-run-node.js +242 -8
- package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
- package/dist/pipeline/nodes/llm-prompt-node.js +38 -36
- package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
- package/dist/pipeline/nodes/review-verdict-node.js +86 -0
- package/dist/pipeline/nodes/select-files-form-node.js +8 -0
- package/dist/pipeline/nodes/structured-summary-node.js +24 -0
- package/dist/pipeline/nodes/user-input-node.js +38 -3
- package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
- package/dist/pipeline/plugin-loader.js +389 -0
- package/dist/pipeline/plugin-types.js +1 -0
- package/dist/pipeline/prompt-registry.js +3 -1
- package/dist/pipeline/prompt-runtime.js +4 -1
- package/dist/pipeline/registry.js +71 -4
- package/dist/pipeline/review-iteration.js +26 -0
- package/dist/pipeline/spec-compiler.js +3 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-types.js +3 -0
- package/dist/pipeline/spec-validator.js +20 -0
- package/dist/pipeline/value-resolver.js +76 -2
- package/dist/plugin-sdk.js +1 -0
- package/dist/prompts.js +36 -14
- package/dist/review-severity.js +45 -0
- package/dist/runtime/artifact-registry.js +405 -0
- package/dist/runtime/design-review-input-contract.js +17 -16
- package/dist/runtime/env-loader.js +3 -0
- package/dist/runtime/execution-routing-store.js +134 -0
- package/dist/runtime/execution-routing.js +233 -0
- package/dist/runtime/interactive-execution-routing.js +471 -0
- package/dist/runtime/plan-revise-input-contract.js +35 -32
- package/dist/runtime/planning-bundle.js +123 -0
- package/dist/runtime/ready-to-merge.js +22 -1
- package/dist/runtime/review-input-contract.js +100 -0
- package/dist/structured-artifact-schema-registry.js +9 -0
- package/dist/structured-artifact-schemas.json +140 -1
- package/dist/structured-artifacts.js +77 -6
- package/dist/user-input.js +70 -3
- package/docs/example/.flows/examples/claude-example.json +50 -0
- package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/examples/.flows/claude-example.json +50 -0
- package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/plugin-sdk.md +731 -0
- package/package.json +11 -4
|
@@ -4,7 +4,8 @@ import { DoctorImpact, DoctorStatus, WorkflowContinuityState } from "../types.js
|
|
|
4
4
|
import { CATEGORY } from "./category.js";
|
|
5
5
|
import { BUILT_IN_COMMAND_FLOW_IDS } from "../../pipeline/flow-catalog.js";
|
|
6
6
|
import { validateStructuredArtifact } from "../../structured-artifacts.js";
|
|
7
|
-
import { designJsonFile,
|
|
7
|
+
import { designJsonFile, latestArtifactIteration, planJsonFile, taskContextJsonFile, } from "../../artifacts.js";
|
|
8
|
+
import { inspectLatestPlanningBundle } from "../../runtime/planning-bundle.js";
|
|
8
9
|
const GO_BINARY_PATTERNS = ["go", "go.exe"];
|
|
9
10
|
const GIT_BINARY_PATTERNS = ["git", "git.exe"];
|
|
10
11
|
function statePriority(state) {
|
|
@@ -84,19 +85,15 @@ function checkStructuredArtifactFile(entry, artifactPath, schemaId, missingReaso
|
|
|
84
85
|
}
|
|
85
86
|
function checkImplementWorkflowContinuity(scopeKey) {
|
|
86
87
|
const entry = createEntry("implement");
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing design artifact from the planning stage.");
|
|
88
|
+
const planningBundle = inspectLatestPlanningBundle(scopeKey);
|
|
89
|
+
if (planningBundle.status === "missing") {
|
|
90
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, planningBundle.errorMessage);
|
|
91
91
|
}
|
|
92
|
-
else {
|
|
93
|
-
|
|
92
|
+
else if (planningBundle.status === "incomplete") {
|
|
93
|
+
setEntryState(entry, WorkflowContinuityState.InvalidState, planningBundle.errorMessage);
|
|
94
94
|
}
|
|
95
|
-
if (
|
|
96
|
-
setEntryState(entry, WorkflowContinuityState.
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
checkStructuredArtifactFile(entry, planJsonFile(scopeKey, latestPlanIteration), "implementation-plan/v1", "Missing plan artifact from the planning stage.", "Plan artifact schema is invalid");
|
|
95
|
+
else if (planningBundle.status === "invalid") {
|
|
96
|
+
setEntryState(entry, WorkflowContinuityState.InvalidState, planningBundle.errorMessage);
|
|
100
97
|
}
|
|
101
98
|
if (entry.state === WorkflowContinuityState.NeedsPreviousStage) {
|
|
102
99
|
entry.nextStep = "Run plan to generate design, plan, and QA artifacts for this scope.";
|
|
@@ -154,18 +151,18 @@ function checkPlanJiraWorkflowContinuity(scopeKey) {
|
|
|
154
151
|
}
|
|
155
152
|
function checkPlanProjectWorkflowContinuity(scopeKey) {
|
|
156
153
|
const entry = createEntry("plan", "project");
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing task
|
|
154
|
+
const latestTaskContextIteration = getLatestJsonArtifactIteration(scopeKey, "task-context");
|
|
155
|
+
if (latestTaskContextIteration === null) {
|
|
156
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing normalized task-context artifact.");
|
|
160
157
|
}
|
|
161
158
|
else {
|
|
162
|
-
checkStructuredArtifactFile(entry,
|
|
159
|
+
checkStructuredArtifactFile(entry, taskContextJsonFile(scopeKey, latestTaskContextIteration), "task-context/v1", "Missing normalized task-context artifact.", "Task-context artifact schema is invalid");
|
|
163
160
|
}
|
|
164
161
|
if (entry.state === WorkflowContinuityState.NeedsPreviousStage) {
|
|
165
|
-
entry.nextStep = "Run
|
|
162
|
+
entry.nextStep = "Run a source flow that produces task-context for this scope before running plan:project.";
|
|
166
163
|
}
|
|
167
164
|
else if (entry.state === WorkflowContinuityState.InvalidState) {
|
|
168
|
-
entry.nextStep = "Regenerate the task
|
|
165
|
+
entry.nextStep = "Regenerate the task-context artifact before running plan:project.";
|
|
169
166
|
}
|
|
170
167
|
return entry;
|
|
171
168
|
}
|
package/dist/flow-state.js
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { ensureScopeWorkspaceDir, flowStateFile } from "./artifacts.js";
|
|
3
4
|
import { TaskRunnerError } from "./errors.js";
|
|
4
|
-
|
|
5
|
+
import { isFlowRunResumeEnvelope } from "./pipeline/flow-run-resume.js";
|
|
6
|
+
import { resolveStoredExecutionRoutingSnapshot, singleLaunchProfileExecutionRouting } from "./runtime/execution-routing.js";
|
|
7
|
+
const FLOW_STATE_SCHEMA_VERSION = 3;
|
|
8
|
+
const CONTINUABLE_FLOW_KINDS = new Set([
|
|
9
|
+
"design-review-loop-flow",
|
|
10
|
+
"review-loop-flow",
|
|
11
|
+
"review-project-loop-flow",
|
|
12
|
+
"run-go-linter-loop-flow",
|
|
13
|
+
"run-go-tests-loop-flow",
|
|
14
|
+
]);
|
|
15
|
+
const CONTINUABLE_PARENT_FLOW_IDS = new Set([
|
|
16
|
+
"auto-common",
|
|
17
|
+
"auto-simple",
|
|
18
|
+
"auto-golang",
|
|
19
|
+
"instant-task",
|
|
20
|
+
]);
|
|
21
|
+
const CONTINUABLE_DIRECT_FLOW_IDS = new Set([
|
|
22
|
+
"review-loop",
|
|
23
|
+
"run-go-linter-loop",
|
|
24
|
+
"run-go-tests-loop",
|
|
25
|
+
]);
|
|
5
26
|
function nowIso8601() {
|
|
6
27
|
return new Date().toISOString();
|
|
7
28
|
}
|
|
29
|
+
function ensurePublicationRunId(executionState) {
|
|
30
|
+
executionState.publicationRunId ??= randomUUID();
|
|
31
|
+
return executionState.publicationRunId;
|
|
32
|
+
}
|
|
8
33
|
export function stripExecutionStatePayload(executionState) {
|
|
34
|
+
ensurePublicationRunId(executionState);
|
|
9
35
|
return {
|
|
36
|
+
...(executionState.runId ? { runId: executionState.runId } : {}),
|
|
37
|
+
...(executionState.publicationRunId ? { publicationRunId: executionState.publicationRunId } : {}),
|
|
10
38
|
flowKind: executionState.flowKind,
|
|
11
39
|
flowVersion: executionState.flowVersion,
|
|
12
40
|
terminated: executionState.terminated,
|
|
13
41
|
...(executionState.terminationReason ? { terminationReason: executionState.terminationReason } : {}),
|
|
42
|
+
...(executionState.terminationOutcome ? { terminationOutcome: executionState.terminationOutcome } : {}),
|
|
14
43
|
phases: executionState.phases.map((phase) => ({
|
|
15
44
|
id: phase.id,
|
|
16
45
|
status: phase.status,
|
|
@@ -22,6 +51,7 @@ export function stripExecutionStatePayload(executionState) {
|
|
|
22
51
|
status: step.status,
|
|
23
52
|
...(step.outputs ? { outputs: step.outputs } : {}),
|
|
24
53
|
...(step.value !== undefined ? { value: step.value } : {}),
|
|
54
|
+
...(step.publishedArtifacts ? { publishedArtifacts: step.publishedArtifacts } : {}),
|
|
25
55
|
...(step.startedAt ? { startedAt: step.startedAt } : {}),
|
|
26
56
|
...(step.finishedAt ? { finishedAt: step.finishedAt } : {}),
|
|
27
57
|
...(step.stopFlow !== undefined ? { stopFlow: step.stopFlow } : {}),
|
|
@@ -29,7 +59,11 @@ export function stripExecutionStatePayload(executionState) {
|
|
|
29
59
|
})),
|
|
30
60
|
};
|
|
31
61
|
}
|
|
32
|
-
export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, launchProfile) {
|
|
62
|
+
export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, launchProfile, executionRouting, selectedRoutingPreset) {
|
|
63
|
+
ensurePublicationRunId(executionState);
|
|
64
|
+
const effectiveExecutionRouting = executionRouting ?? (launchProfile ? singleLaunchProfileExecutionRouting(launchProfile) : undefined);
|
|
65
|
+
const effectiveLaunchProfile = launchProfile ?? effectiveExecutionRouting?.defaultRoute;
|
|
66
|
+
const continuation = inferContinuationMetadata(flowId, executionState);
|
|
33
67
|
return {
|
|
34
68
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
35
69
|
flowId,
|
|
@@ -38,10 +72,98 @@ export function createFlowRunState(scopeKey, flowId, executionState, jiraRef, la
|
|
|
38
72
|
status: "pending",
|
|
39
73
|
currentStep: null,
|
|
40
74
|
updatedAt: nowIso8601(),
|
|
41
|
-
...(
|
|
75
|
+
...(effectiveLaunchProfile ? { launchProfile: effectiveLaunchProfile } : {}),
|
|
76
|
+
...(effectiveExecutionRouting ? { executionRouting: effectiveExecutionRouting, routingFingerprint: effectiveExecutionRouting.fingerprint } : {}),
|
|
77
|
+
...(selectedRoutingPreset ? { selectedRoutingPreset } : {}),
|
|
78
|
+
continuation,
|
|
42
79
|
executionState: stripExecutionStatePayload(executionState),
|
|
43
80
|
};
|
|
44
81
|
}
|
|
82
|
+
function upgradeFlowRunStateV1(state) {
|
|
83
|
+
const executionRouting = state.launchProfile ? singleLaunchProfileExecutionRouting(state.launchProfile) : undefined;
|
|
84
|
+
return {
|
|
85
|
+
...state,
|
|
86
|
+
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
87
|
+
...(executionRouting ? { executionRouting, routingFingerprint: executionRouting.fingerprint } : {}),
|
|
88
|
+
...(executionRouting ? { selectedRoutingPreset: { kind: "custom", label: "Legacy launch profile" } } : {}),
|
|
89
|
+
continuation: {
|
|
90
|
+
continueEligible: false,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function upgradeFlowRunStateV2(state) {
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
98
|
+
continuation: {
|
|
99
|
+
continueEligible: false,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function parseTerminationLocation(terminationReason) {
|
|
104
|
+
if (typeof terminationReason !== "string") {
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
const match = /^Stopped by ([^:]+):(.+)$/.exec(terminationReason.trim());
|
|
108
|
+
if (!match) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
const stopPhaseId = match[1];
|
|
112
|
+
const stopStepId = match[2];
|
|
113
|
+
return {
|
|
114
|
+
...(stopPhaseId ? { stopPhaseId } : {}),
|
|
115
|
+
...(stopStepId ? { stopStepId } : {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function inferContinuationMetadata(flowId, executionState) {
|
|
119
|
+
const stopLocation = parseTerminationLocation(executionState.terminationReason);
|
|
120
|
+
const continueEligible = CONTINUABLE_FLOW_KINDS.has(executionState.flowKind)
|
|
121
|
+
|| (CONTINUABLE_PARENT_FLOW_IDS.has(flowId) && Boolean(stopLocation.stopPhaseId && stopLocation.stopStepId));
|
|
122
|
+
return {
|
|
123
|
+
continueEligible,
|
|
124
|
+
...(stopLocation.stopPhaseId ? { stopPhaseId: stopLocation.stopPhaseId } : {}),
|
|
125
|
+
...(stopLocation.stopStepId ? { stopStepId: stopLocation.stopStepId } : {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function normalizeFlowRunState(raw, flowId, filePath) {
|
|
129
|
+
if (!raw || typeof raw !== "object") {
|
|
130
|
+
throw new TaskRunnerError(`Invalid flow state file format: ${filePath}`);
|
|
131
|
+
}
|
|
132
|
+
const schemaVersion = raw.schemaVersion;
|
|
133
|
+
let state;
|
|
134
|
+
if (schemaVersion === 1) {
|
|
135
|
+
state = upgradeFlowRunStateV1(raw);
|
|
136
|
+
}
|
|
137
|
+
else if (schemaVersion === 2) {
|
|
138
|
+
state = upgradeFlowRunStateV2(raw);
|
|
139
|
+
}
|
|
140
|
+
else if (schemaVersion === FLOW_STATE_SCHEMA_VERSION) {
|
|
141
|
+
state = raw;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new TaskRunnerError(`Unsupported flow state schema in ${filePath}: ${String(schemaVersion ?? "unknown")}`);
|
|
145
|
+
}
|
|
146
|
+
if (state.flowId !== flowId) {
|
|
147
|
+
throw new TaskRunnerError(`Flow state ${filePath} belongs to flow '${state.flowId}', expected '${flowId}'`);
|
|
148
|
+
}
|
|
149
|
+
if (state.executionRouting) {
|
|
150
|
+
const executionRouting = resolveStoredExecutionRoutingSnapshot(state.executionRouting);
|
|
151
|
+
state.executionRouting = executionRouting;
|
|
152
|
+
state.routingFingerprint = executionRouting.fingerprint;
|
|
153
|
+
state.launchProfile = executionRouting.defaultRoute;
|
|
154
|
+
}
|
|
155
|
+
else if (state.launchProfile) {
|
|
156
|
+
const executionRouting = singleLaunchProfileExecutionRouting(state.launchProfile);
|
|
157
|
+
state.executionRouting = executionRouting;
|
|
158
|
+
state.routingFingerprint = executionRouting.fingerprint;
|
|
159
|
+
}
|
|
160
|
+
const inferredContinuation = inferContinuationMetadata(state.flowId, state.executionState);
|
|
161
|
+
state.continuation = {
|
|
162
|
+
...inferredContinuation,
|
|
163
|
+
continueEligible: inferredContinuation.continueEligible && state.continuation?.continueEligible !== false,
|
|
164
|
+
};
|
|
165
|
+
return state;
|
|
166
|
+
}
|
|
45
167
|
export function loadFlowRunState(scopeKey, flowId) {
|
|
46
168
|
const filePath = flowStateFile(scopeKey, flowId);
|
|
47
169
|
if (!existsSync(filePath)) {
|
|
@@ -54,20 +176,21 @@ export function loadFlowRunState(scopeKey, flowId) {
|
|
|
54
176
|
catch (error) {
|
|
55
177
|
throw new TaskRunnerError(`Failed to parse flow state file ${filePath}: ${error.message}`);
|
|
56
178
|
}
|
|
57
|
-
|
|
58
|
-
throw new TaskRunnerError(`Invalid flow state file format: ${filePath}`);
|
|
59
|
-
}
|
|
60
|
-
const state = raw;
|
|
61
|
-
if (state.schemaVersion !== FLOW_STATE_SCHEMA_VERSION) {
|
|
62
|
-
throw new TaskRunnerError(`Unsupported flow state schema in ${filePath}: ${state.schemaVersion}`);
|
|
63
|
-
}
|
|
64
|
-
if (state.flowId !== flowId) {
|
|
65
|
-
throw new TaskRunnerError(`Flow state ${filePath} belongs to flow '${state.flowId}', expected '${flowId}'`);
|
|
66
|
-
}
|
|
67
|
-
return state;
|
|
179
|
+
return normalizeFlowRunState(raw, flowId, filePath);
|
|
68
180
|
}
|
|
69
181
|
export function saveFlowRunState(state) {
|
|
70
182
|
state.updatedAt = nowIso8601();
|
|
183
|
+
state.schemaVersion = FLOW_STATE_SCHEMA_VERSION;
|
|
184
|
+
if (state.executionRouting) {
|
|
185
|
+
state.executionRouting = resolveStoredExecutionRoutingSnapshot(state.executionRouting);
|
|
186
|
+
state.routingFingerprint = state.executionRouting.fingerprint;
|
|
187
|
+
state.launchProfile = state.executionRouting.defaultRoute;
|
|
188
|
+
}
|
|
189
|
+
else if (state.launchProfile) {
|
|
190
|
+
state.executionRouting = singleLaunchProfileExecutionRouting(state.launchProfile);
|
|
191
|
+
state.routingFingerprint = state.executionRouting.fingerprint;
|
|
192
|
+
}
|
|
193
|
+
state.continuation = inferContinuationMetadata(state.flowId, state.executionState);
|
|
71
194
|
ensureScopeWorkspaceDir(state.scopeKey);
|
|
72
195
|
writeFileSync(flowStateFile(state.scopeKey, state.flowId), `${JSON.stringify({
|
|
73
196
|
...state,
|
|
@@ -97,14 +220,63 @@ export function hasResumableFlowState(state) {
|
|
|
97
220
|
}
|
|
98
221
|
return state.executionState.phases.some((phase) => phase.steps.some((step) => step.status === "done" || step.status === "running"));
|
|
99
222
|
}
|
|
223
|
+
function hasContinuableFlowState(state) {
|
|
224
|
+
if (!state) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (!state.executionState.terminated && state.status !== "completed") {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return state.continuation?.continueEligible === true;
|
|
231
|
+
}
|
|
232
|
+
export function classifyFlowLaunchAvailability(state) {
|
|
233
|
+
if (!state) {
|
|
234
|
+
return {
|
|
235
|
+
hasExistingState: false,
|
|
236
|
+
requiresExplicitChoice: false,
|
|
237
|
+
resume: { available: false, reason: "No saved state found." },
|
|
238
|
+
continue: { available: false, reason: "No saved state found." },
|
|
239
|
+
restart: { available: true, reason: "Start a fresh attempt." },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const resumeAvailable = hasResumableFlowState(state);
|
|
243
|
+
const continueAvailable = hasContinuableFlowState(state);
|
|
244
|
+
const availability = {
|
|
245
|
+
hasExistingState: true,
|
|
246
|
+
requiresExplicitChoice: resumeAvailable || continueAvailable,
|
|
247
|
+
resume: resumeAvailable
|
|
248
|
+
? { available: true, reason: "Continue the interrupted execution state." }
|
|
249
|
+
: {
|
|
250
|
+
available: false,
|
|
251
|
+
reason: state.executionState.terminated || state.status === "completed"
|
|
252
|
+
? "The saved run already terminated and cannot be resumed."
|
|
253
|
+
: "The saved state is not resumable.",
|
|
254
|
+
},
|
|
255
|
+
continue: continueAvailable
|
|
256
|
+
? { available: true, reason: "Start the next iteration from the latest active artifacts." }
|
|
257
|
+
: {
|
|
258
|
+
available: false,
|
|
259
|
+
reason: state.schemaVersion < FLOW_STATE_SCHEMA_VERSION
|
|
260
|
+
? "Legacy flow state lacks safe continuation metadata."
|
|
261
|
+
: "The saved run does not expose a continuable loop boundary.",
|
|
262
|
+
},
|
|
263
|
+
restart: {
|
|
264
|
+
available: true,
|
|
265
|
+
reason: "Archive the active attempt and start a fresh run.",
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
return availability;
|
|
269
|
+
}
|
|
100
270
|
function normalizeStepState(step) {
|
|
101
271
|
if (step.status !== "running") {
|
|
102
272
|
return step;
|
|
103
273
|
}
|
|
104
|
-
const
|
|
274
|
+
const resumeValue = isFlowRunResumeEnvelope(step.value) ? step.value : undefined;
|
|
275
|
+
const { finishedAt: _finishedAt, outputs: _outputs, value: _value, publishedArtifacts: _publishedArtifacts, stopFlow: _stopFlow, ...rest } = step;
|
|
105
276
|
return {
|
|
106
277
|
...rest,
|
|
107
278
|
status: "pending",
|
|
279
|
+
...(resumeValue ? { value: resumeValue } : {}),
|
|
108
280
|
};
|
|
109
281
|
}
|
|
110
282
|
function normalizePhaseState(phase) {
|
|
@@ -171,9 +343,34 @@ export function prepareFlowStateForResume(state) {
|
|
|
171
343
|
state.currentStep = null;
|
|
172
344
|
state.executionState = {
|
|
173
345
|
...state.executionState,
|
|
346
|
+
publicationRunId: randomUUID(),
|
|
174
347
|
terminated: false,
|
|
175
348
|
phases: state.executionState.phases.map(normalizePhaseState),
|
|
176
349
|
};
|
|
177
350
|
delete state.executionState.terminationReason;
|
|
178
351
|
return state;
|
|
179
352
|
}
|
|
353
|
+
export function prepareFlowStateForContinue(state, orderedPhases) {
|
|
354
|
+
state.status = "pending";
|
|
355
|
+
state.lastError = null;
|
|
356
|
+
state.currentStep = null;
|
|
357
|
+
const flowKind = state.executionState.flowKind;
|
|
358
|
+
if (CONTINUABLE_FLOW_KINDS.has(flowKind) || CONTINUABLE_DIRECT_FLOW_IDS.has(state.flowId)) {
|
|
359
|
+
state.executionState = {
|
|
360
|
+
...state.executionState,
|
|
361
|
+
publicationRunId: randomUUID(),
|
|
362
|
+
terminated: false,
|
|
363
|
+
phases: orderedPhases.map(createPendingPhaseState),
|
|
364
|
+
};
|
|
365
|
+
delete state.executionState.terminationReason;
|
|
366
|
+
delete state.executionState.terminationOutcome;
|
|
367
|
+
return state;
|
|
368
|
+
}
|
|
369
|
+
const targetPhaseId = state.continuation?.stopPhaseId ?? parseTerminationLocation(state.executionState.terminationReason).stopPhaseId;
|
|
370
|
+
if (!targetPhaseId) {
|
|
371
|
+
throw new TaskRunnerError("Continue is impossible because the stop phase could not be determined safely. Use restart.");
|
|
372
|
+
}
|
|
373
|
+
rewindFlowRunStateToPhase(state, orderedPhases, targetPhaseId);
|
|
374
|
+
state.executionState.publicationRunId = randomUUID();
|
|
375
|
+
return state;
|
|
376
|
+
}
|