akm-cli 0.9.2-alpha.4 → 0.9.2
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 +493 -0
- package/STABILITY.md +23 -5
- package/dist/assets/hints/cli-hints-full.md +12 -7
- package/dist/assets/tasks/core/extract.yml +3 -5
- package/dist/assets/tasks/core/improve.yml +3 -5
- package/dist/assets/tasks/core/index-refresh.yml +3 -5
- package/dist/assets/tasks/core/sync.yml +3 -5
- package/dist/assets/tasks/core/version-check.yml +3 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -6
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +3 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +3 -5
- package/dist/cli/unknown-flags.js +12 -1
- package/dist/cli.js +8 -1
- package/dist/commands/command/command-execution.js +23 -2
- package/dist/commands/health/improve-metrics.js +38 -0
- package/dist/commands/health/windows.js +8 -4
- package/dist/commands/health.js +8 -4
- package/dist/commands/lint/index.js +1 -1
- package/dist/commands/migrate-cli.js +130 -24
- package/dist/commands/proposal/validators/proposal-validators.js +7 -2
- package/dist/commands/tasks/explain.js +304 -0
- package/dist/commands/tasks/tasks-cli.js +185 -3
- package/dist/commands/tasks/tasks.js +265 -52
- package/dist/commands/workflow/plan.js +159 -0
- package/dist/commands/workflow-cli.js +94 -2
- package/dist/core/activation-policy.js +2 -12
- package/dist/core/adapter/adapters/akm-lint.js +7 -4
- package/dist/core/adapter/adapters/akm-metadata.js +26 -14
- package/dist/core/adapter/adapters/akm-task-adapter.js +13 -10
- package/dist/core/errors.js +45 -0
- package/dist/core/json-schema.js +15 -5
- package/dist/core/state/migrations.js +57 -0
- package/dist/core/state-db.js +16 -14
- package/dist/core/subprocess.js +47 -13
- package/dist/execution/guarded-source.js +44 -0
- package/dist/execution/input-contract.js +250 -0
- package/dist/execution/target-ref.js +63 -0
- package/dist/indexer/usage/usage-events.js +14 -3
- package/dist/integrations/agent/execution-lowering.js +12 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +12 -3
- package/dist/output/text/workflow-format.js +192 -10
- package/dist/output/text/workflow.js +2 -1
- package/dist/runtime.js +1 -0
- package/dist/scripts/akm-migrate-node.js +11838 -10118
- package/dist/scripts/akm-migrate.js +11828 -10117
- package/dist/setup/steps/tasks.js +34 -17
- package/dist/storage/repositories/task-history-repository.js +5 -1
- package/dist/storage/repositories/workflow-runs-repository.js +144 -6
- package/dist/tasks/backends/launchd.js +31 -84
- package/dist/tasks/embedded.js +13 -7
- package/dist/tasks/model/invocation.js +4 -0
- package/dist/tasks/prepare/prepare-script-target.js +9 -0
- package/dist/tasks/prepare/prepare-support.js +154 -0
- package/dist/tasks/prepare/prepare.js +117 -0
- package/dist/tasks/prepare/prepared-execution.js +4 -0
- package/dist/tasks/prepare/script-capture.js +80 -0
- package/dist/tasks/run/attempt-lifecycle.js +165 -0
- package/dist/tasks/run/load-task.js +117 -0
- package/dist/tasks/run/provenance.js +20 -0
- package/dist/tasks/run/run-command-task.js +92 -0
- package/dist/tasks/run/run-native-task.js +222 -0
- package/dist/tasks/run/run-task.js +99 -0
- package/dist/tasks/run/run-workflow-task.js +222 -0
- package/dist/tasks/run/task-history.js +134 -0
- package/dist/tasks/run/task-log.js +179 -0
- package/dist/tasks/run/task-result.js +19 -0
- package/dist/tasks/scheduler-binding.js +66 -2
- package/dist/tasks/scheduler-invocation.js +63 -3
- package/dist/tasks/scheduler-sync.js +77 -14
- package/dist/tasks/source/bounded-document.js +455 -0
- package/dist/tasks/source/parse-task-source.js +59 -0
- package/dist/tasks/source/project-v4.js +62 -0
- package/dist/tasks/source/task-input-diagnostics.js +36 -0
- package/dist/tasks/source/task-source-v4.js +626 -0
- package/dist/tasks/source-v3.js +10 -733
- package/dist/tasks/task-run-reserved-flags.js +79 -0
- package/dist/workflows/authoring/authoring.js +17 -8
- package/dist/workflows/exec/child-invocation.js +34 -0
- package/dist/workflows/exec/child-workflow.js +370 -0
- package/dist/workflows/exec/exec-unit.js +50 -170
- package/dist/workflows/exec/frozen-judge.js +19 -2
- package/dist/workflows/exec/native-executor.js +49 -27
- package/dist/workflows/exec/param-secrets.js +12 -0
- package/dist/workflows/exec/run-workflow.js +48 -59
- package/dist/workflows/exec/step-work.js +222 -80
- package/dist/workflows/exec/unit-dispatch.js +72 -0
- package/dist/workflows/freeze/child-output-references.js +94 -0
- package/dist/workflows/freeze/environment.js +174 -0
- package/dist/workflows/freeze/identity.js +22 -0
- package/dist/workflows/freeze/resolve-steps.js +78 -0
- package/dist/workflows/freeze/source-freeze.js +57 -0
- package/dist/workflows/freeze/step-values.js +68 -0
- package/dist/workflows/freeze/targets/child-workflow.js +206 -0
- package/dist/workflows/freeze/targets/command.js +81 -0
- package/dist/workflows/freeze/targets/script.js +57 -0
- package/dist/workflows/freeze/targets/shell.js +31 -0
- package/dist/workflows/freeze/targets/task.js +179 -0
- package/dist/workflows/freeze/task-bindings.js +180 -0
- package/dist/workflows/ir/compile.js +59 -11
- package/dist/workflows/ir/environment-v4.js +3 -3
- package/dist/workflows/ir/freeze-v4.js +41 -7
- package/dist/workflows/ir/params.js +58 -131
- package/dist/workflows/ir/plan-hash.js +3 -3
- package/dist/workflows/ir/schema-v4.js +246 -17
- package/dist/workflows/parser.js +74 -2
- package/dist/workflows/program/schema.js +5 -2
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/plan-classifier.js +24 -7
- package/dist/workflows/runtime/run-outputs.js +103 -0
- package/dist/workflows/runtime/runs.js +114 -9
- package/dist/workflows/runtime/workflow-asset-loader.js +14 -6
- package/dist/workflows/source-files.js +5 -5
- package/dist/workflows/source-ir/compare.js +17 -0
- package/dist/workflows/source-ir/compile.js +7 -3
- package/dist/workflows/source-ir/github-yaml.js +64 -17
- package/dist/workflows/source-ir/schema.js +69 -21
- package/dist/workflows/source-ir/semantics.js +7 -25
- package/dist/workflows/source-ir/triggers.js +79 -0
- package/dist/workflows/source-ir/uses.js +33 -7
- package/docs/migration/README.md +1 -1
- package/docs/migration/release-notes/0.9.2.md +87 -11
- package/docs/migration/release-notes/README.md +3 -2
- package/docs/migration/v0.8-to-v0.9.md +13 -11
- package/docs/migration/v0.9.0-troubleshooting.md +20 -13
- package/docs/migration/v0.9.1-to-v0.9.2.md +598 -49
- package/docs/reference/README.md +1 -1
- package/docs/reference/cli.md +140 -46
- package/docs/reference/configuration.md +6 -5
- package/docs/reference/supported-formats.md +9 -5
- package/docs/reference/tasks.md +338 -75
- package/docs/reference/workflow-schema.md +290 -16
- package/docs/reference/workflows.md +57 -7
- package/package.json +1 -1
- package/schemas/akm-task.json +173 -118
- package/schemas/akm-workflow.json +28 -0
- package/dist/tasks/runner.js +0 -941
- package/dist/tasks/runtime-v3.js +0 -281
- package/dist/workflows/ir/source-freeze-v4.js +0 -506
- package/dist/workflows/source-ir/ordering.js +0 -38
|
@@ -26,14 +26,19 @@ import { commitWriteTargetBoundary, deleteAssetFromSource, prepareWriteTargetFor
|
|
|
26
26
|
import { withEngineFallback } from "../../integrations/agent/engine-fallback.js";
|
|
27
27
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
28
28
|
import { backendNameForPlatform, selectBackend } from "../../tasks/backends/index.js";
|
|
29
|
+
import { prepareTaskV3Execution } from "../../tasks/prepare/prepare.js";
|
|
29
30
|
import { resolveAkmInvocation } from "../../tasks/resolve-akm-bin.js";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
31
|
+
import { createExecutionProvenanceContext } from "../../tasks/run/provenance.js";
|
|
32
|
+
import { runTask } from "../../tasks/run/run-task.js";
|
|
33
|
+
import { readTaskHistory } from "../../tasks/run/task-history.js";
|
|
34
|
+
import { exitCodeForStatus } from "../../tasks/run/task-result.js";
|
|
32
35
|
import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT } from "../../tasks/schedule.js";
|
|
33
36
|
import { assertSchedulerMutationArtifact, assertSchedulerNativeArtifactCardinality, compileTaskSchedulerBindings, schedulerBindingNativeId, schedulerBindingOrdinal, schedulerNativeArtifactKey, schedulerNativeBindingId, } from "../../tasks/scheduler-binding.js";
|
|
34
37
|
import { schedulerContextDescriptor, schedulerContextPath, validateSchedulerContextDescriptor, writeSchedulerContextDescriptor, } from "../../tasks/scheduler-invocation.js";
|
|
35
38
|
import { assertSchedulerNativeArtifactOwnership, assertSchedulerSourceSnapshot, finalizeSchedulerSyncPlan, prepareSchedulerSyncSourceSet, } from "../../tasks/scheduler-sync.js";
|
|
36
|
-
import {
|
|
39
|
+
import { parseTaskSource } from "../../tasks/source/parse-task-source.js";
|
|
40
|
+
import { projectTaskSourceV4 } from "../../tasks/source/project-v4.js";
|
|
41
|
+
import { TASK_V3_MAX_SOURCE_BYTES } from "../../tasks/source-v3.js";
|
|
37
42
|
import { normaliseTaskConceptId, normaliseTaskId } from "../../tasks/task-id.js";
|
|
38
43
|
import { applyAutonomyGate, configuredDirectAutonomyLanes, describeGatedLanes } from "../improve/autonomy-gate.js";
|
|
39
44
|
import { resolveImproveStrategy } from "../improve/improve-strategies.js";
|
|
@@ -77,7 +82,8 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
77
82
|
tags: input.tags,
|
|
78
83
|
enabled: input.disabled !== true,
|
|
79
84
|
});
|
|
80
|
-
const
|
|
85
|
+
const parsedTask = parseTaskSource({ yaml, filePath: assetPath, workspaceRoot: stashDir });
|
|
86
|
+
const task = projectTaskSourceV4(parsedTask.v4);
|
|
81
87
|
const qualifiedRef = makeBundleRef(bundle.bundleName, `tasks/${id}`);
|
|
82
88
|
await prepareTaskV3Execution(task, {
|
|
83
89
|
taskId: id,
|
|
@@ -87,12 +93,27 @@ export async function akmTasksAdd(input, deps = {}) {
|
|
|
87
93
|
config: bundle.config,
|
|
88
94
|
resolveAsset: taskProjectionAssetResolver(bundle.config, bundle.bundleName, stashDir),
|
|
89
95
|
});
|
|
96
|
+
// Bindings are compiled from the ORIGINAL parsed document, not the
|
|
97
|
+
// `task`/`projectTaskSourceV4` projection above — mirroring
|
|
98
|
+
// scheduler-sync.ts's compileTaskSources (spec §3.2.7's project-v4.ts
|
|
99
|
+
// header): the projection deliberately drops each schedule entry's own
|
|
100
|
+
// `enabled` (P4-N6), so building bindings from it would silently ignore
|
|
101
|
+
// `--disabled`. A task source v4 document has no document-level
|
|
102
|
+
// `akm.enabled`, so `enabled: true` is passed at the document level and
|
|
103
|
+
// every entry's own `enabled` (always present, defaulted at parse time)
|
|
104
|
+
// decides.
|
|
90
105
|
const taskBindings = compileTaskSchedulerBindings({
|
|
91
106
|
id,
|
|
92
107
|
qualifiedRef,
|
|
93
108
|
...(bundle.installTarget ? { bundleTarget: bundle.installTarget } : {}),
|
|
94
|
-
enabled:
|
|
95
|
-
schedules:
|
|
109
|
+
enabled: true,
|
|
110
|
+
schedules: parsedTask.v4.schedule.map((schedule) => ({
|
|
111
|
+
cron: schedule.cron,
|
|
112
|
+
ordinal: schedule.ordinal,
|
|
113
|
+
enabled: schedule.enabled,
|
|
114
|
+
source: schedule.source,
|
|
115
|
+
inputs: schedule.inputs,
|
|
116
|
+
})),
|
|
96
117
|
});
|
|
97
118
|
const taskBinding = taskBindings[0];
|
|
98
119
|
if (!taskBinding)
|
|
@@ -227,20 +248,52 @@ export async function akmTasksRun(id, options = {}) {
|
|
|
227
248
|
const bundle = resolveTaskReadBundle(parsed.bundle, options.target);
|
|
228
249
|
const adapterId = bundle.source.adapterId ?? detectAdapterId(bundle.source.path);
|
|
229
250
|
const resolvedId = taskIdForAdapter(parsed.id, adapterId);
|
|
251
|
+
const scheduled = options.scheduled === true;
|
|
252
|
+
// D5 "Construction" (spec docs/plans/specs/p1b-model-extraction.md §1.2/
|
|
253
|
+
// §5.2): built ONCE at this invocation boundary. eventSource is "task"
|
|
254
|
+
// whether or not --scheduled was passed (§1.6 D5-N1) — scheduled stays a
|
|
255
|
+
// separate field carrying its own pre-existing meaning (activation policy,
|
|
256
|
+
// scheduler env), never selecting the event source.
|
|
257
|
+
const provenance = createExecutionProvenanceContext(scheduled);
|
|
258
|
+
// F-3 (spec §5.4): RunTaskOptions.stashDir renamed to bundleDir — VALUE-
|
|
259
|
+
// preserving, no CLI flag change.
|
|
260
|
+
//
|
|
261
|
+
// P2a Lane C (spec docs/plans/specs/p2a-task-source-v4.md §5.1): the raw,
|
|
262
|
+
// exact-name input flags `tasks-cli.ts`'s Stage 1 captures ride through
|
|
263
|
+
// unchanged to `runTask` -> `loadPreparedTask`'s Stage 2 materializer,
|
|
264
|
+
// which owns declaring `inputFlags` on `RunTaskOptions` and attaching the
|
|
265
|
+
// materialized literals to the constructed `TaskInvocation`. This is only
|
|
266
|
+
// the pass-through surface: a valid flag set stays byte-identical to the
|
|
267
|
+
// same run without flags (§0), and P2a delivers nothing to the target.
|
|
268
|
+
//
|
|
269
|
+
// No `as` cast (test-review finding, spec §6 F-5): `RunTaskOptions` (this
|
|
270
|
+
// literal's inferred type is checked directly against it, below) declares
|
|
271
|
+
// every one of these fields, so the compiler — not a suppressed excess-
|
|
272
|
+
// property check — enforces this seam. A future rename or removal on
|
|
273
|
+
// either side now fails `tsc`, not silently at the `runTask` boundary.
|
|
230
274
|
const runOptions = {
|
|
231
|
-
|
|
275
|
+
bundleDir: bundle.source.path,
|
|
232
276
|
bundleName: bundle.source.name,
|
|
233
277
|
adapterId,
|
|
234
|
-
scheduled
|
|
278
|
+
scheduled,
|
|
279
|
+
provenance,
|
|
280
|
+
inputFlags: options.inputFlags,
|
|
235
281
|
};
|
|
236
282
|
// The runner owns the prepare-before-reserve boundary. Invalid source,
|
|
237
283
|
// projectability, and resolver failures therefore create no history row.
|
|
238
284
|
const result = await runTask(resolvedId, runOptions);
|
|
239
|
-
|
|
285
|
+
// C-7 (spec §5.6): after D8's result-vocabulary re-code, "command" means
|
|
286
|
+
// the agent/LLM arm — the native shell/script arm now reports "shell" /
|
|
287
|
+
// "script". Rewired in the SAME commit as the vocabulary re-code so a
|
|
288
|
+
// shell/script task's process exit 78 still passes through as CLI exit 78
|
|
289
|
+
// (documented behavior, src/assets/hints/cli-hints-short.md:95).
|
|
290
|
+
const exitCode = result.status === "failed" &&
|
|
291
|
+
(result.target.kind === "shell" || result.target.kind === "script") &&
|
|
292
|
+
result.detail?.exitCode === 78
|
|
240
293
|
? 78
|
|
241
294
|
: exitCodeForStatus(result.status);
|
|
242
295
|
return {
|
|
243
|
-
ok: result.status === "completed"
|
|
296
|
+
ok: result.status === "completed",
|
|
244
297
|
result,
|
|
245
298
|
exitCode,
|
|
246
299
|
};
|
|
@@ -284,18 +337,34 @@ export async function akmTasksSync(deps = {}, bundleTarget, options = {}) {
|
|
|
284
337
|
}
|
|
285
338
|
const inspection = await sched.inspectBindings({ rebind: options.rebind === true });
|
|
286
339
|
const rawEntries = [...inspection.installed];
|
|
287
|
-
const allEntries = rawEntries.map((entry) =>
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
340
|
+
const allEntries = rawEntries.map((entry) => {
|
|
341
|
+
const contextPath = "contextPath" in entry ? entry.contextPath : "";
|
|
342
|
+
// #846: recover the resolved bundle path this entry was installed
|
|
343
|
+
// under from its own scheduler-context descriptor. Any failure (no
|
|
344
|
+
// descriptor, unreadable, corrupt, owned by another user) leaves
|
|
345
|
+
// ownerBundlePath unset — belongsToBundle must never treat that as
|
|
346
|
+
// "mine".
|
|
347
|
+
const ownerBundlePath = contextPath ? resolveInstalledOwnerPath(contextPath) : undefined;
|
|
348
|
+
return {
|
|
349
|
+
...entry,
|
|
350
|
+
...(entry.nativeId !== undefined ? { nativeId: entry.nativeId } : {}),
|
|
351
|
+
...(entry.invocation !== undefined ? { invocation: Object.freeze([...entry.invocation]) } : {}),
|
|
352
|
+
binding: "binding" in entry ? [...entry.binding] : [],
|
|
353
|
+
contextPath,
|
|
354
|
+
...(ownerBundlePath !== undefined ? { ownerBundlePath } : {}),
|
|
355
|
+
};
|
|
356
|
+
});
|
|
294
357
|
const nativeArtifacts = inspection.artifacts;
|
|
295
358
|
const common = {
|
|
296
359
|
sourceRoot: stashDir,
|
|
297
360
|
adapterId: resolved.source.adapterId ?? detectAdapterId(stashDir),
|
|
298
361
|
bundleName: resolved.source.name,
|
|
362
|
+
// #846: only meaningful for a primary/unconfigured-bundle sync. A
|
|
363
|
+
// `--bundle <target>` entry's scheduler-context descriptor records the
|
|
364
|
+
// invoking process's OWN primary AKM_BUNDLE_DIR, not the targeted
|
|
365
|
+
// bundle's directory, so path-scoping stays gated on the case it's
|
|
366
|
+
// actually valid for (see belongsToBundle).
|
|
367
|
+
...(syncTarget === undefined ? { bundlePath: path.resolve(stashDir) } : {}),
|
|
299
368
|
...(syncTarget ? { bundleTarget: syncTarget } : {}),
|
|
300
369
|
backend: sched.name,
|
|
301
370
|
installed: allEntries,
|
|
@@ -720,6 +789,15 @@ function groupInstalledBindings(entries, invocation) {
|
|
|
720
789
|
}
|
|
721
790
|
return [...groups.values()].map((group) => ({ ...group, taskIds: group.taskIds.sort() }));
|
|
722
791
|
}
|
|
792
|
+
/** Best-effort recovery of an installed binding's owning bundle path (#846). */
|
|
793
|
+
function resolveInstalledOwnerPath(contextPath) {
|
|
794
|
+
try {
|
|
795
|
+
return validateSchedulerContextDescriptor(contextPath).environment.AKM_BUNDLE_DIR;
|
|
796
|
+
}
|
|
797
|
+
catch {
|
|
798
|
+
return undefined;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
723
801
|
function inspectInstalledBinding(entry, invocation) {
|
|
724
802
|
const status = [];
|
|
725
803
|
const binding = entry.binding;
|
|
@@ -905,7 +983,14 @@ function taskProjectionAssetResolver(config, bundleName, bundleRoot) {
|
|
|
905
983
|
};
|
|
906
984
|
};
|
|
907
985
|
}
|
|
908
|
-
|
|
986
|
+
/**
|
|
987
|
+
* Exported for `src/commands/tasks/explain.ts` (P2b Lane B, spec
|
|
988
|
+
* docs/plans/specs/p2b-input-bindings.md §4.5, B-N4): `akm task explain`
|
|
989
|
+
* resolves its `--bundle` axis identically to every other read-only task
|
|
990
|
+
* verb here (`akm task history`, `akm task run`) — the SAME resolver, not a
|
|
991
|
+
* second one.
|
|
992
|
+
*/
|
|
993
|
+
export function resolveTaskReadBundle(refBundle, flagBundle) {
|
|
909
994
|
if (refBundle && flagBundle && refBundle !== flagBundle) {
|
|
910
995
|
throw new UsageError(`Task ref selects bundle ${JSON.stringify(refBundle)}, but --bundle selects ${JSON.stringify(flagBundle)}.`, "INVALID_FLAG_VALUE");
|
|
911
996
|
}
|
|
@@ -983,12 +1068,43 @@ function assertNoForeignSchedule(entries, id, installTarget) {
|
|
|
983
1068
|
if (foreign)
|
|
984
1069
|
throw new UsageError(foreignScheduleMessage(id, foreign.target), "RESOURCE_ALREADY_EXISTS");
|
|
985
1070
|
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Infer a JSON-Schema-subset `type` keyword from one `--params` value's
|
|
1073
|
+
* runtime shape (spec docs/plans/specs/p4-deletions-closeout.md §3.2.6, row
|
|
1074
|
+
* B-20). `null` is not one of the five runtime types the spec enumerates
|
|
1075
|
+
* (string/number/boolean/object/array) but is a value JSON.parse can still
|
|
1076
|
+
* produce for a param; `src/core/json-schema.ts`'s subset validator accepts
|
|
1077
|
+
* `"null"` as a `type`, so it is handled rather than mis-typed.
|
|
1078
|
+
*/
|
|
1079
|
+
function jsonSchemaTypeOf(value) {
|
|
1080
|
+
if (value === null)
|
|
1081
|
+
return "null";
|
|
1082
|
+
if (Array.isArray(value))
|
|
1083
|
+
return "array";
|
|
1084
|
+
return typeof value;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Render `--params` as typed `inputs:` declarations, one per key, each
|
|
1088
|
+
* carrying a `default:` equal to the authored value and a `type:` inferred
|
|
1089
|
+
* from its own JSON runtime shape (row B-20). Never emitted as `with:` —
|
|
1090
|
+
* task source v4 accepts `with:` only on `uses: akm/command` (§3.2.6's
|
|
1091
|
+
* `parseTarget`/`checkTopLevelKeys`), and a workflow target's declared
|
|
1092
|
+
* inputs are what `load-task.ts`'s existing v4 delivery override binds into
|
|
1093
|
+
* the child run's params.
|
|
1094
|
+
*/
|
|
1095
|
+
function renderInputsFromParams(params) {
|
|
1096
|
+
const inputs = {};
|
|
1097
|
+
for (const [name, value] of Object.entries(params)) {
|
|
1098
|
+
inputs[name] = { type: jsonSchemaTypeOf(value), default: value };
|
|
1099
|
+
}
|
|
1100
|
+
return inputs;
|
|
1101
|
+
}
|
|
986
1102
|
function renderTaskYaml(input) {
|
|
987
|
-
const obj = { version:
|
|
1103
|
+
const obj = { version: 4 };
|
|
988
1104
|
if (input.workflow) {
|
|
989
1105
|
obj.uses = input.workflow;
|
|
990
1106
|
if (input.params)
|
|
991
|
-
obj.
|
|
1107
|
+
obj.inputs = renderInputsFromParams(parseJsonObjectArg(input.params));
|
|
992
1108
|
}
|
|
993
1109
|
else if (input.prompt) {
|
|
994
1110
|
obj.uses = "akm/command";
|
|
@@ -996,22 +1112,40 @@ function renderTaskYaml(input) {
|
|
|
996
1112
|
}
|
|
997
1113
|
else if (input.command !== undefined) {
|
|
998
1114
|
if (Array.isArray(input.command)) {
|
|
999
|
-
throw new UsageError("
|
|
1115
|
+
throw new UsageError("--command accepts one shell string; argv arrays require manual migration.", "INVALID_FLAG_VALUE");
|
|
1000
1116
|
}
|
|
1001
1117
|
obj.run = input.command;
|
|
1002
1118
|
}
|
|
1003
1119
|
if (input.name)
|
|
1004
1120
|
obj.name = input.name;
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1121
|
+
if (input.description !== undefined)
|
|
1122
|
+
obj.description = input.description;
|
|
1123
|
+
if (input.when_to_use !== undefined)
|
|
1124
|
+
obj.when_to_use = input.when_to_use;
|
|
1125
|
+
if (input.tags && input.tags.length > 0)
|
|
1126
|
+
obj.tags = input.tags;
|
|
1127
|
+
if (input.engine !== undefined)
|
|
1128
|
+
obj.engine = input.engine;
|
|
1129
|
+
if (input.model !== undefined)
|
|
1130
|
+
obj.model = input.model;
|
|
1131
|
+
if (input.timeoutMs !== undefined)
|
|
1132
|
+
obj.timeout = input.timeoutMs;
|
|
1133
|
+
// Task source v4's `enabled` is per schedule-binding, not document-level
|
|
1134
|
+
// (P4-N6, row B-21): `--disabled` writes a one-entry schedule[] list
|
|
1135
|
+
// carrying `enabled: false` rather than the v3 `akm.enabled: false` flag.
|
|
1136
|
+
// `TasksAddInput.schedule`/the `add` CLI's `--schedule` are both still
|
|
1137
|
+
// required, so the "no schedule to disable" usage error B-21 also
|
|
1138
|
+
// describes is unreachable through this call site today; the check below
|
|
1139
|
+
// still guards `renderTaskYaml` itself against ever being called with an
|
|
1140
|
+
// empty schedule string.
|
|
1141
|
+
if (input.schedule.length === 0) {
|
|
1142
|
+
if (!input.enabled) {
|
|
1143
|
+
throw new UsageError("--disabled requires --schedule; a task with no schedule is already manual-only.");
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
obj.schedule = input.enabled ? input.schedule : [{ cron: input.schedule, enabled: false }];
|
|
1148
|
+
}
|
|
1015
1149
|
return yamlStringify(obj);
|
|
1016
1150
|
}
|
|
1017
1151
|
function assertInlineTaskPrompt(input) {
|
|
@@ -1036,38 +1170,116 @@ function parseJsonObjectArg(raw) {
|
|
|
1036
1170
|
return parsed;
|
|
1037
1171
|
}
|
|
1038
1172
|
/**
|
|
1039
|
-
* Toggle
|
|
1040
|
-
* parse/render round-trip (which would reformat the file).
|
|
1041
|
-
*
|
|
1173
|
+
* Toggle a task source v4 YAML file's `enabled` state without a full
|
|
1174
|
+
* parse/render round-trip (which would reformat the file). Task source v4
|
|
1175
|
+
* has no document-level `enabled` flag — it lives on each `schedule[]` entry
|
|
1176
|
+
* instead (D2-N5, P4-N6) — so this walks the top-level `schedule:` block,
|
|
1177
|
+
* finds every list entry in it (each line starting with `-` at the block's
|
|
1178
|
+
* item indent), and toggles that entry's own `enabled:` key, the closest v4
|
|
1179
|
+
* equivalent of v3's single document-level flag broadcasting to every
|
|
1180
|
+
* trigger. Each entry is handled independently — one entry already carrying
|
|
1181
|
+
* `enabled:` and a sibling entry with no such key (D2-N3's `schedule[i]`
|
|
1182
|
+
* shape: `{cron, enabled?, inputs?}`) toggles the first and inserts into the
|
|
1183
|
+
* second, rather than one entry's existing key short-circuiting the other's
|
|
1184
|
+
* insertion.
|
|
1185
|
+
*
|
|
1186
|
+
* A bare string-shorthand schedule (`schedule: "0 9 * * *"`) has nowhere for
|
|
1187
|
+
* `enabled:` to live and is rewritten to the one-entry list form. A list
|
|
1188
|
+
* entry with no explicit `enabled:` key (defaulting to `true` at parse) gets
|
|
1189
|
+
* one inserted rather than being silently left unaffected. A document with
|
|
1190
|
+
* no `schedule:` key at all throws — there is no trigger to enable or
|
|
1191
|
+
* disable (mirrors `renderTaskYaml`'s `--disabled`-with-no-`--schedule`
|
|
1192
|
+
* usage error, row B-21).
|
|
1193
|
+
*
|
|
1194
|
+
* Each entry's own key indent is taken from its `-` line (the indent before
|
|
1195
|
+
* `-`, plus two spaces for the conventional single space after it), so a
|
|
1196
|
+
* nested mapping inside an entry — e.g. `schedule[i].inputs` — sits deeper
|
|
1197
|
+
* and is never mistaken for the entry's own `enabled:` key.
|
|
1042
1198
|
*
|
|
1043
1199
|
* Preserves inline comments (e.g. `enabled: true # important`) and uses
|
|
1044
1200
|
* case-sensitive matching (YAML keys are case-sensitive).
|
|
1045
1201
|
*/
|
|
1046
1202
|
export function setEnabledInYaml(yaml, enabled) {
|
|
1047
1203
|
const lines = yaml.replace(/\r\n/g, "\n").split("\n");
|
|
1048
|
-
const
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1204
|
+
const scalarLine = lines.findIndex((line) => /^schedule:[ \t]+\S/.test(line));
|
|
1205
|
+
if (scalarLine >= 0) {
|
|
1206
|
+
const line = lines[scalarLine];
|
|
1207
|
+
const match = line?.match(/^schedule:[ \t]+([^\r\n]+?)[ \t]*(#[^\r\n]*)?$/);
|
|
1208
|
+
const cron = match?.[1] ?? "";
|
|
1209
|
+
const comment = match?.[2] ? ` ${match[2]}` : "";
|
|
1210
|
+
lines.splice(scalarLine, 1, "schedule:", ` - cron: ${cron}${comment}`, ` enabled: ${enabled}`);
|
|
1211
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
1212
|
+
}
|
|
1213
|
+
const blockLine = lines.findIndex((line) => /^schedule:\s*(?:#.*)?$/.test(line));
|
|
1214
|
+
if (blockLine < 0) {
|
|
1215
|
+
throw new UsageError("Task source v4 must declare a schedule before its enabled state can be toggled.");
|
|
1216
|
+
}
|
|
1217
|
+
// Find the block's extent and every top-level list item (`-`) within it.
|
|
1218
|
+
// Only items at the *first* item's own indent count as entries — anything
|
|
1219
|
+
// deeper belongs to a nested mapping/list inside an entry (e.g. an array
|
|
1220
|
+
// input under `inputs:`) and must not be treated as a sibling entry.
|
|
1221
|
+
let blockEnd = lines.length;
|
|
1222
|
+
const itemStarts = [];
|
|
1223
|
+
let topIndent = null;
|
|
1224
|
+
for (let index = blockLine + 1; index < lines.length; index += 1) {
|
|
1053
1225
|
const line = lines[index];
|
|
1054
|
-
if (line === undefined)
|
|
1226
|
+
if (line === undefined) {
|
|
1227
|
+
blockEnd = index;
|
|
1055
1228
|
break;
|
|
1056
|
-
|
|
1229
|
+
}
|
|
1230
|
+
if (line !== "" && !/^[ \t]/.test(line)) {
|
|
1231
|
+
blockEnd = index;
|
|
1057
1232
|
break;
|
|
1058
|
-
insertAt = index + 1;
|
|
1059
|
-
const match = line.match(/^([ \t]+enabled:\s*)([^\s#\r\n][^\r\n]*?)(\s*(?:#[^\r\n]*))?$/);
|
|
1060
|
-
if (match) {
|
|
1061
|
-
lines[index] = `${match[1]}${enabled}${match[3] ?? ""}`;
|
|
1062
|
-
return `${lines.join("\n").trimEnd()}\n`;
|
|
1063
1233
|
}
|
|
1064
|
-
const
|
|
1065
|
-
if (
|
|
1066
|
-
|
|
1067
|
-
|
|
1234
|
+
const itemMatch = line.match(/^([ \t]*)-(?=[ \t]|$)/);
|
|
1235
|
+
if (itemMatch) {
|
|
1236
|
+
const itemIndent = itemMatch[1] ?? "";
|
|
1237
|
+
if (topIndent === null)
|
|
1238
|
+
topIndent = itemIndent;
|
|
1239
|
+
if (itemIndent === topIndent)
|
|
1240
|
+
itemStarts.push(index);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (itemStarts.length === 0) {
|
|
1244
|
+
throw new UsageError("Task source v4's schedule: block has no entries to toggle enabled on.");
|
|
1245
|
+
}
|
|
1246
|
+
// Walk entries back-to-front: inserting a missing `enabled:` line shifts
|
|
1247
|
+
// every later line index by one, but never touches `itemStarts[j]` for
|
|
1248
|
+
// j <= i (an insertion for entry i lands at `itemStarts[i] + 1`, which is
|
|
1249
|
+
// at or after entry i's own start), so already-computed start/end bounds
|
|
1250
|
+
// for entries processed later in this loop (earlier in the list) stay valid.
|
|
1251
|
+
for (let i = itemStarts.length - 1; i >= 0; i -= 1) {
|
|
1252
|
+
const start = itemStarts[i];
|
|
1253
|
+
const end = i + 1 < itemStarts.length ? itemStarts[i + 1] : blockEnd;
|
|
1254
|
+
const dashLead = lines[start]?.match(/^([ \t]*)-/)?.[1] ?? "";
|
|
1255
|
+
const keyIndent = `${dashLead} `;
|
|
1256
|
+
let found = false;
|
|
1257
|
+
for (let index = start; index < end; index += 1) {
|
|
1258
|
+
const line = lines[index];
|
|
1259
|
+
if (line === undefined)
|
|
1260
|
+
continue;
|
|
1261
|
+
const isStart = index === start;
|
|
1262
|
+
const prefixMatch = isStart ? line.match(/^([ \t]*-[ \t]*)(.*)$/) : line.match(/^([ \t]*)(.*)$/);
|
|
1263
|
+
const prefix = prefixMatch?.[1] ?? "";
|
|
1264
|
+
const content = prefixMatch?.[2] ?? "";
|
|
1265
|
+
if (prefix.length !== keyIndent.length)
|
|
1266
|
+
continue;
|
|
1267
|
+
const withValue = content.match(/^(enabled:[ \t]*)([^\s#\r\n][^\r\n]*?)([ \t]*(?:#[^\r\n]*))?$/);
|
|
1268
|
+
if (withValue) {
|
|
1269
|
+
lines[index] = `${prefix}${withValue[1]}${enabled}${withValue[3] ?? ""}`;
|
|
1270
|
+
found = true;
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
const bare = content.match(/^(enabled:)[ \t]*$/);
|
|
1274
|
+
if (bare) {
|
|
1275
|
+
lines[index] = `${prefix}${bare[1]} ${enabled}`;
|
|
1276
|
+
found = true;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
if (!found) {
|
|
1280
|
+
lines.splice(start + 1, 0, `${keyIndent}enabled: ${enabled}`);
|
|
1068
1281
|
}
|
|
1069
1282
|
}
|
|
1070
|
-
lines.splice(insertAt, 0, ` enabled: ${enabled}`);
|
|
1071
1283
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
1072
1284
|
}
|
|
1073
1285
|
// Re-exported so tests can verify the validator path directly.
|
|
@@ -1096,7 +1308,8 @@ export function parseTaskRef(input) {
|
|
|
1096
1308
|
}
|
|
1097
1309
|
return { id: normaliseTaskId(trimmed) };
|
|
1098
1310
|
}
|
|
1099
|
-
|
|
1311
|
+
/** Exported for `src/commands/tasks/explain.ts` — see {@link resolveTaskReadBundle}'s header. */
|
|
1312
|
+
export function taskIdForAdapter(parsedId, adapterId) {
|
|
1100
1313
|
if (adapterId === "akm-task")
|
|
1101
1314
|
return normaliseTaskConceptId(parsedId);
|
|
1102
1315
|
if (adapterId === "akm") {
|
|
@@ -0,0 +1,159 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* `akm workflow plan <ref>` — compile + freeze WITHOUT publishing (P3b, spec
|
|
6
|
+
* docs/plans/specs/p3b-child-executor.md §4.6). Zero durable writes, zero
|
|
7
|
+
* usage/event rows (row B-48): this module calls exactly the same two
|
|
8
|
+
* functions `startWorkflowRun` does to reach a frozen plan
|
|
9
|
+
* (`loadWorkflowAsset`, `compileResolveFreezeWorkflowV4`) and NOTHING else —
|
|
10
|
+
* never `publishWorkflowRunV4`, `startWorkflowRun`, `warn()`, `appendEvent`,
|
|
11
|
+
* or `akmIndex`.
|
|
12
|
+
*
|
|
13
|
+
* SECRET-FREE, by construction (§4.6's closed print list): a resolved
|
|
14
|
+
* reference VALUE is never printed (references resolve at pre-attempt, not
|
|
15
|
+
* here); a `literal` **environment** binding's value is never printed (only
|
|
16
|
+
* `environment[].kind`/`.name`, and an `env-ref`'s `.ref`/`.keys`/
|
|
17
|
+
* `.secretNames` — all NAMES); `request.command.content`,
|
|
18
|
+
* `request.persona`, `request.conversation`, `request.runtime.environment`,
|
|
19
|
+
* and a script target's `bytesBase64` are never read at all.
|
|
20
|
+
*/
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { loadConfig } from "../../core/config/config.js";
|
|
23
|
+
import { lowerResolvedExecutionRequest } from "../../integrations/agent/execution-lowering.js";
|
|
24
|
+
import { collectWorkflowWarnings } from "../../workflows/ir/compile.js";
|
|
25
|
+
import { compileResolveFreezeWorkflowV4 } from "../../workflows/ir/freeze-v4.js";
|
|
26
|
+
import { computePlanHash } from "../../workflows/ir/plan-hash.js";
|
|
27
|
+
import { loadWorkflowAsset } from "../../workflows/runtime/workflow-asset-loader.js";
|
|
28
|
+
/** The step's dispatch unit — the map template for a fan-out, else the root unit. Undefined for a route step. */
|
|
29
|
+
function stepUnit(step) {
|
|
30
|
+
const root = step.root;
|
|
31
|
+
if (!root)
|
|
32
|
+
return undefined;
|
|
33
|
+
return root.kind === "map" ? root.template : root;
|
|
34
|
+
}
|
|
35
|
+
function projectEnvironmentBinding(binding) {
|
|
36
|
+
if (binding.kind === "env-ref") {
|
|
37
|
+
return { kind: binding.kind, ref: binding.ref, keys: binding.keys, secretNames: binding.secretNames };
|
|
38
|
+
}
|
|
39
|
+
// literal / pass-through: kind + name only — a literal's VALUE is never printed.
|
|
40
|
+
return { kind: binding.kind, name: binding.name };
|
|
41
|
+
}
|
|
42
|
+
function projectInputBinding(binding) {
|
|
43
|
+
return binding.kind === "literal"
|
|
44
|
+
? { name: binding.name, kind: "literal", value: binding.value }
|
|
45
|
+
: { name: binding.name, kind: "reference", from: binding.from };
|
|
46
|
+
}
|
|
47
|
+
function childExportedOutputNames(frozenPlan) {
|
|
48
|
+
return frozenPlan.outputs ? Object.keys(frozenPlan.outputs) : ["runId", "status"];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A `child-workflow` target's `expansion`. Recurses into the embedded
|
|
52
|
+
* plan's own steps in the identical shape (§4.6) — `sourceStepsById` is
|
|
53
|
+
* omitted for the recursive call because a nested child's own authored
|
|
54
|
+
* source is not available here (only its already-frozen plan is), so a
|
|
55
|
+
* task-wrapped step nested inside a child conservatively reports `via:
|
|
56
|
+
* "direct"` rather than guessing at its authoring surface.
|
|
57
|
+
*/
|
|
58
|
+
function childExpansion(target) {
|
|
59
|
+
return {
|
|
60
|
+
via: "child",
|
|
61
|
+
childRef: target.ref,
|
|
62
|
+
childPlanHash: target.planHash,
|
|
63
|
+
childVia: target.via,
|
|
64
|
+
...(target.taskRef !== undefined ? { childTaskRef: target.taskRef } : {}),
|
|
65
|
+
childOutputs: childExportedOutputNames(target.frozenPlan),
|
|
66
|
+
steps: target.frozenPlan.steps.map((step, index) => projectStep(step, index, undefined)),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** The task/child expansion boundary for one step (§4.6). */
|
|
70
|
+
function stepExpansion(step, frozenTarget, sourceStepsById) {
|
|
71
|
+
if (frozenTarget?.kind === "child-workflow")
|
|
72
|
+
return childExpansion(frozenTarget);
|
|
73
|
+
const uses = sourceStepsById?.get(step.stepId)?.uses;
|
|
74
|
+
if (uses?.startsWith("tasks/"))
|
|
75
|
+
return { via: "task", taskRef: uses };
|
|
76
|
+
return { via: "direct" };
|
|
77
|
+
}
|
|
78
|
+
function projectStep(step, sequenceIndex, sourceStepsById) {
|
|
79
|
+
const unit = stepUnit(step);
|
|
80
|
+
const frozenTarget = unit?.frozenTarget;
|
|
81
|
+
const kind = step.route ? "route" : step.root?.kind === "map" ? "map" : "unit";
|
|
82
|
+
const inputBindings = frozenTarget?.inputBindings;
|
|
83
|
+
return {
|
|
84
|
+
stepId: step.stepId,
|
|
85
|
+
sequenceIndex,
|
|
86
|
+
kind,
|
|
87
|
+
targetKind: frozenTarget?.kind ?? null,
|
|
88
|
+
...(step.root?.kind === "map" ? { concurrency: step.root.concurrency } : {}),
|
|
89
|
+
inputs: unit?.inputs ?? [],
|
|
90
|
+
environment: (unit?.environment ?? []).map(projectEnvironmentBinding),
|
|
91
|
+
...(inputBindings && inputBindings.length > 0 ? { inputBindings: inputBindings.map(projectInputBinding) } : {}),
|
|
92
|
+
gate: {
|
|
93
|
+
criteria: step.gate.criteria,
|
|
94
|
+
maxLoops: step.gate.maxLoops,
|
|
95
|
+
judgeEngine: step.gate.frozenJudge ? step.gate.frozenJudge.request.engine.name : null,
|
|
96
|
+
},
|
|
97
|
+
...(step.outputSchema !== undefined ? { outputSchema: step.outputSchema } : {}),
|
|
98
|
+
expansion: stepExpansion(step, frozenTarget, sourceStepsById),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Every `command`-kind frozen target's lowering notices, recomputed PURELY
|
|
103
|
+
* from its own already-frozen `request` (the identical computation
|
|
104
|
+
* `freeze/targets/command.ts`'s `commandResult` already performs at freeze
|
|
105
|
+
* time and discards) — walked over the whole plan, including gate judges and
|
|
106
|
+
* recursively into every embedded child plan. Read-only: `lowerResolvedExecutionRequest`
|
|
107
|
+
* takes no config it could write through and dispatches nothing.
|
|
108
|
+
*/
|
|
109
|
+
function collectLoweringNotices(plan, config) {
|
|
110
|
+
const notices = [];
|
|
111
|
+
const lower = (request) => {
|
|
112
|
+
notices.push(...lowerResolvedExecutionRequest(request, config).notices);
|
|
113
|
+
};
|
|
114
|
+
for (const step of plan.steps) {
|
|
115
|
+
const unit = stepUnit(step);
|
|
116
|
+
if (unit) {
|
|
117
|
+
if (unit.frozenTarget.kind === "command")
|
|
118
|
+
lower(unit.frozenTarget.request);
|
|
119
|
+
else if (unit.frozenTarget.kind === "child-workflow")
|
|
120
|
+
notices.push(...collectLoweringNotices(unit.frozenTarget.frozenPlan, config));
|
|
121
|
+
}
|
|
122
|
+
if (step.gate.frozenJudge)
|
|
123
|
+
lower(step.gate.frozenJudge.request);
|
|
124
|
+
}
|
|
125
|
+
return notices;
|
|
126
|
+
}
|
|
127
|
+
function relativeSourceReadSet(plan) {
|
|
128
|
+
return plan.sourceReadSet.map((snapshot) => snapshot.identity.file);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Compile + freeze `ref` and project the frozen plan into the read-only
|
|
132
|
+
* `akm workflow plan` envelope. Never publishes, never writes, never warns.
|
|
133
|
+
*/
|
|
134
|
+
export async function akmWorkflowPlan(ref) {
|
|
135
|
+
const asset = await loadWorkflowAsset(ref);
|
|
136
|
+
const config = loadConfig();
|
|
137
|
+
const frozen = await compileResolveFreezeWorkflowV4(asset, config);
|
|
138
|
+
const plan = frozen.plan;
|
|
139
|
+
const sourceStepsById = new Map((asset.sourceIr.jobs[0]?.steps ?? []).map((step) => [step.id, step]));
|
|
140
|
+
const sourceFormat = path.extname(asset.path).toLowerCase() === ".md" ? "markdown" : "github-yaml";
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
ref: asset.ref,
|
|
144
|
+
title: asset.title,
|
|
145
|
+
sourceFormat,
|
|
146
|
+
sourcePath: asset.path,
|
|
147
|
+
irVersion: plan.irVersion,
|
|
148
|
+
planHash: computePlanHash(plan),
|
|
149
|
+
published: false,
|
|
150
|
+
execution: plan.execution,
|
|
151
|
+
...(plan.budget ? { budget: plan.budget } : {}),
|
|
152
|
+
...(plan.params ? { params: plan.params } : {}),
|
|
153
|
+
...(plan.outputs ? { outputs: plan.outputs } : {}),
|
|
154
|
+
steps: plan.steps.map((step, index) => projectStep(step, index, sourceStepsById)),
|
|
155
|
+
sourceReadSet: relativeSourceReadSet(plan),
|
|
156
|
+
notices: collectLoweringNotices(plan, config),
|
|
157
|
+
warnings: collectWorkflowWarnings(asset.sourceIr).map((warning) => warning.message),
|
|
158
|
+
};
|
|
159
|
+
}
|