@jini-ai/agent-plugins 0.4.6 → 0.4.8
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 +30 -0
- package/README.md +48 -0
- package/dist/admin-surfaces/action-dispatcher.d.ts +37 -0
- package/dist/admin-surfaces/action-dispatcher.js +69 -0
- package/dist/admin-surfaces/action-schema.d.ts +56 -0
- package/dist/admin-surfaces/action-schema.js +167 -0
- package/dist/admin-surfaces/action-tools.d.ts +40 -0
- package/dist/admin-surfaces/action-tools.js +52 -0
- package/dist/admin-surfaces/callback-field-inbox.d.ts +35 -0
- package/dist/admin-surfaces/callback-field-inbox.js +53 -0
- package/dist/admin-surfaces/capability-declarations.d.ts +41 -0
- package/dist/admin-surfaces/capability-declarations.js +102 -0
- package/dist/admin-surfaces/contribution.d.ts +49 -0
- package/dist/admin-surfaces/contribution.js +30 -0
- package/dist/admin-surfaces/delivery-bindings.d.ts +76 -0
- package/dist/admin-surfaces/delivery-bindings.js +99 -0
- package/dist/admin-surfaces/descriptor.d.ts +69 -0
- package/dist/admin-surfaces/descriptor.js +196 -0
- package/dist/admin-surfaces/external-links.d.ts +45 -0
- package/dist/admin-surfaces/external-links.js +70 -0
- package/dist/admin-surfaces/host-validator.d.ts +62 -0
- package/dist/admin-surfaces/host-validator.js +87 -0
- package/dist/admin-surfaces/index.d.ts +23 -0
- package/dist/admin-surfaces/index.js +21 -0
- package/dist/admin-surfaces/protocol.d.ts +103 -0
- package/dist/admin-surfaces/protocol.js +25 -0
- package/dist/admin-surfaces/sandbox.d.ts +78 -0
- package/dist/admin-surfaces/sandbox.js +92 -0
- package/dist/admin-surfaces/settings-value.d.ts +28 -0
- package/dist/admin-surfaces/settings-value.js +45 -0
- package/dist/admin-surfaces/surface-capabilities.d.ts +167 -0
- package/dist/admin-surfaces/surface-capabilities.js +215 -0
- package/dist/admin-surfaces/surface-client.d.ts +116 -0
- package/dist/admin-surfaces/surface-client.js +219 -0
- package/dist/admin-surfaces/surface-handlers.d.ts +111 -0
- package/dist/admin-surfaces/surface-handlers.js +172 -0
- package/dist/admin-surfaces/theme.d.ts +59 -0
- package/dist/admin-surfaces/theme.js +57 -0
- package/dist/folder-zip-node.d.ts +11 -0
- package/dist/folder-zip-node.js +84 -0
- package/dist/folder-zip.d.ts +38 -0
- package/dist/folder-zip.js +134 -0
- package/dist/lifecycle/automatic-use.d.ts +79 -0
- package/dist/lifecycle/automatic-use.js +165 -0
- package/dist/lifecycle/compatibility-metadata.d.ts +19 -0
- package/dist/lifecycle/compatibility-metadata.js +45 -0
- package/dist/lifecycle/compatibility.d.ts +52 -0
- package/dist/lifecycle/compatibility.js +60 -0
- package/dist/lifecycle/fetch-archive.js +26 -24
- package/dist/lifecycle/index.d.ts +14 -2
- package/dist/lifecycle/index.js +8 -1
- package/dist/lifecycle/install-errors.d.ts +14 -0
- package/dist/lifecycle/install-errors.js +9 -0
- package/dist/lifecycle/install.d.ts +2 -13
- package/dist/lifecycle/install.js +12 -123
- package/dist/lifecycle/installed-package-index.d.ts +15 -0
- package/dist/lifecycle/installed-package-index.js +128 -0
- package/dist/lifecycle/installed-package-roots.js +28 -20
- package/dist/lifecycle/manifest.d.ts +14 -0
- package/dist/lifecycle/manifest.js +115 -100
- package/dist/lifecycle/package-paths.js +3 -3
- package/dist/lifecycle/ports.d.ts +1 -1
- package/dist/lifecycle/provider-contribution.d.ts +65 -0
- package/dist/lifecycle/provider-contribution.js +93 -0
- package/dist/lifecycle/resolve-agent-plugin-refs.js +19 -6
- package/dist/lifecycle/retire-bundled.js +20 -18
- package/dist/lifecycle/search.d.ts +6 -1
- package/dist/lifecycle/search.js +45 -7
- package/dist/lifecycle/set-enabled.d.ts +2 -1
- package/dist/lifecycle/set-enabled.js +52 -8
- package/dist/lifecycle/skill-file-reader.js +85 -71
- package/dist/lifecycle/tools/capabilities.d.ts +199 -0
- package/dist/lifecycle/tools/capabilities.js +213 -0
- package/dist/lifecycle/tools/definition.d.ts +32 -0
- package/dist/lifecycle/tools/definition.js +33 -0
- package/dist/lifecycle/tools/index.d.ts +10 -0
- package/dist/lifecycle/tools/index.js +10 -0
- package/dist/lifecycle/tools/manifest.d.ts +33 -0
- package/dist/lifecycle/tools/manifest.js +89 -0
- package/dist/lifecycle/tools/projection.d.ts +90 -0
- package/dist/lifecycle/tools/projection.js +283 -0
- package/dist/lifecycle/types.d.ts +2 -0
- package/dist/lifecycle/uninstall.d.ts +3 -1
- package/dist/lifecycle/uninstall.js +51 -27
- package/dist/lifecycle/update-plan.d.ts +18 -0
- package/dist/lifecycle/update-plan.js +22 -0
- package/dist/lifecycle/update-store.d.ts +71 -0
- package/dist/lifecycle/update-store.js +150 -0
- package/dist/lifecycle/update.d.ts +55 -0
- package/dist/lifecycle/update.js +321 -0
- package/dist/manifest.d.ts +2 -1
- package/dist/manifest.js +2 -1
- package/package.json +50 -6
- package/ui-ux-design/plugin.json +8 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lifecycle/activation.d.ts.map +0 -1
- package/dist/lifecycle/activation.js.map +0 -1
- package/dist/lifecycle/bundled-digests.d.ts.map +0 -1
- package/dist/lifecycle/bundled-digests.js.map +0 -1
- package/dist/lifecycle/bundled-source-archive.d.ts.map +0 -1
- package/dist/lifecycle/bundled-source-archive.js.map +0 -1
- package/dist/lifecycle/capability-projection.d.ts.map +0 -1
- package/dist/lifecycle/capability-projection.js.map +0 -1
- package/dist/lifecycle/contribution-loader.d.ts.map +0 -1
- package/dist/lifecycle/contribution-loader.js.map +0 -1
- package/dist/lifecycle/fetch-archive.d.ts.map +0 -1
- package/dist/lifecycle/fetch-archive.js.map +0 -1
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/index.js.map +0 -1
- package/dist/lifecycle/install-from-url.d.ts.map +0 -1
- package/dist/lifecycle/install-from-url.js.map +0 -1
- package/dist/lifecycle/install.d.ts.map +0 -1
- package/dist/lifecycle/install.js.map +0 -1
- package/dist/lifecycle/installed-package-roots.d.ts.map +0 -1
- package/dist/lifecycle/installed-package-roots.js.map +0 -1
- package/dist/lifecycle/layout.d.ts.map +0 -1
- package/dist/lifecycle/layout.js.map +0 -1
- package/dist/lifecycle/manifest.d.ts.map +0 -1
- package/dist/lifecycle/manifest.js.map +0 -1
- package/dist/lifecycle/mcp-provisioning.d.ts.map +0 -1
- package/dist/lifecycle/mcp-provisioning.js.map +0 -1
- package/dist/lifecycle/node.d.ts.map +0 -1
- package/dist/lifecycle/node.js.map +0 -1
- package/dist/lifecycle/package-paths.d.ts.map +0 -1
- package/dist/lifecycle/package-paths.js.map +0 -1
- package/dist/lifecycle/persistent-state.d.ts.map +0 -1
- package/dist/lifecycle/persistent-state.js.map +0 -1
- package/dist/lifecycle/ports.d.ts.map +0 -1
- package/dist/lifecycle/ports.js.map +0 -1
- package/dist/lifecycle/resolve-agent-plugin-refs.d.ts.map +0 -1
- package/dist/lifecycle/resolve-agent-plugin-refs.js.map +0 -1
- package/dist/lifecycle/retire-bundled.d.ts.map +0 -1
- package/dist/lifecycle/retire-bundled.js.map +0 -1
- package/dist/lifecycle/search.d.ts.map +0 -1
- package/dist/lifecycle/search.js.map +0 -1
- package/dist/lifecycle/seed-bundled.d.ts.map +0 -1
- package/dist/lifecycle/seed-bundled.js.map +0 -1
- package/dist/lifecycle/set-enabled.d.ts.map +0 -1
- package/dist/lifecycle/set-enabled.js.map +0 -1
- package/dist/lifecycle/skill-file-reader.d.ts.map +0 -1
- package/dist/lifecycle/skill-file-reader.js.map +0 -1
- package/dist/lifecycle/trusted-plugin-files.d.ts.map +0 -1
- package/dist/lifecycle/trusted-plugin-files.js.map +0 -1
- package/dist/lifecycle/types.d.ts.map +0 -1
- package/dist/lifecycle/types.js.map +0 -1
- package/dist/lifecycle/uninstall.d.ts.map +0 -1
- package/dist/lifecycle/uninstall.js.map +0 -1
- package/dist/lifecycle/yauzl-archive-reader.d.ts.map +0 -1
- package/dist/lifecycle/yauzl-archive-reader.js.map +0 -1
- package/dist/manifest.d.ts.map +0 -1
- package/dist/manifest.js.map +0 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Plugin-contributed providers for `@jini-ai/core`'s `createProviderRegistry` (FROZEN CONTRACT,
|
|
3
|
+
* 2026-10-21). Lives here rather than in core because core cannot import agent-plugins (agent-plugins
|
|
4
|
+
* already depends on core). Defined on the one executable loader (`defineExecutablePluginContribution`)
|
|
5
|
+
* so a provider module passes the same gates as every other seam that runs plugin code: active
|
|
6
|
+
* plugin, bundled digest, contained `.mjs` path. No second loader.
|
|
7
|
+
*
|
|
8
|
+
* Every failure is a refusal line, never a throw: a broken or duplicate plugin provider must not
|
|
9
|
+
* crash boot (core/built-in registrations keep the registry's throwing policy).
|
|
10
|
+
*/
|
|
11
|
+
import { type ProviderDefinition, type ProviderRegistry } from '@jini-ai/core/provider-registry';
|
|
12
|
+
import { type PluginContributionDefinition, type PluginContributionResult } from './contribution-loader.js';
|
|
13
|
+
/** The minimum a provider manifest entry declares; a seam's parser may add fields. */
|
|
14
|
+
export interface ProviderContributionDescriptor {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
/** Plain-JS module inside the plugin root (`.mjs`); containment is checked on disk at import. */
|
|
17
|
+
readonly module: string;
|
|
18
|
+
readonly label?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface LoadedProviderContribution<T> {
|
|
21
|
+
readonly contract: string;
|
|
22
|
+
readonly pluginId: string;
|
|
23
|
+
readonly definition: ProviderDefinition<T>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Defines the contribution of providers for one contract (e.g. `mail`) from a host-chosen file.
|
|
27
|
+
* The id is checked before the module is imported, so a malformed entry never runs code.
|
|
28
|
+
* @param required.contract - Must equal the target registry's `contract`.
|
|
29
|
+
* @param required.filename - The file a plugin ships at its package root to contribute providers.
|
|
30
|
+
* @param required.parse - The seam's manifest parser.
|
|
31
|
+
* @param required.validate - The module export as a provider of this contract, or why not.
|
|
32
|
+
* @param optional - The loader's read/import recovery policy. Unlike the bare executable loader, absent
|
|
33
|
+
* policies default to refusal lines (`could not be read`, `its module failed to load`) instead of
|
|
34
|
+
* propagating the fault, so one unreadable manifest or throwing module never discards the healthy
|
|
35
|
+
* contributions loaded alongside it. Pass `onReadError: () => []` to tolerate a missing manifest.
|
|
36
|
+
* @returns A definition for `loadPluginContributions`; executable, so activation is always required.
|
|
37
|
+
* @complexity O(1); each load is one contained import per declared provider.
|
|
38
|
+
*/
|
|
39
|
+
export declare function defineProviderContribution<D extends ProviderContributionDescriptor, T>(required: {
|
|
40
|
+
readonly contract: string;
|
|
41
|
+
readonly filename: string;
|
|
42
|
+
readonly parse: PluginContributionDefinition<D, LoadedProviderContribution<T>>['parse'];
|
|
43
|
+
readonly validate: (required: {
|
|
44
|
+
readonly exported: unknown;
|
|
45
|
+
}) => T | string;
|
|
46
|
+
}, optional?: {
|
|
47
|
+
readonly onReadError?: PluginContributionDefinition<D, LoadedProviderContribution<T>>['onReadError'];
|
|
48
|
+
readonly importRefusalReason?: string;
|
|
49
|
+
readonly importErrorReason?: string;
|
|
50
|
+
}): PluginContributionDefinition<D, LoadedProviderContribution<T>>;
|
|
51
|
+
/**
|
|
52
|
+
* Offer loaded plugin providers to a registry and merge every refusal into one host-facing list:
|
|
53
|
+
* the loader's (gate, manifest, import, validation) first, then the registry's (contract mismatch,
|
|
54
|
+
* duplicate, invalid provider) in contribution order.
|
|
55
|
+
* @returns The ids the registry accepted and every refusal line; never throws.
|
|
56
|
+
* @complexity O(n) contributions.
|
|
57
|
+
*/
|
|
58
|
+
export declare function contributeProviders<T>(required: {
|
|
59
|
+
readonly registry: ProviderRegistry<T>;
|
|
60
|
+
readonly loaded: PluginContributionResult<LoadedProviderContribution<T>>;
|
|
61
|
+
}, _optional?: Record<string, never>): {
|
|
62
|
+
readonly accepted: readonly string[];
|
|
63
|
+
readonly refusals: readonly string[];
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=provider-contribution.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Plugin-contributed providers for `@jini-ai/core`'s `createProviderRegistry` (FROZEN CONTRACT,
|
|
3
|
+
* 2026-10-21). Lives here rather than in core because core cannot import agent-plugins (agent-plugins
|
|
4
|
+
* already depends on core). Defined on the one executable loader (`defineExecutablePluginContribution`)
|
|
5
|
+
* so a provider module passes the same gates as every other seam that runs plugin code: active
|
|
6
|
+
* plugin, bundled digest, contained `.mjs` path. No second loader.
|
|
7
|
+
*
|
|
8
|
+
* Every failure is a refusal line, never a throw: a broken or duplicate plugin provider must not
|
|
9
|
+
* crash boot (core/built-in registrations keep the registry's throwing policy).
|
|
10
|
+
*/
|
|
11
|
+
import { PROVIDER_ID_PATTERN } from '@jini-ai/core/provider-registry';
|
|
12
|
+
import { defineExecutablePluginContribution, } from './contribution-loader.js';
|
|
13
|
+
/**
|
|
14
|
+
* Defines the contribution of providers for one contract (e.g. `mail`) from a host-chosen file.
|
|
15
|
+
* The id is checked before the module is imported, so a malformed entry never runs code.
|
|
16
|
+
* @param required.contract - Must equal the target registry's `contract`.
|
|
17
|
+
* @param required.filename - The file a plugin ships at its package root to contribute providers.
|
|
18
|
+
* @param required.parse - The seam's manifest parser.
|
|
19
|
+
* @param required.validate - The module export as a provider of this contract, or why not.
|
|
20
|
+
* @param optional - The loader's read/import recovery policy. Unlike the bare executable loader, absent
|
|
21
|
+
* policies default to refusal lines (`could not be read`, `its module failed to load`) instead of
|
|
22
|
+
* propagating the fault, so one unreadable manifest or throwing module never discards the healthy
|
|
23
|
+
* contributions loaded alongside it. Pass `onReadError: () => []` to tolerate a missing manifest.
|
|
24
|
+
* @returns A definition for `loadPluginContributions`; executable, so activation is always required.
|
|
25
|
+
* @complexity O(1); each load is one contained import per declared provider.
|
|
26
|
+
*/
|
|
27
|
+
export function defineProviderContribution(required, optional = {}) {
|
|
28
|
+
const { contract } = required;
|
|
29
|
+
const refusal = ({ plugin, descriptor, reason }) => `${contract} provider '${String(descriptor.id)}' from '${plugin.pluginId}' was not loaded: ${reason}`;
|
|
30
|
+
const { filename } = required;
|
|
31
|
+
const executable = defineExecutablePluginContribution({
|
|
32
|
+
filename, contribution: `${contract} providers`, parse: guardedParse({ parse: required.parse }),
|
|
33
|
+
modulePath: ({ descriptor }) => descriptor.module, validate: required.validate, refusal,
|
|
34
|
+
}, {
|
|
35
|
+
onReadError: optional.onReadError ?? (({ plugin }) => [`${contract} providers from '${plugin.pluginId}' were not loaded: ${filename} could not be read`]),
|
|
36
|
+
importErrorReason: optional.importErrorReason ?? 'its module failed to load',
|
|
37
|
+
...(optional.importRefusalReason === undefined ? {} : { importRefusalReason: optional.importRefusalReason }),
|
|
38
|
+
});
|
|
39
|
+
const loadOne = async (input) => {
|
|
40
|
+
const { plugin, descriptor } = input;
|
|
41
|
+
if (typeof descriptor.id !== 'string' || !PROVIDER_ID_PATTERN.test(descriptor.id)) {
|
|
42
|
+
return refusal({ plugin, descriptor, reason: `its id must match ${PROVIDER_ID_PATTERN.source}` });
|
|
43
|
+
}
|
|
44
|
+
const loaded = await executable.load(input);
|
|
45
|
+
if (typeof loaded === 'string')
|
|
46
|
+
return loaded;
|
|
47
|
+
const { id, label } = descriptor;
|
|
48
|
+
return { contract, pluginId: loaded.pluginId, definition: { id, provider: loaded.module, ...(label === undefined ? {} : { label }) } };
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
...executable,
|
|
52
|
+
// Per-descriptor boundary: a hostile descriptor (e.g. a throwing getter) becomes one refusal and
|
|
53
|
+
// the loader moves on to the next declaration.
|
|
54
|
+
load: async (input) => {
|
|
55
|
+
try {
|
|
56
|
+
return await loadOne(input);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return `${contract} provider from '${input.plugin.pluginId}' was not loaded: its declaration could not be read`;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** A throwing seam parser is a refused manifest, not a boot fault. */
|
|
65
|
+
function guardedParse(required, _optional = {}) {
|
|
66
|
+
return (input) => {
|
|
67
|
+
try {
|
|
68
|
+
return required.parse(input);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return { ok: false, reason: 'its parser failed' };
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Offer loaded plugin providers to a registry and merge every refusal into one host-facing list:
|
|
77
|
+
* the loader's (gate, manifest, import, validation) first, then the registry's (contract mismatch,
|
|
78
|
+
* duplicate, invalid provider) in contribution order.
|
|
79
|
+
* @returns The ids the registry accepted and every refusal line; never throws.
|
|
80
|
+
* @complexity O(n) contributions.
|
|
81
|
+
*/
|
|
82
|
+
export function contributeProviders(required, _optional = {}) {
|
|
83
|
+
const { registry, loaded } = required;
|
|
84
|
+
const before = registry.refusals().length;
|
|
85
|
+
const accepted = [];
|
|
86
|
+
for (const item of loaded.items) {
|
|
87
|
+
if (registry.contribute({ definition: item.definition, pluginId: item.pluginId, contract: item.contract }))
|
|
88
|
+
accepted.push(item.definition.id);
|
|
89
|
+
}
|
|
90
|
+
const registryRefusals = registry.refusals().slice(before).map(refusal => refusal.message);
|
|
91
|
+
return { accepted, refusals: [...loaded.refusals, ...registryRefusals] };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=provider-contribution.js.map
|
|
@@ -22,7 +22,8 @@ import { listInstalledPackageRoots, INSTALLED_PLUGIN_DIGEST_PATTERN } from "./in
|
|
|
22
22
|
* `packages/sha256/*` and matches on the installed `plugin.json`'s own `name` field, after dropping
|
|
23
23
|
* any digest of a bundled plugin the ledger records as superseded:
|
|
24
24
|
*
|
|
25
|
-
* - Exactly one match: resolves normally
|
|
25
|
+
* - Exactly one match: resolves normally, unless the bundled ledger records that id at a different
|
|
26
|
+
* digest (a copy the build did not ship never guides under a bundled id).
|
|
26
27
|
* - Zero matches: fails with a "not installed" reason — the operator pinned a chip for a plugin
|
|
27
28
|
* that is not (or no longer) actually on disk for this workspace.
|
|
28
29
|
* - More than one match: fails with an explicit ambiguity reason naming every matching digest.
|
|
@@ -50,7 +51,7 @@ import { listInstalledPackageRoots, INSTALLED_PLUGIN_DIGEST_PATTERN } from "./in
|
|
|
50
51
|
* and CLI agents — supporting content is deferred to an explicit follow-up read.
|
|
51
52
|
*
|
|
52
53
|
* Architectural role:
|
|
53
|
-
* Server-side only (reads
|
|
54
|
+
* Server-side only (reads injected filesystem ports through the installed-package index) — never
|
|
54
55
|
* imported into a browser bundle. Returns a discriminated result rather than throwing, so
|
|
55
56
|
* `agent-daemon-server.ts`'s `onStarted` gets one place to branch on success/failure without a
|
|
56
57
|
* second try/catch layer duplicating this module's own error classification.
|
|
@@ -60,7 +61,7 @@ import path from "node:path";
|
|
|
60
61
|
import { createPackagePathsModule } from "./package-paths.js";
|
|
61
62
|
import { createActivationModule } from "./activation.js";
|
|
62
63
|
import { createBundledDigestsModule } from "./bundled-digests.js";
|
|
63
|
-
import {
|
|
64
|
+
import { createInstalledPackageIndex } from "./installed-package-index.js";
|
|
64
65
|
import { createCapabilityProjectionModule } from "./capability-projection.js";
|
|
65
66
|
function buildModule(ports) {
|
|
66
67
|
const persistentState = createPersistentStateModule(ports);
|
|
@@ -68,7 +69,7 @@ function buildModule(ports) {
|
|
|
68
69
|
const stat = ports.filesystem.stat.bind(ports.filesystem);
|
|
69
70
|
const { resolveAgentPluginActivation } = createActivationModule(ports);
|
|
70
71
|
const { preferBundledAgentPluginDigests, readBundledAgentPluginDigests } = createBundledDigestsModule(ports);
|
|
71
|
-
const { indexInstalledRoot } =
|
|
72
|
+
const { indexInstalledRoot } = createInstalledPackageIndex({ ports });
|
|
72
73
|
const { readInstalledSkillFile } = createCapabilityProjectionModule(ports);
|
|
73
74
|
/** Reads the delivery mode for this process. An unset or unrecognised value is `inject` — an A/B
|
|
74
75
|
* affordance must never be able to change production behaviour by typo. */
|
|
@@ -149,7 +150,7 @@ function buildModule(ports) {
|
|
|
149
150
|
const installed = [];
|
|
150
151
|
for (const root of await listInstalledPackageRoots({ ports, workspaceRoot })) {
|
|
151
152
|
try {
|
|
152
|
-
const plugin = await indexInstalledRoot(root.packageRoot, root.archiveDigest);
|
|
153
|
+
const plugin = await indexInstalledRoot({ packageRoot: root.packageRoot, archiveDigest: root.archiveDigest });
|
|
153
154
|
if (plugin.pluginId === root.localId)
|
|
154
155
|
installed.push(plugin);
|
|
155
156
|
}
|
|
@@ -218,9 +219,21 @@ function buildModule(ports) {
|
|
|
218
219
|
};
|
|
219
220
|
}
|
|
220
221
|
const plugin = matches[0];
|
|
221
|
-
|
|
222
|
+
// Activation is checked by the caller. A lone copy under an id the bundled ledger records must be
|
|
223
|
+
// the digest this build shipped; an operator install (no ledger entry) was digest-verified when
|
|
224
|
+
// installed and its skills may guide once enabled (install-upload's trust warning). Inline, not
|
|
225
|
+
// trusted-plugin-files' verdict: that module lists installs through this one (import cycle).
|
|
226
|
+
const shippedDigest = bundledDigests.get(pluginRefId);
|
|
227
|
+
if (shippedDigest !== undefined && shippedDigest !== plugin.archiveDigest) {
|
|
228
|
+
return { ok: false, reason: `Agent Plugin '${pluginRefId}' was not loaded: only plugins shipped with ${ports.productName} may add agent guidance under a bundled id (installed digest ${plugin.archiveDigest.slice(0, 12)} is not the one this build shipped)` };
|
|
229
|
+
}
|
|
222
230
|
if (deliveryMode === "pointer")
|
|
223
231
|
return buildPointerSection(pluginRefId, plugin);
|
|
232
|
+
return buildInjectedSection({ pluginRefId, packagesDir, plugin });
|
|
233
|
+
}
|
|
234
|
+
/** `inject` mode: the eponymous SKILL.md, the plugin's user notes, and its readable-file inventory. */
|
|
235
|
+
async function buildInjectedSection({ pluginRefId, packagesDir, plugin }) {
|
|
236
|
+
const skillPath = `skills/${pluginRefId}/SKILL.md`;
|
|
224
237
|
let skillMarkdown;
|
|
225
238
|
let files;
|
|
226
239
|
let fileNote;
|
|
@@ -41,28 +41,13 @@ function buildModule(ports) {
|
|
|
41
41
|
const allInstalled = await listInstalledPlugins(workspaceLayout.root);
|
|
42
42
|
const installed = allInstalled.filter((plugin) => plugin.pluginId === pluginId);
|
|
43
43
|
const hasRecord = Object.hasOwn((await readAgentPluginActivations(workspaceRoot)).plugins, pluginId);
|
|
44
|
-
if (installed.length === 0 && !hasRecord)
|
|
45
|
-
|
|
46
|
-
if (!ledgerEntryRemoved)
|
|
47
|
-
return { pluginId, ...ids, status: "absent" };
|
|
48
|
-
return { pluginId, ...ids, status: "retired", removedDigests: [], activationRemoved: false, ledgerEntryRemoved, successor: "not-needed", ...(ids.successorIds ? { successors: {} } : {}) };
|
|
49
|
-
}
|
|
44
|
+
if (installed.length === 0 && !hasRecord)
|
|
45
|
+
return dropLeftoverLedgerEntry({ workspaceRoot, pluginId, ids });
|
|
50
46
|
const unusable = await unusableSuccessor(workspaceRoot, allInstalled, successorIds);
|
|
51
47
|
if (unusable !== undefined) {
|
|
52
48
|
return { pluginId, ...ids, status: "failed", reason: `'${pluginId}' is kept until every successor is usable: ${unusable}` };
|
|
53
49
|
}
|
|
54
|
-
const successors = {};
|
|
55
|
-
for (const successorId of successorIds) {
|
|
56
|
-
successors[successorId] =
|
|
57
|
-
verdict.verdict === "active"
|
|
58
|
-
? await enableBundledAgentPluginUnlessOperatorDisabled({
|
|
59
|
-
workspaceRoot,
|
|
60
|
-
pluginId: successorId,
|
|
61
|
-
actor: `system:retire-${pluginId}`,
|
|
62
|
-
...(optional.now !== undefined ? { now: optional.now } : {}),
|
|
63
|
-
})
|
|
64
|
-
: "not-needed";
|
|
65
|
-
}
|
|
50
|
+
const successors = await switchOnSuccessors({ workspaceRoot, pluginId, successorIds, wasActive: verdict.verdict === "active" }, { now: optional.now });
|
|
66
51
|
const successor = successors[ids.successorId] ?? "not-needed";
|
|
67
52
|
let removedDigests = [];
|
|
68
53
|
if (installed.length > 0) {
|
|
@@ -74,6 +59,23 @@ function buildModule(ports) {
|
|
|
74
59
|
const ledgerEntryRemoved = await removeBundledAgentPluginDigest({ workspaceRoot, pluginId });
|
|
75
60
|
return { pluginId, ...ids, status: "retired", removedDigests, activationRemoved: hasRecord, ledgerEntryRemoved, successor, ...(ids.successorIds ? { successors } : {}) };
|
|
76
61
|
}
|
|
62
|
+
/** Step 1: nothing installed and no record — only a leftover ledger entry can remain to drop. */
|
|
63
|
+
async function dropLeftoverLedgerEntry({ workspaceRoot, pluginId, ids }) {
|
|
64
|
+
const ledgerEntryRemoved = await removeBundledAgentPluginDigest({ workspaceRoot, pluginId });
|
|
65
|
+
if (!ledgerEntryRemoved)
|
|
66
|
+
return { pluginId, ...ids, status: "absent" };
|
|
67
|
+
return { pluginId, ...ids, status: "retired", removedDigests: [], activationRemoved: false, ledgerEntryRemoved, successor: "not-needed", ...(ids.successorIds ? { successors: {} } : {}) };
|
|
68
|
+
}
|
|
69
|
+
/** Step 2: each successor's outcome — switched on only when the retired plugin was active. */
|
|
70
|
+
async function switchOnSuccessors({ workspaceRoot, pluginId, successorIds, wasActive }, { now } = {}) {
|
|
71
|
+
const successors = {};
|
|
72
|
+
for (const successorId of successorIds) {
|
|
73
|
+
successors[successorId] = wasActive
|
|
74
|
+
? await enableBundledAgentPluginUnlessOperatorDisabled({ workspaceRoot, pluginId: successorId, actor: `system:retire-${pluginId}`, ...(now !== undefined ? { now } : {}) })
|
|
75
|
+
: "not-needed";
|
|
76
|
+
}
|
|
77
|
+
return successors;
|
|
78
|
+
}
|
|
77
79
|
/** Why one of `successorIds` cannot take over yet, or `undefined` when every one is installed at the
|
|
78
80
|
* digest the ledger records for it (this file's header, step 1a).
|
|
79
81
|
* @complexity O(s * n) for s successors over n installed packages; both are small bundled counts. */
|
|
@@ -60,6 +60,11 @@ export interface AgentPluginSearchSkill {
|
|
|
60
60
|
* `packageRoot`/`skillPath`/any other host-filesystem detail (see `tool-registrations.ts`'s own
|
|
61
61
|
* SECURITY note for why: this candidate shape is what actually reaches the model). */
|
|
62
62
|
export interface AgentPluginSearchCandidate {
|
|
63
|
+
readonly origin?: import("./activation.js").AgentPluginOrigin;
|
|
64
|
+
readonly hostOk?: boolean;
|
|
65
|
+
readonly missingRequires?: readonly string[];
|
|
66
|
+
readonly requiredBy?: readonly string[];
|
|
67
|
+
readonly archiveDigest?: string;
|
|
63
68
|
readonly pluginId: string;
|
|
64
69
|
readonly version?: (string) | undefined;
|
|
65
70
|
/** Human-facing title from the host extension namespace in `plugin.json`: `displayName`, when the package set one. */
|
|
@@ -83,6 +88,6 @@ export interface AgentPluginSearchMatch extends AgentPluginSearchCandidate {
|
|
|
83
88
|
}
|
|
84
89
|
/** Internal composition; one state/queue instance per injected host context. */
|
|
85
90
|
export declare function createSearchModule(ports: AgentPluginLifecyclePorts): {
|
|
86
|
-
rankInstalledAgentPlugins: (query: string, candidates: readonly AgentPluginSearchCandidate[], limit: number) => readonly AgentPluginSearchMatch[];
|
|
91
|
+
rankInstalledAgentPlugins: (query: string, candidates: readonly AgentPluginSearchCandidate[], limit: number, automatic?: boolean) => readonly AgentPluginSearchMatch[];
|
|
87
92
|
};
|
|
88
93
|
//# sourceMappingURL=search.d.ts.map
|
package/dist/lifecycle/search.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
/** Extracted from the host plugin lifecycle. Effects are supplied per host context. */
|
|
2
|
+
/** These describe the conversation or target, not a plugin's topic. Prose-only hits remain
|
|
3
|
+
* available to explicit search without silently loading unrelated guidance. */
|
|
4
|
+
const INCIDENTAL_TERMS = new Set('a an and are as at be build can could create do for from get help i in is it make me my new of on or please the this to use want we with you your site sites page pages website websites plugin plugins tell which load just don t anything'.split(' '));
|
|
5
|
+
const DEPLOYMENT_TERMS = new Set('deploy deployment deploying deployed publish publishing published host hosting hosted'.split(' '));
|
|
6
|
+
function automaticTerms(query) {
|
|
7
|
+
return [...new Set((query.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? []).filter(term => !INCIDENTAL_TERMS.has(term)))];
|
|
8
|
+
}
|
|
9
|
+
/** A dedicated deployment provider (only `deploy-*` and `credential-scheme:*` capabilities) matches only a
|
|
10
|
+
* query naming both a deployment action and one of its vendor terms; any other plugin passes. */
|
|
11
|
+
function passesDeploymentGate({ candidate, capabilities, terms }, _options = {}) {
|
|
12
|
+
const deployment = capabilities.filter(capability => capability.startsWith('deploy-'));
|
|
13
|
+
const dedicatedDeployment = deployment.length > 0 && capabilities.every(capability => capability.startsWith('deploy-') || capability.startsWith('credential-scheme:'));
|
|
14
|
+
if (!dedicatedDeployment)
|
|
15
|
+
return true;
|
|
16
|
+
const vendors = automaticTerms([candidate.pluginId, candidate.displayName ?? '', ...deployment.map(capability => capability.split(':')[1] ?? '')].join(' '))
|
|
17
|
+
.filter(term => !DEPLOYMENT_TERMS.has(term));
|
|
18
|
+
return terms.some(term => DEPLOYMENT_TERMS.has(term)) && terms.some(term => vendors.includes(term));
|
|
19
|
+
}
|
|
20
|
+
/** Score complete authored terms only: long descriptions must not manufacture relevance.
|
|
21
|
+
* Dedicated deployment providers additionally need both the action and their declared vendor.
|
|
22
|
+
* Mixed-purpose plugins can still match their other capabilities (for example source control).
|
|
23
|
+
*/
|
|
24
|
+
function automaticScore({ candidate, terms }, _options = {}) {
|
|
25
|
+
if (!candidate.enabled)
|
|
26
|
+
return 0;
|
|
27
|
+
const capabilities = candidate.provides ?? [];
|
|
28
|
+
if (!passesDeploymentGate({ candidate, capabilities, terms }))
|
|
29
|
+
return 0;
|
|
30
|
+
const fields = [
|
|
31
|
+
{ terms: automaticTerms(capabilities.join(' ')), weight: 6 },
|
|
32
|
+
{ terms: automaticTerms(`${candidate.pluginId} ${candidate.id ?? ''} ${candidate.displayName ?? ''}`), weight: 5 },
|
|
33
|
+
{ terms: automaticTerms(candidate.keywords.join(' ')), weight: 4 },
|
|
34
|
+
];
|
|
35
|
+
return terms.reduce((score, term) => score + fields.reduce((sum, field) => sum + (field.terms.includes(term) ? field.weight : 0), 0), 0);
|
|
36
|
+
}
|
|
2
37
|
function buildModule(ports) {
|
|
3
38
|
const ID_FIELD_WEIGHT = 5;
|
|
4
39
|
const KEYWORD_FIELD_WEIGHT = 4;
|
|
@@ -18,7 +53,7 @@ function buildModule(ports) {
|
|
|
18
53
|
* plain, independently-readable list rather than interleaved with the scoring loop itself. */
|
|
19
54
|
function fieldHaystacks(candidate) {
|
|
20
55
|
return [
|
|
21
|
-
{ text: `${candidate.pluginId} ${candidate.id ?? ""}`.toLowerCase(), weight: ID_FIELD_WEIGHT },
|
|
56
|
+
{ text: `${candidate.pluginId} ${candidate.id ?? ""} ${candidate.displayName ?? ""}`.toLowerCase(), weight: ID_FIELD_WEIGHT },
|
|
22
57
|
{ text: [...candidate.keywords, ...(candidate.provides ?? [])].join(" ").toLowerCase(), weight: KEYWORD_FIELD_WEIGHT },
|
|
23
58
|
{ text: (candidate.description ?? "").toLowerCase(), weight: DESCRIPTION_FIELD_WEIGHT },
|
|
24
59
|
{
|
|
@@ -48,20 +83,23 @@ function buildModule(ports) {
|
|
|
48
83
|
* @param limit - Max matches to return, already clamped by the caller (mirrors
|
|
49
84
|
* `component-catalog-tool.ts`'s handler, which owns the clamp so this function can stay a pure
|
|
50
85
|
* "given these inputs, rank them" step with no knowledge of the tool's own schema bounds).
|
|
51
|
-
* @returns Matches with score > 0, sorted by score descending, truncated to `limit`. A candidate
|
|
86
|
+
* @returns Matches with score > 0, sorted by score descending then plugin id, truncated to `limit`. A candidate
|
|
52
87
|
* that scores 0 (no term matched anything) is dropped, not returned at the bottom — the caller's own
|
|
53
88
|
* "N installed, 0 matched" framing is what tells the model the workspace is not empty even when a
|
|
54
89
|
* query matches nothing.
|
|
55
90
|
* @complexity See this file's header.
|
|
56
91
|
*/
|
|
57
|
-
function rankInstalledAgentPlugins(query, candidates, limit) {
|
|
58
|
-
const terms = tokenize(query);
|
|
92
|
+
function rankInstalledAgentPlugins(query, candidates, limit, automatic = false) {
|
|
93
|
+
const terms = automatic ? automaticTerms(query) : tokenize(query);
|
|
59
94
|
if (terms.length === 0)
|
|
60
95
|
return [];
|
|
61
|
-
|
|
62
|
-
.map((candidate) => ({ ...candidate, score: scoreCandidate(candidate, terms) }))
|
|
96
|
+
const ranked = candidates
|
|
97
|
+
.map((candidate) => ({ ...candidate, score: automatic ? automaticScore({ candidate, terms }) : scoreCandidate(candidate, terms) }))
|
|
63
98
|
.filter((match) => match.score > 0)
|
|
64
|
-
.sort((a, b) => b.score - a.score)
|
|
99
|
+
.sort((a, b) => b.score - a.score || a.pluginId.localeCompare(b.pluginId));
|
|
100
|
+
// A weak shared keyword must not ride alongside a substantially stronger task owner.
|
|
101
|
+
const threshold = automatic ? Math.max(4, (ranked[0]?.score ?? 0) * 0.6) : 0;
|
|
102
|
+
return ranked.filter(match => match.score >= threshold)
|
|
65
103
|
.slice(0, Math.max(limit, 0));
|
|
66
104
|
}
|
|
67
105
|
return { rankInstalledAgentPlugins };
|
|
@@ -20,9 +20,10 @@ export interface SetAgentPluginEnabledResult {
|
|
|
20
20
|
readonly pluginId: string;
|
|
21
21
|
/** As actually written and read back, not as requested. */
|
|
22
22
|
readonly enabled: boolean;
|
|
23
|
+
readonly warnings?: readonly string[];
|
|
23
24
|
}
|
|
24
25
|
/** Internal composition; one state/queue instance per injected host context. */
|
|
25
26
|
export declare function createSetEnabledModule(ports: AgentPluginLifecyclePorts): {
|
|
26
|
-
setAgentPluginEnabled: (input: SetAgentPluginEnabledInput) => Promise<SetAgentPluginEnabledResult>;
|
|
27
|
+
setAgentPluginEnabled: (input: SetAgentPluginEnabledInput, _optional?: Record<string, never>) => Promise<SetAgentPluginEnabledResult>;
|
|
27
28
|
};
|
|
28
29
|
//# sourceMappingURL=set-enabled.d.ts.map
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** Extracted from the host plugin lifecycle. Effects are supplied per host context. */
|
|
2
|
+
import { createPersistentStateModule } from './persistent-state.js';
|
|
3
|
+
import { AgentPluginHostIncompatibleError, describeAgentPluginCompatibility, agentPluginDependentWarnings } from './compatibility.js';
|
|
2
4
|
import { createActivationModule } from "./activation.js";
|
|
3
5
|
import { createResolveAgentPluginRefsModule } from "./resolve-agent-plugin-refs.js";
|
|
6
|
+
import { createAgentPluginUpdateStore, settleAgentPluginUpdateJournal } from "./update-store.js";
|
|
4
7
|
/**
|
|
5
8
|
* The id named is not installed in this workspace. A distinct class rather than a bare `Error` so
|
|
6
9
|
* each caller can map it to its own boundary's vocabulary — 404 `AGENT_PLUGIN_NOT_FOUND` at the
|
|
@@ -13,21 +16,62 @@ export class AgentPluginNotInstalledError extends Error {
|
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
function buildModule(ports) {
|
|
16
|
-
const { setAgentPluginActivation } = createActivationModule(ports);
|
|
19
|
+
const { setAgentPluginActivation, readAgentPluginActivations } = createActivationModule(ports);
|
|
17
20
|
const { listInstalledPlugins } = createResolveAgentPluginRefsModule(ports);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const installed = await listInstalledPlugins(
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const persistentState = createPersistentStateModule(ports);
|
|
22
|
+
async function findInstalled({ workspaceRoot, pluginId }) {
|
|
23
|
+
const installed = await listInstalledPlugins(workspaceRoot);
|
|
24
|
+
const plugin = installed.find(candidate => candidate.pluginId === pluginId);
|
|
25
|
+
if (!plugin) {
|
|
26
|
+
throw new AgentPluginNotInstalledError({ pluginId });
|
|
23
27
|
}
|
|
28
|
+
return { installed, plugin };
|
|
29
|
+
}
|
|
30
|
+
/** Refuses a host-incompatible enable; otherwise returns advisory warnings, never blocking. */
|
|
31
|
+
async function enablementWarnings({ input, workspaceRoot, installed, plugin }) {
|
|
32
|
+
const activations = await readAgentPluginActivations(workspaceRoot);
|
|
33
|
+
if (!input.enabled)
|
|
34
|
+
return agentPluginDependentWarnings({ plugin, installed, activations, nextProvides: [] });
|
|
35
|
+
const compatibility = describeAgentPluginCompatibility({ plugin, installed, activations });
|
|
36
|
+
// Several digests of one id can be installed (a bundled upgrade lands beside the old one), and
|
|
37
|
+
// the listing order is arbitrary, so every installed revision must be host-compatible.
|
|
38
|
+
const revisions = installed.filter(candidate => candidate.pluginId === plugin.pluginId);
|
|
39
|
+
if (revisions.some(revision => !describeAgentPluginCompatibility({ plugin: revision, installed, activations }).hostOk)) {
|
|
40
|
+
throw new AgentPluginHostIncompatibleError({ pluginId: input.pluginId });
|
|
41
|
+
}
|
|
42
|
+
return compatibility.missingRequires.map(id => `Needs ${id}`);
|
|
43
|
+
}
|
|
44
|
+
async function setEnabledLocked({ input, workspaceRoot }) {
|
|
45
|
+
// Re-read under the lock: an uninstall or update may have finished while this call waited.
|
|
46
|
+
const { installed, plugin } = await findInstalled({ workspaceRoot, pluginId: input.pluginId });
|
|
47
|
+
// Boot recovery of an unfinished update would overwrite this decision, so it refuses
|
|
48
|
+
// (PLUGIN_UPDATE_PENDING) until a boot recovers it.
|
|
49
|
+
await settleAgentPluginUpdateJournal({ store: createAgentPluginUpdateStore({ ports, workspaceId: input.workspaceId, pluginId: input.pluginId }) });
|
|
50
|
+
const warnings = await enablementWarnings({ input, workspaceRoot, installed, plugin });
|
|
24
51
|
const written = await setAgentPluginActivation({
|
|
25
|
-
workspaceRoot
|
|
52
|
+
workspaceRoot,
|
|
26
53
|
pluginId: input.pluginId,
|
|
27
54
|
enabled: input.enabled,
|
|
28
55
|
actor: input.actor,
|
|
29
56
|
});
|
|
30
|
-
|
|
57
|
+
// Always present: setAgentPluginActivation asserts the same id grammar its normalizer keeps and
|
|
58
|
+
// writes a boolean `enabled`, so the record it returns holds this entry (the former
|
|
59
|
+
// `?? input.enabled` fallback was unreachable).
|
|
60
|
+
return { pluginId: input.pluginId, enabled: written.plugins[input.pluginId].enabled, ...(warnings.length ? { warnings } : {}) };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Serialized with install/update/uninstall of the same plugin by the per-plugin state lock, so a
|
|
64
|
+
* toggle never judges compatibility against a package that is mid-swap. The lock is a
|
|
65
|
+
* non-reentrant file lock: no caller holds it around this function (checked 2026-10-21 in this
|
|
66
|
+
* package and in Tovu's admin route / `plugins_set_enabled` tool).
|
|
67
|
+
*/
|
|
68
|
+
async function setAgentPluginEnabled(input, _optional = {}) {
|
|
69
|
+
const workspaceRoot = ports.layout.forWorkspace({ workspaceId: input.workspaceId }).root;
|
|
70
|
+
// Checked once before locking so an unknown or ungrammatical id stays a not-installed refusal
|
|
71
|
+
// and never creates workspace or lock directories (the lock path re-asserts the id grammar).
|
|
72
|
+
await findInstalled({ workspaceRoot, pluginId: input.pluginId });
|
|
73
|
+
return persistentState.withPluginLock({ workspaceRoot, pluginId: input.pluginId,
|
|
74
|
+
run: () => setEnabledLocked({ input, workspaceRoot }) });
|
|
31
75
|
}
|
|
32
76
|
return { setAgentPluginEnabled };
|
|
33
77
|
}
|