akm-cli 0.9.31 → 0.10.26101002-alpha
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/CHANGELOG.md +265 -0
- package/STABILITY.md +79 -53
- package/dist/api-node.mjs +20 -0
- package/dist/api.d.ts +23 -0
- package/dist/api.js +45 -0
- package/dist/assets/hints/cli-hints-full.md +13 -27
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/improve-strategies/default.json +3 -3
- package/dist/assets/prompts/consolidate-pair.md +9 -7
- package/dist/cli/shared.js +31 -48
- package/dist/cli/unknown-flags.js +6 -43
- package/dist/cli.js +8 -34
- package/dist/commands/agent/contribute-cli.js +1 -1
- package/dist/commands/completions.js +1 -2
- package/dist/commands/config-cli.js +5 -28
- package/dist/commands/env/env-cli.js +5 -5
- package/dist/commands/env/secret-cli.js +2 -2
- package/dist/commands/health/checks.js +41 -16
- package/dist/commands/health/plugin-staleness.js +52 -22
- package/dist/commands/improve/autonomy-gate.js +5 -3
- package/dist/commands/improve/consolidate/pair-pass.js +7 -1
- package/dist/commands/improve/consolidate.js +6 -35
- package/dist/commands/improve/distill/lesson-update.js +91 -0
- package/dist/commands/improve/distill-guards.js +1 -43
- package/dist/commands/improve/distill.js +140 -20
- package/dist/commands/improve/eligibility.js +14 -1
- package/dist/commands/improve/extract-cli.js +1 -1
- package/dist/commands/improve/improve-cli.js +87 -102
- package/dist/commands/improve/improve-strategies.js +8 -12
- package/dist/commands/improve/improve.js +2 -2
- package/dist/commands/improve/loop-stages.js +0 -1
- package/dist/commands/improve/planner.js +0 -1
- package/dist/commands/improve/preparation.js +40 -99
- package/dist/commands/improve/proactive-maintenance.js +2 -2
- package/dist/commands/improve/reflect-noise.js +3 -55
- package/dist/commands/improve/reflect.js +3 -8
- package/dist/commands/improve/retrieval-scope.js +2 -2
- package/dist/commands/improve/salience.js +2 -9
- package/dist/commands/improve/stage.js +14 -9
- package/dist/commands/lint/index.js +5 -20
- package/dist/commands/observability-cli.js +3 -9
- package/dist/commands/proposal/drain.js +39 -6
- package/dist/commands/proposal/proposal-cli.js +11 -31
- package/dist/commands/proposal/repository.js +3 -3
- package/dist/commands/read/knowledge.js +3 -3
- package/dist/commands/read/remember-cli.js +0 -13
- package/dist/commands/read/search-cli.js +29 -97
- package/dist/commands/read/search.js +0 -11
- package/dist/commands/read/show.js +0 -19
- package/dist/commands/sources/plugin-upgrade.js +164 -101
- package/dist/commands/sources/sources-cli.js +1 -15
- package/dist/commands/sources/stash-cli.js +4 -12
- package/dist/commands/tasks/tasks-cli.js +4 -115
- package/dist/commands/workflow/plan.js +11 -33
- package/dist/commands/workflow-cli.js +2 -7
- package/dist/core/adapter/adapter-ids.js +1 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
- package/dist/core/adapter/adapters/akm-adapter.js +9 -48
- package/dist/core/adapter/adapters/akm-lint.js +10 -26
- package/dist/core/adapter/adapters/akm-metadata.js +2 -72
- package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
- package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
- package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
- package/dist/core/adapter/recognize-match.js +3 -4
- package/dist/core/common.js +18 -2
- package/dist/core/config/config-schema.js +12 -15
- package/dist/core/config/config-types.js +6 -15
- package/dist/core/config/config-walker.js +6 -23
- package/dist/core/config/config.js +17 -80
- package/dist/core/config/engine-semantics.js +7 -3
- package/dist/core/config/schema/engines.js +7 -7
- package/dist/core/config/schema/improve-processes.js +8 -61
- package/dist/core/config/schema/improve.js +2 -21
- package/dist/core/config/schema/index-config.js +6 -39
- package/dist/core/env-secret-ref.js +2 -45
- package/dist/core/errors.js +1 -2
- package/dist/core/extra-params.js +2 -86
- package/dist/core/improve-result.js +11 -1
- package/dist/core/mutation-target.js +2 -2
- package/dist/core/recognition-util.js +4 -5
- package/dist/core/warn.js +21 -4
- package/dist/core/write-source.js +3 -3
- package/dist/execution/target-ref.js +1 -2
- package/dist/indexer/search/db-search.js +4 -19
- package/dist/indexer/search/fts-query.js +0 -19
- package/dist/integrations/agent/engine-resolution.js +5 -2
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +41 -98
- package/dist/integrations/agent/request-lowering.js +3 -3
- package/dist/integrations/harnesses/aider/index.js +5 -0
- package/dist/integrations/harnesses/amazonq/index.js +1 -0
- package/dist/integrations/harnesses/claude/config-import.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +5 -0
- package/dist/integrations/harnesses/codex/index.js +1 -0
- package/dist/integrations/harnesses/copilot/index.js +1 -0
- package/dist/integrations/harnesses/gemini/index.js +1 -0
- package/dist/integrations/harnesses/index.js +18 -39
- package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
- package/dist/integrations/harnesses/opencode/config-import.js +121 -52
- package/dist/integrations/harnesses/opencode/index.js +22 -0
- package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
- package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
- package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
- package/dist/integrations/harnesses/opencode/session-log.js +104 -115
- package/dist/integrations/harnesses/opencode/version.js +98 -0
- package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +228 -0
- package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
- package/dist/integrations/harnesses/openhands/index.js +1 -0
- package/dist/integrations/harnesses/pi/index.js +1 -0
- package/dist/integrations/harnesses/shared.js +2 -0
- package/dist/integrations/harnesses/types.js +2 -0
- package/dist/llm/client.js +41 -11
- package/dist/output/cli-hints.js +1 -1
- package/dist/output/context.js +10 -19
- package/dist/output/format-exempt.js +5 -8
- package/dist/output/shapes/helpers.js +1 -22
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/shapes.js +3 -31
- package/dist/output/stdout.js +5 -1
- package/dist/output/text/command-format.js +6 -1
- package/dist/output/text/workflow-format.js +4 -22
- package/dist/scripts/akm-migrate-node.js +13049 -14013
- package/dist/scripts/akm-migrate.js +16316 -17276
- package/dist/setup/detect.js +10 -4
- package/dist/setup/harness-config-import.js +5 -14
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +12 -13
- package/dist/storage/repositories/task-history-repository.js +0 -5
- package/dist/storage/repositories/workflow-runs-repository.js +5 -15
- package/dist/tasks/embedded.js +2 -1
- package/dist/tasks/run/task-history.js +0 -2
- package/dist/tasks/schedule-gates.js +39 -0
- package/dist/tasks/scheduler-binding.js +0 -16
- package/dist/tasks/scheduler-sync.js +3 -97
- package/dist/tasks/source/bounded-document.js +1 -1
- package/dist/tasks/source/task-source-v4.js +2 -12
- package/dist/tasks/task-run-reserved-flags.js +2 -31
- package/dist/workflows/authoring/authoring.js +6 -6
- package/dist/workflows/compile.js +8 -65
- package/dist/workflows/exec/child-workflow.js +22 -3
- package/dist/workflows/exec/native-executor.js +12 -147
- package/dist/workflows/exec/run-workflow.js +31 -103
- package/dist/workflows/exec/step-work.js +9 -178
- package/dist/workflows/exec/unit-dispatch.js +0 -10
- package/dist/workflows/freeze/environment.js +3 -44
- package/dist/workflows/freeze/freeze.js +8 -41
- package/dist/workflows/freeze/targets/child-workflow.js +27 -63
- package/dist/workflows/freeze/targets/command.js +2 -16
- package/dist/workflows/freeze/targets/shell.js +0 -2
- package/dist/workflows/freeze/task-bindings.js +2 -28
- package/dist/workflows/ir/params.js +12 -2
- package/dist/workflows/parser.js +82 -232
- package/dist/workflows/plan.js +1 -1
- package/dist/workflows/renderer.js +10 -19
- package/dist/workflows/runtime/run-plan.js +11 -53
- package/dist/workflows/runtime/runs.js +4 -27
- package/dist/workflows/source-files.js +12 -16
- package/dist/workflows/source-semantics.js +1 -93
- package/docs/README.md +1 -1
- package/docs/agents/README.md +3 -3
- package/docs/agents/agent-install.md +1 -1
- package/docs/architecture/adapters.md +17 -31
- package/docs/architecture/architecture.md +4 -5
- package/docs/architecture/improvement.md +52 -21
- package/docs/architecture/internals/improve-workflow.md +15 -8
- package/docs/architecture/internals/indexing.md +1 -1
- package/docs/architecture/internals/search.md +1 -2
- package/docs/architecture/internals/storage-locations.md +5 -5
- package/docs/architecture/persisted-data-compat.md +13 -4
- package/docs/architecture/specs/0.9.0-decisions.md +6 -2
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
- package/docs/architecture/specs/workflow-format-unification.md +1 -6
- package/docs/architecture/testing/manual-testing-checklist.md +20 -29
- package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
- package/docs/architecture/testing/testing-workflow.md +1 -1
- package/docs/architecture/workflow-engine.md +5 -56
- package/docs/guides/author-workflows.md +39 -69
- package/docs/guides/capture-knowledge.md +1 -1
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
- package/docs/guides/discover-and-load.md +4 -4
- package/docs/guides/getting-started.md +1 -1
- package/docs/guides/improve-the-library.md +7 -7
- package/docs/guides/recipes/headless-install.md +2 -2
- package/docs/guides/run-workflows.md +1 -1
- package/docs/guides/use-with-any-agent.md +49 -4
- package/docs/integration/bundling-akm.md +7 -6
- package/docs/maintainers/curate-workmap.md +1 -2
- package/docs/maintainers/release-checklist.md +30 -1
- package/docs/posts/akm-command-tour.md +3 -3
- package/docs/product-surface.md +1 -1
- package/docs/reference/README.md +1 -0
- package/docs/reference/api.md +65 -0
- package/docs/reference/asset-types.md +3 -5
- package/docs/reference/bundle-types.md +2 -4
- package/docs/reference/cli.md +129 -171
- package/docs/reference/configuration.md +84 -19
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +2 -2
- package/docs/reference/supported-formats.md +3 -4
- package/docs/reference/tasks.md +12 -19
- package/docs/reference/website-sources.md +1 -1
- package/docs/reference/workflow-schema.md +89 -317
- package/docs/reference/workflows.md +20 -48
- package/package.json +12 -2
- package/schemas/akm-config.json +2 -243
- package/schemas/akm-task.json +2 -3
- package/schemas/akm-workflow.json +47 -84
- package/dist/assets/improve-strategies/catchup.json +0 -41
- package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
- package/dist/assets/improve-strategies/quick.json +0 -17
- package/dist/assets/improve-strategies/reflect-distill.json +0 -29
- package/dist/assets/improve-strategies/thorough.json +0 -49
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
- package/dist/cli/retired-commands.js +0 -118
- package/dist/commands/tasks/validate.js +0 -176
- package/dist/core/config/legacy-source-shape-shim.js +0 -117
- package/dist/integrations/harnesses/ids.js +0 -28
- package/dist/tasks/source/task-source-v3-frozen.js +0 -424
- package/dist/tasks/source/task-to-v4.js +0 -863
- package/dist/workflows/exec/worktree.js +0 -372
- package/dist/workflows/freeze/child-output-references.js +0 -83
- package/dist/workflows/freeze/identity.js +0 -22
- package/dist/workflows/freeze/targets/script.js +0 -50
- package/dist/workflows/freeze/targets/task.js +0 -124
- package/dist/workflows/github-yaml.js +0 -506
- package/dist/workflows/runtime/run-outputs.js +0 -80
|
@@ -3,43 +3,26 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
5
|
* The workflow compiler's front door. `.md` goes through the Markdown grammar
|
|
6
|
-
* (`parser.ts`),
|
|
7
|
-
* (`github-yaml.ts`); both produce a {@link WorkflowPlan} directly.
|
|
6
|
+
* (`parser.ts`), which produces a {@link WorkflowPlan} directly.
|
|
8
7
|
*
|
|
9
8
|
* {@link checkWorkflowPlan} is the one cross-step pass the grammars do not
|
|
10
|
-
* run: every `steps.<id>` reference must name an earlier step
|
|
11
|
-
* name any step), `inputs:` never name a param, and two non-fatal advisories.
|
|
9
|
+
* run: every `steps.<id>` reference must name an earlier step, `inputs:` never name a param, and two non-fatal advisories.
|
|
12
10
|
*/
|
|
13
11
|
import path from "node:path";
|
|
14
|
-
import { parseBuiltinCommandAction } from "../commands/command/builtin-action.js";
|
|
15
|
-
import { PORTABLE_ARGUMENTS_PLACEHOLDER } from "../commands/command/portable-template.js";
|
|
16
|
-
import { parseGithubWorkflowSource, WorkflowSourceFailure } from "./github-yaml.js";
|
|
17
12
|
import { parseWorkflow } from "./parser.js";
|
|
18
13
|
import { formatReference, parseReference } from "./program/expressions.js";
|
|
19
14
|
import { canonicalizeWorkflowWorkingDirectory, WorkflowSourceSemanticError } from "./source-semantics.js";
|
|
20
|
-
export { looksLikeGithubWorkflowSource } from "./github-yaml.js";
|
|
21
15
|
/** Compile by authoritative source extension. */
|
|
22
16
|
export function compileWorkflowSource(source, options) {
|
|
23
17
|
const extension = path.extname(options.path).toLowerCase();
|
|
24
18
|
const title = options.title ?? path.basename(options.path, path.extname(options.path));
|
|
25
|
-
if (extension === ".yml") {
|
|
26
|
-
try {
|
|
27
|
-
return { ok: true, plan: parseGithubWorkflowSource(source, { ...options, title }) };
|
|
28
|
-
}
|
|
29
|
-
catch (cause) {
|
|
30
|
-
if (cause instanceof WorkflowSourceFailure)
|
|
31
|
-
return { ok: false, errors: [cause.error] };
|
|
32
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
33
|
-
return { ok: false, errors: [{ code: "invalid-workflow-source", message, path: options.path, line: 1 }] };
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
19
|
if (extension !== ".md") {
|
|
37
20
|
return {
|
|
38
21
|
ok: false,
|
|
39
22
|
errors: [
|
|
40
23
|
{
|
|
41
24
|
code: "unsupported-workflow-extension",
|
|
42
|
-
message: `Workflow source ${options.path} must use .md
|
|
25
|
+
message: `Workflow source ${options.path} must use .md.`,
|
|
43
26
|
path: options.path,
|
|
44
27
|
line: 1,
|
|
45
28
|
},
|
|
@@ -74,8 +57,7 @@ export function compileWorkflowSource(source, options) {
|
|
|
74
57
|
}
|
|
75
58
|
/**
|
|
76
59
|
* The display text a step contributes to `show`, search hints, and the run
|
|
77
|
-
* spine: its authored prose, else what its target does.
|
|
78
|
-
* step with no section.
|
|
60
|
+
* spine: its authored prose, else what its target does.
|
|
79
61
|
*/
|
|
80
62
|
export function workflowStepInstructions(step) {
|
|
81
63
|
const spec = step.spec;
|
|
@@ -83,25 +65,9 @@ export function workflowStepInstructions(step) {
|
|
|
83
65
|
return "";
|
|
84
66
|
if (spec.instructions !== undefined)
|
|
85
67
|
return spec.instructions;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return `Invoke stored command ${action.ref}${action.arguments === undefined ? "" : " with arguments"}.`;
|
|
90
|
-
}
|
|
91
|
-
if (spec.commandMode === "literal")
|
|
92
|
-
return action.content;
|
|
93
|
-
return action.content.split(PORTABLE_ARGUMENTS_PLACEHOLDER).join(action.arguments ?? "");
|
|
94
|
-
}
|
|
95
|
-
if (spec.uses !== undefined)
|
|
96
|
-
return `Invoke local target ${spec.uses}.`;
|
|
97
|
-
return "";
|
|
98
|
-
}
|
|
99
|
-
/** A route step's deterministic one-line description of its branch table. */
|
|
100
|
-
export function routeDescription(route) {
|
|
101
|
-
const branches = Object.entries(route.when).map(([match, stepId]) => `"${match}" -> ${stepId}`);
|
|
102
|
-
if (route.defaultStepId !== undefined)
|
|
103
|
-
branches.push(`default -> ${route.defaultStepId}`);
|
|
104
|
-
return `Route on ${route.input}: ${branches.join(", ")}.`;
|
|
68
|
+
// A prose step is `akm/command` with its body as literal content.
|
|
69
|
+
const content = spec.with?.content;
|
|
70
|
+
return spec.uses === "akm/command" && typeof content === "string" ? content : "";
|
|
105
71
|
}
|
|
106
72
|
/** Cross-step reference validation plus the non-fatal advisories. Pure. */
|
|
107
73
|
export function checkWorkflowPlan(plan) {
|
|
@@ -136,40 +102,18 @@ export function checkWorkflowPlan(plan) {
|
|
|
136
102
|
const line = step.spec?.source.start ?? 1;
|
|
137
103
|
if (step.spec?.map)
|
|
138
104
|
check(step.spec.map.over, line, `Step "${step.stepId}" map.over`, true);
|
|
139
|
-
if (step.route)
|
|
140
|
-
check(step.route.input, line, `Step "${step.stepId}" route.input`, true);
|
|
141
105
|
for (const [index, reference] of (step.spec?.inputs ?? []).entries()) {
|
|
142
106
|
check(reference, line, `Step "${step.stepId}" inputs[${index}]`, false);
|
|
143
107
|
}
|
|
144
108
|
earlierStepIds.add(step.stepId);
|
|
145
109
|
}
|
|
146
|
-
// Outputs resolve at run completion, so they may name any declared step.
|
|
147
|
-
for (const [name, declaration] of Object.entries(plan.outputs ?? {})) {
|
|
148
|
-
const parsed = parseReference(declaration.from);
|
|
149
|
-
const label = `Output "${name}" from`;
|
|
150
|
-
if (!parsed.ok)
|
|
151
|
-
errors.push({ line: 1, message: `${label}: ${parsed.message}` });
|
|
152
|
-
else if (parsed.expr.kind === "param") {
|
|
153
|
-
errors.push({
|
|
154
|
-
line: 1,
|
|
155
|
-
message: `${label}: "${formatReference(parsed.expr)}" names a param, not a step output — an output projects a ` +
|
|
156
|
-
`STEP artifact, never a param. "outputs:" only names step outputs (steps.<id>.output...).`,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
else if (!allStepIds.has(parsed.expr.stepId)) {
|
|
160
|
-
errors.push({
|
|
161
|
-
line: 1,
|
|
162
|
-
message: `${label}: "${formatReference(parsed.expr)}" cannot be resolved — "${parsed.expr.stepId}" is not a step in this workflow.`,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
110
|
if (errors.length > 0)
|
|
167
111
|
return { ok: false, errors };
|
|
168
112
|
return { ok: true, warnings: workflowWarnings(plan) };
|
|
169
113
|
}
|
|
170
114
|
/**
|
|
171
115
|
* Advisories that never fail compilation or change the plan:
|
|
172
|
-
* A. a `params.<name>` reference (in `map.over
|
|
116
|
+
* A. a `params.<name>` reference (in `map.over`) to a param the
|
|
173
117
|
* document's `params:` block does not declare — a likely typo;
|
|
174
118
|
* B. `gate.max_loops` above 1 on an exec step, which is judged but never
|
|
175
119
|
* looped (a frozen argv cannot read the judge's feedback).
|
|
@@ -205,7 +149,6 @@ function workflowWarnings(plan) {
|
|
|
205
149
|
});
|
|
206
150
|
};
|
|
207
151
|
scan(step.spec?.map?.over, `Step "${step.stepId}" map.over`);
|
|
208
|
-
scan(step.route?.input, `Step "${step.stepId}" route.input`);
|
|
209
152
|
}
|
|
210
153
|
return warnings;
|
|
211
154
|
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* `with:` bindings against the child's `params:`; (2) derive the deterministic
|
|
9
9
|
* invocation key; (3) publish the child run idempotently; (4) drive it with
|
|
10
10
|
* the same engine as a top-level run unless it is already `blocked`/`failed`;
|
|
11
|
-
* (5) map the child's final status onto this unit's outcome
|
|
11
|
+
* (5) map the child's final status onto this unit's outcome; a completed
|
|
12
|
+
* child returns the output of its last step.
|
|
12
13
|
*
|
|
13
14
|
* `runWorkflowSteps` is reached through a lazy dynamic import: a static one
|
|
14
15
|
* would close the cycle native-executor -> child-workflow -> run-workflow ->
|
|
@@ -19,7 +20,6 @@ import { TransientError } from "../../core/errors.js";
|
|
|
19
20
|
import { withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
|
|
20
21
|
import { validateWorkflowParams } from "../ir/params.js";
|
|
21
22
|
import { canonicalPlanJson } from "../ir/plan-hash.js";
|
|
22
|
-
import { workflowRunExportedResult } from "../runtime/run-outputs.js";
|
|
23
23
|
import { frozenStepRows } from "../runtime/run-plan.js";
|
|
24
24
|
import { computeChildInvocationKey } from "./child-invocation.js";
|
|
25
25
|
/** The real engine, via a dynamic import (see the module doc). The caller re-reads the child row afterward. */
|
|
@@ -27,6 +27,25 @@ async function driveWithRealEngine(options) {
|
|
|
27
27
|
const { runWorkflowSteps } = await import("./run-workflow.js");
|
|
28
28
|
await runWorkflowSteps(options);
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* A completed child's result: the artifact of its last step (a step with no
|
|
32
|
+
* recorded evidence yields `null`). Read from the persisted step rows, so a
|
|
33
|
+
* resumed parent sees the same value as the live one.
|
|
34
|
+
*/
|
|
35
|
+
async function childFinalOutput(childRunId) {
|
|
36
|
+
const steps = await withWorkflowRunsRepo((repo) => repo.getStepsForRun(childRunId));
|
|
37
|
+
const evidenceJson = steps[steps.length - 1]?.evidence_json;
|
|
38
|
+
if (!evidenceJson)
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
const evidence = JSON.parse(evidenceJson);
|
|
42
|
+
// Same projection as `steps.<id>.output` (step-work.ts `projectStepOutput`).
|
|
43
|
+
return Object.hasOwn(evidence, "output") ? evidence.output : evidence;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
30
49
|
function errorMessage(err) {
|
|
31
50
|
return err instanceof Error ? err.message : String(err);
|
|
32
51
|
}
|
|
@@ -218,7 +237,7 @@ export async function driveChildWorkflowUnit(input) {
|
|
|
218
237
|
return {
|
|
219
238
|
unitId: request.unitId,
|
|
220
239
|
ok: true,
|
|
221
|
-
result:
|
|
240
|
+
result: await childFinalOutput(finalRow.id),
|
|
222
241
|
childRun: childRunSummary,
|
|
223
242
|
};
|
|
224
243
|
case "failed": {
|
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
* gate loops journal under `~r<n>` / `~l<n>`.
|
|
21
21
|
* - `onError: "fail"` fails on any unit failure, `"continue"` lets the gate
|
|
22
22
|
* decide; `retry: { max, on }` re-dispatches listed failure reasons.
|
|
23
|
-
* - `isolation: worktree` runs each attempt of an agent/sdk unit in a fresh
|
|
24
|
-
* detached worktree (removed when clean, kept when dirty); llm units reject it.
|
|
25
23
|
* - Run `budget` ceilings are consumed per actual dispatch and abort the step
|
|
26
24
|
* when crossed, regardless of `on_error`.
|
|
27
25
|
*/
|
|
@@ -52,9 +50,7 @@ import { scheduleUnits } from "./scheduler.js";
|
|
|
52
50
|
// path. This module dispatches; step-work.ts owns the pure decisions.
|
|
53
51
|
import { clip, computeStepWorkList, reduceEmptyStep, reduceStepOutcomes, stepOutputsFromEvidence, unitOutcomeFromRow, } from "./step-work.js";
|
|
54
52
|
import { dispatchWorkflowExecution, } from "./unit-dispatch.js";
|
|
55
|
-
import { cleanupFrozenScript, frozenScriptCommand, materializeFrozenScript } from "../../tasks/frozen-script.js";
|
|
56
53
|
import { enqueueUnitWrite } from "./unit-writer.js";
|
|
57
|
-
import { assertGitWorkTree, cleanupUnitWorktree, createUnitWorktree } from "./worktree.js";
|
|
58
54
|
/**
|
|
59
55
|
* Per-step dispatch budget against the run's `budget` ceilings, seeded from
|
|
60
56
|
* the journal and consumed per actual dispatch (never by a reused row).
|
|
@@ -171,7 +167,7 @@ function openDispatchBudget(ctx, dispatched) {
|
|
|
171
167
|
}
|
|
172
168
|
/** Resolve the live-at-dispatch prerequisites once, after durable-row reuse is known. */
|
|
173
169
|
async function prepareStepDispatchPrerequisites(input) {
|
|
174
|
-
const { plan,
|
|
170
|
+
const { plan, workUnits, willDispatch, dispatched } = input;
|
|
175
171
|
let env;
|
|
176
172
|
let sensitiveValues;
|
|
177
173
|
const frozenEnvironment = workUnits[0]?.environment;
|
|
@@ -195,35 +191,10 @@ async function prepareStepDispatchPrerequisites(input) {
|
|
|
195
191
|
};
|
|
196
192
|
}
|
|
197
193
|
}
|
|
198
|
-
let worktreeBase;
|
|
199
|
-
if (willDispatch && template.isolation === "worktree") {
|
|
200
|
-
if (template.frozenTarget.kind === "command" && template.frozenTarget.runner.kind === "llm") {
|
|
201
|
-
return {
|
|
202
|
-
ok: false,
|
|
203
|
-
result: failedStep(dispatched, `Step "${plan.stepId}" declares isolation: worktree on an llm unit — the llm runner has no ` +
|
|
204
|
-
`working directory to isolate. Use the agent or sdk runner for worktree-isolated units.`),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
const target = workUnits[0]?.frozenTarget;
|
|
208
|
-
const frozenCwd = target && "cwdIdentity" in target ? target.cwdIdentity : undefined;
|
|
209
|
-
if (frozenCwd)
|
|
210
|
-
assertFrozenDirectoryContained(frozenCwd);
|
|
211
|
-
const base = frozenCwd?.realRoot ?? ctx.workDir ?? process.cwd();
|
|
212
|
-
const preflightWorktree = ctx.preflightWorktree ?? assertGitWorkTree;
|
|
213
|
-
const gitError = preflightWorktree(base);
|
|
214
|
-
if (gitError !== undefined) {
|
|
215
|
-
return {
|
|
216
|
-
ok: false,
|
|
217
|
-
result: failedStep(dispatched, `Step "${plan.stepId}" cannot use isolation: worktree: ${gitError}`),
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
worktreeBase = base;
|
|
221
|
-
}
|
|
222
194
|
return {
|
|
223
195
|
ok: true,
|
|
224
196
|
...(env ? { env } : {}),
|
|
225
197
|
...(sensitiveValues ? { sensitiveValues } : {}),
|
|
226
|
-
...(worktreeBase !== undefined ? { worktreeBase } : {}),
|
|
227
198
|
};
|
|
228
199
|
}
|
|
229
200
|
async function executeStepPlanInConnection(plan, ctx) {
|
|
@@ -251,32 +222,25 @@ async function executeStepPlanInConnection(plan, ctx) {
|
|
|
251
222
|
const dispatcher = ctx.dispatcher ?? defaultUnitDispatcher;
|
|
252
223
|
// Load journaled rows first: a completed unit is reused, never re-dispatched.
|
|
253
224
|
const completedRows = indexCompletedRows(await withWorkflowRunsRepo((repo) => repo.getUnitsForStep(ctx.runId, plan.stepId)));
|
|
254
|
-
// Env resolution
|
|
255
|
-
//
|
|
256
|
-
//
|
|
225
|
+
// Env resolution runs only when some unit will dispatch, so a fully-journaled
|
|
226
|
+
// step resumes even if an env asset is gone. Every unit is classified once;
|
|
227
|
+
// the preflight and dispatch share it.
|
|
257
228
|
const reuseDecisions = workUnits.map((unit) => classifyUnitReuse(unit, completedRows));
|
|
258
229
|
const willDispatch = reuseDecisions.some((decision) => decision.kind === "dispatch");
|
|
259
230
|
const prerequisites = await prepareStepDispatchPrerequisites({
|
|
260
231
|
plan,
|
|
261
|
-
template,
|
|
262
232
|
workUnits,
|
|
263
|
-
ctx,
|
|
264
233
|
willDispatch,
|
|
265
234
|
dispatched,
|
|
266
235
|
});
|
|
267
236
|
if (!prerequisites.ok)
|
|
268
237
|
return prerequisites.result;
|
|
269
|
-
const { env, sensitiveValues
|
|
238
|
+
const { env, sensitiveValues } = prerequisites;
|
|
270
239
|
// Budget ceilings + lifetime-cap accounting, and the budget-chained abort
|
|
271
240
|
// signal they trip. Extracted verbatim (behavior-identical) — see
|
|
272
241
|
// {@link openDispatchBudget}.
|
|
273
242
|
const { signal, budget, unchainSignal } = openDispatchBudget(ctx, dispatched);
|
|
274
243
|
let outcomes;
|
|
275
|
-
// Worktree removals started by finished units and awaited below, before this
|
|
276
|
-
// step reports anything. Cleanup is best-effort, but "the step resolved" must
|
|
277
|
-
// still mean "its clean worktrees are gone" — only the WAIT moves off the
|
|
278
|
-
// unit's scheduler slot, not the guarantee.
|
|
279
|
-
const pendingWorktreeCleanups = [];
|
|
280
244
|
const frozenTargetConcurrency = template.frozenTarget.kind === "command" ? template.frozenTarget.concurrency : undefined;
|
|
281
245
|
try {
|
|
282
246
|
outcomes = await scheduleUnits(workUnits, (workUnit, index) => runUnit({
|
|
@@ -284,12 +248,10 @@ async function executeStepPlanInConnection(plan, ctx) {
|
|
|
284
248
|
workUnit,
|
|
285
249
|
env,
|
|
286
250
|
sensitiveValues,
|
|
287
|
-
...(worktreeBase !== undefined ? { worktreeBase } : {}),
|
|
288
251
|
ctx,
|
|
289
252
|
signal,
|
|
290
253
|
dispatcher,
|
|
291
254
|
reuse: reuseDecisions[index],
|
|
292
|
-
pendingWorktreeCleanups,
|
|
293
255
|
budget,
|
|
294
256
|
}), {
|
|
295
257
|
concurrency: workList.list.concurrency,
|
|
@@ -300,9 +262,6 @@ async function executeStepPlanInConnection(plan, ctx) {
|
|
|
300
262
|
}
|
|
301
263
|
finally {
|
|
302
264
|
unchainSignal?.();
|
|
303
|
-
// The barrier. `allSettled` because each task already swallowed its own
|
|
304
|
-
// failure into a warn — nothing here can fail the step.
|
|
305
|
-
await Promise.allSettled(pendingWorktreeCleanups);
|
|
306
265
|
}
|
|
307
266
|
// Capture live-only diagnostics BEFORE any hard reduction replaces the unit
|
|
308
267
|
// list with a failed-step envelope. Budget/cap and journal-write failures
|
|
@@ -358,11 +317,9 @@ async function runUnit(input) {
|
|
|
358
317
|
prompt,
|
|
359
318
|
frozenTarget: workUnit.frozenTarget,
|
|
360
319
|
...(workUnit.execContext ? { execContext: workUnit.execContext } : {}),
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
// it: handing an agent unit a cwd it never had would change harness
|
|
365
|
-
// behavior, and the agent path already takes its cwd from its profile.
|
|
320
|
+
// An exec unit spawns in the engine invocation's working directory. Only
|
|
321
|
+
// exec units get it: handing an agent unit a cwd it never had would change
|
|
322
|
+
// harness behavior, and the agent path already takes its cwd from its profile.
|
|
366
323
|
...(workUnit.frozenTarget.kind !== "command" && ctx.workDir !== undefined ? { cwd: ctx.workDir } : {}),
|
|
367
324
|
timeoutMs: workUnit.timeoutMs,
|
|
368
325
|
...(workUnit.schema ? { schema: workUnit.schema } : {}),
|
|
@@ -370,7 +327,7 @@ async function runUnit(input) {
|
|
|
370
327
|
...(sensitiveValues ? { sensitiveValues } : {}),
|
|
371
328
|
...(input.signal ? { signal: input.signal } : {}),
|
|
372
329
|
// F-1: forwarded to exec-unit.ts's childEnv for a
|
|
373
|
-
// "
|
|
330
|
+
// "shell" unit, and to dispatchWorkflowExecution's
|
|
374
331
|
// runExecution eventSource option (unit-dispatch.ts)
|
|
375
332
|
// for a "command" unit — both arms observe it.
|
|
376
333
|
...(ctx.eventSource !== undefined ? { eventSource: ctx.eventSource } : {}),
|
|
@@ -424,8 +381,6 @@ async function runUnit(input) {
|
|
|
424
381
|
request: { ...request, unitId: attemptId },
|
|
425
382
|
attemptId,
|
|
426
383
|
inputHash,
|
|
427
|
-
...(input.worktreeBase !== undefined ? { worktreeBase: input.worktreeBase } : {}),
|
|
428
|
-
pendingWorktreeCleanups: input.pendingWorktreeCleanups,
|
|
429
384
|
});
|
|
430
385
|
// Attempts use `~r<n>` journal suffixes while durable step evidence remains
|
|
431
386
|
// attached to the content-derived base identity.
|
|
@@ -463,30 +418,7 @@ function journaledUnitResultJson(outcome) {
|
|
|
463
418
|
return null;
|
|
464
419
|
return JSON.stringify(clip(parts.join("\n--- unit output ---\n"), WORKFLOW_UNIT_DIAGNOSTIC_CLIP));
|
|
465
420
|
}
|
|
466
|
-
async function
|
|
467
|
-
if (input.worktreeBase === undefined)
|
|
468
|
-
return { ok: true, request: input.request };
|
|
469
|
-
const created = await createUnitWorktree(input.worktreeBase, input.ctx.runId, input.attemptId,
|
|
470
|
-
// A child-workflow target has no commit of its own (its prepared worktree goes unused).
|
|
471
|
-
input.workUnit.frozenTarget.kind === "child-workflow" ? undefined : input.workUnit.frozenTarget.gitCommitOid);
|
|
472
|
-
if (created.preservedLeftover !== undefined) {
|
|
473
|
-
warn(`Workflow unit ${input.attemptId}: a previous attempt left uncollected work in its isolation worktree; ` +
|
|
474
|
-
`preserved at ${created.preservedLeftover}`);
|
|
475
|
-
}
|
|
476
|
-
if (!created.ok) {
|
|
477
|
-
return {
|
|
478
|
-
ok: false,
|
|
479
|
-
outcome: {
|
|
480
|
-
unitId: input.request.unitId,
|
|
481
|
-
ok: false,
|
|
482
|
-
failureReason: "worktree_failed",
|
|
483
|
-
error: created.error,
|
|
484
|
-
},
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
return { ok: true, request: { ...input.request, cwd: created.path }, worktreePath: created.path };
|
|
488
|
-
}
|
|
489
|
-
async function reserveJournaledDispatch(input, worktreePath, startedAt) {
|
|
421
|
+
async function reserveJournaledDispatch(input, startedAt) {
|
|
490
422
|
const { plan, workUnit, ctx, attemptId, inputHash } = input;
|
|
491
423
|
let durableAttempt;
|
|
492
424
|
await enqueueUnitWrite(async () => {
|
|
@@ -503,7 +435,6 @@ async function reserveJournaledDispatch(input, worktreePath, startedAt) {
|
|
|
503
435
|
engine: target.kind === "command" ? target.request.engine.name : null,
|
|
504
436
|
model: target.kind === "command" ? (target.request.model?.resolved ?? null) : null,
|
|
505
437
|
inputHash,
|
|
506
|
-
worktreePath: worktreePath ?? null,
|
|
507
438
|
now: startedAt,
|
|
508
439
|
}).attempt;
|
|
509
440
|
});
|
|
@@ -538,37 +469,14 @@ async function finishJournaledDispatch(input) {
|
|
|
538
469
|
}
|
|
539
470
|
}));
|
|
540
471
|
}
|
|
541
|
-
function queueAttemptWorktreeCleanup(input, worktreePath) {
|
|
542
|
-
if (worktreePath === undefined || input.worktreeBase === undefined)
|
|
543
|
-
return;
|
|
544
|
-
const worktreeBase = input.worktreeBase;
|
|
545
|
-
input.pendingWorktreeCleanups.push((async () => {
|
|
546
|
-
try {
|
|
547
|
-
const cleanup = await cleanupUnitWorktree(worktreeBase, worktreePath);
|
|
548
|
-
if (cleanup.dirty) {
|
|
549
|
-
warn(`Workflow unit ${input.attemptId} left uncommitted changes in its isolation worktree; retained at ${worktreePath}`);
|
|
550
|
-
}
|
|
551
|
-
else if (!cleanup.removed) {
|
|
552
|
-
warn(`Workflow unit ${input.attemptId}: could not clean up isolation worktree ${worktreePath}: ${cleanup.error}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
catch (err) {
|
|
556
|
-
warn(`Workflow unit ${input.attemptId}: could not clean up isolation worktree ${worktreePath}: ${message(err)}`);
|
|
557
|
-
}
|
|
558
|
-
})());
|
|
559
|
-
}
|
|
560
472
|
/** Journal one dispatch attempt: insert row, events, dispatch, finish row. */
|
|
561
473
|
async function dispatchJournaledAttempt(input) {
|
|
562
474
|
const { workUnit, ctx, dispatcher, attemptId } = input;
|
|
563
|
-
|
|
564
|
-
if (!prepared.ok)
|
|
565
|
-
return prepared.outcome;
|
|
566
|
-
let { request } = prepared;
|
|
567
|
-
const { worktreePath } = prepared;
|
|
475
|
+
let { request } = input;
|
|
568
476
|
const startedAt = new Date().toISOString();
|
|
569
477
|
let durableAttempt;
|
|
570
478
|
try {
|
|
571
|
-
durableAttempt = await reserveJournaledDispatch(input,
|
|
479
|
+
durableAttempt = await reserveJournaledDispatch(input, startedAt);
|
|
572
480
|
}
|
|
573
481
|
catch (err) {
|
|
574
482
|
// A failed dispatch-row insert means NOTHING dispatched (the row is the
|
|
@@ -576,8 +484,6 @@ async function dispatchJournaledAttempt(input) {
|
|
|
576
484
|
// letting the throw escape into the scheduler, where a swallowed worker
|
|
577
485
|
// error is indistinguishable from "never claimed" and used to be
|
|
578
486
|
// misreported as an aborted, never-dispatched unit.
|
|
579
|
-
if (worktreePath !== undefined && input.worktreeBase !== undefined)
|
|
580
|
-
await cleanupUnitWorktree(input.worktreeBase, worktreePath);
|
|
581
487
|
return {
|
|
582
488
|
unitId: request.unitId,
|
|
583
489
|
ok: false,
|
|
@@ -632,9 +538,6 @@ async function dispatchJournaledAttempt(input) {
|
|
|
632
538
|
catch (err) {
|
|
633
539
|
journalError = err;
|
|
634
540
|
}
|
|
635
|
-
// A clean worktree is removed, a dirty one kept and logged. Cleanup starts
|
|
636
|
-
// here and is awaited at the step barrier, never holding this scheduler slot.
|
|
637
|
-
queueAttemptWorktreeCleanup(input, worktreePath);
|
|
638
541
|
// A journal-write failure AFTER a successful dispatch is its own loud
|
|
639
542
|
// failure class: the unit's work ran (and may have succeeded), but its
|
|
640
543
|
// terminal state could not be recorded, so the row may be stuck `running`.
|
|
@@ -796,44 +699,6 @@ async function dispatchUnit(request, dispatcher) {
|
|
|
796
699
|
*/
|
|
797
700
|
export const defaultUnitDispatcher = async (request, feedback) => {
|
|
798
701
|
const frozenTarget = request.frozenTarget;
|
|
799
|
-
if (frozenTarget.kind === "script") {
|
|
800
|
-
assertFrozenDirectoryContained(frozenTarget.cwdIdentity);
|
|
801
|
-
const materialized = materializeFrozenScript({
|
|
802
|
-
sourceRef: frozenTarget.ref,
|
|
803
|
-
interpreter: frozenTarget.interpreter,
|
|
804
|
-
extension: frozenTarget.extension,
|
|
805
|
-
bytesBase64: frozenTarget.bytesBase64,
|
|
806
|
-
byteLength: frozenTarget.byteLength,
|
|
807
|
-
sha256: frozenTarget.contentHash,
|
|
808
|
-
});
|
|
809
|
-
try {
|
|
810
|
-
const command = frozenScriptCommand({
|
|
811
|
-
sourceRef: frozenTarget.ref,
|
|
812
|
-
interpreter: frozenTarget.interpreter,
|
|
813
|
-
extension: frozenTarget.extension,
|
|
814
|
-
bytesBase64: frozenTarget.bytesBase64,
|
|
815
|
-
byteLength: frozenTarget.byteLength,
|
|
816
|
-
sha256: frozenTarget.contentHash,
|
|
817
|
-
}, materialized.file);
|
|
818
|
-
return await runExecUnit({
|
|
819
|
-
unitId: request.unitId,
|
|
820
|
-
exec: {
|
|
821
|
-
...frozenTarget.exec,
|
|
822
|
-
command: command,
|
|
823
|
-
},
|
|
824
|
-
baseDir: request.cwd ?? frozenTarget.cwdIdentity.realCwd,
|
|
825
|
-
...(request.env ? { env: request.env } : {}),
|
|
826
|
-
...(request.execContext ? { context: request.execContext } : {}),
|
|
827
|
-
...(request.schema ? { hasOutputSchema: true } : {}),
|
|
828
|
-
timeoutMs: request.timeoutMs,
|
|
829
|
-
...(request.signal ? { signal: request.signal } : {}),
|
|
830
|
-
...(request.eventSource !== undefined ? { eventSource: request.eventSource } : {}),
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
finally {
|
|
834
|
-
cleanupFrozenScript(materialized);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
702
|
if (frozenTarget.kind === "shell") {
|
|
838
703
|
if (frozenTarget.cwdIdentity)
|
|
839
704
|
assertFrozenDirectoryContained(frozenTarget.cwdIdentity);
|