diff options
| author | Adam Malczewski <[email protected]> | 2026-06-04 22:26:46 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-04 22:26:46 +0900 |
| commit | fd855ffb335e72a94b6f992ede5a859237460a8b (patch) | |
| tree | 3833674b0957ddec1ed3b3e6140c89360e675037 /packages/kernel/src/contracts/extension.ts | |
| parent | a6119e0434597399c773da6f0b31363003f6aa09 (diff) | |
| download | dispatch-fd855ffb335e72a94b6f992ede5a859237460a8b.tar.gz dispatch-fd855ffb335e72a94b6f992ede5a859237460a8b.zip | |
feat(kernel): define the ABI contracts (conversation, tool, provider, auth, dispatch, hooks, extension/HostAPI, runtime, events)
Diffstat (limited to 'packages/kernel/src/contracts/extension.ts')
| -rw-r--r-- | packages/kernel/src/contracts/extension.ts | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/packages/kernel/src/contracts/extension.ts b/packages/kernel/src/contracts/extension.ts new file mode 100644 index 0000000..ac252b4 --- /dev/null +++ b/packages/kernel/src/contracts/extension.ts @@ -0,0 +1,255 @@ +/** + * 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 { 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<string | null>; + readonly set: (key: string, value: string) => Promise<void>; + readonly delete: (key: string) => Promise<void>; + readonly has: (key: string) => Promise<boolean>; + readonly keys: (prefix?: string) => Promise<readonly string[]>; +} + +// --- 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<Record<string, unknown>>; +} + +/** Permission gate exposed through the Host API. */ +export interface PermissionGate { + readonly check: (request: PermissionRequest) => Promise<PermissionDecision>; +} + +// --- 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<void>; +} + +// --- Logger --- + +/** Logger interface available to every extension via the Host API. */ +export interface Logger { + readonly debug: (message: string, ...args: unknown[]) => void; + readonly info: (message: string, ...args: unknown[]) => void; + readonly warn: (message: string, ...args: unknown[]) => void; + readonly error: (message: string, ...args: unknown[]) => void; +} + +// --- Config --- + +/** Read-only config access for an extension's own settings namespace. */ +export interface ConfigAccess { + readonly get: <T = unknown>(key: string) => T | undefined; + readonly getAll: () => Readonly<Record<string, unknown>>; +} + +// --- Secrets --- + +/** Capability-gated access to the secret/credential vault. */ +export interface SecretsAccess { + readonly get: (key: string) => Promise<string | null>; + readonly set: (key: string, value: string) => Promise<void>; + readonly delete: (key: string) => Promise<void>; +} + +// --- 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: <TPayload>( + hook: EventHookDescriptor<TPayload>, + handler: EventHandler<TPayload>, + ) => () => void; + + /** Add a filter to a filter hook chain. Filters are awaited in-band. */ + readonly addFilter: <TValue>( + hook: FilterDescriptor<TValue>, + fn: FilterHandler<TValue>, + ) => () => void; + + /** Provide an implementation for a typed service handle. */ + readonly provideService: <T>(handle: ServiceHandle<T>, impl: T) => void; + + /** Retrieve the implementation for a typed service handle. */ + readonly getService: <T>(handle: ServiceHandle<T>) => 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; + + /** 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<void>; + + /** + * Optional cleanup called when the extension is deactivated (shutdown, + * reload, or auto-disable). Should dispose resources the extension owns. + */ + readonly deactivate?: () => void | Promise<void>; +} |
