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.
Files changed (110) hide show
  1. package/README.md +76 -19
  2. package/dist/artifact-manifest.js +219 -0
  3. package/dist/artifacts.js +88 -3
  4. package/dist/doctor/checks/env-diagnostics.js +25 -0
  5. package/dist/doctor/checks/executors.js +2 -2
  6. package/dist/doctor/checks/flow-readiness.js +15 -18
  7. package/dist/flow-state.js +212 -15
  8. package/dist/index.js +539 -209
  9. package/dist/interactive/blessed-session.js +361 -0
  10. package/dist/interactive/controller.js +1326 -0
  11. package/dist/interactive/create-interactive-session.js +5 -0
  12. package/dist/interactive/ink/index.js +597 -0
  13. package/dist/interactive/progress.js +245 -0
  14. package/dist/interactive/selectors.js +14 -0
  15. package/dist/interactive/session.js +1 -0
  16. package/dist/interactive/state.js +34 -0
  17. package/dist/interactive/tree.js +155 -0
  18. package/dist/interactive/types.js +1 -0
  19. package/dist/interactive/view-model.js +1 -0
  20. package/dist/interactive-ui.js +159 -194
  21. package/dist/pipeline/auto-flow.js +9 -6
  22. package/dist/pipeline/context.js +7 -5
  23. package/dist/pipeline/declarative-flow-runner.js +212 -6
  24. package/dist/pipeline/declarative-flows.js +63 -17
  25. package/dist/pipeline/execution-routing-config.js +15 -0
  26. package/dist/pipeline/flow-catalog.js +50 -12
  27. package/dist/pipeline/flow-run-resume.js +29 -0
  28. package/dist/pipeline/flow-specs/auto-common.json +90 -360
  29. package/dist/pipeline/flow-specs/auto-golang.json +81 -360
  30. package/dist/pipeline/flow-specs/auto-simple.json +141 -0
  31. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
  32. package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
  33. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +316 -0
  34. package/dist/pipeline/flow-specs/design-review.json +10 -0
  35. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
  36. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
  37. package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
  38. package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
  39. package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
  40. package/dist/pipeline/flow-specs/implement.json +13 -6
  41. package/dist/pipeline/flow-specs/instant-task.json +177 -0
  42. package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
  43. package/dist/pipeline/flow-specs/plan-revise.json +7 -1
  44. package/dist/pipeline/flow-specs/plan.json +51 -71
  45. package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
  46. package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
  47. package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
  48. package/dist/pipeline/flow-specs/review/review-project.json +12 -0
  49. package/dist/pipeline/flow-specs/review/review.json +37 -31
  50. package/dist/pipeline/flow-specs/task-describe.json +2 -0
  51. package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
  52. package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
  53. package/dist/pipeline/launch-profile-config.js +30 -18
  54. package/dist/pipeline/node-contract.js +1 -0
  55. package/dist/pipeline/node-registry.js +115 -6
  56. package/dist/pipeline/node-runner.js +3 -2
  57. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
  58. package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
  59. package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
  60. package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
  61. package/dist/pipeline/nodes/ensure-summary-json-node.js +13 -2
  62. package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
  63. package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
  64. package/dist/pipeline/nodes/flow-run-node.js +242 -8
  65. package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
  66. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
  67. package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
  68. package/dist/pipeline/nodes/llm-prompt-node.js +38 -36
  69. package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
  70. package/dist/pipeline/nodes/review-verdict-node.js +86 -0
  71. package/dist/pipeline/nodes/select-files-form-node.js +8 -0
  72. package/dist/pipeline/nodes/structured-summary-node.js +24 -0
  73. package/dist/pipeline/nodes/user-input-node.js +38 -3
  74. package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
  75. package/dist/pipeline/plugin-loader.js +389 -0
  76. package/dist/pipeline/plugin-types.js +1 -0
  77. package/dist/pipeline/prompt-registry.js +3 -1
  78. package/dist/pipeline/prompt-runtime.js +4 -1
  79. package/dist/pipeline/registry.js +71 -4
  80. package/dist/pipeline/review-iteration.js +26 -0
  81. package/dist/pipeline/spec-compiler.js +3 -0
  82. package/dist/pipeline/spec-loader.js +14 -0
  83. package/dist/pipeline/spec-types.js +3 -0
  84. package/dist/pipeline/spec-validator.js +20 -0
  85. package/dist/pipeline/value-resolver.js +76 -2
  86. package/dist/plugin-sdk.js +1 -0
  87. package/dist/prompts.js +36 -14
  88. package/dist/review-severity.js +45 -0
  89. package/dist/runtime/artifact-registry.js +405 -0
  90. package/dist/runtime/design-review-input-contract.js +17 -16
  91. package/dist/runtime/env-loader.js +3 -0
  92. package/dist/runtime/execution-routing-store.js +134 -0
  93. package/dist/runtime/execution-routing.js +233 -0
  94. package/dist/runtime/interactive-execution-routing.js +471 -0
  95. package/dist/runtime/plan-revise-input-contract.js +35 -32
  96. package/dist/runtime/planning-bundle.js +123 -0
  97. package/dist/runtime/ready-to-merge.js +22 -1
  98. package/dist/runtime/review-input-contract.js +100 -0
  99. package/dist/structured-artifact-schema-registry.js +9 -0
  100. package/dist/structured-artifact-schemas.json +140 -1
  101. package/dist/structured-artifacts.js +77 -6
  102. package/dist/user-input.js +70 -3
  103. package/docs/example/.flows/examples/claude-example.json +50 -0
  104. package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
  105. package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
  106. package/docs/examples/.flows/claude-example.json +50 -0
  107. package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
  108. package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
  109. package/docs/plugin-sdk.md +731 -0
  110. 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, jiraDescriptionJsonFile, latestArtifactIteration, planJsonFile, } from "../../artifacts.js";
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 latestDesignIteration = getLatestJsonArtifactIteration(scopeKey, "design");
88
- const latestPlanIteration = getLatestJsonArtifactIteration(scopeKey, "plan");
89
- if (latestDesignIteration === null) {
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
- checkStructuredArtifactFile(entry, designJsonFile(scopeKey, latestDesignIteration), "implementation-design/v1", "Missing design artifact from the planning stage.", "Design artifact schema is invalid");
92
+ else if (planningBundle.status === "incomplete") {
93
+ setEntryState(entry, WorkflowContinuityState.InvalidState, planningBundle.errorMessage);
94
94
  }
95
- if (latestPlanIteration === null) {
96
- setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing plan artifact from the planning stage.");
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 latestDescriptionIteration = getLatestJsonArtifactIteration(scopeKey, "jira-description");
158
- if (latestDescriptionIteration === null) {
159
- setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing task description artifact from task-describe.");
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, jiraDescriptionJsonFile(scopeKey, latestDescriptionIteration), "jira-description/v1", "Missing task description artifact from task-describe.", "Task description artifact schema is invalid");
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 task-describe first to create a task description artifact for this scope.";
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 description artifact before running plan:project.";
165
+ entry.nextStep = "Regenerate the task-context artifact before running plan:project.";
169
166
  }
170
167
  return entry;
171
168
  }
@@ -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
- const FLOW_STATE_SCHEMA_VERSION = 1;
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
- ...(launchProfile ? { launchProfile } : {}),
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
- if (!raw || typeof raw !== "object") {
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 { finishedAt: _finishedAt, outputs: _outputs, value: _value, stopFlow: _stopFlow, ...rest } = step;
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
+ }