@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,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the host-namespace compatibility metadata (`extensions.<ns>.requires` / `hostRange`).
|
|
3
|
+
*
|
|
4
|
+
* A leaf on purpose: `manifest.ts` calls this while `compatibility.ts` needs manifest-shaped
|
|
5
|
+
* types, so keeping the reader here (semver only, no lifecycle imports) keeps the lifecycle
|
|
6
|
+
* import graph acyclic (`__tests__/type-cycles.test.ts`).
|
|
7
|
+
*/
|
|
8
|
+
import * as semver from 'semver';
|
|
9
|
+
/** Stored in place of a present-but-unusable range so the plugin stays listable but never enables. */
|
|
10
|
+
export const INVALID_AGENT_PLUGIN_HOST_RANGE = '<invalid>';
|
|
11
|
+
/** A blank string is a semver wildcard, so blank is rejected here rather than silently allowed.
|
|
12
|
+
* The same holds for a blank `||` alternative: semver reads '>=99 ||' as '*', i.e. allow-all. */
|
|
13
|
+
export function isValidAgentPluginHostRange({ range }, _optional = {}) {
|
|
14
|
+
return range.split('||').every(part => part.trim().length > 0) && semver.validRange(range) !== null;
|
|
15
|
+
}
|
|
16
|
+
function isCapabilityId(value) {
|
|
17
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
18
|
+
}
|
|
19
|
+
function readRequires({ value, warnings }) {
|
|
20
|
+
if (value === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (Array.isArray(value) && value.every(isCapabilityId))
|
|
23
|
+
return [...new Set(value.map(id => id.trim()))];
|
|
24
|
+
warnings.push('requires must be an array of non-empty capability ids');
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
function readHostRange({ value, warnings }) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (typeof value === 'string' && isValidAgentPluginHostRange({ range: value }))
|
|
31
|
+
return value;
|
|
32
|
+
warnings.push('hostRange is invalid; enabling this plugin is refused');
|
|
33
|
+
return INVALID_AGENT_PLUGIN_HOST_RANGE;
|
|
34
|
+
}
|
|
35
|
+
/** Reads metadata in the host-selected namespace. A present malformed range stays incompatible;
|
|
36
|
+
* malformed `requires` is advisory only, so it is dropped with a warning.
|
|
37
|
+
* @returns Normalized advisory metadata and parse warnings. @complexity O(r).
|
|
38
|
+
*/
|
|
39
|
+
export function readAgentPluginCompatibilityMetadata({ extension }, _optional = {}) {
|
|
40
|
+
const warnings = [];
|
|
41
|
+
const requires = readRequires({ value: extension.requires, warnings });
|
|
42
|
+
const hostRange = readHostRange({ value: extension.hostRange, warnings });
|
|
43
|
+
return { ...(requires === undefined ? {} : { requires }), ...(hostRange === undefined ? {} : { hostRange }), warnings };
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=compatibility-metadata.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { InstalledAgentPlugin } from './types.js';
|
|
2
|
+
import type { AgentPluginActivations, AgentPluginOrigin } from './activation.js';
|
|
3
|
+
export { readAgentPluginCompatibilityMetadata, isValidAgentPluginHostRange, INVALID_AGENT_PLUGIN_HOST_RANGE } from './compatibility-metadata.js';
|
|
4
|
+
/** Host extension contract, independent of product releases and the upstream manifest schema.
|
|
5
|
+
* `hostRange` is checked against this, never against an app version. */
|
|
6
|
+
export declare const AGENT_PLUGIN_CONTRACT_VERSION = "1.0.0";
|
|
7
|
+
/** Named refusal shared by HTTP and tool adapters. */
|
|
8
|
+
export declare class AgentPluginHostIncompatibleError extends Error {
|
|
9
|
+
readonly code = "PLUGIN_HOST_INCOMPATIBLE";
|
|
10
|
+
constructor(required: {
|
|
11
|
+
pluginId: string;
|
|
12
|
+
}, _optional?: Record<string, never>);
|
|
13
|
+
}
|
|
14
|
+
export interface AgentPluginCompatibility {
|
|
15
|
+
readonly hostOk: boolean;
|
|
16
|
+
readonly missingRequires: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
/** Projects live metadata, excluding disabled providers and the plugin itself.
|
|
19
|
+
* @returns Origin, host compatibility, and advisory requirements. @complexity O(p + r).
|
|
20
|
+
*/
|
|
21
|
+
export declare function describeAgentPluginCompatibility({ plugin, installed, activations }: {
|
|
22
|
+
plugin: InstalledAgentPlugin;
|
|
23
|
+
installed: readonly InstalledAgentPlugin[];
|
|
24
|
+
activations: AgentPluginActivations;
|
|
25
|
+
}, { hostVersion }?: {
|
|
26
|
+
hostVersion?: string;
|
|
27
|
+
}): AgentPluginCompatibility & {
|
|
28
|
+
readonly origin: AgentPluginOrigin;
|
|
29
|
+
};
|
|
30
|
+
/** Names active dependents that would lose a capability after disabling or updating a provider.
|
|
31
|
+
* Other enabled providers can satisfy the same requirement. Advisory only. @complexity O(p * (p + r)).
|
|
32
|
+
*/
|
|
33
|
+
export declare function agentPluginDependentWarnings({ plugin, installed, activations, nextProvides }: {
|
|
34
|
+
plugin: InstalledAgentPlugin;
|
|
35
|
+
installed: readonly InstalledAgentPlugin[];
|
|
36
|
+
activations: AgentPluginActivations;
|
|
37
|
+
nextProvides: readonly string[];
|
|
38
|
+
}, _optional?: Record<string, never>): string[];
|
|
39
|
+
/** Checks the declared host contract and advisory capabilities; never throws for a bad range.
|
|
40
|
+
* Callers supply capabilities from other enabled plugins only (no self-satisfaction).
|
|
41
|
+
* Uses `semver.satisfies` with default options, the same check site plugins apply to `sdkRange`.
|
|
42
|
+
* @returns Compatibility plus unmet advisory requirements. @complexity O(r + p).
|
|
43
|
+
*/
|
|
44
|
+
export declare function checkAgentPluginCompatibility({ manifest, hostVersion, activeProvides }: {
|
|
45
|
+
readonly manifest: {
|
|
46
|
+
readonly hostRange?: string | undefined;
|
|
47
|
+
readonly requires?: readonly string[] | undefined;
|
|
48
|
+
};
|
|
49
|
+
readonly hostVersion: string;
|
|
50
|
+
readonly activeProvides: readonly string[];
|
|
51
|
+
}, _optional?: Record<string, never>): AgentPluginCompatibility;
|
|
52
|
+
//# sourceMappingURL=compatibility.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as semver from 'semver';
|
|
2
|
+
import { isValidAgentPluginHostRange } from './compatibility-metadata.js';
|
|
3
|
+
export { readAgentPluginCompatibilityMetadata, isValidAgentPluginHostRange, INVALID_AGENT_PLUGIN_HOST_RANGE } from './compatibility-metadata.js';
|
|
4
|
+
/** Host extension contract, independent of product releases and the upstream manifest schema.
|
|
5
|
+
* `hostRange` is checked against this, never against an app version. */
|
|
6
|
+
export const AGENT_PLUGIN_CONTRACT_VERSION = '1.0.0';
|
|
7
|
+
/** Named refusal shared by HTTP and tool adapters. */
|
|
8
|
+
export class AgentPluginHostIncompatibleError extends Error {
|
|
9
|
+
code = 'PLUGIN_HOST_INCOMPATIBLE';
|
|
10
|
+
constructor(required, _optional = {}) {
|
|
11
|
+
super(`Plugin '${required.pluginId}' is not compatible with this host's Agent Plugin contract.`);
|
|
12
|
+
this.name = 'AgentPluginHostIncompatibleError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Same rule as activation.ts `isAgentPluginActive`: an absent activation record means active. */
|
|
16
|
+
function isActive({ activations, pluginId }) {
|
|
17
|
+
return !Object.hasOwn(activations.plugins, pluginId) || activations.plugins[pluginId].enabled;
|
|
18
|
+
}
|
|
19
|
+
/** Active plugins other than `excludeId`, so a plugin never satisfies its own requirements. */
|
|
20
|
+
function activeOthers({ installed, activations, excludeId }) {
|
|
21
|
+
return installed.filter(other => other.pluginId !== excludeId && isActive({ activations, pluginId: other.pluginId }));
|
|
22
|
+
}
|
|
23
|
+
function providedBy({ plugins }) {
|
|
24
|
+
return plugins.flatMap(plugin => plugin.provides ?? []);
|
|
25
|
+
}
|
|
26
|
+
/** Projects live metadata, excluding disabled providers and the plugin itself.
|
|
27
|
+
* @returns Origin, host compatibility, and advisory requirements. @complexity O(p + r).
|
|
28
|
+
*/
|
|
29
|
+
export function describeAgentPluginCompatibility({ plugin, installed, activations }, { hostVersion = AGENT_PLUGIN_CONTRACT_VERSION } = {}) {
|
|
30
|
+
const activeProvides = providedBy({ plugins: activeOthers({ installed, activations, excludeId: plugin.pluginId }) });
|
|
31
|
+
const origin = Object.hasOwn(activations.plugins, plugin.pluginId) ? activations.plugins[plugin.pluginId].origin : 'operator-installed';
|
|
32
|
+
return { origin, ...checkAgentPluginCompatibility({ manifest: plugin, hostVersion, activeProvides }) };
|
|
33
|
+
}
|
|
34
|
+
/** Requirements of `dependent` that only the removed capabilities satisfied. */
|
|
35
|
+
function lostRequirements({ dependent, providers, removed }) {
|
|
36
|
+
const available = new Set(providedBy({ plugins: providers.filter(provider => provider.pluginId !== dependent.pluginId) }));
|
|
37
|
+
return [...new Set(dependent.requires ?? [])].filter(id => removed.has(id) && !available.has(id));
|
|
38
|
+
}
|
|
39
|
+
/** Names active dependents that would lose a capability after disabling or updating a provider.
|
|
40
|
+
* Other enabled providers can satisfy the same requirement. Advisory only. @complexity O(p * (p + r)).
|
|
41
|
+
*/
|
|
42
|
+
export function agentPluginDependentWarnings({ plugin, installed, activations, nextProvides }, _optional = {}) {
|
|
43
|
+
const removed = new Set((plugin.provides ?? []).filter(id => !nextProvides.includes(id)));
|
|
44
|
+
const others = activeOthers({ installed, activations, excludeId: plugin.pluginId });
|
|
45
|
+
// One dependent id may have several installed digests; report each lost capability once.
|
|
46
|
+
return [...new Set(others.flatMap(dependent => lostRequirements({ dependent, providers: others, removed })
|
|
47
|
+
.map(id => `${dependent.pluginId} needs ${id}`)))];
|
|
48
|
+
}
|
|
49
|
+
/** Checks the declared host contract and advisory capabilities; never throws for a bad range.
|
|
50
|
+
* Callers supply capabilities from other enabled plugins only (no self-satisfaction).
|
|
51
|
+
* Uses `semver.satisfies` with default options, the same check site plugins apply to `sdkRange`.
|
|
52
|
+
* @returns Compatibility plus unmet advisory requirements. @complexity O(r + p).
|
|
53
|
+
*/
|
|
54
|
+
export function checkAgentPluginCompatibility({ manifest, hostVersion, activeProvides }, _optional = {}) {
|
|
55
|
+
const available = new Set(activeProvides);
|
|
56
|
+
const range = manifest.hostRange;
|
|
57
|
+
const hostOk = range === undefined || (isValidAgentPluginHostRange({ range }) && semver.satisfies(hostVersion, range));
|
|
58
|
+
return { hostOk, missingRequires: [...new Set(manifest.requires ?? [])].filter(id => !available.has(id)) };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=compatibility.js.map
|
|
@@ -57,12 +57,28 @@ export async function fetchAgentPluginArchive(required, optional = {}) {
|
|
|
57
57
|
throw new RangeError("maxBytes must be a positive integer");
|
|
58
58
|
assertAllowedUrl(required.url, "requested");
|
|
59
59
|
const diagnosticUrl = redactArchiveUrl({ value: required.url });
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const { response, resolvedUrl: finalRequestedUrl } = await requestArchive({ required, diagnosticUrl }, optional);
|
|
61
|
+
// A server-controlled reason phrase can reflect secrets; use the standard status label.
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new AgentPluginFetchError({ code: "HTTP_ERROR", message: `'${diagnosticUrl}' returned HTTP ${response.status} ${STATUS_CODES[response.status] ?? ''}`.trimEnd() });
|
|
64
|
+
}
|
|
65
|
+
const resolvedUrl = response.url || finalRequestedUrl;
|
|
66
|
+
assertAllowedUrl(resolvedUrl, "redirected-to");
|
|
67
|
+
assertDeclaredSizeWithinCap(response, maxBytes, diagnosticUrl);
|
|
68
|
+
const archive = await readArchiveBody({ response, maxBytes, diagnosticUrl });
|
|
69
|
+
if (archive.byteLength === 0) {
|
|
70
|
+
throw new AgentPluginFetchError({ code: "EMPTY_BODY", message: `'${diagnosticUrl}' returned an empty body` });
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
archive,
|
|
74
|
+
sha256: createHash("sha256").update(archive).digest("hex"),
|
|
75
|
+
resolvedUrl,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Runs the guarded request; any failure becomes one redacted `REQUEST_FAILED`. */
|
|
79
|
+
async function requestArchive({ required, diagnosticUrl }, optional = {}) {
|
|
62
80
|
try {
|
|
63
|
-
|
|
64
|
-
response = downloaded.response;
|
|
65
|
-
finalRequestedUrl = downloaded.resolvedUrl;
|
|
81
|
+
return await fetchWithGuard(required, optional);
|
|
66
82
|
}
|
|
67
83
|
catch {
|
|
68
84
|
// Dependency messages and nested causes can contain signed redirect URLs or bare tokens.
|
|
@@ -71,16 +87,11 @@ export async function fetchAgentPluginArchive(required, optional = {}) {
|
|
|
71
87
|
cause: new Error("archive request failed"),
|
|
72
88
|
});
|
|
73
89
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
const resolvedUrl = response.url || finalRequestedUrl;
|
|
79
|
-
assertAllowedUrl(resolvedUrl, "redirected-to");
|
|
80
|
-
assertDeclaredSizeWithinCap(response, maxBytes, diagnosticUrl);
|
|
81
|
-
let archive;
|
|
90
|
+
}
|
|
91
|
+
/** Reads the body within the cap; an over-cap body stays `ARCHIVE_TOO_LARGE`, anything else one redacted `REQUEST_FAILED`. */
|
|
92
|
+
async function readArchiveBody({ response, maxBytes, diagnosticUrl }) {
|
|
82
93
|
try {
|
|
83
|
-
|
|
94
|
+
return await readBodyWithinCap(response, maxBytes, diagnosticUrl);
|
|
84
95
|
}
|
|
85
96
|
catch (error) {
|
|
86
97
|
// Preserve our bounded-download error; stream failures must not escape as raw transport text.
|
|
@@ -91,14 +102,6 @@ export async function fetchAgentPluginArchive(required, optional = {}) {
|
|
|
91
102
|
cause: new Error("archive response body failed"),
|
|
92
103
|
});
|
|
93
104
|
}
|
|
94
|
-
if (archive.byteLength === 0) {
|
|
95
|
-
throw new AgentPluginFetchError({ code: "EMPTY_BODY", message: `'${diagnosticUrl}' returned an empty body` });
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
archive,
|
|
99
|
-
sha256: createHash("sha256").update(archive).digest("hex"),
|
|
100
|
-
resolvedUrl,
|
|
101
|
-
};
|
|
102
105
|
}
|
|
103
106
|
/**
|
|
104
107
|
* The module's own cap, exported so a caller (or a test pinning it against `install.ts`'s
|
|
@@ -150,10 +153,9 @@ async function readBodyWithinCap(response, maxBytes, url) {
|
|
|
150
153
|
try {
|
|
151
154
|
for (;;) {
|
|
152
155
|
const { done, value } = await reader.read();
|
|
156
|
+
// A read that is not `done` always carries a chunk (`ReadableStreamReadValueResult`).
|
|
153
157
|
if (done)
|
|
154
158
|
break;
|
|
155
|
-
if (value === undefined)
|
|
156
|
-
continue;
|
|
157
159
|
total += value.byteLength;
|
|
158
160
|
if (total > maxBytes) {
|
|
159
161
|
throw new AgentPluginFetchError({ code: "ARCHIVE_TOO_LARGE", message: `'${url}' body exceeded the ${maxBytes}-byte cap` });
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export * from './update-plan.js';
|
|
2
|
+
export { AgentPluginUpdateError } from './update-store.js';
|
|
3
|
+
export type { UpdateAgentPluginRequired, UpdateAgentPluginResult } from './update.js';
|
|
4
|
+
export * from './compatibility.js';
|
|
5
|
+
export * from './automatic-use.js';
|
|
1
6
|
import { maxAgentPluginArchiveBytes } from './fetch-archive.js';
|
|
2
7
|
import type { FetchAgentPluginArchiveOptional } from './fetch-archive.js';
|
|
3
8
|
import type { AgentPluginLifecycleRequired, AgentPluginLifecycleOptional } from './ports.js';
|
|
@@ -13,6 +18,7 @@ export * from './manifest.js';
|
|
|
13
18
|
export * from './fetch-archive.js';
|
|
14
19
|
export * from './mcp-provisioning.js';
|
|
15
20
|
export * from './contribution-loader.js';
|
|
21
|
+
export * from './provider-contribution.js';
|
|
16
22
|
export { createAgentPluginActivations, ACTIVATIONS_FILENAME, ACTIVATIONS_LOCK_FILENAME, AgentPluginActivationsBusyError, AgentPluginActivationsUnreadableError } from './activation.js';
|
|
17
23
|
export { BUNDLED_DIGESTS_FILENAME } from './bundled-digests.js';
|
|
18
24
|
export { AgentPluginInstallError } from './install.js';
|
|
@@ -72,7 +78,7 @@ export declare function createAgentPluginLifecycle(required: AgentPluginLifecycl
|
|
|
72
78
|
}) => Promise<boolean>;
|
|
73
79
|
installAgentPlugin: (required: import("./install.js").InstallAgentPluginRequired, optional?: import("./install.js").InstallAgentPluginOptional) => Promise<import("./types.js").InstalledAgentPlugin>;
|
|
74
80
|
installAgentPluginFromUrl: (required: import("./install-from-url.js").InstallAgentPluginFromUrlRequired, optional?: import("./install-from-url.js").InstallAgentPluginFromUrlOptional) => Promise<import("./install-from-url.js").InstalledAgentPluginFromUrl>;
|
|
75
|
-
setAgentPluginEnabled: (input: import("./set-enabled.js").SetAgentPluginEnabledInput) => Promise<import("./set-enabled.js").SetAgentPluginEnabledResult>;
|
|
81
|
+
setAgentPluginEnabled: (input: import("./set-enabled.js").SetAgentPluginEnabledInput, _optional?: Record<string, never>) => Promise<import("./set-enabled.js").SetAgentPluginEnabledResult>;
|
|
76
82
|
findTrustedPluginPackages: (input: Omit<Parameters<(query: import("./trusted-plugin-files.js").TrustedPluginPackagesQuery) => Promise<readonly import("./trusted-plugin-files.js").TrustedPluginVerdict[]>>[0], "orderByPluginId" | "onInactive">, optional?: Pick<Parameters<(query: import("./trusted-plugin-files.js").TrustedPluginPackagesQuery) => Promise<readonly import("./trusted-plugin-files.js").TrustedPluginVerdict[]>>[0], "orderByPluginId" | "onInactive">) => Promise<readonly import("./trusted-plugin-files.js").TrustedPluginVerdict[]>;
|
|
77
83
|
previewAgentPluginUninstall: (required: import("./uninstall.js").UninstallAgentPluginRequired) => Promise<import("./uninstall.js").AgentPluginUninstallPreview>;
|
|
78
84
|
uninstallAgentPlugin: (required: import("./uninstall.js").UninstallAgentPluginRequired, optional?: import("./uninstall.js").UninstallAgentPluginOptional) => Promise<import("./uninstall.js").UninstallAgentPluginResult>;
|
|
@@ -176,8 +182,10 @@ export declare function createAgentPluginLifecycle(required: AgentPluginLifecycl
|
|
|
176
182
|
}) => Promise<boolean>;
|
|
177
183
|
rankInstalledAgentPlugins: (input: {
|
|
178
184
|
readonly query: string;
|
|
179
|
-
readonly candidates: Parameters<(query: string, candidates: readonly import("./search.js").AgentPluginSearchCandidate[], limit: number) => readonly import("./search.js").AgentPluginSearchMatch[]>[1];
|
|
185
|
+
readonly candidates: Parameters<(query: string, candidates: readonly import("./search.js").AgentPluginSearchCandidate[], limit: number, automatic?: boolean) => readonly import("./search.js").AgentPluginSearchMatch[]>[1];
|
|
180
186
|
readonly limit: number;
|
|
187
|
+
}, optional?: {
|
|
188
|
+
readonly automatic?: boolean;
|
|
181
189
|
}) => readonly import("./search.js").AgentPluginSearchMatch[];
|
|
182
190
|
readTrustedPluginFile: (input: {
|
|
183
191
|
readonly plugin: Parameters<(plugin: import("./trusted-plugin-files.js").TrustedPluginPackage, filename: string) => Promise<string>>[0];
|
|
@@ -208,5 +216,9 @@ export declare function createAgentPluginLifecycle(required: AgentPluginLifecycl
|
|
|
208
216
|
readonly plugin: import("./trusted-plugin-files.js").TrustedPluginPackage;
|
|
209
217
|
readonly definition: import("./contribution-loader.js").PluginContributionDefinition<D, T>;
|
|
210
218
|
}, _options?: Record<string, never>) => Promise<import("./contribution-loader.js").PluginContributionResult<T>>;
|
|
219
|
+
updateAgentPlugin: (required: import("./update.js").UpdateAgentPluginRequired, _optional?: Record<string, never>) => Promise<import("./update.js").UpdateAgentPluginResult>;
|
|
220
|
+
reconcileAgentPluginUpdates: ({ workspaceId }: {
|
|
221
|
+
workspaceId: string;
|
|
222
|
+
}, _optional?: Record<string, never>) => Promise<void>;
|
|
211
223
|
};
|
|
212
224
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/lifecycle/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export * from './update-plan.js';
|
|
2
|
+
export { AgentPluginUpdateError } from './update-store.js';
|
|
3
|
+
import { createUpdateModule } from './update.js';
|
|
4
|
+
export * from './compatibility.js';
|
|
5
|
+
export * from './automatic-use.js';
|
|
1
6
|
import { createPersistentStateModule } from "./persistent-state.js";
|
|
2
7
|
import { fetchAgentPluginArchive, maxAgentPluginArchiveBytes } from './fetch-archive.js';
|
|
3
8
|
import { createSeedBundledModule } from './seed-bundled.js';
|
|
@@ -22,6 +27,7 @@ export * from './manifest.js';
|
|
|
22
27
|
export * from './fetch-archive.js';
|
|
23
28
|
export * from './mcp-provisioning.js';
|
|
24
29
|
export * from './contribution-loader.js';
|
|
30
|
+
export * from './provider-contribution.js';
|
|
25
31
|
export { createAgentPluginActivations, ACTIVATIONS_FILENAME, ACTIVATIONS_LOCK_FILENAME, AgentPluginActivationsBusyError, AgentPluginActivationsUnreadableError } from './activation.js';
|
|
26
32
|
export { BUNDLED_DIGESTS_FILENAME } from './bundled-digests.js';
|
|
27
33
|
export { AgentPluginInstallError } from './install.js';
|
|
@@ -56,6 +62,7 @@ export function createAgentPluginLifecycle(required, optional = {}) {
|
|
|
56
62
|
});
|
|
57
63
|
const uninstall = createUninstallModule(context);
|
|
58
64
|
return {
|
|
65
|
+
...createUpdateModule(context),
|
|
59
66
|
...contributions,
|
|
60
67
|
fetchAgentPluginArchive: (input, optional = {}) => fetchAgentPluginArchive({ ...input, fetch: context.fetch, outboundGuard: context.outboundGuard }, optional),
|
|
61
68
|
maxAgentPluginArchiveBytes,
|
|
@@ -102,7 +109,7 @@ export function createAgentPluginLifecycle(required, optional = {}) {
|
|
|
102
109
|
}),
|
|
103
110
|
listInstalledPlugins: (input) => refs.listInstalledPlugins(input.workspaceRoot),
|
|
104
111
|
isInstalledDigestPresent: (input) => refs.isInstalledDigestPresent(input.packagesDir, input.archiveDigest),
|
|
105
|
-
rankInstalledAgentPlugins: (input) => search.rankInstalledAgentPlugins(input.query, input.candidates, input.limit),
|
|
112
|
+
rankInstalledAgentPlugins: (input, optional = {}) => search.rankInstalledAgentPlugins(input.query, input.candidates, input.limit, optional.automatic),
|
|
106
113
|
readTrustedPluginFile: (input) => trusted.readTrustedPluginFile(input.plugin, input.filename),
|
|
107
114
|
importContainedModule: (input) => trusted.importContainedModule(input.plugin, input.modulePath),
|
|
108
115
|
maxAgentPluginInstallArchiveBytes: (_input) => install.maxAgentPluginInstallArchiveBytes(),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** One extraction/install failure reason. A caller (an admin route, a future marketplace installer)
|
|
2
|
+
* branches on `code` rather than parsing `message` — this codebase's own convention
|
|
3
|
+
* (`PluginLoadError.reason` in `plugin-runtime/loader.ts` is the direct precedent). */
|
|
4
|
+
export type AgentPluginInstallErrorCode = "PLUGIN_ID_CONFLICT" | "ARCHIVE_TOO_LARGE" | "DIGEST_MISMATCH" | "TOO_MANY_ENTRIES" | "UNSAFE_ENTRY_PATH" | "SYMLINK_ENTRY_REJECTED" | "UNSUPPORTED_ENTRY_KIND" | "DUPLICATE_ENTRY" | "FILE_TOO_LARGE" | "DECOMPRESSION_BOMB" | "TOTAL_SIZE_EXCEEDED" | "MANIFEST_MISSING" | "MANIFEST_INVALID" | "PUBLISH_FAILED";
|
|
5
|
+
export declare class AgentPluginInstallError extends Error {
|
|
6
|
+
readonly code: AgentPluginInstallErrorCode;
|
|
7
|
+
constructor({ code, message }: {
|
|
8
|
+
readonly code: AgentPluginInstallErrorCode;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
}, options?: {
|
|
11
|
+
cause?: unknown;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=install-errors.d.ts.map
|
|
@@ -2,19 +2,8 @@ import type { AgentPluginLayout } from "./layout.js";
|
|
|
2
2
|
import type { AgentPluginLifecyclePorts } from "./ports.js";
|
|
3
3
|
import type { InstalledAgentPlugin } from "./types.js";
|
|
4
4
|
export type { InstalledAgentPlugin, InstalledAgentPluginSkill } from "./types.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* (`PluginLoadError.reason` in `plugin-runtime/loader.ts` is the direct precedent). */
|
|
8
|
-
export type AgentPluginInstallErrorCode = "PLUGIN_ID_CONFLICT" | "ARCHIVE_TOO_LARGE" | "DIGEST_MISMATCH" | "TOO_MANY_ENTRIES" | "UNSAFE_ENTRY_PATH" | "SYMLINK_ENTRY_REJECTED" | "UNSUPPORTED_ENTRY_KIND" | "DUPLICATE_ENTRY" | "FILE_TOO_LARGE" | "DECOMPRESSION_BOMB" | "TOTAL_SIZE_EXCEEDED" | "MANIFEST_MISSING" | "MANIFEST_INVALID" | "PUBLISH_FAILED";
|
|
9
|
-
export declare class AgentPluginInstallError extends Error {
|
|
10
|
-
readonly code: AgentPluginInstallErrorCode;
|
|
11
|
-
constructor({ code, message }: {
|
|
12
|
-
readonly code: AgentPluginInstallErrorCode;
|
|
13
|
-
readonly message: string;
|
|
14
|
-
}, options?: {
|
|
15
|
-
cause?: unknown;
|
|
16
|
-
});
|
|
17
|
-
}
|
|
5
|
+
export { AgentPluginInstallError } from "./install-errors.js";
|
|
6
|
+
export type { AgentPluginInstallErrorCode } from "./install-errors.js";
|
|
18
7
|
/** One archive entry. A symlink is modeled as its OWN kind rather than folded into "file" — the
|
|
19
8
|
* zip-slip research this module's header cites is explicit that the symlink-entry vector is
|
|
20
9
|
* distinct from the lexical-traversal one, and the two need independent tests and independent
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { listInstalledPackageRoots } from "./installed-package-roots.js";
|
|
2
2
|
import { createUninstallModule } from "./uninstall.js";
|
|
3
|
+
import { AgentPluginInstallError } from "./install-errors.js";
|
|
4
|
+
import { createInstalledPackageIndex } from "./installed-package-index.js";
|
|
3
5
|
import { readPluginIdentity, assertPluginIdentityAvailable } from "@jini-ai/core/plugin-directory";
|
|
4
6
|
/**
|
|
5
7
|
* @file `installAgentPlugin()` — content-addressed, adversarially-hardened extraction of one Agent
|
|
@@ -60,33 +62,15 @@ import { readPluginIdentity, assertPluginIdentityAvailable } from "@jini-ai/core
|
|
|
60
62
|
* (archive bytes and their expected digest are caller-supplied — see the module header above for why
|
|
61
63
|
* "how does the archive get here" is out of this slice) and no execution of anything extracted.
|
|
62
64
|
*/
|
|
63
|
-
/** One trimmed, non-blank string field of the host-supplied extension namespace, capped at `maxLength`, else
|
|
64
|
-
* `undefined` — a malformed value falls back rather than failing the whole index, since every
|
|
65
|
-
* field read this way is cosmetic. The cap stops a hostile package flooding the admin row.
|
|
66
|
-
* @complexity O(maxLength). */
|
|
67
|
-
/** `{ displayName }` when the host namespace's `displayName` is a non-blank string, else `{}` (callers
|
|
68
|
-
* title-case the id).
|
|
69
|
-
* @complexity O(1). */
|
|
70
|
-
/** `{ summary }` when the host namespace's `summary` is a non-blank string, else `{}` (the admin shows
|
|
71
|
-
* the spec `description` instead). Inner newlines are kept: blank lines are the paragraph breaks
|
|
72
|
-
* the admin renders.
|
|
73
|
-
* @complexity O(1). */
|
|
74
65
|
import { createPersistentStateModule } from "./persistent-state.js";
|
|
66
|
+
import { createAgentPluginUpdateStore, settleAgentPluginUpdateJournal } from "./update-store.js";
|
|
75
67
|
/** Extracted from the host plugin lifecycle. Effects are supplied per host context. */
|
|
76
68
|
import { createHash } from "node:crypto";
|
|
77
69
|
import { constants } from "node:fs";
|
|
78
70
|
import path from "node:path";
|
|
79
|
-
import { readAgentPluginExtension, parseAgentPluginManifest as parseAgentPluginManifestValue } from "./manifest.js";
|
|
80
71
|
import { PackagePathViolation } from "./package-paths.js";
|
|
81
72
|
import { createPackagePathsModule } from "./package-paths.js";
|
|
82
|
-
export
|
|
83
|
-
code;
|
|
84
|
-
constructor({ code, message }, options = {}) {
|
|
85
|
-
super(message, options);
|
|
86
|
-
this.name = "AgentPluginInstallError";
|
|
87
|
-
this.code = code;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
73
|
+
export { AgentPluginInstallError } from "./install-errors.js";
|
|
90
74
|
function buildModule(ports) {
|
|
91
75
|
const persistentState = createPersistentStateModule(ports);
|
|
92
76
|
const chmod = ports.filesystem.chmod.bind(ports.filesystem);
|
|
@@ -98,7 +82,8 @@ function buildModule(ports) {
|
|
|
98
82
|
const rename = ports.filesystem.rename.bind(ports.filesystem);
|
|
99
83
|
const rm = ports.filesystem.rm.bind(ports.filesystem);
|
|
100
84
|
const stat = ports.filesystem.stat.bind(ports.filesystem);
|
|
101
|
-
const
|
|
85
|
+
const index = createInstalledPackageIndex({ ports });
|
|
86
|
+
const indexInstalledRoot = (packageRoot, archiveDigest) => index.indexInstalledRoot({ packageRoot, archiveDigest });
|
|
102
87
|
const { assertContainedOnDisk, normalizePackageEntryPath } = createPackagePathsModule(ports);
|
|
103
88
|
const LIMITS = {
|
|
104
89
|
maxArchiveBytes: 32 * 1024 * 1024,
|
|
@@ -145,7 +130,6 @@ function buildModule(ports) {
|
|
|
145
130
|
*/
|
|
146
131
|
async function pruneSupersededRevisions({ workspaceRoot, installed }, _optional = {}) {
|
|
147
132
|
try {
|
|
148
|
-
// Resolve lazily: uninstall's read-side index uses this install module for manifest indexing.
|
|
149
133
|
const removal = createUninstallModule(ports);
|
|
150
134
|
for (const root of await listInstalledPackageRoots({ ports, workspaceRoot })) {
|
|
151
135
|
if (root.localId !== installed.pluginId || root.archiveDigest === installed.archiveDigest)
|
|
@@ -213,6 +197,12 @@ function buildModule(ports) {
|
|
|
213
197
|
return await persistentState.withIdentityLock({ workspaceRoot: workspaceLayout.root, run: async () => {
|
|
214
198
|
await assertIdentityAvailable(workspaceLayout.root, indexed);
|
|
215
199
|
const publishWithinPluginLock = () => persistentState.withPluginLock({ workspaceRoot: workspaceLayout.root, pluginId: indexed.pluginId, run: async () => {
|
|
200
|
+
// Every install and replacement, not only an update, is a mutation of this plugin: an
|
|
201
|
+
// unfinished update journal refuses it here, under the same plugin lock, because boot
|
|
202
|
+
// recovery removes only the journal's own candidate digest and would otherwise restore the
|
|
203
|
+
// old revision beside whatever this install published.
|
|
204
|
+
// The journal lives under the layout this install was given, the one it publishes into.
|
|
205
|
+
await settleAgentPluginUpdateJournal({ store: createAgentPluginUpdateStore({ ports: { ...ports, layout }, workspaceId, pluginId: indexed.pluginId }) });
|
|
216
206
|
// Layout B needs the manifest id before choosing the package destination. Dedup still
|
|
217
207
|
// skips publication and freezing, after one bounded staging extraction.
|
|
218
208
|
// Owner decision 2026-10-04: learned facts begin empty for each site; author guidance stays
|
|
@@ -400,107 +390,6 @@ function buildModule(ports) {
|
|
|
400
390
|
}
|
|
401
391
|
return totalBytes;
|
|
402
392
|
}
|
|
403
|
-
/** Cosmetic fields are trimmed and capped rather than invalidating the package. The cap stops
|
|
404
|
-
* a hostile package flooding an admin row. Inner newlines remain paragraph breaks in summaries.
|
|
405
|
-
* @complexity O(n) in the supplied text length; output is capped at maxLength. */
|
|
406
|
-
function readDisplayText(required, _optional = {}) {
|
|
407
|
-
const text = readAgentPluginExtension({ manifest: required.manifest, namespace: ports.extensionNamespace,
|
|
408
|
-
read: ({ value }) => {
|
|
409
|
-
const field = value[required.key];
|
|
410
|
-
return typeof field === 'string' ? field.trim() : undefined;
|
|
411
|
-
} });
|
|
412
|
-
return text ? { [required.key]: text.slice(0, required.maxLength) } : {};
|
|
413
|
-
}
|
|
414
|
-
/**
|
|
415
|
-
* Reads the (already-extracted-and-trusted, or already-published) `plugin.json` at `packageRoot`
|
|
416
|
-
* and walks the tree to build the `files`/`skills` index. Shared between a fresh install and the
|
|
417
|
-
* content-addressed dedup fast path so both return an identically-shaped result.
|
|
418
|
-
*
|
|
419
|
-
* Exported (2026-08-21) for a second caller outside this module:
|
|
420
|
-
* `resolve-agent-plugin-refs.ts`'s run-time resolution of a pinned `pluginRefId` against the
|
|
421
|
-
* packages already on disk needs the SAME `plugin.json`-parse-plus-tree-walk this function
|
|
422
|
-
* already does correctly (manifest validation via `parseAgentPluginManifest`, sorted `files`), so
|
|
423
|
-
* it re-derives an `InstalledAgentPlugin` per installed digest rather than hand-rolling a second,
|
|
424
|
-
* less-validated reader.
|
|
425
|
-
*/
|
|
426
|
-
// Conditional spreads, not `field: parsed.manifest.field` directly: an `exactOptionalPropertyTypes`-
|
|
427
|
-
// style caller (and this codebase's own convention elsewhere — see `buildToolSource`'s identical
|
|
428
|
-
// `defaultSkillReason` spread in `tool-registrations.ts`) must see an ABSENT key, not a key present
|
|
429
|
-
// with value `undefined`, when the manifest declared none of these — `deepEqual` fixtures across
|
|
430
|
-
// this feature's own tests already assert exact object shape.
|
|
431
|
-
async function indexInstalledRoot(packageRoot, /** SHA-256 of the raw archive bytes — the content-addressing key and the descriptor `revision`
|
|
432
|
-
* a future capability projection pins invocation to (`capability-projection.ts`). */ archiveDigest) {
|
|
433
|
-
let manifestRaw;
|
|
434
|
-
try {
|
|
435
|
-
manifestRaw = await readFile(path.join(packageRoot, "plugin.json"), "utf8");
|
|
436
|
-
}
|
|
437
|
-
catch (error) {
|
|
438
|
-
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
439
|
-
throw new AgentPluginInstallError({ code: "MANIFEST_MISSING", message: "the archive does not contain a plugin.json at its root" });
|
|
440
|
-
}
|
|
441
|
-
throw error;
|
|
442
|
-
}
|
|
443
|
-
let manifestValue;
|
|
444
|
-
try {
|
|
445
|
-
manifestValue = JSON.parse(manifestRaw);
|
|
446
|
-
}
|
|
447
|
-
catch (error) {
|
|
448
|
-
throw new AgentPluginInstallError({ code: "MANIFEST_INVALID", message: "plugin.json is not valid JSON" }, { cause: error });
|
|
449
|
-
}
|
|
450
|
-
const parsed = parseAgentPluginManifest(manifestValue);
|
|
451
|
-
if (!parsed.ok) {
|
|
452
|
-
throw new AgentPluginInstallError({ code: "MANIFEST_INVALID", message: `plugin.json failed validation: ${parsed.errors.join("; ")}` });
|
|
453
|
-
}
|
|
454
|
-
const files = [];
|
|
455
|
-
for (const message of parsed.warnings)
|
|
456
|
-
ports.onEvent?.({ event: 'agent-plugin-identity-warning', message });
|
|
457
|
-
const skills = [];
|
|
458
|
-
async function walk(absolute, prefix) {
|
|
459
|
-
for (const entry of await readdir(absolute, { withFileTypes: true })) {
|
|
460
|
-
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
461
|
-
if (entry.isDirectory()) {
|
|
462
|
-
await walk(path.join(absolute, entry.name), relative);
|
|
463
|
-
}
|
|
464
|
-
else if (entry.isFile()) {
|
|
465
|
-
files.push(relative);
|
|
466
|
-
const match = relative.match(/^skills\/([^/]+)\/SKILL\.md$/);
|
|
467
|
-
if (match)
|
|
468
|
-
skills.push({ name: match[1], skillPath: relative });
|
|
469
|
-
}
|
|
470
|
-
// Anything that is neither a directory nor a regular file (a symlink somehow already present,
|
|
471
|
-
// a device node) is skipped rather than indexed — extraction itself never creates one, so
|
|
472
|
-
// encountering one here would mean the filesystem changed out from under an already-published,
|
|
473
|
-
// supposedly-frozen package, which this function has no business acting on either way.
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
await walk(packageRoot, "");
|
|
477
|
-
return {
|
|
478
|
-
pluginId: parsed.manifest.name,
|
|
479
|
-
id: parsed.manifest.id,
|
|
480
|
-
provides: parsed.manifest.provides,
|
|
481
|
-
version: parsed.manifest.version,
|
|
482
|
-
...( /**
|
|
483
|
-
* `plugin.json`'s own `description`/`keywords`/`author`/`license` (`manifest.ts`'s
|
|
484
|
-
* `AgentPluginManifest`) — parsed at index time but, before 2026-09-09, discarded immediately
|
|
485
|
-
* after: only `name`/`version` ever reached `InstalledAgentPlugin`. Added for
|
|
486
|
-
* `search_agent_plugin_local` (`tool-registrations.ts`), which needs the description and the
|
|
487
|
-
* author-curated keywords to rank installed plugins against a free-text query — the manifest is
|
|
488
|
-
* already read and parsed here on every call, so carrying these through is zero additional I/O,
|
|
489
|
-
* not a new read path. All four are optional because the spec itself makes them optional (the real
|
|
490
|
-
* installed `ui-ux-design` fixture ships `description` but no `keywords`; a bare-minimum manifest
|
|
491
|
-
* ships neither) — a caller must not assume any of them are present.
|
|
492
|
-
*/parsed.manifest.description !== undefined ? { description: parsed.manifest.description } : {}),
|
|
493
|
-
...(parsed.manifest.keywords !== undefined ? { keywords: parsed.manifest.keywords } : {}),
|
|
494
|
-
...(parsed.manifest.author !== undefined ? { author: parsed.manifest.author } : {}),
|
|
495
|
-
...(parsed.manifest.license !== undefined ? { license: parsed.manifest.license } : {}),
|
|
496
|
-
...readDisplayText({ manifest: parsed.manifest, key: "displayName", maxLength: 64 }),
|
|
497
|
-
...readDisplayText({ manifest: parsed.manifest, key: "summary", maxLength: 1200 }),
|
|
498
|
-
archiveDigest,
|
|
499
|
-
packageRoot,
|
|
500
|
-
files: files.sort(),
|
|
501
|
-
skills: skills.sort((a, b) => a.name.localeCompare(b.name)),
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
393
|
/** Makes every file/directory under `root` read-only (executables keep `+x`, per the Agent Plugins
|
|
505
394
|
* spec's own MCP `command` needing to launch a `./`-relative script — not exercised by anything in
|
|
506
395
|
* this slice, since MCP execution is out of scope for v1, but the bit is preserved now rather than
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentPluginLifecyclePorts } from "./ports.js";
|
|
2
|
+
import type { InstalledAgentPlugin } from "./types.js";
|
|
3
|
+
/** Indexes manifests and files with the same injected effects used by installation and resolution.
|
|
4
|
+
* @complexity O(f) reads/walk work for f package files; no mutation or executable imports. */
|
|
5
|
+
export declare function createInstalledPackageIndex(required: {
|
|
6
|
+
readonly ports: AgentPluginLifecyclePorts;
|
|
7
|
+
}, _optional?: Record<string, never>): {
|
|
8
|
+
indexInstalledRoot: ({ packageRoot, archiveDigest }: {
|
|
9
|
+
readonly packageRoot: string;
|
|
10
|
+
/** SHA-256 of the raw archive bytes — the content-addressing key and the descriptor `revision`
|
|
11
|
+
* a future capability projection pins invocation to (`capability-projection.ts`). */
|
|
12
|
+
readonly archiveDigest: string;
|
|
13
|
+
}, _optional?: Record<string, never>) => Promise<InstalledAgentPlugin>;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=installed-package-index.d.ts.map
|