/** * Extension model — manifest, extension lifecycle, and the Host API. * * An extension is a unit that contributes capabilities via the Host API. * The manifest declares what it provides and what it requires. The Host API * is the object an extension receives in `activate(host)` — it is the * §2.3 surface through which all registration and service access flows. */ import type { AuthContract } from "./auth.js"; import type { EventHandler, EventHookDescriptor, FilterDescriptor, FilterHandler, ServiceHandle, } from "./hooks.js"; import type { Logger } from "./logging.js"; export type { Logger } from "./logging.js"; import type { ProviderContract } from "./provider.js"; import type { ToolContract } from "./tool.js"; // --- Manifest --- /** Trust level of an extension, recorded for future policy differentiation. */ export type TrustLevel = "bundled" | "local" | "external"; /** * What an extension contributes to the system. Used by the host for * discovery, dependency resolution, and the capability gate. */ export interface ManifestContributions { readonly tools?: readonly string[]; readonly providers?: readonly string[]; readonly auth?: readonly string[]; readonly hooks?: readonly string[]; readonly routes?: readonly string[]; readonly commands?: readonly string[]; readonly services?: readonly string[]; readonly migrations?: readonly string[]; readonly scheduledJobs?: readonly string[]; readonly settings?: readonly string[]; } /** * Capabilities an extension declares it needs. The host uses these for the * capability gate — an extension can only access Host API surfaces it has * declared capability for. */ export interface ManifestCapabilities { readonly fs?: boolean; readonly shell?: boolean; readonly network?: boolean; readonly secrets?: boolean; readonly db?: boolean; readonly spawn?: boolean; } /** * An extension's declaration — what it is, what it provides, and what it * requires. The host uses this to resolve dependency DAG, check apiVersion * compatibility, and enforce the capability gate. */ export interface Manifest { /** Unique extension identifier (e.g. "tools-fs", "provider-anthropic"). */ readonly id: string; /** Human-readable display name. */ readonly name: string; /** Extension's own version (semver). */ readonly version: string; /** Semver range of kernel API versions this extension is compatible with. */ readonly apiVersion: string; /** Ids of extensions this one depends on (resolved topologically). */ readonly dependsOn?: readonly string[]; /** Activation strategy: "eager" (on boot) or lazy event triggers. */ readonly activation?: "eager" | string; /** What this extension contributes to the system. */ readonly contributes?: ManifestContributions; /** Capabilities this extension requires from the host. */ readonly capabilities?: ManifestCapabilities; /** Trust level — bundled (first-party), local (project), or external. */ readonly trust: TrustLevel; } // --- Storage interface --- /** * Namespaced storage interface exposed through the Host API. * The concrete backend (SQLite) is a core extension; the kernel defines * only the contract. Supports key-value and simple query operations. */ export interface StorageNamespace { readonly get: (key: string) => Promise; readonly set: (key: string, value: string) => Promise; readonly delete: (key: string) => Promise; readonly has: (key: string) => Promise; readonly keys: (prefix?: string) => Promise; } // --- Permission --- /** The outcome of a permission check. */ export interface PermissionDecision { readonly allowed: boolean; readonly reason?: string; } /** A request to check whether an action is permitted. */ export interface PermissionRequest { readonly tool: string; readonly action: string; readonly context?: Readonly>; } /** Permission gate exposed through the Host API. */ export interface PermissionGate { readonly check: (request: PermissionRequest) => Promise; } // --- Scheduler --- /** A scheduled job definition an extension can register with the host. */ export interface ScheduledJob { readonly id: string; readonly cron: string; readonly execute: () => void | Promise; } // --- Logger is re-exported from logging.ts (structured, correlated) --- // --- Config --- /** Read-only config access for an extension's own settings namespace. */ export interface ConfigAccess { readonly get: (key: string) => T | undefined; readonly getAll: () => Readonly>; } // --- Secrets --- /** Capability-gated access to the secret/credential vault. */ export interface SecretsAccess { readonly get: (key: string) => Promise; readonly set: (key: string, value: string) => Promise; readonly delete: (key: string) => Promise; } // --- Events emitter --- /** Outward event emitter available to extensions via the Host API. */ export interface EventsEmitter { readonly emit: (event: { readonly type: string; readonly [key: string]: unknown }) => void; } // --- Host API --- /** * The Host API — what every extension receives in `activate(host)`. * * This is the §2.3 surface: the single object through which an extension * registers tools, providers, auth, hooks, services, and accesses kernel * services (storage, config, secrets, permissions, logging, scheduling). * * Method signatures are typed contracts; implementations live in the host * module (not the kernel contracts). */ export interface HostAPI { /** Register a tool with the kernel's tool registry. */ readonly defineTool: (tool: ToolContract) => void; /** Register a provider with the kernel's provider registry. */ readonly defineProvider: (provider: ProviderContract) => void; /** Register an auth provider with the kernel's auth registry. */ readonly defineAuth: (auth: AuthContract) => void; /** Subscribe to an event hook. Handlers are error-isolated per call. */ readonly on: ( hook: EventHookDescriptor, handler: EventHandler, ) => () => void; /** * Emit an event hook: fire-and-forget dispatch to every `on` subscriber, * error-isolated per handler (a thrown handler is caught + logged, never * breaks the caller). The counterpart of `on`. * * This lets a core extension that OWNS a lifecycle publish typed events that * standard extensions react to — e.g. the session-orchestrator emitting * per-turn start/settle events a cache-warming extension subscribes to. The * kernel owns the mechanism; the owner declares the typed `EventHookDescriptor`. */ readonly emit: (hook: EventHookDescriptor, payload: TPayload) => void; /** Add a filter to a filter hook chain. Filters are awaited in-band. */ readonly addFilter: ( hook: FilterDescriptor, fn: FilterHandler, ) => () => void; /** * Run a filter chain: thread `value` through every filter registered for * `hook` in priority/registration order and return the final value. The * single-value-in/value-out counterpart to `addFilter`. Awaited in-band. * * Fail-open by default (a thrown filter is logged and the value passes * through unchanged); pass `{ failClosed: true }` to make a thrown filter * reject. With no registered filters the input value is returned as-is. * * This is what lets a core extension expose a contribution point (e.g. the * session-orchestrator running a per-turn tool/context-assembly chain) that * standard extensions plug into via `addFilter` — the kernel owns the * mechanism, the owner declares the typed `FilterDescriptor`. */ readonly applyFilters: ( hook: FilterDescriptor, value: TValue, opts?: { readonly failClosed?: boolean }, ) => Promise; /** Provide an implementation for a typed service handle. */ readonly provideService: (handle: ServiceHandle, impl: T) => void; /** Retrieve the implementation for a typed service handle. */ readonly getService: (handle: ServiceHandle) => T; /** Get a namespaced storage interface for this extension. */ readonly storage: (namespace: string) => StorageNamespace; /** Read-only access to merged config (global → project → extension). */ readonly config: ConfigAccess; /** Capability-gated access to the secret/credential vault. */ readonly secrets: SecretsAccess; /** Permission gate — check whether an action is allowed. */ readonly permissions: PermissionGate; /** Emit outward events (transport pushes these to clients). */ readonly events: EventsEmitter; /** Logger — always available, even before other extensions activate. */ readonly logger: Logger; /** Read-only view of all registered providers. */ readonly getProviders: () => ReadonlyMap; /** Read-only view of all registered tools. */ readonly getTools: () => ReadonlyMap; /** Read-only view of all registered auth providers. */ readonly getAuthProviders: () => ReadonlyMap; /** Look up a single auth provider by id. */ readonly getAuthProvider: (id: string) => AuthContract | undefined; /** Read-only view of all activated extensions' manifests (what is loaded). */ readonly getExtensions: () => readonly Manifest[]; /** Register a scheduled job with the host's scheduler. */ readonly scheduler: { readonly register: (job: ScheduledJob) => void; }; } // --- Extension lifecycle --- /** * An extension — the unit that contributes capabilities to Dispatch. * `activate` is called by the host during boot (or lazy activation); * `deactivate` is optional and called on shutdown or reload. */ export interface Extension { /** The extension's manifest — its declaration of identity and capabilities. */ readonly manifest: Manifest; /** * Called by the host to activate the extension. The extension registers * its contributions (tools, providers, hooks, services) through the Host API. */ readonly activate: (host: HostAPI) => void | Promise; /** * Optional cleanup called when the extension is deactivated (shutdown, * reload, or auto-disable). Should dispose resources the extension owns. */ readonly deactivate?: () => void | Promise; }