mercury-agent 0.5.0 → 0.5.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.
- package/README.md +452 -451
- package/container/Dockerfile +127 -127
- package/container/Dockerfile.base +109 -109
- package/container/Dockerfile.power +17 -17
- package/container/agent-package.json +8 -8
- package/container/build.sh +54 -54
- package/docs/ROADMAP.md +3 -4
- package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
- package/docs/auth/dashboard.md +28 -28
- package/docs/auth/overview.md +109 -109
- package/docs/auth/whatsapp.md +173 -173
- package/docs/authoring-profiles.md +174 -174
- package/docs/configuration.md +54 -54
- package/docs/container-lifecycle.md +349 -349
- package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
- package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
- package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
- package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
- package/docs/deployment.md +199 -199
- package/docs/extensions.md +375 -375
- package/docs/graceful-shutdown.md +62 -62
- package/docs/kb-distillation.md +77 -77
- package/docs/media/overview.md +140 -140
- package/docs/media/whatsapp.md +171 -171
- package/docs/memory.md +137 -137
- package/docs/permissions.md +217 -217
- package/docs/pipeline.md +228 -228
- package/docs/prd-chat-memory.md +76 -76
- package/docs/prd-config-load.md +82 -82
- package/docs/rate-limiting.md +229 -229
- package/docs/runbooks/publish-checklist.md +9 -1
- package/docs/scheduler.md +288 -288
- package/docs/setup-discord.md +100 -100
- package/docs/setup-slack.md +119 -119
- package/docs/setup-whatsapp.md +94 -94
- package/docs/subagents.md +166 -166
- package/docs/web-search.md +62 -62
- package/examples/extensions/README.md +12 -12
- package/examples/extensions/charts/index.ts +13 -13
- package/examples/extensions/charts/skill/SKILL.md +98 -98
- package/examples/extensions/gws/README.md +52 -52
- package/examples/extensions/gws/skill/SKILL.md +57 -57
- package/examples/extensions/gws/skill/references/calendar.md +101 -101
- package/examples/extensions/gws/skill/references/docs.md +65 -65
- package/examples/extensions/gws/skill/references/drive.md +79 -79
- package/examples/extensions/gws/skill/references/gmail.md +85 -85
- package/examples/extensions/gws/skill/references/sheets.md +60 -60
- package/examples/extensions/napkin/skill/SKILL.md +728 -728
- package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
- package/examples/extensions/pdf/skill/SKILL.md +314 -314
- package/examples/extensions/pdf/skill/forms.md +294 -294
- package/examples/extensions/pdf/skill/reference.md +611 -611
- package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
- package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
- package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
- package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
- package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
- package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
- package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
- package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
- package/examples/extensions/permission-guard/index.ts +65 -65
- package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
- package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
- package/examples/extensions/pinchtab/skill/references/api.md +297 -297
- package/examples/extensions/pinchtab/skill/references/env.md +45 -45
- package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
- package/examples/extensions/tradestation/host/refresh.ts +102 -102
- package/examples/extensions/tradestation/index.ts +153 -153
- package/examples/extensions/tradestation/skill/SKILL.md +67 -67
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
- package/examples/extensions/voice-transcribe/requirements.txt +8 -8
- package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
- package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
- package/examples/extensions/yahoo-mail/cli/package.json +13 -13
- package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
- package/package.json +106 -106
- package/resources/agents/explore.md +50 -50
- package/resources/agents/worker.md +24 -24
- package/resources/connection-env-vars.json +25 -25
- package/resources/pi-extensions/subagent/agents.ts +126 -126
- package/resources/pi-extensions/subagent/index.ts +964 -964
- package/resources/profiles/coding/AGENTS.md +43 -43
- package/resources/profiles/coding/mercury-profile.yaml +15 -15
- package/resources/profiles/general/AGENTS.md +31 -31
- package/resources/profiles/general/mercury-profile.yaml +15 -15
- package/resources/profiles/research/AGENTS.md +40 -40
- package/resources/profiles/research/mercury-profile.yaml +15 -15
- package/resources/skills/config/SKILL.md +25 -25
- package/resources/skills/context/SKILL.md +33 -33
- package/resources/skills/conversation-recap/SKILL.md +19 -19
- package/resources/skills/mutes/SKILL.md +31 -31
- package/resources/skills/permissions/SKILL.md +19 -19
- package/resources/skills/preferences/SKILL.md +31 -31
- package/resources/skills/recall/SKILL.md +24 -24
- package/resources/skills/roles/SKILL.md +18 -18
- package/resources/skills/spaces/SKILL.md +18 -18
- package/resources/skills/tasks/SKILL.md +45 -45
- package/resources/templates/AGENTS.md +157 -157
- package/resources/templates/env.template +38 -38
- package/resources/templates/mercury.example.yaml +99 -99
- package/src/agent/container-entry.ts +1 -1
- package/src/agent/container-runner.ts +1345 -1346
- package/src/cli/mercury.ts +39 -7
- package/src/cli/mrctl.ts +636 -636
- package/src/config-file.ts +540 -540
- package/src/config.ts +339 -339
- package/src/core/api.ts +125 -125
- package/src/core/caller-token.ts +101 -101
- package/src/core/permissions.ts +228 -228
- package/src/core/profiles.ts +271 -271
- package/src/core/routes/capability.ts +70 -70
- package/src/core/routes/index.ts +15 -15
- package/src/core/runtime.ts +1530 -1530
- package/src/dashboard/index.html +729 -729
- package/src/extensions/api.ts +273 -273
- package/src/extensions/loader.ts +286 -286
- package/src/extensions/types.ts +517 -517
- package/src/main.ts +605 -605
- package/docs/pending-updates/applicative-profiles.md +0 -15
- package/docs/pending-updates/caller-bound-capability-token.md +0 -15
- package/docs/pending-updates/dm-auto-space.md +0 -15
- /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
- /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
- /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
package/src/extensions/types.ts
CHANGED
|
@@ -1,517 +1,517 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mercury Extension System — Type Definitions
|
|
3
|
-
*
|
|
4
|
-
* All types for the extension API, events, metadata, and supporting structures.
|
|
5
|
-
* No runtime code — types only.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { ContainerError } from "../agent/container-error.js";
|
|
9
|
-
import type { ModelCapabilityKey } from "../agent/model-capabilities.js";
|
|
10
|
-
import type { AppConfig } from "../config.js";
|
|
11
|
-
import type { Logger } from "../logger.js";
|
|
12
|
-
import type { Db } from "../storage/db.js";
|
|
13
|
-
import type { EgressFile, MessageAttachment } from "../types.js";
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Extension context — passed to event handlers and job runners
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
/** Context available to extension hooks and jobs at runtime. */
|
|
20
|
-
export interface MercuryExtensionContext {
|
|
21
|
-
/** Database access. */
|
|
22
|
-
readonly db: Db;
|
|
23
|
-
/** Mercury configuration. */
|
|
24
|
-
readonly config: AppConfig;
|
|
25
|
-
/** Logger scoped to the extension. */
|
|
26
|
-
readonly log: Logger;
|
|
27
|
-
/**
|
|
28
|
-
* True if the caller has the permission in this space (built-in or extension-registered).
|
|
29
|
-
* Used by extensions in hooks to mirror container RBAC.
|
|
30
|
-
*/
|
|
31
|
-
hasCallerPermission(
|
|
32
|
-
spaceId: string,
|
|
33
|
-
callerId: string,
|
|
34
|
-
permission: string,
|
|
35
|
-
): boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Events
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
/** All lifecycle events an extension can subscribe to. */
|
|
43
|
-
export interface MercuryEvents {
|
|
44
|
-
/** Fired after all extensions are loaded and the runtime is ready. */
|
|
45
|
-
startup: StartupEvent;
|
|
46
|
-
/** Fired when Mercury is shutting down. */
|
|
47
|
-
shutdown: ShutdownEvent;
|
|
48
|
-
/** Fired when a space workspace directory is created or ensured. */
|
|
49
|
-
workspace_init: WorkspaceInitEvent;
|
|
50
|
-
/** Fired just before a container is spawned for a message. */
|
|
51
|
-
before_container: BeforeContainerEvent;
|
|
52
|
-
/** Fired after a container finishes (success or error). */
|
|
53
|
-
after_container: AfterContainerEvent;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type StartupEvent = Record<string, never>;
|
|
57
|
-
|
|
58
|
-
export type ShutdownEvent = Record<string, never>;
|
|
59
|
-
|
|
60
|
-
export interface WorkspaceInitEvent {
|
|
61
|
-
/** The space this workspace belongs to. */
|
|
62
|
-
spaceId: string;
|
|
63
|
-
/** Absolute path to the workspace directory. */
|
|
64
|
-
workspace: string;
|
|
65
|
-
/** Container-relative path to the workspace (e.g. /spaces/main). */
|
|
66
|
-
containerWorkspace: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface BeforeContainerEvent {
|
|
70
|
-
/** The space the message belongs to. */
|
|
71
|
-
spaceId: string;
|
|
72
|
-
/** The user's prompt. */
|
|
73
|
-
prompt: string;
|
|
74
|
-
/** Platform-specific caller identifier. */
|
|
75
|
-
callerId: string;
|
|
76
|
-
/** Absolute path to the space workspace. */
|
|
77
|
-
workspace: string;
|
|
78
|
-
/** Container-relative path to the workspace (e.g. /spaces/main). */
|
|
79
|
-
containerWorkspace: string;
|
|
80
|
-
/** Incoming attachments (e.g. voice, images), if any. */
|
|
81
|
-
attachments?: MessageAttachment[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface AfterContainerEvent {
|
|
85
|
-
/** The space the message belongs to. */
|
|
86
|
-
spaceId: string;
|
|
87
|
-
/** Absolute path to the space workspace on the host. */
|
|
88
|
-
workspace: string;
|
|
89
|
-
/** Platform user id for this turn (same as container `CALLER_ID`). */
|
|
90
|
-
callerId: string;
|
|
91
|
-
/** User prompt for this turn (includes any `promptAppend` from `before_container`). */
|
|
92
|
-
prompt: string;
|
|
93
|
-
/** The agent's reply (empty string on error). */
|
|
94
|
-
reply: string;
|
|
95
|
-
/** How long the container ran, in milliseconds. */
|
|
96
|
-
durationMs: number;
|
|
97
|
-
/** Present if the container failed. */
|
|
98
|
-
error?: ContainerError;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
// Event return types — mutations hooks can apply
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Return value from a `before_container` handler.
|
|
107
|
-
* All fields are optional — return only what you want to mutate.
|
|
108
|
-
*/
|
|
109
|
-
export interface BeforeContainerResult {
|
|
110
|
-
/** Extra text appended to the system prompt inside the container. */
|
|
111
|
-
systemPrompt?: string;
|
|
112
|
-
/** Text appended to the user prompt (newline-joined across handlers). */
|
|
113
|
-
promptAppend?: string;
|
|
114
|
-
/** Extra environment variables passed to the container. */
|
|
115
|
-
env?: Record<string, string>;
|
|
116
|
-
/** If set, blocks the container from running entirely. */
|
|
117
|
-
block?: { reason: string };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Return value from an `after_container` handler.
|
|
122
|
-
* All fields are optional — return only what you want to mutate.
|
|
123
|
-
*/
|
|
124
|
-
export interface AfterContainerResult {
|
|
125
|
-
/** Replace the agent's reply. */
|
|
126
|
-
reply?: string;
|
|
127
|
-
/** If true, suppress the reply (don't send it to the chat). */
|
|
128
|
-
suppress?: boolean;
|
|
129
|
-
/**
|
|
130
|
-
* Extra egress files to attach (e.g. host-generated audio). Appended after
|
|
131
|
-
* container outbox files; order is concatenation of handlers in registration order.
|
|
132
|
-
*/
|
|
133
|
-
files?: EgressFile[];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Maps event names to their allowed return types. */
|
|
137
|
-
export type EventResult<E extends keyof MercuryEvents> =
|
|
138
|
-
E extends "before_container"
|
|
139
|
-
? BeforeContainerResult | undefined
|
|
140
|
-
: E extends "after_container"
|
|
141
|
-
? AfterContainerResult | undefined
|
|
142
|
-
: undefined;
|
|
143
|
-
|
|
144
|
-
/** A typed event handler for a specific event. */
|
|
145
|
-
export type EventHandler<E extends keyof MercuryEvents> = (
|
|
146
|
-
event: MercuryEvents[E],
|
|
147
|
-
ctx: MercuryExtensionContext,
|
|
148
|
-
) => Promise<EventResult<E>>;
|
|
149
|
-
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
// Jobs
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
/** Definition for a background job registered by an extension. */
|
|
155
|
-
export interface JobDef {
|
|
156
|
-
/** Run on a fixed interval (milliseconds). Mutually exclusive with `cron`. */
|
|
157
|
-
interval?: number;
|
|
158
|
-
/** Run on a cron schedule (5-field expression). Mutually exclusive with `interval`. */
|
|
159
|
-
cron?: string;
|
|
160
|
-
/** The function to execute on each tick. */
|
|
161
|
-
run: (ctx: MercuryExtensionContext) => Promise<void>;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
// Config
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
|
|
168
|
-
/** Definition for a per-space config key registered by an extension. */
|
|
169
|
-
export interface ConfigDef {
|
|
170
|
-
/** Human-readable description shown in `mrctl config get`. */
|
|
171
|
-
description: string;
|
|
172
|
-
/** Default value when not explicitly set. */
|
|
173
|
-
default: string;
|
|
174
|
-
/** Optional validator — return true if value is acceptable. */
|
|
175
|
-
validate?: (value: string) => boolean;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
// Widgets
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
|
|
182
|
-
/** Definition for a dashboard widget registered by an extension. */
|
|
183
|
-
export interface WidgetDef {
|
|
184
|
-
/** Display label shown in the dashboard. */
|
|
185
|
-
label: string;
|
|
186
|
-
/** Render function returning an HTML fragment. */
|
|
187
|
-
render: (ctx: MercuryExtensionContext) => string;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
// Capabilities — host-side broker actions invoked from the container
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
|
-
|
|
194
|
-
/** A single broker request, dispatched from `POST /api/capability/:name/:action`. */
|
|
195
|
-
export interface CapabilityRequest {
|
|
196
|
-
/** Capability name (matches the required permission). */
|
|
197
|
-
name: string;
|
|
198
|
-
/** Sub-action within the capability (e.g. "book", "cancel"). */
|
|
199
|
-
action: string;
|
|
200
|
-
/**
|
|
201
|
-
* The authoritative, token-derived caller id. Trustworthy for ownership
|
|
202
|
-
* checks — NOT a container-supplied argument.
|
|
203
|
-
*/
|
|
204
|
-
callerId: string;
|
|
205
|
-
/** The space the call was made from. */
|
|
206
|
-
spaceId: string;
|
|
207
|
-
/** Parsed JSON request body (or null). */
|
|
208
|
-
body: unknown;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Result returned by a capability handler. */
|
|
212
|
-
export interface CapabilityResult {
|
|
213
|
-
/** HTTP status to return (default 200). */
|
|
214
|
-
status?: number;
|
|
215
|
-
/** JSON-serializable response payload. */
|
|
216
|
-
data: unknown;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Host-side handler for a capability. Runs in the Mercury host process with the
|
|
221
|
-
* full extension context (credentials in host storage stay on the host). The
|
|
222
|
-
* container never sees secrets — only the returned `data`.
|
|
223
|
-
*/
|
|
224
|
-
export type CapabilityHandler = (
|
|
225
|
-
req: CapabilityRequest,
|
|
226
|
-
ctx: MercuryExtensionContext,
|
|
227
|
-
) => Promise<CapabilityResult>;
|
|
228
|
-
|
|
229
|
-
// ---------------------------------------------------------------------------
|
|
230
|
-
// Store
|
|
231
|
-
// ---------------------------------------------------------------------------
|
|
232
|
-
|
|
233
|
-
/** Scoped key-value store for extension-private persistent state. */
|
|
234
|
-
export interface ExtensionStore {
|
|
235
|
-
/** Get a value by key, or null if not set. */
|
|
236
|
-
get(key: string): string | null;
|
|
237
|
-
/** Set a key-value pair (upsert). */
|
|
238
|
-
set(key: string, value: string): void;
|
|
239
|
-
/** Delete a key. Returns true if the key existed. */
|
|
240
|
-
delete(key: string): boolean;
|
|
241
|
-
/** List all key-value pairs for this extension. */
|
|
242
|
-
list(): Array<{ key: string; value: string }>;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ---------------------------------------------------------------------------
|
|
246
|
-
// Extension API — the object passed to each extension's setup function
|
|
247
|
-
// ---------------------------------------------------------------------------
|
|
248
|
-
|
|
249
|
-
/** The API surface available to extensions during setup. */
|
|
250
|
-
export interface MercuryExtensionAPI {
|
|
251
|
-
/** The extension's name (directory name). */
|
|
252
|
-
readonly name: string;
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Declare a CLI tool to install in the derived container image.
|
|
256
|
-
* Can only be called once per extension.
|
|
257
|
-
*
|
|
258
|
-
* @example
|
|
259
|
-
* mercury.cli({ name: "napkin", install: "bun add -g napkin-ai" });
|
|
260
|
-
*/
|
|
261
|
-
cli(opts: CliDef): void;
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Register this extension's permission and set which roles get it by default.
|
|
265
|
-
* The permission name is the extension name. Can only be called once.
|
|
266
|
-
*
|
|
267
|
-
* @example
|
|
268
|
-
* mercury.permission({ defaultRoles: ["admin", "member"] });
|
|
269
|
-
*/
|
|
270
|
-
permission(opts: PermissionDef): void;
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Declare an environment variable this extension needs.
|
|
274
|
-
* Only injected into containers when the caller has permission for this extension.
|
|
275
|
-
* Can be called multiple times for multiple env vars.
|
|
276
|
-
*
|
|
277
|
-
* @example
|
|
278
|
-
* mercury.env({ from: "MERCURY_GH_TOKEN" }); // injected as GH_TOKEN
|
|
279
|
-
* mercury.env({ from: "MERCURY_GH_TOKEN", as: "GITHUB_TOKEN" }); // custom name
|
|
280
|
-
*/
|
|
281
|
-
env(def: EnvDef): void;
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Register a skill directory containing a SKILL.md for agent discovery.
|
|
285
|
-
* Path is relative to the extension directory.
|
|
286
|
-
*
|
|
287
|
-
* @example
|
|
288
|
-
* mercury.skill("./skill");
|
|
289
|
-
*/
|
|
290
|
-
skill(relativePath: string): void;
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Declare capability requirements for this extension's skill / CLI workflows.
|
|
294
|
-
* If no model leg in the chain satisfies all listed capabilities, the extension
|
|
295
|
-
* skill is not installed and a startup warning is logged.
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* mercury.requires(["tools"]);
|
|
299
|
-
*/
|
|
300
|
-
requires(capabilities: ModelCapabilityKey[]): void;
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Subscribe to a lifecycle event.
|
|
304
|
-
*
|
|
305
|
-
* @example
|
|
306
|
-
* mercury.on("workspace_init", async (event, ctx) => {
|
|
307
|
-
* mkdirSync(join(event.workspace, "my-dir"), { recursive: true });
|
|
308
|
-
* });
|
|
309
|
-
*/
|
|
310
|
-
on<E extends keyof MercuryEvents>(event: E, handler: EventHandler<E>): void;
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Register a background job that runs on the host.
|
|
314
|
-
*
|
|
315
|
-
* @example
|
|
316
|
-
* mercury.job("cleanup", { interval: 3600_000, run: async (ctx) => { ... } });
|
|
317
|
-
*/
|
|
318
|
-
job(name: string, def: JobDef): void;
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Register a per-space config key. Namespaced to the extension automatically.
|
|
322
|
-
*
|
|
323
|
-
* @example
|
|
324
|
-
* mercury.config("enabled", { description: "Enable for this group", default: "true" });
|
|
325
|
-
* // Registers as "napkin.enabled" in the DB
|
|
326
|
-
*/
|
|
327
|
-
config(key: string, def: ConfigDef): void;
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Register a dashboard widget.
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* mercury.widget({ label: "Status", render: (ctx) => "<p>OK</p>" });
|
|
334
|
-
*/
|
|
335
|
-
widget(def: WidgetDef): void;
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Declare this extension as a personal service connection. Additive on top
|
|
339
|
-
* of cli/env/skill; extensions that never call this are unaffected. Can
|
|
340
|
-
* only be called once per extension.
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* mercury.connection({
|
|
344
|
-
* displayName: "Google Workspace",
|
|
345
|
-
* category: "workspace",
|
|
346
|
-
* authType: "credentials-file",
|
|
347
|
-
* credentialEnvVar: "MERCURY_GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE",
|
|
348
|
-
* });
|
|
349
|
-
*/
|
|
350
|
-
connection(def: ConnectionDef): void;
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Register a host-side capability handler, invoked from the container via
|
|
354
|
-
* `mrctl capability <name> <action> <json>` → `POST /api/capability/:name/:action`.
|
|
355
|
-
* The caller must hold the `<name>` permission. Credentials used by the
|
|
356
|
-
* handler stay on the host and never enter the agent container.
|
|
357
|
-
*
|
|
358
|
-
* @example
|
|
359
|
-
* mercury.capability("barber", async (req, ctx) => {
|
|
360
|
-
* if (req.action === "book") return { data: await book(req.callerId, req.body) };
|
|
361
|
-
* return { status: 400, data: { error: "unknown action" } };
|
|
362
|
-
* });
|
|
363
|
-
*/
|
|
364
|
-
capability(name: string, handler: CapabilityHandler): void;
|
|
365
|
-
|
|
366
|
-
/** Scoped key-value store for persistent extension state. */
|
|
367
|
-
readonly store: ExtensionStore;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// ---------------------------------------------------------------------------
|
|
371
|
-
// CLI + Permission definitions
|
|
372
|
-
// ---------------------------------------------------------------------------
|
|
373
|
-
|
|
374
|
-
/** Declaration for a CLI tool to install in the container image. */
|
|
375
|
-
export interface CliDef {
|
|
376
|
-
/** CLI binary name (should match the extension name). */
|
|
377
|
-
name: string;
|
|
378
|
-
/** Shell command to install the CLI (runs as a Dockerfile RUN step). */
|
|
379
|
-
install: string;
|
|
380
|
-
/**
|
|
381
|
-
* Absolute path to a local script to copy into `/usr/local/bin/{name}`.
|
|
382
|
-
* Set by the extension loader from the extension's directory.
|
|
383
|
-
*/
|
|
384
|
-
bin?: string;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/** Permission configuration for an extension. */
|
|
388
|
-
export interface PermissionDef {
|
|
389
|
-
/** Roles that should have this permission by default. */
|
|
390
|
-
defaultRoles: string[];
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/** Environment variable declaration for an extension. */
|
|
394
|
-
export interface EnvDef {
|
|
395
|
-
/** Env var name as it appears in .env (e.g. "MERCURY_GH_TOKEN"). */
|
|
396
|
-
from: string;
|
|
397
|
-
/** Env var name inside the container (e.g. "GH_TOKEN"). Defaults to `from` with MERCURY_ prefix stripped. */
|
|
398
|
-
as?: string;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// ---------------------------------------------------------------------------
|
|
402
|
-
// Connection metadata — first-class personal service connections
|
|
403
|
-
// ---------------------------------------------------------------------------
|
|
404
|
-
|
|
405
|
-
/** Runtime status of a personal service connection. */
|
|
406
|
-
export type ConnectionStatus =
|
|
407
|
-
| "connected"
|
|
408
|
-
| "needs-reauth"
|
|
409
|
-
| "broken"
|
|
410
|
-
| "unknown";
|
|
411
|
-
|
|
412
|
-
/** Closed category taxonomy for connections (v1 — tags deferred). */
|
|
413
|
-
export type ConnectionCategory =
|
|
414
|
-
| "email"
|
|
415
|
-
| "drive"
|
|
416
|
-
| "calendar"
|
|
417
|
-
| "finance"
|
|
418
|
-
| "messaging"
|
|
419
|
-
| "docs"
|
|
420
|
-
| "workspace"
|
|
421
|
-
| "other";
|
|
422
|
-
|
|
423
|
-
/** How the connection authenticates. */
|
|
424
|
-
export type ConnectionAuthType =
|
|
425
|
-
| "oauth2"
|
|
426
|
-
| "apikey"
|
|
427
|
-
| "app-password"
|
|
428
|
-
| "credentials-file"
|
|
429
|
-
| "form"
|
|
430
|
-
| "custom";
|
|
431
|
-
|
|
432
|
-
/** Result of an on-request `statusCheck` probe. */
|
|
433
|
-
export interface ConnectionStatusResult {
|
|
434
|
-
status: ConnectionStatus;
|
|
435
|
-
/** Optional one-line explanation surfaced to the UI (e.g. "token expired 2h ago"). */
|
|
436
|
-
detail?: string;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Declaration for a personal service connection registered via
|
|
441
|
-
* `mercury.connection()`. At least one of `credentialEnvVar` or `statusCheck`
|
|
442
|
-
* must be set — enforced at load.
|
|
443
|
-
*/
|
|
444
|
-
export interface ConnectionDef {
|
|
445
|
-
/** User-facing name (e.g. "Google Workspace"). */
|
|
446
|
-
displayName: string;
|
|
447
|
-
/** Optional icon URL surfaced to the console. */
|
|
448
|
-
iconUrl?: string;
|
|
449
|
-
/** Category used for grouping in the UI. */
|
|
450
|
-
category: ConnectionCategory;
|
|
451
|
-
/** Auth mechanism used by the upstream service. */
|
|
452
|
-
authType: ConnectionAuthType;
|
|
453
|
-
/**
|
|
454
|
-
* Optional. If set, MUST match one of the env var names declared via
|
|
455
|
-
* `mercury.env({ from })`. Validated at load. Used for the default
|
|
456
|
-
* presence-check status when `statusCheck` is absent. Extensions that store
|
|
457
|
-
* credentials in `extension_state` (e.g. tradestation OAuth) omit this and
|
|
458
|
-
* rely on `statusCheck`.
|
|
459
|
-
*/
|
|
460
|
-
credentialEnvVar?: string;
|
|
461
|
-
/** Optional OAuth-style scope list (informational, surfaced to the UI). */
|
|
462
|
-
scopes?: string[];
|
|
463
|
-
/**
|
|
464
|
-
* Optional probe. Runs on the host with the full extension context. The
|
|
465
|
-
* caller enforces a 5-second timeout. Must be side-effect free.
|
|
466
|
-
*/
|
|
467
|
-
statusCheck?: (
|
|
468
|
-
ctx: MercuryExtensionContext,
|
|
469
|
-
) => Promise<ConnectionStatusResult>;
|
|
470
|
-
/**
|
|
471
|
-
* If true, this connection accesses personal/sensitive data (email, finance,
|
|
472
|
-
* authenticated browser). In group-linked spaces, the runtime guard requires
|
|
473
|
-
* explicit admin enablement and per-request confirmation before proceeding.
|
|
474
|
-
*/
|
|
475
|
-
sensitive?: boolean;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// ---------------------------------------------------------------------------
|
|
479
|
-
// Extension metadata — collected after running the setup function
|
|
480
|
-
// ---------------------------------------------------------------------------
|
|
481
|
-
|
|
482
|
-
/** Fully resolved metadata for a loaded extension. */
|
|
483
|
-
export interface ExtensionMeta {
|
|
484
|
-
/** Extension name (directory name). */
|
|
485
|
-
name: string;
|
|
486
|
-
/** Absolute path to the extension directory. */
|
|
487
|
-
dir: string;
|
|
488
|
-
/** CLI declarations (may be empty). */
|
|
489
|
-
clis: CliDef[];
|
|
490
|
-
/** Permission configuration, if any. */
|
|
491
|
-
permission?: PermissionDef;
|
|
492
|
-
/** If set, skill install requires these capabilities on at least one model chain leg. */
|
|
493
|
-
requires?: ModelCapabilityKey[];
|
|
494
|
-
/** Absolute path to the skill directory, if declared. */
|
|
495
|
-
skillDir?: string;
|
|
496
|
-
/** Event handlers keyed by event name. */
|
|
497
|
-
hooks: Map<keyof MercuryEvents, EventHandler<keyof MercuryEvents>[]>;
|
|
498
|
-
/** Background jobs keyed by job name. */
|
|
499
|
-
jobs: Map<string, JobDef>;
|
|
500
|
-
/** Config key definitions keyed by local key (not namespaced). */
|
|
501
|
-
configs: Map<string, ConfigDef>;
|
|
502
|
-
/** Dashboard widgets. */
|
|
503
|
-
widgets: WidgetDef[];
|
|
504
|
-
/** Host-side capability handlers, keyed by capability name. */
|
|
505
|
-
capabilities: Map<string, CapabilityHandler>;
|
|
506
|
-
/** Declared environment variables. */
|
|
507
|
-
envVars: EnvDef[];
|
|
508
|
-
/** Personal service connection metadata, if declared via `mercury.connection()`. */
|
|
509
|
-
connection?: ConnectionDef;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// ---------------------------------------------------------------------------
|
|
513
|
-
// Extension setup function signature
|
|
514
|
-
// ---------------------------------------------------------------------------
|
|
515
|
-
|
|
516
|
-
/** The default export every extension must provide. */
|
|
517
|
-
export type ExtensionSetupFn = (api: MercuryExtensionAPI) => void;
|
|
1
|
+
/**
|
|
2
|
+
* Mercury Extension System — Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* All types for the extension API, events, metadata, and supporting structures.
|
|
5
|
+
* No runtime code — types only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ContainerError } from "../agent/container-error.js";
|
|
9
|
+
import type { ModelCapabilityKey } from "../agent/model-capabilities.js";
|
|
10
|
+
import type { AppConfig } from "../config.js";
|
|
11
|
+
import type { Logger } from "../logger.js";
|
|
12
|
+
import type { Db } from "../storage/db.js";
|
|
13
|
+
import type { EgressFile, MessageAttachment } from "../types.js";
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Extension context — passed to event handlers and job runners
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/** Context available to extension hooks and jobs at runtime. */
|
|
20
|
+
export interface MercuryExtensionContext {
|
|
21
|
+
/** Database access. */
|
|
22
|
+
readonly db: Db;
|
|
23
|
+
/** Mercury configuration. */
|
|
24
|
+
readonly config: AppConfig;
|
|
25
|
+
/** Logger scoped to the extension. */
|
|
26
|
+
readonly log: Logger;
|
|
27
|
+
/**
|
|
28
|
+
* True if the caller has the permission in this space (built-in or extension-registered).
|
|
29
|
+
* Used by extensions in hooks to mirror container RBAC.
|
|
30
|
+
*/
|
|
31
|
+
hasCallerPermission(
|
|
32
|
+
spaceId: string,
|
|
33
|
+
callerId: string,
|
|
34
|
+
permission: string,
|
|
35
|
+
): boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Events
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
/** All lifecycle events an extension can subscribe to. */
|
|
43
|
+
export interface MercuryEvents {
|
|
44
|
+
/** Fired after all extensions are loaded and the runtime is ready. */
|
|
45
|
+
startup: StartupEvent;
|
|
46
|
+
/** Fired when Mercury is shutting down. */
|
|
47
|
+
shutdown: ShutdownEvent;
|
|
48
|
+
/** Fired when a space workspace directory is created or ensured. */
|
|
49
|
+
workspace_init: WorkspaceInitEvent;
|
|
50
|
+
/** Fired just before a container is spawned for a message. */
|
|
51
|
+
before_container: BeforeContainerEvent;
|
|
52
|
+
/** Fired after a container finishes (success or error). */
|
|
53
|
+
after_container: AfterContainerEvent;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type StartupEvent = Record<string, never>;
|
|
57
|
+
|
|
58
|
+
export type ShutdownEvent = Record<string, never>;
|
|
59
|
+
|
|
60
|
+
export interface WorkspaceInitEvent {
|
|
61
|
+
/** The space this workspace belongs to. */
|
|
62
|
+
spaceId: string;
|
|
63
|
+
/** Absolute path to the workspace directory. */
|
|
64
|
+
workspace: string;
|
|
65
|
+
/** Container-relative path to the workspace (e.g. /spaces/main). */
|
|
66
|
+
containerWorkspace: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface BeforeContainerEvent {
|
|
70
|
+
/** The space the message belongs to. */
|
|
71
|
+
spaceId: string;
|
|
72
|
+
/** The user's prompt. */
|
|
73
|
+
prompt: string;
|
|
74
|
+
/** Platform-specific caller identifier. */
|
|
75
|
+
callerId: string;
|
|
76
|
+
/** Absolute path to the space workspace. */
|
|
77
|
+
workspace: string;
|
|
78
|
+
/** Container-relative path to the workspace (e.g. /spaces/main). */
|
|
79
|
+
containerWorkspace: string;
|
|
80
|
+
/** Incoming attachments (e.g. voice, images), if any. */
|
|
81
|
+
attachments?: MessageAttachment[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface AfterContainerEvent {
|
|
85
|
+
/** The space the message belongs to. */
|
|
86
|
+
spaceId: string;
|
|
87
|
+
/** Absolute path to the space workspace on the host. */
|
|
88
|
+
workspace: string;
|
|
89
|
+
/** Platform user id for this turn (same as container `CALLER_ID`). */
|
|
90
|
+
callerId: string;
|
|
91
|
+
/** User prompt for this turn (includes any `promptAppend` from `before_container`). */
|
|
92
|
+
prompt: string;
|
|
93
|
+
/** The agent's reply (empty string on error). */
|
|
94
|
+
reply: string;
|
|
95
|
+
/** How long the container ran, in milliseconds. */
|
|
96
|
+
durationMs: number;
|
|
97
|
+
/** Present if the container failed. */
|
|
98
|
+
error?: ContainerError;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Event return types — mutations hooks can apply
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Return value from a `before_container` handler.
|
|
107
|
+
* All fields are optional — return only what you want to mutate.
|
|
108
|
+
*/
|
|
109
|
+
export interface BeforeContainerResult {
|
|
110
|
+
/** Extra text appended to the system prompt inside the container. */
|
|
111
|
+
systemPrompt?: string;
|
|
112
|
+
/** Text appended to the user prompt (newline-joined across handlers). */
|
|
113
|
+
promptAppend?: string;
|
|
114
|
+
/** Extra environment variables passed to the container. */
|
|
115
|
+
env?: Record<string, string>;
|
|
116
|
+
/** If set, blocks the container from running entirely. */
|
|
117
|
+
block?: { reason: string };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Return value from an `after_container` handler.
|
|
122
|
+
* All fields are optional — return only what you want to mutate.
|
|
123
|
+
*/
|
|
124
|
+
export interface AfterContainerResult {
|
|
125
|
+
/** Replace the agent's reply. */
|
|
126
|
+
reply?: string;
|
|
127
|
+
/** If true, suppress the reply (don't send it to the chat). */
|
|
128
|
+
suppress?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Extra egress files to attach (e.g. host-generated audio). Appended after
|
|
131
|
+
* container outbox files; order is concatenation of handlers in registration order.
|
|
132
|
+
*/
|
|
133
|
+
files?: EgressFile[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Maps event names to their allowed return types. */
|
|
137
|
+
export type EventResult<E extends keyof MercuryEvents> =
|
|
138
|
+
E extends "before_container"
|
|
139
|
+
? BeforeContainerResult | undefined
|
|
140
|
+
: E extends "after_container"
|
|
141
|
+
? AfterContainerResult | undefined
|
|
142
|
+
: undefined;
|
|
143
|
+
|
|
144
|
+
/** A typed event handler for a specific event. */
|
|
145
|
+
export type EventHandler<E extends keyof MercuryEvents> = (
|
|
146
|
+
event: MercuryEvents[E],
|
|
147
|
+
ctx: MercuryExtensionContext,
|
|
148
|
+
) => Promise<EventResult<E>>;
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Jobs
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/** Definition for a background job registered by an extension. */
|
|
155
|
+
export interface JobDef {
|
|
156
|
+
/** Run on a fixed interval (milliseconds). Mutually exclusive with `cron`. */
|
|
157
|
+
interval?: number;
|
|
158
|
+
/** Run on a cron schedule (5-field expression). Mutually exclusive with `interval`. */
|
|
159
|
+
cron?: string;
|
|
160
|
+
/** The function to execute on each tick. */
|
|
161
|
+
run: (ctx: MercuryExtensionContext) => Promise<void>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Config
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
/** Definition for a per-space config key registered by an extension. */
|
|
169
|
+
export interface ConfigDef {
|
|
170
|
+
/** Human-readable description shown in `mrctl config get`. */
|
|
171
|
+
description: string;
|
|
172
|
+
/** Default value when not explicitly set. */
|
|
173
|
+
default: string;
|
|
174
|
+
/** Optional validator — return true if value is acceptable. */
|
|
175
|
+
validate?: (value: string) => boolean;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Widgets
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
/** Definition for a dashboard widget registered by an extension. */
|
|
183
|
+
export interface WidgetDef {
|
|
184
|
+
/** Display label shown in the dashboard. */
|
|
185
|
+
label: string;
|
|
186
|
+
/** Render function returning an HTML fragment. */
|
|
187
|
+
render: (ctx: MercuryExtensionContext) => string;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Capabilities — host-side broker actions invoked from the container
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/** A single broker request, dispatched from `POST /api/capability/:name/:action`. */
|
|
195
|
+
export interface CapabilityRequest {
|
|
196
|
+
/** Capability name (matches the required permission). */
|
|
197
|
+
name: string;
|
|
198
|
+
/** Sub-action within the capability (e.g. "book", "cancel"). */
|
|
199
|
+
action: string;
|
|
200
|
+
/**
|
|
201
|
+
* The authoritative, token-derived caller id. Trustworthy for ownership
|
|
202
|
+
* checks — NOT a container-supplied argument.
|
|
203
|
+
*/
|
|
204
|
+
callerId: string;
|
|
205
|
+
/** The space the call was made from. */
|
|
206
|
+
spaceId: string;
|
|
207
|
+
/** Parsed JSON request body (or null). */
|
|
208
|
+
body: unknown;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Result returned by a capability handler. */
|
|
212
|
+
export interface CapabilityResult {
|
|
213
|
+
/** HTTP status to return (default 200). */
|
|
214
|
+
status?: number;
|
|
215
|
+
/** JSON-serializable response payload. */
|
|
216
|
+
data: unknown;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Host-side handler for a capability. Runs in the Mercury host process with the
|
|
221
|
+
* full extension context (credentials in host storage stay on the host). The
|
|
222
|
+
* container never sees secrets — only the returned `data`.
|
|
223
|
+
*/
|
|
224
|
+
export type CapabilityHandler = (
|
|
225
|
+
req: CapabilityRequest,
|
|
226
|
+
ctx: MercuryExtensionContext,
|
|
227
|
+
) => Promise<CapabilityResult>;
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Store
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
/** Scoped key-value store for extension-private persistent state. */
|
|
234
|
+
export interface ExtensionStore {
|
|
235
|
+
/** Get a value by key, or null if not set. */
|
|
236
|
+
get(key: string): string | null;
|
|
237
|
+
/** Set a key-value pair (upsert). */
|
|
238
|
+
set(key: string, value: string): void;
|
|
239
|
+
/** Delete a key. Returns true if the key existed. */
|
|
240
|
+
delete(key: string): boolean;
|
|
241
|
+
/** List all key-value pairs for this extension. */
|
|
242
|
+
list(): Array<{ key: string; value: string }>;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Extension API — the object passed to each extension's setup function
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
/** The API surface available to extensions during setup. */
|
|
250
|
+
export interface MercuryExtensionAPI {
|
|
251
|
+
/** The extension's name (directory name). */
|
|
252
|
+
readonly name: string;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Declare a CLI tool to install in the derived container image.
|
|
256
|
+
* Can only be called once per extension.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* mercury.cli({ name: "napkin", install: "bun add -g napkin-ai" });
|
|
260
|
+
*/
|
|
261
|
+
cli(opts: CliDef): void;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Register this extension's permission and set which roles get it by default.
|
|
265
|
+
* The permission name is the extension name. Can only be called once.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* mercury.permission({ defaultRoles: ["admin", "member"] });
|
|
269
|
+
*/
|
|
270
|
+
permission(opts: PermissionDef): void;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Declare an environment variable this extension needs.
|
|
274
|
+
* Only injected into containers when the caller has permission for this extension.
|
|
275
|
+
* Can be called multiple times for multiple env vars.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* mercury.env({ from: "MERCURY_GH_TOKEN" }); // injected as GH_TOKEN
|
|
279
|
+
* mercury.env({ from: "MERCURY_GH_TOKEN", as: "GITHUB_TOKEN" }); // custom name
|
|
280
|
+
*/
|
|
281
|
+
env(def: EnvDef): void;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Register a skill directory containing a SKILL.md for agent discovery.
|
|
285
|
+
* Path is relative to the extension directory.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* mercury.skill("./skill");
|
|
289
|
+
*/
|
|
290
|
+
skill(relativePath: string): void;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Declare capability requirements for this extension's skill / CLI workflows.
|
|
294
|
+
* If no model leg in the chain satisfies all listed capabilities, the extension
|
|
295
|
+
* skill is not installed and a startup warning is logged.
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* mercury.requires(["tools"]);
|
|
299
|
+
*/
|
|
300
|
+
requires(capabilities: ModelCapabilityKey[]): void;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Subscribe to a lifecycle event.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* mercury.on("workspace_init", async (event, ctx) => {
|
|
307
|
+
* mkdirSync(join(event.workspace, "my-dir"), { recursive: true });
|
|
308
|
+
* });
|
|
309
|
+
*/
|
|
310
|
+
on<E extends keyof MercuryEvents>(event: E, handler: EventHandler<E>): void;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Register a background job that runs on the host.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* mercury.job("cleanup", { interval: 3600_000, run: async (ctx) => { ... } });
|
|
317
|
+
*/
|
|
318
|
+
job(name: string, def: JobDef): void;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Register a per-space config key. Namespaced to the extension automatically.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* mercury.config("enabled", { description: "Enable for this group", default: "true" });
|
|
325
|
+
* // Registers as "napkin.enabled" in the DB
|
|
326
|
+
*/
|
|
327
|
+
config(key: string, def: ConfigDef): void;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Register a dashboard widget.
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* mercury.widget({ label: "Status", render: (ctx) => "<p>OK</p>" });
|
|
334
|
+
*/
|
|
335
|
+
widget(def: WidgetDef): void;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Declare this extension as a personal service connection. Additive on top
|
|
339
|
+
* of cli/env/skill; extensions that never call this are unaffected. Can
|
|
340
|
+
* only be called once per extension.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* mercury.connection({
|
|
344
|
+
* displayName: "Google Workspace",
|
|
345
|
+
* category: "workspace",
|
|
346
|
+
* authType: "credentials-file",
|
|
347
|
+
* credentialEnvVar: "MERCURY_GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE",
|
|
348
|
+
* });
|
|
349
|
+
*/
|
|
350
|
+
connection(def: ConnectionDef): void;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Register a host-side capability handler, invoked from the container via
|
|
354
|
+
* `mrctl capability <name> <action> <json>` → `POST /api/capability/:name/:action`.
|
|
355
|
+
* The caller must hold the `<name>` permission. Credentials used by the
|
|
356
|
+
* handler stay on the host and never enter the agent container.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* mercury.capability("barber", async (req, ctx) => {
|
|
360
|
+
* if (req.action === "book") return { data: await book(req.callerId, req.body) };
|
|
361
|
+
* return { status: 400, data: { error: "unknown action" } };
|
|
362
|
+
* });
|
|
363
|
+
*/
|
|
364
|
+
capability(name: string, handler: CapabilityHandler): void;
|
|
365
|
+
|
|
366
|
+
/** Scoped key-value store for persistent extension state. */
|
|
367
|
+
readonly store: ExtensionStore;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ---------------------------------------------------------------------------
|
|
371
|
+
// CLI + Permission definitions
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
/** Declaration for a CLI tool to install in the container image. */
|
|
375
|
+
export interface CliDef {
|
|
376
|
+
/** CLI binary name (should match the extension name). */
|
|
377
|
+
name: string;
|
|
378
|
+
/** Shell command to install the CLI (runs as a Dockerfile RUN step). */
|
|
379
|
+
install: string;
|
|
380
|
+
/**
|
|
381
|
+
* Absolute path to a local script to copy into `/usr/local/bin/{name}`.
|
|
382
|
+
* Set by the extension loader from the extension's directory.
|
|
383
|
+
*/
|
|
384
|
+
bin?: string;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Permission configuration for an extension. */
|
|
388
|
+
export interface PermissionDef {
|
|
389
|
+
/** Roles that should have this permission by default. */
|
|
390
|
+
defaultRoles: string[];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Environment variable declaration for an extension. */
|
|
394
|
+
export interface EnvDef {
|
|
395
|
+
/** Env var name as it appears in .env (e.g. "MERCURY_GH_TOKEN"). */
|
|
396
|
+
from: string;
|
|
397
|
+
/** Env var name inside the container (e.g. "GH_TOKEN"). Defaults to `from` with MERCURY_ prefix stripped. */
|
|
398
|
+
as?: string;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
// Connection metadata — first-class personal service connections
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
/** Runtime status of a personal service connection. */
|
|
406
|
+
export type ConnectionStatus =
|
|
407
|
+
| "connected"
|
|
408
|
+
| "needs-reauth"
|
|
409
|
+
| "broken"
|
|
410
|
+
| "unknown";
|
|
411
|
+
|
|
412
|
+
/** Closed category taxonomy for connections (v1 — tags deferred). */
|
|
413
|
+
export type ConnectionCategory =
|
|
414
|
+
| "email"
|
|
415
|
+
| "drive"
|
|
416
|
+
| "calendar"
|
|
417
|
+
| "finance"
|
|
418
|
+
| "messaging"
|
|
419
|
+
| "docs"
|
|
420
|
+
| "workspace"
|
|
421
|
+
| "other";
|
|
422
|
+
|
|
423
|
+
/** How the connection authenticates. */
|
|
424
|
+
export type ConnectionAuthType =
|
|
425
|
+
| "oauth2"
|
|
426
|
+
| "apikey"
|
|
427
|
+
| "app-password"
|
|
428
|
+
| "credentials-file"
|
|
429
|
+
| "form"
|
|
430
|
+
| "custom";
|
|
431
|
+
|
|
432
|
+
/** Result of an on-request `statusCheck` probe. */
|
|
433
|
+
export interface ConnectionStatusResult {
|
|
434
|
+
status: ConnectionStatus;
|
|
435
|
+
/** Optional one-line explanation surfaced to the UI (e.g. "token expired 2h ago"). */
|
|
436
|
+
detail?: string;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Declaration for a personal service connection registered via
|
|
441
|
+
* `mercury.connection()`. At least one of `credentialEnvVar` or `statusCheck`
|
|
442
|
+
* must be set — enforced at load.
|
|
443
|
+
*/
|
|
444
|
+
export interface ConnectionDef {
|
|
445
|
+
/** User-facing name (e.g. "Google Workspace"). */
|
|
446
|
+
displayName: string;
|
|
447
|
+
/** Optional icon URL surfaced to the console. */
|
|
448
|
+
iconUrl?: string;
|
|
449
|
+
/** Category used for grouping in the UI. */
|
|
450
|
+
category: ConnectionCategory;
|
|
451
|
+
/** Auth mechanism used by the upstream service. */
|
|
452
|
+
authType: ConnectionAuthType;
|
|
453
|
+
/**
|
|
454
|
+
* Optional. If set, MUST match one of the env var names declared via
|
|
455
|
+
* `mercury.env({ from })`. Validated at load. Used for the default
|
|
456
|
+
* presence-check status when `statusCheck` is absent. Extensions that store
|
|
457
|
+
* credentials in `extension_state` (e.g. tradestation OAuth) omit this and
|
|
458
|
+
* rely on `statusCheck`.
|
|
459
|
+
*/
|
|
460
|
+
credentialEnvVar?: string;
|
|
461
|
+
/** Optional OAuth-style scope list (informational, surfaced to the UI). */
|
|
462
|
+
scopes?: string[];
|
|
463
|
+
/**
|
|
464
|
+
* Optional probe. Runs on the host with the full extension context. The
|
|
465
|
+
* caller enforces a 5-second timeout. Must be side-effect free.
|
|
466
|
+
*/
|
|
467
|
+
statusCheck?: (
|
|
468
|
+
ctx: MercuryExtensionContext,
|
|
469
|
+
) => Promise<ConnectionStatusResult>;
|
|
470
|
+
/**
|
|
471
|
+
* If true, this connection accesses personal/sensitive data (email, finance,
|
|
472
|
+
* authenticated browser). In group-linked spaces, the runtime guard requires
|
|
473
|
+
* explicit admin enablement and per-request confirmation before proceeding.
|
|
474
|
+
*/
|
|
475
|
+
sensitive?: boolean;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ---------------------------------------------------------------------------
|
|
479
|
+
// Extension metadata — collected after running the setup function
|
|
480
|
+
// ---------------------------------------------------------------------------
|
|
481
|
+
|
|
482
|
+
/** Fully resolved metadata for a loaded extension. */
|
|
483
|
+
export interface ExtensionMeta {
|
|
484
|
+
/** Extension name (directory name). */
|
|
485
|
+
name: string;
|
|
486
|
+
/** Absolute path to the extension directory. */
|
|
487
|
+
dir: string;
|
|
488
|
+
/** CLI declarations (may be empty). */
|
|
489
|
+
clis: CliDef[];
|
|
490
|
+
/** Permission configuration, if any. */
|
|
491
|
+
permission?: PermissionDef;
|
|
492
|
+
/** If set, skill install requires these capabilities on at least one model chain leg. */
|
|
493
|
+
requires?: ModelCapabilityKey[];
|
|
494
|
+
/** Absolute path to the skill directory, if declared. */
|
|
495
|
+
skillDir?: string;
|
|
496
|
+
/** Event handlers keyed by event name. */
|
|
497
|
+
hooks: Map<keyof MercuryEvents, EventHandler<keyof MercuryEvents>[]>;
|
|
498
|
+
/** Background jobs keyed by job name. */
|
|
499
|
+
jobs: Map<string, JobDef>;
|
|
500
|
+
/** Config key definitions keyed by local key (not namespaced). */
|
|
501
|
+
configs: Map<string, ConfigDef>;
|
|
502
|
+
/** Dashboard widgets. */
|
|
503
|
+
widgets: WidgetDef[];
|
|
504
|
+
/** Host-side capability handlers, keyed by capability name. */
|
|
505
|
+
capabilities: Map<string, CapabilityHandler>;
|
|
506
|
+
/** Declared environment variables. */
|
|
507
|
+
envVars: EnvDef[];
|
|
508
|
+
/** Personal service connection metadata, if declared via `mercury.connection()`. */
|
|
509
|
+
connection?: ConnectionDef;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// Extension setup function signature
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
/** The default export every extension must provide. */
|
|
517
|
+
export type ExtensionSetupFn = (api: MercuryExtensionAPI) => void;
|