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
|
@@ -1,7 +1,6 @@
|
|
|
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 { captureFrozenDirectoryIdentity } from "../../../execution/directory-identity.js";
|
|
5
4
|
import { freezeEnvironment } from "../environment.js";
|
|
6
5
|
import { freezeExecSpec, } from "../step-values.js";
|
|
7
6
|
export function directShell(source, baseUnit, context) {
|
|
@@ -9,13 +8,11 @@ export function directShell(source, baseUnit, context) {
|
|
|
9
8
|
if (!authoredExec)
|
|
10
9
|
throw new Error(`workflow shell step ${source.id} has no argv`);
|
|
11
10
|
const exec = freezeExecSpec(source, authoredExec, context);
|
|
12
|
-
const cwdIdentity = captureFrozenDirectoryIdentity(context.asset.sourcePath, authoredExec.cwd);
|
|
13
11
|
const environment = Object.freeze(freezeEnvironment(source, authoredExec, context));
|
|
14
12
|
const target = Object.freeze({
|
|
15
13
|
kind: "shell",
|
|
16
14
|
contentHash: "",
|
|
17
15
|
exec,
|
|
18
|
-
cwdIdentity,
|
|
19
16
|
});
|
|
20
17
|
return {
|
|
21
18
|
target,
|
package/dist/workflows/parser.js
CHANGED
|
@@ -35,7 +35,7 @@ import { LineCounter, parseDocument } from "yaml";
|
|
|
35
35
|
import { bundleRefToString, parseBundleRef } from "../core/asset/asset-ref.js";
|
|
36
36
|
import { parseFrontmatterBlock } from "../core/asset/frontmatter.js";
|
|
37
37
|
import { parseMarkdownToc } from "../core/asset/markdown.js";
|
|
38
|
-
import {
|
|
38
|
+
import { isRecord } from "../core/common.js";
|
|
39
39
|
import { formatExtraParamsIssue, validateExtraParams } from "../core/extra-params.js";
|
|
40
40
|
import { checkJsonSchemaDefinition, JSON_SCHEMA_SUBSET_SUPPORTED_KEYWORDS } from "../core/json-schema.js";
|
|
41
41
|
import { INPUT_NAME_PATTERN } from "../execution/input-contract.js";
|
|
@@ -182,7 +182,6 @@ export function parseWorkflow(markdown, source) {
|
|
|
182
182
|
const ctx = {
|
|
183
183
|
filePath: path,
|
|
184
184
|
errors,
|
|
185
|
-
...(source.validateExecCwd ? { validateExecCwd: source.validateExecCwd } : {}),
|
|
186
185
|
lineAt,
|
|
187
186
|
lineAtOffset: (offset) => Math.max(1, lineCounter.linePos(offset).line + lineOffset),
|
|
188
187
|
nodeAt: (p) => (p.length === 0 ? doc.contents : doc.getIn(p, true)),
|
|
@@ -829,36 +828,18 @@ function parseExecCommand(ctx, raw, path, stepLabel) {
|
|
|
829
828
|
return argv;
|
|
830
829
|
}
|
|
831
830
|
/**
|
|
832
|
-
* The optional
|
|
833
|
-
*
|
|
834
|
-
*
|
|
831
|
+
* The optional `cwd:`: a non-empty string, relative to the workflow's bundle root or absolute, and
|
|
832
|
+
* free to carry `${ref}` tokens. Nothing else is checked here; the unit's working directory is
|
|
833
|
+
* resolved at dispatch.
|
|
835
834
|
*/
|
|
836
835
|
function parseExecCwd(ctx, raw, path, stepLabel) {
|
|
837
836
|
if (raw === undefined)
|
|
838
837
|
return undefined;
|
|
839
838
|
if (typeof raw !== "string" || raw.trim() === "") {
|
|
840
|
-
ctx.err(path, `${stepLabel} "exec.cwd" must be a non-empty
|
|
839
|
+
ctx.err(path, `${stepLabel} "exec.cwd" must be a non-empty string.`);
|
|
841
840
|
return undefined;
|
|
842
841
|
}
|
|
843
|
-
|
|
844
|
-
if (!isContainedRelativePath(value)) {
|
|
845
|
-
ctx.err(path, `${stepLabel} "exec.cwd" (${JSON.stringify(value)}) must be a RELATIVE path inside the unit's working ` +
|
|
846
|
-
`directory — absolute paths, Windows drive letters, "~", and ".." segments are rejected.`);
|
|
847
|
-
return undefined;
|
|
848
|
-
}
|
|
849
|
-
if (ctx.validateExecCwd) {
|
|
850
|
-
const semantic = ctx.validateExecCwd(value);
|
|
851
|
-
if (!semantic.ok) {
|
|
852
|
-
ctx.errors.push({
|
|
853
|
-
code: semantic.code,
|
|
854
|
-
line: ctx.lineAt(path),
|
|
855
|
-
message: semantic.message,
|
|
856
|
-
});
|
|
857
|
-
return undefined;
|
|
858
|
-
}
|
|
859
|
-
return semantic.value;
|
|
860
|
-
}
|
|
861
|
-
return value;
|
|
842
|
+
return raw.trim();
|
|
862
843
|
}
|
|
863
844
|
function parseMap(ctx, raw, path, stepLabel) {
|
|
864
845
|
if (!isRecord(raw)) {
|
package/dist/workflows/plan.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
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
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Stored plans record this; 0.9.15 and 0.9.16 wrote 5 and early 0.10 wrote 6,
|
|
6
|
+
* which still decode (the 0.9.15 floor, #1091). 7 stopped freezing locations:
|
|
7
|
+
* the plan names the refs the engine dereferences, never a directory or file.
|
|
8
|
+
*/
|
|
9
|
+
export const WORKFLOW_PLAN_VERSION = 7;
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* declares one.
|
|
10
10
|
*/
|
|
11
11
|
import { displayRef } from "../core/asset/resolve-ref.js";
|
|
12
|
+
import { isInstalledBundle, resolveRefOwner } from "../core/env-secret-ref.js";
|
|
12
13
|
import { UsageError } from "../core/errors.js";
|
|
14
|
+
import { substituteRefTokens } from "../core/ref-token.js";
|
|
13
15
|
import { compileWorkflowSource, workflowStepInstructions } from "./compile.js";
|
|
14
16
|
function shellQuote(value) {
|
|
15
17
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -25,7 +27,7 @@ function deriveName(ctx) {
|
|
|
25
27
|
return ext > 0 ? ctx.relPath.slice(0, ext) : ctx.relPath;
|
|
26
28
|
}
|
|
27
29
|
function loadPlan(ctx) {
|
|
28
|
-
const result = compileWorkflowSource(ctx.content(), { path: ctx.relPath
|
|
30
|
+
const result = compileWorkflowSource(ctx.content(), { path: ctx.relPath });
|
|
29
31
|
if (result.ok)
|
|
30
32
|
return result.plan;
|
|
31
33
|
const summary = result.errors.map((e) => `${e.path}:${e.line} — ${e.message}`).join("\n");
|
|
@@ -70,10 +72,14 @@ function projectParameters(plan) {
|
|
|
70
72
|
* It is far smaller than the step `instructions` this same projection
|
|
71
73
|
* already carries whole.
|
|
72
74
|
*/
|
|
73
|
-
function summarizeStepOrchestration(step, defaults) {
|
|
75
|
+
export function summarizeStepOrchestration(step, defaults) {
|
|
74
76
|
const spec = step.spec;
|
|
75
77
|
const unit = spec?.unit;
|
|
76
|
-
|
|
78
|
+
// A stored plan carries no authored spec: read the same fields off its frozen dispatch.
|
|
79
|
+
const frozen = spec ? undefined : frozenDispatch(step);
|
|
80
|
+
const exec = spec ? spec.exec : frozen?.exec;
|
|
81
|
+
const env = unit?.env ?? frozen?.env;
|
|
82
|
+
const workflow = spec ? spec.workflow : frozen?.workflow;
|
|
77
83
|
const engine = exec ? undefined : (unit?.engine ?? defaults?.engine);
|
|
78
84
|
const model = exec ? undefined : (unit?.model ?? defaults?.model);
|
|
79
85
|
const timeoutMs = unit?.timeoutMs !== undefined ? unit.timeoutMs : defaults?.timeoutMs;
|
|
@@ -93,10 +99,77 @@ function summarizeStepOrchestration(step, defaults) {
|
|
|
93
99
|
}
|
|
94
100
|
: {}),
|
|
95
101
|
...(unit?.output !== undefined || step.outputSchema !== undefined ? { hasSchema: true } : {}),
|
|
96
|
-
...(
|
|
102
|
+
...(env !== undefined ? { env: [...env] } : {}),
|
|
103
|
+
...(workflow !== undefined ? { workflow } : {}),
|
|
97
104
|
};
|
|
98
105
|
return Object.keys(summary).length > 0 ? summary : undefined;
|
|
99
106
|
}
|
|
107
|
+
/** The exec argv, env refs and child workflow a frozen step dispatches. */
|
|
108
|
+
function frozenDispatch(step) {
|
|
109
|
+
const root = step.root;
|
|
110
|
+
const unit = root?.kind === "map" ? root.template : root;
|
|
111
|
+
if (!unit)
|
|
112
|
+
return {};
|
|
113
|
+
const target = unit.frozenTarget;
|
|
114
|
+
const env = unit.environment.flatMap((binding) => (binding.kind === "env-ref" ? [binding.ref] : []));
|
|
115
|
+
return {
|
|
116
|
+
...(target.kind === "shell"
|
|
117
|
+
? {
|
|
118
|
+
exec: {
|
|
119
|
+
command: [...target.exec.command],
|
|
120
|
+
...(target.exec.cwd !== undefined ? { cwd: target.exec.cwd } : {}),
|
|
121
|
+
...(target.exec.passEnv ? { passEnv: [...target.exec.passEnv] } : {}),
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
: {}),
|
|
125
|
+
...(env.length > 0 ? { env } : {}),
|
|
126
|
+
...(target.kind === "child-workflow" ? { workflow: target.ref } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Add the bundle-qualified ref every `${ref}` token in the argv or cwd, every
|
|
131
|
+
* `env:` entry and the child `workflow:` resolves to right now, through the
|
|
132
|
+
* one owner walk (`resolveRefOwner`). A ref no bundle owns lands in
|
|
133
|
+
* `unresolved` instead; callers decide whether that is an error. No secret
|
|
134
|
+
* is read and no path is returned.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveOrchestrationRefs(summary) {
|
|
137
|
+
const refs = [];
|
|
138
|
+
const unresolved = [];
|
|
139
|
+
const note = (ref, where) => {
|
|
140
|
+
let owner;
|
|
141
|
+
try {
|
|
142
|
+
owner = resolveRefOwner(ref);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
owner = undefined;
|
|
146
|
+
}
|
|
147
|
+
if (owner)
|
|
148
|
+
refs.push({ ref, resolved: owner.ref, in: where });
|
|
149
|
+
else
|
|
150
|
+
unresolved.push({ ref, in: where });
|
|
151
|
+
};
|
|
152
|
+
const tokens = (values, where) => {
|
|
153
|
+
const seen = new Set();
|
|
154
|
+
substituteRefTokens(Object.fromEntries(values.map((value, index) => [String(index), value])), (ref) => {
|
|
155
|
+
if (!seen.has(ref)) {
|
|
156
|
+
seen.add(ref);
|
|
157
|
+
note(ref, where);
|
|
158
|
+
}
|
|
159
|
+
return { kind: "path", path: "" };
|
|
160
|
+
}, isInstalledBundle);
|
|
161
|
+
};
|
|
162
|
+
if (summary.exec) {
|
|
163
|
+
tokens(summary.exec.command, "argv");
|
|
164
|
+
if (summary.exec.cwd !== undefined)
|
|
165
|
+
tokens([summary.exec.cwd], "cwd");
|
|
166
|
+
}
|
|
167
|
+
for (const ref of summary.env ?? [])
|
|
168
|
+
note(ref, "env");
|
|
169
|
+
if (summary.workflow !== undefined)
|
|
170
|
+
note(summary.workflow, "workflow");
|
|
171
|
+
return { ...summary, refs, ...(unresolved.length > 0 ? { unresolved } : {}) };
|
|
172
|
+
}
|
|
100
173
|
function projectStepDefinitions(plan) {
|
|
101
174
|
return plan.steps.map((step, sequenceIndex) => {
|
|
102
175
|
const orchestration = summarizeStepOrchestration(step, plan.defaults);
|
|
@@ -7,15 +7,20 @@
|
|
|
7
7
|
*
|
|
8
8
|
* {@link decodeWorkflowPlan} accepts every plan shape a release at or above
|
|
9
9
|
* the 0.9.15 floor has stored — irVersion 5 (with its `sourceReadSet` and
|
|
10
|
-
* host-identity fields, which 0.9.15 and 0.9.16 wrote)
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* host-identity fields, which 0.9.15 and 0.9.16 wrote) and irVersion 6 (with
|
|
11
|
+
* its frozen `cwdIdentity` and env owner paths) as well as the current one —
|
|
12
|
+
* and keeps keys it does not know rather than refusing them. A stored target
|
|
13
|
+
* is kept exactly as stored, location fields included: the unit `input_hash`
|
|
14
|
+
* and the child invocation key derive from the whole target, so dropping them
|
|
15
|
+
* would make an interrupted run publish a second child after an upgrade.
|
|
16
|
+
* Dispatch finds the bundle and the env asset by ref and never reads them. It
|
|
17
|
+
* rejects only a plan it cannot run: no steps, a unit with no frozen target, a
|
|
18
|
+
* request that does not decode. `plan_hash` is an informational column. A plan
|
|
19
|
+
* that cannot be decoded comes back from {@link readRunPlan} as a `problem`
|
|
16
20
|
* sentence and the caller decides the remedy (the engine abandons such a run
|
|
17
|
-
* instead of throwing) — except for a plan a NEWER akm froze
|
|
18
|
-
*
|
|
21
|
+
* instead of throwing) — except for a plan a NEWER akm froze (`plan_ir_version`
|
|
22
|
+
* above this release's), which is refused unread as `newer` with a problem that
|
|
23
|
+
* names upgrading akm as the remedy.
|
|
19
24
|
*/
|
|
20
25
|
import { UsageError } from "../../core/errors.js";
|
|
21
26
|
import { warnOnce } from "../../core/warn.js";
|
|
@@ -26,6 +31,14 @@ export function readRunPlan(row) {
|
|
|
26
31
|
if (!row.plan_json)
|
|
27
32
|
return { ok: false, newer: false, problem: `Workflow run ${row.id} has no frozen workflow plan.` };
|
|
28
33
|
const version = row.plan_ir_version ?? null;
|
|
34
|
+
if (version !== null && version > WORKFLOW_PLAN_VERSION) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
newer: true,
|
|
38
|
+
problem: `Workflow run ${row.id} was frozen by a newer akm (plan irVersion ${version}) and this akm ` +
|
|
39
|
+
`(irVersion ${WORKFLOW_PLAN_VERSION}) will not run it. Upgrade akm to continue this run.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
29
42
|
try {
|
|
30
43
|
const plan = decodeWorkflowPlan(JSON.parse(row.plan_json), {
|
|
31
44
|
...(row.workflow_ref ? { workflowRef: row.workflow_ref } : {}),
|
|
@@ -38,14 +51,6 @@ export function readRunPlan(row) {
|
|
|
38
51
|
}
|
|
39
52
|
catch (cause) {
|
|
40
53
|
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
41
|
-
if (version !== null && version > WORKFLOW_PLAN_VERSION) {
|
|
42
|
-
return {
|
|
43
|
-
ok: false,
|
|
44
|
-
newer: true,
|
|
45
|
-
problem: `Workflow run ${row.id} was frozen by a newer akm (plan irVersion ${version}) and this akm cannot decode it: ` +
|
|
46
|
-
`${detail}. Upgrade akm to continue this run.`,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
54
|
const frozenAs = version === null ? "" : ` (frozen as plan irVersion ${version})`;
|
|
50
55
|
return {
|
|
51
56
|
ok: false,
|
|
@@ -166,7 +171,7 @@ function decodeTarget(value, label) {
|
|
|
166
171
|
kind: "shell",
|
|
167
172
|
contentHash: string(target.contentHash, `${label} contentHash`),
|
|
168
173
|
exec: decodeExec(target.exec, label),
|
|
169
|
-
|
|
174
|
+
...legacyField(target, "cwdIdentity"),
|
|
170
175
|
...optional,
|
|
171
176
|
};
|
|
172
177
|
case "child-workflow":
|
|
@@ -203,12 +208,15 @@ function decodeCommandTarget(target, label) {
|
|
|
203
208
|
request,
|
|
204
209
|
runner,
|
|
205
210
|
...(concurrency !== undefined ? { concurrency } : {}),
|
|
206
|
-
...(target
|
|
207
|
-
? { cwdIdentity: target.cwdIdentity }
|
|
208
|
-
: {}),
|
|
211
|
+
...legacyField(target, "cwdIdentity"),
|
|
209
212
|
...(inputBindings ? { inputBindings } : {}),
|
|
210
213
|
};
|
|
211
214
|
}
|
|
215
|
+
/** A location field a stored target carries, kept as stored (see the module doc). */
|
|
216
|
+
function legacyField(source, key) {
|
|
217
|
+
const value = source[key];
|
|
218
|
+
return value && typeof value === "object" ? { [key]: value } : {};
|
|
219
|
+
}
|
|
212
220
|
function decodeExec(value, label) {
|
|
213
221
|
const exec = record(value, `${label} exec`);
|
|
214
222
|
const command = exec.command;
|
|
@@ -246,14 +254,20 @@ function decodeEnvironmentBinding(value, label) {
|
|
|
246
254
|
return { kind: "pass-through", name: string(binding.name, label) };
|
|
247
255
|
if (binding.kind !== "env-ref")
|
|
248
256
|
fail(`${label} has an unsupported kind`);
|
|
257
|
+
// Plans before irVersion 7 also carry the env file's owner, key names and secret names: kept as stored,
|
|
258
|
+
// never read (dispatch reads the env asset by ref).
|
|
249
259
|
const strings = (items) => Array.isArray(items) ? items.filter((item) => typeof item === "string") : [];
|
|
250
260
|
return {
|
|
251
261
|
kind: "env-ref",
|
|
252
262
|
ref: string(binding.ref, `${label} ref`),
|
|
253
|
-
owner: record(binding.owner, `${label} owner`),
|
|
254
|
-
keys: strings(binding.keys),
|
|
255
|
-
secretNames: strings(binding.secretNames),
|
|
256
263
|
precedence: typeof binding.precedence === "number" ? binding.precedence : 0,
|
|
264
|
+
...(binding.owner && typeof binding.owner === "object"
|
|
265
|
+
? {
|
|
266
|
+
owner: binding.owner,
|
|
267
|
+
keys: strings(binding.keys),
|
|
268
|
+
secretNames: strings(binding.secretNames),
|
|
269
|
+
}
|
|
270
|
+
: {}),
|
|
257
271
|
};
|
|
258
272
|
}
|
|
259
273
|
function positiveInteger(value) {
|
|
@@ -9,17 +9,17 @@ import { appendEvent } from "../../core/events.js";
|
|
|
9
9
|
import { warn } from "../../core/warn.js";
|
|
10
10
|
import { insertEventOnce } from "../../storage/repositories/events-repository.js";
|
|
11
11
|
import { withWorkflowRunsRepo, } from "../../storage/repositories/workflow-runs-repository.js";
|
|
12
|
-
import {
|
|
13
|
-
import { frozenSummaryJudge } from "../exec/frozen-judge.js";
|
|
12
|
+
import { frozenSummaryJudge, resolveJudgeCwd } from "../exec/frozen-judge.js";
|
|
14
13
|
import { detectSecretShapedParams } from "../exec/param-secrets.js";
|
|
15
14
|
import { freezeWorkflow } from "../freeze/freeze.js";
|
|
16
15
|
import { applyWorkflowParamDefaults, materializeWorkflowParameterFlags, validateWorkflowParams, } from "../ir/params.js";
|
|
17
16
|
import { canonicalPlanJson, computePlanHash } from "../ir/plan-hash.js";
|
|
17
|
+
import { resolveOrchestrationRefs, summarizeStepOrchestration } from "../renderer.js";
|
|
18
18
|
import { clip, WORKFLOW_UNIT_DIAGNOSTIC_CLIP } from "../resource-limits.js";
|
|
19
19
|
import { validateStepSummary } from "../validate-summary.js";
|
|
20
20
|
import { resolveAgentIdentity } from "./agent-identity.js";
|
|
21
|
-
import { frozenStepRows, readRunPlan } from "./run-plan.js";
|
|
22
|
-
import { canonicalizeWorkflowRefInput, loadWorkflowAsset
|
|
21
|
+
import { decodeWorkflowPlan, frozenStepRows, readRunPlan } from "./run-plan.js";
|
|
22
|
+
import { canonicalizeWorkflowRefInput, loadWorkflowAsset } from "./workflow-asset-loader.js";
|
|
23
23
|
/**
|
|
24
24
|
* Membership test for the journaled `runner` column, which is an untyped string.
|
|
25
25
|
* The `Record<WorkflowRuntimeKind, …>` is exhaustiveness-checked, so a new runtime
|
|
@@ -98,38 +98,21 @@ export async function startWorkflowRun(ref, params = {}, options) {
|
|
|
98
98
|
return withWorkflowRunsRepo(async (repo) => {
|
|
99
99
|
const now = new Date().toISOString();
|
|
100
100
|
const runId = randomUUID();
|
|
101
|
-
const scopeKey = getCurrentWorkflowScopeKey();
|
|
102
101
|
const currentStepId = plan.steps[0]?.stepId ?? null;
|
|
103
|
-
const workflowEntryId = resolveWorkflowEntryId(asset.sourcePath, asset.ref, asset.adapterId);
|
|
104
102
|
// Capture the invoking harness/session identity for this run. Explicit options
|
|
105
103
|
// win; otherwise fall back to best-effort environment detection. This is
|
|
106
104
|
// identity-only — no background thread or timer is started here.
|
|
107
105
|
const detected = resolveAgentIdentity();
|
|
108
106
|
const agentHarness = options?.agentHarness !== undefined ? options.agentHarness : detected.harness;
|
|
109
107
|
const agentSessionId = options?.agentSessionId !== undefined ? options.agentSessionId : detected.sessionId;
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
// The active-alias query and all inserts share one immediate transaction.
|
|
115
|
-
// #942: an active run of this ref in a different scope (e.g. a scheduled
|
|
116
|
-
// task's cwd vs. a shell) is warned about once; the uniqueness guard stays
|
|
117
|
-
// scope-local. The query excludes the caller's own scope in SQL.
|
|
118
|
-
const crossScopeActive = repo.findActiveRunOutsideScope(workflowRefs, scopeKey);
|
|
119
|
-
const crossScopeWarning = crossScopeActive
|
|
120
|
-
? `Workflow ${asset.ref} already has an active run in another scope ` +
|
|
121
|
-
`(id ${crossScopeActive.id}, started ${crossScopeActive.created_at}, scope ${crossScopeActive.scope_key ?? "unknown"}); ` +
|
|
122
|
-
`starting a separate run here. Resume it from anywhere with "akm workflow run ${crossScopeActive.id}" ` +
|
|
123
|
-
`or free it with "akm workflow abandon ${crossScopeActive.id}".`
|
|
124
|
-
: undefined;
|
|
108
|
+
// Starting a ref always starts a run. Other active runs of the same ref
|
|
109
|
+
// are not an error: name them once, after the start succeeds, so the
|
|
110
|
+
// operator can continue or abandon the ones they no longer want.
|
|
111
|
+
const otherActive = repo.listRuns({ workflowRefs, activeOnly: true });
|
|
125
112
|
repo.publishWorkflowRunV4({
|
|
126
|
-
workflowRefs,
|
|
127
|
-
...(options?.force ? { force: true } : {}),
|
|
128
113
|
run: {
|
|
129
114
|
id: runId,
|
|
130
115
|
workflowRef: asset.ref,
|
|
131
|
-
scopeKey,
|
|
132
|
-
workflowEntryId,
|
|
133
116
|
workflowTitle: asset.title,
|
|
134
117
|
paramsJson: JSON.stringify(effectiveParams),
|
|
135
118
|
currentStepId,
|
|
@@ -149,9 +132,12 @@ export async function startWorkflowRun(ref, params = {}, options) {
|
|
|
149
132
|
planJson,
|
|
150
133
|
planHash,
|
|
151
134
|
});
|
|
135
|
+
if (otherActive.length > 0) {
|
|
136
|
+
const ids = otherActive.map((run) => run.id);
|
|
137
|
+
warn(`Workflow ${asset.ref} already has ${ids.length} active run(s) (${ids.join(", ")}); started another. ` +
|
|
138
|
+
`Continue one with "akm workflow run ${ids[0]}" or give up on it with "akm workflow abandon ${ids[0]}".`);
|
|
139
|
+
}
|
|
152
140
|
const result = await getWorkflowStatus(runId);
|
|
153
|
-
if (crossScopeWarning)
|
|
154
|
-
result.warnings = [...(result.warnings ?? []), crossScopeWarning];
|
|
155
141
|
// Params reach every unit prompt in clear (only the journal scrubs them).
|
|
156
142
|
// Surface a loud, best-effort warning when a param LOOKS like a credential
|
|
157
143
|
// so the author moves it to an env binding. Advisory only — never blocks
|
|
@@ -167,6 +153,16 @@ export async function startWorkflowRun(ref, params = {}, options) {
|
|
|
167
153
|
return result;
|
|
168
154
|
});
|
|
169
155
|
}
|
|
156
|
+
function decodeStatusPlan(planJson) {
|
|
157
|
+
if (!planJson)
|
|
158
|
+
return undefined;
|
|
159
|
+
try {
|
|
160
|
+
return decodeWorkflowPlan(JSON.parse(planJson));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
170
166
|
export async function getWorkflowStatus(runId, opts) {
|
|
171
167
|
return withWorkflowRunsRepo((repo) => {
|
|
172
168
|
const run = readWorkflowRun(repo, runId);
|
|
@@ -176,25 +172,32 @@ export async function getWorkflowStatus(runId, opts) {
|
|
|
176
172
|
// The honest diagnostic surface (#22): read the unit journal straight and
|
|
177
173
|
// project each row, INCLUDING failures whose diagnostic text the
|
|
178
174
|
// deterministic evidence graph drops. Read-only; never mutates the run.
|
|
179
|
-
|
|
175
|
+
const rows = repo.getUnitsForRun(run.id);
|
|
176
|
+
// The plan is decoded only to describe a failed unit, read-only: no run
|
|
177
|
+
// warning (`readRunPlan` says it is "running" an older plan) and no
|
|
178
|
+
// orchestration when the plan cannot be decoded.
|
|
179
|
+
const plan = rows.some((row) => row.status === "failed") ? decodeStatusPlan(run.plan_json) : undefined;
|
|
180
|
+
detail.units = rows.map((row) => {
|
|
181
|
+
const unit = toUnitDiagnostic(row);
|
|
182
|
+
const step = row.status === "failed" ? plan?.steps.find((candidate) => candidate.stepId === row.step_id) : undefined;
|
|
183
|
+
const summary = step ? summarizeStepOrchestration(step, undefined) : undefined;
|
|
184
|
+
return summary ? { ...unit, orchestration: resolveOrchestrationRefs(summary) } : unit;
|
|
185
|
+
});
|
|
180
186
|
}
|
|
181
187
|
return detail;
|
|
182
188
|
});
|
|
183
189
|
}
|
|
184
190
|
export async function listWorkflowRuns(input) {
|
|
185
|
-
const scopeKey = input?.allScopes === true ? null : getCurrentWorkflowScopeKey();
|
|
186
191
|
const activeOnly = input?.activeOnly === true;
|
|
187
192
|
const includeChildren = input?.includeChildren === true;
|
|
188
193
|
if (input?.workflowRef === undefined) {
|
|
189
194
|
return withWorkflowRunsRepo((repo) => ({
|
|
190
195
|
runs: repo
|
|
191
196
|
.listRuns({
|
|
192
|
-
scopeKey,
|
|
193
197
|
...(activeOnly ? { activeOnly: true } : {}),
|
|
194
198
|
...(includeChildren ? { includeChildren: true } : {}),
|
|
195
199
|
})
|
|
196
200
|
.map(toWorkflowRunSummary),
|
|
197
|
-
scopeKey,
|
|
198
201
|
}));
|
|
199
202
|
}
|
|
200
203
|
const exactRef = input.workflowRef.trim();
|
|
@@ -212,12 +215,11 @@ export async function listWorkflowRuns(input) {
|
|
|
212
215
|
}
|
|
213
216
|
catch (error) {
|
|
214
217
|
if (parsedExactRef.bundle !== undefined) {
|
|
215
|
-
const exactRows = await withWorkflowRunsRepo((repo) => repo.listRuns({
|
|
218
|
+
const exactRows = await withWorkflowRunsRepo((repo) => repo.listRuns({ workflowRef: exactRef, ...(includeChildren ? { includeChildren: true } : {}) }));
|
|
216
219
|
if (exactRows.length === 0)
|
|
217
220
|
throw error;
|
|
218
221
|
return {
|
|
219
222
|
runs: exactRows.filter((row) => !activeOnly || row.status === "active").map(toWorkflowRunSummary),
|
|
220
|
-
scopeKey,
|
|
221
223
|
};
|
|
222
224
|
}
|
|
223
225
|
if (!(error instanceof NotFoundError))
|
|
@@ -226,23 +228,20 @@ export async function listWorkflowRuns(input) {
|
|
|
226
228
|
return withWorkflowRunsRepo((repo) => ({
|
|
227
229
|
runs: repo
|
|
228
230
|
.listRuns({
|
|
229
|
-
scopeKey,
|
|
230
231
|
workflowRefs,
|
|
231
232
|
...(activeOnly ? { activeOnly: true } : {}),
|
|
232
233
|
...(includeChildren ? { includeChildren: true } : {}),
|
|
233
234
|
})
|
|
234
235
|
.map(toWorkflowRunSummary),
|
|
235
|
-
scopeKey,
|
|
236
236
|
}));
|
|
237
237
|
}
|
|
238
238
|
export async function getNextWorkflowStep(specifier, params, options) {
|
|
239
239
|
return withWorkflowRunsRepo(async (repo) => {
|
|
240
|
-
const { run, autoStarted,
|
|
240
|
+
const { run, autoStarted, startWarnings } = await resolveRunSpecifier(repo, specifier, params, options?.parameterFlags);
|
|
241
241
|
const steps = readWorkflowRunSteps(repo, run.id);
|
|
242
242
|
return {
|
|
243
243
|
...projectNextResult(run, steps),
|
|
244
244
|
...(autoStarted ? { autoStarted: true } : {}),
|
|
245
|
-
...(resumed ? { resumed: true } : {}),
|
|
246
245
|
...(startWarnings?.length ? { startWarnings } : {}),
|
|
247
246
|
};
|
|
248
247
|
});
|
|
@@ -348,7 +347,7 @@ export async function completeWorkflowStep(input) {
|
|
|
348
347
|
const stepPlan = storedPlan.steps.find((step) => step.stepId === input.stepId);
|
|
349
348
|
if (!stepPlan)
|
|
350
349
|
throw new NotFoundError(`Step "${input.stepId}" was not found in workflow run ${run.id}.`);
|
|
351
|
-
return { existing, plan: storedPlan, stepPlan };
|
|
350
|
+
return { existing, plan: storedPlan, stepPlan, workflowRef: run.workflow_ref };
|
|
352
351
|
});
|
|
353
352
|
const summary = input.summary?.trim();
|
|
354
353
|
// #506: completing a step requires a summary of the work done.
|
|
@@ -365,10 +364,7 @@ export async function completeWorkflowStep(input) {
|
|
|
365
364
|
? // Manual completion journals no gate row, so there is no `<stepId>.gate:l<loop>`
|
|
366
365
|
// identity to agree with — but the dispatch still names the REAL run and
|
|
367
366
|
// step (frozen-judge falls back to the gate node id for the unit id).
|
|
368
|
-
frozenSummaryJudge(preflight.stepPlan.gate.frozenJudge, input.signal, undefined, {
|
|
369
|
-
runId: input.runId,
|
|
370
|
-
stepId: input.stepId,
|
|
371
|
-
})
|
|
367
|
+
frozenSummaryJudge(preflight.stepPlan.gate.frozenJudge, input.signal, undefined, { runId: input.runId, stepId: input.stepId }, undefined, resolveJudgeCwd(preflight.stepPlan.gate.frozenJudge, preflight.workflowRef))
|
|
372
368
|
: input.summaryJudge;
|
|
373
369
|
if (criteria.length > 0 && !judge) {
|
|
374
370
|
throw new ConfigError(`Workflow run ${input.runId} has completion criteria for step "${input.stepId}" but its frozen plan has no judge. ` +
|
|
@@ -457,57 +453,27 @@ export async function completeWorkflowStep(input) {
|
|
|
457
453
|
return detail;
|
|
458
454
|
});
|
|
459
455
|
}
|
|
460
|
-
async function resolveRunSpecifier(repo, specifier, params, parameterFlags
|
|
461
|
-
const hasParameters = (params && Object.keys(params).length > 0) || (parameterFlags?.length ?? 0) > 0;
|
|
456
|
+
async function resolveRunSpecifier(repo, specifier, params, parameterFlags) {
|
|
462
457
|
const explicitRun = findRunByIdOrPrefix(repo, specifier);
|
|
463
458
|
if (explicitRun) {
|
|
464
|
-
|
|
465
|
-
// run id (or an id prefix), which already names the run to act on (#919).
|
|
466
|
-
if (forceNew) {
|
|
467
|
-
throw new UsageError(`--new starts a fresh run from a workflow ref; "${specifier}" already names a run id.`, "INVALID_FLAG_VALUE");
|
|
468
|
-
}
|
|
469
|
-
if (hasParameters) {
|
|
459
|
+
if ((params && Object.keys(params).length > 0) || (parameterFlags?.length ?? 0) > 0) {
|
|
470
460
|
throw new UsageError(`Workflow parameter flags can only be used when starting a new run, not with existing run id "${specifier}".`);
|
|
471
461
|
}
|
|
472
462
|
return { run: explicitRun, autoStarted: false };
|
|
473
463
|
}
|
|
474
|
-
|
|
475
|
-
const exactRef = specifier.trim();
|
|
476
|
-
const parsedExact = parseBundleRef(exactRef);
|
|
477
|
-
const qualifiedExact = parsedExact.bundle !== undefined && parsedExact.fragment === undefined;
|
|
478
|
-
const detached = qualifiedExact && !forceNew ? repo.getActiveRunRowForScope(exactRef, scopeKey) : undefined;
|
|
464
|
+
// A workflow ref always starts a new run; only a run id drives an existing one.
|
|
479
465
|
let ref;
|
|
480
466
|
try {
|
|
481
467
|
ref = await canonicalizeWorkflowSpecifier(specifier);
|
|
482
468
|
}
|
|
483
469
|
catch (error) {
|
|
484
|
-
if (detached) {
|
|
485
|
-
if (hasParameters) {
|
|
486
|
-
throw new UsageError(`Workflow parameter flags can only be set on a new run; ${specifier} is already active ` +
|
|
487
|
-
`(id ${detached.id}, scope ${scopeKey}).`, "INVALID_FLAG_VALUE", `Pass --new to start a separate run, or run "akm workflow abandon ${detached.id}" to free up ${specifier} first.`);
|
|
488
|
-
}
|
|
489
|
-
return { run: detached, autoStarted: false, resumed: true };
|
|
490
|
-
}
|
|
491
470
|
if (error instanceof NotFoundError && !specifier.includes(":") && !specifier.includes("/")) {
|
|
492
471
|
throw new NotFoundError(`Workflow run or workflow "${specifier}" not found.`, "WORKFLOW_NOT_FOUND");
|
|
493
472
|
}
|
|
494
473
|
throw error;
|
|
495
474
|
}
|
|
496
|
-
const active = forceNew ? undefined : repo.getActiveRunRowForScope(await workflowRunRefSet(ref, exactRef), scopeKey);
|
|
497
|
-
if (active) {
|
|
498
|
-
if (hasParameters) {
|
|
499
|
-
throw new UsageError(`Workflow parameter flags can only be set on a new run; ${ref} is already active ` +
|
|
500
|
-
`(id ${active.id}, scope ${scopeKey}).`, "INVALID_FLAG_VALUE", `Pass --new to start a separate run, or run "akm workflow abandon ${active.id}" to free up ${ref} first.`);
|
|
501
|
-
}
|
|
502
|
-
return { run: active, autoStarted: false, resumed: true };
|
|
503
|
-
}
|
|
504
|
-
// `force` (#485's own escape hatch) is what lets `--new` create a second
|
|
505
|
-
// active run for this (ref, scope) pair instead of the concurrency guard
|
|
506
|
-
// refusing it — the caller explicitly asked for a fresh run, so the guard
|
|
507
|
-
// that exists to catch an ACCIDENTAL second run does not apply (#919).
|
|
508
475
|
const started = await startWorkflowRun(ref, params ?? {}, {
|
|
509
476
|
...(parameterFlags !== undefined ? { parameterFlags } : {}),
|
|
510
|
-
...(forceNew ? { force: true } : {}),
|
|
511
477
|
});
|
|
512
478
|
return {
|
|
513
479
|
run: readWorkflowRun(repo, started.run.id),
|
|
@@ -648,7 +614,6 @@ function toWorkflowRunSummary(run) {
|
|
|
648
614
|
return {
|
|
649
615
|
id: run.id,
|
|
650
616
|
workflowRef: run.workflow_ref,
|
|
651
|
-
scopeKey: run.scope_key,
|
|
652
617
|
workflowEntryId: run.workflow_entry_id,
|
|
653
618
|
workflowTitle: run.workflow_title,
|
|
654
619
|
status: run.status,
|
|
@@ -733,11 +698,3 @@ function parseJsonArray(value) {
|
|
|
733
698
|
}
|
|
734
699
|
return undefined;
|
|
735
700
|
}
|
|
736
|
-
export async function getActiveWorkflowRun(scopeKey = getCurrentWorkflowScopeKey()) {
|
|
737
|
-
return withWorkflowRunsRepo((repo) => {
|
|
738
|
-
const row = repo.findActiveOrBlockedRunForScope(scopeKey);
|
|
739
|
-
if (!row)
|
|
740
|
-
return null;
|
|
741
|
-
return { runId: row.id, stepId: row.current_step_id, workflowRef: row.workflow_ref };
|
|
742
|
-
}).catch(() => null); // fail-open: never crash show output due to DB error
|
|
743
|
-
}
|