akm-cli
Version:
akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur
153 lines (152 loc) • 7.05 kB
JavaScript
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/**
* `akm workflow plan <ref>` — compile + freeze WITHOUT publishing. Zero
* durable writes, zero usage/event rows: this module calls exactly the same
* two functions `startWorkflowRun` does to reach a frozen plan
* (`loadWorkflowAsset`, `freezeWorkflow`) and NOTHING else — never
* `publishWorkflowRunV4`, `startWorkflowRun`, `appendEvent`, or `akmIndex`.
*
* SECRET-FREE, by construction (§4.6's closed print list): a resolved
* reference VALUE is never printed (references resolve at pre-attempt, not
* here); a `literal` **environment** binding's value is never printed (only
* `environment[].kind`/`.name`, and an `env-ref`'s `.ref`/`.keys`/
* `.secretNames` — all NAMES); `request.command.content`,
* `request.persona`, `request.conversation`, `request.runtime.environment`,
* and a script target's `bytesBase64` are never read at all.
*/
import path from "node:path";
import { loadConfig } from "../../core/config/config.js";
import { buildExecutionFromWire } from "../../integrations/agent/execution.js";
import { freezeWorkflow } from "../../workflows/freeze/freeze.js";
import { computePlanHash } from "../../workflows/ir/plan-hash.js";
import { loadWorkflowAsset } from "../../workflows/runtime/workflow-asset-loader.js";
/** The step's dispatch unit — the map template for a fan-out, else the root unit. Undefined for a route step. */
function stepUnit(step) {
const root = step.root;
if (!root)
return undefined;
return root.kind === "map" ? root.template : root;
}
function projectEnvironmentBinding(binding) {
if (binding.kind === "env-ref") {
return { kind: binding.kind, ref: binding.ref, keys: binding.keys, secretNames: binding.secretNames };
}
// literal / pass-through: kind + name only — a literal's VALUE is never printed.
return { kind: binding.kind, name: binding.name };
}
function projectInputBinding(binding) {
return binding.kind === "literal"
? { name: binding.name, kind: "literal", value: binding.value }
: { name: binding.name, kind: "reference", from: binding.from };
}
function childExportedOutputNames(frozenPlan) {
return frozenPlan.outputs ? Object.keys(frozenPlan.outputs) : ["runId", "status"];
}
/**
* A `child-workflow` target's `expansion`. Recurses into the embedded
* plan's own steps in the identical shape — `usesById` is
* omitted for the recursive call because a nested child's own authored
* source is not available here (only its already-frozen plan is), so a
* task-wrapped step nested inside a child conservatively reports `via:
* "direct"` rather than guessing at its authoring surface.
*/
function childExpansion(target) {
return {
via: "child",
childRef: target.ref,
childPlanHash: target.planHash,
childVia: target.via,
...(target.taskRef !== undefined ? { childTaskRef: target.taskRef } : {}),
childOutputs: childExportedOutputNames(target.frozenPlan),
steps: target.frozenPlan.steps.map((step, index) => projectStep(step, index, undefined)),
};
}
/** The task/child expansion boundary for one step (§4.6). */
function stepExpansion(step, frozenTarget, usesById) {
if (frozenTarget?.kind === "child-workflow")
return childExpansion(frozenTarget);
const uses = usesById?.get(step.stepId);
if (uses?.startsWith("tasks/"))
return { via: "task", taskRef: uses };
return { via: "direct" };
}
function projectStep(step, sequenceIndex, usesById) {
const unit = stepUnit(step);
const frozenTarget = unit?.frozenTarget;
const kind = step.route ? "route" : step.root?.kind === "map" ? "map" : "unit";
const inputBindings = frozenTarget?.inputBindings;
return {
stepId: step.stepId,
sequenceIndex,
kind,
targetKind: frozenTarget?.kind ?? null,
...(step.root?.kind === "map" ? { concurrency: step.root.concurrency } : {}),
inputs: unit?.inputs ?? [],
environment: (unit?.environment ?? []).map(projectEnvironmentBinding),
...(inputBindings && inputBindings.length > 0 ? { inputBindings: inputBindings.map(projectInputBinding) } : {}),
gate: {
criteria: step.gate.criteria,
maxLoops: step.gate.maxLoops,
judgeEngine: step.gate.frozenJudge ? step.gate.frozenJudge.request.engine.name : null,
},
...(step.outputSchema !== undefined ? { outputSchema: step.outputSchema } : {}),
expansion: stepExpansion(step, frozenTarget, usesById),
};
}
/**
* Every `command`-kind frozen target's lowering notices, recomputed PURELY
* from its own already-frozen `request` (the identical computation
* `freeze/targets/command.ts`'s `commandResult` already performs at freeze
* time and discards) — walked over the whole plan, including gate judges and
* recursively into every embedded child plan. Read-only: `buildExecutionFromWire`
* reads no config and dispatches nothing.
*/
function collectLoweringNotices(plan, config) {
const notices = [];
const lower = (target) => {
notices.push(...buildExecutionFromWire(target).notices);
};
for (const step of plan.steps) {
const unit = stepUnit(step);
if (unit) {
if (unit.frozenTarget.kind === "command")
lower(unit.frozenTarget);
else if (unit.frozenTarget.kind === "child-workflow")
notices.push(...collectLoweringNotices(unit.frozenTarget.frozenPlan, config));
}
if (step.gate.frozenJudge)
lower(step.gate.frozenJudge);
}
return notices;
}
/**
* Compile + freeze `ref` and project the frozen plan into the read-only
* `akm workflow plan` envelope. Never publishes, never writes, never warns.
*/
export async function akmWorkflowPlan(ref) {
const asset = await loadWorkflowAsset(ref);
const config = loadConfig();
const frozen = await freezeWorkflow(asset, config);
const plan = frozen.plan;
const usesById = new Map(asset.plan.steps.map((step) => [step.stepId, step.spec?.uses]));
const sourceFormat = path.extname(asset.path).toLowerCase() === ".md" ? "markdown" : "github-yaml";
return {
ok: true,
ref: asset.ref,
title: asset.title,
sourceFormat,
sourcePath: asset.path,
irVersion: plan.irVersion,
planHash: computePlanHash(plan),
published: false,
execution: plan.execution,
...(plan.budget ? { budget: plan.budget } : {}),
...(plan.params ? { params: plan.params } : {}),
...(plan.outputs ? { outputs: plan.outputs } : {}),
steps: plan.steps.map((step, index) => projectStep(step, index, usesById)),
notices: collectLoweringNotices(plan, config),
warnings: frozen.warnings.map((warning) => warning.message),
};
}