akm-cli 0.9.31 → 0.10.26101002-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +265 -0
- package/STABILITY.md +79 -53
- package/dist/api-node.mjs +20 -0
- package/dist/api.d.ts +23 -0
- package/dist/api.js +45 -0
- package/dist/assets/hints/cli-hints-full.md +13 -27
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/improve-strategies/default.json +3 -3
- package/dist/assets/prompts/consolidate-pair.md +9 -7
- package/dist/cli/shared.js +31 -48
- package/dist/cli/unknown-flags.js +6 -43
- package/dist/cli.js +8 -34
- package/dist/commands/agent/contribute-cli.js +1 -1
- package/dist/commands/completions.js +1 -2
- package/dist/commands/config-cli.js +5 -28
- package/dist/commands/env/env-cli.js +5 -5
- package/dist/commands/env/secret-cli.js +2 -2
- package/dist/commands/health/checks.js +41 -16
- package/dist/commands/health/plugin-staleness.js +52 -22
- package/dist/commands/improve/autonomy-gate.js +5 -3
- package/dist/commands/improve/consolidate/pair-pass.js +7 -1
- package/dist/commands/improve/consolidate.js +6 -35
- package/dist/commands/improve/distill/lesson-update.js +91 -0
- package/dist/commands/improve/distill-guards.js +1 -43
- package/dist/commands/improve/distill.js +140 -20
- package/dist/commands/improve/eligibility.js +14 -1
- package/dist/commands/improve/extract-cli.js +1 -1
- package/dist/commands/improve/improve-cli.js +87 -102
- package/dist/commands/improve/improve-strategies.js +8 -12
- package/dist/commands/improve/improve.js +2 -2
- package/dist/commands/improve/loop-stages.js +0 -1
- package/dist/commands/improve/planner.js +0 -1
- package/dist/commands/improve/preparation.js +40 -99
- package/dist/commands/improve/proactive-maintenance.js +2 -2
- package/dist/commands/improve/reflect-noise.js +3 -55
- package/dist/commands/improve/reflect.js +3 -8
- package/dist/commands/improve/retrieval-scope.js +2 -2
- package/dist/commands/improve/salience.js +2 -9
- package/dist/commands/improve/stage.js +14 -9
- package/dist/commands/lint/index.js +5 -20
- package/dist/commands/observability-cli.js +3 -9
- package/dist/commands/proposal/drain.js +39 -6
- package/dist/commands/proposal/proposal-cli.js +11 -31
- package/dist/commands/proposal/repository.js +3 -3
- package/dist/commands/read/knowledge.js +3 -3
- package/dist/commands/read/remember-cli.js +0 -13
- package/dist/commands/read/search-cli.js +29 -97
- package/dist/commands/read/search.js +0 -11
- package/dist/commands/read/show.js +0 -19
- package/dist/commands/sources/plugin-upgrade.js +164 -101
- package/dist/commands/sources/sources-cli.js +1 -15
- package/dist/commands/sources/stash-cli.js +4 -12
- package/dist/commands/tasks/tasks-cli.js +4 -115
- package/dist/commands/workflow/plan.js +11 -33
- package/dist/commands/workflow-cli.js +2 -7
- package/dist/core/adapter/adapter-ids.js +1 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
- package/dist/core/adapter/adapters/akm-adapter.js +9 -48
- package/dist/core/adapter/adapters/akm-lint.js +10 -26
- package/dist/core/adapter/adapters/akm-metadata.js +2 -72
- package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
- package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
- package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
- package/dist/core/adapter/recognize-match.js +3 -4
- package/dist/core/common.js +18 -2
- package/dist/core/config/config-schema.js +12 -15
- package/dist/core/config/config-types.js +6 -15
- package/dist/core/config/config-walker.js +6 -23
- package/dist/core/config/config.js +17 -80
- package/dist/core/config/engine-semantics.js +7 -3
- package/dist/core/config/schema/engines.js +7 -7
- package/dist/core/config/schema/improve-processes.js +8 -61
- package/dist/core/config/schema/improve.js +2 -21
- package/dist/core/config/schema/index-config.js +6 -39
- package/dist/core/env-secret-ref.js +2 -45
- package/dist/core/errors.js +1 -2
- package/dist/core/extra-params.js +2 -86
- package/dist/core/improve-result.js +11 -1
- package/dist/core/mutation-target.js +2 -2
- package/dist/core/recognition-util.js +4 -5
- package/dist/core/warn.js +21 -4
- package/dist/core/write-source.js +3 -3
- package/dist/execution/target-ref.js +1 -2
- package/dist/indexer/search/db-search.js +4 -19
- package/dist/indexer/search/fts-query.js +0 -19
- package/dist/integrations/agent/engine-resolution.js +5 -2
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +41 -98
- package/dist/integrations/agent/request-lowering.js +3 -3
- package/dist/integrations/harnesses/aider/index.js +5 -0
- package/dist/integrations/harnesses/amazonq/index.js +1 -0
- package/dist/integrations/harnesses/claude/config-import.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +5 -0
- package/dist/integrations/harnesses/codex/index.js +1 -0
- package/dist/integrations/harnesses/copilot/index.js +1 -0
- package/dist/integrations/harnesses/gemini/index.js +1 -0
- package/dist/integrations/harnesses/index.js +18 -39
- package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
- package/dist/integrations/harnesses/opencode/config-import.js +121 -52
- package/dist/integrations/harnesses/opencode/index.js +22 -0
- package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
- package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
- package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
- package/dist/integrations/harnesses/opencode/session-log.js +104 -115
- package/dist/integrations/harnesses/opencode/version.js +98 -0
- package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +228 -0
- package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
- package/dist/integrations/harnesses/openhands/index.js +1 -0
- package/dist/integrations/harnesses/pi/index.js +1 -0
- package/dist/integrations/harnesses/shared.js +2 -0
- package/dist/integrations/harnesses/types.js +2 -0
- package/dist/llm/client.js +41 -11
- package/dist/output/cli-hints.js +1 -1
- package/dist/output/context.js +10 -19
- package/dist/output/format-exempt.js +5 -8
- package/dist/output/shapes/helpers.js +1 -22
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/shapes.js +3 -31
- package/dist/output/stdout.js +5 -1
- package/dist/output/text/command-format.js +6 -1
- package/dist/output/text/workflow-format.js +4 -22
- package/dist/scripts/akm-migrate-node.js +13049 -14013
- package/dist/scripts/akm-migrate.js +16316 -17276
- package/dist/setup/detect.js +10 -4
- package/dist/setup/harness-config-import.js +5 -14
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/tasks.js +12 -13
- package/dist/storage/repositories/task-history-repository.js +0 -5
- package/dist/storage/repositories/workflow-runs-repository.js +5 -15
- package/dist/tasks/embedded.js +2 -1
- package/dist/tasks/run/task-history.js +0 -2
- package/dist/tasks/schedule-gates.js +39 -0
- package/dist/tasks/scheduler-binding.js +0 -16
- package/dist/tasks/scheduler-sync.js +3 -97
- package/dist/tasks/source/bounded-document.js +1 -1
- package/dist/tasks/source/task-source-v4.js +2 -12
- package/dist/tasks/task-run-reserved-flags.js +2 -31
- package/dist/workflows/authoring/authoring.js +6 -6
- package/dist/workflows/compile.js +8 -65
- package/dist/workflows/exec/child-workflow.js +22 -3
- package/dist/workflows/exec/native-executor.js +12 -147
- package/dist/workflows/exec/run-workflow.js +31 -103
- package/dist/workflows/exec/step-work.js +9 -178
- package/dist/workflows/exec/unit-dispatch.js +0 -10
- package/dist/workflows/freeze/environment.js +3 -44
- package/dist/workflows/freeze/freeze.js +8 -41
- package/dist/workflows/freeze/targets/child-workflow.js +27 -63
- package/dist/workflows/freeze/targets/command.js +2 -16
- package/dist/workflows/freeze/targets/shell.js +0 -2
- package/dist/workflows/freeze/task-bindings.js +2 -28
- package/dist/workflows/ir/params.js +12 -2
- package/dist/workflows/parser.js +82 -232
- package/dist/workflows/plan.js +1 -1
- package/dist/workflows/renderer.js +10 -19
- package/dist/workflows/runtime/run-plan.js +11 -53
- package/dist/workflows/runtime/runs.js +4 -27
- package/dist/workflows/source-files.js +12 -16
- package/dist/workflows/source-semantics.js +1 -93
- package/docs/README.md +1 -1
- package/docs/agents/README.md +3 -3
- package/docs/agents/agent-install.md +1 -1
- package/docs/architecture/adapters.md +17 -31
- package/docs/architecture/architecture.md +4 -5
- package/docs/architecture/improvement.md +52 -21
- package/docs/architecture/internals/improve-workflow.md +15 -8
- package/docs/architecture/internals/indexing.md +1 -1
- package/docs/architecture/internals/search.md +1 -2
- package/docs/architecture/internals/storage-locations.md +5 -5
- package/docs/architecture/persisted-data-compat.md +13 -4
- package/docs/architecture/specs/0.9.0-decisions.md +6 -2
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
- package/docs/architecture/specs/workflow-format-unification.md +1 -6
- package/docs/architecture/testing/manual-testing-checklist.md +20 -29
- package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
- package/docs/architecture/testing/testing-workflow.md +1 -1
- package/docs/architecture/workflow-engine.md +5 -56
- package/docs/guides/author-workflows.md +39 -69
- package/docs/guides/capture-knowledge.md +1 -1
- package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
- package/docs/guides/discover-and-load.md +4 -4
- package/docs/guides/getting-started.md +1 -1
- package/docs/guides/improve-the-library.md +7 -7
- package/docs/guides/recipes/headless-install.md +2 -2
- package/docs/guides/run-workflows.md +1 -1
- package/docs/guides/use-with-any-agent.md +49 -4
- package/docs/integration/bundling-akm.md +7 -6
- package/docs/maintainers/curate-workmap.md +1 -2
- package/docs/maintainers/release-checklist.md +30 -1
- package/docs/posts/akm-command-tour.md +3 -3
- package/docs/product-surface.md +1 -1
- package/docs/reference/README.md +1 -0
- package/docs/reference/api.md +65 -0
- package/docs/reference/asset-types.md +3 -5
- package/docs/reference/bundle-types.md +2 -4
- package/docs/reference/cli.md +129 -171
- package/docs/reference/configuration.md +84 -19
- package/docs/reference/data-and-telemetry.md +1 -1
- package/docs/reference/env-and-secrets.md +2 -2
- package/docs/reference/supported-formats.md +3 -4
- package/docs/reference/tasks.md +12 -19
- package/docs/reference/website-sources.md +1 -1
- package/docs/reference/workflow-schema.md +89 -317
- package/docs/reference/workflows.md +20 -48
- package/package.json +12 -2
- package/schemas/akm-config.json +2 -243
- package/schemas/akm-task.json +2 -3
- package/schemas/akm-workflow.json +47 -84
- package/dist/assets/improve-strategies/catchup.json +0 -41
- package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
- package/dist/assets/improve-strategies/quick.json +0 -17
- package/dist/assets/improve-strategies/reflect-distill.json +0 -29
- package/dist/assets/improve-strategies/thorough.json +0 -49
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
- package/dist/cli/retired-commands.js +0 -118
- package/dist/commands/tasks/validate.js +0 -176
- package/dist/core/config/legacy-source-shape-shim.js +0 -117
- package/dist/integrations/harnesses/ids.js +0 -28
- package/dist/tasks/source/task-source-v3-frozen.js +0 -424
- package/dist/tasks/source/task-to-v4.js +0 -863
- package/dist/workflows/exec/worktree.js +0 -372
- package/dist/workflows/freeze/child-output-references.js +0 -83
- package/dist/workflows/freeze/identity.js +0 -22
- package/dist/workflows/freeze/targets/script.js +0 -50
- package/dist/workflows/freeze/targets/task.js +0 -124
- package/dist/workflows/github-yaml.js +0 -506
- package/dist/workflows/runtime/run-outputs.js +0 -80
|
@@ -21,16 +21,15 @@ 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,
|
|
24
|
+
import { abandonWorkflowRun, getNextWorkflowStep, resumeWorkflowRun } from "../runtime/runs.js";
|
|
25
25
|
import { loadWorkflowAsset } from "../runtime/workflow-asset-loader.js";
|
|
26
26
|
import { frozenSummaryJudge } from "./frozen-judge.js";
|
|
27
27
|
import { mergeLoweringNotices } from "./lowering-notices.js";
|
|
28
28
|
import { defaultUnitDispatcher, executeStepPlan, } from "./native-executor.js";
|
|
29
|
-
// Shared step semantics —
|
|
30
|
-
// gate-evaluation journaling, and the whole step-completion path
|
|
29
|
+
// Shared step semantics — gate-evaluation journaling, and the whole step-completion path
|
|
31
30
|
// (`finalizeExecutedStep`) live in step-work.ts as ONE implementation, so the
|
|
32
31
|
// fresh-execution and resume paths cannot drift from each other.
|
|
33
|
-
import { activeGateLoop, blockStepForJudgeFailure,
|
|
32
|
+
import { activeGateLoop, blockStepForJudgeFailure, effectiveGateMaxLoops, finalizeExecutedStep, recoverGateFeedback, referencedStepIds, } from "./step-work.js";
|
|
34
33
|
export async function runWorkflowSteps(options) {
|
|
35
34
|
let target = options.target;
|
|
36
35
|
let params = options.params;
|
|
@@ -72,8 +71,8 @@ export async function runWorkflowSteps(options) {
|
|
|
72
71
|
}
|
|
73
72
|
if (remainingSteps !== undefined) {
|
|
74
73
|
// The step budget is DISTINCT PROCESSED STEPS, not `executed` entries:
|
|
75
|
-
// gate loops of one step
|
|
76
|
-
//
|
|
74
|
+
// gate loops of one step must not shrink a retry's remaining budget
|
|
75
|
+
// (they never counted against maxSteps either).
|
|
77
76
|
remainingSteps -= result.stepsProcessed;
|
|
78
77
|
if (remainingSteps <= 0)
|
|
79
78
|
return aggregate;
|
|
@@ -262,28 +261,6 @@ async function loadAuthoritativeRunPlan(options, next, stored) {
|
|
|
262
261
|
}
|
|
263
262
|
return stored;
|
|
264
263
|
}
|
|
265
|
-
/**
|
|
266
|
-
* Complete a branch target no completed router selected as `skipped` — no
|
|
267
|
-
* dispatch, no gate loop, and (per the `maxSteps` contract) no step consumed.
|
|
268
|
-
* Returns the re-read spine state so the caller can continue its walk.
|
|
269
|
-
*/
|
|
270
|
-
async function skipUnselectedRouteTarget(input) {
|
|
271
|
-
const { runId, stepId, stepPlan, skipInfo, routeUnselected, executed } = input;
|
|
272
|
-
// Cascade: a skipped step that is ITSELF a router
|
|
273
|
-
// never evaluates its route, so none of its declared targets were
|
|
274
|
-
// selected — mark them all skip-on-reach too (a target another
|
|
275
|
-
// completed router selects stays protected via routeSelected). Without
|
|
276
|
-
// this, every branch of the skipped router would run unconditionally.
|
|
277
|
-
if (stepPlan.route) {
|
|
278
|
-
cascadeSkippedRouter(stepPlan.route, stepId, routeUnselected);
|
|
279
|
-
}
|
|
280
|
-
const notes = skipInfo.selected === null
|
|
281
|
-
? `Skipped by route: step "${skipInfo.router}" was itself skipped, so none of its branch targets run.`
|
|
282
|
-
: `Skipped by route: step "${skipInfo.router}" selected "${skipInfo.selected}".`;
|
|
283
|
-
executed.push({ stepId, ok: true, unitCount: 0, failedUnits: 0, summary: notes });
|
|
284
|
-
await completeWorkflowStep({ runId, stepId, status: "skipped", notes });
|
|
285
|
-
return getNextWorkflowStep(runId);
|
|
286
|
-
}
|
|
287
264
|
/**
|
|
288
265
|
* Seed a step's starting gate loop and feedback from its journaled gate rows,
|
|
289
266
|
* so a run interrupted after a rejection resumes at the next loop with the
|
|
@@ -307,41 +284,31 @@ async function recoverGateLoopState(runId, stepPlan) {
|
|
|
307
284
|
*/
|
|
308
285
|
const STEP_FINISHED_KINDS = new Set(["advanced", "failed", "gate-exhausted"]);
|
|
309
286
|
/**
|
|
310
|
-
* One attempt at a step's work
|
|
311
|
-
*
|
|
312
|
-
* `finalizeExecutedStep`. Everything else executes its subgraph through the
|
|
313
|
-
* native executor.
|
|
287
|
+
* One attempt at a step's work: execute its subgraph through the native
|
|
288
|
+
* executor.
|
|
314
289
|
*/
|
|
315
290
|
async function executeStepSubgraph(ctx, loop) {
|
|
316
|
-
const { options, next, plan, stepPlan,
|
|
291
|
+
const { options, next, plan, stepPlan, evidence } = ctx;
|
|
317
292
|
const { gateLoop, gateFeedback, unitsDispatched, tokensUsed } = loop;
|
|
318
|
-
return
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
...(gateFeedback ? { gateFeedback } : {}),
|
|
338
|
-
// F-1: threaded to an exec unit's child env;
|
|
339
|
-
// undefined for every non-task caller (byte-identical, RunWorkflowOptions doc).
|
|
340
|
-
...(options.eventSource !== undefined ? { eventSource: options.eventSource } : {}),
|
|
341
|
-
...(options.signal ? { signal: options.signal } : {}),
|
|
342
|
-
...(options.dispatcher ? { dispatcher: options.dispatcher } : {}),
|
|
343
|
-
maxConcurrency: Math.min(options.maxConcurrency ?? Number.POSITIVE_INFINITY, plan.execution?.maxConcurrency ?? 1),
|
|
344
|
-
});
|
|
293
|
+
return await executeStepPlan(stepPlan, {
|
|
294
|
+
runId: next.run.id,
|
|
295
|
+
workflowRef: next.run.workflowRef,
|
|
296
|
+
params: next.run.params ?? {},
|
|
297
|
+
evidence,
|
|
298
|
+
unitsDispatched,
|
|
299
|
+
tokensUsed,
|
|
300
|
+
// Budget ceilings ride the FROZEN plan: a mid-run
|
|
301
|
+
// asset edit can never loosen or tighten a run's budget.
|
|
302
|
+
...(plan.budget ? { budget: plan.budget } : {}),
|
|
303
|
+
gateLoop,
|
|
304
|
+
...(gateFeedback ? { gateFeedback } : {}),
|
|
305
|
+
// F-1: threaded to an exec unit's child env;
|
|
306
|
+
// undefined for every non-task caller (byte-identical, RunWorkflowOptions doc).
|
|
307
|
+
...(options.eventSource !== undefined ? { eventSource: options.eventSource } : {}),
|
|
308
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
309
|
+
...(options.dispatcher ? { dispatcher: options.dispatcher } : {}),
|
|
310
|
+
maxConcurrency: Math.min(options.maxConcurrency ?? Number.POSITIVE_INFINITY, plan.execution?.maxConcurrency ?? 1),
|
|
311
|
+
});
|
|
345
312
|
}
|
|
346
313
|
/**
|
|
347
314
|
* Drive one step's bounded gate loop: a rejection with loops left re-executes
|
|
@@ -349,7 +316,7 @@ async function executeStepSubgraph(ctx, loop) {
|
|
|
349
316
|
* decision and running budget totals; `ctx.executed` is appended in place.
|
|
350
317
|
*/
|
|
351
318
|
async function runStepGateLoop(ctx, gate, totals) {
|
|
352
|
-
const { options, next, stepPlan, step,
|
|
319
|
+
const { options, next, stepPlan, step, executed } = ctx;
|
|
353
320
|
const { summaryJudge } = ctx;
|
|
354
321
|
const { startLoop, maxLoops } = gate;
|
|
355
322
|
let { unitsDispatched, tokensUsed } = totals;
|
|
@@ -376,7 +343,7 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
376
343
|
summary: result.summary,
|
|
377
344
|
...(result.notices ? { notices: result.notices } : {}),
|
|
378
345
|
});
|
|
379
|
-
//
|
|
346
|
+
// Gate and advance: the shared completion path, live or resumed.
|
|
380
347
|
let finalize;
|
|
381
348
|
try {
|
|
382
349
|
finalize = await finalizeExecutedStep({
|
|
@@ -388,10 +355,6 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
388
355
|
gateLoop,
|
|
389
356
|
loopsRemaining: gateLoop < maxLoops,
|
|
390
357
|
result,
|
|
391
|
-
priorEvidence: evidence,
|
|
392
|
-
params: next.run.params ?? {},
|
|
393
|
-
routeSelected,
|
|
394
|
-
routeUnselected,
|
|
395
358
|
summaryJudge,
|
|
396
359
|
signal: options.signal,
|
|
397
360
|
});
|
|
@@ -413,10 +376,6 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
413
376
|
// when some later reference can read it (`referencedStepIds`).
|
|
414
377
|
if (ctx.liveEvidenceConsumers.has(step.id))
|
|
415
378
|
ctx.liveEvidence.set(step.id, result.evidence);
|
|
416
|
-
// A route-only step's summary IS its decision (finalize surfaces it).
|
|
417
|
-
if (finalize.summaryOverride !== undefined) {
|
|
418
|
-
executed[executed.length - 1] = { ...executed[executed.length - 1], summary: finalize.summaryOverride };
|
|
419
|
-
}
|
|
420
379
|
return outcome({ kind: "advanced" });
|
|
421
380
|
}
|
|
422
381
|
if (finalize.kind === "judge-failed") {
|
|
@@ -451,11 +410,6 @@ async function runStepGateLoop(ctx, gate, totals) {
|
|
|
451
410
|
});
|
|
452
411
|
}
|
|
453
412
|
if (finalize.kind === "failed") {
|
|
454
|
-
// A route-failure was pushed as ok:true (the units succeeded); reflect
|
|
455
|
-
// the deterministic route failure in the executed report.
|
|
456
|
-
if (finalize.routeFailure) {
|
|
457
|
-
executed[executed.length - 1] = { ...executed[executed.length - 1], ok: false, summary: finalize.summary };
|
|
458
|
-
}
|
|
459
413
|
return outcome({ kind: "failed" });
|
|
460
414
|
}
|
|
461
415
|
// gate-exhausted: rejected with no loop budget left — stop with feedback.
|
|
@@ -486,9 +440,8 @@ liveEvidence) {
|
|
|
486
440
|
let aborted = false;
|
|
487
441
|
const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY;
|
|
488
442
|
// The `maxSteps` budget counts DISTINCT spine steps that finished processing
|
|
489
|
-
// — never `executed.length`, which grows once per gate-loop iteration
|
|
490
|
-
//
|
|
491
|
-
// a route-skipped step consumes NOTHING (no work was dispatched for it).
|
|
443
|
+
// — never `executed.length`, which grows once per gate-loop iteration. A
|
|
444
|
+
// step's whole bounded gate loop consumes ONE step.
|
|
492
445
|
let stepsProcessed = 0;
|
|
493
446
|
let { unitsDispatched, tokensUsed } = await seedRunAccountingFromJournal(next.run.id);
|
|
494
447
|
const plan = await loadAuthoritativeRunPlan(options, next, storedPlan);
|
|
@@ -498,16 +451,6 @@ liveEvidence) {
|
|
|
498
451
|
// and holding every step's for the whole invocation is pure ballast when
|
|
499
452
|
// nothing downstream names it.
|
|
500
453
|
const liveEvidenceConsumers = referencedStepIds(plan);
|
|
501
|
-
// Route bookkeeping: targets a completed router did NOT select are skipped
|
|
502
|
-
// when the spine reaches them; a target ANY router selected is protected
|
|
503
|
-
// (two routers may share a target).
|
|
504
|
-
const routeSelected = new Set();
|
|
505
|
-
const routeUnselected = new Map();
|
|
506
|
-
// Replay journaled route decisions before the spine advances, so a
|
|
507
|
-
// re-invoked run skips the unselected branches. A done run skips this.
|
|
508
|
-
if (!next.done) {
|
|
509
|
-
seedJournaledRouteDecisions(plan, next, routeSelected, routeUnselected);
|
|
510
|
-
}
|
|
511
454
|
while (!next.done && next.step && next.run.status === "active" && stepsProcessed < maxSteps) {
|
|
512
455
|
// A caller abort (options.signal) is a graceful break.
|
|
513
456
|
if (options.signal?.aborted) {
|
|
@@ -520,19 +463,6 @@ liveEvidence) {
|
|
|
520
463
|
throw new UsageError(`Step "${step.id}" of run ${next.run.id} is not present in its frozen workflow plan (${next.run.workflowRef}). ` +
|
|
521
464
|
"The run journal is inconsistent; abandon this run and start a new one.");
|
|
522
465
|
}
|
|
523
|
-
// A branch target no completed router selected → auto-skip, no dispatch.
|
|
524
|
-
const skipInfo = routeUnselected.get(step.id);
|
|
525
|
-
if (skipInfo && !routeSelected.has(step.id)) {
|
|
526
|
-
next = await skipUnselectedRouteTarget({
|
|
527
|
-
runId: next.run.id,
|
|
528
|
-
stepId: step.id,
|
|
529
|
-
stepPlan,
|
|
530
|
-
skipInfo,
|
|
531
|
-
routeUnselected,
|
|
532
|
-
executed,
|
|
533
|
-
});
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
466
|
const evidence = {};
|
|
537
467
|
for (const s of next.workflow.steps)
|
|
538
468
|
evidence[s.id] = liveEvidence.get(s.id) ?? s.evidence;
|
|
@@ -589,8 +519,6 @@ liveEvidence) {
|
|
|
589
519
|
liveEvidence,
|
|
590
520
|
liveEvidenceConsumers,
|
|
591
521
|
executed,
|
|
592
|
-
routeSelected,
|
|
593
|
-
routeUnselected,
|
|
594
522
|
summaryJudge,
|
|
595
523
|
}, { startLoop, maxLoops, seededFeedback }, { unitsDispatched, tokensUsed });
|
|
596
524
|
unitsDispatched = outcome.unitsDispatched;
|
|
@@ -109,11 +109,10 @@ function resolveTaskInputBindings(bindings, stepId, scope) {
|
|
|
109
109
|
*/
|
|
110
110
|
export function computeStepWorkList(plan, input) {
|
|
111
111
|
const root = plan.root;
|
|
112
|
-
// Route-only steps (YAML `route:`) carry no execution subgraph.
|
|
113
112
|
if (!root) {
|
|
114
113
|
return {
|
|
115
114
|
ok: false,
|
|
116
|
-
error: `Step "${plan.stepId}" has no execution subgraph
|
|
115
|
+
error: `Step "${plan.stepId}" has no execution subgraph; the native executor cannot dispatch it.`,
|
|
117
116
|
};
|
|
118
117
|
}
|
|
119
118
|
const template = root.kind === "map" ? root.template : root;
|
|
@@ -121,7 +120,7 @@ export function computeStepWorkList(plan, input) {
|
|
|
121
120
|
const scope = { params: input.params, stepOutputs: input.stepOutputs };
|
|
122
121
|
// Instructions are ALWAYS the step's body prose, byte-exact — never
|
|
123
122
|
// templated, never scanned for reference syntax (workflow-format-
|
|
124
|
-
// unification, spec §2.3). Only `map.over` / `
|
|
123
|
+
// unification, spec §2.3). Only `map.over` / `inputs[]`
|
|
125
124
|
// carry the closed reference grammar.
|
|
126
125
|
// Resolve the step's declared `inputs:` ONCE (shared by every unit in this
|
|
127
126
|
// step — map items differ, declared inputs do not): prior-step artifacts
|
|
@@ -177,7 +176,7 @@ export function computeStepWorkList(plan, input) {
|
|
|
177
176
|
: [unitIdFor(template.id, undefined, false, true)];
|
|
178
177
|
const gateLoop = input.gateLoop ?? 1;
|
|
179
178
|
const target = template.frozenTarget;
|
|
180
|
-
const frozenExec = target.kind === "shell"
|
|
179
|
+
const frozenExec = target.kind === "shell" ? target.exec : undefined;
|
|
181
180
|
const runner = target.kind === "command" ? target.runner.kind : "exec";
|
|
182
181
|
// Taken verbatim from the frozen plan, resolved once at freeze (an exec
|
|
183
182
|
// unit's on its exec spec). A frozen `null` means genuinely unbounded
|
|
@@ -271,7 +270,6 @@ function buildStepWorkUnit(ctx, unitId, item, index) {
|
|
|
271
270
|
...(template.schema ? { schema: template.schema } : {}),
|
|
272
271
|
...(template.retry ? { retry: template.retry } : {}),
|
|
273
272
|
onError: template.onError,
|
|
274
|
-
...(template.isolation ? { isolation: template.isolation } : {}),
|
|
275
273
|
// the SAME resolved `with:` bindings `taskInputs` already
|
|
276
274
|
// carries, exposed under the name `child-workflow.ts`'s drive contract
|
|
277
275
|
// reads. Absent (never `{}`) when the step binds nothing.
|
|
@@ -330,7 +328,8 @@ function computeUnitInputHash(ctx, item) {
|
|
|
330
328
|
frozenTarget: ctx.target,
|
|
331
329
|
environment: ctx.template.environment,
|
|
332
330
|
schema: ctx.template.schema ?? null,
|
|
333
|
-
isolation:
|
|
331
|
+
// Retired `isolation: worktree` field; kept constant so in-flight runs keep their journaled input hashes.
|
|
332
|
+
isolation: "none",
|
|
334
333
|
...(ctx.taskInputs !== undefined ? { taskInputs: ctx.taskInputs } : {}),
|
|
335
334
|
...(ctx.input.gateFeedback ? { gateFeedback: ctx.input.gateFeedback } : {}),
|
|
336
335
|
}))
|
|
@@ -422,7 +421,7 @@ function stepTemplate(stepPlan) {
|
|
|
422
421
|
}
|
|
423
422
|
/**
|
|
424
423
|
* The step ids another step can still read (named by `inputs[]`, `map.over`,
|
|
425
|
-
*
|
|
424
|
+
* — the whole reference surface). A step outside this set
|
|
426
425
|
* need not keep its artifact in memory once journaled. Derived from the plan alone.
|
|
427
426
|
*/
|
|
428
427
|
export function referencedStepIds(plan) {
|
|
@@ -437,8 +436,6 @@ export function referencedStepIds(plan) {
|
|
|
437
436
|
note(step.root.over);
|
|
438
437
|
for (const reference of stepTemplate(step)?.inputs ?? [])
|
|
439
438
|
note(reference);
|
|
440
|
-
if (step.route)
|
|
441
|
-
note(step.route.input);
|
|
442
439
|
}
|
|
443
440
|
return referenced;
|
|
444
441
|
}
|
|
@@ -830,148 +827,6 @@ export async function journalGateEvaluationFinish(gate, errored, rejection) {
|
|
|
830
827
|
throw new UsageError(`Gate ${unitId} was already finished; refusing a duplicate terminal write.`);
|
|
831
828
|
}
|
|
832
829
|
}
|
|
833
|
-
/**
|
|
834
|
-
* Resolve a route's input (a single whole-value reference string — `params.x` or
|
|
835
|
-
* `steps.<id>.output…`, with no `${{ }}` delimiters) and pick the branch. No
|
|
836
|
-
* ambient key search. Only primitive values route; the comparison is exact
|
|
837
|
-
* string equality against the declared `when:` matches.
|
|
838
|
-
*/
|
|
839
|
-
export function evaluateRoute(route, scope) {
|
|
840
|
-
const resolved = resolveStepReference(route.input, scope);
|
|
841
|
-
if (!resolved.ok) {
|
|
842
|
-
return { ok: false, error: `route input ${route.input} failed to resolve: ${resolved.error.message}` };
|
|
843
|
-
}
|
|
844
|
-
const value = resolved.value;
|
|
845
|
-
if (typeof value === "object" && value !== null) {
|
|
846
|
-
return {
|
|
847
|
-
ok: false,
|
|
848
|
-
error: `route input ${route.input} resolved to a non-primitive value; branches match on strings/numbers/booleans.`,
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
const valueString = typeof value === "string" ? value : String(value);
|
|
852
|
-
// Own-property check: `when` is author-controlled, and a value such as
|
|
853
|
-
// "constructor" must not resolve through Object.prototype.
|
|
854
|
-
const selected = Object.hasOwn(route.when, valueString) ? route.when[valueString] : route.defaultStepId;
|
|
855
|
-
if (!selected) {
|
|
856
|
-
return {
|
|
857
|
-
ok: false,
|
|
858
|
-
error: `value "${valueString}" matched no "when:" branch and the route declares no default.`,
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
return { ok: true, value: valueString, selected };
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Cascade a SKIPPED router: it never evaluated its route, so every declared
|
|
865
|
-
* target (branches + default) is marked skip-on-reach unless an earlier router
|
|
866
|
-
* already claimed it. Shared by the live skip path and the journal replay.
|
|
867
|
-
*/
|
|
868
|
-
export function cascadeSkippedRouter(route, routerId, routeUnselected) {
|
|
869
|
-
const targets = [...Object.values(route.when), ...(route.defaultStepId ? [route.defaultStepId] : [])];
|
|
870
|
-
for (const target of targets) {
|
|
871
|
-
if (!routeUnselected.has(target)) {
|
|
872
|
-
routeUnselected.set(target, { router: routerId, selected: null });
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Record one router's decision in the skip bookkeeping: the selected target is
|
|
878
|
-
* protected, every other declared target (branches + default) is marked
|
|
879
|
-
* skip-on-reach unless an earlier router already claimed it. Shared by the live
|
|
880
|
-
* evaluation path and the journal replay.
|
|
881
|
-
*/
|
|
882
|
-
export function applyRouteDecision(route, routerId, selected, routeSelected, routeUnselected) {
|
|
883
|
-
routeSelected.add(selected);
|
|
884
|
-
const targets = [...Object.values(route.when), ...(route.defaultStepId ? [route.defaultStepId] : [])];
|
|
885
|
-
for (const target of targets) {
|
|
886
|
-
if (target !== selected && !routeUnselected.has(target)) {
|
|
887
|
-
routeUnselected.set(target, { router: routerId, selected });
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
/**
|
|
892
|
-
* The `stepOutputs` scope a route resolves against: every prior step's recorded
|
|
893
|
-
* evidence plus the just-finished step's fresh evidence — each projected
|
|
894
|
-
* through {@link projectStepOutput}. Same projection as unit templates, so the
|
|
895
|
-
* two scopes cannot drift.
|
|
896
|
-
*/
|
|
897
|
-
export function routeStepOutputs(evidence, currentStepId, currentEvidence) {
|
|
898
|
-
const outputs = {};
|
|
899
|
-
for (const [stepId, stepEvidence] of Object.entries(evidence)) {
|
|
900
|
-
if (stepEvidence !== undefined)
|
|
901
|
-
outputs[stepId] = projectStepOutput(stepEvidence);
|
|
902
|
-
}
|
|
903
|
-
outputs[currentStepId] = projectStepOutput(currentEvidence);
|
|
904
|
-
return outputs;
|
|
905
|
-
}
|
|
906
|
-
/** The `selected` target journaled on a route step's evidence, if well-formed. */
|
|
907
|
-
function journaledRouteSelection(evidence) {
|
|
908
|
-
const route = evidence?.route;
|
|
909
|
-
if (typeof route !== "object" || route === null || Array.isArray(route))
|
|
910
|
-
return undefined;
|
|
911
|
-
const selected = route.selected;
|
|
912
|
-
return typeof selected === "string" && selected !== "" ? selected : undefined;
|
|
913
|
-
}
|
|
914
|
-
/** The set of steps a route may legally select: its `when` branches + default. */
|
|
915
|
-
function routeTargets(route) {
|
|
916
|
-
return new Set([...Object.values(route.when), ...(route.defaultStepId ? [route.defaultStepId] : [])]);
|
|
917
|
-
}
|
|
918
|
-
/** A journaled route decision must name a target the route declares; anything else fails loudly. */
|
|
919
|
-
function assertRouteTargetDeclared(route, stepId, selected, runId) {
|
|
920
|
-
const targets = routeTargets(route);
|
|
921
|
-
if (!targets.has(selected)) {
|
|
922
|
-
throw new UsageError(`Workflow run ${runId} has a completed route step "${stepId}" whose journaled route decision selected ` +
|
|
923
|
-
`"${selected}", which is not a declared branch or default target of the route (valid targets: ` +
|
|
924
|
-
`${[...targets].join(", ") || "(none)"}). The route evidence was corrupted or manually edited — refusing to ` +
|
|
925
|
-
`apply a bogus route decision that would skip the real branch targets. Start a new run.`);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
/**
|
|
929
|
-
* Replay journaled route decisions into the skip bookkeeping (resume path).
|
|
930
|
-
* For every COMPLETED route step of the frozen plan, in spine order: the
|
|
931
|
-
* journaled decision wins; else a re-derivation from the frozen plan +
|
|
932
|
-
* journaled evidence; else fail loudly. A SKIPPED route step cascades its
|
|
933
|
-
* targets into the skip set exactly as on the live path.
|
|
934
|
-
*/
|
|
935
|
-
export function seedJournaledRouteDecisions(plan, state, routeSelected, routeUnselected) {
|
|
936
|
-
const evidence = {};
|
|
937
|
-
for (const s of state.workflow.steps)
|
|
938
|
-
evidence[s.id] = s.evidence;
|
|
939
|
-
for (const stepPlan of plan.steps) {
|
|
940
|
-
if (!stepPlan.route)
|
|
941
|
-
continue;
|
|
942
|
-
const stepState = state.workflow.steps.find((s) => s.id === stepPlan.stepId);
|
|
943
|
-
if (!stepState)
|
|
944
|
-
continue;
|
|
945
|
-
if (stepState.status === "skipped") {
|
|
946
|
-
cascadeSkippedRouter(stepPlan.route, stepPlan.stepId, routeUnselected);
|
|
947
|
-
continue;
|
|
948
|
-
}
|
|
949
|
-
if (stepState.status !== "completed")
|
|
950
|
-
continue;
|
|
951
|
-
let selected = journaledRouteSelection(stepState.evidence);
|
|
952
|
-
if (selected !== undefined) {
|
|
953
|
-
// a stored decision must name a declared target — a bogus one
|
|
954
|
-
// (tampered/hand-edited evidence) fails loudly rather than seeding a skip
|
|
955
|
-
// set that buries the real branches.
|
|
956
|
-
assertRouteTargetDeclared(stepPlan.route, stepPlan.stepId, selected, state.run.id);
|
|
957
|
-
}
|
|
958
|
-
if (selected === undefined) {
|
|
959
|
-
const scope = {
|
|
960
|
-
params: state.run.params ?? {},
|
|
961
|
-
stepOutputs: routeStepOutputs(evidence, stepPlan.stepId, stepState.evidence ?? {}),
|
|
962
|
-
};
|
|
963
|
-
const decision = evaluateRoute(stepPlan.route, scope);
|
|
964
|
-
if (decision.ok)
|
|
965
|
-
selected = decision.selected;
|
|
966
|
-
}
|
|
967
|
-
if (selected === undefined) {
|
|
968
|
-
throw new UsageError(`Workflow run ${state.run.id} has a completed route step "${stepPlan.stepId}" with no journaled route ` +
|
|
969
|
-
`decision, and the decision cannot be re-derived from the journaled evidence. Refusing to guess which ` +
|
|
970
|
-
`branch was selected. The run journal is inconsistent; abandon this run and start a new one.`);
|
|
971
|
-
}
|
|
972
|
-
applyRouteDecision(stepPlan.route, stepPlan.stepId, selected, routeSelected, routeUnselected);
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
830
|
/**
|
|
976
831
|
* The blocked-step notes for a verifier-infrastructure failure (bug: judge
|
|
977
832
|
* outage must not burn the gate budget). Shared by every judge-failure path —
|
|
@@ -1050,8 +905,6 @@ async function blockFinalizedStepForChildWorkflow(input, childBlocked) {
|
|
|
1050
905
|
* Perform one completion attempt for an executed step:
|
|
1051
906
|
* - a hard unit failure fails the step (a retryable artifact-schema mismatch
|
|
1052
907
|
* with loops left returns `retry` without a gate row);
|
|
1053
|
-
* - a route decision is evaluated, journaled on the evidence, and applied to
|
|
1054
|
-
* the skip bookkeeping; an unroutable value fails the step;
|
|
1055
908
|
* - the gate judges a summary built from the promoted artifact: a rejection
|
|
1056
909
|
* returns `retry` (loops left) or `gate-exhausted`, a pass `advanced`;
|
|
1057
910
|
* - a judge infrastructure failure is not a verdict: it blocks the step for
|
|
@@ -1093,34 +946,12 @@ export async function finalizeExecutedStep(input) {
|
|
|
1093
946
|
return blockFinalizedStep(input, "this step declares completion criteria but no verification judge is available " +
|
|
1094
947
|
"(the frozen plan resolves no judge — set workflow.judgeEngine, or restore the judge configuration)");
|
|
1095
948
|
}
|
|
1096
|
-
// Route evaluation BEFORE completion: an unroutable value is an
|
|
1097
|
-
// authoring/config failure that must fail the step deterministically.
|
|
1098
|
-
let summaryOverride;
|
|
1099
|
-
if (stepPlan.route) {
|
|
1100
|
-
const scope = {
|
|
1101
|
-
params: input.params,
|
|
1102
|
-
stepOutputs: routeStepOutputs(input.priorEvidence, stepId, result.evidence),
|
|
1103
|
-
};
|
|
1104
|
-
const decision = evaluateRoute(stepPlan.route, scope);
|
|
1105
|
-
if (!decision.ok) {
|
|
1106
|
-
const notes = `Step "${stepId}" route failed: ${decision.error}`;
|
|
1107
|
-
await completeWorkflowStep({ runId, stepId, status: "failed", notes, evidence: result.evidence });
|
|
1108
|
-
return { kind: "failed", summary: notes, routeFailure: true };
|
|
1109
|
-
}
|
|
1110
|
-
applyRouteDecision(stepPlan.route, stepId, decision.selected, input.routeSelected, input.routeUnselected);
|
|
1111
|
-
// Journal the decision on the evidence: resume replays it via
|
|
1112
|
-
// seedJournaledRouteDecisions, so the skip set survives re-invocation.
|
|
1113
|
-
result.evidence.route = { input: stepPlan.route.input, value: decision.value, selected: decision.selected };
|
|
1114
|
-
if (!stepPlan.root) {
|
|
1115
|
-
summaryOverride = `Step "${stepId}" routed on ${stepPlan.route.input}: value "${decision.value}" selected step "${decision.selected}".`;
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
949
|
// Artifact-judging gate: a criteria-bearing executing step is judged on a
|
|
1119
950
|
// summary BUILT FROM the promoted artifact; everything else keeps the machine
|
|
1120
|
-
// summary
|
|
951
|
+
// summary.
|
|
1121
952
|
const summary = stepPlan.root && completionCriteria.length > 0
|
|
1122
953
|
? buildArtifactSummary(stepId, result.units, result.evidence)
|
|
1123
|
-
:
|
|
954
|
+
: result.summary;
|
|
1124
955
|
// Journal engine-driven judge calls as unit rows. With no criteria there is
|
|
1125
956
|
// no judge invocation or row; a criteria-bearing plan without a judge is a
|
|
1126
957
|
// configuration error rather than a silent bypass.
|
|
@@ -1233,7 +1064,7 @@ export async function finalizeExecutedStep(input) {
|
|
|
1233
1064
|
return blockFinalizedStep(input, judgeFailure ?? "the verification judge failed");
|
|
1234
1065
|
}
|
|
1235
1066
|
if (!rejection) {
|
|
1236
|
-
return { kind: "advanced"
|
|
1067
|
+
return { kind: "advanced" };
|
|
1237
1068
|
}
|
|
1238
1069
|
if (loopsRemaining) {
|
|
1239
1070
|
return { kind: "retry", gateFeedback: { feedback: rejection.feedback, missing: rejection.missing } };
|
|
@@ -73,16 +73,6 @@ export async function dispatchWorkflowExecution(request, feedback) {
|
|
|
73
73
|
...(notices ? { notices } : {}),
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
-
if (request.cwd && lowered.runner.kind === "llm") {
|
|
77
|
-
return {
|
|
78
|
-
ok: false,
|
|
79
|
-
text: "",
|
|
80
|
-
failureReason: "isolation_unsupported",
|
|
81
|
-
error: `unit ${JSON.stringify(request.unitId)} declares isolation: worktree but resolved to the "llm" runner, ` +
|
|
82
|
-
`which has no working directory to isolate. Use the agent or sdk runner for isolated units.`,
|
|
83
|
-
...(notices ? { notices } : {}),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
76
|
let result;
|
|
87
77
|
try {
|
|
88
78
|
const eventSource = forwardedDispatchEventSource(request);
|
|
@@ -12,23 +12,13 @@ import { UsageError } from "../../core/errors.js";
|
|
|
12
12
|
import { deriveInstallations } from "../../indexer/installations.js";
|
|
13
13
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
14
14
|
import { resolveAssetPath } from "../../sources/resolve.js";
|
|
15
|
-
import { isInferredSecretName } from "../../tasks/log-redaction.js";
|
|
16
15
|
import { SECRET_TOKEN_RE } from "../exec/environment.js";
|
|
17
|
-
import { detectSecretShapedParams } from "../exec/param-secrets.js";
|
|
18
16
|
/**
|
|
19
|
-
* A step's frozen environment:
|
|
17
|
+
* A step's frozen environment: `pass_env` names, then
|
|
20
18
|
* one value-free descriptor per `unit.env` ref (owner, key set, secret-token
|
|
21
19
|
* names). Values are read at dispatch (`exec/environment.ts`), never frozen.
|
|
22
20
|
*/
|
|
23
21
|
export function freezeEnvironment(source, exec, context) {
|
|
24
|
-
const literals = Object.entries(source.env ?? {}).map(([name, raw]) => {
|
|
25
|
-
const value = String(raw);
|
|
26
|
-
// A literal is frozen into plan_json; a credential belongs in an env ref, as for task env.
|
|
27
|
-
if (isInferredSecretName(name) || detectSecretShapedParams({ [name]: value }).length > 0) {
|
|
28
|
-
throw new UsageError(`Workflow step ${source.id} env.${name} is a secret-shaped literal env value. Store credentials in an env asset (unit.env) rather than workflow source.`, "WORKFLOW_SOURCE_INVALID");
|
|
29
|
-
}
|
|
30
|
-
return Object.freeze({ kind: "literal", name, value });
|
|
31
|
-
});
|
|
32
22
|
const passThrough = (exec?.passEnv ?? []).map((name) => Object.freeze({ kind: "pass-through", name }));
|
|
33
23
|
const seen = new Set();
|
|
34
24
|
const envRefs = [];
|
|
@@ -62,7 +52,7 @@ export function freezeEnvironment(source, exec, context) {
|
|
|
62
52
|
precedence,
|
|
63
53
|
}));
|
|
64
54
|
}
|
|
65
|
-
return [...
|
|
55
|
+
return [...passThrough, ...envRefs];
|
|
66
56
|
}
|
|
67
57
|
/** Load a command or persona the workflow names, resolved through the workflow's own bundles. */
|
|
68
58
|
export async function workflowExecutionSource(ref, kind, context) {
|
|
@@ -143,39 +133,8 @@ function configuredOwner(bundle, config) {
|
|
|
143
133
|
adapter: component?.adapter ?? "akm",
|
|
144
134
|
};
|
|
145
135
|
}
|
|
146
|
-
const SCRIPT_EXTENSIONS = [
|
|
147
|
-
"",
|
|
148
|
-
".sh",
|
|
149
|
-
".ts",
|
|
150
|
-
".js",
|
|
151
|
-
".py",
|
|
152
|
-
".rb",
|
|
153
|
-
".go",
|
|
154
|
-
".pl",
|
|
155
|
-
".php",
|
|
156
|
-
".lua",
|
|
157
|
-
".r",
|
|
158
|
-
".swift",
|
|
159
|
-
".kt",
|
|
160
|
-
".kts",
|
|
161
|
-
".ps1",
|
|
162
|
-
".cmd",
|
|
163
|
-
".bat",
|
|
164
|
-
];
|
|
165
136
|
function assetExtensions(type) {
|
|
166
|
-
if (type === "script")
|
|
167
|
-
return SCRIPT_EXTENSIONS;
|
|
168
137
|
if (type === "env")
|
|
169
138
|
return ["", ".env"];
|
|
170
|
-
return ["", ".md"
|
|
171
|
-
}
|
|
172
|
-
export function qualifyRef(ref, plural, asset, config) {
|
|
173
|
-
const parsed = parseBundleRef(ref);
|
|
174
|
-
if (parsed.bundle)
|
|
175
|
-
return ref;
|
|
176
|
-
const bundle = parseBundleRef(asset.ref).bundle ?? config.defaultBundle;
|
|
177
|
-
if (!bundle)
|
|
178
|
-
throw new UsageError(`Workflow ref ${ref} has no owning bundle.`, "WORKFLOW_SOURCE_INVALID");
|
|
179
|
-
const concept = parsed.conceptId.startsWith(`${plural}/`) ? parsed.conceptId : `${plural}/${parsed.conceptId}`;
|
|
180
|
-
return makeBundleRef(bundle, concept);
|
|
139
|
+
return ["", ".md"];
|
|
181
140
|
}
|