/** * Agent Registry Accessor — cross-DB CRUD for agent data. * * Post-T11622 cutover (folds T11578 AC2), agent identity lives in the GLOBAL * PREFIXED `agent_registry_agents` table (+ `agent_registry_capabilities` / * `agent_registry_skills` / `agent_registry_agent_capabilities` / * `agent_registry_agent_skills`); per-project visibility and overrides live in the * PROJECT `conduit_project_agent_refs` table (T11578 · AC4 — conduit namespace * cutover). The bare legacy `agents` shape is no longer read or written. * * Post-E6-L5 (T11525) / E6-L6 (T11526), both tables are hosted inside the * CONSOLIDATED dual-scope `cleo.db` files (global `$XDG_DATA_HOME/cleo/cleo.db` * and project `.cleo/cleo.db`) — the standalone `signaldock.db` / `conduit.db` * files are gone. The global `agent_registry_*` tables are owned by the * consolidated cleo-global migration (T11622 rename of `signaldock_*`); * `ensureGlobalAgentRegistryDb()` opens the shared consolidated handle. The * prefixed conduit `conduit_project_agent_refs` table is created by the * consolidated cleo-project migration (T11578 · AC4) and `ensureConduitDb()` * ensures the project `cleo.db` is open. The read path MUST therefore go through * those `ensure*` calls so it shares the same handle as the write path (T11562 — * agents read/write path divergence regression). * * The consolidated `agent_registry_agents` table carries TEXT ISO-8601 timestamps * (`created_at` / `updated_at` / `last_used_at`, each with a GLOB CHECK) — every * write path below stamps ISO strings, not the legacy epoch integers (T11622 * write-path constraint reconciliation). * * This module provides three module-level functions that perform the in-memory * cross-DB join, plus the backward-compatible `AgentRegistryAccessor` class that * wraps them. * * Architecture: * global cleo.db — canonical identity `agent_registry_agents` (openGlobalDb → ensureGlobalAgentRegistryDb) * project cleo.db — `conduit_project_agent_refs` (openConduitDb) * Join performed in Node (SQLite cannot cross-file-handle JOIN). * * @see .cleo/rcasd/T310/specification/T310-specification.md §3.5 * @see .cleo/adrs/ADR-037-conduit-signaldock-separation.md * @task T355 * @task T11562 * @task T11622 * @epic T310 * @epic T11249 */ import type { AgentCredential, AgentListFilter, AgentRegistryAPI, AgentWithProjectOverride, ProjectAgentRef, ResolvedAgent } from '@cleocode/contracts'; /** * Raw row shape from global cleo.db:agent_registry_agents. * * The T897 v3 columns (tier, can_spawn, orch_level, reports_to, cant_path, * cant_sha256, installed_from, installed_at) are typed as OPTIONAL here so * this interface can accept rows from databases where the v3 migration has * not yet been applied (freshly-cloned projects, pre-upgrade snapshots). * * Callers that require v3 fields (e.g. `rowToResolvedAgent`) MUST check * migration state or narrow the row through `hasV3Fields()` first. * * @task T897 * @epic T889 */ interface AgentDbRow { id: string; agent_id: string; name: string; description: string | null; class: string; privacy_tier: string; capabilities: string; skills: string; transport_type: string; api_key_encrypted: string | null; api_base_url: string; classification: string | null; transport_config: string; is_active: number; last_used_at: string | null; created_at: string; updated_at: string; tier?: string; can_spawn?: number; orch_level?: number; reports_to?: string | null; cant_path?: string | null; cant_sha256?: string | null; installed_from?: string | null; installed_at?: string | null; } /** * Build a `ResolvedAgent` envelope from a global agents row. This is * scaffolding consumed by future `resolveAgent()` / `installAgentFromCant()` * work (W2-3, W2-4). It does NOT perform tier-merging, alias lookup, or * skill unioning — it simply projects a single row into the resolved shape. * * Returns `null` when the row lacks the hard-required `cant_path` / * `cant_sha256` pair (a row from a pre-T897 DB, or an agents row that has * never been bound to a `.cant`). Callers that need a synthesized fallback * envelope MUST handle the null return explicitly. * * @param row - Raw agent_registry_agents row from the global cleo.db * @returns A ResolvedAgent envelope, or null when provenance is missing * @task T897 * @epic T889 */ export declare function rowToResolvedAgent(row: AgentDbRow): ResolvedAgent | null; /** * Cross-DB agent lookup. Opens both the global cleo.db (Agent Registry) and the * current project's conduit.db, joins project_agent_refs ⨝ agents by * agentId, and returns the merged view. * * Default (includeGlobal=false): returns null if no project_agent_refs row * exists, even if the agent exists globally. An enabled=0 row is also treated * as absent. * * includeGlobal=true: returns the global agent with `projectRef: null` if no * project attachment row exists. * * Dangling soft-FK detection: if a project_agent_refs row exists but the * referenced global agent does not, logs a WARN and returns null. * * Both `agents` (global) and `project_agent_refs` (project) tables are * materialized into their consolidated `cleo.db` files lazily, so this function * ensures both DBs (via the shared `openGlobalDb` / `ensureConduitDb` * chokepoints) before reading — keeping the read path aligned with the write * path (T11562). It is therefore async. * * @param projectRoot - Absolute path to the project root directory. * @param agentId - Agent business identifier. * @param opts.includeGlobal - When true, returns global identity even without project ref. * @returns Merged agent record or null if not found. * * @task T355 * @task T11562 * @epic T310 * @epic T11249 */ export declare function lookupAgent(projectRoot: string, agentId: string, opts?: { includeGlobal?: boolean; }): Promise; /** * Lists agents visible in the current project. * * Default (includeGlobal=false): INNER JOIN on project_agent_refs (enabled=1) * — only agents explicitly attached to this project are returned. * * includeGlobal=true: returns all global agents regardless of project * attachment, with projectRef populated for attached ones and null for the rest. * * includeDisabled=true: also returns agents with enabled=0 in project_agent_refs. * Ignored when includeGlobal=true (all global agents are returned regardless). * * Both `agents` (global) and `project_agent_refs` (project) tables are * materialized into their consolidated `cleo.db` files lazily, so this function * ensures both DBs (via the shared `openGlobalDb` / `ensureConduitDb` * chokepoints) before reading — keeping the read path aligned with the write * path (T11562). It is therefore async. * * @param projectRoot - Absolute path to the project root directory. * @param opts.includeGlobal - Include all global agents (bypasses project filter). * @param opts.includeDisabled - Include agents with enabled=0 in project_agent_refs. * @returns Array of merged agent records. * * @task T355 * @task T11562 * @epic T310 * @epic T11249 */ export declare function listAgentsForProject(projectRoot: string, opts?: { includeGlobal?: boolean; includeDisabled?: boolean; }): Promise; /** * Creates a new agent: writes identity row to global cleo.db (Agent Registry) AND attaches * it to the current project via conduit.db:project_agent_refs. * * Write order: global first, then project ref. If the project ref write fails, * the global row remains (recoverable via `cleo agent attach `). * * API key derivation: HMAC-SHA256(machineKey || globalSalt, agentId) per ADR-037 §5. * * @param projectRoot - Absolute path to the project root directory. * @param spec - Agent creation spec (without createdAt/updatedAt). * @returns Merged agent record including the new project ref. * * @task T355 * @epic T310 */ export declare function createProjectAgent(projectRoot: string, spec: Omit): Promise; /** * Attach a globally-registered agent to the current project. * * Creates a `project_agent_refs` row with `enabled=1`. If a row already exists * with `enabled=0`, it is re-enabled (idempotent). If the row already has * `enabled=1`, this is a no-op. * * The agent MUST already exist in the global `cleo.db:agent_registry_agents` table. * This function does NOT validate global existence — callers must check via * `lookupAgent(..., { includeGlobal: true })` first. * * @param projectRoot - Absolute path to the project root directory. * @param agentId - Agent business identifier. * @param opts.role - Optional per-project role override (nullable). * @param opts.capabilitiesOverride - Optional JSON blob of capability overrides (nullable). * * @task T364 * @epic T310 */ export declare function attachAgentToProject(projectRoot: string, agentId: string, opts?: { role?: string | null; capabilitiesOverride?: string | null; }): void; /** * Detach an agent from the current project by setting `project_agent_refs.enabled=0`. * * This is a soft-delete: the global `cleo.db:agent_registry_agents` row is preserved. * The agent can be re-attached later via `attachAgentToProject`. * * Returns `false` if no row exists in `project_agent_refs` for the given agentId * (agent was never attached or was already fully removed). * * @param projectRoot - Absolute path to the project root directory. * @param agentId - Agent business identifier. * @returns `true` if a row was updated; `false` if no row existed. * * @task T364 * @epic T310 */ export declare function detachAgentFromProject(projectRoot: string, agentId: string): boolean; /** * Get the raw `project_agent_refs` row for a given agentId in this project. * * Returns `null` if no row exists (agent not attached to this project). * * @param projectRoot - Absolute path to the project root directory. * @param agentId - Agent business identifier. * @returns Typed `ProjectAgentRef` object or `null`. * * @task T364 * @epic T310 */ export declare function getProjectAgentRef(projectRoot: string, agentId: string): ProjectAgentRef | null; /** * AgentRegistryAccessor — backward-compatible CRUD wrapper around the * cross-DB module-level functions. * * Post-T310 (ADR-037), the constructor accepts the project root (same * semantics as `projectPath` in the pre-T310 version). All operations are * routed through the cross-DB functions above. * * @task T355 * @epic T310 */ export declare class AgentRegistryAccessor implements AgentRegistryAPI { private readonly projectPath; /** * @param projectPath - Absolute path to the project root directory. * Used as the `projectRoot` argument for all cross-DB operations. * @task T355 * @epic T310 */ constructor(projectPath: string); /** * Ensure both databases exist with their full schemas before any operation. * * @task T355 * @epic T310 */ private ensureDbs; /** * Register (create or update) an agent in global cleo.db (Agent Registry) and attach * it to the current project via conduit.db:project_agent_refs. * * @param credential - Agent spec (without createdAt/updatedAt). * @returns The registered agent credential. * @task T355 * @epic T310 */ register(credential: Omit): Promise; /** * Get agent by agentId. Project-scoped by default (INNER JOIN). * * @param agentId - Agent business identifier. * @param opts.includeGlobal - When true, returns global identity even without project ref. * @returns The agent credential, or null if not found. * @task T355 * @epic T310 */ get(agentId: string, opts?: { includeGlobal?: boolean; }): Promise; /** * Lists project-scoped agents (INNER JOIN on project_agent_refs with enabled=1). * * @param filter - Optional filter (active field maps to is_active in global agents). * @returns Array of agent credentials visible in this project. * @task T355 * @epic T310 */ list(filter?: AgentListFilter): Promise; /** * Lists all global agents (no project filter). Exposed for `--global` CLI flag. * * @param filter - Optional filter (active field maps to is_active in global agents). * @returns Array of all globally registered agent credentials. * @task T355 * @epic T310 */ listGlobal(filter?: AgentListFilter): Promise; /** * Update agent identity fields in global cleo.db (Agent Registry). * Project-specific fields (role, capabilitiesOverride) require direct * conduit.db manipulation (not yet exposed by this method). * * @param agentId - Agent business identifier. * @param updates - Partial set of fields to update. * @returns The updated agent credential (project-scoped lookup). * @task T355 * @epic T310 */ update(agentId: string, updates: Partial>): Promise; /** * Remove agent from current project (sets project_agent_refs.enabled=0). * Does NOT delete from global cleo.db (Agent Registry) (per ADR-037 §6 / Q4=C). * * @param agentId - Agent business identifier. * @task T355 * @epic T310 */ remove(agentId: string): Promise; /** * Remove agent from global cleo.db (Agent Registry). * Requires explicit opt-in. Warns if cross-project refs may exist. * * @param agentId - Agent business identifier. * @param opts.force - Skip the global-delete warning when refs exist. * @task T355 * @epic T310 */ removeGlobal(agentId: string, opts?: { force?: boolean; }): Promise; /** * Rotate API key via cloud endpoint and re-encrypt with the new T310 KDF * in global cleo.db (Agent Registry). * * @param agentId - Agent business identifier. * @returns Object with agentId and a redacted new API key string. * @task T355 * @epic T310 */ rotateKey(agentId: string): Promise<{ agentId: string; newApiKey: string; }>; /** * Get the most recently used active agent in the current project. * * @returns The most-recently-used active agent, or null if none found. * @task T355 * @epic T310 */ getActive(): Promise; /** * Update last_used_at in both global cleo.db:agent_registry_agents and * conduit.db:project_agent_refs. * * @param agentId - Agent business identifier. * @task T355 * @epic T310 */ markUsed(agentId: string): Promise; } export {}; //# sourceMappingURL=agent-registry-accessor.d.ts.map