summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/src/contracts/extension.ts
blob: 4d6cf078ff2149192cf1cfa42aca11b50b9804fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/**
 * 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<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 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: <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;

	/**
	 * 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: <TPayload>(hook: EventHookDescriptor<TPayload>, payload: 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;

	/**
	 * 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: <TValue>(
		hook: FilterDescriptor<TValue>,
		value: TValue,
		opts?: { readonly failClosed?: boolean },
	) => Promise<TValue>;

	/** 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;

	/** Read-only view of all registered providers. */
	readonly getProviders: () => ReadonlyMap<string, ProviderContract>;

	/** Read-only view of all registered tools. */
	readonly getTools: () => ReadonlyMap<string, ToolContract>;

	/** Read-only view of all registered auth providers. */
	readonly getAuthProviders: () => ReadonlyMap<string, AuthContract>;

	/** 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<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>;
}