@stratusagent/plugins 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stratus Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @stratusagent/plugins
2
+
3
+ The plugin host: what a plugin claims, what it is allowed to register, and how
4
+ a config block becomes running capability.
5
+
6
+ The contract lives in
7
+ [`docs/architecture/plugins.md`](../../docs/architecture/plugins.md) and is not
8
+ re-derived here. This package is what makes it enforceable.
9
+
10
+ You do not install this directly — it comes with `@stratusagent/cli`. You do
11
+ read it if you are writing a plugin.
12
+
13
+ ## What a plugin author needs
14
+
15
+ A `stratus` field in `package.json`, and one named export:
16
+
17
+ ```jsonc
18
+ {
19
+ "name": "stratus-plugin-notes",
20
+ "keywords": ["stratus-plugin"],
21
+ "stratus": {
22
+ "pluginVersion": 1,
23
+ "contributes": {
24
+ "tools": [
25
+ { "name": "notes.read", "risk": "safe" },
26
+ { "name": "notes.write", "risk": "gated" }
27
+ ]
28
+ },
29
+ "config": {
30
+ "type": "object",
31
+ "properties": { "vault": { "type": "string" } },
32
+ "required": ["vault"]
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ ```ts
39
+ export const createPlugin = (config) => ({
40
+ name: 'stratus-plugin-notes',
41
+ setup(context) {
42
+ context.tools.register(readTool(config));
43
+ },
44
+ // Optional. Only if setup acquired something a daemon shutdown must release.
45
+ async dispose() {},
46
+ });
47
+ ```
48
+
49
+ ## What the host does with it
50
+
51
+ - **Reads the manifest without importing the package.** A misconfigured or
52
+ over-reaching plugin fails at load, with its own name in the message, rather
53
+ than at the first tool call in the middle of somebody's turn.
54
+ - **Validates your config against your schema** — including each per-agent
55
+ entry under `agents`, with `required` relaxed there, because an override says
56
+ what differs for one agent rather than restating the defaults.
57
+ - **Hands `setup()` a manifest-bound registry, not the real one.** A name your
58
+ manifest does not declare is refused. The risk a tool registers at is the
59
+ riskiest of three claims — your manifest's, the floor your package is held
60
+ to, and whatever the object says about itself — so a tool can raise itself
61
+ and can never talk its way below what it declared.
62
+ - **Stages and commits.** Nothing reaches the shared registry until your whole
63
+ `setup()` has succeeded, so a plugin that over-reaches on its fourth tool
64
+ leaves none of the first three behind.
65
+ - **Refuses collisions.** Two packages contributing one tool name is a
66
+ load-time error naming both, never whichever loaded last.
67
+
68
+ ## Resolving settings per call
69
+
70
+ ```ts
71
+ import { resolvePluginAgentConfig } from '@stratusagent/plugins';
72
+
73
+ async execute(input, session) {
74
+ const settings = resolvePluginAgentConfig(config, session.agent.id);
75
+ …
76
+ }
77
+ ```
78
+
79
+ Per call, from the session — not once at setup. For a plugin whose settings
80
+ are an access boundary (`tool-fs` roots are the example), resolving once hands
81
+ every agent whichever value was resolved first, which is one agent reading
82
+ another's files from code that looks correct.
83
+
84
+ ## `loadOptionalModule`
85
+
86
+ The resolve-then-import split, in one place:
87
+
88
+ ```ts
89
+ const slack = await loadOptionalModule('@stratusagent/channel-slack', {
90
+ resolve: (id) => import.meta.resolve(id),
91
+ import: (id) => import(id),
92
+ });
93
+ ```
94
+
95
+ Only a package failing to **resolve** means "not installed". One that is
96
+ installed but missing a dependency of its own throws `ERR_MODULE_NOT_FOUND`
97
+ too, naming that dependency — so reading the import's error would turn a broken
98
+ install into a silently disabled feature.
99
+
100
+ The two capabilities come from the caller because `import.meta.resolve` answers
101
+ relative to the module that calls it: resolvable *from the daemon* and
102
+ resolvable *from this package* are different questions, and only the first is
103
+ the one being asked.
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The plugin host: what a plugin claims, what it is allowed to register,
3
+ * and how a config block becomes running capability.
4
+ *
5
+ * The contract lives in `docs/architecture/plugins.md` and is not
6
+ * re-derived here. What this package adds is enforcement — a manifest read
7
+ * without importing the package it describes, a registration view that
8
+ * holds `setup()` to that manifest, and a loader that refuses rather than
9
+ * degrades when a plugin over-reaches.
10
+ */
11
+ export { declaredRiskFor, parsePluginManifest, resolvePluginAgentConfig, validateAgainstSchema, validatePluginConfig, HOST_CONFIG_KEYS, PLUGIN_MANIFEST_VERSION, PluginConfigError, PluginManifestError, type PluginContributions, type PluginManifest, type PluginNamespaceDeclaration, type PluginSkillDeclaration, type PluginToolDeclaration, } from './manifest.ts';
12
+ export { ManifestBoundToolRegistry, riskFloorFor, type PluginToolRecord, } from './registry.ts';
13
+ export { isFirstPartyPackage, loadOptionalModule, loadPlugins, type CreatePlugin, type LoadedPlugin, type LoadPluginsOptions, type LoadPluginsResult, type OptionalModuleHost, type PluginLoadFailure, } from './loader.ts';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,yBAAyB,EACzB,YAAY,EACZ,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,GACvB,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The plugin host: what a plugin claims, what it is allowed to register,
3
+ * and how a config block becomes running capability.
4
+ *
5
+ * The contract lives in `docs/architecture/plugins.md` and is not
6
+ * re-derived here. What this package adds is enforcement — a manifest read
7
+ * without importing the package it describes, a registration view that
8
+ * holds `setup()` to that manifest, and a loader that refuses rather than
9
+ * degrades when a plugin over-reaches.
10
+ */
11
+ export { declaredRiskFor, parsePluginManifest, resolvePluginAgentConfig, validateAgainstSchema, validatePluginConfig, HOST_CONFIG_KEYS, PLUGIN_MANIFEST_VERSION, PluginConfigError, PluginManifestError, } from "./manifest.js";
12
+ export { ManifestBoundToolRegistry, riskFloorFor, } from "./registry.js";
13
+ export { isFirstPartyPackage, loadOptionalModule, loadPlugins, } from "./loader.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,GAMpB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,yBAAyB,EACzB,YAAY,GAEb,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,GAOZ,MAAM,aAAa,CAAC"}
@@ -0,0 +1,105 @@
1
+ import type { EventBus, JsonObject, Plugin, ToolRegistry } from '@stratusagent/core';
2
+ import { type PluginManifest } from './manifest.ts';
3
+ import { type PluginToolRecord } from './registry.ts';
4
+ /**
5
+ * The two capabilities loading an optional package needs, taken from the
6
+ * caller rather than used here.
7
+ *
8
+ * `import.meta.resolve` answers relative to the module that calls it, so a
9
+ * helper that called its own would answer for this package's node_modules
10
+ * rather than the host's — resolvable from the daemon and resolvable from
11
+ * here are different questions, and only the first one is the one being
12
+ * asked. Callers pass `{ resolve: (id) => import.meta.resolve(id), import:
13
+ * (id) => import(id) }` and get the rule without a copy of it.
14
+ */
15
+ export interface OptionalModuleHost {
16
+ resolve(specifier: string): string;
17
+ import(specifier: string): Promise<unknown>;
18
+ }
19
+ /**
20
+ * Load a package that may not be installed, or report that it is not.
21
+ *
22
+ * Resolution and loading are separate questions, and only the first one
23
+ * means "not installed". Inspecting the import's error cannot tell them
24
+ * apart: a package that IS installed but is missing one of its own
25
+ * dependencies throws `ERR_MODULE_NOT_FOUND` too, naming that dependency —
26
+ * so a broken install would read as an absent one, silently disabling
27
+ * something whose configuration says it should be running.
28
+ *
29
+ * Extracted because there were three copies of it — the Slack adapter, the
30
+ * control API, and the dashboard resolved from inside the control API —
31
+ * and a plugin loader would have been the fourth.
32
+ */
33
+ export declare const loadOptionalModule: <T = unknown>(specifier: string, host: OptionalModuleHost) => Promise<T | undefined>;
34
+ /** The ABI every loadable plugin exports. See `plugins.md`. */
35
+ export type CreatePlugin = (config: JsonObject) => Plugin | Promise<Plugin>;
36
+ /**
37
+ * Whether a package's code is trusted — which is to say whether its
38
+ * manifest may declare a tool `safe`.
39
+ *
40
+ * First-party packages ship from this repository and pass its CI; anything
41
+ * else floors at `gated`. An operator can widen the set deliberately, and
42
+ * doing so is the same act as enabling the plugin at all: enablement is the
43
+ * security boundary, and this is a statement about how far it extends.
44
+ */
45
+ export declare const isFirstPartyPackage: (packageName: string) => boolean;
46
+ export interface LoadedPlugin {
47
+ /** The package name — a plugin's identity is its package. */
48
+ package: string;
49
+ /** What the plugin called itself in its `Plugin.name`. */
50
+ name: string;
51
+ manifest: PluginManifest;
52
+ trusted: boolean;
53
+ tools: PluginToolRecord[];
54
+ /**
55
+ * The plugin itself, so the host can shut it down. A browser plugin holds
56
+ * a Chromium and a listening socket; a daemon that stopped without
57
+ * telling it would leak both.
58
+ */
59
+ instance: Plugin;
60
+ }
61
+ /** A plugin an operator asked for that did not load, and why. */
62
+ export interface PluginLoadFailure {
63
+ package: string;
64
+ reason: string;
65
+ }
66
+ export interface LoadPluginsOptions {
67
+ /** The `plugins` block: package name to that package's settings. */
68
+ config: Record<string, JsonObject> | undefined;
69
+ host: OptionalModuleHost;
70
+ /** The registry a plugin's tools are committed into once it loads whole. */
71
+ tools: ToolRegistry;
72
+ bus: EventBus;
73
+ /**
74
+ * Where tool output belongs on this machine. Supplied to any plugin whose
75
+ * schema declares `workspaceRoot` and whose operator did not set one —
76
+ * the host knows the platform's answer (`~/.stratus/workspaces`) and a
77
+ * plugin that had to derive it would be re-deriving a path this
78
+ * repository owns.
79
+ */
80
+ workspaceRoot?: string;
81
+ /** Overrides the trusted set. See `isFirstPartyPackage`. */
82
+ trusted?: (packageName: string) => boolean;
83
+ }
84
+ export interface LoadPluginsResult {
85
+ loaded: LoadedPlugin[];
86
+ failures: PluginLoadFailure[];
87
+ }
88
+ /**
89
+ * Turn a `plugins` config block into running capability.
90
+ *
91
+ * Ordered the way the trust model requires: **nothing auto-loads** (only
92
+ * what is listed and enabled, and callers pass this only what a trusted
93
+ * config named), the **manifest is validated before the module is
94
+ * imported**, and `setup()` registers through the manifest-bound view
95
+ * rather than the raw registry.
96
+ *
97
+ * One plugin failing does not take the others — or the daemon — down: a
98
+ * failure is collected, named, and reported to the operator, because a
99
+ * daemon that refuses to start over a mistyped package name is a worse
100
+ * outcome than one running the plugins that do load and saying which did
101
+ * not. What is *never* degraded is the security half: an undeclared name, a
102
+ * collision, or a manifest that will not parse refuses that plugin whole.
103
+ */
104
+ export declare const loadPlugins: (options: LoadPluginsOptions) => Promise<LoadPluginsResult>;
105
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAErF,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjF;;;;;;;;;;GAUG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7C;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,kBAAkB,GAAU,CAAC,GAAG,OAAO,EAClD,WAAW,MAAM,EACjB,MAAM,kBAAkB,KACvB,OAAO,CAAC,CAAC,GAAG,SAAS,CASvB,CAAC;AAEF,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAuB5E;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,aAAa,MAAM,KAAG,OACmB,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC;IAC/C,IAAI,EAAE,kBAAkB,CAAC;IACzB,4EAA4E;IAC5E,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,QAAQ,CAAC;IACd;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAuBD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,WAAW,GAAU,SAAS,kBAAkB,KAAG,OAAO,CAAC,iBAAiB,CA0ExF,CAAC"}
package/dist/loader.js ADDED
@@ -0,0 +1,158 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { parsePluginManifest, validatePluginConfig, PluginManifestError, } from "./manifest.js";
5
+ import { ManifestBoundToolRegistry } from "./registry.js";
6
+ /**
7
+ * Load a package that may not be installed, or report that it is not.
8
+ *
9
+ * Resolution and loading are separate questions, and only the first one
10
+ * means "not installed". Inspecting the import's error cannot tell them
11
+ * apart: a package that IS installed but is missing one of its own
12
+ * dependencies throws `ERR_MODULE_NOT_FOUND` too, naming that dependency —
13
+ * so a broken install would read as an absent one, silently disabling
14
+ * something whose configuration says it should be running.
15
+ *
16
+ * Extracted because there were three copies of it — the Slack adapter, the
17
+ * control API, and the dashboard resolved from inside the control API —
18
+ * and a plugin loader would have been the fourth.
19
+ */
20
+ export const loadOptionalModule = async (specifier, host) => {
21
+ try {
22
+ host.resolve(specifier);
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ // Resolvable: any failure from here is a real problem with the installed
28
+ // package, and surfaces.
29
+ return (await host.import(specifier));
30
+ };
31
+ /** Where a package's own package.json is, given something it resolved to. */
32
+ const packageJsonFor = async (resolvedUrl, specifier) => {
33
+ let directory = path.dirname(fileURLToPath(resolvedUrl));
34
+ // Bounded rather than "until the filesystem root": a walk that reaches
35
+ // `/` would read some unrelated package.json and validate a manifest
36
+ // that never described this package.
37
+ for (let depth = 0; depth < 8; depth += 1) {
38
+ try {
39
+ const raw = await readFile(path.join(directory, 'package.json'), 'utf8');
40
+ return JSON.parse(raw);
41
+ }
42
+ catch {
43
+ const parent = path.dirname(directory);
44
+ if (parent === directory) {
45
+ break;
46
+ }
47
+ directory = parent;
48
+ }
49
+ }
50
+ throw new PluginManifestError(`Could not find a package.json for ${specifier}.`);
51
+ };
52
+ /**
53
+ * Whether a package's code is trusted — which is to say whether its
54
+ * manifest may declare a tool `safe`.
55
+ *
56
+ * First-party packages ship from this repository and pass its CI; anything
57
+ * else floors at `gated`. An operator can widen the set deliberately, and
58
+ * doing so is the same act as enabling the plugin at all: enablement is the
59
+ * security boundary, and this is a statement about how far it extends.
60
+ */
61
+ export const isFirstPartyPackage = (packageName) => packageName === '@stratusagent' || packageName.startsWith('@stratusagent/');
62
+ const WORKSPACE_ROOT_KEY = 'workspaceRoot';
63
+ const configFor = (block, manifest, workspaceRoot) => {
64
+ const { enabled: _enabled, ...rest } = block;
65
+ const declaresWorkspace = Boolean(manifest.config
66
+ && typeof manifest.config.properties === 'object'
67
+ && manifest.config.properties !== null
68
+ && !Array.isArray(manifest.config.properties)
69
+ && WORKSPACE_ROOT_KEY in manifest.config.properties);
70
+ if (declaresWorkspace && workspaceRoot !== undefined && rest[WORKSPACE_ROOT_KEY] === undefined) {
71
+ return { ...rest, [WORKSPACE_ROOT_KEY]: workspaceRoot };
72
+ }
73
+ return rest;
74
+ };
75
+ /**
76
+ * Turn a `plugins` config block into running capability.
77
+ *
78
+ * Ordered the way the trust model requires: **nothing auto-loads** (only
79
+ * what is listed and enabled, and callers pass this only what a trusted
80
+ * config named), the **manifest is validated before the module is
81
+ * imported**, and `setup()` registers through the manifest-bound view
82
+ * rather than the raw registry.
83
+ *
84
+ * One plugin failing does not take the others — or the daemon — down: a
85
+ * failure is collected, named, and reported to the operator, because a
86
+ * daemon that refuses to start over a mistyped package name is a worse
87
+ * outcome than one running the plugins that do load and saying which did
88
+ * not. What is *never* degraded is the security half: an undeclared name, a
89
+ * collision, or a manifest that will not parse refuses that plugin whole.
90
+ */
91
+ export const loadPlugins = async (options) => {
92
+ const entries = Object.entries(options.config ?? {});
93
+ const trusted = options.trusted ?? isFirstPartyPackage;
94
+ const loaded = [];
95
+ const failures = [];
96
+ // Which package owns each name so far, so a collision can name both.
97
+ const owners = new Map();
98
+ for (const [specifier, rawBlock] of entries) {
99
+ const block = rawBlock ?? {};
100
+ if (block.enabled === false) {
101
+ continue;
102
+ }
103
+ // Held outside the try so a plugin that was *constructed* and then
104
+ // failed can still be told to let go. A plugin acquires its resources
105
+ // in `createPlugin` and `setup` — a subscription on the bus, a socket,
106
+ // a child process — and a load that fails after that point leaves them
107
+ // held for the life of a daemon that goes on running without it.
108
+ let instance;
109
+ try {
110
+ const resolved = options.host.resolve(specifier);
111
+ const manifest = parsePluginManifest(await packageJsonFor(resolved, specifier), specifier);
112
+ const isTrusted = trusted(manifest.packageName);
113
+ // Validated *after* the host's defaults are folded in, because that
114
+ // is the configuration the plugin will actually be handed: a manifest
115
+ // that declares `workspaceRoot` required would otherwise be refused
116
+ // for missing the very setting the host supplies.
117
+ const config = configFor(block, manifest, options.workspaceRoot);
118
+ validatePluginConfig(manifest, config);
119
+ const module = (await options.host.import(specifier));
120
+ if (typeof module.createPlugin !== 'function') {
121
+ throw new PluginManifestError(`Plugin ${manifest.packageName} does not export createPlugin(config). See docs/architecture/plugins.md.`);
122
+ }
123
+ const plugin = await module.createPlugin(config);
124
+ if (!plugin || typeof plugin.setup !== 'function') {
125
+ throw new PluginManifestError(`Plugin ${manifest.packageName}: createPlugin did not return a plugin with a setup(context).`);
126
+ }
127
+ instance = plugin;
128
+ const view = new ManifestBoundToolRegistry({ manifest, target: options.tools, trusted: isTrusted });
129
+ await plugin.setup({ bus: options.bus, tools: view });
130
+ const tools = view.commit(owners);
131
+ loaded.push({
132
+ package: manifest.packageName,
133
+ name: typeof plugin.name === 'string' && plugin.name.length > 0 ? plugin.name : manifest.packageName,
134
+ manifest,
135
+ trusted: isTrusted,
136
+ tools,
137
+ instance: plugin,
138
+ });
139
+ }
140
+ catch (error) {
141
+ // Refused, and then released. A plugin whose `dispose` also throws is
142
+ // ignored: it is already being reported as failed, and the second
143
+ // failure would replace the reason that says why.
144
+ try {
145
+ await instance?.dispose?.();
146
+ }
147
+ catch {
148
+ // Nothing more to do for a plugin that cannot even let go.
149
+ }
150
+ failures.push({
151
+ package: specifier,
152
+ reason: error instanceof Error ? error.message : String(error),
153
+ });
154
+ }
155
+ }
156
+ return { loaded, failures };
157
+ };
158
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import { type JsonObject, type JsonValue, type ToolRisk } from '@stratusagent/core';
2
+ /**
3
+ * The manifest version this host understands. A plugin declaring anything
4
+ * else is refused rather than read optimistically: the whole point of the
5
+ * manifest is that the daemon can trust what it says about a package it has
6
+ * not imported, and reading an unknown dialect on a guess gives that up.
7
+ */
8
+ export declare const PLUGIN_MANIFEST_VERSION = 1;
9
+ /** A tool the manifest names outright. */
10
+ export interface PluginToolDeclaration {
11
+ name: string;
12
+ risk: ToolRisk;
13
+ }
14
+ /**
15
+ * A namespace the manifest claims instead of naming what is inside it —
16
+ * the form a bridge whose tool names exist only at connect time can be
17
+ * declared under. The risk is a ceiling on trust, never a floor the plugin
18
+ * picks: everything discovered under it registers at this risk or higher.
19
+ */
20
+ export interface PluginNamespaceDeclaration {
21
+ namespace: string;
22
+ risk: ToolRisk;
23
+ }
24
+ export interface PluginSkillDeclaration {
25
+ id: string;
26
+ path: string;
27
+ }
28
+ export interface PluginContributions {
29
+ tools: PluginToolDeclaration[];
30
+ toolsDiscovered: PluginNamespaceDeclaration[];
31
+ /** Parsed and reported, but not yet registrable — skills are step 09. */
32
+ skills: PluginSkillDeclaration[];
33
+ }
34
+ export interface PluginManifest {
35
+ /** The package name, which is the plugin's identity. */
36
+ packageName: string;
37
+ pluginVersion: number;
38
+ contributes: PluginContributions;
39
+ credentials: string[];
40
+ /** JSON Schema for the plugin's own config block, if it declared one. */
41
+ config?: JsonObject;
42
+ }
43
+ /** A manifest that cannot be trusted to describe the package it came from. */
44
+ export declare class PluginManifestError extends Error {
45
+ constructor(message: string);
46
+ }
47
+ /** A config block that does not match what the manifest said it would be. */
48
+ export declare class PluginConfigError extends Error {
49
+ constructor(message: string);
50
+ }
51
+ /**
52
+ * Read the `stratus` field of a package.json into a manifest, or refuse it.
53
+ *
54
+ * Nothing here imports the package — that is the property the manifest
55
+ * exists for. A misconfigured or over-reaching plugin fails at load, with
56
+ * its own name in the message, rather than at the first tool call in the
57
+ * middle of somebody's turn.
58
+ */
59
+ export declare const parsePluginManifest: (packageJson: unknown, specifier: string) => PluginManifest;
60
+ /**
61
+ * The risk a manifest says a tool registers at, or undefined when the
62
+ * manifest does not authorize the name at all.
63
+ *
64
+ * A literal declaration wins over a namespace: a plugin that both owns
65
+ * `mcp.*` at `gated` and names `mcp.ping` at `safe` gets what it wrote for
66
+ * the name it wrote. Namespace matching is `matchesToolAllowlist` — the
67
+ * same reading of `mcp.*` a soul's allowlist gets, because two readings of
68
+ * one glob is two answers to what a package may register.
69
+ */
70
+ export declare const declaredRiskFor: (manifest: PluginManifest, toolName: string) => ToolRisk | undefined;
71
+ /**
72
+ * Keys the host owns inside a plugin's config block. They are stripped
73
+ * before the plugin's own schema sees the block — a plugin author writes a
74
+ * schema for their settings, not for the machinery that selects them.
75
+ */
76
+ export declare const HOST_CONFIG_KEYS: readonly ["enabled", "agents"];
77
+ /**
78
+ * Check a config block against the manifest's schema.
79
+ *
80
+ * A deliberately small subset of JSON Schema — `type`, `properties`,
81
+ * `required`, `items`, `enum`, and `additionalProperties: false` — chosen
82
+ * so that the daemon can answer "is this configuration well-formed" without
83
+ * carrying a validator library into every install. Keywords outside the
84
+ * subset are ignored rather than refused, so a schema written against the
85
+ * full standard still loads; what it buys is fewer checks, never a false
86
+ * pass on a keyword we pretended to understand.
87
+ */
88
+ export declare const validateAgainstSchema: (value: JsonValue, schema: JsonObject, where: string, options?: {
89
+ requireRequired?: boolean;
90
+ }) => void;
91
+ /**
92
+ * Validate one plugin's whole config block — the defaults and every
93
+ * per-agent override — against the manifest's schema.
94
+ *
95
+ * The per-agent entries are checked with `required` relaxed, and that is
96
+ * the point of them: an agent block says what differs for that agent, so
97
+ * holding a two-key override to a schema's required list would make the
98
+ * defaults above it unusable.
99
+ */
100
+ export declare const validatePluginConfig: (manifest: PluginManifest, block: JsonObject) => void;
101
+ /**
102
+ * One agent's settings for a plugin: its own entry over the defaults, key
103
+ * by key — the shape `approvals` already uses, and the reason a plugin
104
+ * whose settings are an access boundary must call this **per execution**
105
+ * rather than closing over a value at setup. `session.agent.id` is what
106
+ * makes the answer different for two agents; resolving once at setup gives
107
+ * every agent whichever one happened to be asked about first.
108
+ */
109
+ export declare const resolvePluginAgentConfig: (block: JsonObject | undefined, agentId: string) => JsonObject;
110
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAC;AAE5B;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,0CAA0C;AAC1C,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,qBAAqB,EAAE,CAAC;IAC/B,eAAe,EAAE,0BAA0B,EAAE,CAAC;IAC9C,yEAAyE;IACzE,MAAM,EAAE,sBAAsB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,mBAAmB,CAAC;IACjC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,yEAAyE;IACzE,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,8EAA8E;AAC9E,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,6EAA6E;AAC7E,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AA6BD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAAI,aAAa,OAAO,EAAE,WAAW,MAAM,KAAG,cAyG7E,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,cAAc,EAAE,UAAU,MAAM,KAAG,QAAQ,GAAG,SASvF,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,gCAAiC,CAAC;AAY/D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,SAAS,EAChB,QAAQ,UAAU,EAClB,OAAO,MAAM,EACb,UAAS;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,KAC1C,IA6CF,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,UAAU,cAAc,EAAE,OAAO,UAAU,KAAG,IA6BlF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,GAAI,OAAO,UAAU,GAAG,SAAS,EAAE,SAAS,MAAM,KAAG,UAOzF,CAAC"}
@@ -0,0 +1,264 @@
1
+ import { DEFAULT_TOOL_RISK, matchesToolAllowlist, } from '@stratusagent/core';
2
+ /**
3
+ * The manifest version this host understands. A plugin declaring anything
4
+ * else is refused rather than read optimistically: the whole point of the
5
+ * manifest is that the daemon can trust what it says about a package it has
6
+ * not imported, and reading an unknown dialect on a guess gives that up.
7
+ */
8
+ export const PLUGIN_MANIFEST_VERSION = 1;
9
+ /** A manifest that cannot be trusted to describe the package it came from. */
10
+ export class PluginManifestError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = 'PluginManifestError';
14
+ }
15
+ }
16
+ /** A config block that does not match what the manifest said it would be. */
17
+ export class PluginConfigError extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = 'PluginConfigError';
21
+ }
22
+ }
23
+ // `namespace.verb`, with the nesting a bridged server's names need
24
+ // (`mcp.linear.create_issue`). Underscores are allowed inside a segment
25
+ // because somebody else's API chose those names; the leading character is
26
+ // not, so nothing can be typed to look like a flag or a hidden file.
27
+ const TOOL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9_-]*)+$/;
28
+ const NAMESPACE_PATTERN = /^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9_-]*)*\.\*$/;
29
+ const SKILL_ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
30
+ const RISKS = ['safe', 'gated', 'dangerous'];
31
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
32
+ const parseRisk = (raw, where) => {
33
+ // Undeclared is `gated`, exactly as it is on a Tool: a manifest entry
34
+ // that forgot to think about risk is held back rather than waved through.
35
+ if (raw === undefined) {
36
+ return DEFAULT_TOOL_RISK;
37
+ }
38
+ if (typeof raw !== 'string' || !RISKS.includes(raw)) {
39
+ throw new PluginManifestError(`Invalid risk in ${where}: ${JSON.stringify(raw)}. Use safe, gated, or dangerous.`);
40
+ }
41
+ return raw;
42
+ };
43
+ /**
44
+ * Read the `stratus` field of a package.json into a manifest, or refuse it.
45
+ *
46
+ * Nothing here imports the package — that is the property the manifest
47
+ * exists for. A misconfigured or over-reaching plugin fails at load, with
48
+ * its own name in the message, rather than at the first tool call in the
49
+ * middle of somebody's turn.
50
+ */
51
+ export const parsePluginManifest = (packageJson, specifier) => {
52
+ if (!isObject(packageJson)) {
53
+ throw new PluginManifestError(`Package ${specifier} has no readable package.json.`);
54
+ }
55
+ const packageName = typeof packageJson.name === 'string' && packageJson.name.length > 0
56
+ ? packageJson.name
57
+ : specifier;
58
+ const stratus = packageJson.stratus;
59
+ if (!isObject(stratus)) {
60
+ throw new PluginManifestError(`Package ${packageName} is not a Stratus plugin: its package.json has no "stratus" manifest.`);
61
+ }
62
+ const version = stratus.pluginVersion;
63
+ if (version !== PLUGIN_MANIFEST_VERSION) {
64
+ throw new PluginManifestError(`Plugin ${packageName} declares pluginVersion ${JSON.stringify(version)}; this host understands ${PLUGIN_MANIFEST_VERSION}.`);
65
+ }
66
+ const contributes = isObject(stratus.contributes) ? stratus.contributes : {};
67
+ const tools = [];
68
+ const toolsDiscovered = [];
69
+ const skills = [];
70
+ if (contributes.tools !== undefined) {
71
+ if (!Array.isArray(contributes.tools)) {
72
+ throw new PluginManifestError(`Plugin ${packageName}: contributes.tools must be an array.`);
73
+ }
74
+ for (const entry of contributes.tools) {
75
+ if (!isObject(entry) || typeof entry.name !== 'string') {
76
+ throw new PluginManifestError(`Plugin ${packageName}: every contributes.tools entry needs a "name".`);
77
+ }
78
+ if (!TOOL_NAME_PATTERN.test(entry.name)) {
79
+ throw new PluginManifestError(`Plugin ${packageName}: ${JSON.stringify(entry.name)} is not a tool name. Tools are namespace.verb, lowercase (fs.read).`);
80
+ }
81
+ tools.push({ name: entry.name, risk: parseRisk(entry.risk, `${packageName} tool ${entry.name}`) });
82
+ }
83
+ }
84
+ if (contributes.toolsDiscovered !== undefined) {
85
+ if (!Array.isArray(contributes.toolsDiscovered)) {
86
+ throw new PluginManifestError(`Plugin ${packageName}: contributes.toolsDiscovered must be an array.`);
87
+ }
88
+ for (const entry of contributes.toolsDiscovered) {
89
+ if (!isObject(entry) || typeof entry.namespace !== 'string') {
90
+ throw new PluginManifestError(`Plugin ${packageName}: every contributes.toolsDiscovered entry needs a "namespace".`);
91
+ }
92
+ if (!NAMESPACE_PATTERN.test(entry.namespace)) {
93
+ throw new PluginManifestError(`Plugin ${packageName}: ${JSON.stringify(entry.namespace)} is not a namespace. Declare one as a prefix and a star (mcp.*).`);
94
+ }
95
+ toolsDiscovered.push({
96
+ namespace: entry.namespace,
97
+ risk: parseRisk(entry.risk, `${packageName} namespace ${entry.namespace}`),
98
+ });
99
+ }
100
+ }
101
+ if (contributes.skills !== undefined) {
102
+ if (!Array.isArray(contributes.skills)) {
103
+ throw new PluginManifestError(`Plugin ${packageName}: contributes.skills must be an array.`);
104
+ }
105
+ for (const entry of contributes.skills) {
106
+ if (!isObject(entry) || typeof entry.id !== 'string' || typeof entry.path !== 'string') {
107
+ throw new PluginManifestError(`Plugin ${packageName}: every contributes.skills entry needs an "id" and a "path".`);
108
+ }
109
+ if (!SKILL_ID_PATTERN.test(entry.id)) {
110
+ throw new PluginManifestError(`Plugin ${packageName}: ${JSON.stringify(entry.id)} is not a skill id. Skill ids are kebab-case (web-research).`);
111
+ }
112
+ skills.push({ id: entry.id, path: entry.path });
113
+ }
114
+ }
115
+ const credentials = [];
116
+ if (stratus.credentials !== undefined) {
117
+ if (!Array.isArray(stratus.credentials) || stratus.credentials.some((name) => typeof name !== 'string')) {
118
+ throw new PluginManifestError(`Plugin ${packageName}: credentials must be an array of names.`);
119
+ }
120
+ credentials.push(...stratus.credentials);
121
+ }
122
+ if (stratus.config !== undefined && !isObject(stratus.config)) {
123
+ throw new PluginManifestError(`Plugin ${packageName}: config must be a JSON Schema object.`);
124
+ }
125
+ return {
126
+ packageName,
127
+ pluginVersion: PLUGIN_MANIFEST_VERSION,
128
+ contributes: { tools, toolsDiscovered, skills },
129
+ credentials,
130
+ ...(isObject(stratus.config) ? { config: stratus.config } : {}),
131
+ };
132
+ };
133
+ /**
134
+ * The risk a manifest says a tool registers at, or undefined when the
135
+ * manifest does not authorize the name at all.
136
+ *
137
+ * A literal declaration wins over a namespace: a plugin that both owns
138
+ * `mcp.*` at `gated` and names `mcp.ping` at `safe` gets what it wrote for
139
+ * the name it wrote. Namespace matching is `matchesToolAllowlist` — the
140
+ * same reading of `mcp.*` a soul's allowlist gets, because two readings of
141
+ * one glob is two answers to what a package may register.
142
+ */
143
+ export const declaredRiskFor = (manifest, toolName) => {
144
+ const literal = manifest.contributes.tools.find((entry) => entry.name === toolName);
145
+ if (literal) {
146
+ return literal.risk;
147
+ }
148
+ const namespace = manifest.contributes.toolsDiscovered.find((entry) => matchesToolAllowlist(toolName, [entry.namespace]));
149
+ return namespace?.risk;
150
+ };
151
+ // ---- config validation -----------------------------------------------------
152
+ /**
153
+ * Keys the host owns inside a plugin's config block. They are stripped
154
+ * before the plugin's own schema sees the block — a plugin author writes a
155
+ * schema for their settings, not for the machinery that selects them.
156
+ */
157
+ export const HOST_CONFIG_KEYS = ['enabled', 'agents'];
158
+ const typeOf = (value) => {
159
+ if (value === null)
160
+ return 'null';
161
+ if (Array.isArray(value))
162
+ return 'array';
163
+ if (Number.isInteger(value))
164
+ return 'integer';
165
+ return typeof value;
166
+ };
167
+ const typeMatches = (expected, actual) => expected === actual || (expected === 'number' && actual === 'integer');
168
+ /**
169
+ * Check a config block against the manifest's schema.
170
+ *
171
+ * A deliberately small subset of JSON Schema — `type`, `properties`,
172
+ * `required`, `items`, `enum`, and `additionalProperties: false` — chosen
173
+ * so that the daemon can answer "is this configuration well-formed" without
174
+ * carrying a validator library into every install. Keywords outside the
175
+ * subset are ignored rather than refused, so a schema written against the
176
+ * full standard still loads; what it buys is fewer checks, never a false
177
+ * pass on a keyword we pretended to understand.
178
+ */
179
+ export const validateAgainstSchema = (value, schema, where, options = {}) => {
180
+ const requireRequired = options.requireRequired ?? true;
181
+ const allowedValues = Array.isArray(schema.enum) ? schema.enum : undefined;
182
+ if (allowedValues && !allowedValues.some((allowed) => JSON.stringify(allowed) === JSON.stringify(value))) {
183
+ throw new PluginConfigError(`${where} must be one of ${allowedValues.map((entry) => JSON.stringify(entry)).join(', ')}; got ${JSON.stringify(value)}.`);
184
+ }
185
+ if (typeof schema.type === 'string' && !typeMatches(schema.type, typeOf(value))) {
186
+ throw new PluginConfigError(`${where} must be a ${schema.type}; got ${typeOf(value)}.`);
187
+ }
188
+ if (Array.isArray(value) && schema.items !== undefined && isObject(schema.items)) {
189
+ value.forEach((entry, index) => {
190
+ validateAgainstSchema(entry, schema.items, `${where}[${index}]`, options);
191
+ });
192
+ return;
193
+ }
194
+ if (isObject(value) && !Array.isArray(value)) {
195
+ const properties = isObject(schema.properties) ? schema.properties : undefined;
196
+ if (requireRequired && Array.isArray(schema.required)) {
197
+ for (const key of schema.required) {
198
+ if (typeof key === 'string' && !(key in value)) {
199
+ throw new PluginConfigError(`${where} is missing required setting ${JSON.stringify(key)}.`);
200
+ }
201
+ }
202
+ }
203
+ if (properties) {
204
+ for (const [key, entry] of Object.entries(value)) {
205
+ const propertySchema = properties[key];
206
+ if (propertySchema === undefined) {
207
+ if (schema.additionalProperties === false) {
208
+ throw new PluginConfigError(`${where} has no setting named ${JSON.stringify(key)}.`);
209
+ }
210
+ continue;
211
+ }
212
+ if (isObject(propertySchema)) {
213
+ validateAgainstSchema(entry, propertySchema, `${where}.${key}`, options);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ };
219
+ /**
220
+ * Validate one plugin's whole config block — the defaults and every
221
+ * per-agent override — against the manifest's schema.
222
+ *
223
+ * The per-agent entries are checked with `required` relaxed, and that is
224
+ * the point of them: an agent block says what differs for that agent, so
225
+ * holding a two-key override to a schema's required list would make the
226
+ * defaults above it unusable.
227
+ */
228
+ export const validatePluginConfig = (manifest, block) => {
229
+ const schema = manifest.config;
230
+ if (!schema) {
231
+ return;
232
+ }
233
+ const { enabled: _enabled, agents, ...defaults } = block;
234
+ validateAgainstSchema(defaults, schema, `plugins["${manifest.packageName}"]`);
235
+ if (agents === undefined) {
236
+ return;
237
+ }
238
+ if (!isObject(agents)) {
239
+ throw new PluginConfigError(`plugins["${manifest.packageName}"].agents must be an object keyed by agent id.`);
240
+ }
241
+ for (const [agentId, entry] of Object.entries(agents)) {
242
+ if (!isObject(entry)) {
243
+ throw new PluginConfigError(`plugins["${manifest.packageName}"].agents.${agentId} must be an object of settings.`);
244
+ }
245
+ validateAgainstSchema(entry, schema, `plugins["${manifest.packageName}"].agents.${agentId}`, { requireRequired: false });
246
+ }
247
+ };
248
+ /**
249
+ * One agent's settings for a plugin: its own entry over the defaults, key
250
+ * by key — the shape `approvals` already uses, and the reason a plugin
251
+ * whose settings are an access boundary must call this **per execution**
252
+ * rather than closing over a value at setup. `session.agent.id` is what
253
+ * makes the answer different for two agents; resolving once at setup gives
254
+ * every agent whichever one happened to be asked about first.
255
+ */
256
+ export const resolvePluginAgentConfig = (block, agentId) => {
257
+ if (!block) {
258
+ return {};
259
+ }
260
+ const { enabled: _enabled, agents, ...defaults } = block;
261
+ const overrides = isObject(agents) ? agents[agentId] : undefined;
262
+ return isObject(overrides) ? { ...defaults, ...overrides } : { ...defaults };
263
+ };
264
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import { ToolRegistry, type Tool, type ToolDescriptor, type ToolRisk } from '@stratusagent/core';
2
+ import { type PluginManifest } from './manifest.ts';
3
+ /** What the daemon knows about one registered tool once a plugin is loaded. */
4
+ export interface PluginToolRecord {
5
+ name: string;
6
+ description?: string;
7
+ risk: ToolRisk;
8
+ /** The package whose code this tool is — provenance, kept for risk questions. */
9
+ package: string;
10
+ /** Whether that package is inside the trusted set (see the risk floor). */
11
+ trusted: boolean;
12
+ }
13
+ /**
14
+ * The risk floor a package's tools cannot register below.
15
+ *
16
+ * `safe` means "run this unattended, with nobody watching", and it is not a
17
+ * claim the code being judged gets to make about itself. First-party
18
+ * packages ship in this repository and are gated by its CI, so their
19
+ * manifests are read as written; everything else floors at `gated`.
20
+ */
21
+ export declare const riskFloorFor: (trusted: boolean) => ToolRisk;
22
+ /**
23
+ * The manifest-bound view a plugin's `setup()` registers through.
24
+ *
25
+ * Validating package.json before importing says what a plugin *claims*; it
26
+ * says nothing about what `setup()` then does, and `ToolRegistry.register`
27
+ * is a bare `Map.set` that records neither the originating package nor
28
+ * anything to check a claim against. Handed the raw registry, a plugin
29
+ * could register a tool it never declared, mark it `safe`, and run
30
+ * unattended — which would make the floor decoration. So this view rejects
31
+ * a name the manifest does not declare, applies the declared risk and the
32
+ * floor rather than trusting the object it is handed, and keeps the package
33
+ * as provenance.
34
+ *
35
+ * Registrations are **staged, not committed**. A plugin that registers
36
+ * three tools and then throws — or whose fourth name collides with another
37
+ * package's — must not leave the first three live in a shared registry, so
38
+ * nothing reaches the target until `commit()` says the whole plugin loaded.
39
+ */
40
+ export declare class ManifestBoundToolRegistry extends ToolRegistry {
41
+ private readonly manifest;
42
+ private readonly target;
43
+ private readonly trusted;
44
+ private readonly staged;
45
+ private readonly records;
46
+ constructor(options: {
47
+ manifest: PluginManifest;
48
+ target: ToolRegistry;
49
+ trusted: boolean;
50
+ });
51
+ register(tool: Tool): void;
52
+ /** Reads see the whole registry: a plugin composing on another's tool is not the boundary here. */
53
+ get(name: string): Tool | undefined;
54
+ list(): Tool[];
55
+ describe(): ToolDescriptor[];
56
+ /**
57
+ * Move this plugin's tools into the shared registry, or refuse the whole
58
+ * plugin. A name already registered is a load-time error naming both
59
+ * packages, never a silent overwrite — the same reason a duplicate agent
60
+ * id refuses the roster rather than picking whichever loaded last.
61
+ */
62
+ commit(owners: Map<string, string>): PluginToolRecord[];
63
+ }
64
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,YAAY,EACZ,KAAK,IAAI,EACT,KAAK,cAAc,EACnB,KAAK,QAAQ,EACd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAE1F,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,KAAG,QAAwC,CAAC;AAEzF;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,yBAA0B,SAAQ,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAE1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAElD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;gBAEtC,OAAO,EAAE;QAAE,QAAQ,EAAE,cAAc,CAAC;QAAC,MAAM,EAAE,YAAY,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE;IAOhF,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAuCnC,mGAAmG;IAC1F,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;IAInC,IAAI,IAAI,IAAI,EAAE;IAId,QAAQ,IAAI,cAAc,EAAE;IASrC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,gBAAgB,EAAE;CAsBxD"}
@@ -0,0 +1,111 @@
1
+ import { raiseRiskTo, resolveToolRisk, ToolRegistry, } from '@stratusagent/core';
2
+ import { declaredRiskFor, PluginManifestError } from "./manifest.js";
3
+ /**
4
+ * The risk floor a package's tools cannot register below.
5
+ *
6
+ * `safe` means "run this unattended, with nobody watching", and it is not a
7
+ * claim the code being judged gets to make about itself. First-party
8
+ * packages ship in this repository and are gated by its CI, so their
9
+ * manifests are read as written; everything else floors at `gated`.
10
+ */
11
+ export const riskFloorFor = (trusted) => (trusted ? 'safe' : 'gated');
12
+ /**
13
+ * The manifest-bound view a plugin's `setup()` registers through.
14
+ *
15
+ * Validating package.json before importing says what a plugin *claims*; it
16
+ * says nothing about what `setup()` then does, and `ToolRegistry.register`
17
+ * is a bare `Map.set` that records neither the originating package nor
18
+ * anything to check a claim against. Handed the raw registry, a plugin
19
+ * could register a tool it never declared, mark it `safe`, and run
20
+ * unattended — which would make the floor decoration. So this view rejects
21
+ * a name the manifest does not declare, applies the declared risk and the
22
+ * floor rather than trusting the object it is handed, and keeps the package
23
+ * as provenance.
24
+ *
25
+ * Registrations are **staged, not committed**. A plugin that registers
26
+ * three tools and then throws — or whose fourth name collides with another
27
+ * package's — must not leave the first three live in a shared registry, so
28
+ * nothing reaches the target until `commit()` says the whole plugin loaded.
29
+ */
30
+ export class ManifestBoundToolRegistry extends ToolRegistry {
31
+ manifest;
32
+ target;
33
+ trusted;
34
+ staged = new Map();
35
+ records = [];
36
+ constructor(options) {
37
+ super();
38
+ this.manifest = options.manifest;
39
+ this.target = options.target;
40
+ this.trusted = options.trusted;
41
+ }
42
+ register(tool) {
43
+ const declared = declaredRiskFor(this.manifest, tool.name);
44
+ if (declared === undefined) {
45
+ throw new PluginManifestError(`Plugin ${this.manifest.packageName} tried to register ${tool.name}, which its manifest does not declare. `
46
+ + 'Add it to contributes.tools, or declare the namespace it belongs to in contributes.toolsDiscovered.');
47
+ }
48
+ if (this.staged.has(tool.name)) {
49
+ throw new PluginManifestError(`Plugin ${this.manifest.packageName} registered ${tool.name} twice.`);
50
+ }
51
+ // Three claims, and the effective risk is the riskiest of them: the
52
+ // manifest's declaration, the floor its package is held to, and
53
+ // whatever the object says about itself. The object can raise itself
54
+ // and can never talk its way below what its own package declared.
55
+ const claimed = tool.risk;
56
+ const risk = raiseRiskTo(raiseRiskTo(declared, riskFloorFor(this.trusted)), claimed ?? 'safe');
57
+ // Prototype preserved: a tool may be a class instance or carry the
58
+ // `runtime`/`createCommand` shape the local-command executor detects,
59
+ // and a plain spread would quietly turn either into an object the
60
+ // executor no longer recognises.
61
+ const bound = Object.assign(Object.create(Object.getPrototypeOf(tool)), tool, { risk });
62
+ this.staged.set(tool.name, bound);
63
+ this.records.push({
64
+ name: tool.name,
65
+ ...(tool.description ? { description: tool.description } : {}),
66
+ risk,
67
+ package: this.manifest.packageName,
68
+ trusted: this.trusted,
69
+ });
70
+ }
71
+ /** Reads see the whole registry: a plugin composing on another's tool is not the boundary here. */
72
+ get(name) {
73
+ return this.staged.get(name) ?? this.target.get(name);
74
+ }
75
+ list() {
76
+ return [...this.target.list(), ...this.staged.values()];
77
+ }
78
+ describe() {
79
+ return this.list().map((tool) => ({
80
+ name: tool.name,
81
+ ...(tool.description ? { description: tool.description } : {}),
82
+ ...(tool.parameters ? { parameters: tool.parameters } : {}),
83
+ risk: resolveToolRisk(tool),
84
+ }));
85
+ }
86
+ /**
87
+ * Move this plugin's tools into the shared registry, or refuse the whole
88
+ * plugin. A name already registered is a load-time error naming both
89
+ * packages, never a silent overwrite — the same reason a duplicate agent
90
+ * id refuses the roster rather than picking whichever loaded last.
91
+ */
92
+ commit(owners) {
93
+ for (const name of this.staged.keys()) {
94
+ const existing = owners.get(name);
95
+ if (existing !== undefined) {
96
+ throw new PluginManifestError(`Tool name collision: ${name} is contributed by both ${existing} and ${this.manifest.packageName}. `
97
+ + 'Disable one of them; a tool name is unique per install.');
98
+ }
99
+ if (this.target.get(name)) {
100
+ throw new PluginManifestError(`Tool name collision: ${name} is already registered by the kernel. `
101
+ + `Plugin ${this.manifest.packageName} cannot replace it.`);
102
+ }
103
+ }
104
+ for (const [name, tool] of this.staged) {
105
+ this.target.register(tool);
106
+ owners.set(name, this.manifest.packageName);
107
+ }
108
+ return [...this.records];
109
+ }
110
+ }
111
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@stratusagent/plugins",
3
+ "version": "0.6.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ },
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22.13 <23 || >=23.4"
19
+ },
20
+ "dependencies": {
21
+ "@stratusagent/core": "0.6.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^24.6.0",
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/stratuslabs/agent.git",
31
+ "directory": "packages/plugins"
32
+ },
33
+ "description": "The Stratus Agent plugin host: manifest validation, the manifest-bound registration view, and the loader that turns a config block into running capability",
34
+ "keywords": [
35
+ "stratus",
36
+ "agent",
37
+ "ai",
38
+ "llm",
39
+ "plugins"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -b",
46
+ "typecheck": "tsc --noEmit --project tsconfig.test.json",
47
+ "test": "node --test --experimental-strip-types"
48
+ }
49
+ }