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
122 lines (121 loc) • 5.62 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/.
import { ConfigError } from "../../core/errors.js";
import { assertFrozenDirectoryContained } from "../../execution/directory-identity.js";
import { canonicalResolvedExecutionRequest } from "../../execution/resolved-request.js";
import { buildExecutionFromWire } from "../../integrations/agent/execution.js";
import { runExecution, unwrapHarnessReply } from "../../integrations/agent/runner-dispatch.js";
/** Lower a frozen common request through its frozen runner only. */
export function prepareWorkflowExecution(request, prompt = request.prompt) {
const target = request.frozenTarget;
if (target.cwdIdentity)
assertFrozenDirectoryContained(target.cwdIdentity);
const wire = JSON.parse(canonicalResolvedExecutionRequest(target.request));
const command = { ...wire.command, content: prompt };
const runtime = {
...wire.runtime,
...(request.cwd !== undefined
? { workspace: request.cwd }
: target.cwdIdentity
? { workspace: target.cwdIdentity.realCwd }
: {}),
...(request.env !== undefined ? { environment: request.env } : {}),
};
wire.command = command;
wire.runtime = runtime;
if (request.systemPrompt !== undefined) {
wire.conversation = [{ role: "system", content: request.systemPrompt }];
}
return buildExecutionFromWire({ request: wire, runner: target.runner });
}
function message(err) {
return err instanceof Error ? err.message : String(err);
}
/**
* The `eventSource` to forward into `runExecution`, or undefined when the
* unit's own `env:` already binds `AKM_EVENT_SOURCE` — an authored binding
* wins, as it does for exec units.
*/
export function forwardedDispatchEventSource(request) {
if (request.eventSource === undefined)
return undefined;
if (request.env?.AKM_EVENT_SOURCE !== undefined)
return undefined;
return request.eventSource;
}
/**
* Dispatch one frozen workflow engine call through the common prepared/lowered
* seam. Credential materialization happens inside the final dispatch only.
*/
export async function dispatchWorkflowExecution(request, feedback) {
const prompt = feedback ? `${request.prompt}\n\n${feedback}` : request.prompt;
// Internal invariant: native-executor routes child-workflow units to the
// child executor before dispatch, so reaching here is an engine bug.
if (request.frozenTarget.kind === "child-workflow") {
throw new Error(`unit ${JSON.stringify(request.unitId)} targets child workflow ${JSON.stringify(request.frozenTarget.ref)}, ` +
"but reached dispatchWorkflowExecution directly. The child-workflow dispatch seam " +
"(src/workflows/exec/child-workflow.ts) should have routed it before dispatch was ever reached — " +
"this is an engine routing bug, not a problem with the workflow itself.");
}
if (request.frozenTarget.kind !== "command") {
throw new ConfigError(`unit ${JSON.stringify(request.unitId)} is not a command target.`, "INVALID_CONFIG_FILE");
}
const lowered = prepareWorkflowExecution(request, prompt);
const notices = lowered.notices.length > 0 ? lowered.notices : undefined;
if (request.env && Object.keys(request.env).length > 0 && lowered.runner.kind === "llm") {
return {
ok: false,
text: "",
failureReason: "env_unsupported",
error: `unit ${JSON.stringify(request.unitId)} declares env bindings, which require a child process ` +
`(agent or sdk runner) — the "llm" runner cannot inject a per-unit child environment.`,
...(notices ? { notices } : {}),
};
}
if (request.cwd && lowered.runner.kind === "llm") {
return {
ok: false,
text: "",
failureReason: "isolation_unsupported",
error: `unit ${JSON.stringify(request.unitId)} declares isolation: worktree but resolved to the "llm" runner, ` +
`which has no working directory to isolate. Use the agent or sdk runner for isolated units.`,
...(notices ? { notices } : {}),
};
}
let result;
try {
const eventSource = forwardedDispatchEventSource(request);
result = await runExecution(lowered, {
runOptions: {
stdio: "captured",
parseOutput: "text",
...(request.signal ? { signal: request.signal } : {}),
},
...(eventSource !== undefined ? { eventSource } : {}),
});
}
catch (err) {
if (err instanceof ConfigError)
throw err;
return {
ok: false,
text: "",
failureReason: "dispatch_error",
error: message(err),
...(notices ? { notices } : {}),
};
}
const extraction = result.ok ? unwrapHarnessReply(lowered.runner, result) : undefined;
const text = extraction?.text ?? result.stdout;
const sessionId = extraction?.sessionId ?? result.sessionId;
return {
ok: result.ok,
text,
...(sessionId !== undefined ? { sessionId } : {}),
...(result.reason ? { failureReason: result.reason } : {}),
...(result.error ? { error: result.error } : {}),
...(result.usage ? { usage: result.usage } : {}),
...(notices ? { notices } : {}),
};
}