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

122 lines (121 loc) • 6.99 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/. /** * Load and project one task asset into immutable executable work: id * validation, adapter detection, owner resolution, source read, strict task * source parsing, config selection, bundle-name resolution, and the * `prepareTaskV3Execution` call. Everything here is non-mutating — run-task.ts * reserves a durable attempt only after this resolves. */ import fs from "node:fs"; import { detectAdapterId } from "../../core/adapter/detect-adapter.js"; import { makeBundleRef } from "../../core/asset/asset-ref.js"; import { loadConfig } from "../../core/config/config.js"; import { NotFoundError } from "../../core/errors.js"; import { resolveWriteTarget } from "../../core/write-source.js"; import { applyInputDefaults, materializeInputFlags, validateInputs, } from "../../execution/input-contract.js"; import { resolveAdapterConceptOwner } from "../../indexer/lookup/adapter-concept-owner.js"; import { resolveAssetPath } from "../../sources/resolve.js"; import { prepareTaskV3Execution } from "../prepare/prepare.js"; import { scheduledTaskContextEnv } from "../scheduler-invocation.js"; import { parseTaskSource } from "../source/parse-task-source.js"; import { projectTaskSourceV4 } from "../source/project-v4.js"; import { TASK_INPUT_DIAGNOSTICS } from "../source/task-input-diagnostics.js"; import { validateTaskConceptId, validateTaskId } from "../task-id.js"; /** * F-3 (spec §5.4): the literal bundle-name fallback, hoisted to a named * constant — the VALUE does not change (it is user-visible data, R-09's * observable behavior). The now-deleted `runner.ts:173`'s (P4) bare * `"stash"` became this at the P1b runner.ts split. */ export const DEFAULT_BUNDLE_NAME = "stash"; const CONFIG_FREE_TASK_RUNTIME = Object.freeze({ configVersion: "0.9.0", semanticSearchMode: "off", }); /** Resolve, parse, and project one task-v3 asset into a frozen, executable projection. */ export async function loadPreparedTask(id, options) { const bundleDir = options.bundleDir; const adapterId = options.adapterId ?? detectAdapterId(bundleDir); if (adapterId === "akm-task") validateTaskConceptId(id); else validateTaskId(id); const taskConceptId = adapterId === "akm" ? `tasks/${id}` : id; const owner = resolveAdapterConceptOwner(bundleDir, adapterId, taskConceptId); if (!owner) { throw new NotFoundError(`Task ${JSON.stringify(id)} was not found in the configured ${JSON.stringify(adapterId)} component.`, "ASSET_NOT_FOUND"); } const filePath = owner.path; const yaml = fs.readFileSync(filePath, "utf8"); const parsed = parseTaskSource({ yaml, filePath, workspaceRoot: bundleDir }); const source = projectTaskSourceV4(parsed.v4); const requiresCommandConfig = source.target.kind === "uses" && (source.target.uses.kind === "builtin-command" || source.target.uses.kind === "command"); const config = requiresCommandConfig ? loadConfig() : CONFIG_FREE_TASK_RUNTIME; const bundleName = options.bundleName ?? config.defaultBundle ?? DEFAULT_BUNDLE_NAME; // P2a Lane C, Stage 2 (spec docs/plans/specs/p2a-task-source-v4.md §5.1): // materialize akm task run's raw input flags (Stage 1, // src/commands/tasks/tasks-cli.ts) against the task's own declared // inputs: contract (empty when the document declares none, so any input // flag on such a task fails UNKNOWN_FLAG — there is nothing declared to // match against). // materializeInputFlags already validates the flag-supplied values; a // required input satisfied only by its own default (never supplied as a // flag) needs the SEPARATE validateInputs call below, run after defaults // are applied — materializeInputFlags returns {} immediately for zero // flags, before ever checking `required` (spec §5.1, input-contract.ts's // own header). const inputContract = parsed.v4.inputs ?? {}; const materializedInputs = materializeInputFlags(inputContract, options.inputFlags ?? [], TASK_INPUT_DIAGNOSTICS); const defaultedInputs = applyInputDefaults(inputContract, materializedInputs); const requiredErrors = validateInputs(inputContract, defaultedInputs); if (requiredErrors.length > 0) throw TASK_INPUT_DIAGNOSTICS.contractViolation(requiredErrors); const inputBindings = Object.entries(defaultedInputs).map(([name, value]) => Object.freeze({ kind: "literal", name, value })); if (options.captureTaskInvocation) { const invocation = Object.freeze({ taskRef: makeBundleRef(bundleName, taskConceptId), caller: Object.freeze({ kind: "cli" }), ...(inputBindings.length > 0 ? { inputs: Object.freeze(inputBindings) } : {}), }); options.captureTaskInvocation(invocation); } // P2b Lane B (spec docs/plans/specs/p2b-input-bindings.md §4.3, B-40): a // task source v4 document's own declared `inputs:` deliver into a // `uses: workflows/<ref>` target's child-run params through the EXISTING // with-> params path (prepare.ts's workflow branch already reads // `document.target.with`) — task source v4 never authors `with:` on a // workflow target itself (task-source-v4.ts's own parser accepts `with:` // only on `uses: akm/command`, source/task-source-v4.ts:329-333), so this // override is purely additive for a workflow-target task. const deliverySource = source.target.kind === "uses" && source.target.uses.kind === "workflow" ? { ...source, target: { ...source.target, with: defaultedInputs } } : source; return prepareTaskV3Execution(deliverySource, { taskId: id, taskRef: makeBundleRef(bundleName, taskConceptId), bundleName, bundleRoot: bundleDir, config, // Agent profiles build child env from an allowlist, so freeze the closed // AKM directory context the scheduled row set before command preparation. ...(options.scheduled ? { schedulerContext: scheduledTaskContextEnv() } : {}), resolveAsset: async ({ bundle, type, name }) => { if (bundle === bundleName) { return { file: await resolveAssetPath(bundleDir, type, name), bundleRoot: bundleDir }; } const resolutionConfig = requiresCommandConfig ? config : loadConfig(); // `bundle` is the qualifier of an asset ref in the task, not a flag. const resolvedBundle = resolveWriteTarget(resolutionConfig, bundle, { requireWritable: false, flag: "The asset ref's bundle", }); return { file: await resolveAssetPath(resolvedBundle.source.path, type, name), bundleRoot: resolvedBundle.source.path, }; }, }); }