@vonzio/plugin-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +5 -0
- package/capabilities.d.ts +69 -0
- package/capabilities.js +286 -0
- package/errors.d.ts +72 -0
- package/errors.js +106 -0
- package/frontend.d.ts +13 -0
- package/frontend.js +16 -0
- package/index.d.ts +945 -0
- package/index.js +24 -0
- package/manifest-validate.d.ts +44 -0
- package/manifest-validate.js +338 -0
- package/manifest.d.ts +121 -0
- package/manifest.js +37 -0
- package/package.json +49 -0
- package/policy.d.ts +98 -0
- package/policy.js +223 -0
- package/version.d.ts +16 -0
- package/version.js +54 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
/**
|
|
3
|
+
* Current plugin-api version. Plugins encode the version they were built
|
|
4
|
+
* against in their package.json `vonzio.apiVersion`; the loader rejects
|
|
5
|
+
* plugins whose major differs or whose minor is ahead of core's (see
|
|
6
|
+
* `assertApiCompatible`). Bumped to 1.0.0 with the external-loader contract
|
|
7
|
+
* (docs/PLUGIN_LOADER_SPEC.md) — the loader surface is now a stability
|
|
8
|
+
* commitment.
|
|
9
|
+
*/
|
|
10
|
+
export declare const PLUGIN_API_VERSION = "1.0.0";
|
|
11
|
+
/**
|
|
12
|
+
* The shape every plugin's default export must satisfy. Generic over
|
|
13
|
+
* the plugin's own config type so init() receives a fully-typed config.
|
|
14
|
+
*/
|
|
15
|
+
export interface VonzioPlugin<TConfig = unknown> {
|
|
16
|
+
/**
|
|
17
|
+
* Stable identifier. Used for the auto-route prefix, log scope, and
|
|
18
|
+
* the migration namespace in the `_migrations` table. Conventionally
|
|
19
|
+
* matches the npm package's unscoped name (e.g. `telegram` for
|
|
20
|
+
* `@vonzio/plugin-telegram`).
|
|
21
|
+
*/
|
|
22
|
+
name: string;
|
|
23
|
+
/**
|
|
24
|
+
* Semver of `@vonzio/plugin-api` this plugin was built against. The
|
|
25
|
+
* loader compares the major against core's PLUGIN_API_VERSION and
|
|
26
|
+
* refuses to load plugins targeting a newer major.
|
|
27
|
+
*/
|
|
28
|
+
apiVersion: string;
|
|
29
|
+
/**
|
|
30
|
+
* Zod schema for the plugin's env-derived config. The loader calls
|
|
31
|
+
* `.parse(process.env)` on it and rejects malformed values with a
|
|
32
|
+
* useful error. Plugins should namespace their env vars
|
|
33
|
+
* (`SLACK_CLIENT_ID`, `TELLER_API_KEY`, etc.) to avoid collisions.
|
|
34
|
+
*
|
|
35
|
+
* Typed as `ConfigSchemaLike` (a structural shape with just
|
|
36
|
+
* `.parse()`) rather than `z.ZodType<TConfig>` for two reasons:
|
|
37
|
+
* 1. The workspace can have zod v3 and v4 simultaneously (different
|
|
38
|
+
* deps pin different majors); using zod's strong types here
|
|
39
|
+
* would force every plugin's schema to be the same major
|
|
40
|
+
* instance as plugin-api's.
|
|
41
|
+
* 2. ZodObject is technically a ZodType subtype, but TS variance
|
|
42
|
+
* rules reject ZodObject<T> as a ZodType<T> in many cases.
|
|
43
|
+
* The plugin's TConfig flows from `.parse()`'s return value at the
|
|
44
|
+
* use site, so typing stays useful where it matters.
|
|
45
|
+
*/
|
|
46
|
+
configSchema: ConfigSchemaLike<TConfig>;
|
|
47
|
+
/**
|
|
48
|
+
* SQL migrations owned by this plugin. The core migration runner
|
|
49
|
+
* applies them in order interleaved with core's own, tagged in
|
|
50
|
+
* `_migrations` as `<plugin-name>_<migration-name>`. Plugins should
|
|
51
|
+
* keep migrations idempotent (CREATE TABLE IF NOT EXISTS, etc.) so
|
|
52
|
+
* partial-apply failures don't poison subsequent boots.
|
|
53
|
+
*/
|
|
54
|
+
migrations?: PluginMigration[];
|
|
55
|
+
/**
|
|
56
|
+
* Where this plugin's routes live in the URL space. Default
|
|
57
|
+
* (`{ kind: "auto" }`) mounts everything under `/plugins/<name>/*`.
|
|
58
|
+
* Plugins with externally-registered URLs (Slack OAuth callback,
|
|
59
|
+
* Telegram webhook secret in the Telegram app config, etc.) can
|
|
60
|
+
* use `{ kind: "absolute", prefix }` to keep their legacy URLs and
|
|
61
|
+
* avoid forcing every self-hoster to update their third-party app
|
|
62
|
+
* configuration.
|
|
63
|
+
*/
|
|
64
|
+
routePrefix?: PluginRoutePrefix;
|
|
65
|
+
/**
|
|
66
|
+
* Called once at server boot, after config parsing and after this
|
|
67
|
+
* plugin's migrations have been applied. The plugin registers its
|
|
68
|
+
* handlers via `ctx.server`, `ctx.notificationBus`,
|
|
69
|
+
* `ctx.mcpRegistry`, and `ctx.scheduler`. init() must not block on
|
|
70
|
+
* external services (e.g. an API call) -- those belong in scheduled
|
|
71
|
+
* jobs or lazy on-first-request initialization, so a flaky upstream
|
|
72
|
+
* doesn't block server startup.
|
|
73
|
+
*/
|
|
74
|
+
init: (ctx: PluginContext<TConfig>) => Promise<void> | void;
|
|
75
|
+
/**
|
|
76
|
+
* Called on graceful server shutdown. Plugins MUST clear timers,
|
|
77
|
+
* cancel intervals, close sockets, and stop any worker threads they
|
|
78
|
+
* spawned. The scheduler-registered jobs are cancelled by core
|
|
79
|
+
* automatically, but anything the plugin spawned outside that
|
|
80
|
+
* channel is its own responsibility.
|
|
81
|
+
*/
|
|
82
|
+
teardown?: () => Promise<void> | void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Where the plugin's routes live. `auto` mounts under
|
|
86
|
+
* `/plugins/<plugin-name>/*` which is the recommended default --
|
|
87
|
+
* keeps URLs collision-free and self-documenting. `absolute` is the
|
|
88
|
+
* escape hatch for plugins that need a stable legacy URL (e.g. Slack
|
|
89
|
+
* OAuth callback is registered in the Slack app config; changing it
|
|
90
|
+
* would force every self-hoster to update their Slack app).
|
|
91
|
+
*/
|
|
92
|
+
export type PluginRoutePrefix = {
|
|
93
|
+
kind: "auto";
|
|
94
|
+
} | {
|
|
95
|
+
kind: "absolute";
|
|
96
|
+
prefix: string;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* One SQL migration. The core migration runner applies these in the
|
|
100
|
+
* order the plugin lists them, and records `<plugin-name>_<name>` in
|
|
101
|
+
* the `_migrations` table so a partial apply can resume cleanly.
|
|
102
|
+
*/
|
|
103
|
+
export interface PluginMigration {
|
|
104
|
+
/**
|
|
105
|
+
* Migration name. Conventionally `NNNN_short_description.sql`-style
|
|
106
|
+
* (e.g. `0001_initial_schema`, `0002_add_thread_label`).
|
|
107
|
+
* Combined with the plugin name to form the key core stores.
|
|
108
|
+
*/
|
|
109
|
+
name: string;
|
|
110
|
+
/** Idempotent SQL. Plugins should use CREATE ... IF NOT EXISTS etc. */
|
|
111
|
+
up: string;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What init() receives. Everything a plugin needs to integrate with
|
|
115
|
+
* core lives here -- plugins should never `import` from
|
|
116
|
+
* `@vonzio/core-server` directly.
|
|
117
|
+
*/
|
|
118
|
+
export interface PluginContext<TConfig = unknown> {
|
|
119
|
+
/**
|
|
120
|
+
* Fastify scope. Routes registered here are auto-prefixed if the
|
|
121
|
+
* plugin uses the default `routePrefix`. The plugin still owns the
|
|
122
|
+
* relative URL space inside its prefix.
|
|
123
|
+
*/
|
|
124
|
+
server: FastifyInstance;
|
|
125
|
+
/** The result of `configSchema.parse(process.env)`. */
|
|
126
|
+
config: TConfig;
|
|
127
|
+
/** Logger pre-tagged with `{ plugin: name }`. */
|
|
128
|
+
log: PluginLogger;
|
|
129
|
+
/**
|
|
130
|
+
* Versioned access to core services. At runtime this is a capability
|
|
131
|
+
* MEMBRANE (a revocable Proxy) — accessing a `core` surface the plugin
|
|
132
|
+
* did not declare + get granted throws `CapabilityViolationError` and is
|
|
133
|
+
* audited. The membrane is hygiene against honest mistakes via THIS
|
|
134
|
+
* reference; it is not a sandbox against `require('@vonzio/core-server')`.
|
|
135
|
+
* See docs/PLUGIN_LOADER_SPEC.md §2, §7.
|
|
136
|
+
*/
|
|
137
|
+
core: PluginCore;
|
|
138
|
+
/**
|
|
139
|
+
* Per-plugin namespaced key/value store. Present only when the plugin
|
|
140
|
+
* declared `storage.kv` and the operator granted it; otherwise accessing
|
|
141
|
+
* it throws `CapabilityViolationError`. Preferred over `db.*` for new
|
|
142
|
+
* plugins (§5).
|
|
143
|
+
*/
|
|
144
|
+
storage: PluginStorageKv;
|
|
145
|
+
/**
|
|
146
|
+
* Audited outbound HTTP. Present only when the plugin declared
|
|
147
|
+
* `http.outbound` (with a non-empty `outboundHosts`) and the operator
|
|
148
|
+
* granted it. Every call is SSRF-checked, allowlist-checked against
|
|
149
|
+
* manifest∩policy hosts, and logged. See §10.
|
|
150
|
+
*/
|
|
151
|
+
http: PluginHttp;
|
|
152
|
+
/** Where the plugin claims a notification channel kind. */
|
|
153
|
+
notificationBus: NotificationBus;
|
|
154
|
+
/** Where the plugin contributes an MCP server. */
|
|
155
|
+
mcpRegistry: McpRegistry;
|
|
156
|
+
/**
|
|
157
|
+
* Resolve the per-task bearer token core attaches when it injects this
|
|
158
|
+
* plugin's MCP server into an agent container. Present only when the plugin
|
|
159
|
+
* declared `mcp.register` and the operator granted it; otherwise accessing it
|
|
160
|
+
* throws `CapabilityViolationError`. See §10.
|
|
161
|
+
*/
|
|
162
|
+
mcpSessions: McpSessions;
|
|
163
|
+
/** Where the plugin schedules background work. */
|
|
164
|
+
scheduler: Scheduler;
|
|
165
|
+
/**
|
|
166
|
+
* Subscribe to session-lifecycle events emitted by the orchestrator.
|
|
167
|
+
* Used by integrations that relay task progress to external
|
|
168
|
+
* surfaces (e.g. Telegram chat, Slack thread).
|
|
169
|
+
*/
|
|
170
|
+
sessionEvents: SessionEvents;
|
|
171
|
+
/**
|
|
172
|
+
* Resolve operator-provisioned secret material into opaque references.
|
|
173
|
+
* Present only when the plugin declared `secrets.mtls` (with a non-empty
|
|
174
|
+
* `manifest.mtlsSecrets`) and the operator both granted it and provisioned
|
|
175
|
+
* the cert/key files in policy; otherwise accessing it throws
|
|
176
|
+
* `CapabilityViolationError`. v1 covers mTLS client certs only. See §5, §10.
|
|
177
|
+
*/
|
|
178
|
+
secrets: PluginSecrets;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* One user's integration row, as seen by a plugin. Used by
|
|
182
|
+
* notification handlers to resolve `req.recipient` (an integration
|
|
183
|
+
* id) into the bot token / channel / chat id / etc. needed to send a
|
|
184
|
+
* message.
|
|
185
|
+
*
|
|
186
|
+
* `config` is type-erased on this contract -- the actual shape is
|
|
187
|
+
* provider-specific (Telegram: bot_token + owner_tg_user_id;
|
|
188
|
+
* Slack: bot_token + authed_user_id; etc.) and is the plugin's
|
|
189
|
+
* responsibility to assert. With `opts.decrypt: true` the loader
|
|
190
|
+
* runs the standard decrypt pass against config before returning.
|
|
191
|
+
*/
|
|
192
|
+
export interface PluginIntegration {
|
|
193
|
+
id: string;
|
|
194
|
+
user_id: string;
|
|
195
|
+
type: string;
|
|
196
|
+
config: Record<string, unknown>;
|
|
197
|
+
enabled: boolean;
|
|
198
|
+
/**
|
|
199
|
+
* Last-modified timestamp (ISO-8601). Plugins use this for
|
|
200
|
+
* optimistic-locking writes -- see `update({...}, { expectUpdatedAt })`.
|
|
201
|
+
*/
|
|
202
|
+
updated_at: string;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Adapter around core's IntegrationService. Plugins use this to look
|
|
206
|
+
* up + manage user-integration rows (Slack tokens, Telegram bots,
|
|
207
|
+
* Gmail OAuth grants, etc.). All eight methods mirror core's
|
|
208
|
+
* IntegrationService surface 1:1 -- the structural shape lets the
|
|
209
|
+
* loader pass the real service through without leaking the concrete
|
|
210
|
+
* class type into plugin-api.
|
|
211
|
+
*/
|
|
212
|
+
export interface PluginIntegrationLookup {
|
|
213
|
+
get(id: string, opts?: {
|
|
214
|
+
decrypt?: boolean;
|
|
215
|
+
}): Promise<PluginIntegration | null>;
|
|
216
|
+
getByUserAndType(userId: string, type: string, opts?: {
|
|
217
|
+
decrypt?: boolean;
|
|
218
|
+
}): Promise<PluginIntegration | null>;
|
|
219
|
+
listByType(type: string, opts?: {
|
|
220
|
+
decrypt?: boolean;
|
|
221
|
+
}): Promise<PluginIntegration[]>;
|
|
222
|
+
listByUserAndType(userId: string, type: string, opts?: {
|
|
223
|
+
decrypt?: boolean;
|
|
224
|
+
}): Promise<PluginIntegration[]>;
|
|
225
|
+
findByTypeAndExternalId(type: string, externalId: string, opts?: {
|
|
226
|
+
decrypt?: boolean;
|
|
227
|
+
}): Promise<PluginIntegration | null>;
|
|
228
|
+
/**
|
|
229
|
+
* Multi-result variant for the case where a single external id maps
|
|
230
|
+
* to multiple integrations -- e.g. the platform Telegram bot has
|
|
231
|
+
* one bot_user_id but many user-integration rows (one per paired
|
|
232
|
+
* user). The relay routes by `(user_id, external_id)` to disambiguate.
|
|
233
|
+
*/
|
|
234
|
+
listByTypeAndExternalId(type: string, externalId: string, opts?: {
|
|
235
|
+
decrypt?: boolean;
|
|
236
|
+
}): Promise<PluginIntegration[]>;
|
|
237
|
+
/**
|
|
238
|
+
* Lazy backfill of the indexed `external_id` column for legacy rows
|
|
239
|
+
* that pre-date the column. The plugin calls this on first read of
|
|
240
|
+
* a row that's missing the index -- avoids a one-time migration
|
|
241
|
+
* that would need decryption inside the migration runner.
|
|
242
|
+
*/
|
|
243
|
+
backfillExternalId(id: string): Promise<void>;
|
|
244
|
+
create(userId: string, type: string, config: Record<string, unknown>, scopeInput?: unknown): Promise<PluginIntegration>;
|
|
245
|
+
/**
|
|
246
|
+
* Update an integration row. `opts.expectUpdatedAt` gates the write
|
|
247
|
+
* on the matching `updated_at` value -- used by the chat-surface
|
|
248
|
+
* pairing flow to make /link claims race-safe (the loser sees a
|
|
249
|
+
* null return and refuses without echoing "Linked.").
|
|
250
|
+
*/
|
|
251
|
+
update(id: string, input: Partial<{
|
|
252
|
+
config: Record<string, unknown>;
|
|
253
|
+
enabled: boolean;
|
|
254
|
+
scope: string;
|
|
255
|
+
profile_ids: string[];
|
|
256
|
+
}>, opts?: {
|
|
257
|
+
expectUpdatedAt?: string;
|
|
258
|
+
}): Promise<PluginIntegration | null>;
|
|
259
|
+
delete(id: string): Promise<void>;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Narrow read-only profile lookup. Plugins use this when validating
|
|
263
|
+
* that a user-supplied profile_id (e.g. for binding a Telegram bot to
|
|
264
|
+
* a specific agent profile) actually belongs to the caller.
|
|
265
|
+
*
|
|
266
|
+
* Use `profileResolver.getResolved` (separate field on PluginCore) for
|
|
267
|
+
* the full ResolvedProfile shape with credentials, tools, claude_md,
|
|
268
|
+
* etc. This narrow surface keeps plugins that only need slug/name
|
|
269
|
+
* from pulling in that wider type tree.
|
|
270
|
+
*/
|
|
271
|
+
export interface PluginProfileLookup {
|
|
272
|
+
/**
|
|
273
|
+
* Returns the full Profile shape (slug, name, model, default_tools,
|
|
274
|
+
* persistent_sessions, bound_profile_id, ...). Chat-surface pickers
|
|
275
|
+
* need most of these fields; mirroring a narrow subset structurally
|
|
276
|
+
* would silently drift as features land.
|
|
277
|
+
*/
|
|
278
|
+
list(userId: string): Promise<Array<import("@vonzio/shared").Profile>>;
|
|
279
|
+
/**
|
|
280
|
+
* Single-row fetch by id. Same full Profile shape as `list`. Use
|
|
281
|
+
* `profileResolver.getResolved` when you need the resolved variant
|
|
282
|
+
* (credentials, env, setup_commands, ...).
|
|
283
|
+
*/
|
|
284
|
+
get(profileId: string): Promise<import("@vonzio/shared").Profile | null>;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Workspace lookup + lightweight mutations. Plugins use this for:
|
|
288
|
+
* - ownership checks before exposing a workspace-bound resource
|
|
289
|
+
* (`get`)
|
|
290
|
+
* - chat-side `/list` commands that show the user their recent
|
|
291
|
+
* workspaces (`list`)
|
|
292
|
+
* - chat-side `/model` overrides and "set workspace title from first
|
|
293
|
+
* line of chat" updates (`update`)
|
|
294
|
+
*/
|
|
295
|
+
export interface PluginWorkspaceLookup {
|
|
296
|
+
/**
|
|
297
|
+
* Returns the full Workspace shape from @vonzio/shared (session_id,
|
|
298
|
+
* user_id, profile_id, container_id, name, status, model_override,
|
|
299
|
+
* tags, ...). Chat surfaces use most of these for cross-resume,
|
|
300
|
+
* model display, and title updates.
|
|
301
|
+
*/
|
|
302
|
+
get(sessionId: string): import("@vonzio/shared").Workspace | null;
|
|
303
|
+
list(filters: {
|
|
304
|
+
userId?: string;
|
|
305
|
+
orgId?: string;
|
|
306
|
+
status?: "active" | "resumable" | "idle" | "expired";
|
|
307
|
+
includeArchived?: boolean;
|
|
308
|
+
starredOnly?: boolean;
|
|
309
|
+
page?: number;
|
|
310
|
+
limit?: number;
|
|
311
|
+
}): Promise<{
|
|
312
|
+
workspaces: Array<import("@vonzio/shared").Workspace>;
|
|
313
|
+
total: number;
|
|
314
|
+
}>;
|
|
315
|
+
update(sessionId: string, fields: {
|
|
316
|
+
name?: string;
|
|
317
|
+
starred?: boolean;
|
|
318
|
+
pinned?: boolean;
|
|
319
|
+
archived?: boolean;
|
|
320
|
+
tags?: string[];
|
|
321
|
+
public_preview?: boolean;
|
|
322
|
+
model_override?: string | null;
|
|
323
|
+
last_run_model?: string | null;
|
|
324
|
+
}, opts?: {
|
|
325
|
+
orgId?: string;
|
|
326
|
+
}): Promise<import("@vonzio/shared").Workspace | null>;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Telegram platform-bot surface. As of Phase 3D.1d.1 the concrete
|
|
330
|
+
* class is plugin-internal (@vonzio/plugin-telegram/services/
|
|
331
|
+
* platform-bot-service.ts); this interface is the structural shape
|
|
332
|
+
* the plugin's own setup routes accept so they don't reach into the
|
|
333
|
+
* service module directly.
|
|
334
|
+
*
|
|
335
|
+
* No longer exposed on PluginCore -- other plugins should ignore it.
|
|
336
|
+
* Kept exported so the telegram plugin's setup-routes signature can
|
|
337
|
+
* reference the contract type instead of the concrete class.
|
|
338
|
+
*/
|
|
339
|
+
export interface PluginTelegramPlatformBot {
|
|
340
|
+
getMetadata(): {
|
|
341
|
+
botUserId: string;
|
|
342
|
+
botUsername: string;
|
|
343
|
+
} | null;
|
|
344
|
+
getToken(): string | null;
|
|
345
|
+
getWebhookSecret(): string | null;
|
|
346
|
+
isConfigured(): boolean;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Session-lifecycle events emitted by the orchestrator. Plugins
|
|
350
|
+
* subscribe via `ctx.sessionEvents.on(event, handler)` to react to
|
|
351
|
+
* task progress without core having to know they exist -- e.g. the
|
|
352
|
+
* telegram plugin's relay echoes `task:token` to the user's Telegram
|
|
353
|
+
* chat, and `task:done` posts the final result.
|
|
354
|
+
*
|
|
355
|
+
* The signatures mirror orchestrator's existing `emit("task:*", ...)`
|
|
356
|
+
* calls exactly so the typed facade is a zero-overhead pass-through.
|
|
357
|
+
* sessionId may be `undefined` for tasks not bound to a session
|
|
358
|
+
* (one-off API calls); plugins typically early-return in that case.
|
|
359
|
+
*
|
|
360
|
+
* Handlers are NOT async-awaited by core -- they fire in parallel.
|
|
361
|
+
* Plugins that need ordering must coordinate via their own queues.
|
|
362
|
+
*/
|
|
363
|
+
export interface SessionEvents {
|
|
364
|
+
on(event: "task:token", handler: (taskId: string, sessionId: string | undefined, text: string) => void): void;
|
|
365
|
+
on(event: "task:tool_use", handler: (taskId: string, sessionId: string | undefined, tool: string, input?: unknown) => void): void;
|
|
366
|
+
on(event: "task:ask_user", handler: (taskId: string, sessionId: string | undefined, input: unknown) => void | Promise<void>): void;
|
|
367
|
+
on(event: "task:done", handler: (taskId: string, sessionId: string | undefined, result?: {
|
|
368
|
+
text?: string;
|
|
369
|
+
}) => void | Promise<void>): void;
|
|
370
|
+
on(event: "task:failed", handler: (taskId: string, sessionId: string | undefined, error?: string) => void | Promise<void>): void;
|
|
371
|
+
/**
|
|
372
|
+
* Bulk unsubscribe -- called by core during plugin teardown so a
|
|
373
|
+
* reloaded plugin doesn't double up subscriptions. Plugins normally
|
|
374
|
+
* don't call this themselves.
|
|
375
|
+
*/
|
|
376
|
+
off(event: SessionEventName, handler: (...args: never[]) => void): void;
|
|
377
|
+
}
|
|
378
|
+
export type SessionEventName = "task:token" | "task:tool_use" | "task:ask_user" | "task:done" | "task:failed";
|
|
379
|
+
/**
|
|
380
|
+
* Agent-facing description of one chat surface. Surfaced verbatim in
|
|
381
|
+
* the system-prompt Reachability section that tells the agent where a
|
|
382
|
+
* `AskUserQuestion` call will be delivered. `label` is the human
|
|
383
|
+
* sentence shown to the agent ("Telegram (chat bound — may take
|
|
384
|
+
* minutes if the user isn't near their phone)"); `slow` is true for
|
|
385
|
+
* surfaces with phone-typing latency, which triggers the
|
|
386
|
+
* "phrase as 2-4 button options" steer.
|
|
387
|
+
*/
|
|
388
|
+
export interface PresenceSurfaceMetadata {
|
|
389
|
+
label: string;
|
|
390
|
+
slow?: boolean;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* One chat-surface provider that core's presence/fallback logic walks
|
|
394
|
+
* to decide whether a session is reachable. Plugins register one of
|
|
395
|
+
* these for each surface they own (e.g. the telegram plugin
|
|
396
|
+
* registers `{ surface: "telegram", ... }`). Implementations may
|
|
397
|
+
* leave optional methods undefined when they don't apply -- the
|
|
398
|
+
* registry tolerates partial providers.
|
|
399
|
+
*
|
|
400
|
+
* The provider replaces the direct `db.select().from(schema.<plugin-
|
|
401
|
+
* table>)` calls that core used to do for each surface, breaking the
|
|
402
|
+
* reverse-coupling that blocks plugin schema moves.
|
|
403
|
+
*/
|
|
404
|
+
export interface SessionPresenceProvider {
|
|
405
|
+
/**
|
|
406
|
+
* Stable surface key. Used for dedup (registering two providers
|
|
407
|
+
* for the same key throws at boot) and for logging. Conventionally
|
|
408
|
+
* matches the plugin name.
|
|
409
|
+
*/
|
|
410
|
+
surface: string;
|
|
411
|
+
/** Agent-visible description; rendered verbatim. */
|
|
412
|
+
metadata: PresenceSurfaceMetadata;
|
|
413
|
+
/**
|
|
414
|
+
* "Is this session bound to a chat on my surface?" Used by the
|
|
415
|
+
* orchestrator's Reachability section and by ask-user fallback's
|
|
416
|
+
* in-band-suppression check. Errors are swallowed by the registry
|
|
417
|
+
* (treated as "no surface") so a flaky provider can't block a task.
|
|
418
|
+
*/
|
|
419
|
+
hasSession(sessionId: string): Promise<boolean>;
|
|
420
|
+
/**
|
|
421
|
+
* "Will my surface deliver to this user's account-wide channel,
|
|
422
|
+
* regardless of session binding?" Telegram returns true if the user
|
|
423
|
+
* has a linked bot DM; Slack returns true if the user has a linked
|
|
424
|
+
* workspace DM. Used only by ask-user fallback to suppress its
|
|
425
|
+
* plain-text notification when the in-band relay will fire.
|
|
426
|
+
*/
|
|
427
|
+
hasOwnerSurface?(userId: string): Promise<boolean>;
|
|
428
|
+
/**
|
|
429
|
+
* Session ids the user has actively engaged with via this surface
|
|
430
|
+
* (e.g. claimed a playbook thread). Used by the workspace list to
|
|
431
|
+
* keep these visible even when the standard "hide playbook
|
|
432
|
+
* executions" filter would drop them.
|
|
433
|
+
*/
|
|
434
|
+
listEngagedSessionIds?(): Promise<Set<string>>;
|
|
435
|
+
/**
|
|
436
|
+
* Fallback user_id lookup when the session isn't in the in-process
|
|
437
|
+
* registry (e.g. a brand-new chat-initiated session hasn't reached
|
|
438
|
+
* the workspace registry yet). Walked by ask-user fallback in
|
|
439
|
+
* registry order; first non-null wins.
|
|
440
|
+
*/
|
|
441
|
+
resolveUserIdBySession?(sessionId: string): Promise<string | null>;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Registration-side surface plugins program against. Plugins receive
|
|
445
|
+
* this via `PluginCore.sessionPresence` and call `register(provider)`
|
|
446
|
+
* at init() to contribute their surface. The query-side (used by
|
|
447
|
+
* core's orchestrator + fallback + workspace-service) is internal --
|
|
448
|
+
* plugins never iterate the registry themselves.
|
|
449
|
+
*/
|
|
450
|
+
export interface PluginSessionPresenceRegistry {
|
|
451
|
+
register(provider: SessionPresenceProvider): void;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Subset of `SubmitTaskInput` plugins use when handing core a new
|
|
455
|
+
* task. Mirrors core's interface 1:1 but lives in plugin-api so
|
|
456
|
+
* plugins don't have to import from core-server. New optional fields
|
|
457
|
+
* can be added; required-field changes need an apiVersion bump.
|
|
458
|
+
*/
|
|
459
|
+
export interface PluginTaskInput {
|
|
460
|
+
mode?: "session" | "batch" | "pooled" | "single";
|
|
461
|
+
prompt: string;
|
|
462
|
+
profile_id?: string;
|
|
463
|
+
session_id?: string;
|
|
464
|
+
allowed_tools?: string[];
|
|
465
|
+
egress_domains?: string[];
|
|
466
|
+
max_turns?: number;
|
|
467
|
+
max_budget_usd?: number;
|
|
468
|
+
model?: string;
|
|
469
|
+
effort?: string;
|
|
470
|
+
timeout_seconds?: number;
|
|
471
|
+
/**
|
|
472
|
+
* Inline file attachments (images, PDFs, text docs) the chat
|
|
473
|
+
* surface received with this message. Forwarded to the agent
|
|
474
|
+
* verbatim; the orchestrator handles MIME-typed presentation.
|
|
475
|
+
*/
|
|
476
|
+
attachments?: Array<import("@vonzio/shared").TaskAttachment>;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Narrow facade over `TaskService.submit` for plugins that launch
|
|
480
|
+
* tasks from external triggers (e.g. a chat message arrives ->
|
|
481
|
+
* submit a session task). callerProfileIds gates which profiles the
|
|
482
|
+
* caller is allowed to submit against; plugins typically pass
|
|
483
|
+
* `[input.profile_id!]` since the trigger is bound to one profile.
|
|
484
|
+
*/
|
|
485
|
+
export interface PluginTaskSubmitter {
|
|
486
|
+
submit(input: PluginTaskInput, callerProfileIds: string[]): Promise<{
|
|
487
|
+
task_id: string;
|
|
488
|
+
status: string;
|
|
489
|
+
created_at: string;
|
|
490
|
+
}>;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Session lifecycle mutations the plugin needs for chat-initiated
|
|
494
|
+
* sessions (e.g. /new in Telegram creates a session before any
|
|
495
|
+
* dashboard tab has bound to it). Read-only ownership stays on
|
|
496
|
+
* `core.workspaces`; this surface is for create-and-mutate.
|
|
497
|
+
*/
|
|
498
|
+
export interface PluginSessionLifecycle {
|
|
499
|
+
/**
|
|
500
|
+
* Insert a new workspace row + in-memory entry. `persistent` true
|
|
501
|
+
* survives container teardown; `orgId` defaults to null on OSS.
|
|
502
|
+
*/
|
|
503
|
+
register(sessionId: string, userId: string, profileId: string, opts?: {
|
|
504
|
+
persistent?: boolean;
|
|
505
|
+
orgId?: string | null;
|
|
506
|
+
}): Promise<{
|
|
507
|
+
session_id: string;
|
|
508
|
+
user_id: string;
|
|
509
|
+
profile_id: string;
|
|
510
|
+
}>;
|
|
511
|
+
/** Push expiry forward (ISO-8601 timestamp). */
|
|
512
|
+
extendExpiry(sessionId: string, expiresAtIso: string): Promise<void>;
|
|
513
|
+
/** Move the session between status states (e.g. "idle" -> "active"). */
|
|
514
|
+
setStatus(sessionId: string, status: "active" | "resumable" | "idle" | "expired"): Promise<void>;
|
|
515
|
+
/** Set of session_ids the dashboard currently has WS-connected. */
|
|
516
|
+
getConnectedSessionIds(): Set<string>;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Narrow orchestrator surface plugins call directly. Today this is
|
|
520
|
+
* just wakeWorkspaceContainer (the "boot a container for this session
|
|
521
|
+
* before submitting a task to it" call). More methods land here only
|
|
522
|
+
* with clear plugin need + a justification block on the field.
|
|
523
|
+
*
|
|
524
|
+
* `ResolvedProfile` is imported from @vonzio/shared rather than
|
|
525
|
+
* mirrored structurally -- the type is wide (env, tools, claude_md,
|
|
526
|
+
* setup_commands, persistent_sessions, ...) and the orchestrator
|
|
527
|
+
* reads more of it as features land. Mirroring would silently drift.
|
|
528
|
+
*/
|
|
529
|
+
export interface PluginOrchestrator {
|
|
530
|
+
wakeWorkspaceContainer(sessionId: string, profile: import("@vonzio/shared").ResolvedProfile): Promise<string | null>;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Append-only session event log used by the dashboard's replay view.
|
|
534
|
+
* Plugins that bridge an external surface to a session (telegram /new
|
|
535
|
+
* inbound, slack reply) append user_message events so the dashboard
|
|
536
|
+
* timeline shows them. Read is used by webhook callback handlers
|
|
537
|
+
* that need to replay context.
|
|
538
|
+
*/
|
|
539
|
+
export interface PluginEventLog {
|
|
540
|
+
append(sessionId: string, type: string, data: Record<string, unknown>): void;
|
|
541
|
+
read(sessionId: string, afterSeq?: number): Array<{
|
|
542
|
+
seq: number;
|
|
543
|
+
type: string;
|
|
544
|
+
data: Record<string, unknown>;
|
|
545
|
+
ts: number;
|
|
546
|
+
}>;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Dashboard WS push. Plugins call this to broadcast an event to the
|
|
550
|
+
* dashboard tab(s) watching a session (e.g. "an external chat reply
|
|
551
|
+
* just landed -- here it is in real time"). Same channel core uses
|
|
552
|
+
* for orchestrator events.
|
|
553
|
+
*/
|
|
554
|
+
export interface PluginConnectionManager {
|
|
555
|
+
sendToSession(sessionId: string, message: Record<string, unknown>): void;
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Image rewriter for inline `` markdown in agent output.
|
|
559
|
+
* Chat surfaces that don't render inline images (Telegram) need to
|
|
560
|
+
* strip the references and queue the URLs for a separate sendPhoto
|
|
561
|
+
* call. forSession signs the URLs so chat backends fetching them
|
|
562
|
+
* can authenticate against the preview gateway.
|
|
563
|
+
*/
|
|
564
|
+
export interface PluginImageRewriter {
|
|
565
|
+
forSession(sessionId: string, text: string): Promise<{
|
|
566
|
+
textWithUrls: string;
|
|
567
|
+
textWithoutImages: string;
|
|
568
|
+
images: Array<{
|
|
569
|
+
url: string;
|
|
570
|
+
alt: string;
|
|
571
|
+
originalUrl: string;
|
|
572
|
+
}>;
|
|
573
|
+
} | null>;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Model picker for chat-side "switch model" interactions. Returns the
|
|
577
|
+
* available list + the profile's current default so the picker can
|
|
578
|
+
* mark a "current" pin without a second profile fetch.
|
|
579
|
+
*/
|
|
580
|
+
export interface PluginModelList {
|
|
581
|
+
listForProfile(profileId: string): Promise<{
|
|
582
|
+
ok: true;
|
|
583
|
+
models: Array<{
|
|
584
|
+
id: string;
|
|
585
|
+
display_name: string | null;
|
|
586
|
+
provider: "anthropic" | "ollama";
|
|
587
|
+
}>;
|
|
588
|
+
profileDefault: string | null;
|
|
589
|
+
} | {
|
|
590
|
+
ok: false;
|
|
591
|
+
status: number;
|
|
592
|
+
error: string;
|
|
593
|
+
}>;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Extended profile lookup. The existing `PluginProfileLookup` only
|
|
597
|
+
* does `list(userId)`; chat surfaces also need to resolve a single
|
|
598
|
+
* profile's full ResolvedProfile (model defaults, allowed tools,
|
|
599
|
+
* setup commands, etc.) to hand to `core.orchestrator.wakeWorkspaceContainer`.
|
|
600
|
+
*
|
|
601
|
+
* Kept on a separate field rather than widening PluginProfileLookup
|
|
602
|
+
* so plugins that need only `.list()` aren't forced to depend on the
|
|
603
|
+
* full ResolvedProfile type tree.
|
|
604
|
+
*/
|
|
605
|
+
export interface PluginProfileResolver {
|
|
606
|
+
getResolved(profileId: string): Promise<import("@vonzio/shared").ResolvedProfile | null>;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Core services exposed to plugins. Add fields here only with strong
|
|
610
|
+
* justification -- the surface is a stability commitment.
|
|
611
|
+
*/
|
|
612
|
+
export interface PluginCore {
|
|
613
|
+
/**
|
|
614
|
+
* Drizzle handle. Plugins should only query their own tables; cross-
|
|
615
|
+
* table access requires going through a documented core call (none
|
|
616
|
+
* yet defined for v0.1). Typed as `unknown` in the contract -- the
|
|
617
|
+
* loader injects the real handle. Plugins cast to
|
|
618
|
+
* `NodePgDatabase<typeof pluginSchema>`.
|
|
619
|
+
*/
|
|
620
|
+
db: unknown;
|
|
621
|
+
/**
|
|
622
|
+
* AES-256-GCM wrapper around the master vault key. Use this for
|
|
623
|
+
* any plugin-owned secret that lands in the DB (OAuth tokens, API
|
|
624
|
+
* keys, bot tokens). Never log decrypted values; never persist them
|
|
625
|
+
* outside the encryption flow.
|
|
626
|
+
*/
|
|
627
|
+
encryption: {
|
|
628
|
+
encrypt(plaintext: string): string;
|
|
629
|
+
decrypt(ciphertext: string): string;
|
|
630
|
+
};
|
|
631
|
+
/**
|
|
632
|
+
* Integration lookup. Plugins resolve `req.recipient` against this
|
|
633
|
+
* to get the credentials + config for the user's connected
|
|
634
|
+
* provider (Slack workspace, Telegram bot, etc.).
|
|
635
|
+
*/
|
|
636
|
+
integrations: PluginIntegrationLookup;
|
|
637
|
+
/**
|
|
638
|
+
* Profile lookup (read-only). Plugins use this when validating
|
|
639
|
+
* profile_ids in their own routes.
|
|
640
|
+
*/
|
|
641
|
+
profiles: PluginProfileLookup;
|
|
642
|
+
/**
|
|
643
|
+
* Workspace lookup (read-only). Plugins use this for ownership
|
|
644
|
+
* checks on workspace-bound resources.
|
|
645
|
+
*/
|
|
646
|
+
workspaces: PluginWorkspaceLookup;
|
|
647
|
+
/**
|
|
648
|
+
* Auth hook plugins can opt into. Plugins with routes that need
|
|
649
|
+
* authenticated access call
|
|
650
|
+
* `server.addHook("onRequest", ctx.core.authHook)` inside their
|
|
651
|
+
* route registration scope. v0.1 mirrors what core wires onto the
|
|
652
|
+
* /v1 fastify subscope -- a session-cookie-or-bearer check that
|
|
653
|
+
* populates request.user on success and returns 401 otherwise.
|
|
654
|
+
*
|
|
655
|
+
* Plugins whose routes are public (e.g. webhook receivers verified
|
|
656
|
+
* via a shared secret) skip this entirely.
|
|
657
|
+
*/
|
|
658
|
+
authHook: import("fastify").onRequestHookHandler;
|
|
659
|
+
/**
|
|
660
|
+
* Where plugins contribute a chat-surface presence provider. Lets
|
|
661
|
+
* core's orchestrator + ask-user-fallback + workspace-service ask
|
|
662
|
+
* "is this session reachable on a chat surface?" without reading
|
|
663
|
+
* plugin-owned tables directly. The plugin's provider does the
|
|
664
|
+
* actual DB read.
|
|
665
|
+
*/
|
|
666
|
+
sessionPresence: PluginSessionPresenceRegistry;
|
|
667
|
+
/**
|
|
668
|
+
* Submit new tasks from a plugin (e.g. a chat surface received a
|
|
669
|
+
* user message; submit a session task with the prompt). Same gate
|
|
670
|
+
* as the dashboard's submit route: caller must have access to the
|
|
671
|
+
* profile via callerProfileIds.
|
|
672
|
+
*/
|
|
673
|
+
tasks: PluginTaskSubmitter;
|
|
674
|
+
/**
|
|
675
|
+
* Mutate session state. Used by chat surfaces that initiate
|
|
676
|
+
* sessions from outside the dashboard (e.g. Telegram /new).
|
|
677
|
+
*/
|
|
678
|
+
sessionLifecycle: PluginSessionLifecycle;
|
|
679
|
+
/**
|
|
680
|
+
* Orchestrator surface: wake a workspace container for a session
|
|
681
|
+
* before submitting a task. Plugins call this from their session-
|
|
682
|
+
* resume codepath after pairing a chat message with an existing
|
|
683
|
+
* workspace.
|
|
684
|
+
*/
|
|
685
|
+
orchestrator: PluginOrchestrator;
|
|
686
|
+
/**
|
|
687
|
+
* Append-only event log. Plugins write user_message events when
|
|
688
|
+
* relaying inbound chat messages so they show up in the dashboard
|
|
689
|
+
* timeline; read is used by webhook handlers that need session
|
|
690
|
+
* context.
|
|
691
|
+
*/
|
|
692
|
+
eventLog: PluginEventLog;
|
|
693
|
+
/**
|
|
694
|
+
* Dashboard WS push surface. Plugins use it to broadcast events
|
|
695
|
+
* sourced from the external chat into the dashboard view of the
|
|
696
|
+
* session.
|
|
697
|
+
*/
|
|
698
|
+
connectionManager: PluginConnectionManager;
|
|
699
|
+
/**
|
|
700
|
+
* Image rewriter for chat surfaces that don't render inline
|
|
701
|
+
* markdown images. Returns the stripped text + a signed-URL list
|
|
702
|
+
* the plugin can hand to the chat API's sendPhoto-equivalent.
|
|
703
|
+
*/
|
|
704
|
+
imageRewriter: PluginImageRewriter;
|
|
705
|
+
/**
|
|
706
|
+
* Model list lookup for chat-side model pickers.
|
|
707
|
+
*/
|
|
708
|
+
modelList: PluginModelList;
|
|
709
|
+
/**
|
|
710
|
+
* Resolved profile lookup (full ResolvedProfile shape). Required
|
|
711
|
+
* before calling `orchestrator.wakeWorkspaceContainer`. Separate
|
|
712
|
+
* from `profiles.list` to keep plugins that need only listing from
|
|
713
|
+
* pulling in the wider type tree.
|
|
714
|
+
*/
|
|
715
|
+
profileResolver: PluginProfileResolver;
|
|
716
|
+
}
|
|
717
|
+
/** Minimal logger contract. Backed by core's pino logger at runtime. */
|
|
718
|
+
export interface PluginLogger {
|
|
719
|
+
info(meta: Record<string, unknown> | string, msg?: string): void;
|
|
720
|
+
warn(meta: Record<string, unknown> | string, msg?: string): void;
|
|
721
|
+
error(meta: Record<string, unknown> | string, msg?: string): void;
|
|
722
|
+
debug(meta: Record<string, unknown> | string, msg?: string): void;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* One outbound notification request. Core services hand these to the
|
|
726
|
+
* notification bus, which dispatches to whichever plugin claimed the
|
|
727
|
+
* `kind`.
|
|
728
|
+
*/
|
|
729
|
+
export interface NotificationRequest {
|
|
730
|
+
/**
|
|
731
|
+
* The channel family this request targets. Plugins claim a kind in
|
|
732
|
+
* `init()` via `notificationBus.registerHandler(kind, handler)`.
|
|
733
|
+
* Examples: `"telegram"`, `"slack"`, `"email"`.
|
|
734
|
+
*/
|
|
735
|
+
kind: string;
|
|
736
|
+
/**
|
|
737
|
+
* Plugin-specific recipient identifier. For most plugins this is
|
|
738
|
+
* the user-integration id; the plugin uses it to look up the right
|
|
739
|
+
* bot token, channel, thread, etc. in its own DB.
|
|
740
|
+
*/
|
|
741
|
+
recipient: string;
|
|
742
|
+
/** Human-readable body. Plugins may format-translate before sending. */
|
|
743
|
+
text: string;
|
|
744
|
+
/** Free-form per-message metadata (e.g. priority, thread anchors). */
|
|
745
|
+
metadata?: Record<string, unknown>;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Plugin-side notification handler. Returns a result so callers (and
|
|
749
|
+
* core's retry/circuit-breaker machinery) can react. Never throws --
|
|
750
|
+
* unexpected errors should be wrapped into `{ ok: false, retryable }`.
|
|
751
|
+
*/
|
|
752
|
+
export type NotificationHandler = (req: NotificationRequest) => Promise<NotificationResult>;
|
|
753
|
+
export type NotificationResult = {
|
|
754
|
+
ok: true;
|
|
755
|
+
} | {
|
|
756
|
+
ok: false;
|
|
757
|
+
error: string;
|
|
758
|
+
retryable: boolean;
|
|
759
|
+
};
|
|
760
|
+
/**
|
|
761
|
+
* Where plugins register notification handlers. One handler per
|
|
762
|
+
* kind -- attempting to register a second handler for the same kind
|
|
763
|
+
* is an error (caught at boot).
|
|
764
|
+
*/
|
|
765
|
+
export interface NotificationBus {
|
|
766
|
+
registerHandler(kind: string, handler: NotificationHandler): void;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Spec for an MCP server the plugin contributes. The loader hands
|
|
770
|
+
* these to the core MCP runtime, which exposes them to agents
|
|
771
|
+
* according to the agent's profile config.
|
|
772
|
+
*/
|
|
773
|
+
export interface McpServerSpec {
|
|
774
|
+
/** Identifier shown in agent-side MCP listings. */
|
|
775
|
+
name: string;
|
|
776
|
+
/**
|
|
777
|
+
* How agents reach the server. `stdio` = spawn a process per agent
|
|
778
|
+
* session; `http` = a single endpoint reachable by all sessions.
|
|
779
|
+
*
|
|
780
|
+
* For `http`, `url` MUST be an absolute PATH (e.g. `/plugins/teller/mcp`) — the
|
|
781
|
+
* plugin serves its MCP route via `ctx.server` and core resolves the path
|
|
782
|
+
* against its internal server URL at injection time, so the plugin needn't
|
|
783
|
+
* know the internal host. External / protocol-relative / traversing urls are
|
|
784
|
+
* REFUSED at `registerServer`: core attaches a per-task bearer token and that
|
|
785
|
+
* token must never leave the deployment. Core adds the `Authorization` header
|
|
786
|
+
* itself; the plugin's route resolves it via {@link McpSessions.resolve}.
|
|
787
|
+
*/
|
|
788
|
+
transport: {
|
|
789
|
+
type: "stdio";
|
|
790
|
+
command: string;
|
|
791
|
+
args?: string[];
|
|
792
|
+
env?: Record<string, string>;
|
|
793
|
+
} | {
|
|
794
|
+
type: "http";
|
|
795
|
+
url: string;
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
export interface McpRegistry {
|
|
799
|
+
registerServer(spec: McpServerSpec): void;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Identity behind a per-task token core minted when it injected the plugin's
|
|
803
|
+
* MCP server into an agent container. The plugin's MCP HTTP route reads the
|
|
804
|
+
* `Authorization: Bearer <token>` header and resolves it here to scope the
|
|
805
|
+
* call to the right user / profile / tenant. Gated by `mcp.register`.
|
|
806
|
+
*/
|
|
807
|
+
export interface McpSessions {
|
|
808
|
+
/** Resolve a per-task MCP token, or null if unknown/expired. */
|
|
809
|
+
resolve(token: string): {
|
|
810
|
+
userId: string;
|
|
811
|
+
profileId: string;
|
|
812
|
+
orgId: string | null;
|
|
813
|
+
} | null;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Scheduled work the plugin owns. Core cancels everything registered
|
|
817
|
+
* here during teardown; plugins don't need to track timer handles.
|
|
818
|
+
*/
|
|
819
|
+
export interface Scheduler {
|
|
820
|
+
/**
|
|
821
|
+
* Standard 5-field cron expression, evaluated in UTC. `name` is for
|
|
822
|
+
* logging + dedup -- registering the same name twice is an error.
|
|
823
|
+
*/
|
|
824
|
+
cron(name: string, schedule: string, fn: () => Promise<void>): void;
|
|
825
|
+
/**
|
|
826
|
+
* Fixed-interval job. `ms` is the gap BETWEEN runs (not from start),
|
|
827
|
+
* so a slow fn won't queue up backlog.
|
|
828
|
+
*/
|
|
829
|
+
interval(name: string, ms: number, fn: () => Promise<void>): void;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* The auth-decorated user attached to a FastifyRequest by core's
|
|
833
|
+
* userAuthHook. Plugins receive this as `request.user` inside any
|
|
834
|
+
* route that runs under an auth-gated scope (e.g. /v1/*).
|
|
835
|
+
*
|
|
836
|
+
* Plugins typed against this interface should cast at the request
|
|
837
|
+
* site -- `const user = request.user as AuthUser` -- because the
|
|
838
|
+
* module augmentation that sets `user?: AuthUser` on FastifyRequest
|
|
839
|
+
* lives in core-server's auth/user-auth.ts; declaring it again here
|
|
840
|
+
* would create a conflicting declaration in plugin-api's tsconfig
|
|
841
|
+
* project.
|
|
842
|
+
*
|
|
843
|
+
* The shape mirrors core's AuthUser (id + email + role + minor
|
|
844
|
+
* fields) but is structurally typed here so plugin-api doesn't have
|
|
845
|
+
* to import from core-server.
|
|
846
|
+
*/
|
|
847
|
+
export interface AuthUser {
|
|
848
|
+
id: string;
|
|
849
|
+
email: string;
|
|
850
|
+
name?: string;
|
|
851
|
+
role: string;
|
|
852
|
+
feature_flags?: string;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Per-plugin key/value store (`ctx.storage`). Backed by the core-owned
|
|
856
|
+
* `plugin_storage` table; every read/write is filtered server-side by the
|
|
857
|
+
* plugin's id, so one plugin cannot read another's keys via this surface.
|
|
858
|
+
* Gated by the `storage.kv` capability. See §5.
|
|
859
|
+
*/
|
|
860
|
+
export interface PluginStorageKv {
|
|
861
|
+
get<T>(key: string): Promise<T | null>;
|
|
862
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
863
|
+
delete(key: string): Promise<void>;
|
|
864
|
+
list(prefix?: string): Promise<Array<{
|
|
865
|
+
key: string;
|
|
866
|
+
value: unknown;
|
|
867
|
+
}>>;
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Audited outbound HTTP/WS surface (`ctx.http`). Gated by `http.outbound`.
|
|
871
|
+
* Every call resolves the hostname, blocks SSRF targets (private/link-local
|
|
872
|
+
* IPs, DNS rebinding), and requires the host to match the manifest∩policy
|
|
873
|
+
* `outboundHosts` allowlist. See §10.
|
|
874
|
+
*
|
|
875
|
+
* `fetch` returns a real WHATWG `Response`, so existing `.json()` / `.ok` /
|
|
876
|
+
* `.text()` / `.arrayBuffer()` call sites keep working — binary downloads use
|
|
877
|
+
* `.arrayBuffer()` and are not corrupted (the bytes are preserved end to end).
|
|
878
|
+
*/
|
|
879
|
+
export interface PluginHttpInit {
|
|
880
|
+
method?: string;
|
|
881
|
+
headers?: Record<string, string>;
|
|
882
|
+
body?: string | Uint8Array | FormData;
|
|
883
|
+
/** Per-call timeout override (ms), capped at 30s. */
|
|
884
|
+
timeoutMs?: number;
|
|
885
|
+
/** Per-call max response size override (bytes), capped at 5 MiB. */
|
|
886
|
+
maxResponseBytes?: number;
|
|
887
|
+
/**
|
|
888
|
+
* Present a client certificate for mutual TLS on this call. Pass an
|
|
889
|
+
* {@link MtlsRef} obtained from `ctx.secrets.mtls(name)` — core reads the
|
|
890
|
+
* operator-provisioned cert/key files server-side at request time; the cert
|
|
891
|
+
* material never passes through plugin code. Gated by `secrets.mtls`. See §10.
|
|
892
|
+
*/
|
|
893
|
+
mtls?: MtlsRef;
|
|
894
|
+
}
|
|
895
|
+
export interface PluginHttp {
|
|
896
|
+
fetch(url: string, init?: PluginHttpInit): Promise<Response>;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Opaque handle to operator-provisioned mTLS client material, resolved from a
|
|
900
|
+
* logical name the plugin declared in `manifest.mtlsSecrets`. The plugin CANNOT
|
|
901
|
+
* read the cert/key bytes through this object — it carries only the logical
|
|
902
|
+
* `name`. The cert/key files are read server-side when the ref is passed to
|
|
903
|
+
* `ctx.http.fetch({ mtls })`. See §5, §10.
|
|
904
|
+
*/
|
|
905
|
+
export interface MtlsRef {
|
|
906
|
+
/** Brand: lets the HTTP surface recognize a genuine ref and keeps the type
|
|
907
|
+
* nominal to plugin authors. */
|
|
908
|
+
readonly __vonzioMtls: true;
|
|
909
|
+
/** The logical secret name — the only field a plugin can observe. */
|
|
910
|
+
readonly name: string;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Secret-material resolution surface (`ctx.secrets`). Gated by `secrets.mtls`.
|
|
914
|
+
* v1 exposes only mTLS client certs as opaque {@link MtlsRef}s; the bytes are
|
|
915
|
+
* never readable through this surface (the operator provisions them as host
|
|
916
|
+
* files in policy and core loads them server-side at request time).
|
|
917
|
+
*/
|
|
918
|
+
export interface PluginSecrets {
|
|
919
|
+
/**
|
|
920
|
+
* Resolve a declared mTLS secret `name` (from `manifest.mtlsSecrets`) into an
|
|
921
|
+
* opaque ref for `ctx.http.fetch({ mtls })`. Throws `CapabilityViolationError`
|
|
922
|
+
* if the name was not declared + provisioned.
|
|
923
|
+
*/
|
|
924
|
+
mtls(name: string): MtlsRef;
|
|
925
|
+
}
|
|
926
|
+
export { PLUGIN_CAPABILITIES, CAPABILITY_SURFACE_MAP, ROOT_EQUIVALENT_COMBINATIONS, BUILTIN_ONLY_CAPABILITIES, isPluginCapability, } from "./capabilities.js";
|
|
927
|
+
export type { PluginCapability, CapabilitySurface, SurfaceKind } from "./capabilities.js";
|
|
928
|
+
export { MANIFEST_ALLOWED_KEYS, POLICY_ENTRY_ALLOWED_KEYS, SCHEMA_PREFIX_PATTERN, MTLS_SECRET_NAME_PATTERN, } from "./manifest.js";
|
|
929
|
+
export type { PluginManifest, ManifestRoutePrefix, MtlsSecretFiles, PluginSource, PolicyEntry, OperatorPolicy, } from "./manifest.js";
|
|
930
|
+
export { validateManifest, validatePolicy, matchOutboundHost, normalizeHostPattern, } from "./manifest-validate.js";
|
|
931
|
+
export type { ManifestValidationResult } from "./manifest-validate.js";
|
|
932
|
+
export { CapabilityViolationError, OutboundHostViolationError, DbScopeViolationError, PluginRefusedError, PolicyViolationError, REFUSAL_REASONS, } from "./errors.js";
|
|
933
|
+
export type { RefusalReason } from "./errors.js";
|
|
934
|
+
export { assertApiCompatible } from "./version.js";
|
|
935
|
+
/**
|
|
936
|
+
* Structural shape the loader needs from a plugin's `configSchema`.
|
|
937
|
+
* Both zod v3 and v4 satisfy this naturally -- they both expose a
|
|
938
|
+
* `parse(input): T` method. Plugins typically use `z.object({...})`
|
|
939
|
+
* which yields a `ZodObject` whose `.parse()` returns the inferred
|
|
940
|
+
* shape; the inference flows into TConfig.
|
|
941
|
+
*/
|
|
942
|
+
export interface ConfigSchemaLike<TConfig> {
|
|
943
|
+
parse(input: unknown): TConfig;
|
|
944
|
+
}
|
|
945
|
+
//# sourceMappingURL=index.d.ts.map
|