@jini-ai/agent-plugins 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/LICENSE +201 -0
- package/README.md +261 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/lifecycle/activation.d.ts +177 -0
- package/dist/lifecycle/activation.js +874 -0
- package/dist/lifecycle/bundled-digests.d.ts +27 -0
- package/dist/lifecycle/bundled-digests.js +149 -0
- package/dist/lifecycle/bundled-source-archive.d.ts +13 -0
- package/dist/lifecycle/bundled-source-archive.js +96 -0
- package/dist/lifecycle/capability-projection.d.ts +11 -0
- package/dist/lifecycle/capability-projection.js +60 -0
- package/dist/lifecycle/fetch-archive.d.ts +88 -0
- package/dist/lifecycle/fetch-archive.js +215 -0
- package/dist/lifecycle/index.d.ts +174 -0
- package/dist/lifecycle/index.js +94 -0
- package/dist/lifecycle/install-from-url.d.ts +78 -0
- package/dist/lifecycle/install-from-url.js +73 -0
- package/dist/lifecycle/install.d.ts +52 -0
- package/dist/lifecycle/install.js +286 -0
- package/dist/lifecycle/layout.d.ts +20 -0
- package/dist/lifecycle/layout.js +30 -0
- package/dist/lifecycle/manifest.d.ts +214 -0
- package/dist/lifecycle/manifest.js +409 -0
- package/dist/lifecycle/mcp-provisioning.d.ts +20 -0
- package/dist/lifecycle/mcp-provisioning.js +14 -0
- package/dist/lifecycle/node.d.ts +11 -0
- package/dist/lifecycle/node.js +34 -0
- package/dist/lifecycle/package-paths.d.ts +12 -0
- package/dist/lifecycle/package-paths.js +74 -0
- package/dist/lifecycle/ports.d.ts +81 -0
- package/dist/lifecycle/ports.js +2 -0
- package/dist/lifecycle/resolve-agent-plugin-refs.d.ts +18 -0
- package/dist/lifecycle/resolve-agent-plugin-refs.js +143 -0
- package/dist/lifecycle/retire-bundled.d.ts +36 -0
- package/dist/lifecycle/retire-bundled.js +69 -0
- package/dist/lifecycle/search.d.ts +23 -0
- package/dist/lifecycle/search.js +50 -0
- package/dist/lifecycle/seed-bundled.d.ts +29 -0
- package/dist/lifecycle/seed-bundled.js +118 -0
- package/dist/lifecycle/set-enabled.d.ts +21 -0
- package/dist/lifecycle/set-enabled.js +39 -0
- package/dist/lifecycle/trusted-plugin-files.d.ts +29 -0
- package/dist/lifecycle/trusted-plugin-files.js +82 -0
- package/dist/lifecycle/types.d.ts +20 -0
- package/dist/lifecycle/types.js +2 -0
- package/dist/lifecycle/uninstall.d.ts +41 -0
- package/dist/lifecycle/uninstall.js +148 -0
- package/dist/lifecycle/yauzl-archive-reader.d.ts +62 -0
- package/dist/lifecycle/yauzl-archive-reader.js +73 -0
- package/dist/manifest.d.ts +68 -0
- package/dist/manifest.js +38 -0
- package/package.json +92 -0
- package/ui-ux-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
- package/ui-ux-design/mcp.json +4 -0
- package/ui-ux-design/plugin.json +5 -0
- package/ui-ux-design/skills/frontend-accessibility/SKILL.md +60 -0
- package/ui-ux-design/skills/gstack-design/SKILL.md +53 -0
- package/ui-ux-design/skills/gstack-design/references/consultation.md +30 -0
- package/ui-ux-design/skills/gstack-design/references/html.md +29 -0
- package/ui-ux-design/skills/gstack-design/references/review.md +28 -0
- package/ui-ux-design/skills/gstack-design/references/shotgun.md +33 -0
- package/ui-ux-design/skills/gstack-design/references/upstream-notes.md +23 -0
- package/ui-ux-design/skills/interface-design/ORIGINAL.md +16 -0
- package/ui-ux-design/skills/interface-design/SKILL.md +41 -0
- package/ui-ux-design/skills/interface-design/references/commands/audit.md +40 -0
- package/ui-ux-design/skills/interface-design/references/commands/critique.md +21 -0
- package/ui-ux-design/skills/interface-design/references/commands/extract.md +34 -0
- package/ui-ux-design/skills/interface-design/references/commands/init.md +31 -0
- package/ui-ux-design/skills/interface-design/references/commands/status.md +22 -0
- package/ui-ux-design/skills/interface-design/references/examples/system-precision.md +29 -0
- package/ui-ux-design/skills/interface-design/references/examples/system-warmth.md +29 -0
- package/ui-ux-design/skills/interface-design/references/system-template.md +57 -0
- package/ui-ux-design/skills/shadcn-ui/README.md +244 -0
- package/ui-ux-design/skills/shadcn-ui/SKILL.md +326 -0
- package/ui-ux-design/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
- package/ui-ux-design/skills/shadcn-ui/examples/data-table.tsx +313 -0
- package/ui-ux-design/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
- package/ui-ux-design/skills/shadcn-ui/resources/component-catalog.md +481 -0
- package/ui-ux-design/skills/shadcn-ui/resources/customization-guide.md +516 -0
- package/ui-ux-design/skills/shadcn-ui/resources/migration-guide.md +463 -0
- package/ui-ux-design/skills/shadcn-ui/resources/setup-guide.md +412 -0
- package/ui-ux-design/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
- package/ui-ux-design/skills/ui-ux-design/SKILL.md +77 -0
- package/ui-ux-design/skills/ui-ux-design/references/brand-and-voice.md +72 -0
- package/ui-ux-design/skills/ui-ux-design/references/components-and-states.md +107 -0
- package/ui-ux-design/skills/ui-ux-design/references/delight-and-motion.md +65 -0
- package/ui-ux-design/skills/ui-ux-design/references/foundations.md +144 -0
- package/ui-ux-design/skills/ui-ux-design/references/inclusive-ai-imagery.md +57 -0
- package/ui-ux-design/skills/ui-ux-design/references/kole-jain-uiux-concepts.md +102 -0
- package/ui-ux-design/skills/ui-ux-design/references/openai-frontend-skill.md +139 -0
- package/ui-ux-design/skills/ui-ux-design/references/premium-ui.md +63 -0
- package/ui-ux-design/skills/ui-ux-design/references/research-and-validation.md +78 -0
- package/ui-ux-design/skills/ui-ux-design/references/sam-crawford-premium-websites.md +93 -0
- package/ui-ux-design/skills/ui-ux-design/references/self-made-web-designer-core-skills.md +123 -0
- package/ui-ux-design/skills/ui-ux-design/references/visual-storytelling.md +57 -0
- package/ui-ux-design/skills/vercel-web-design-guidelines/SKILL.md +39 -0
- package/ui-ux-design/skills/web-compliance/SKILL.md +129 -0
- package/ui-ux-design/skills/web-compliance/references/audit-evidence.md +59 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Historical lifecycle rationale, carried with the implementation during consumer adoption.
|
|
3
|
+
* @file `parseAgentPluginManifest()` / `parseAgentPluginMcpConfig()` — the Agent Plugins v1.0.0
|
|
4
|
+
* `plugin.json` / `mcp.json` grammar (agent-plugins.org/specification), verified against the live
|
|
5
|
+
* spec rather than inferred from an example manifest.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately a SEPARATE, smaller validator from `src/features/plugin-runtime/manifest.ts`'s
|
|
8
|
+
* `validateManifest()`. That file validates `.host-plugin`'s own tarball format — `integrity`
|
|
9
|
+
* (per-file SHA-256), `sdkRange`, `tier`, an exactly-three-token capability vocabulary, a single
|
|
10
|
+
* `content.entry.beforeSave` hook. This file validates the OPEN standard's `plugin.json`, which
|
|
11
|
+
* shares none of those fields and defines no execution/trust/capability model at all — see this
|
|
12
|
+
* feature's own header comment in `install.ts` for why the two formats cannot be unified.
|
|
13
|
+
*
|
|
14
|
+
* Spec facts this module encodes, quoted/paraphrased from agent-plugins.org/specification (verified
|
|
15
|
+
* 2026-08-12, not carried forward from an earlier round's inference):
|
|
16
|
+
* - `plugin.json`'s `name`: 1-64 chars, lowercase alphanumeric/hyphen/period only, must start and
|
|
17
|
+
* end alphanumeric, no `--` or `..` consecutive delimiters (§5.5). Valid: `my-plugin`,
|
|
18
|
+
* `acme.tools`, `lint3r`, `a`. Invalid: `My-Plugin`, `-start`, `has--double`.
|
|
19
|
+
* - `plugin.json`'s `$schema` MUST equal `https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`
|
|
20
|
+
* for this pinned version; `mcp.json`'s MUST equal
|
|
21
|
+
* `https://agent-plugins.org/schemas/1.0.0/mcp.schema.json`.
|
|
22
|
+
* - Unknown top-level `plugin.json` fields produce warnings but do not block loading — this module
|
|
23
|
+
* returns them as `warnings`, never as a rejection reason.
|
|
24
|
+
* - `mcp.json`'s top-level shape is `{ "$schema": ..., "mcpServers": { "<server-id>": {...} } }` —
|
|
25
|
+
* `mcpServers` MUST be an object whose member names identify servers.
|
|
26
|
+
* - Each server entry MUST carry a `type` discriminator matching exactly one of three CLOSED
|
|
27
|
+
* variants (re-verified live against `agent-plugins.org/schemas/1.0.0/mcp.schema.json` and
|
|
28
|
+
* `/specification`, 2026-09-10 — superseding this file's earlier "not validated, no caller needs
|
|
29
|
+
* it" note, which is now false: `capability-projection.ts`'s MCP-server descriptors are no longer
|
|
30
|
+
* unconditionally `execute: { kind: "unavailable" }`, and launching a server needs its real
|
|
31
|
+
* transport config, not just its id):
|
|
32
|
+
* - `stdio`: requires `command` (non-empty string); optional `args` (string[]), `env`
|
|
33
|
+
* (string-valued object — MUST NOT set `PLUGIN_ROOT`/`PLUGIN_DATA`, which the spec reserves),
|
|
34
|
+
* `cwd` (plugin-relative path).
|
|
35
|
+
* - `streamable-http` / `sse`: requires `url` (non-empty string); optional `headers`
|
|
36
|
+
* (string-valued object).
|
|
37
|
+
* - **The spec defines NO auth-related field on a server entry at all.** Verbatim from
|
|
38
|
+
* `/specification`: "Agent Plugins v1 defines no OAuth configuration or portable
|
|
39
|
+
* credential-reference fields. Authorization discovery, user interaction, and credential storage
|
|
40
|
+
* are client-managed." A plugin therefore cannot declare "this server needs OAuth" using any
|
|
41
|
+
* spec-defined field — the host is the client the spec defers that decision to. This module accepts
|
|
42
|
+
* the host-specific metadata in `plugin.json`'s `extensions.<host-namespace>.mcpServers[serverId]`, keeping
|
|
43
|
+
* `mcp.json` transport entries closed per §7.2.1. Hosts may translate legacy auth and sign-in defaults in MCP
|
|
44
|
+
* entries through readServerMetadata for existing packages, but those cannot supply operator read trust.
|
|
45
|
+
* `authMode: "oauth" | "none"` selects client auth; absent defaults to "none".
|
|
46
|
+
* `defaultTools: { allow?, write?, read? }` names sign-in grants and reviewed read tools.
|
|
47
|
+
* `write` requires `allow` and must be its subset; `read` must be a subset when `allow` is
|
|
48
|
+
* present. A read-only declaration without `allow` leaves sign-in grants unchanged.
|
|
49
|
+
* Remote hints may veto a read declaration but never supply it. A third,
|
|
50
|
+
* `tokenAuth: { helpUrl, probeUrl }`, offers a pasted access token as the sign-in fallback
|
|
51
|
+
* (`access-token-tool.ts`). A fourth, `renamedTools: { oldName: newName }`, carries saved tool
|
|
52
|
+
* selections across a vendor's tool rename (`apply-tool-renames.ts`). A malformed per-server entry
|
|
53
|
+
* (wrong/missing `type`, missing required field) does not fail the whole file — see
|
|
54
|
+
* `ParseAgentPluginMcpConfigResult`'s own doc for the fail-open contract this preserves.
|
|
55
|
+
*
|
|
56
|
+
* Architectural role:
|
|
57
|
+
* Pure, no-I/O parsing over an already-`JSON.parse`d value — mirrors `plugin-runtime/manifest.ts`'s
|
|
58
|
+
* own "receives a JS value, never a file path or raw bytes" contract. `install.ts` is the only
|
|
59
|
+
* caller that reads the file and hands the parsed value here.
|
|
60
|
+
*/
|
|
61
|
+
export { validatePluginManifest, validateMcpManifest } from '../manifest.js';
|
|
62
|
+
/**
|
|
63
|
+
* The one plugin.json schema version this loader understands. A manifest declaring any other
|
|
64
|
+
* value is rejected outright (C2 in the debate: "the standard is a Working Draft; the loader pins
|
|
65
|
+
* v1 and must survive spec churn") rather than guessed at.
|
|
66
|
+
*/
|
|
67
|
+
const PLUGIN_SCHEMA_1_0_0 = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
|
|
68
|
+
const MCP_SCHEMA_1_0_0 = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
|
|
69
|
+
/**
|
|
70
|
+
* §5.5's grammar as one regex: alnum runs separated by single `-`/`.` delimiters. This shape alone
|
|
71
|
+
* guarantees start/end are alphanumeric (the regex can only start and end on `[a-z0-9]+`) AND that
|
|
72
|
+
* no two delimiters are ever adjacent (a delimiter is always followed by at least one alnum char
|
|
73
|
+
* before the next one can appear) — so "no leading/trailing hyphen" and "no consecutive delimiters"
|
|
74
|
+
* fall out of one pattern rather than needing separate checks that could disagree.
|
|
75
|
+
*/
|
|
76
|
+
const NAME_PATTERN = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/;
|
|
77
|
+
const MAX_NAME_LENGTH = 64;
|
|
78
|
+
const KNOWN_MANIFEST_KEYS = new Set(["$schema", "name", "version", "description", "author", "license", "keywords", "extensions"]);
|
|
79
|
+
/**
|
|
80
|
+
* True for a non-null, non-array plain object — the "is this actually a JSON object" gate both
|
|
81
|
+
* `parseAgentPluginManifest` and `parseAgentPluginMcpConfig` need before touching any field.
|
|
82
|
+
*/
|
|
83
|
+
function isJsonObject(value) {
|
|
84
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* `value` narrowed to a `string`, or `undefined` — every optional string field below coerces
|
|
88
|
+
* through this one check instead of repeating its own `typeof ... === "string" ? ... : undefined`
|
|
89
|
+
* ternary.
|
|
90
|
+
*/
|
|
91
|
+
function coerceOptionalString(value) {
|
|
92
|
+
return typeof value === "string" ? value : undefined;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `value` narrowed to a `string[]` (non-string entries dropped), or `undefined` when `value`
|
|
96
|
+
* itself isn't an array — `keywords`'s own coercion.
|
|
97
|
+
*/
|
|
98
|
+
function coerceStringArray(value) {
|
|
99
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : undefined;
|
|
100
|
+
}
|
|
101
|
+
function parseManifestAuthor(value) {
|
|
102
|
+
if (typeof value === 'string')
|
|
103
|
+
return value; // Existing installed manifests may carry a string.
|
|
104
|
+
if (!isJsonObject(value))
|
|
105
|
+
return undefined;
|
|
106
|
+
return {
|
|
107
|
+
...(typeof value.name === 'string' ? { name: value.name } : {}),
|
|
108
|
+
...(typeof value.email === 'string' ? { email: value.email } : {}),
|
|
109
|
+
...(typeof value.url === 'string' ? { url: value.url } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Validates `plugin.json`'s `name` field per §5.5's grammar (see `NAME_PATTERN`'s own header for
|
|
114
|
+
* what the one regex encodes). Returns the valid name, or the one error message to report — never
|
|
115
|
+
* both, so the caller needs exactly one branch to consume this instead of the original's
|
|
116
|
+
* required-then-format else-if chain.
|
|
117
|
+
*/
|
|
118
|
+
function resolveManifestName(raw) {
|
|
119
|
+
const name = coerceOptionalString(raw.name);
|
|
120
|
+
if (name === undefined)
|
|
121
|
+
return { error: "plugin.json 'name' is required and must be a string" };
|
|
122
|
+
if (name.length === 0 || name.length > MAX_NAME_LENGTH || !NAME_PATTERN.test(name)) {
|
|
123
|
+
return {
|
|
124
|
+
error: `plugin.json 'name' ('${name}') must be 1-${MAX_NAME_LENGTH} lowercase alphanumeric/hyphen/period characters, start and end alphanumeric, with no consecutive delimiters`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { name };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Validates an already-`JSON.parse`d `plugin.json` value against the v1.0.0 grammar.
|
|
131
|
+
*
|
|
132
|
+
* @throws Nothing — every failure is reported via the returned `errors`, matching this codebase's
|
|
133
|
+
* own convention for expected validation outcomes (`plugin-runtime/manifest.ts`,
|
|
134
|
+
* `plugin-runtime/discovery.ts`).
|
|
135
|
+
* @complexity O(k + n) time and space in the input keys and keyword entries.
|
|
136
|
+
*/
|
|
137
|
+
export function parseAgentPluginManifest({ value }) {
|
|
138
|
+
if (!isJsonObject(value))
|
|
139
|
+
return { ok: false, errors: ["plugin.json must be a JSON object"] };
|
|
140
|
+
const raw = value;
|
|
141
|
+
const errors = [];
|
|
142
|
+
const warnings = [];
|
|
143
|
+
if (raw.$schema !== PLUGIN_SCHEMA_1_0_0) {
|
|
144
|
+
errors.push(`plugin.json '$schema' must be '${PLUGIN_SCHEMA_1_0_0}', got '${String(raw.$schema)}'`);
|
|
145
|
+
}
|
|
146
|
+
if (raw.extensions !== undefined && !isJsonObject(raw.extensions))
|
|
147
|
+
errors.push("plugin.json extensions must be an object");
|
|
148
|
+
const nameResult = resolveManifestName(raw);
|
|
149
|
+
if (nameResult.error)
|
|
150
|
+
errors.push(nameResult.error);
|
|
151
|
+
for (const key of Object.keys(raw)) {
|
|
152
|
+
if (!KNOWN_MANIFEST_KEYS.has(key))
|
|
153
|
+
warnings.push(`unrecognized plugin.json field '${key}' (ignored per spec)`);
|
|
154
|
+
}
|
|
155
|
+
if (errors.length > 0)
|
|
156
|
+
return { ok: false, errors };
|
|
157
|
+
const manifest = {
|
|
158
|
+
name: nameResult.name,
|
|
159
|
+
version: coerceOptionalString(raw.version),
|
|
160
|
+
description: coerceOptionalString(raw.description),
|
|
161
|
+
author: parseManifestAuthor(raw.author),
|
|
162
|
+
license: coerceOptionalString(raw.license),
|
|
163
|
+
keywords: coerceStringArray(raw.keywords),
|
|
164
|
+
...(isJsonObject(raw.extensions) ? { extensions: raw.extensions } : {}),
|
|
165
|
+
};
|
|
166
|
+
return { ok: true, manifest, warnings };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The three closed transport variants `mcp.schema.json` v1.0.0 defines. See this file's header for
|
|
170
|
+
* the verified field shape of each.
|
|
171
|
+
*/
|
|
172
|
+
export const MCP_SERVER_TRANSPORTS = ["stdio", "streamable-http", "sse"];
|
|
173
|
+
/**
|
|
174
|
+
* Reserved by the spec — a `stdio` server's `env` MUST NOT set either, since the client (the host)
|
|
175
|
+
* owns them. Checked defensively; nothing upstream of this parser fills them in yet, so this is a
|
|
176
|
+
* spec-conformance check today rather than one guarding a real collision.
|
|
177
|
+
*/
|
|
178
|
+
const RESERVED_STDIO_ENV_KEYS = ["PLUGIN_ROOT", "PLUGIN_DATA"];
|
|
179
|
+
/**
|
|
180
|
+
* `value` narrowed to an object whose own values are all strings, or `undefined` for anything
|
|
181
|
+
* else — the shared shape `env`/`headers` both need.
|
|
182
|
+
*/
|
|
183
|
+
function isStringRecord(value) {
|
|
184
|
+
return isJsonObject(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Validates one `type: "stdio"` server entry.
|
|
188
|
+
*
|
|
189
|
+
* @returns The parsed config, or `null` for any shape violation (missing/empty `command`, a
|
|
190
|
+
* malformed `args`/`env`/`cwd`, or an `env` setting a reserved key) — the caller folds `null` into
|
|
191
|
+
* "this one server is excluded from `servers`", never into a whole-file rejection.
|
|
192
|
+
* @complexity O(k) in the entry's own field count.
|
|
193
|
+
*/
|
|
194
|
+
function parseStdioServerConfig(raw) {
|
|
195
|
+
if (typeof raw.command !== "string" || raw.command.length === 0)
|
|
196
|
+
return null;
|
|
197
|
+
const { args, env, cwd } = raw;
|
|
198
|
+
if (args !== undefined && !(Array.isArray(args) && args.every((entry) => typeof entry === "string")))
|
|
199
|
+
return null;
|
|
200
|
+
if (env !== undefined && (!isStringRecord(env) || RESERVED_STDIO_ENV_KEYS.some((key) => Object.hasOwn(env, key))))
|
|
201
|
+
return null;
|
|
202
|
+
if (cwd !== undefined && typeof cwd !== "string")
|
|
203
|
+
return null;
|
|
204
|
+
return {
|
|
205
|
+
type: "stdio",
|
|
206
|
+
command: raw.command,
|
|
207
|
+
...(args !== undefined ? { args: args } : {}),
|
|
208
|
+
...(env !== undefined ? { env } : {}),
|
|
209
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Same bound and grammar as `external-mcp-store.ts`'s `MAX_ALLOWED_TOOLS`/`REMOTE_TOOL_NAME_PATTERN`,
|
|
214
|
+
* so a declared default can never be one the store would refuse on save.
|
|
215
|
+
*/
|
|
216
|
+
const MAX_DEFAULT_TOOLS = 64;
|
|
217
|
+
const DEFAULT_TOOL_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/;
|
|
218
|
+
/**
|
|
219
|
+
* A bounded array of valid tool names, or `null`.
|
|
220
|
+
*/
|
|
221
|
+
function parseDefaultToolNames(value) {
|
|
222
|
+
if (!Array.isArray(value) || value.length > MAX_DEFAULT_TOOLS)
|
|
223
|
+
return null;
|
|
224
|
+
return value.every((name) => typeof name === "string" && DEFAULT_TOOL_NAME_PATTERN.test(name)) ? value : null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Validates bounded sign-in and read lists. `read` and `write` default to `[]`.
|
|
228
|
+
* A provided `allow` bounds both lists; without it only a read declaration is meaningful,
|
|
229
|
+
* and sign-in defaults stay absent. Any malformed list excludes the entire server.
|
|
230
|
+
*
|
|
231
|
+
* @returns Parsed defaults, or `null` for a malformed declaration.
|
|
232
|
+
* @complexity O(n) time and space in the lists' total length (each capped at 64).
|
|
233
|
+
*/
|
|
234
|
+
function parseDefaultTools(value) {
|
|
235
|
+
if (!isJsonObject(value))
|
|
236
|
+
return null;
|
|
237
|
+
const allow = value.allow === undefined ? undefined : parseDefaultToolNames(value.allow);
|
|
238
|
+
const write = value.write === undefined ? [] : parseDefaultToolNames(value.write);
|
|
239
|
+
const read = value.read === undefined ? [] : parseDefaultToolNames(value.read);
|
|
240
|
+
if (allow === null || write === null || read === null)
|
|
241
|
+
return null;
|
|
242
|
+
if (allow === undefined)
|
|
243
|
+
return value.read !== undefined && write.length === 0 ? { write, read } : null;
|
|
244
|
+
const allowed = new Set(allow);
|
|
245
|
+
return write.every((name) => allowed.has(name)) && read.every((name) => allowed.has(name)) ? { allow, write, read } : null;
|
|
246
|
+
}
|
|
247
|
+
const MAX_TOKEN_AUTH_URL_LENGTH = 2048;
|
|
248
|
+
function isHttpsUrl(value) {
|
|
249
|
+
if (typeof value !== "string" || value.length > MAX_TOKEN_AUTH_URL_LENGTH)
|
|
250
|
+
return false;
|
|
251
|
+
try {
|
|
252
|
+
return new URL(value).protocol === "https:";
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* An env var name a manifest may point at: upper-case, digits and `_`.
|
|
260
|
+
*/
|
|
261
|
+
const ENV_VAR_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
262
|
+
const MAX_RETIRED_ENV = 32;
|
|
263
|
+
function isEnvVarName(value) {
|
|
264
|
+
return typeof value === "string" && ENV_VAR_NAME_PATTERN.test(value);
|
|
265
|
+
}
|
|
266
|
+
function parseRetiredEnv(value) {
|
|
267
|
+
return Array.isArray(value) && value.length <= MAX_RETIRED_ENV && value.every(isEnvVarName) ? value : null;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Validates a remote entry's `renamedTools` extension: at most {@link MAX_DEFAULT_TOOLS} entries,
|
|
271
|
+
* every key and value a valid tool name, no name renamed to itself, and no new name that is itself
|
|
272
|
+
* renamed again (so one pass is the whole rewrite).
|
|
273
|
+
*
|
|
274
|
+
* @returns The parsed map, or `null` for any shape violation — the caller excludes the whole server.
|
|
275
|
+
* @complexity O(n) in the entry count.
|
|
276
|
+
*/
|
|
277
|
+
function parseRenamedTools(value) {
|
|
278
|
+
if (!isStringRecord(value))
|
|
279
|
+
return null;
|
|
280
|
+
const entries = Object.entries(value);
|
|
281
|
+
if (entries.length > MAX_DEFAULT_TOOLS)
|
|
282
|
+
return null;
|
|
283
|
+
const valid = entries.every(([from, to]) => DEFAULT_TOOL_NAME_PATTERN.test(from) && DEFAULT_TOOL_NAME_PATTERN.test(to) && from !== to && !Object.hasOwn(value, to));
|
|
284
|
+
return valid ? value : null;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Validates a remote entry's `tokenAuth` extension, or `null` for any shape violation — the
|
|
288
|
+
* caller excludes the whole server, same as a malformed `defaultTools`.
|
|
289
|
+
*/
|
|
290
|
+
function parseTokenAuth(value) {
|
|
291
|
+
if (!isJsonObject(value) || !isHttpsUrl(value.helpUrl) || !isHttpsUrl(value.probeUrl))
|
|
292
|
+
return null;
|
|
293
|
+
const { importFromEnv } = value;
|
|
294
|
+
if (importFromEnv !== undefined && !isEnvVarName(importFromEnv))
|
|
295
|
+
return null;
|
|
296
|
+
const retiredEnv = value.retiredEnv === undefined ? undefined : parseRetiredEnv(value.retiredEnv);
|
|
297
|
+
if (retiredEnv === null)
|
|
298
|
+
return null;
|
|
299
|
+
return {
|
|
300
|
+
helpUrl: value.helpUrl,
|
|
301
|
+
probeUrl: value.probeUrl,
|
|
302
|
+
...(importFromEnv !== undefined ? { importFromEnv } : {}),
|
|
303
|
+
...(retiredEnv !== undefined ? { retiredEnv } : {}),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Validates one `type: "streamable-http"` or `type: "sse"` server entry — the two remote transports
|
|
308
|
+
* share this one implementation because they share every field (see {@link RemoteMcpServerConfig}).
|
|
309
|
+
*
|
|
310
|
+
* @returns The parsed config, or `null` for any shape violation. Same fold-into-exclusion contract
|
|
311
|
+
* as {@link parseStdioServerConfig}.
|
|
312
|
+
* @complexity O(k) in the entry's own field count.
|
|
313
|
+
*
|
|
314
|
+
* Read trust is accepted only from the plugin extension. Keep legacy sign-in grants.
|
|
315
|
+
*/
|
|
316
|
+
function parseRemoteServerConfig(type, raw, extension) {
|
|
317
|
+
if (typeof raw.url !== "string" || raw.url.length === 0)
|
|
318
|
+
return null;
|
|
319
|
+
const { headers } = raw;
|
|
320
|
+
const authMode = extension?.authMode;
|
|
321
|
+
if (headers !== undefined && !isStringRecord(headers))
|
|
322
|
+
return null;
|
|
323
|
+
if (authMode !== undefined && authMode !== "oauth" && authMode !== "none")
|
|
324
|
+
return null;
|
|
325
|
+
const defaults = extension?.defaultTools;
|
|
326
|
+
const defaultTools = defaults === undefined ? undefined : parseDefaultTools(defaults);
|
|
327
|
+
if (defaultTools === null)
|
|
328
|
+
return null;
|
|
329
|
+
const tokenAuth = extension?.tokenAuth === undefined ? undefined : parseTokenAuth(extension?.tokenAuth);
|
|
330
|
+
if (tokenAuth === null)
|
|
331
|
+
return null;
|
|
332
|
+
const renamedTools = extension?.renamedTools === undefined ? undefined : parseRenamedTools(extension?.renamedTools);
|
|
333
|
+
if (renamedTools === null)
|
|
334
|
+
return null;
|
|
335
|
+
return {
|
|
336
|
+
type,
|
|
337
|
+
url: raw.url,
|
|
338
|
+
...(headers !== undefined ? { headers } : {}),
|
|
339
|
+
...(authMode !== undefined ? { authMode } : {}),
|
|
340
|
+
...(defaultTools !== undefined ? { defaultTools } : {}),
|
|
341
|
+
...(tokenAuth !== undefined ? { tokenAuth } : {}),
|
|
342
|
+
...(renamedTools !== undefined ? { renamedTools } : {}),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Dispatches one raw `mcpServers` entry to its transport's validator by `type`, or `null` for a
|
|
347
|
+
* non-object entry or a `type` outside {@link MCP_SERVER_TRANSPORTS}.
|
|
348
|
+
*/
|
|
349
|
+
function parseMcpServerConfig(value, extension) {
|
|
350
|
+
if (!isJsonObject(value))
|
|
351
|
+
return null;
|
|
352
|
+
if (value.type === "stdio")
|
|
353
|
+
return parseStdioServerConfig(value);
|
|
354
|
+
if (value.type === "streamable-http" || value.type === "sse")
|
|
355
|
+
return parseRemoteServerConfig(value.type, value, extension);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Validates an already-`JSON.parse`d `mcp.json` value against the v1.0.0 top-level shape:
|
|
360
|
+
* `{ "$schema": ..., "mcpServers": { "<server-id>": {...} } }`, then validates each declared
|
|
361
|
+
* server's own transport shape (see this file's header for the three variants).
|
|
362
|
+
*
|
|
363
|
+
* @throws Nothing — see {@link parseAgentPluginManifest}.
|
|
364
|
+
* @complexity O(s) in the number of declared servers.
|
|
365
|
+
*/
|
|
366
|
+
export function parseAgentPluginMcpConfig({ value, extensionNamespace }, optional = {}) {
|
|
367
|
+
if (!extensionNamespace.trim())
|
|
368
|
+
throw new Error("An extension namespace is required");
|
|
369
|
+
if (!isJsonObject(value))
|
|
370
|
+
return { ok: false, errors: ["mcp.json must be a JSON object"] };
|
|
371
|
+
const raw = value;
|
|
372
|
+
const errors = [];
|
|
373
|
+
if (raw.$schema !== MCP_SCHEMA_1_0_0) {
|
|
374
|
+
errors.push(`mcp.json '$schema' must be '${MCP_SCHEMA_1_0_0}', got '${String(raw.$schema)}'`);
|
|
375
|
+
}
|
|
376
|
+
const mcpServers = raw.mcpServers;
|
|
377
|
+
if (typeof mcpServers !== "object" || mcpServers === null || Array.isArray(mcpServers)) {
|
|
378
|
+
errors.push("mcp.json 'mcpServers' is required and must be an object");
|
|
379
|
+
}
|
|
380
|
+
if (errors.length > 0)
|
|
381
|
+
return { ok: false, errors };
|
|
382
|
+
const manifest = parseAgentPluginManifest({ value: optional.pluginManifest });
|
|
383
|
+
const extension = manifest.ok ? readAgentPluginExtension({ manifest: manifest.manifest, namespace: extensionNamespace, read: ({ value }) => isJsonObject(value.mcpServers) ? value.mcpServers : {} }) : undefined;
|
|
384
|
+
const extensionServers = extension ?? {};
|
|
385
|
+
const entries = Object.entries(mcpServers);
|
|
386
|
+
const servers = Object.create(null);
|
|
387
|
+
for (const [serverId, rawServer] of entries) {
|
|
388
|
+
const extension = Object.hasOwn(extensionServers, serverId) ? extensionServers[serverId] : undefined;
|
|
389
|
+
// A malformed declared extension excludes the server before the translator can hide it.
|
|
390
|
+
const metadata = isJsonObject(rawServer) && (extension === undefined || isJsonObject(extension))
|
|
391
|
+
? optional.readServerMetadata?.({ serverId, value: extension ?? {}, server: rawServer }) ?? extension
|
|
392
|
+
: undefined;
|
|
393
|
+
const parsed = extension !== undefined && !isJsonObject(extension) ? null : parseMcpServerConfig(rawServer, metadata);
|
|
394
|
+
if (parsed)
|
|
395
|
+
servers[serverId] = parsed;
|
|
396
|
+
}
|
|
397
|
+
return { ok: true, config: { serverIds: entries.map(([serverId]) => serverId), servers } };
|
|
398
|
+
}
|
|
399
|
+
/** Typed extension reader: unknown namespaces have no semantics until a host supplies a reader. */
|
|
400
|
+
export function readAgentPluginExtension(required) {
|
|
401
|
+
if (!required.namespace.trim())
|
|
402
|
+
throw new Error('An extension namespace is required');
|
|
403
|
+
const extensions = required.manifest.extensions;
|
|
404
|
+
if (!extensions || !Object.hasOwn(extensions, required.namespace))
|
|
405
|
+
return undefined;
|
|
406
|
+
const value = extensions[required.namespace];
|
|
407
|
+
return isJsonObject(value) ? required.read({ value }) : undefined;
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentPluginMcpProvisioningPort } from './ports.js';
|
|
2
|
+
import type { InstalledAgentPlugin } from './install.js';
|
|
3
|
+
/** A host performs MCP federation. Notify only after its durable provisioning step succeeds. */
|
|
4
|
+
export declare function provisionAgentPluginMcp(required: {
|
|
5
|
+
readonly mcpProvisioning: AgentPluginMcpProvisioningPort;
|
|
6
|
+
readonly workspaceId: string;
|
|
7
|
+
readonly installed: InstalledAgentPlugin;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
export declare function syncAgentPluginMcpEnabled(required: {
|
|
10
|
+
readonly mcpProvisioning: AgentPluginMcpProvisioningPort;
|
|
11
|
+
readonly workspaceId: string;
|
|
12
|
+
readonly pluginId: string;
|
|
13
|
+
readonly enabled: boolean;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
export declare function removeAgentPluginMcp(required: {
|
|
16
|
+
readonly mcpProvisioning: AgentPluginMcpProvisioningPort;
|
|
17
|
+
readonly workspaceId: string;
|
|
18
|
+
readonly pluginId: string;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=mcp-provisioning.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A host performs MCP federation. Notify only after its durable provisioning step succeeds. */
|
|
2
|
+
export async function provisionAgentPluginMcp(required) {
|
|
3
|
+
await required.mcpProvisioning.provision({ workspaceId: required.workspaceId, installed: required.installed });
|
|
4
|
+
await required.mcpProvisioning.notifyRosterChanged({ workspaceId: required.workspaceId });
|
|
5
|
+
}
|
|
6
|
+
export async function syncAgentPluginMcpEnabled(required) {
|
|
7
|
+
await required.mcpProvisioning.setEnabled({ workspaceId: required.workspaceId, pluginId: required.pluginId, enabled: required.enabled });
|
|
8
|
+
await required.mcpProvisioning.notifyRosterChanged({ workspaceId: required.workspaceId });
|
|
9
|
+
}
|
|
10
|
+
export async function removeAgentPluginMcp(required) {
|
|
11
|
+
await required.mcpProvisioning.remove({ workspaceId: required.workspaceId, pluginId: required.pluginId });
|
|
12
|
+
await required.mcpProvisioning.notifyRosterChanged({ workspaceId: required.workspaceId });
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=mcp-provisioning.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentPluginClockPort, AgentPluginIdsPort, AgentPluginProcessPort, FilesystemPort } from './ports.js';
|
|
2
|
+
/** Opt-in native effects. Layout, fetch, guard, namespace and provisioning remain host-owned. */
|
|
3
|
+
export declare function createNodeAgentPluginEffects(_required: Record<string, never>, optional?: {
|
|
4
|
+
readonly filesystem?: FilesystemPort;
|
|
5
|
+
}): {
|
|
6
|
+
filesystem: FilesystemPort;
|
|
7
|
+
clock: AgentPluginClockPort;
|
|
8
|
+
ids: AgentPluginIdsPort;
|
|
9
|
+
process: AgentPluginProcessPort;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as nativeFilesystem from 'node:fs/promises';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { performance } from 'node:perf_hooks';
|
|
5
|
+
import { setTimeout } from 'node:timers/promises';
|
|
6
|
+
/** Opt-in native effects. Layout, fetch, guard, namespace and provisioning remain host-owned. */
|
|
7
|
+
export function createNodeAgentPluginEffects(_required, optional = {}) {
|
|
8
|
+
return {
|
|
9
|
+
filesystem: optional.filesystem ?? nativeFilesystem,
|
|
10
|
+
clock: {
|
|
11
|
+
nowMs: () => Date.now(),
|
|
12
|
+
monotonicMs: () => performance.now(),
|
|
13
|
+
sleep: ({ ms }) => setTimeout(ms),
|
|
14
|
+
},
|
|
15
|
+
ids: { newId: () => randomUUID(), random: () => Math.random() },
|
|
16
|
+
process: {
|
|
17
|
+
pid: process.pid,
|
|
18
|
+
platform: process.platform,
|
|
19
|
+
hostname: () => os.hostname(),
|
|
20
|
+
isAlive: ({ pid }) => {
|
|
21
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
22
|
+
return false;
|
|
23
|
+
try {
|
|
24
|
+
process.kill(pid, 0);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return !(typeof error === 'object' && error !== null && 'code' in error && error.code === 'ESRCH');
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentPluginLifecyclePorts } from "./ports.js";
|
|
2
|
+
export declare class PackagePathViolation extends Error {
|
|
3
|
+
constructor({ message }: {
|
|
4
|
+
readonly message: string;
|
|
5
|
+
}, optional?: ErrorOptions);
|
|
6
|
+
}
|
|
7
|
+
/** Internal composition; one state/queue instance per injected host context. */
|
|
8
|
+
export declare function createPackagePathsModule(ports: AgentPluginLifecyclePorts): {
|
|
9
|
+
normalizePackageEntryPath: (rawEntryPath: string) => string;
|
|
10
|
+
assertContainedOnDisk: (packageRoot: string, entryPath: string) => Promise<string>;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=package-paths.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Extracted from the host plugin lifecycle. Effects are supplied per host context. */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class PackagePathViolation extends Error {
|
|
4
|
+
constructor({ message }, optional = {}) { super(message, optional); }
|
|
5
|
+
}
|
|
6
|
+
function buildModule(ports) {
|
|
7
|
+
const realpath = ports.filesystem.realpath.bind(ports.filesystem);
|
|
8
|
+
function normalizePackageEntryPath(rawEntryPath) {
|
|
9
|
+
if (rawEntryPath.includes("\0")) {
|
|
10
|
+
throw new PackagePathViolation({ message: `package entry path contains a NUL byte: '${rawEntryPath}'` });
|
|
11
|
+
}
|
|
12
|
+
const portable = rawEntryPath.replaceAll("\\", "/");
|
|
13
|
+
if (path.posix.isAbsolute(portable) || /^[a-zA-Z]:/.test(portable)) {
|
|
14
|
+
throw new PackagePathViolation({ message: `package entry path must be relative: '${rawEntryPath}'` });
|
|
15
|
+
}
|
|
16
|
+
const normalized = path.posix.normalize(portable);
|
|
17
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized === "." || normalized === "") {
|
|
18
|
+
throw new PackagePathViolation({ message: `package entry path escapes the package root: '${rawEntryPath}'` });
|
|
19
|
+
}
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
async function assertContainedOnDisk(packageRoot, entryPath) {
|
|
23
|
+
const normalized = normalizePackageEntryPath(entryPath);
|
|
24
|
+
const realRoot = await realpath(packageRoot);
|
|
25
|
+
const lexicalCandidate = path.resolve(realRoot, normalized);
|
|
26
|
+
if (!isWithin(realRoot, lexicalCandidate)) {
|
|
27
|
+
throw new PackagePathViolation({ message: `package entry resolves outside the package root: '${entryPath}'` });
|
|
28
|
+
}
|
|
29
|
+
let realCandidate;
|
|
30
|
+
try {
|
|
31
|
+
realCandidate = await realpath(lexicalCandidate);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
realCandidate = await realpathDeepestExistingAncestor(lexicalCandidate);
|
|
35
|
+
}
|
|
36
|
+
if (!isWithin(realRoot, realCandidate)) {
|
|
37
|
+
throw new PackagePathViolation({ message: `package entry resolves outside the package root via a symlink: '${entryPath}'` });
|
|
38
|
+
}
|
|
39
|
+
return lexicalCandidate;
|
|
40
|
+
}
|
|
41
|
+
function isWithin(root, candidate) {
|
|
42
|
+
const relative = path.relative(root, candidate);
|
|
43
|
+
return relative === "" || (!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`));
|
|
44
|
+
}
|
|
45
|
+
async function realpathDeepestExistingAncestor(candidate) {
|
|
46
|
+
let current = path.dirname(candidate);
|
|
47
|
+
const tail = [path.basename(candidate)];
|
|
48
|
+
for (;;) {
|
|
49
|
+
try {
|
|
50
|
+
const real = await realpath(current);
|
|
51
|
+
return path.join(real, ...tail);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
const parent = path.dirname(current);
|
|
55
|
+
if (parent === current)
|
|
56
|
+
return path.join(current, ...tail); // reached the filesystem root
|
|
57
|
+
tail.unshift(path.basename(current));
|
|
58
|
+
current = parent;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { normalizePackageEntryPath, assertContainedOnDisk };
|
|
63
|
+
}
|
|
64
|
+
const instances = new WeakMap();
|
|
65
|
+
/** Internal composition; one state/queue instance per injected host context. */
|
|
66
|
+
export function createPackagePathsModule(ports) {
|
|
67
|
+
let instance = instances.get(ports);
|
|
68
|
+
if (!instance) {
|
|
69
|
+
instance = buildModule(ports);
|
|
70
|
+
instances.set(ports, instance);
|
|
71
|
+
}
|
|
72
|
+
return instance;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=package-paths.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Clock, IdGenerator } from "@jini-ai/core/primitives";
|
|
2
|
+
import type { AgentPluginServerMetadataReader } from './manifest.js';
|
|
3
|
+
import type { AgentPluginLayoutPort } from './layout.js';
|
|
4
|
+
import type { AgentPluginDeliveryMode, InstalledAgentPlugin } from './types.js';
|
|
5
|
+
/** Native Node filesystem ABI, narrowed to the operations used by lifecycle.
|
|
6
|
+
* Inject instrumented fs/promises effects directly; native handles are never rewrapped. */
|
|
7
|
+
export type FilesystemPort = Pick<typeof import('node:fs/promises'), 'chmod' | 'mkdir' | 'mkdtemp' | 'open' | 'readdir' | 'readFile' | 'rename' | 'rm' | 'stat' | 'realpath' | 'unlink'>;
|
|
8
|
+
export type AgentPluginFetchPort = (required: {
|
|
9
|
+
readonly url: string;
|
|
10
|
+
}, optional?: RequestInit) => Promise<Response>;
|
|
11
|
+
export type AgentPluginClockPort = Clock & {
|
|
12
|
+
monotonicMs(): number;
|
|
13
|
+
sleep(required: {
|
|
14
|
+
readonly ms: number;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
export type AgentPluginIdsPort = IdGenerator & {
|
|
18
|
+
random(): number;
|
|
19
|
+
};
|
|
20
|
+
export interface AgentPluginProcessPort {
|
|
21
|
+
readonly pid: number;
|
|
22
|
+
readonly platform: NodeJS.Platform;
|
|
23
|
+
hostname(required: Record<string, never>): string;
|
|
24
|
+
isAlive(required: {
|
|
25
|
+
readonly pid: number;
|
|
26
|
+
}): boolean;
|
|
27
|
+
}
|
|
28
|
+
/** The host implements federation in its own composition layer. No MCP dependency is required. */
|
|
29
|
+
export interface AgentPluginMcpProvisioningPort {
|
|
30
|
+
provision(required: {
|
|
31
|
+
readonly workspaceId: string;
|
|
32
|
+
readonly installed: InstalledAgentPlugin;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
setEnabled(required: {
|
|
35
|
+
readonly workspaceId: string;
|
|
36
|
+
readonly pluginId: string;
|
|
37
|
+
readonly enabled: boolean;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
remove(required: {
|
|
40
|
+
readonly workspaceId: string;
|
|
41
|
+
readonly pluginId: string;
|
|
42
|
+
}): Promise<void>;
|
|
43
|
+
notifyRosterChanged(required: {
|
|
44
|
+
readonly workspaceId: string;
|
|
45
|
+
}): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export interface AgentPluginOutboundGuardPort {
|
|
48
|
+
/** Must reject forbidden destinations before every request, including each redirect. A fetch
|
|
49
|
+
* adapter that pins DNS at connection time is responsible for preventing DNS rebinding. */
|
|
50
|
+
assertAllowed(required: {
|
|
51
|
+
readonly url: string;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export interface AgentPluginLifecyclePorts {
|
|
55
|
+
readonly onEvent?: ((required: {
|
|
56
|
+
readonly event: string;
|
|
57
|
+
readonly message: string;
|
|
58
|
+
}) => void) | undefined;
|
|
59
|
+
readonly formatPluginToolPointer: (required: {
|
|
60
|
+
readonly pluginId: string;
|
|
61
|
+
}) => string;
|
|
62
|
+
readonly seededEnabledPluginIds: ReadonlySet<string>;
|
|
63
|
+
readonly retiredBundledPlugins: ReadonlyMap<string, string>;
|
|
64
|
+
readonly bundledArchiveMagic: string;
|
|
65
|
+
readonly filesystem: FilesystemPort;
|
|
66
|
+
readonly clock: AgentPluginClockPort;
|
|
67
|
+
readonly ids: AgentPluginIdsPort;
|
|
68
|
+
readonly process: AgentPluginProcessPort;
|
|
69
|
+
readonly layout: AgentPluginLayoutPort;
|
|
70
|
+
readonly productName: string;
|
|
71
|
+
readonly extensionNamespace: string;
|
|
72
|
+
readonly readServerMetadata?: AgentPluginServerMetadataReader | undefined;
|
|
73
|
+
readonly deliveryMode: AgentPluginDeliveryMode;
|
|
74
|
+
readonly mcpProvisioning: AgentPluginMcpProvisioningPort;
|
|
75
|
+
readonly fetch: AgentPluginFetchPort;
|
|
76
|
+
readonly outboundGuard: AgentPluginOutboundGuardPort;
|
|
77
|
+
}
|
|
78
|
+
/** Application policy/effects are required; observers and metadata translators are optional. */
|
|
79
|
+
export type AgentPluginLifecycleRequired = Omit<AgentPluginLifecyclePorts, 'onEvent' | 'readServerMetadata'>;
|
|
80
|
+
export type AgentPluginLifecycleOptional = Pick<AgentPluginLifecyclePorts, 'onEvent' | 'readServerMetadata'>;
|
|
81
|
+
//# sourceMappingURL=ports.d.ts.map
|