UNPKG

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

292 lines (291 loc) • 12.4 kB
// 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 { UsageError } from "../../core/errors.js"; import { createInlineResolvedCommand, createResolvedCommand, createResolvedPersona, } from "../../execution/resolved-request.js"; import { isPortableExecutionAgentSelector, } from "../../execution/source.js"; import { recordIndexedShowUsage } from "../../indexer/usage/show-usage.js"; import { resolveUsageEventSource } from "../../indexer/usage/usage-events.js"; import { fallbackAnnouncement } from "../../integrations/agent/engine-fallback.js"; import { buildExecution, resolveExecution, } from "../../integrations/agent/execution.js"; import { runExecution } from "../../integrations/agent/runner-dispatch.js"; import { parseBuiltinCommandAction } from "./builtin-action.js"; import { loadAdapterExecutionSource, } from "./execution-source-loader.js"; import { applyPortableCommandArguments } from "./portable-template.js"; /** * The ambient `AKM_EVENT_SOURCE` wins over the task runner's provenance; it * rides `runExecution`'s single-purpose `eventSource`, never `runOptions.env`, * so a caller cannot replace the request's own environment. */ function runOptionsFor(options) { const { eventSource, ...rest } = options; if (eventSource === undefined) return rest; return { ...rest, eventSource: process.env.AKM_EVENT_SOURCE ?? eventSource }; } function own(value, key) { return Object.hasOwn(value, key); } function selectAgent(command, invocationDefaults, current) { let selected = { present: false, source: "command", }; for (const [source, values] of [ ["command", command], ["invocation", invocationDefaults], ["current", current], ]) { if (values && own(values, "agent")) selected = { present: true, value: values.agent, source }; } return selected; } function defaultSourceLoader(ref, kind, options) { return kind === "command" ? loadAdapterExecutionSource(ref, "command", options) : loadAdapterExecutionSource(ref, "persona", options); } function qualifyCommandSelectedPersona(selector, command) { if (!command || selector.includes("//")) return selector; return `${command.identity.bundle}//${selector}`; } export async function prepareCommandInvocation(options) { const action = parseBuiltinCommandAction(options.action); const sourceLoader = options.sourceLoader ?? defaultSourceLoader; let renderedCommand; let commandDefaults = Object.freeze({}); let command; if (action.kind === "stored") { const rendered = await sourceLoader(action.ref, "command", { config: options.config, ...(options.sourceLookup ? { lookup: options.sourceLookup } : {}), }); if (rendered.kind !== "command") throw new TypeError("command source loader returned a non-command source"); renderedCommand = rendered; commandDefaults = rendered.defaults; const applied = applyPortableCommandArguments(rendered.content, action.arguments, rendered.identity.ref); command = createResolvedCommand({ source: rendered, ...(own(applied, "argumentInput") ? { argumentInput: applied.argumentInput } : {}), content: applied.content, }); } else { if (options.inlineContentMode === "literal") { if (action.arguments !== undefined) { throw new UsageError("Literal inline command content cannot declare portable arguments.", "INVALID_FLAG_VALUE"); } command = createInlineResolvedCommand({ template: action.content, content: action.content }); } else { const applied = applyPortableCommandArguments(action.content, action.arguments, "inline command"); command = createInlineResolvedCommand({ template: applied.template, ...(own(applied, "argumentInput") ? { argumentInput: applied.argumentInput } : {}), content: applied.content, }); } } const selectedAgent = selectAgent(commandDefaults, options.invocationDefaults, options.current); let renderedPersona; if (selectedAgent.present && typeof selectedAgent.value === "string" && isPortableExecutionAgentSelector(selectedAgent.value)) { const lookupRef = selectedAgent.source === "command" ? qualifyCommandSelectedPersona(selectedAgent.value, renderedCommand) : selectedAgent.value; const rendered = await sourceLoader(lookupRef, "persona", { config: options.config, ...(options.sourceLookup ? { lookup: options.sourceLookup } : {}), }); if (rendered.kind !== "persona") throw new TypeError("persona source loader returned a non-persona source"); renderedPersona = rendered; } const persona = renderedPersona ? createResolvedPersona(renderedPersona) : selectedAgent.present ? null : undefined; return resolveExecution({ command, config: options.config, ...(persona !== undefined ? { persona } : {}), ...(renderedPersona ? { agentLayer: { id: renderedPersona.identity.ref, values: renderedPersona.defaults } } : {}), commandLayer: { id: renderedCommand?.identity.ref ?? "inline-command", values: commandDefaults, }, ...(options.invocationDefaults ? { invocationDefaults: options.invocationDefaults } : {}), ...(options.current ? { current: options.current } : {}), ...(options.modelMap ? { modelMap: options.modelMap } : {}), }); } function compareText(left, right) { return left < right ? -1 : left > right ? 1 : 0; } const DIAGNOSTIC_PROVENANCE_FIELDS = new Set([ "/inference", "agent", "authorization", "command", "engine", "engine.requested", "model", "outputSchema", "persona", "runtime.environment", "runtime.settings", "runtime.timeoutMs", "runtime.workspace", "tools", ]); const DIAGNOSTIC_NOTICE_FIELDS = new Set([ "agent", "command.content", "command.extensions", "conversation", "engine", "engine.extensions", "extensions", "inference", "model", "outputSchema", "persona", "persona.extensions", "runtime.environment", "runtime.extensions", "runtime.settings", "runtime.timeoutMs", "runtime.workspace", "tools", ]); function canonicalDiagnosticProvenanceField(field) { if (field.startsWith("/inference/")) return "/inference/*"; return DIAGNOSTIC_PROVENANCE_FIELDS.has(field) ? field : undefined; } function canonicalDiagnosticNoticeField(field) { if (typeof field !== "string") return undefined; if (field.startsWith("inference.")) return "inference.*"; return DIAGNOSTIC_NOTICE_FIELDS.has(field) ? field : undefined; } function diagnosticProvenance(provenance) { const canonical = new Map(); for (const [field, source] of Object.entries(provenance)) { const safeField = canonicalDiagnosticProvenanceField(field); if (!safeField) continue; const candidate = Object.freeze({ field: safeField, layer: source.layer, kind: source.kind, via: source.via }); const previous = canonical.get(safeField); if (!previous || compareText(JSON.stringify(candidate), JSON.stringify(previous)) < 0) { canonical.set(safeField, candidate); } } return Object.freeze([...canonical.values()].sort((left, right) => compareText(left.field, right.field))); } function safeDiagnosticToken(value) { return value.length <= 128 && /^[a-z][a-z0-9-]*$/.test(value); } /** Rebuild a known lowerer notice instead of forwarding its optional details or arbitrary message bytes. */ function safeDiagnosticNotice(notice) { if (!safeDiagnosticToken(notice.adapter)) return undefined; switch (notice.code) { case "untranslated-field": { const field = canonicalDiagnosticNoticeField(notice.field); if (!field) return undefined; return Object.freeze({ code: "untranslated-field", severity: "warning", adapter: notice.adapter, field, message: `The ${notice.adapter} lowerer does not translate resolved field ${field}; dispatch will continue optimistically.`, }); } case "conversation-prompt-composed": return Object.freeze({ code: "conversation-prompt-composed", severity: "warning", adapter: notice.adapter, field: "conversation", message: `The ${notice.adapter} transport has no native multi-message channel; AKM composed the conversation prefix into one deterministic JSON prompt block.`, }); case "persona-prompt-composed": return Object.freeze({ code: "persona-prompt-composed", severity: "info", adapter: notice.adapter, field: "persona", message: "The selected engine has no native persona channel; AKM composed the persona into the prompt.", }); default: return undefined; } } function diagnosticNotices(notices) { const canonical = new Map(); for (const notice of notices) { const safe = safeDiagnosticNotice(notice); if (safe) canonical.set(JSON.stringify(safe), safe); } return Object.freeze([...canonical.values()].sort((left, right) => compareText(JSON.stringify(left), JSON.stringify(right)))); } /** * Build a prepared invocation and project only safe structural diagnostics. * Reads no credential, records no usage, and exposes no request values. */ export function inspectPreparedCommandInvocation(prepared) { const built = buildExecution(prepared.request, prepared.runner); return { schemaVersion: 1, shape: "command-dry-run", ok: true, dryRun: true, engine: prepared.request.engine.name, provenance: diagnosticProvenance(prepared.provenance), notices: diagnosticNotices(built.notices), }; } function resultEnvelope(result, engine, warnings, notices) { return { schemaVersion: 2, ok: result.ok, shape: "agent-result", engine, exitCode: result.exitCode, stdout: result.stdout ?? "", stderr: result.stderr ?? "", durationMs: result.durationMs, ...(result.error !== undefined ? { error: result.error } : {}), ...(result.reason !== undefined ? { reason: result.reason } : {}), ...(warnings.length > 0 ? { warnings } : {}), ...(notices.length > 0 ? { notices } : {}), }; } export async function dispatchPreparedCommandInvocation(prepared, options = {}) { const { request } = prepared; const built = buildExecution(request, prepared.runner); const selectedEngine = request.engine.name; const result = await runExecution(built, runOptionsFor(options)); const consumedRefs = new Set(); if (request.command.source) consumedRefs.add(request.command.source.ref); if (request.persona) consumedRefs.add(request.persona.source.ref); // F-1 (spec §5.2 point 3): options.eventSource is only a FALLBACK — an // ambient AKM_EVENT_SOURCE still wins (D5 clause d). Absent, this is // byte-identical to the pre-P1b bare resolveUsageEventSource() call (P-07's // own default is "user"). const eventSource = resolveUsageEventSource(process.env, options.eventSource ?? "user"); for (const ref of consumedRefs) recordIndexedShowUsage(ref, eventSource); const announcement = fallbackAnnouncement(prepared.fallbackEngineName, selectedEngine); return resultEnvelope(result, selectedEngine, announcement ? [announcement] : [], built.notices); } export async function executeCommandInvocation(options, dispatchOptions = {}) { return dispatchPreparedCommandInvocation(await prepareCommandInvocation(options), dispatchOptions); }