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
79 lines (78 loc) • 3.96 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/.
/**
* The gate judge built from the step's frozen judge target. Its outcome is
* journaled (the gate row, or a blocked step's notes), so it goes through the
* same dispatch redaction as every unit; its dispatch carries the real
* run/step and gate unit ids the gate row records. A judge carries no `env:`
* bindings (a step's environment belongs to the work, not the verifier).
*/
import { warn } from "../../core/warn.js";
import { collectWorkflowDispatchSensitiveValues, withDispatchRedaction } from "./dispatch-redaction.js";
import { dispatchWorkflowExecution, prepareWorkflowExecution, } from "./unit-dispatch.js";
/** The gate's node id — identical to what `journalGateEvaluationStart` writes. */
export function gateNodeId(stepId) {
return `${stepId}.gate`;
}
/** Identity for one judge dispatch: the caller's row identity, else the run/step with the gate node id. */
function dispatchIdentity(owner, identity) {
return identity ?? { ...owner, unitId: gateNodeId(owner.stepId) };
}
function warnLoweringNotices(...groups) {
const emitted = new Set();
for (const notices of groups) {
for (const notice of notices ?? []) {
const key = JSON.stringify([notice.code, notice.severity, notice.adapter, notice.field, notice.message]);
if (emitted.has(key))
continue;
emitted.add(key);
warn(`Workflow judge lowering notice (${notice.code}; ${notice.adapter}; ${notice.field}): ${notice.message}`);
}
}
}
/** Build a gate judge from the frozen target without consulting live config; `eventSource` stamps it like a unit. */
export function frozenSummaryJudge(target, signal, dispatcher, owner, eventSource) {
if (!target)
return null;
const dispatch = withDispatchRedaction(dispatcher ?? dispatchWorkflowExecution);
return async ({ system, user }, identity) => {
const id = dispatchIdentity(owner, identity);
const commonRequest = {
runId: id.runId,
stepId: id.stepId,
unitId: id.unitId,
nodeId: gateNodeId(id.stepId),
...(id.attempt !== undefined ? { attempt: id.attempt } : {}),
...(id.dispatchId !== undefined ? { dispatchId: id.dispatchId } : {}),
prompt: user,
systemPrompt: system,
...(signal ? { signal } : {}),
};
const request = {
...commonRequest,
frozenTarget: target,
timeoutMs: target.runner.timeoutMs ?? null,
...(eventSource !== undefined ? { eventSource } : {}),
};
// Lowering and authorization precede every live credential/passthrough
// sample. The injected dispatcher may be a test seam, but it receives the
// exact same already-validated request and redaction declaration.
const lowered = prepareWorkflowExecution(request);
const sensitiveValues = collectWorkflowDispatchSensitiveValues({ runner: target.runner }, undefined);
const outcome = await dispatch({
...request,
...(sensitiveValues.length > 0 ? { sensitiveValues } : {}),
});
if (outcome.usage && id.recordTokens) {
id.recordTokens((outcome.usage.inputTokens ?? 0) + (outcome.usage.outputTokens ?? 0) + (outcome.usage.reasoningTokens ?? 0));
}
// Only the common lowerer's own sanitized notices are loggable here. An
// injected dispatcher is not trusted to supply prompt/body-free messages.
warnLoweringNotices(lowered.notices);
if (!outcome.ok) {
throw new Error(outcome.error || `Verification engine "${target.request.engine.name}" failed.`);
}
return outcome.text ?? "";
};
}