@getfamiliar/shared 0.1.2

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.
Files changed (83) hide show
  1. package/build/AgentRun.d.ts +187 -0
  2. package/build/AgentRun.js +1 -0
  3. package/build/AgentRunBus.d.ts +147 -0
  4. package/build/AgentRunBus.js +362 -0
  5. package/build/Calendar.d.ts +450 -0
  6. package/build/Calendar.js +37 -0
  7. package/build/ChatMessage.d.ts +53 -0
  8. package/build/ChatMessage.js +11 -0
  9. package/build/ChatMessageBus.d.ts +110 -0
  10. package/build/ChatMessageBus.js +257 -0
  11. package/build/Config.d.ts +83 -0
  12. package/build/Config.js +1 -0
  13. package/build/ContainerTool.d.ts +34 -0
  14. package/build/ContainerTool.js +1 -0
  15. package/build/CronExpression.d.ts +53 -0
  16. package/build/CronExpression.js +77 -0
  17. package/build/Event.d.ts +280 -0
  18. package/build/Event.js +14 -0
  19. package/build/EventBus.d.ts +198 -0
  20. package/build/EventBus.js +482 -0
  21. package/build/HandlerCatalog.d.ts +75 -0
  22. package/build/HandlerCatalog.js +165 -0
  23. package/build/InferenceEvent.d.ts +30 -0
  24. package/build/InferenceEvent.js +1 -0
  25. package/build/InferenceEventBus.d.ts +39 -0
  26. package/build/InferenceEventBus.js +80 -0
  27. package/build/Mail.d.ts +250 -0
  28. package/build/Mail.js +73 -0
  29. package/build/MailStyleTemplate.d.ts +71 -0
  30. package/build/MailStyleTemplate.js +47 -0
  31. package/build/ModelMetaData.d.ts +63 -0
  32. package/build/ModelMetaData.js +12 -0
  33. package/build/PathGlob.d.ts +20 -0
  34. package/build/PathGlob.js +33 -0
  35. package/build/Plugin.d.ts +709 -0
  36. package/build/Plugin.js +29 -0
  37. package/build/PostgresConnection.d.ts +75 -0
  38. package/build/PostgresConnection.js +162 -0
  39. package/build/ScheduledSubagent.d.ts +49 -0
  40. package/build/ScheduledSubagent.js +1 -0
  41. package/build/ScheduledSubagentBus.d.ts +94 -0
  42. package/build/ScheduledSubagentBus.js +187 -0
  43. package/build/Schema.d.ts +84 -0
  44. package/build/Schema.js +570 -0
  45. package/build/StepResult.d.ts +103 -0
  46. package/build/StepResult.js +1 -0
  47. package/build/StepResultBus.d.ts +64 -0
  48. package/build/StepResultBus.js +169 -0
  49. package/build/Timezone.d.ts +58 -0
  50. package/build/Timezone.js +84 -0
  51. package/build/ToolCall.d.ts +25 -0
  52. package/build/ToolCall.js +1 -0
  53. package/build/ToolCallBus.d.ts +65 -0
  54. package/build/ToolCallBus.js +110 -0
  55. package/build/ToolLevel.d.ts +20 -0
  56. package/build/ToolLevel.js +2 -0
  57. package/build/ToolRunner.d.ts +118 -0
  58. package/build/ToolRunner.js +192 -0
  59. package/build/ToolsExpressionUtils.d.ts +80 -0
  60. package/build/ToolsExpressionUtils.js +102 -0
  61. package/build/WorkspaceFile.d.ts +104 -0
  62. package/build/WorkspaceFile.js +7 -0
  63. package/build/estimateTokens.d.ts +16 -0
  64. package/build/estimateTokens.js +18 -0
  65. package/build/index.d.ts +37 -0
  66. package/build/index.js +26 -0
  67. package/build/logging/Logger.d.ts +66 -0
  68. package/build/logging/Logger.js +90 -0
  69. package/build/markdownHtml.d.ts +14 -0
  70. package/build/markdownHtml.js +21 -0
  71. package/build/markdownTerminal.d.ts +14 -0
  72. package/build/markdownTerminal.js +376 -0
  73. package/build/testing/MockAgentRunBus.d.ts +52 -0
  74. package/build/testing/MockAgentRunBus.js +175 -0
  75. package/build/testing/MockBusStore.d.ts +22 -0
  76. package/build/testing/MockBusStore.js +22 -0
  77. package/build/testing/MockEventBus.d.ts +20 -0
  78. package/build/testing/MockEventBus.js +57 -0
  79. package/build/testing/MockLogger.d.ts +52 -0
  80. package/build/testing/MockLogger.js +65 -0
  81. package/build/testing/index.d.ts +4 -0
  82. package/build/testing/index.js +4 -0
  83. package/package.json +63 -0
@@ -0,0 +1,709 @@
1
+ import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import type { CommandDef } from "citty";
3
+ import type { AgentRunRow } from "./AgentRun.js";
4
+ import type { CalendarApi } from "./Calendar.js";
5
+ import type { ChatFilter } from "./ChatMessage.js";
6
+ import type { ChatHandler, ChatUnsubscribe } from "./ChatMessageBus.js";
7
+ import type { ConfigService } from "./Config.js";
8
+ import type { EventFile, EventRow, NewEvent } from "./Event.js";
9
+ import type { Logger } from "./logging/Logger.js";
10
+ import type { MailApi } from "./Mail.js";
11
+ import type { MailStyleTemplate } from "./MailStyleTemplate.js";
12
+ import type { ModelMetaData, ModelProviderDescriptor } from "./ModelMetaData.js";
13
+ import type { StepResultRow } from "./StepResult.js";
14
+ import type { ToolLevel } from "./ToolLevel.js";
15
+ import type { ToolRunContext } from "./ToolRunner.js";
16
+ import type { WorkspaceWatcherApi } from "./WorkspaceFile.js";
17
+ export type { ChatHandler, Client as McpClient };
18
+ /**
19
+ * Citty's `CommandDef` is generic over its `ArgsDef`, and that
20
+ * generic appears in contravariant position on `run`. As a result a
21
+ * `CommandDef<{ message: ... }>` is not assignable to the default
22
+ * `CommandDef<ArgsDef>`. Citty's own `SubCommandsDef` works around
23
+ * this with `CommandDef<any>`; we mirror that pattern in the plugin
24
+ * manifest so plugins can return well-typed commands without
25
+ * casting at the boundary.
26
+ */
27
+ export type AnyCommandDef = CommandDef<any>;
28
+ /**
29
+ * Capabilities the host exposes to plugins. Passed into every plugin
30
+ * lifecycle method (`start`, `commands`, `cronjobs`).
31
+ *
32
+ * **Plugins must reach host capabilities only through this object.**
33
+ * Direct imports of `EventBus`, `PostgresConnection`, or other host
34
+ * internals are not allowed in plugin code — the host owns those
35
+ * lifecycles, and routing through ctx is what lets the host enforce
36
+ * policy (later: assert a plugin only emits topics it declared, scope
37
+ * logs by plugin id, gate writes through approval, etc.) and what
38
+ * makes plugins unit-testable with a fake ctx.
39
+ *
40
+ * The contract is intentionally minimal today: only what the first
41
+ * plugin (cli-chat) needs. New capabilities are added on demand as
42
+ * plugins surface them.
43
+ */
44
+ /**
45
+ * Async function a plugin registers via
46
+ * `ctx.events.registerContextProvider(fn)` to contribute per-event
47
+ * situational knowledge to the handler that is about to run. Receives
48
+ * the agentrun about to execute and its parent event; returns the
49
+ * markdown body injected into the current-run user message, right after
50
+ * the `# Runtime` block (see `buildRuntimeContextBlock`). The system
51
+ * prompt itself stays static so it remains a cacheable prefix.
52
+ * Returning `null`, `undefined`, or an empty/whitespace string
53
+ * contributes nothing — useful for "only speak up when relevant"
54
+ * providers.
55
+ *
56
+ * The function is called once per `buildRuntimeContextBlock` (i.e. once
57
+ * per agentrun start). It runs in the host process — providers may close
58
+ * over plugin-local services (DB handles, caches) without going through
59
+ * the bastion. The PromptBuilder reaches the registry through the
60
+ * bastion's `/event-context/` gateway, which fans out to every
61
+ * registered provider in parallel; one bad provider must not poison
62
+ * the prompt, so the gateway isolates rejections and per-call timeouts.
63
+ */
64
+ export type EventContextProvider = (agentrun: AgentRunRow, event: EventRow) => Promise<string | null | undefined>;
65
+ /**
66
+ * Optional callbacks passed alongside `ctx.events.emit`. Bundled into
67
+ * an options object so additional per-emit hooks (e.g. cancellation,
68
+ * progress) can be added without breaking the call signature.
69
+ */
70
+ export interface EmitOptions {
71
+ /**
72
+ * Fired for every step row inserted into `stepresults` for this
73
+ * event, in INSERT order. Errors thrown by the callback are caught
74
+ * and logged so a buggy subscriber can't break the emit. When
75
+ * omitted, no `stepresults_new` LISTEN is registered (zero
76
+ * overhead).
77
+ */
78
+ readonly onStep?: (step: StepResultRow) => void | Promise<void>;
79
+ /**
80
+ * Fired whenever an `agentruns` row tied to this event is inserted
81
+ * or transitions state. Receives the current {@link AgentRunRow} —
82
+ * callers that only care about a specific transition must filter
83
+ * on `row.state` themselves. Errors thrown by the callback are
84
+ * caught and logged so a buggy subscriber can't break the emit.
85
+ * When omitted, no `agentruns_changed` LISTEN is registered (zero
86
+ * overhead).
87
+ */
88
+ readonly onAgentRun?: (row: AgentRunRow) => void | Promise<void>;
89
+ }
90
+ /**
91
+ * Handle returned by `ctx.events.emit` once the event row has been
92
+ * persisted. The two halves let callers act on the assigned id
93
+ * immediately (e.g. registering it in an in-flight tracker) without
94
+ * blocking on agent execution.
95
+ */
96
+ export interface EmitHandle {
97
+ /** Database id of the inserted event row, available immediately. */
98
+ readonly id: string;
99
+ /**
100
+ * Resolves with the `result_text` of the last-settled agentrun
101
+ * for the event (empty string if the handler produced no text).
102
+ * Rejects with an `Error` carrying the failed agentrun's `error`
103
+ * message when the event terminates in `failed`.
104
+ *
105
+ * Consumers MUST attach a `.then`/`.catch` (or `await`) to this
106
+ * promise — otherwise a `failed` event surfaces as an unhandled
107
+ * rejection.
108
+ */
109
+ readonly settled: Promise<string>;
110
+ }
111
+ /**
112
+ * Metadata projection of one MCP entry as `ctx.mcp.getList()` exposes
113
+ * it to plugins. Combines the `mcp.yml` key with the (non-sensitive)
114
+ * identifying string of whatever provides the MCP: image for
115
+ * `docker-mcp-registry`, package name for `npm` / `pypi`, URL for
116
+ * `external`.
117
+ */
118
+ export interface McpInfo {
119
+ /** The top-level key in `mcp.yml`. Plugin-stable identifier. */
120
+ readonly key: string;
121
+ /** Source classifier; matches the `source` value in `mcp.yml`. */
122
+ readonly source: string;
123
+ /**
124
+ * The image (docker-mcp-registry), bare package name (npm / pypi),
125
+ * or URL (external) — whichever fits the source. One field so
126
+ * callers can grep across sources uniformly; the source tag tells
127
+ * them how to interpret it. Always non-empty.
128
+ */
129
+ readonly package: string;
130
+ }
131
+ export interface HostContext {
132
+ /**
133
+ * Emit an event into the bus. The returned outer promise resolves
134
+ * once the row has been inserted (so the assigned id is known); the
135
+ * `settled` field on the resolved handle reaches its terminal
136
+ * state when the event finishes processing.
137
+ *
138
+ * Implementation subscribes to `events_state` before inserting to
139
+ * avoid missing the terminal NOTIFY on a fast-processed event, and
140
+ * tears the subscription down when `settled` resolves or rejects.
141
+ *
142
+ * Rejects with {@link DuplicateIdempotencyKeyError} when the event's
143
+ * `idempotencyKey` collides with one already in the bus. Emitters
144
+ * that re-derive a stable key (delta-poll re-walks, chat compaction)
145
+ * should catch it and treat the collision as a no-op; the event
146
+ * already exists.
147
+ */
148
+ readonly events: {
149
+ emit(event: NewEvent, options?: EmitOptions): Promise<EmitHandle>;
150
+ /**
151
+ * Register an async function called once per agentrun start with
152
+ * the agentrun row and its parent event. Its returned string is
153
+ * injected below the `# Available tools` section in the system
154
+ * prompt the handler runs against. Use this to teach the agent
155
+ * situational facts a plugin knows — e.g. "the sender of this
156
+ * mail is currently in a meeting", "this chat thread has 14
157
+ * unread mails attached to it".
158
+ *
159
+ * Providers must be cheap to run: the PromptBuilder fans out to
160
+ * every registered provider in parallel via the bastion's
161
+ * `/event-context/` gateway and waits on them before assembling
162
+ * the prompt. Slow providers delay the start of every agentrun.
163
+ *
164
+ * A provider that returns `null`, `undefined`, or whitespace
165
+ * contributes nothing — typical for "only speak up when
166
+ * relevant" cases. Exceptions thrown inside the provider are
167
+ * isolated by the gateway and logged with the plugin id; the
168
+ * prompt still assembles with the remaining providers' output.
169
+ *
170
+ * Plugins typically call this from their `start(ctx)` hook.
171
+ */
172
+ registerContextProvider(fn: EventContextProvider): void;
173
+ };
174
+ /**
175
+ * Chat capabilities.
176
+ *
177
+ * `subscribe` taps the host's chatmessages NOTIFY stream for live
178
+ * delivery. Delivery is at-least-once: when no listener returned
179
+ * `true` during a message's lifetime, a future subscriber matching
180
+ * the filter will receive it on registration via the bus's replay
181
+ * pass. Listeners must therefore tolerate being called for an
182
+ * already-displayed message; the typical pattern is to render and
183
+ * return `true` unconditionally.
184
+ *
185
+ * `appendAssistantMessage` is a **history maintenance** helper,
186
+ * not a parallel send API. The user → agent direction still flows
187
+ * exclusively through events. Use it for recording an agent reply
188
+ * that wasn't produced by the AgentRunner's `outputChat` path or
189
+ * by `send_chat` — e.g. the result text of a direct cli-chat
190
+ * handler call after `handle.settled` resolves.
191
+ *
192
+ * User-side messages have **no** equivalent post-emit helper: doing
193
+ * a chatmessages INSERT after `events.emit` returns can race the
194
+ * input-event watcher's claim (FK row-lock vs.
195
+ * `FOR UPDATE SKIP LOCKED`) and strand the event in `pending`.
196
+ * Use {@link NewEvent.userChatMessage} for atomic insertion of a
197
+ * user chatmessage at emit time instead.
198
+ */
199
+ readonly chat: {
200
+ subscribe(filter: ChatFilter, handler: ChatHandler): Promise<ChatUnsubscribe>;
201
+ /**
202
+ * Insert a `role='assistant'` chatmessage attached to `eventId`.
203
+ * Channel is resolved via the event's
204
+ * `preferred_chat_channel_id`. Safe to call only after the
205
+ * event has settled (agentruns done / failed) — earlier calls
206
+ * can deadlock the input-event watcher's claim.
207
+ */
208
+ appendAssistantMessage(eventId: string, text: string): Promise<void>;
209
+ };
210
+ /**
211
+ * Per-event scratch directory operations. Each event the bus
212
+ * persists has a private scratch dir at `/scratch/<event-id>/`
213
+ * (bind-mounted into the agent container and every MCP container).
214
+ * Files staged via `NewEvent.files` land there at emit time; this
215
+ * surface lets plugin code *add* more files later, while an
216
+ * agentrun for that event is still running — typically from inside
217
+ * a {@link PluginTool} that fetches bytes on demand.
218
+ *
219
+ * Path resolution is host-side: callers pass the event id, the
220
+ * host writes under its own `scratchDir`. The directory is
221
+ * created if it doesn't exist yet (defensive — should always exist
222
+ * for events that were emitted with `files`).
223
+ */
224
+ readonly scratch: {
225
+ /**
226
+ * Stage one or more files into `/scratch/<eventId>/`. Same
227
+ * `EventFile` shape as `NewEvent.files`, same basename
228
+ * validation, same per-file atomic write. Returns the absolute
229
+ * agent-visible paths the files were written to (i.e.
230
+ * `/scratch/<eventId>/<name>`), in input order.
231
+ *
232
+ * Throws if a `name` fails validation (path separator, `..`,
233
+ * empty). Existing files with the same name are overwritten —
234
+ * caller is responsible for unique names.
235
+ */
236
+ addFiles(eventId: string, files: readonly EventFile[]): Promise<readonly string[]>;
237
+ };
238
+ /**
239
+ * The pino-style {@link Logger} for the plugin. Use `.info` for the
240
+ * common case and `.debug` / `.warn` / `.error` for leveled output,
241
+ * plus per-call structured fields. Already scoped to the plugin by
242
+ * the host, so records carry a stable `component` tag without the
243
+ * plugin doing anything.
244
+ */
245
+ readonly logger: Logger;
246
+ /**
247
+ * Absolute host path of the project's `data/` directory.
248
+ *
249
+ * Plugins that need to persist host-side state (auth tokens that
250
+ * must not cross into the container, on-disk caches, etc.) should
251
+ * scope their files under `<dataDir>/<plugin-id>/...` to avoid
252
+ * collisions. Anything that needs to be visible to the agent or to
253
+ * other plugins should go through the bus or workspace instead —
254
+ * `dataDir` is for plugin-private host-only state.
255
+ */
256
+ readonly dataDir: string;
257
+ /**
258
+ * Absolute host path of the project's `tmp/` directory — the
259
+ * gitignored, safe-to-wipe scratch root (it also holds the daemon
260
+ * pidfile, the postgres port file, and per-event scratch dirs).
261
+ *
262
+ * Use this for ephemeral on-disk caches that can be regenerated at
263
+ * any time (e.g. a refetchable model catalogue). Plugin-private
264
+ * state that must survive a `tmp/` wipe belongs under
265
+ * {@link dataDir} instead.
266
+ */
267
+ readonly tmpDir: string;
268
+ /**
269
+ * Cheap synchronous probe for whether the host daemon (`./cli.sh
270
+ * start`) is currently running on this machine. Inspects the
271
+ * daemon's pidfile under `<tmpDir>/.daemon.pid` and confirms the
272
+ * recorded pid is still alive — no network calls, no MCP traffic.
273
+ *
274
+ * One-shot CLI commands that need to reach MCPs (i.e. anything
275
+ * routed through `ctx.mcp`) should call this up front and exit
276
+ * early when it returns `false`: the bastion that fronts MCPs
277
+ * lives inside the daemon, so attempting an MCP call without it
278
+ * fails with a generic `fetch failed` that misleads users into
279
+ * chasing login state instead of the missing daemon.
280
+ */
281
+ readonly isDaemonRunning: () => boolean;
282
+ /**
283
+ * Read/write access to the host's YAML config (`config/config.yml`).
284
+ *
285
+ * Plugins read their own subtree (e.g. `telegram.botToken`) via
286
+ * `ctx.config.getString(...)` rather than touching `process.env`
287
+ * or parsing the file themselves. Required-vs-optional is decided
288
+ * per call: omit the default to throw on missing, or pass `null`
289
+ * to get a `string | null` back and self-disable on absence.
290
+ */
291
+ readonly config: ConfigService;
292
+ /**
293
+ * Inference-provider resolution. Lets a host-side plugin turn a
294
+ * provider key (as configured under `inference.apiKeys.<key>`) into
295
+ * the concrete `{ apiKey, npmPackage, apiEndpoint? }` it needs to
296
+ * talk to the provider directly — without re-implementing the
297
+ * models.dev + plugin-descriptor lookup the platform already owns.
298
+ *
299
+ * The motivating consumer is the memory plugin, which builds an
300
+ * embedding client against the real upstream (it does not go through
301
+ * the bastion). `resolveProvider` resolves `npmPackage` / `apiEndpoint`
302
+ * from the models.dev catalogue or a plugin's
303
+ * {@link PluginHostManifest.getModelProviders} descriptor, and reads
304
+ * `apiKey` from config. Returns `undefined` when the key isn't
305
+ * configured or doesn't resolve to a known provider.
306
+ */
307
+ readonly inference: {
308
+ resolveProvider(key: string): Promise<{
309
+ apiKey: string;
310
+ npmPackage: string;
311
+ apiEndpoint?: string;
312
+ } | undefined>;
313
+ };
314
+ /**
315
+ * Access to MCPs declared in `config/mcp.yml`. `getList()` returns
316
+ * metadata only (no connections opened). The two getters return the
317
+ * official `@modelcontextprotocol/sdk` `Client` ready to use —
318
+ * `client.callTool({ name, arguments })`, `client.listTools()`, etc.
319
+ *
320
+ * Connections are lazy and cached: returning a client from
321
+ * `getByKey` does not open one; the first method invocation on the
322
+ * client opens a connection to the bastion's MCP gateway and that
323
+ * connection is reused thereafter. The host closes every cached
324
+ * client on daemon shutdown.
325
+ *
326
+ * First-call latency note: stdio-transport MCPs (npm / pypi /
327
+ * docker-mcp-registry) are cold-spawned on the bastion's first
328
+ * request and idle-reaped after `idleTimeoutSeconds` (per-entry in
329
+ * `mcp.yml`, default 30 min). The first `callTool` / `listTools`
330
+ * after the daemon starts can therefore take seconds; subsequent
331
+ * calls within the idle window are milliseconds.
332
+ */
333
+ /**
334
+ * Shared calendar data layer. Plugins that act as calendar
335
+ * providers register a {@link CalendarProvider} during `start()`
336
+ * via `ctx.calendar.registerProvider(provider)` and then feed the
337
+ * cache through `upsertCalendar` / `addEvent`. Plugins that just
338
+ * read calendars (notifications, summaries) use the read side
339
+ * (`findEvents`, `getEvent`, `resolveDefaultCalendar`). The core
340
+ * `cal_*` agent tools dispatch through the same interface.
341
+ */
342
+ readonly calendar: CalendarApi;
343
+ /**
344
+ * Shared mail dispatch layer. Plugins that act as mail providers
345
+ * register a {@link MailProvider} during `start()` via
346
+ * `ctx.mail.registerProvider(provider)`. The core `mail_*` agent
347
+ * tools route through the registered provider by parsing the
348
+ * `<pluginId>:` prefix on every mail id.
349
+ *
350
+ * Unlike `calendar`, there is no read-side surface here — the
351
+ * core does not cache mail bodies or metadata. Pollers emit
352
+ * `mail:<plugin>` events with a prefixed `mail_id` in the payload;
353
+ * the agent reaches the body via `mail_fetch_body` on demand.
354
+ */
355
+ readonly mail: MailApi;
356
+ /**
357
+ * Read the per-mailbox style template the user (or the
358
+ * extract-style handler) wrote at `data/mail/templates/<mailbox>/<name>.json`.
359
+ * Used by mail provider implementations at send time to inject the
360
+ * user's signature + CSS into outgoing mail. Returns `undefined`
361
+ * when no template exists yet for the (mailbox, name) pair — the
362
+ * caller should fall back to sending bare HTML.
363
+ *
364
+ * `name` defaults to `"default"`. Writes flow through the core
365
+ * `mailstyle_*` agent tools, not directly through this surface —
366
+ * `ctx` exposes only the read path because that's what plugins
367
+ * (mail providers) need.
368
+ */
369
+ readonly getMailStyleTemplate: (mailbox: string, name?: string) => Promise<MailStyleTemplate | undefined>;
370
+ /**
371
+ * Observe markdown files in the workspace. The snapshot-then-diff
372
+ * pattern: `ctx.workspace.listMarkdownFiles(filter)` for the
373
+ * baseline, `ctx.workspace.onMarkdownFileUpdate(filter, cb)` for
374
+ * live transitions (added / changed / removed under the filter
375
+ * matched against frontmatter and/or workspace-relative path). See
376
+ * {@link WorkspaceWatcherApi} for the full contract.
377
+ *
378
+ * Only available inside the daemon: one-shot CLI invocations
379
+ * (`./cli.sh <plugin> …`) do not spin up the watcher, so calling
380
+ * either method from a CLI command throws synchronously. Daemon
381
+ * plugins are the intended consumer.
382
+ */
383
+ readonly workspace: WorkspaceWatcherApi;
384
+ readonly mcp: {
385
+ /**
386
+ * Snapshot of every MCP declared in `mcp.yml` as `{ key, source,
387
+ * package }`. Does not open any connections.
388
+ */
389
+ getList(): readonly McpInfo[];
390
+ /**
391
+ * Return the MCP SDK client for the entry with this yml key.
392
+ * Throws if the key is unknown. The returned client opens its
393
+ * connection lazily on its first method call.
394
+ */
395
+ getByKey(key: string): Client;
396
+ /**
397
+ * Return the MCP SDK client whose combined `package` field
398
+ * (image / package / url, per source) exactly matches `pkg`.
399
+ * The optional `source` arg narrows the search first. Throws
400
+ * if zero or multiple entries match.
401
+ */
402
+ getByPackage(pkg: string, source?: string): Client;
403
+ };
404
+ /**
405
+ * Aborts when the host daemon is no longer running — i.e. when the
406
+ * `./cli.sh start` process backing this context has stopped or
407
+ * crashed. Always present; consumers don't need to null-check.
408
+ *
409
+ * Intended use: one-shot CLI commands (`./cli.sh <plugin> ...`,
410
+ * `./cli.sh cli-chat`, etc.) race long waits (`emit().settled`,
411
+ * polling loops, interactive prompts) against this signal and exit
412
+ * cleanly when it fires instead of hanging on a postgres connection
413
+ * the dying daemon took with it.
414
+ *
415
+ * For daemon-internal contexts (cron, scheduled handlers, plugin
416
+ * `start`/tool contexts that live inside the daemon process itself)
417
+ * this signal never fires under normal operation — there is no
418
+ * "other daemon" to watch; the daemon owns its own shutdown.
419
+ *
420
+ * When fired by the CLI-side watcher, the abort reason is the
421
+ * string `"daemon-stopped"`. Consumers that need to distinguish
422
+ * daemon-down from other aborts can check {@link DaemonStoppedError}
423
+ * on rejected promises from `ctx.events.emit().settled`.
424
+ */
425
+ readonly daemonDownSignal: AbortSignal;
426
+ }
427
+ /**
428
+ * Thrown by `ctx.events.emit().settled` when the host daemon stops
429
+ * (or crashes) while a one-shot CLI command was waiting for the event
430
+ * to reach a terminal state. Catch and exit cleanly — there is no
431
+ * postgres on the other side to recover state from.
432
+ */
433
+ export declare class DaemonStoppedError extends Error {
434
+ constructor(message?: string);
435
+ }
436
+ /**
437
+ * One tool a plugin contributes to the agent. The agent reaches it
438
+ * via the bastion's `/plugin-tools/` gateway: the container fetches
439
+ * the catalog (name, description, schema), and any tool call routes
440
+ * back to the host where {@link execute} runs **inside the plugin's
441
+ * own Node process** with access to the plugin's deps.
442
+ *
443
+ * Naming: `name` is the bare tool name (e.g. `draft_response`); the
444
+ * gateway registers it under `${pluginId}_${name}` so a handler's
445
+ * `tools:` can address each plugin's tools as a group. Keep `name`
446
+ * lowercase alnum + `_` to avoid the sanitization fold the gateway
447
+ * applies on the key.
448
+ *
449
+ * `inputSchema` is raw JSON Schema — the same shape MCP tools return
450
+ * from `listTools()`. The container converts it via the AI SDK's
451
+ * `jsonSchema()` helper before handing the tool to the model.
452
+ *
453
+ * Result is returned to the agent **unwrapped**: whatever {@link
454
+ * execute} resolves with goes straight back to the model. The
455
+ * `{ ok, result | error }` envelope on the wire is purely the
456
+ * HTTP-layer error channel; agent-facing semantics match how MCP
457
+ * tools and the built-in subagent tools (`schedule_subagent`,
458
+ * `start_subagent`) feel.
459
+ */
460
+ export interface PluginTool<TInput = unknown, TOutput = unknown> {
461
+ /**
462
+ * Bare tool name (no plugin prefix). The gateway namespaces it as
463
+ * `${pluginId}_${name}` for registration, so this stays short and
464
+ * action-oriented.
465
+ */
466
+ readonly name: string;
467
+ /** Plain-English description the model sees on its tool list. */
468
+ readonly description: string;
469
+ /** Raw JSON Schema describing the `execute` args. */
470
+ readonly inputSchema: object;
471
+ /**
472
+ * Run the tool. Receives the parsed args and the call context the
473
+ * gateway resolves per invocation (full event + agentrun rows,
474
+ * plugin-scoped HostContext, scoped logger). Throw or reject to
475
+ * surface a tool error to the agent.
476
+ */
477
+ execute(args: TInput, ctx: PluginToolCallContext): Promise<TOutput>;
478
+ /**
479
+ * Curated tool groups this tool joins, in addition to its
480
+ * identity-derived auto-group (the plugin id). Every name in the
481
+ * array becomes a group that resolves to a union of every tool
482
+ * declaring it — built-in container tools, host-side core tools,
483
+ * and plugin tools all contribute through the same mechanism.
484
+ *
485
+ * Conventional names today:
486
+ *
487
+ * - `core` — the implicit default tool set every handler that
488
+ * omits `tools:` receives. Reserve this for genuinely ambient
489
+ * capabilities (long-term memory, basic chat reply, file read)
490
+ * whose `description` reads cleanly out of any context.
491
+ * - `fs`, `reflection`, … — curated bundles a handler opts into
492
+ * via `tools: fs` or `tools: core, reflection`. New names can
493
+ * be coined freely.
494
+ *
495
+ * Each name must match {@link IDENT_PATTERN} and must not be one
496
+ * of the three reserved names (`all`, `none`, `mcp`). A
497
+ * plugin tool cannot list its own plugin id (the auto-group
498
+ * already covers that). Registration fails loudly on either
499
+ * violation.
500
+ */
501
+ readonly groups?: readonly string[];
502
+ /**
503
+ * Security classification gating who may invoke this tool. Omitted
504
+ * ⇒ {@link DEFAULT_TOOL_LEVEL} (`default`, anyone). Set `approval`
505
+ * for external mutations (sending mail, …) and `privileged` for
506
+ * capabilities restricted to trusted-input runs. Enforced
507
+ * container-side in `ToolsFactory`. See {@link ToolLevel}.
508
+ */
509
+ readonly level?: ToolLevel;
510
+ }
511
+ /**
512
+ * Per-invocation context the host gateway resolves for every plugin
513
+ * tool call. The container POSTs `{ args, eventId, agentrunId }`; the
514
+ * gateway loads the full rows and the plugin's scoped {@link
515
+ * HostContext} before calling {@link PluginTool.execute}.
516
+ */
517
+ export interface PluginToolCallContext {
518
+ /**
519
+ * The originating event row. Tools dig into `payload` here for
520
+ * the entity they're acting on (e.g. mail id for `draft_response`).
521
+ */
522
+ readonly event: EventRow;
523
+ /** The agentrun row that issued the tool call. */
524
+ readonly agentrun: AgentRunRow;
525
+ /**
526
+ * The plugin's own {@link HostContext} — same instance the
527
+ * plugin's `start`/`tools` saw. Lets the tool emit follow-up
528
+ * events, reach MCPs, read config, etc.
529
+ */
530
+ readonly host: HostContext;
531
+ /** Logger child pre-scoped to `{ plugin, tool, eventId, agentrunId }`. */
532
+ readonly log: Logger;
533
+ /**
534
+ * Per-call runner context: byte budget plus a `spill` callback that
535
+ * writes oversized results into the calling event's scratch dir.
536
+ * Pass this into {@link import("./ToolRunner.js").runJsonTool} (or
537
+ * its `JsonLines`/`Text` siblings) so the runner can decide between
538
+ * inline and offload-to-scratch consistently across host and
539
+ * container.
540
+ */
541
+ readonly toolRunContext: ToolRunContext;
542
+ }
543
+ /**
544
+ * Host-side surface a plugin may declare. All fields optional; a
545
+ * plugin that's pure workspace template (no host code) leaves `host`
546
+ * out of its manifest entirely.
547
+ *
548
+ * Each function receives the {@link HostContext} so handlers close
549
+ * over `ctx` and never need to look up host services another way.
550
+ */
551
+ export interface PluginHostManifest {
552
+ /**
553
+ * Synchronous, side-effect-light setup. Runs once per process,
554
+ * **before any plugin's `start`** and before any plugin one-shot
555
+ * CLI command's `run`. Use this for module-level state that other
556
+ * plugins consume — e.g. populating an API-key constant the
557
+ * plugin's library exports — so the order in which siblings call
558
+ * each other doesn't depend on `start` finishing first.
559
+ *
560
+ * Hard rules: no async, no network, no DB, no daemons, no
561
+ * subscriptions. If you need any of those, do them in `start`.
562
+ *
563
+ * Skipped for introspective paths (`--help`, `config lint`) so a
564
+ * misconfigured file still lets the user inspect / lint.
565
+ */
566
+ prepare?(ctx: HostContext): void;
567
+ /**
568
+ * Long-running daemon (sockets, listeners, etc.). Awaited at host
569
+ * startup; should resolve once setup is done. Long-lived work
570
+ * lives on background promises the daemon owns.
571
+ *
572
+ * Runs after every plugin's {@link prepare} has completed, so
573
+ * cross-plugin module state populated in `prepare` is reliably
574
+ * visible by the time any `start` body runs.
575
+ */
576
+ start?(ctx: HostContext): Promise<void>;
577
+ /**
578
+ * Drain hook called during daemon shutdown. Awaited in reverse
579
+ * registration order (LIFO of `start`), wrapped in a `safeStop`
580
+ * so a throw is logged but does not abort the rest of the drain.
581
+ *
582
+ * Use this for plugins that own dirty in-memory state which must
583
+ * be flushed to disk before the process exits — the memory
584
+ * plugin's Orama index is the motivating case.
585
+ *
586
+ * Hard rules: must resolve within the daemon-wide drain budget
587
+ * (see `DRAINING_DEADLINE_MS` in `host/src/commands/Start.ts`).
588
+ * The shared budget covers every plugin's stop plus the postgres
589
+ * + bastion teardown, so individual stops should keep their work
590
+ * tight — a single bad flush should not eat the whole budget.
591
+ *
592
+ * Not called for one-shot CLI command runs — only on daemon
593
+ * shutdown via `PluginHost.close()`.
594
+ */
595
+ stop?(ctx: HostContext): Promise<void>;
596
+ /**
597
+ * Declarative list of {@link PluginTool}s this plugin contributes
598
+ * to the agent. Collected once, **after** the plugin's `start`
599
+ * resolves, so closures over `start`-time state are safe.
600
+ *
601
+ * Hard rules: synchronous, side-effect-free — just build and
602
+ * return the tool list. The host registers each tool under
603
+ * `${plugin.id}_${tool.name}` (after sanitization) on the bastion's
604
+ * `/plugin-tools/` gateway, and the container's tools client
605
+ * discovers them per agentrun.
606
+ *
607
+ * A plugin id used for tools must match {@link IDENT_PATTERN} and
608
+ * not collide with an MCP id — the registry enforces both at
609
+ * register time so the failure surfaces at startup, not at first
610
+ * call.
611
+ */
612
+ tools?(ctx: HostContext): readonly PluginTool[];
613
+ /**
614
+ * Supply {@link ModelMetaData} for a model the default (models.dev)
615
+ * database does not cover. The host calls this on every plugin that
616
+ * declares it — in registration order — only when a `(provider,
617
+ * model)` lookup misses the built-in database, and takes the first
618
+ * non-`undefined` result.
619
+ *
620
+ * - Return `undefined` to defer (this plugin doesn't know the model,
621
+ * or its own data isn't ready yet).
622
+ * - Throw {@link ModelNotSupported} when the plugin authoritatively
623
+ * owns `provider` and knows `model` is not supported there — the
624
+ * host treats that as a definitive "no" and stops the lookup
625
+ * without consulting further plugins.
626
+ *
627
+ * Other thrown errors are logged and the lookup continues with the
628
+ * next plugin, so a transient read failure doesn't poison results
629
+ * another plugin could still provide.
630
+ */
631
+ getModelMetaData?(ctx: HostContext, provider: string, model: string): Promise<ModelMetaData | undefined>;
632
+ /**
633
+ * Declare the inference provider(s) this plugin owns — providers the
634
+ * models.dev database doesn't cover (e.g. `featherless`). Returned
635
+ * synchronously from constants (no fetch); the host calls it during
636
+ * provider resolution to learn the provider's `npmPackage` (which
637
+ * `create*` to use, how to inject auth) and `apiEndpoint` (where the
638
+ * reverse proxy forwards). The declared `key` is what the operator
639
+ * puts under `inference.apiKeys.<key>`.
640
+ *
641
+ * On a key collision with models.dev, the models.dev entry wins; on a
642
+ * collision between two plugins, the first registered wins (logged).
643
+ */
644
+ getModelProviders?(ctx: HostContext): readonly ModelProviderDescriptor[];
645
+ /**
646
+ * Default command for the plugin's CLI root. When set, invoking
647
+ * the plugin id with no subcommand (e.g. `cli.sh cli-chat`) runs
648
+ * this command's `run`. Its `args` are also lifted onto the root.
649
+ * Useful for plugins whose primary mode is interactive (REPL-like)
650
+ * — the user shouldn't have to remember a subcommand name.
651
+ *
652
+ * Subcommands declared in {@link commands} are still available
653
+ * under the same root, so `cli.sh cli-chat send "hi"` keeps
654
+ * working alongside the bare-root invocation.
655
+ */
656
+ main?(ctx: HostContext): AnyCommandDef;
657
+ /**
658
+ * Citty commands contributed by this plugin. They are mounted
659
+ * under `subCommands[<plugin.id>]` in the root CLI by default
660
+ * (e.g. `cli.sh cli-chat send "hi"`).
661
+ */
662
+ commands?(ctx: HostContext): readonly AnyCommandDef[];
663
+ }
664
+ /**
665
+ * The full plugin manifest shape returned by {@link definePlugin}.
666
+ *
667
+ * A minimal plugin needs only `id`. Any combination of `host` and
668
+ * `workspaceTemplate` may be added — they are independent concerns.
669
+ * Plugin code never ships into the container: tools the agent can
670
+ * call are declared on {@link PluginHostManifest.tools} and execute
671
+ * host-side inside the plugin's own Node process via the bastion.
672
+ */
673
+ export interface PluginManifest {
674
+ /**
675
+ * Plugin id, matching `[a-z0-9-]+`. Used to namespace the
676
+ * plugin's CLI commands (`cli.sh <id> <subcommand>`). A plugin
677
+ * that contributes {@link PluginHostManifest.tools | tools} must
678
+ * additionally satisfy {@link IDENT_PATTERN} so the id is usable
679
+ * as a tool group name; the tools registry enforces that at
680
+ * register time.
681
+ */
682
+ readonly id: string;
683
+ /** Host-side surface (CLI commands, daemons, cronjobs, tools). */
684
+ readonly host?: PluginHostManifest;
685
+ /**
686
+ * Absolute path to a directory of files copied into
687
+ * `data/workspace/` on first install. Existing files are left
688
+ * alone — diff/merge on plugin updates is a separate concern.
689
+ * Plugins typically derive this with
690
+ * `path.join(__dirname, "..", "workspace-template")` (or the ESM
691
+ * `import.meta.url` equivalent).
692
+ */
693
+ readonly workspaceTemplate?: string;
694
+ }
695
+ /**
696
+ * Identity function used by plugins to declare their manifest:
697
+ *
698
+ * ```ts
699
+ * export default definePlugin({
700
+ * id: "cli-chat",
701
+ * host: { commands: (ctx) => [sendCommand(ctx)] },
702
+ * });
703
+ * ```
704
+ *
705
+ * Exists purely so TypeScript narrows literal types correctly without
706
+ * the caller writing a `satisfies PluginManifest` clause. Same pattern
707
+ * as Vite's `defineConfig` / Astro's `defineConfig`.
708
+ */
709
+ export declare function definePlugin<T extends PluginManifest>(plugin: T): T;