akm-cli 0.9.31 → 0.10.261002-beta
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 +420 -0
- package/STABILITY.md +99 -57
- 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 +15 -29
- package/dist/assets/hints/cli-hints-short.md +2 -2
- package/dist/assets/improve-strategies/default.json +3 -3
- package/dist/assets/prompts/consolidate-pair.md +9 -7
- package/dist/assets/stash-skeleton/README.md +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- 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/feedback-cli.js +75 -49
- 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/chunking.js +3 -10
- package/dist/commands/improve/consolidate/continuity-check.js +4 -4
- package/dist/commands/improve/consolidate/pair-pass.js +29 -10
- package/dist/commands/improve/consolidate.js +10 -65
- 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 +148 -21
- package/dist/commands/improve/eligibility.js +14 -1
- package/dist/commands/improve/extract-cli.js +1 -1
- package/dist/commands/improve/extract-prompt.js +17 -7
- package/dist/commands/improve/extract.js +5 -7
- package/dist/commands/improve/improve-cli.js +92 -106
- package/dist/commands/improve/improve-strategies.js +8 -12
- package/dist/commands/improve/improve.js +134 -52
- package/dist/commands/improve/loop-stages.js +18 -5
- 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 +29 -34
- package/dist/commands/improve/retrieval-queries.js +37 -0
- package/dist/commands/improve/retrieval-scope.js +2 -2
- package/dist/commands/improve/salience.js +2 -9
- package/dist/commands/improve/session-asset.js +4 -1
- package/dist/commands/improve/stage.js +23 -12
- package/dist/commands/lint/index.js +5 -20
- package/dist/commands/observability-cli.js +3 -9
- package/dist/commands/proposal/drain.js +43 -6
- package/dist/commands/proposal/proposal-cli.js +11 -31
- package/dist/commands/proposal/repository.js +20 -14
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -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 +6 -14
- 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 +10 -63
- 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/file-lock.js +2 -6
- package/dist/core/improve-result.js +13 -1
- package/dist/core/mutation-target.js +2 -2
- package/dist/core/paths.js +4 -0
- package/dist/core/recognition-util.js +4 -5
- package/dist/core/run-lock.js +1 -1
- package/dist/core/subprocess.js +49 -0
- 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/indexer.js +15 -6
- package/dist/indexer/passes/memory-inference.js +15 -6
- package/dist/indexer/search/db-search.js +49 -36
- package/dist/indexer/search/fts-query.js +0 -19
- package/dist/indexer/search/ranking.js +29 -1
- package/dist/indexer/usage/usage-events.js +21 -16
- 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 +254 -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 +15745 -16711
- package/dist/scripts/akm-migrate.js +16318 -17280
- 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/sources/providers/git-stash.js +113 -16
- package/dist/storage/repositories/index-utility-repository.js +2 -32
- package/dist/storage/repositories/index-vec-repository.js +4 -0
- 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/run-command-task.js +9 -1
- package/dist/tasks/run/run-workflow-task.js +4 -3
- package/dist/tasks/run/task-history.js +0 -2
- package/dist/tasks/run/task-log.js +38 -1
- 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/0.10-process-rules.md +827 -0
- package/docs/architecture/adapters.md +17 -31
- package/docs/architecture/architecture.md +4 -5
- package/docs/architecture/improvement.md +115 -76
- package/docs/architecture/internals/improve-workflow.md +22 -16
- package/docs/architecture/internals/indexing.md +1 -1
- package/docs/architecture/internals/search.md +1 -2
- package/docs/architecture/internals/storage-locations.md +6 -6
- 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 +21 -30
- 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/bundles.md +3 -1
- 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 +10 -9
- package/docs/guides/recipes/headless-install.md +2 -2
- package/docs/guides/run-workflows.md +1 -1
- package/docs/guides/scheduling.md +9 -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 +33 -1
- package/docs/migration/README.md +2 -0
- package/docs/migration/release-notes/0.10.0.md +201 -0
- package/docs/migration/release-notes/README.md +1 -0
- package/docs/migration/v0.9-to-v0.10.md +507 -0
- 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 +160 -188
- package/docs/reference/configuration.md +101 -20
- package/docs/reference/data-and-telemetry.md +2 -2
- 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/prompts/retrieval-relevance-judge.md +0 -6
- 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/improve/retrieval-gate.js +0 -132
- package/dist/commands/tasks/validate.js +0 -176
- package/dist/core/config/legacy-source-shape-shim.js +0 -117
- package/dist/indexer/feedback/utility-policy.js +0 -75
- 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
|
@@ -109,11 +109,10 @@ function resolveTaskInputBindings(bindings, stepId, scope) {
|
|
|
109
109
|
*/
|
|
110
110
|
export function computeStepWorkList(plan, input) {
|
|
111
111
|
const root = plan.root;
|
|
112
|
-
// Route-only steps (YAML `route:`) carry no execution subgraph.
|
|
113
112
|
if (!root) {
|
|
114
113
|
return {
|
|
115
114
|
ok: false,
|
|
116
|
-
error: `Step "${plan.stepId}" has no execution subgraph
|
|
115
|
+
error: `Step "${plan.stepId}" has no execution subgraph; the native executor cannot dispatch it.`,
|
|
117
116
|
};
|
|
118
117
|
}
|
|
119
118
|
const template = root.kind === "map" ? root.template : root;
|
|
@@ -121,7 +120,7 @@ export function computeStepWorkList(plan, input) {
|
|
|
121
120
|
const scope = { params: input.params, stepOutputs: input.stepOutputs };
|
|
122
121
|
// Instructions are ALWAYS the step's body prose, byte-exact — never
|
|
123
122
|
// templated, never scanned for reference syntax (workflow-format-
|
|
124
|
-
// unification, spec §2.3). Only `map.over` / `
|
|
123
|
+
// unification, spec §2.3). Only `map.over` / `inputs[]`
|
|
125
124
|
// carry the closed reference grammar.
|
|
126
125
|
// Resolve the step's declared `inputs:` ONCE (shared by every unit in this
|
|
127
126
|
// step — map items differ, declared inputs do not): prior-step artifacts
|
|
@@ -177,7 +176,7 @@ export function computeStepWorkList(plan, input) {
|
|
|
177
176
|
: [unitIdFor(template.id, undefined, false, true)];
|
|
178
177
|
const gateLoop = input.gateLoop ?? 1;
|
|
179
178
|
const target = template.frozenTarget;
|
|
180
|
-
const frozenExec = target.kind === "shell"
|
|
179
|
+
const frozenExec = target.kind === "shell" ? target.exec : undefined;
|
|
181
180
|
const runner = target.kind === "command" ? target.runner.kind : "exec";
|
|
182
181
|
// Taken verbatim from the frozen plan, resolved once at freeze (an exec
|
|
183
182
|
// unit's on its exec spec). A frozen `null` means genuinely unbounded
|
|
@@ -271,7 +270,6 @@ function buildStepWorkUnit(ctx, unitId, item, index) {
|
|
|
271
270
|
...(template.schema ? { schema: template.schema } : {}),
|
|
272
271
|
...(template.retry ? { retry: template.retry } : {}),
|
|
273
272
|
onError: template.onError,
|
|
274
|
-
...(template.isolation ? { isolation: template.isolation } : {}),
|
|
275
273
|
// the SAME resolved `with:` bindings `taskInputs` already
|
|
276
274
|
// carries, exposed under the name `child-workflow.ts`'s drive contract
|
|
277
275
|
// reads. Absent (never `{}`) when the step binds nothing.
|
|
@@ -330,7 +328,8 @@ function computeUnitInputHash(ctx, item) {
|
|
|
330
328
|
frozenTarget: ctx.target,
|
|
331
329
|
environment: ctx.template.environment,
|
|
332
330
|
schema: ctx.template.schema ?? null,
|
|
333
|
-
isolation:
|
|
331
|
+
// Retired `isolation: worktree` field; kept constant so in-flight runs keep their journaled input hashes.
|
|
332
|
+
isolation: "none",
|
|
334
333
|
...(ctx.taskInputs !== undefined ? { taskInputs: ctx.taskInputs } : {}),
|
|
335
334
|
...(ctx.input.gateFeedback ? { gateFeedback: ctx.input.gateFeedback } : {}),
|
|
336
335
|
}))
|
|
@@ -422,7 +421,7 @@ function stepTemplate(stepPlan) {
|
|
|
422
421
|
}
|
|
423
422
|
/**
|
|
424
423
|
* The step ids another step can still read (named by `inputs[]`, `map.over`,
|
|
425
|
-
*
|
|
424
|
+
* — the whole reference surface). A step outside this set
|
|
426
425
|
* need not keep its artifact in memory once journaled. Derived from the plan alone.
|
|
427
426
|
*/
|
|
428
427
|
export function referencedStepIds(plan) {
|
|
@@ -437,8 +436,6 @@ export function referencedStepIds(plan) {
|
|
|
437
436
|
note(step.root.over);
|
|
438
437
|
for (const reference of stepTemplate(step)?.inputs ?? [])
|
|
439
438
|
note(reference);
|
|
440
|
-
if (step.route)
|
|
441
|
-
note(step.route.input);
|
|
442
439
|
}
|
|
443
440
|
return referenced;
|
|
444
441
|
}
|
|
@@ -830,148 +827,6 @@ export async function journalGateEvaluationFinish(gate, errored, rejection) {
|
|
|
830
827
|
throw new UsageError(`Gate ${unitId} was already finished; refusing a duplicate terminal write.`);
|
|
831
828
|
}
|
|
832
829
|
}
|
|
833
|
-
/**
|
|
834
|
-
* Resolve a route's input (a single whole-value reference string — `params.x` or
|
|
835
|
-
* `steps.<id>.output…`, with no `${{ }}` delimiters) and pick the branch. No
|
|
836
|
-
* ambient key search. Only primitive values route; the comparison is exact
|
|
837
|
-
* string equality against the declared `when:` matches.
|
|
838
|
-
*/
|
|
839
|
-
export function evaluateRoute(route, scope) {
|
|
840
|
-
const resolved = resolveStepReference(route.input, scope);
|
|
841
|
-
if (!resolved.ok) {
|
|
842
|
-
return { ok: false, error: `route input ${route.input} failed to resolve: ${resolved.error.message}` };
|
|
843
|
-
}
|
|
844
|
-
const value = resolved.value;
|
|
845
|
-
if (typeof value === "object" && value !== null) {
|
|
846
|
-
return {
|
|
847
|
-
ok: false,
|
|
848
|
-
error: `route input ${route.input} resolved to a non-primitive value; branches match on strings/numbers/booleans.`,
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
const valueString = typeof value === "string" ? value : String(value);
|
|
852
|
-
// Own-property check: `when` is author-controlled, and a value such as
|
|
853
|
-
// "constructor" must not resolve through Object.prototype.
|
|
854
|
-
const selected = Object.hasOwn(route.when, valueString) ? route.when[valueString] : route.defaultStepId;
|
|
855
|
-
if (!selected) {
|
|
856
|
-
return {
|
|
857
|
-
ok: false,
|
|
858
|
-
error: `value "${valueString}" matched no "when:" branch and the route declares no default.`,
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
return { ok: true, value: valueString, selected };
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Cascade a SKIPPED router: it never evaluated its route, so every declared
|
|
865
|
-
* target (branches + default) is marked skip-on-reach unless an earlier router
|
|
866
|
-
* already claimed it. Shared by the live skip path and the journal replay.
|
|
867
|
-
*/
|
|
868
|
-
export function cascadeSkippedRouter(route, routerId, routeUnselected) {
|
|
869
|
-
const targets = [...Object.values(route.when), ...(route.defaultStepId ? [route.defaultStepId] : [])];
|
|
870
|
-
for (const target of targets) {
|
|
871
|
-
if (!routeUnselected.has(target)) {
|
|
872
|
-
routeUnselected.set(target, { router: routerId, selected: null });
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Record one router's decision in the skip bookkeeping: the selected target is
|
|
878
|
-
* protected, every other declared target (branches + default) is marked
|
|
879
|
-
* skip-on-reach unless an earlier router already claimed it. Shared by the live
|
|
880
|
-
* evaluation path and the journal replay.
|
|
881
|
-
*/
|
|
882
|
-
export function applyRouteDecision(route, routerId, selected, routeSelected, routeUnselected) {
|
|
883
|
-
routeSelected.add(selected);
|
|
884
|
-
const targets = [...Object.values(route.when), ...(route.defaultStepId ? [route.defaultStepId] : [])];
|
|
885
|
-
for (const target of targets) {
|
|
886
|
-
if (target !== selected && !routeUnselected.has(target)) {
|
|
887
|
-
routeUnselected.set(target, { router: routerId, selected });
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* The `stepOutputs` scope a route resolves against: every prior step's recorded
|
|
893
|
-
* evidence plus the just-finished step's fresh evidence — each projected
|
|
894
|
-
* through {@link projectStepOutput}. Same projection as unit templates, so the
|
|
895
|
-
* two scopes cannot drift.
|
|
896
|
-
*/
|
|
897
|
-
export function routeStepOutputs(evidence, currentStepId, currentEvidence) {
|
|
898
|
-
const outputs = {};
|
|
899
|
-
for (const [stepId, stepEvidence] of Object.entries(evidence)) {
|
|
900
|
-
if (stepEvidence !== undefined)
|
|
901
|
-
outputs[stepId] = projectStepOutput(stepEvidence);
|
|
902
|
-
}
|
|
903
|
-
outputs[currentStepId] = projectStepOutput(currentEvidence);
|
|
904
|
-
return outputs;
|
|
905
|
-
}
|
|
906
|
-
/** The `selected` target journaled on a route step's evidence, if well-formed. */
|
|
907
|
-
function journaledRouteSelection(evidence) {
|
|
908
|
-
const route = evidence?.route;
|
|
909
|
-
if (typeof route !== "object" || route === null || Array.isArray(route))
|
|
910
|
-
return undefined;
|
|
911
|
-
const selected = route.selected;
|
|
912
|
-
return typeof selected === "string" && selected !== "" ? selected : undefined;
|
|
913
|
-
}
|
|
914
|
-
/** The set of steps a route may legally select: its `when` branches + default. */
|
|
915
|
-
function routeTargets(route) {
|
|
916
|
-
return new Set([...Object.values(route.when), ...(route.defaultStepId ? [route.defaultStepId] : [])]);
|
|
917
|
-
}
|
|
918
|
-
/** A journaled route decision must name a target the route declares; anything else fails loudly. */
|
|
919
|
-
function assertRouteTargetDeclared(route, stepId, selected, runId) {
|
|
920
|
-
const targets = routeTargets(route);
|
|
921
|
-
if (!targets.has(selected)) {
|
|
922
|
-
throw new UsageError(`Workflow run ${runId} has a completed route step "${stepId}" whose journaled route decision selected ` +
|
|
923
|
-
`"${selected}", which is not a declared branch or default target of the route (valid targets: ` +
|
|
924
|
-
`${[...targets].join(", ") || "(none)"}). The route evidence was corrupted or manually edited — refusing to ` +
|
|
925
|
-
`apply a bogus route decision that would skip the real branch targets. Start a new run.`);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Replay journaled route decisions into the skip bookkeeping (resume path).
|
|
930
|
-
* For every COMPLETED route step of the frozen plan, in spine order: the
|
|
931
|
-
* journaled decision wins; else a re-derivation from the frozen plan +
|
|
932
|
-
* journaled evidence; else fail loudly. A SKIPPED route step cascades its
|
|
933
|
-
* targets into the skip set exactly as on the live path.
|
|
934
|
-
*/
|
|
935
|
-
export function seedJournaledRouteDecisions(plan, state, routeSelected, routeUnselected) {
|
|
936
|
-
const evidence = {};
|
|
937
|
-
for (const s of state.workflow.steps)
|
|
938
|
-
evidence[s.id] = s.evidence;
|
|
939
|
-
for (const stepPlan of plan.steps) {
|
|
940
|
-
if (!stepPlan.route)
|
|
941
|
-
continue;
|
|
942
|
-
const stepState = state.workflow.steps.find((s) => s.id === stepPlan.stepId);
|
|
943
|
-
if (!stepState)
|
|
944
|
-
continue;
|
|
945
|
-
if (stepState.status === "skipped") {
|
|
946
|
-
cascadeSkippedRouter(stepPlan.route, stepPlan.stepId, routeUnselected);
|
|
947
|
-
continue;
|
|
948
|
-
}
|
|
949
|
-
if (stepState.status !== "completed")
|
|
950
|
-
continue;
|
|
951
|
-
let selected = journaledRouteSelection(stepState.evidence);
|
|
952
|
-
if (selected !== undefined) {
|
|
953
|
-
// a stored decision must name a declared target — a bogus one
|
|
954
|
-
// (tampered/hand-edited evidence) fails loudly rather than seeding a skip
|
|
955
|
-
// set that buries the real branches.
|
|
956
|
-
assertRouteTargetDeclared(stepPlan.route, stepPlan.stepId, selected, state.run.id);
|
|
957
|
-
}
|
|
958
|
-
if (selected === undefined) {
|
|
959
|
-
const scope = {
|
|
960
|
-
params: state.run.params ?? {},
|
|
961
|
-
stepOutputs: routeStepOutputs(evidence, stepPlan.stepId, stepState.evidence ?? {}),
|
|
962
|
-
};
|
|
963
|
-
const decision = evaluateRoute(stepPlan.route, scope);
|
|
964
|
-
if (decision.ok)
|
|
965
|
-
selected = decision.selected;
|
|
966
|
-
}
|
|
967
|
-
if (selected === undefined) {
|
|
968
|
-
throw new UsageError(`Workflow run ${state.run.id} has a completed route step "${stepPlan.stepId}" with no journaled route ` +
|
|
969
|
-
`decision, and the decision cannot be re-derived from the journaled evidence. Refusing to guess which ` +
|
|
970
|
-
`branch was selected. The run journal is inconsistent; abandon this run and start a new one.`);
|
|
971
|
-
}
|
|
972
|
-
applyRouteDecision(stepPlan.route, stepPlan.stepId, selected, routeSelected, routeUnselected);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
830
|
/**
|
|
976
831
|
* The blocked-step notes for a verifier-infrastructure failure (bug: judge
|
|
977
832
|
* outage must not burn the gate budget). Shared by every judge-failure path —
|
|
@@ -1050,8 +905,6 @@ async function blockFinalizedStepForChildWorkflow(input, childBlocked) {
|
|
|
1050
905
|
* Perform one completion attempt for an executed step:
|
|
1051
906
|
* - a hard unit failure fails the step (a retryable artifact-schema mismatch
|
|
1052
907
|
* with loops left returns `retry` without a gate row);
|
|
1053
|
-
* - a route decision is evaluated, journaled on the evidence, and applied to
|
|
1054
|
-
* the skip bookkeeping; an unroutable value fails the step;
|
|
1055
908
|
* - the gate judges a summary built from the promoted artifact: a rejection
|
|
1056
909
|
* returns `retry` (loops left) or `gate-exhausted`, a pass `advanced`;
|
|
1057
910
|
* - a judge infrastructure failure is not a verdict: it blocks the step for
|
|
@@ -1093,34 +946,12 @@ export async function finalizeExecutedStep(input) {
|
|
|
1093
946
|
return blockFinalizedStep(input, "this step declares completion criteria but no verification judge is available " +
|
|
1094
947
|
"(the frozen plan resolves no judge — set workflow.judgeEngine, or restore the judge configuration)");
|
|
1095
948
|
}
|
|
1096
|
-
// Route evaluation BEFORE completion: an unroutable value is an
|
|
1097
|
-
// authoring/config failure that must fail the step deterministically.
|
|
1098
|
-
let summaryOverride;
|
|
1099
|
-
if (stepPlan.route) {
|
|
1100
|
-
const scope = {
|
|
1101
|
-
params: input.params,
|
|
1102
|
-
stepOutputs: routeStepOutputs(input.priorEvidence, stepId, result.evidence),
|
|
1103
|
-
};
|
|
1104
|
-
const decision = evaluateRoute(stepPlan.route, scope);
|
|
1105
|
-
if (!decision.ok) {
|
|
1106
|
-
const notes = `Step "${stepId}" route failed: ${decision.error}`;
|
|
1107
|
-
await completeWorkflowStep({ runId, stepId, status: "failed", notes, evidence: result.evidence });
|
|
1108
|
-
return { kind: "failed", summary: notes, routeFailure: true };
|
|
1109
|
-
}
|
|
1110
|
-
applyRouteDecision(stepPlan.route, stepId, decision.selected, input.routeSelected, input.routeUnselected);
|
|
1111
|
-
// Journal the decision on the evidence: resume replays it via
|
|
1112
|
-
// seedJournaledRouteDecisions, so the skip set survives re-invocation.
|
|
1113
|
-
result.evidence.route = { input: stepPlan.route.input, value: decision.value, selected: decision.selected };
|
|
1114
|
-
if (!stepPlan.root) {
|
|
1115
|
-
summaryOverride = `Step "${stepId}" routed on ${stepPlan.route.input}: value "${decision.value}" selected step "${decision.selected}".`;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
949
|
// Artifact-judging gate: a criteria-bearing executing step is judged on a
|
|
1119
950
|
// summary BUILT FROM the promoted artifact; everything else keeps the machine
|
|
1120
|
-
// summary
|
|
951
|
+
// summary.
|
|
1121
952
|
const summary = stepPlan.root && completionCriteria.length > 0
|
|
1122
953
|
? buildArtifactSummary(stepId, result.units, result.evidence)
|
|
1123
|
-
:
|
|
954
|
+
: result.summary;
|
|
1124
955
|
// Journal engine-driven judge calls as unit rows. With no criteria there is
|
|
1125
956
|
// no judge invocation or row; a criteria-bearing plan without a judge is a
|
|
1126
957
|
// configuration error rather than a silent bypass.
|
|
@@ -1233,7 +1064,7 @@ export async function finalizeExecutedStep(input) {
|
|
|
1233
1064
|
return blockFinalizedStep(input, judgeFailure ?? "the verification judge failed");
|
|
1234
1065
|
}
|
|
1235
1066
|
if (!rejection) {
|
|
1236
|
-
return { kind: "advanced"
|
|
1067
|
+
return { kind: "advanced" };
|
|
1237
1068
|
}
|
|
1238
1069
|
if (loopsRemaining) {
|
|
1239
1070
|
return { kind: "retry", gateFeedback: { feedback: rejection.feedback, missing: rejection.missing } };
|
|
@@ -73,16 +73,6 @@ export async function dispatchWorkflowExecution(request, feedback) {
|
|
|
73
73
|
...(notices ? { notices } : {}),
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
-
if (request.cwd && lowered.runner.kind === "llm") {
|
|
77
|
-
return {
|
|
78
|
-
ok: false,
|
|
79
|
-
text: "",
|
|
80
|
-
failureReason: "isolation_unsupported",
|
|
81
|
-
error: `unit ${JSON.stringify(request.unitId)} declares isolation: worktree but resolved to the "llm" runner, ` +
|
|
82
|
-
`which has no working directory to isolate. Use the agent or sdk runner for isolated units.`,
|
|
83
|
-
...(notices ? { notices } : {}),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
76
|
let result;
|
|
87
77
|
try {
|
|
88
78
|
const eventSource = forwardedDispatchEventSource(request);
|
|
@@ -12,23 +12,13 @@ import { UsageError } from "../../core/errors.js";
|
|
|
12
12
|
import { deriveInstallations } from "../../indexer/installations.js";
|
|
13
13
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
14
14
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
15
|
-
import { isInferredSecretName } from "../../tasks/log-redaction.js";
|
|
16
15
|
import { SECRET_TOKEN_RE } from "../exec/environment.js";
|
|
17
|
-
import { detectSecretShapedParams } from "../exec/param-secrets.js";
|
|
18
16
|
/**
|
|
19
|
-
* A step's frozen environment:
|
|
17
|
+
* A step's frozen environment: `pass_env` names, then
|
|
20
18
|
* one value-free descriptor per `unit.env` ref (owner, key set, secret-token
|
|
21
19
|
* names). Values are read at dispatch (`exec/environment.ts`), never frozen.
|
|
22
20
|
*/
|
|
23
21
|
export function freezeEnvironment(source, exec, context) {
|
|
24
|
-
const literals = Object.entries(source.env ?? {}).map(([name, raw]) => {
|
|
25
|
-
const value = String(raw);
|
|
26
|
-
// A literal is frozen into plan_json; a credential belongs in an env ref, as for task env.
|
|
27
|
-
if (isInferredSecretName(name) || detectSecretShapedParams({ [name]: value }).length > 0) {
|
|
28
|
-
throw new UsageError(`Workflow step ${source.id} env.${name} is a secret-shaped literal env value. Store credentials in an env asset (unit.env) rather than workflow source.`, "WORKFLOW_SOURCE_INVALID");
|
|
29
|
-
}
|
|
30
|
-
return Object.freeze({ kind: "literal", name, value });
|
|
31
|
-
});
|
|
32
22
|
const passThrough = (exec?.passEnv ?? []).map((name) => Object.freeze({ kind: "pass-through", name }));
|
|
33
23
|
const seen = new Set();
|
|
34
24
|
const envRefs = [];
|
|
@@ -62,7 +52,7 @@ export function freezeEnvironment(source, exec, context) {
|
|
|
62
52
|
precedence,
|
|
63
53
|
}));
|
|
64
54
|
}
|
|
65
|
-
return [...
|
|
55
|
+
return [...passThrough, ...envRefs];
|
|
66
56
|
}
|
|
67
57
|
/** Load a command or persona the workflow names, resolved through the workflow's own bundles. */
|
|
68
58
|
export async function workflowExecutionSource(ref, kind, context) {
|
|
@@ -143,39 +133,8 @@ function configuredOwner(bundle, config) {
|
|
|
143
133
|
adapter: component?.adapter ?? "akm",
|
|
144
134
|
};
|
|
145
135
|
}
|
|
146
|
-
const SCRIPT_EXTENSIONS = [
|
|
147
|
-
"",
|
|
148
|
-
".sh",
|
|
149
|
-
".ts",
|
|
150
|
-
".js",
|
|
151
|
-
".py",
|
|
152
|
-
".rb",
|
|
153
|
-
".go",
|
|
154
|
-
".pl",
|
|
155
|
-
".php",
|
|
156
|
-
".lua",
|
|
157
|
-
".r",
|
|
158
|
-
".swift",
|
|
159
|
-
".kt",
|
|
160
|
-
".kts",
|
|
161
|
-
".ps1",
|
|
162
|
-
".cmd",
|
|
163
|
-
".bat",
|
|
164
|
-
];
|
|
165
136
|
function assetExtensions(type) {
|
|
166
|
-
if (type === "script")
|
|
167
|
-
return SCRIPT_EXTENSIONS;
|
|
168
137
|
if (type === "env")
|
|
169
138
|
return ["", ".env"];
|
|
170
|
-
return ["", ".md"
|
|
171
|
-
}
|
|
172
|
-
export function qualifyRef(ref, plural, asset, config) {
|
|
173
|
-
const parsed = parseBundleRef(ref);
|
|
174
|
-
if (parsed.bundle)
|
|
175
|
-
return ref;
|
|
176
|
-
const bundle = parseBundleRef(asset.ref).bundle ?? config.defaultBundle;
|
|
177
|
-
if (!bundle)
|
|
178
|
-
throw new UsageError(`Workflow ref ${ref} has no owning bundle.`, "WORKFLOW_SOURCE_INVALID");
|
|
179
|
-
const concept = parsed.conceptId.startsWith(`${plural}/`) ? parsed.conceptId : `${plural}/${parsed.conceptId}`;
|
|
180
|
-
return makeBundleRef(bundle, concept);
|
|
139
|
+
return ["", ".md"];
|
|
181
140
|
}
|
|
@@ -17,15 +17,10 @@ import { checkWorkflowPlan } from "../compile.js";
|
|
|
17
17
|
import { defaultMapConcurrency, workflowMaxConcurrency } from "../concurrency-policy.js";
|
|
18
18
|
import { canonicalJson } from "../ir/plan-hash.js";
|
|
19
19
|
import { WORKFLOW_PLAN_VERSION, } from "../plan.js";
|
|
20
|
-
import { classifyWorkflowStepUses } from "../source-semantics.js";
|
|
21
|
-
import { assertChildOutputReferences } from "./child-output-references.js";
|
|
22
|
-
import { qualifyRef } from "./environment.js";
|
|
23
20
|
import { baseUnitOf } from "./step-values.js";
|
|
24
21
|
import { childWorkflowDispatch } from "./targets/child-workflow.js";
|
|
25
22
|
import { commandDispatch, commandResult } from "./targets/command.js";
|
|
26
|
-
import { directScript } from "./targets/script.js";
|
|
27
23
|
import { directShell } from "./targets/shell.js";
|
|
28
|
-
import { taskDispatch } from "./targets/task.js";
|
|
29
24
|
/** Resolve and freeze one workflow (and, recursively, every child it composes). */
|
|
30
25
|
export async function freezeWorkflow(asset, config, refPath = [asset.ref]) {
|
|
31
26
|
const compiled = asset.plan;
|
|
@@ -45,12 +40,9 @@ export async function freezeWorkflow(asset, config, refPath = [asset.ref]) {
|
|
|
45
40
|
for (const step of compiled.steps) {
|
|
46
41
|
const { spec, ...frozenStep } = step;
|
|
47
42
|
const source = { ...(spec ?? { source: { path: asset.path, start: 1, end: 1 } }), id: step.stepId };
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
engineAnnouncement ??= resolved.engineAnnouncement;
|
|
52
|
-
root = frozenRoot(step.stepId, source, resolved, compiled, config);
|
|
53
|
-
}
|
|
43
|
+
const resolved = await resolveStep(source, context);
|
|
44
|
+
engineAnnouncement ??= resolved.engineAnnouncement;
|
|
45
|
+
const root = frozenRoot(step.stepId, source, resolved, compiled, config);
|
|
54
46
|
let frozenJudge = null;
|
|
55
47
|
if (step.gate.criteria.length > 0) {
|
|
56
48
|
const judge = resolveJudge(source, step.gate.criteria[0] ?? "", context);
|
|
@@ -64,16 +56,14 @@ export async function freezeWorkflow(asset, config, refPath = [asset.ref]) {
|
|
|
64
56
|
"for `akm workflow resume` once it is reached.");
|
|
65
57
|
}
|
|
66
58
|
}
|
|
67
|
-
steps.push({ ...frozenStep,
|
|
59
|
+
steps.push({ ...frozenStep, root, gate: { ...step.gate, frozenJudge } });
|
|
68
60
|
}
|
|
69
|
-
assertChildOutputReferences(steps);
|
|
70
61
|
const plan = {
|
|
71
62
|
irVersion: WORKFLOW_PLAN_VERSION,
|
|
72
63
|
title: asset.title,
|
|
73
64
|
...(compiled.params ? { params: compiled.params } : {}),
|
|
74
65
|
...(compiled.paramSchemas ? { paramSchemas: compiled.paramSchemas } : {}),
|
|
75
66
|
...(compiled.budget ? { budget: compiled.budget } : {}),
|
|
76
|
-
...(compiled.outputs ? { outputs: compiled.outputs } : {}),
|
|
77
67
|
execution: { maxConcurrency: workflowMaxConcurrency(config.workflow?.maxConcurrency) },
|
|
78
68
|
sourceHash: asset.sourceHash,
|
|
79
69
|
steps,
|
|
@@ -84,33 +74,11 @@ async function resolveStep(source, context) {
|
|
|
84
74
|
const baseUnit = baseUnitOf(source);
|
|
85
75
|
if (source.exec)
|
|
86
76
|
return directShell(source, baseUnit, context);
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
const target = classifyWorkflowStepUses(source.uses);
|
|
90
|
-
if (target.kind === "task")
|
|
91
|
-
return taskDispatch(source, baseUnit, target.ref, context);
|
|
92
|
-
if (target.kind === "workflow") {
|
|
93
|
-
return childWorkflowDispatch({
|
|
94
|
-
source,
|
|
95
|
-
baseUnit,
|
|
96
|
-
childRefInput: target.ref,
|
|
97
|
-
context,
|
|
98
|
-
via: "direct",
|
|
99
|
-
authoredInputs: { kind: "with", value: source.with },
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
// `commands/<ref>` and `scripts/<ref>` are not binding surfaces; `akm/command`'s
|
|
103
|
-
// `with:` is its own argument bag.
|
|
104
|
-
if (target.kind !== "builtin-command" && source.with !== undefined) {
|
|
105
|
-
const family = target.kind === "command" ? "commands" : "scripts";
|
|
106
|
-
throw new UsageError(`Workflow step ${source.id} cannot pass with: to ${family} target ${target.ref}; a ${target.kind} ref is not a binding surface.`, "COMPOSITION_INVALID");
|
|
77
|
+
if (source.workflow) {
|
|
78
|
+
return childWorkflowDispatch({ source, baseUnit, childRefInput: source.workflow, context });
|
|
107
79
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const action = target.kind === "builtin-command"
|
|
111
|
-
? source.with
|
|
112
|
-
: { ref: qualifyRef(target.ref, "commands", context.asset, context.config) };
|
|
113
|
-
return commandDispatch(source, baseUnit, action, context);
|
|
80
|
+
// A prose step: its body is the `akm/command` literal content (`parser.ts` `stepSpec`).
|
|
81
|
+
return commandDispatch(source, baseUnit, source.with, context);
|
|
114
82
|
}
|
|
115
83
|
/** The gate judge's frozen command target, or undefined when no engine is available and none is configured. */
|
|
116
84
|
function resolveJudge(source, rubric, context) {
|
|
@@ -153,7 +121,6 @@ function frozenRoot(stepId, source, resolved, plan, config) {
|
|
|
153
121
|
...(unit.retry ? { retry: { max: unit.retry.max, on: [...unit.retry.on] } } : {}),
|
|
154
122
|
onError: unit.onError ?? plan.defaults?.onError ?? "fail",
|
|
155
123
|
...(unit.env ? { env: [...unit.env] } : {}),
|
|
156
|
-
isolation: unit.isolation ?? "none",
|
|
157
124
|
source: { ...source.source },
|
|
158
125
|
frozenTarget: target,
|
|
159
126
|
environment: [...resolved.environment],
|
|
@@ -1,48 +1,31 @@
|
|
|
1
|
-
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
-
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
-
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
1
|
/**
|
|
5
|
-
* The one child-workflow resolver
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the child's `params:`, and embeds the frozen child plan in the target.
|
|
2
|
+
* The one child-workflow resolver (`unit: { workflow: <ref> }`, Experimental).
|
|
3
|
+
* It resolves the child, refuses a composition cycle, freezes the child
|
|
4
|
+
* completely through the injected `ResolutionContext.freezeChild`, binds the
|
|
5
|
+
* step's `with:` against the child's `params:`, and embeds the frozen child
|
|
6
|
+
* plan in the target.
|
|
11
7
|
*/
|
|
12
8
|
import { createHash } from "node:crypto";
|
|
13
9
|
import { parseBundleRef } from "../../../core/asset/asset-ref.js";
|
|
14
10
|
import { UsageError } from "../../../core/errors.js";
|
|
15
|
-
import { warn } from "../../../core/warn.js";
|
|
16
11
|
import { workflowParamContract } from "../../ir/params.js";
|
|
17
12
|
import { canonicalJson, canonicalPlanJson } from "../../ir/plan-hash.js";
|
|
18
13
|
import { loadWorkflowAsset } from "../../runtime/workflow-asset-loader.js";
|
|
19
14
|
import { resolveOwnedAsset } from "../environment.js";
|
|
20
15
|
import { declaredParamNames, earlierStepIds, } from "../step-values.js";
|
|
21
|
-
import { freezeTaskInputBindings
|
|
22
|
-
/** The child target's own content identity: its ref, embedded plan hash,
|
|
16
|
+
import { freezeTaskInputBindings } from "../task-bindings.js";
|
|
17
|
+
/** The child target's own content identity: its ref, embedded plan hash, and bindings. */
|
|
23
18
|
function childWorkflowContentHash(fields) {
|
|
24
19
|
return createHash("sha256")
|
|
25
|
-
.update("akm.workflow.child-workflow\
|
|
20
|
+
.update("akm.workflow.child-workflow\0v2\0")
|
|
26
21
|
.update(canonicalJson({
|
|
27
22
|
ref: fields.ref,
|
|
28
23
|
planHash: fields.planHash,
|
|
29
|
-
via: fields.via,
|
|
30
|
-
taskRef: fields.taskRef ?? null,
|
|
31
24
|
inputBindings: fields.inputBindings.length > 0 ? fields.inputBindings : null,
|
|
32
25
|
}))
|
|
33
26
|
.digest("hex");
|
|
34
27
|
}
|
|
35
|
-
/** A
|
|
36
|
-
function warnIfStepEnvironment(stepId, childRef, source) {
|
|
37
|
-
const hasLiteralEnv = Object.keys(source.env ?? {}).length > 0;
|
|
38
|
-
const hasEnvRefs = (source.unit?.env ?? []).length > 0;
|
|
39
|
-
if (!hasLiteralEnv && !hasEnvRefs)
|
|
40
|
-
return;
|
|
41
|
-
warn(`Workflow step ${stepId} declares env: while composing ${childRef}: a child run carries its own frozen ` +
|
|
42
|
-
`environment inside its own plan, so this composing step's env: (or unit: env:) is not delivered into it ` +
|
|
43
|
-
`and has no effect. Set those variables inside ${childRef}'s own source instead.`);
|
|
44
|
-
}
|
|
45
|
-
/** A workflow entry of a composition `refPath` — the only entries a cycle can close through. */
|
|
28
|
+
/** A workflow entry of a composition `refPath`. */
|
|
46
29
|
function isWorkflowRef(ref) {
|
|
47
30
|
try {
|
|
48
31
|
return parseBundleRef(ref).conceptId.startsWith("workflows/");
|
|
@@ -62,62 +45,43 @@ function assertNoCompositionCycle(stepId, childRef, refPath) {
|
|
|
62
45
|
"composing itself, directly or through intermediates.");
|
|
63
46
|
}
|
|
64
47
|
export async function childWorkflowDispatch(input) {
|
|
65
|
-
const { source, baseUnit, childRefInput, context
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// composition target (command/script/task) already resolves through.
|
|
48
|
+
const { source, baseUnit, childRefInput, context } = input;
|
|
49
|
+
// Resolution failures propagate unchanged, in code and shape — the same
|
|
50
|
+
// authority every other asset reference resolves through.
|
|
69
51
|
const owned = await resolveOwnedAsset(childRefInput, "workflow", context);
|
|
70
52
|
const childAsset = await loadWorkflowAsset(owned.ref);
|
|
71
53
|
const childRef = childAsset.ref;
|
|
72
|
-
//
|
|
73
|
-
// the child run — warn rather than vanish silently (see
|
|
74
|
-
// warnIfStepEnvironment's doc comment).
|
|
75
|
-
warnIfStepEnvironment(source.id, childRef, source);
|
|
76
|
-
// the composition cycle check, before any child compilation.
|
|
54
|
+
// The composition cycle check, before any child compilation.
|
|
77
55
|
assertNoCompositionCycle(source.id, childRef, context.refPath);
|
|
78
56
|
// Freeze the child completely; its plan is a pure function of its own source.
|
|
79
|
-
const
|
|
80
|
-
const child = await context.freezeChild(childAsset, childRefPath);
|
|
57
|
+
const child = await context.freezeChild(childAsset, [...context.refPath, childRef]);
|
|
81
58
|
// Embed the child plan as plain canonical JSON, exactly as `plan_json` stores it.
|
|
82
59
|
const embeddedPlanJson = canonicalPlanJson(child.plan);
|
|
83
60
|
const frozenPlan = JSON.parse(embeddedPlanJson);
|
|
84
|
-
// Bind the step's
|
|
85
|
-
const inputBindings =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
stepId: source.id,
|
|
94
|
-
targetRef: childRef,
|
|
95
|
-
with: authoredInputs.value,
|
|
96
|
-
contract: workflowParamContract(frozenPlan),
|
|
97
|
-
earlierStepIds: earlierStepIds(context.plan, source.id),
|
|
98
|
-
declaredParamNames: declaredParamNames(context.plan),
|
|
99
|
-
});
|
|
100
|
-
// build the frozen target.
|
|
61
|
+
// Bind the step's `with:` against the child's declared params.
|
|
62
|
+
const inputBindings = freezeTaskInputBindings({
|
|
63
|
+
stepId: source.id,
|
|
64
|
+
targetRef: childRef,
|
|
65
|
+
with: source.with,
|
|
66
|
+
contract: workflowParamContract(frozenPlan),
|
|
67
|
+
earlierStepIds: earlierStepIds(context.plan, source.id),
|
|
68
|
+
declaredParamNames: declaredParamNames(context.plan),
|
|
69
|
+
});
|
|
101
70
|
const planHash = createHash("sha256").update(embeddedPlanJson).digest("hex");
|
|
102
|
-
const contentHash = childWorkflowContentHash({ ref: childRef, planHash, via, taskRef, inputBindings });
|
|
103
71
|
const target = Object.freeze({
|
|
104
72
|
kind: "child-workflow",
|
|
105
73
|
ref: childRef,
|
|
106
74
|
planHash,
|
|
107
75
|
frozenPlan,
|
|
108
|
-
contentHash,
|
|
109
|
-
via,
|
|
110
|
-
...(taskRef !== undefined ? { taskRef } : {}),
|
|
76
|
+
contentHash: childWorkflowContentHash({ ref: childRef, planHash, inputBindings }),
|
|
111
77
|
...(inputBindings.length > 0 ? { inputBindings } : {}),
|
|
112
78
|
});
|
|
113
79
|
return {
|
|
114
80
|
target,
|
|
115
|
-
// A child run carries its own frozen environment inside its own plan
|
|
116
|
-
//
|
|
117
|
-
// above rejects one instead of it silently vanishing here.
|
|
81
|
+
// A child run carries its own frozen environment inside its own plan; the
|
|
82
|
+
// parser rejects an `env` beside `workflow`, so there is nothing to deliver.
|
|
118
83
|
environment: [],
|
|
119
84
|
unit: baseUnit,
|
|
120
|
-
instructions: source.instructions ??
|
|
121
|
-
(via === "task" && taskRef !== undefined ? `Run task ${taskRef}.` : `Run workflow ${childRef}.`),
|
|
85
|
+
instructions: source.instructions ?? `Run workflow ${childRef}.`,
|
|
122
86
|
};
|
|
123
87
|
}
|
|
@@ -2,34 +2,21 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { parseBuiltinCommandAction } from "../../../commands/command/builtin-action.js";
|
|
6
5
|
import { prepareCommandInvocation } from "../../../commands/command/command-execution.js";
|
|
7
|
-
import { PORTABLE_ARGUMENTS_PLACEHOLDER } from "../../../commands/command/portable-template.js";
|
|
8
6
|
import { captureFrozenDirectoryIdentity } from "../../../execution/directory-identity.js";
|
|
9
7
|
import { canonicalResolvedExecutionRequest, } from "../../../execution/resolved-request.js";
|
|
10
8
|
import { fallbackAnnouncement } from "../../../integrations/agent/engine-fallback.js";
|
|
11
9
|
import { buildExecution } from "../../../integrations/agent/execution.js";
|
|
12
10
|
import { freezeEnvironment, workflowExecutionSource } from "../environment.js";
|
|
13
|
-
import { gitIdentity } from "../identity.js";
|
|
14
11
|
import { durableRequest, executionUnitValues, targetConcurrency, } from "../step-values.js";
|
|
15
|
-
function inlineWorkflowCommandAction(action, commandMode) {
|
|
16
|
-
if (commandMode !== "portable-template")
|
|
17
|
-
return action;
|
|
18
|
-
const parsed = parseBuiltinCommandAction(action);
|
|
19
|
-
if (parsed.kind !== "inline")
|
|
20
|
-
return action;
|
|
21
|
-
return { content: parsed.content.split(PORTABLE_ARGUMENTS_PLACEHOLDER).join(parsed.arguments ?? "") };
|
|
22
|
-
}
|
|
23
12
|
export async function commandDispatch(source, baseUnit, action, context) {
|
|
24
13
|
const prepared = await prepareCommandInvocation({
|
|
25
|
-
action
|
|
14
|
+
action,
|
|
26
15
|
config: context.config,
|
|
27
16
|
...(context.plan.defaults
|
|
28
17
|
? { invocationDefaults: executionUnitValues(context.plan.defaults, context.asset.sourcePath) }
|
|
29
18
|
: {}),
|
|
30
|
-
|
|
31
|
-
? { inlineContentMode: "literal" }
|
|
32
|
-
: {}),
|
|
19
|
+
inlineContentMode: "literal",
|
|
33
20
|
current: executionUnitValues(source.unit, context.asset.sourcePath),
|
|
34
21
|
sourceLoader: (ref, kind) => workflowExecutionSource(ref, kind, context),
|
|
35
22
|
});
|
|
@@ -57,7 +44,6 @@ export function commandResult(source, baseUnit, prepared, context, literals = []
|
|
|
57
44
|
runner,
|
|
58
45
|
...(targetConcurrency(runner, context.config) ? { concurrency: targetConcurrency(runner, context.config) } : {}),
|
|
59
46
|
cwdIdentity,
|
|
60
|
-
...gitIdentity(baseUnit, cwdIdentity.realRoot),
|
|
61
47
|
});
|
|
62
48
|
const engineAnnouncement = fallbackAnnouncement(prepared.fallbackEngineName, request.engine.name);
|
|
63
49
|
return {
|