/** * Idempotent seed-agent installer — T897 / T1239 / T1241. * * On `cleo init` (or first `cleo session start`), callers invoke * {@link ensureSeedAgentsInstalled} to materialise canonical `.cant` files for * the project. Two install paths are supported, selected at runtime: * * 1. **Meta-agent synthesis (T1239)** — when a project-context.json is * present AND the caller provides an `AgentDispatcher` (typically the * playbook runtime wiring), the installer delegates to the bundled * `agent-architect` meta-agent so agents are customised against the * project's tech-stack + conventions. * 2. **Static copy (legacy T897)** — when no dispatcher is supplied OR the * meta-agent synthesis fails, the installer falls back to copying * direct-usable `.cant` files from `packages/agents/starter-bundle/` * (team.cant + agents/*.cant) into the user's global CANT agents * directory (`~/.local/share/cleo/cant/agents/`). When * `.cleo/project-context.json` is present on this path, template * placeholders are resolved via the canonical variable-substitution SDK * (T1238) before the file is written. * * Per ADR-068 (T1929) the templates directory lives in `@cleocode/agents/templates/` * (starter-bundle deleted by T1932); the installer routes through the * {@link resolveAgentTemplates} SDK helper (T1935) so the path is resolved via the * module graph rather than hardcoded. * * Both paths are idempotent: `~/.local/share/cleo/.seed-version` stores the * last-installed bundle version, and subsequent calls are no-ops unless the * packaged version has advanced past the marker. * * @module agents/seed-install * @task T897 — idempotent install * @task T1238 — variable substitution * @task T1239 — meta-agent refactor * @task T1241 — starter-bundle relocation (D035) * @epic T889 / T1232 */ /** * Marker file path that records the last installed seed bundle version. * * Written as a plain semver string (or CalVer `YYYY.M.patch`). Absent on a * fresh install — treated as `"0"` for comparison purposes. * * @task T897 */ export declare const SEED_VERSION_MARKER_FILENAME = ".seed-version"; /** * Which code path produced an install result. * * @task T1239 */ export type SeedInstallSource = 'meta-agent' | 'static-copy' | 'noop'; /** * Result returned by {@link ensureSeedAgentsInstalled}. * * @task T897 / T1239 */ export interface SeedInstallResult { /** Agent slugs (filename sans `.cant`) that were newly written. */ readonly installed: string[]; /** * Agent slugs that were skipped because an on-disk file already existed * OR the stored version marker matched the bundle version. */ readonly skipped: string[]; /** Absolute path to the directory agents were installed into. */ readonly destination: string; /** * Bundle version string written to the `.seed-version` marker after a * successful install. `null` when the install was a no-op (up-to-date). */ readonly installedVersion: string | null; /** Which code path handled the install. */ readonly source: SeedInstallSource; /** * Variables that were left unresolved during template substitution on the * static-copy path. Empty in meta-agent and no-op scenarios. */ readonly unresolvedVariables: string[]; } /** * Minimal agent-dispatcher shape consumed by {@link ensureSeedAgentsInstalled} * when meta-agent synthesis is available. * * Structurally compatible with `AgentDispatcher` exported from * `@cleocode/core/playbooks/agent-dispatcher`. Accepting the narrow shape here * avoids a hard import cycle and lets the CLI wire any dispatcher * implementation (core's or a custom one) without churn. * * @task T1239 */ export interface SeedInstallDispatcher { dispatch(input: { runId: string; nodeId: string; agentId: string; taskId: string; context: Record; iteration: number; }): Promise<{ status: 'success' | 'failure'; output: Record; error?: string; }>; } /** * Options accepted by {@link ensureSeedAgentsInstalled}. * * All fields are optional so existing call sites continue to work unchanged. * Supply `dispatcher` to opt into meta-agent synthesis; supply `projectRoot` * to enable template-variable substitution on the static-copy fallback. * * @task T1239 */ export interface EnsureSeedAgentsInstalledOptions { /** Absolute path to the project root (for project-context.json lookup). */ projectRoot?: string; /** Optional dispatcher that can execute the `agent-architect` meta-agent. */ dispatcher?: SeedInstallDispatcher; /** * When `true`, skip the meta-agent path even when a dispatcher is provided. * Used by the legacy global postinstall hook where project context is * absent by definition. */ skipMetaAgent?: boolean; /** * Override for the destination directory. Defaults to * {@link getCleoGlobalCantAgentsDir}. Tests can pin an isolated location. */ destinationOverride?: string; } /** * Ensure the canonical seed agents are installed for the current project. * * Two code paths: * * 1. **Meta-agent synthesis** (preferred) — when `options.dispatcher` is set * and `.cleo/project-context.json` exists, delegate to the bundled * `agent-architect` meta-agent so agents are customised against the * project's tech-stack, conventions, and testing framework. * 2. **Static copy with substitution** (fallback) — copy bundled * `.cant` templates into the destination, resolving any * `{{variable}}` placeholders against project context + environment. * Guarantees backwards compatibility with the T897 behaviour. * * Idempotent: checks `~/.local/share/cleo/.seed-version` against the current * bundle version. When they match, every file is listed under `skipped` and no * I/O is performed. When the bundle version is newer, the meta-agent or static * path runs and the marker is updated. * * A file that already exists on disk (same name) is always skipped — partial * upgrades are therefore additive. Use `cleo agent install --global --force` * to overwrite individual agents. * * @param options - Optional meta-agent + project-root hints. * @returns A {@link SeedInstallResult} describing what was installed, what * was skipped, the destination directory, and which source handled * the call. * * @example * ```typescript * // Legacy call site — static copy only, no substitution. * const basic = await ensureSeedAgentsInstalled(); * * // Project-aware — substitution resolves {{tech_stack}} etc. * const withVars = await ensureSeedAgentsInstalled({ projectRoot: process.cwd() }); * * // Full meta-agent synthesis. * const synthesized = await ensureSeedAgentsInstalled({ * projectRoot: process.cwd(), * dispatcher: myDispatcher, * }); * ``` * * @task T897 / T1238 / T1239 */ export declare function ensureSeedAgentsInstalled(options?: EnsureSeedAgentsInstalledOptions): Promise; /** * Per-agent outcome of {@link forceInstallProjectTierAgents}. * * @task T1242 */ export interface ProjectTierForceInstallEntry { /** Business identifier of the agent (kebab-case file basename). */ readonly agentId: string; /** Absolute source path on the project filesystem. */ readonly cantPath: string; /** Whether the agents-row was inserted (`true`) or updated (`false`). */ readonly inserted: boolean; /** Skill slugs successfully attached to the agents-row. */ readonly skillsAttached: ReadonlyArray; /** Non-fatal warnings surfaced by {@link installAgentFromCant}. */ readonly warnings: ReadonlyArray; } /** * Result envelope returned by {@link forceInstallProjectTierAgents}. * * @task T1242 */ export interface ProjectTierForceInstallResult { /** Successfully (re)installed agents. */ readonly installed: ReadonlyArray; /** Agents that failed to install — paired with the underlying error message. */ readonly failed: ReadonlyArray<{ readonly cantPath: string; readonly error: string; }>; /** Absolute path that was scanned for `.cant` files. */ readonly scannedDir: string; } /** * Force-reinstall every project-tier `.cant` agent into the registry. * * Walks `/.cleo/cant/agents/` (the canonical project-tier CANT * directory) and, for each `.cant` file found, calls * {@link installAgentFromCant} with `force: true` so the agents-row is * rewritten / inserted regardless of whether a stale row already exists. This * closes the T1242 gap where `cleo init` deployed the starter bundle to disk * but never re-attached the agents to the registry, leaving operators with * D-002 / D-003 doctor findings the moment a fresh project was initialised * over a previous install. * * The function is intentionally tolerant of per-file failures — a malformed * `.cant` blocks only its own row and is reported via {@link * ProjectTierForceInstallResult.failed}, leaving sibling installs intact. * * Pre-conditions: * - The global `signaldock.db` is bootstrapped via {@link * ensureGlobalAgentRegistryDb} before any DB write. * - When the scanned directory is empty / missing, the result is an empty * `installed` array (no error). * * @param projectRoot - Absolute path to the project root. * @returns Per-file install outcomes plus the directory that was scanned. * * @example * ```typescript * // Inside cleo init, after deployStarterBundle has copied the seeds: * const result = await forceInstallProjectTierAgents(projectRoot); * for (const agent of result.installed) { * created.push(`agent: ${agent.agentId} (${agent.inserted ? 'inserted' : 'updated'})`); * } * for (const failure of result.failed) { * warnings.push(`agent install failed: ${failure.cantPath} — ${failure.error}`); * } * ``` * * @task T1242 */ export declare function forceInstallProjectTierAgents(projectRoot: string): Promise; /** * Shape of the minimal `DatabaseSync` surface used by * {@link rerouteLegacyStarterBundlePaths}. Structurally compatible with * `node:sqlite`'s `DatabaseSync`; kept narrow here to avoid importing the * runtime type at module-load time. * * @task T1241 */ export interface RerouteLegacyDb { prepare(sql: string): { all(...params: unknown[]): unknown[]; run(...params: unknown[]): { changes?: number | bigint; }; }; } /** * Result envelope returned by {@link rerouteLegacyStarterBundlePaths}. * * @task T1241 */ export interface RerouteLegacyStarterBundleResult { /** Number of rows whose `cant_path` was rewritten. */ readonly rewrittenRows: number; /** * Agents that were rewritten. Each entry carries the agent id and the * old + new on-disk paths for audit. */ readonly agents: ReadonlyArray<{ readonly agentId: string; readonly oldPath: string; readonly newPath: string; }>; /** * Rows that matched the legacy substring but whose rewrite target could * not be resolved (e.g. `@cleocode/agents` unreachable from this process). * Callers should surface these as warnings. */ readonly skippedRows: ReadonlyArray<{ readonly agentId: string; readonly oldPath: string; readonly reason: string; }>; } /** * One-shot registry migration that reroutes `agents.cant_path` rows pointing * at the pre-v2026.4.111 `packages/cleo-os/starter-bundle/` location to the * new `packages/agents/starter-bundle/` location (per D035). * * Strategy: * * 1. Select every row whose `cant_path` contains the legacy substring * `cleo-os/starter-bundle`. * 2. For each row, locate the matching `.cant` file under the new * `packages/agents/starter-bundle/` tree by preserving the sub-path * suffix after `starter-bundle/`. * 3. Rewrite the row's `cant_path` via a `UPDATE … WHERE id = ?` * statement so the doctor walk sees the new canonical path. * * Safe to call repeatedly: after the first run no rows match the legacy * substring so subsequent calls are no-ops. * * Invoked by `cleo agent doctor` on first run after upgrade (wired through * the doctor entry point) so operators are not required to trigger it * manually. * * @param db - Open handle to the global `signaldock.db`. * @returns Summary of rewritten + skipped rows. * @task T1241 / D035 */ export declare function rerouteLegacyStarterBundlePaths(db: RerouteLegacyDb): RerouteLegacyStarterBundleResult; //# sourceMappingURL=seed-install.d.ts.map