akm-cli 0.10.261012-beta → 0.10.261013-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 +216 -0
- package/SECURITY.md +2 -2
- package/STABILITY.md +2 -2
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/hints/cli-hints-short.md +3 -4
- package/dist/commands/command/execution-source-loader.js +10 -14
- package/dist/commands/env/env-binding.js +47 -58
- package/dist/commands/env/env-cli.js +20 -22
- package/dist/commands/env/env.js +10 -58
- package/dist/commands/env/secret-cli.js +9 -27
- package/dist/commands/env/secret.js +3 -10
- package/dist/commands/health/scheduler-binary.js +1 -1
- package/dist/commands/improve/distill.js +0 -2
- package/dist/commands/improve/eligibility.js +0 -2
- package/dist/commands/lint/env-key-rules.js +7 -6
- package/dist/commands/lint/index.js +1 -1
- package/dist/commands/proposal/validators/proposal-validators.js +1 -4
- package/dist/commands/read/show.js +4 -25
- package/dist/commands/sources/add-cli.js +8 -63
- package/dist/commands/sources/bundle-cli.js +8 -9
- package/dist/commands/sources/installed-stashes.js +117 -61
- package/dist/commands/sources/schema-repair.js +0 -2
- package/dist/commands/sources/source-clone.js +31 -7
- package/dist/commands/tasks/explain.js +1 -1
- package/dist/commands/tasks/tasks.js +38 -56
- package/dist/commands/ui/redact.js +4 -4
- package/dist/commands/ui/workspace-actions.js +1 -1
- package/dist/commands/workflow/plan.js +47 -8
- package/dist/commands/workflow-cli.js +37 -41
- package/dist/core/adapter/adapters/akm-adapter.js +3 -3
- package/dist/core/adapter/adapters/akm-lint.js +3 -4
- package/dist/core/adapter/adapters/akm-metadata.js +2 -2
- package/dist/core/adapter/adapters/akm-task-adapter.js +5 -6
- package/dist/core/adapter/adapters/dotenv-adapter.js +4 -3
- package/dist/core/asset/resolve-ref.js +5 -0
- package/dist/core/bundle-rename.js +12 -4
- package/dist/core/common.js +4 -52
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +55 -18
- package/dist/core/config/schema/primitives.js +12 -6
- package/dist/core/config/schema/sources-bundles.js +2 -2
- package/dist/core/env-secret-ref.js +84 -73
- package/dist/core/errors.js +1 -1
- package/dist/core/paths.js +1 -6
- package/dist/core/redaction.js +16 -3
- package/dist/core/ref-token.js +82 -0
- package/dist/core/secret-value.js +14 -0
- package/dist/core/write-source.js +20 -4
- package/dist/indexer/indexer.js +27 -36
- package/dist/indexer/installations.js +1 -8
- package/dist/indexer/lookup/adapter-concept-owner.js +40 -14
- package/dist/indexer/materialize-embeddings.js +1 -1
- package/dist/indexer/scan/drain-dir.js +3 -3
- package/dist/indexer/search/search-source.js +4 -2
- package/dist/indexer/walk/path-resolver.js +13 -68
- package/dist/indexer/walk/walker.js +18 -22
- package/dist/integrations/agent/engine-resolution.js +9 -7
- package/dist/integrations/agent/runner-dispatch.js +4 -5
- package/dist/integrations/agent/runner.js +2 -0
- package/dist/llm/client.js +1 -1
- package/dist/llm/embedders/remote.js +4 -3
- package/dist/output/renderers.js +3 -4
- package/dist/output/shapes/helpers.js +0 -2
- package/dist/output/text/command-format.js +12 -2
- package/dist/output/text/show-directives.js +16 -28
- package/dist/output/text/workflow-format.js +37 -7
- package/dist/scripts/akm-migrate-node.js +45865 -44692
- package/dist/scripts/akm-migrate.js +45804 -44630
- package/dist/setup/steps/tasks.js +1 -2
- package/dist/sources/providers/provider-utils.js +12 -0
- package/dist/sources/resolve.js +12 -16
- package/dist/sources/snapshot-fetchers/secret-seam.js +2 -4
- package/dist/sources/snapshot-fetchers/x.js +1 -1
- package/dist/storage/repositories/proposals-repository.js +2 -2
- package/dist/storage/repositories/task-history-repository.js +2 -2
- package/dist/storage/repositories/workflow-runs-repository.js +38 -116
- package/dist/tasks/embedded.js +1 -1
- package/dist/tasks/prepare/prepare-support.js +3 -2
- package/dist/tasks/prepare/prepare.js +3 -4
- package/dist/tasks/prepare/script-capture.js +8 -22
- package/dist/tasks/run/load-task.js +46 -14
- package/dist/tasks/run/run-native-task.js +0 -8
- package/dist/tasks/run/run-workflow-task.js +2 -2
- package/dist/tasks/scheduler-sync.js +0 -1
- package/dist/tasks/source/bounded-document.js +0 -41
- package/dist/tasks/source/parse-task-source.js +0 -1
- package/dist/tasks/source/task-source-v4.js +1 -4
- package/dist/workflows/compile.js +1 -15
- package/dist/workflows/exec/child-workflow.js +1 -2
- package/dist/workflows/exec/dispatch-redaction.js +24 -6
- package/dist/workflows/exec/environment.js +11 -74
- package/dist/workflows/exec/exec-unit.js +31 -23
- package/dist/workflows/exec/frozen-judge.js +17 -3
- package/dist/workflows/exec/native-executor.js +70 -26
- package/dist/workflows/exec/run-workflow.js +31 -18
- package/dist/workflows/exec/step-work.js +4 -3
- package/dist/workflows/exec/unit-dispatch.js +1 -8
- package/dist/workflows/freeze/environment.js +14 -119
- package/dist/workflows/freeze/freeze.js +0 -1
- package/dist/workflows/freeze/step-values.js +1 -2
- package/dist/workflows/freeze/targets/child-workflow.js +3 -5
- package/dist/workflows/freeze/targets/command.js +6 -9
- package/dist/workflows/freeze/targets/shell.js +0 -3
- package/dist/workflows/parser.js +6 -25
- package/dist/workflows/plan.js +6 -2
- package/dist/workflows/renderer.js +77 -4
- package/dist/workflows/runtime/run-plan.js +37 -23
- package/dist/workflows/runtime/runs.js +41 -84
- package/dist/workflows/runtime/workflow-asset-loader.js +55 -86
- package/dist/workflows/source-files.js +7 -19
- package/docs/architecture/0.10-process-rules.md +6 -6
- package/docs/architecture/architecture.md +6 -6
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +12 -18
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +6 -8
- package/docs/architecture/internals/storage-locations.md +4 -4
- package/docs/architecture/persisted-data-compat.md +3 -2
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +1 -1
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +3 -3
- package/docs/architecture/testing/manual-testing-checklist.md +46 -78
- package/docs/architecture/testing/task-workflow-manual-tests.md +22 -40
- package/docs/architecture/testing/testing-workflow.md +9 -11
- package/docs/architecture/workflow-engine.md +41 -27
- package/docs/guides/author-bundles.md +14 -17
- package/docs/guides/author-workflows.md +43 -9
- package/docs/guides/bundles.md +16 -11
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -1
- package/docs/guides/run-workflows.md +25 -13
- package/docs/guides/scheduling.md +1 -1
- package/docs/maintainers/README.md +2 -3
- package/docs/maintainers/release-checklist.md +73 -126
- package/docs/migration/v0.9-to-v0.10.md +5 -1
- package/docs/reference/cli.md +124 -112
- package/docs/reference/configuration.md +16 -9
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +66 -15
- package/docs/reference/registry.md +6 -11
- package/docs/reference/tasks.md +11 -3
- package/docs/reference/ui-api.md +3 -3
- package/docs/reference/workflow-schema.md +109 -36
- package/package.json +1 -1
- package/schemas/akm-task.json +2 -4
- package/dist/commands/sources/dangerous-env-audit.js +0 -134
- package/dist/core/activation-policy.js +0 -67
- package/dist/execution/directory-identity.js +0 -78
- package/dist/workflows/authoring/scope-key.js +0 -79
- package/dist/workflows/source-semantics.js +0 -110
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
* - Run `budget` ceilings are consumed per actual dispatch and abort the step
|
|
24
24
|
* when crossed, regardless of `on_error`.
|
|
25
25
|
*/
|
|
26
|
-
import {
|
|
26
|
+
import { resolveRefToken } from "../../commands/env/env-binding.js";
|
|
27
|
+
import { isInstalledBundle } from "../../core/env-secret-ref.js";
|
|
28
|
+
import { NotFoundError } from "../../core/errors.js";
|
|
27
29
|
import { validateJsonSchemaSubset } from "../../core/json-schema.js";
|
|
30
|
+
import { substituteRefTokens } from "../../core/ref-token.js";
|
|
28
31
|
import { runStructured } from "../../core/structured.js";
|
|
29
32
|
import { warn } from "../../core/warn.js";
|
|
30
|
-
import { assertFrozenDirectoryContained } from "../../execution/directory-identity.js";
|
|
31
33
|
import { withWorkflowRunsConnection, withWorkflowRunsRepo, } from "../../storage/repositories/workflow-runs-repository.js";
|
|
32
34
|
import { WORKFLOW_UNIT_DIAGNOSTIC_CLIP } from "../resource-limits.js";
|
|
35
|
+
import { resolveRunBundleRoot } from "../runtime/workflow-asset-loader.js";
|
|
33
36
|
// The ONE child-workflow drive — publishes and drives a
|
|
34
37
|
// `child-workflow`-targeted unit; this module's dispatch seam is its only
|
|
35
38
|
// production caller.
|
|
@@ -167,22 +170,28 @@ function openDispatchBudget(ctx, dispatched) {
|
|
|
167
170
|
}
|
|
168
171
|
/** Resolve the live-at-dispatch prerequisites once, after durable-row reuse is known. */
|
|
169
172
|
async function prepareStepDispatchPrerequisites(input) {
|
|
170
|
-
const { plan, workUnits, willDispatch, dispatched } = input;
|
|
173
|
+
const { plan, ctx, workUnits, willDispatch, dispatched } = input;
|
|
171
174
|
let env;
|
|
172
175
|
let sensitiveValues;
|
|
176
|
+
// The run's bundle is found again here, never frozen: a bundle update moves it to a new directory.
|
|
177
|
+
let cwd = ctx.bundleRootOverride;
|
|
178
|
+
if (willDispatch && cwd === undefined) {
|
|
179
|
+
try {
|
|
180
|
+
cwd = resolveRunBundleRoot(ctx.workflowRef);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
result: failedStep(dispatched, `Step "${plan.stepId}" cannot run: ${message(err).replace(/\.$/, "")}.`),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
173
189
|
const frozenEnvironment = workUnits[0]?.environment;
|
|
174
190
|
if (willDispatch && frozenEnvironment && frozenEnvironment.length > 0) {
|
|
175
191
|
try {
|
|
176
192
|
const materialized = materializeFrozenWorkflowEnvironment(frozenEnvironment);
|
|
177
193
|
env = materialized.values;
|
|
178
194
|
sensitiveValues = materialized.sensitiveValues;
|
|
179
|
-
for (const audit of materialized.audits) {
|
|
180
|
-
appendEvent({
|
|
181
|
-
eventType: audit.eventType,
|
|
182
|
-
ref: audit.ref,
|
|
183
|
-
metadata: { keys: audit.keys, secretNames: audit.secretNames },
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
195
|
}
|
|
187
196
|
catch (err) {
|
|
188
197
|
return {
|
|
@@ -193,6 +202,7 @@ async function prepareStepDispatchPrerequisites(input) {
|
|
|
193
202
|
}
|
|
194
203
|
return {
|
|
195
204
|
ok: true,
|
|
205
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
196
206
|
...(env ? { env } : {}),
|
|
197
207
|
...(sensitiveValues ? { sensitiveValues } : {}),
|
|
198
208
|
};
|
|
@@ -229,13 +239,14 @@ async function executeStepPlanInConnection(plan, ctx) {
|
|
|
229
239
|
const willDispatch = reuseDecisions.some((decision) => decision.kind === "dispatch");
|
|
230
240
|
const prerequisites = await prepareStepDispatchPrerequisites({
|
|
231
241
|
plan,
|
|
242
|
+
ctx,
|
|
232
243
|
workUnits,
|
|
233
244
|
willDispatch,
|
|
234
245
|
dispatched,
|
|
235
246
|
});
|
|
236
247
|
if (!prerequisites.ok)
|
|
237
248
|
return prerequisites.result;
|
|
238
|
-
const { env, sensitiveValues } = prerequisites;
|
|
249
|
+
const { cwd, env, sensitiveValues } = prerequisites;
|
|
239
250
|
// Budget ceilings + lifetime-cap accounting, and the budget-chained abort
|
|
240
251
|
// signal they trip. Extracted verbatim (behavior-identical) — see
|
|
241
252
|
// {@link openDispatchBudget}.
|
|
@@ -246,6 +257,7 @@ async function executeStepPlanInConnection(plan, ctx) {
|
|
|
246
257
|
outcomes = await scheduleUnits(workUnits, (workUnit, index) => runUnit({
|
|
247
258
|
plan,
|
|
248
259
|
workUnit,
|
|
260
|
+
cwd,
|
|
249
261
|
env,
|
|
250
262
|
sensitiveValues,
|
|
251
263
|
ctx,
|
|
@@ -299,7 +311,7 @@ async function executeStepPlanInConnection(plan, ctx) {
|
|
|
299
311
|
};
|
|
300
312
|
}
|
|
301
313
|
async function runUnit(input) {
|
|
302
|
-
const { plan, workUnit, env, sensitiveValues, ctx, dispatcher } = input;
|
|
314
|
+
const { plan, workUnit, cwd, env, sensitiveValues, ctx, dispatcher } = input;
|
|
303
315
|
const unitId = workUnit.unitId;
|
|
304
316
|
// Target validity is a WHOLE-LIST invariant, never a per-unit condition:
|
|
305
317
|
// computeStepWorkList rejects a step before building any unit when its sole
|
|
@@ -317,10 +329,7 @@ async function runUnit(input) {
|
|
|
317
329
|
prompt,
|
|
318
330
|
frozenTarget: workUnit.frozenTarget,
|
|
319
331
|
...(workUnit.execContext ? { execContext: workUnit.execContext } : {}),
|
|
320
|
-
|
|
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.
|
|
323
|
-
...(workUnit.frozenTarget.kind !== "command" && ctx.workDir !== undefined ? { cwd: ctx.workDir } : {}),
|
|
332
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
324
333
|
timeoutMs: workUnit.timeoutMs,
|
|
325
334
|
...(workUnit.schema ? { schema: workUnit.schema } : {}),
|
|
326
335
|
...(env ? { env } : {}),
|
|
@@ -498,6 +507,7 @@ async function dispatchJournaledAttempt(input) {
|
|
|
498
507
|
};
|
|
499
508
|
// A child-workflow unit goes to the child executor instead of the
|
|
500
509
|
// dispatcher, after its attempt row is reserved, so it journals like any unit.
|
|
510
|
+
const resolvedSecrets = new Set();
|
|
501
511
|
const dispatched = request.frozenTarget.kind === "child-workflow"
|
|
502
512
|
? await driveChildWorkflowUnit({
|
|
503
513
|
request,
|
|
@@ -507,17 +517,18 @@ async function dispatchJournaledAttempt(input) {
|
|
|
507
517
|
inputHash: input.inputHash,
|
|
508
518
|
dispatcher,
|
|
509
519
|
})
|
|
510
|
-
: await dispatchUnit(request, dispatcher);
|
|
520
|
+
: await dispatchUnit(request, dispatcher, resolvedSecrets);
|
|
511
521
|
// Credential and passthrough values are intentionally sampled only AFTER
|
|
512
522
|
// the default dispatcher has authorized/lowered the frozen request and
|
|
513
523
|
// materialized credentials at its terminal dispatch boundary. Custom test
|
|
514
524
|
// dispatchers receive the same post-dispatch journal scrub. Secret-shaped
|
|
515
525
|
// run params join the set: they must reach the prompt in clear (the unit
|
|
516
|
-
// needs them), but nothing about them needs to reach the journal.
|
|
526
|
+
// needs them), but nothing about them needs to reach the journal. So do the
|
|
527
|
+
// secrets the dispatcher resolved itself (an exec unit's argv/cwd tokens).
|
|
517
528
|
const sensitiveValues = collectWorkflowDispatchSensitiveValues({
|
|
518
529
|
...(request.frozenTarget.kind === "command" ? { runner: request.frozenTarget.runner } : {}),
|
|
519
|
-
sensitiveValues: [...(request.sensitiveValues ?? []), ...secretShapedParamValues(ctx.params)],
|
|
520
|
-
}
|
|
530
|
+
sensitiveValues: [...(request.sensitiveValues ?? []), ...resolvedSecrets, ...secretShapedParamValues(ctx.params)],
|
|
531
|
+
});
|
|
521
532
|
const outcome = redactUnitOutcome(dispatched, sensitiveValues);
|
|
522
533
|
const finishedAt = new Date().toISOString();
|
|
523
534
|
// A dispatched unit's outcome is NEVER silently discarded: the attempt is
|
|
@@ -589,7 +600,9 @@ class UnitTransportError extends Error {
|
|
|
589
600
|
this.name = "UnitTransportError";
|
|
590
601
|
}
|
|
591
602
|
}
|
|
592
|
-
async function dispatchUnit(request, dispatcher
|
|
603
|
+
async function dispatchUnit(request, dispatcher,
|
|
604
|
+
/** Receives the secret values the dispatcher resolved, for the caller's journal scrub. */
|
|
605
|
+
resolvedSecrets) {
|
|
593
606
|
let tokens = 0;
|
|
594
607
|
let sawUsage = false;
|
|
595
608
|
let loweringNotices;
|
|
@@ -600,6 +613,8 @@ async function dispatchUnit(request, dispatcher) {
|
|
|
600
613
|
let sessionId;
|
|
601
614
|
const dispatchOnce = async (feedback) => {
|
|
602
615
|
const result = await dispatcher(request, feedback);
|
|
616
|
+
for (const value of result.sensitiveValues ?? [])
|
|
617
|
+
resolvedSecrets.add(value);
|
|
603
618
|
if (result.usage) {
|
|
604
619
|
sawUsage = true;
|
|
605
620
|
tokens +=
|
|
@@ -700,12 +715,14 @@ async function dispatchUnit(request, dispatcher) {
|
|
|
700
715
|
export const defaultUnitDispatcher = async (request, feedback) => {
|
|
701
716
|
const frozenTarget = request.frozenTarget;
|
|
702
717
|
if (frozenTarget.kind === "shell") {
|
|
703
|
-
if (
|
|
704
|
-
|
|
705
|
-
|
|
718
|
+
if (request.cwd === undefined) {
|
|
719
|
+
throw new Error(`exec unit "${request.unitId}" was dispatched with no working directory.`);
|
|
720
|
+
}
|
|
721
|
+
const { exec, secretValues } = substituteExecRefs(request.unitId, frozenTarget.exec);
|
|
722
|
+
const outcome = await runExecUnit({
|
|
706
723
|
unitId: request.unitId,
|
|
707
|
-
exec
|
|
708
|
-
baseDir: request.cwd
|
|
724
|
+
exec,
|
|
725
|
+
baseDir: request.cwd,
|
|
709
726
|
...(request.env ? { env: request.env } : {}),
|
|
710
727
|
...(request.execContext ? { context: request.execContext } : {}),
|
|
711
728
|
...(request.schema ? { hasOutputSchema: true } : {}),
|
|
@@ -713,9 +730,36 @@ export const defaultUnitDispatcher = async (request, feedback) => {
|
|
|
713
730
|
...(request.signal ? { signal: request.signal } : {}),
|
|
714
731
|
...(request.eventSource !== undefined ? { eventSource: request.eventSource } : {}),
|
|
715
732
|
});
|
|
733
|
+
// A secret substituted into argv joins the redaction set, like one bound through an env asset: the
|
|
734
|
+
// executor scrubs after the structured parse, so a quoted or numeric secret is not missed or corrupted.
|
|
735
|
+
return { ...outcome, sensitiveValues: secretValues };
|
|
716
736
|
}
|
|
717
737
|
return dispatchWorkflowExecution(request, feedback);
|
|
718
738
|
};
|
|
739
|
+
/**
|
|
740
|
+
* Replace the `${ref}` tokens in an exec unit's argv and `cwd` (prompts are not substituted): a
|
|
741
|
+
* `secrets/` ref with the secret's value, any other asset ref with its absolute path. A ref no
|
|
742
|
+
* bundle owns fails the unit before anything is spawned.
|
|
743
|
+
*/
|
|
744
|
+
function substituteExecRefs(unitId, exec) {
|
|
745
|
+
const slots = Object.fromEntries(exec.command.entries());
|
|
746
|
+
if (exec.cwd !== undefined)
|
|
747
|
+
slots.cwd = exec.cwd;
|
|
748
|
+
const { values, missing, secretValues } = substituteRefTokens(slots, resolveRefToken, isInstalledBundle);
|
|
749
|
+
if (missing.length > 0) {
|
|
750
|
+
throw new NotFoundError(`exec unit "${unitId}" references asset(s) that no bundle owns: ${missing.join(", ")}. Nothing was run.`, "FILE_NOT_FOUND",
|
|
751
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: literal `${ref}` token syntax documented for users, not interpolation
|
|
752
|
+
"Write each `${ref}` as a full akm ref that exists, e.g. `${team//scripts/run.sh}`.");
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
exec: {
|
|
756
|
+
...exec,
|
|
757
|
+
command: exec.command.map((_, index) => values[index]),
|
|
758
|
+
...(exec.cwd !== undefined ? { cwd: values.cwd } : {}),
|
|
759
|
+
},
|
|
760
|
+
secretValues,
|
|
761
|
+
};
|
|
762
|
+
}
|
|
719
763
|
// ── Small helpers ────────────────────────────────────────────────────────────
|
|
720
764
|
/**
|
|
721
765
|
* Rehydrate a journaled completed unit row into a UnitOutcome (durable-row
|
|
@@ -14,16 +14,16 @@ import { createHash } from "node:crypto";
|
|
|
14
14
|
import fs from "node:fs";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { TransientError, UsageError } from "../../core/errors.js";
|
|
17
|
-
import { releaseLock } from "../../core/file-lock.js";
|
|
17
|
+
import { probeLock, releaseLock } from "../../core/file-lock.js";
|
|
18
18
|
import { formatLockHolderPid, tryAcquireRunLock } from "../../core/run-lock.js";
|
|
19
19
|
import { disposeDispatchResources } from "../../integrations/agent/runner-dispatch.js";
|
|
20
20
|
import { resolveStorageLocations } from "../../storage/locations.js";
|
|
21
21
|
import { withWorkflowRunsConnection, withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
|
|
22
22
|
import { computePlanHash } from "../ir/plan-hash.js";
|
|
23
23
|
import { decodeWorkflowPlan, readRunPlan } from "../runtime/run-plan.js";
|
|
24
|
-
import { abandonWorkflowRun, getNextWorkflowStep, resumeWorkflowRun } from "../runtime/runs.js";
|
|
24
|
+
import { abandonWorkflowRun, getNextWorkflowStep, listWorkflowRuns, resolveWorkflowRunTarget, resumeWorkflowRun, } from "../runtime/runs.js";
|
|
25
25
|
import { loadWorkflowAsset } from "../runtime/workflow-asset-loader.js";
|
|
26
|
-
import { frozenSummaryJudge } from "./frozen-judge.js";
|
|
26
|
+
import { frozenSummaryJudge, resolveJudgeCwd } from "./frozen-judge.js";
|
|
27
27
|
import { mergeLoweringNotices } from "./lowering-notices.js";
|
|
28
28
|
import { defaultUnitDispatcher, executeStepPlan, } from "./native-executor.js";
|
|
29
29
|
// Shared step semantics — gate-evaluation journaling, and the whole step-completion path
|
|
@@ -34,10 +34,6 @@ export async function runWorkflowSteps(options) {
|
|
|
34
34
|
let target = options.target;
|
|
35
35
|
let params = options.params;
|
|
36
36
|
let parameterFlags = options.parameterFlags;
|
|
37
|
-
// `--new` only applies to the FIRST resolution of `target` (a ref); every
|
|
38
|
-
// retry re-targets the run id `startWorkflowRun` already created, so it is
|
|
39
|
-
// cleared alongside `params`/`parameterFlags` below (#919).
|
|
40
|
-
let newRun = options.newRun;
|
|
41
37
|
let remainingRetries = options.maxRetries ?? 0;
|
|
42
38
|
let remainingSteps = options.maxSteps;
|
|
43
39
|
const executed = [];
|
|
@@ -53,7 +49,6 @@ export async function runWorkflowSteps(options) {
|
|
|
53
49
|
target,
|
|
54
50
|
...(params !== undefined ? { params } : { params: undefined }),
|
|
55
51
|
...(parameterFlags !== undefined ? { parameterFlags } : { parameterFlags: undefined }),
|
|
56
|
-
newRun,
|
|
57
52
|
...(remainingSteps !== undefined ? { maxSteps: remainingSteps } : { maxSteps: undefined }),
|
|
58
53
|
}, liveEvidence);
|
|
59
54
|
executed.push(...result.executed);
|
|
@@ -81,14 +76,12 @@ export async function runWorkflowSteps(options) {
|
|
|
81
76
|
target = result.run.id;
|
|
82
77
|
params = undefined;
|
|
83
78
|
parameterFlags = undefined;
|
|
84
|
-
newRun = undefined;
|
|
85
79
|
remainingRetries -= 1;
|
|
86
80
|
}
|
|
87
81
|
}
|
|
88
82
|
async function runWorkflowAttempt(options, liveEvidence) {
|
|
89
83
|
const next = await getNextWorkflowStep(options.target, options.params, {
|
|
90
84
|
parameterFlags: options.parameterFlags,
|
|
91
|
-
newRun: options.newRun,
|
|
92
85
|
});
|
|
93
86
|
// Refuse non-active runs BEFORE any dispatch — completeWorkflowStep would
|
|
94
87
|
// reject the completion anyway, but only after the units already ran (and
|
|
@@ -125,7 +118,6 @@ async function runWorkflowAttempt(options, liveEvidence) {
|
|
|
125
118
|
executed: [],
|
|
126
119
|
stepsProcessed: 0,
|
|
127
120
|
warnings: [...warnings, message],
|
|
128
|
-
...(next.resumed ? { resumed: true } : {}),
|
|
129
121
|
};
|
|
130
122
|
}
|
|
131
123
|
plan = read.plan;
|
|
@@ -141,11 +133,10 @@ async function runWorkflowAttempt(options, liveEvidence) {
|
|
|
141
133
|
// Creation/resume-time notices reach the caller only here: the run row
|
|
142
134
|
// has no warnings column, and a later invocation of the same run must
|
|
143
135
|
// stay silent about a decision it did not make. `driveRun` never sets
|
|
144
|
-
// `warnings`
|
|
145
|
-
//
|
|
136
|
+
// `warnings` — it is a property of THIS resolution of `target`, not of
|
|
137
|
+
// the run row.
|
|
146
138
|
return {
|
|
147
139
|
...result,
|
|
148
|
-
...(next.resumed ? { resumed: true } : {}),
|
|
149
140
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
150
141
|
};
|
|
151
142
|
}
|
|
@@ -172,6 +163,27 @@ async function runWorkflowAttempt(options, liveEvidence) {
|
|
|
172
163
|
export function workflowRunLockPath(runId) {
|
|
173
164
|
return path.join(path.dirname(resolveStorageLocations().stateDb), "workflow-run-locks", `${runId}.lock`);
|
|
174
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* `--skip-if-locked` for a workflow REF (#1239): an active run of `ref` whose
|
|
168
|
+
* lock a live process holds, or `undefined`. A run id (which drives that one
|
|
169
|
+
* run, and fails with `RUN_LEASE_HELD` if it is held) is never a ref. Read-only:
|
|
170
|
+
* `probeLock` reports a dead holder as stale without reclaiming it.
|
|
171
|
+
*/
|
|
172
|
+
export async function findLiveLockedRunOfRef(target) {
|
|
173
|
+
if ((await resolveWorkflowRunTarget(target)) !== undefined)
|
|
174
|
+
return undefined;
|
|
175
|
+
const { runs } = await listWorkflowRuns({ workflowRef: target, activeOnly: true });
|
|
176
|
+
for (const run of runs) {
|
|
177
|
+
const probe = probeLock(workflowRunLockPath(run.id));
|
|
178
|
+
if (probe.state === "held") {
|
|
179
|
+
return {
|
|
180
|
+
runId: run.id,
|
|
181
|
+
holder: formatLockHolderPid({ pid: probe.holderPid, launcherPid: probe.launcherPid ?? null }),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
175
187
|
/**
|
|
176
188
|
* Take the run's O_EXCL lock file, or refuse with `RUN_LEASE_HELD` (exit 75)
|
|
177
189
|
* naming the live holder. A lock whose holder pid is dead is reclaimed by
|
|
@@ -215,7 +227,7 @@ async function workflowSourceDriftWarning(runId, workflowRef, plan) {
|
|
|
215
227
|
if (current === plan.sourceHash)
|
|
216
228
|
return undefined;
|
|
217
229
|
return (`Workflow run ${runId}: ${workflowRef} (${sourcePath}) has changed since this run was frozen; ` +
|
|
218
|
-
`continuing with the frozen plan. Start a new run with 'akm workflow run ${workflowRef}
|
|
230
|
+
`continuing with the frozen plan. Start a new run with 'akm workflow run ${workflowRef}' to pick up the edit.`);
|
|
219
231
|
}
|
|
220
232
|
/**
|
|
221
233
|
* A terminal run is a pure no-op: do not load or integrity-check its frozen
|
|
@@ -230,11 +242,11 @@ async function completedRunResult(runId) {
|
|
|
230
242
|
...(doneState.run.status === "completed" ? { done: true } : {}),
|
|
231
243
|
};
|
|
232
244
|
}
|
|
233
|
-
function workflowSummaryJudge(options, stepPlan, signal, owner) {
|
|
245
|
+
function workflowSummaryJudge(options, stepPlan, signal, owner, workflowRef) {
|
|
234
246
|
if (options.summaryJudge !== undefined)
|
|
235
247
|
return options.summaryJudge;
|
|
236
248
|
// The judge dispatches under the real run/step identity and the same event source as units.
|
|
237
|
-
return frozenSummaryJudge(stepPlan.gate.frozenJudge, signal, options.dispatcher ?? defaultUnitDispatcher, owner, options.eventSource);
|
|
249
|
+
return frozenSummaryJudge(stepPlan.gate.frozenJudge, signal, options.dispatcher ?? defaultUnitDispatcher, owner, options.eventSource, options.bundleRootOverride ?? resolveJudgeCwd(stepPlan.gate.frozenJudge, workflowRef));
|
|
238
250
|
}
|
|
239
251
|
/**
|
|
240
252
|
* Seed the run's budget from the append-only attempt journal so a resumed run
|
|
@@ -307,6 +319,7 @@ async function executeStepSubgraph(ctx, loop) {
|
|
|
307
319
|
...(options.eventSource !== undefined ? { eventSource: options.eventSource } : {}),
|
|
308
320
|
...(options.signal ? { signal: options.signal } : {}),
|
|
309
321
|
...(options.dispatcher ? { dispatcher: options.dispatcher } : {}),
|
|
322
|
+
...(options.bundleRootOverride !== undefined ? { bundleRootOverride: options.bundleRootOverride } : {}),
|
|
310
323
|
maxConcurrency: Math.min(options.maxConcurrency ?? Number.POSITIVE_INFINITY, plan.execution?.maxConcurrency ?? 1),
|
|
311
324
|
});
|
|
312
325
|
}
|
|
@@ -493,7 +506,7 @@ liveEvidence) {
|
|
|
493
506
|
summaryJudge = workflowSummaryJudge(options, stepPlan, options.signal, {
|
|
494
507
|
runId: next.run.id,
|
|
495
508
|
stepId: step.id,
|
|
496
|
-
});
|
|
509
|
+
}, next.run.workflowRef);
|
|
497
510
|
}
|
|
498
511
|
catch (error) {
|
|
499
512
|
const detail = error instanceof Error && error.message ? ` (${error.message})` : "";
|
|
@@ -837,8 +837,8 @@ function judgeFailureNotes(runId, stepId, cause) {
|
|
|
837
837
|
return (`Step "${stepId}" could not be verified: ${cause}. ` +
|
|
838
838
|
`This is a verification-judge failure, not a verdict — no gate loop was consumed and the step's ` +
|
|
839
839
|
`journaled units are preserved. Fix the verifier configuration or service, then run ` +
|
|
840
|
-
`\`akm workflow resume ${runId}\` to re-evaluate the gate
|
|
841
|
-
`without re-dispatching units.`);
|
|
840
|
+
`\`akm workflow resume ${runId}\` and \`akm workflow run ${runId}\` to re-evaluate the gate ` +
|
|
841
|
+
`against the existing results without re-dispatching units.`);
|
|
842
842
|
}
|
|
843
843
|
/**
|
|
844
844
|
* Complete a step `blocked` for a verifier-INFRASTRUCTURE failure and return
|
|
@@ -1059,7 +1059,8 @@ export async function finalizeExecutedStep(input) {
|
|
|
1059
1059
|
}
|
|
1060
1060
|
// Judge infrastructure failure: the fail-closed rejection is synthetic, not a
|
|
1061
1061
|
// verdict. Consume NO gate loop; block the step (and therefore the run) so
|
|
1062
|
-
// `akm workflow resume` retries the gate over the
|
|
1062
|
+
// `akm workflow resume` then `akm workflow run` retries the gate over the
|
|
1063
|
+
// journaled units.
|
|
1063
1064
|
if (rejection && judgeFailed) {
|
|
1064
1065
|
return blockFinalizedStep(input, judgeFailure ?? "the verification judge failed");
|
|
1065
1066
|
}
|
|
@@ -2,24 +2,17 @@
|
|
|
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 { ConfigError } from "../../core/errors.js";
|
|
5
|
-
import { assertFrozenDirectoryContained } from "../../execution/directory-identity.js";
|
|
6
5
|
import { canonicalResolvedExecutionRequest } from "../../execution/resolved-request.js";
|
|
7
6
|
import { buildExecutionFromWire } from "../../integrations/agent/execution.js";
|
|
8
7
|
import { runExecution, unwrapHarnessReply } from "../../integrations/agent/runner-dispatch.js";
|
|
9
8
|
/** Lower a frozen common request through its frozen runner only. */
|
|
10
9
|
export function prepareWorkflowExecution(request, prompt = request.prompt) {
|
|
11
10
|
const target = request.frozenTarget;
|
|
12
|
-
if (target.cwdIdentity)
|
|
13
|
-
assertFrozenDirectoryContained(target.cwdIdentity);
|
|
14
11
|
const wire = JSON.parse(canonicalResolvedExecutionRequest(target.request));
|
|
15
12
|
const command = { ...wire.command, content: prompt };
|
|
16
13
|
const runtime = {
|
|
17
14
|
...wire.runtime,
|
|
18
|
-
...(request.cwd !== undefined
|
|
19
|
-
? { workspace: request.cwd }
|
|
20
|
-
: target.cwdIdentity
|
|
21
|
-
? { workspace: target.cwdIdentity.realCwd }
|
|
22
|
-
: {}),
|
|
15
|
+
...(request.cwd !== undefined ? { workspace: request.cwd } : {}),
|
|
23
16
|
...(request.env !== undefined ? { environment: request.env } : {}),
|
|
24
17
|
};
|
|
25
18
|
wire.command = command;
|
|
@@ -1,140 +1,35 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
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
|
-
import fs from "node:fs";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import dotenv from "dotenv";
|
|
7
|
-
import { loadAdapterExecutionSource } from "../../commands/command/execution-source-loader.js";
|
|
8
4
|
import { makeBundleRef, parseBundleRef } from "../../core/asset/asset-ref.js";
|
|
9
|
-
import { compareCodePoints, isWithin, toPosix } from "../../core/common.js";
|
|
10
5
|
import { parseEnvRef } from "../../core/env-secret-ref.js";
|
|
11
6
|
import { UsageError } from "../../core/errors.js";
|
|
12
|
-
import {
|
|
7
|
+
import { resolveConceptOwner } from "../../indexer/lookup/adapter-concept-owner.js";
|
|
13
8
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
14
|
-
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
15
|
-
import { SECRET_TOKEN_RE } from "../exec/environment.js";
|
|
16
9
|
/**
|
|
17
|
-
* A step's frozen environment: `pass_env` names, then
|
|
18
|
-
*
|
|
19
|
-
*
|
|
10
|
+
* A step's frozen environment: `pass_env` names, then one bundle-qualified
|
|
11
|
+
* ref per `unit.env` entry. The env file, its keys and the secrets it names
|
|
12
|
+
* are read at dispatch (`exec/environment.ts`), never frozen.
|
|
20
13
|
*/
|
|
21
14
|
export function freezeEnvironment(source, exec, context) {
|
|
22
15
|
const passThrough = (exec?.passEnv ?? []).map((name) => Object.freeze({ kind: "pass-through", name }));
|
|
23
16
|
const seen = new Set();
|
|
24
17
|
const envRefs = [];
|
|
25
18
|
for (const [precedence, ref] of (source.unit?.env ?? []).entries()) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const owned = resolveOwnedAssetSync(ref, "env", context);
|
|
30
|
-
if (seen.has(owned.ref))
|
|
19
|
+
parseEnvRef(ref);
|
|
20
|
+
const qualified = qualifyEnvRef(ref, context);
|
|
21
|
+
if (seen.has(qualified))
|
|
31
22
|
continue;
|
|
32
|
-
seen.add(
|
|
33
|
-
|
|
34
|
-
const secretNames = new Set();
|
|
35
|
-
for (const value of Object.values(parsed)) {
|
|
36
|
-
for (const match of value.matchAll(SECRET_TOKEN_RE))
|
|
37
|
-
if (match[1])
|
|
38
|
-
secretNames.add(match[1]);
|
|
39
|
-
}
|
|
40
|
-
envRefs.push(Object.freeze({
|
|
41
|
-
kind: "env-ref",
|
|
42
|
-
ref: owned.ref,
|
|
43
|
-
owner: Object.freeze({
|
|
44
|
-
bundle: owned.bundle,
|
|
45
|
-
adapter: owned.adapter,
|
|
46
|
-
requestedRoot: owned.root,
|
|
47
|
-
requestedPath: owned.file,
|
|
48
|
-
relativePath: toPosix(path.relative(owned.root, owned.file)),
|
|
49
|
-
}),
|
|
50
|
-
keys: Object.keys(parsed).sort(compareCodePoints),
|
|
51
|
-
secretNames: [...secretNames].sort(compareCodePoints),
|
|
52
|
-
precedence,
|
|
53
|
-
}));
|
|
23
|
+
seen.add(qualified);
|
|
24
|
+
envRefs.push(Object.freeze({ kind: "env-ref", ref: qualified, precedence }));
|
|
54
25
|
}
|
|
55
26
|
return [...passThrough, ...envRefs];
|
|
56
27
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
const owned = await resolveOwnedAsset(ref, kind === "command" ? "command" : "agent", context);
|
|
60
|
-
return kind === "command"
|
|
61
|
-
? loadAdapterExecutionSource(owned.ref, "command", { config: context.config })
|
|
62
|
-
: loadAdapterExecutionSource(owned.ref, "persona", { config: context.config });
|
|
63
|
-
}
|
|
64
|
-
export async function resolveOwnedAsset(ref, type, context) {
|
|
65
|
-
return resolveOwnedAssetCore(ref, type, context, false);
|
|
66
|
-
}
|
|
67
|
-
export function resolveOwnedAssetSync(ref, type, context) {
|
|
68
|
-
return resolveOwnedAssetCore(ref, type, context, true);
|
|
69
|
-
}
|
|
70
|
-
function resolveOwnedAssetCore(refInput, type, context, sync) {
|
|
28
|
+
/** Qualify an env ref a step names with the bundle that owns it right now, through the one walk. */
|
|
29
|
+
function qualifyEnvRef(refInput, context) {
|
|
71
30
|
const parsed = parseBundleRef(refInput);
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const name = conceptId.slice(plural.length + 1);
|
|
75
|
-
const direct = parsed.bundle ? configuredOwner(parsed.bundle, context.config) : undefined;
|
|
76
|
-
const candidates = direct ? [direct] : installedOwners(parsed.bundle, context.config);
|
|
77
|
-
const findSync = () => {
|
|
78
|
-
for (const candidate of candidates) {
|
|
79
|
-
const directory = path.join(candidate.root, plural);
|
|
80
|
-
for (const extension of assetExtensions(type)) {
|
|
81
|
-
const file = path.resolve(directory, `${name}${extension}`);
|
|
82
|
-
if (fs.existsSync(file) && fs.statSync(file).isFile()) {
|
|
83
|
-
if (!isWithin(file, candidate.root)) {
|
|
84
|
-
throw new UsageError(`${file} resolves outside its owning root.`, "PATH_ESCAPE_VIOLATION");
|
|
85
|
-
}
|
|
86
|
-
return { ...candidate, ref: makeBundleRef(candidate.bundle, conceptId), file };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
31
|
+
const match = resolveConceptOwner({ bundle: parsed.bundle, conceptId: parsed.conceptId }, resolveSourceEntries(undefined, context.config), { includeSensitive: true });
|
|
32
|
+
if (!match)
|
|
90
33
|
throw new UsageError(`Workflow source target ${refInput} was not found.`, "INVALID_FLAG_VALUE");
|
|
91
|
-
|
|
92
|
-
if (sync)
|
|
93
|
-
return findSync();
|
|
94
|
-
return (async () => {
|
|
95
|
-
for (const candidate of candidates) {
|
|
96
|
-
try {
|
|
97
|
-
const file = await resolveAssetPath(candidate.root, type, name);
|
|
98
|
-
return { ...candidate, ref: makeBundleRef(candidate.bundle, conceptId), file };
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// Continue in installation priority order.
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return findSync();
|
|
105
|
-
})();
|
|
106
|
-
}
|
|
107
|
-
/** Every installed bundle root (or just `bundle`'s), in installation priority order. */
|
|
108
|
-
function installedOwners(bundle, config) {
|
|
109
|
-
const sources = resolveSourceEntries(undefined, config);
|
|
110
|
-
const installations = deriveInstallations(sources);
|
|
111
|
-
return sources.flatMap((source, index) => {
|
|
112
|
-
const installation = installations[index];
|
|
113
|
-
if (!installation || (bundle && installation.id !== bundle))
|
|
114
|
-
return [];
|
|
115
|
-
return [
|
|
116
|
-
{
|
|
117
|
-
bundle: installation.id,
|
|
118
|
-
root: source.path,
|
|
119
|
-
adapter: source.adapterId ?? installation.components[0]?.adapter ?? "akm",
|
|
120
|
-
},
|
|
121
|
-
];
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function configuredOwner(bundle, config) {
|
|
125
|
-
const entry = config.bundles?.[bundle];
|
|
126
|
-
if (!entry || typeof entry.path !== "string")
|
|
127
|
-
return undefined;
|
|
128
|
-
const components = entry.components ? Object.values(entry.components) : [];
|
|
129
|
-
const component = components[0];
|
|
130
|
-
return {
|
|
131
|
-
bundle,
|
|
132
|
-
root: path.resolve(entry.path, component?.root ?? "."),
|
|
133
|
-
adapter: component?.adapter ?? "akm",
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function assetExtensions(type) {
|
|
137
|
-
if (type === "env")
|
|
138
|
-
return ["", ".env"];
|
|
139
|
-
return ["", ".md"];
|
|
34
|
+
return makeBundleRef(match.bundleId, parsed.conceptId);
|
|
140
35
|
}
|
|
@@ -45,14 +45,13 @@ export function durableRequest(request) {
|
|
|
45
45
|
wire.runtime = runtime;
|
|
46
46
|
return decodeResolvedExecutionRequest(wire);
|
|
47
47
|
}
|
|
48
|
-
export function executionUnitValues(unit
|
|
48
|
+
export function executionUnitValues(unit) {
|
|
49
49
|
return Object.freeze({
|
|
50
50
|
...(unit && Object.hasOwn(unit, "engine") ? { engine: unit.engine } : {}),
|
|
51
51
|
...(unit && Object.hasOwn(unit, "model") ? { model: unit.model } : {}),
|
|
52
52
|
...(unit && Object.hasOwn(unit, "llm") ? { inference: unit.llm } : {}),
|
|
53
53
|
...(unit && Object.hasOwn(unit, "timeoutMs") ? { timeout: unit.timeoutMs } : {}),
|
|
54
54
|
...(unit && "output" in unit && Object.hasOwn(unit, "output") ? { outputSchema: unit.output } : {}),
|
|
55
|
-
workspace,
|
|
56
55
|
});
|
|
57
56
|
}
|
|
58
57
|
/** Step ids declared BEFORE `stepId` — the same ordering `map.over` and `inputs:` rely on. */
|
|
@@ -11,7 +11,6 @@ import { UsageError } from "../../../core/errors.js";
|
|
|
11
11
|
import { workflowParamContract } from "../../ir/params.js";
|
|
12
12
|
import { canonicalJson, canonicalPlanJson } from "../../ir/plan-hash.js";
|
|
13
13
|
import { loadWorkflowAsset } from "../../runtime/workflow-asset-loader.js";
|
|
14
|
-
import { resolveOwnedAsset } from "../environment.js";
|
|
15
14
|
import { declaredParamNames, earlierStepIds, } from "../step-values.js";
|
|
16
15
|
import { freezeTaskInputBindings } from "../task-bindings.js";
|
|
17
16
|
/** The child target's own content identity: its ref, embedded plan hash, and bindings. */
|
|
@@ -46,10 +45,9 @@ function assertNoCompositionCycle(stepId, childRef, refPath) {
|
|
|
46
45
|
}
|
|
47
46
|
export async function childWorkflowDispatch(input) {
|
|
48
47
|
const { source, baseUnit, childRefInput, context } = input;
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
const
|
|
52
|
-
const childAsset = await loadWorkflowAsset(owned.ref);
|
|
48
|
+
// The loader resolves the ref through the one walk, so a missing child fails
|
|
49
|
+
// like any other missing workflow.
|
|
50
|
+
const childAsset = await loadWorkflowAsset(childRefInput);
|
|
53
51
|
const childRef = childAsset.ref;
|
|
54
52
|
// The composition cycle check, before any child compilation.
|
|
55
53
|
assertNoCompositionCycle(source.id, childRef, context.refPath);
|
|
@@ -3,29 +3,24 @@
|
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { prepareCommandInvocation } from "../../../commands/command/command-execution.js";
|
|
6
|
-
import { captureFrozenDirectoryIdentity } from "../../../execution/directory-identity.js";
|
|
7
6
|
import { canonicalResolvedExecutionRequest, } from "../../../execution/resolved-request.js";
|
|
8
7
|
import { fallbackAnnouncement } from "../../../integrations/agent/engine-fallback.js";
|
|
9
8
|
import { buildExecution } from "../../../integrations/agent/execution.js";
|
|
10
|
-
import { freezeEnvironment
|
|
9
|
+
import { freezeEnvironment } from "../environment.js";
|
|
11
10
|
import { durableRequest, executionUnitValues, targetConcurrency, } from "../step-values.js";
|
|
12
11
|
export async function commandDispatch(source, baseUnit, action, context) {
|
|
13
12
|
const prepared = await prepareCommandInvocation({
|
|
14
13
|
action,
|
|
15
14
|
config: context.config,
|
|
16
|
-
...(context.plan.defaults
|
|
17
|
-
? { invocationDefaults: executionUnitValues(context.plan.defaults, context.asset.sourcePath) }
|
|
18
|
-
: {}),
|
|
15
|
+
...(context.plan.defaults ? { invocationDefaults: executionUnitValues(context.plan.defaults) } : {}),
|
|
19
16
|
inlineContentMode: "literal",
|
|
20
|
-
current: executionUnitValues(source.unit
|
|
21
|
-
sourceLoader: (ref, kind) => workflowExecutionSource(ref, kind, context),
|
|
17
|
+
current: executionUnitValues(source.unit),
|
|
22
18
|
});
|
|
23
19
|
return commandResult(source, baseUnit, prepared, context);
|
|
24
20
|
}
|
|
25
21
|
export function commandResult(source, baseUnit, prepared, context, literals = []) {
|
|
26
22
|
const request = durableRequest(prepared.request);
|
|
27
23
|
const lowered = buildExecution(request, prepared.runner);
|
|
28
|
-
const cwdIdentity = captureFrozenDirectoryIdentity(context.asset.sourcePath);
|
|
29
24
|
const runner = lowered.runner;
|
|
30
25
|
const unit = {
|
|
31
26
|
...baseUnit,
|
|
@@ -43,7 +38,9 @@ export function commandResult(source, baseUnit, prepared, context, literals = []
|
|
|
43
38
|
request: JSON.parse(canonicalResolvedExecutionRequest(request)),
|
|
44
39
|
runner,
|
|
45
40
|
...(targetConcurrency(runner, context.config) ? { concurrency: targetConcurrency(runner, context.config) } : {}),
|
|
46
|
-
|
|
41
|
+
// Not a location. akm 0.10.0 and earlier decode a command-only plan and run it in the invoking directory,
|
|
42
|
+
// but their dispatch refuses a directory identity it cannot verify, so they stop here instead.
|
|
43
|
+
cwdIdentity: {},
|
|
47
44
|
});
|
|
48
45
|
const engineAnnouncement = fallbackAnnouncement(prepared.fallbackEngineName, request.engine.name);
|
|
49
46
|
return {
|