@jini-ai/agent-plugins 0.4.1 → 0.4.3
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 +8 -0
- package/core/persistent-state.d.ts +25 -0
- package/core/persistent-state.js +260 -0
- package/dist/lifecycle/activation.js +38 -166
- package/dist/lifecycle/bundled-digests.d.ts +6 -0
- package/dist/lifecycle/bundled-digests.js +160 -0
- package/dist/lifecycle/bundled-source-archive.d.ts +3 -0
- package/dist/lifecycle/bundled-source-archive.js +74 -0
- package/dist/lifecycle/contribution-loader.d.ts +102 -0
- package/dist/lifecycle/contribution-loader.js +92 -0
- package/dist/lifecycle/fetch-archive.d.ts +1 -2
- package/dist/lifecycle/index.d.ts +24 -4
- package/dist/lifecycle/index.js +11 -0
- package/dist/lifecycle/install-from-url.d.ts +2 -5
- package/dist/lifecycle/install-from-url.js +2 -5
- package/dist/lifecycle/install.d.ts +58 -3
- package/dist/lifecycle/install.js +301 -23
- package/dist/lifecycle/installed-package-roots.d.ts +16 -0
- package/dist/lifecycle/installed-package-roots.js +55 -0
- package/dist/lifecycle/layout.js +1 -1
- package/dist/lifecycle/manifest.d.ts +5 -60
- package/dist/lifecycle/manifest.js +12 -20
- package/dist/lifecycle/package-paths.d.ts +2 -0
- package/dist/lifecycle/package-paths.js +62 -0
- package/dist/lifecycle/persistent-state.d.ts +5 -1
- package/dist/lifecycle/persistent-state.js +3 -1
- package/dist/lifecycle/ports.d.ts +20 -2
- package/dist/lifecycle/resolve-agent-plugin-refs.d.ts +13 -1
- package/dist/lifecycle/resolve-agent-plugin-refs.js +150 -40
- package/dist/lifecycle/retire-bundled.d.ts +50 -1
- package/dist/lifecycle/retire-bundled.js +9 -2
- package/dist/lifecycle/search.d.ts +65 -0
- package/dist/lifecycle/search.js +31 -2
- package/dist/lifecycle/seed-bundled.d.ts +18 -1
- package/dist/lifecycle/seed-bundled.js +131 -3
- package/dist/lifecycle/set-enabled.d.ts +7 -0
- package/dist/lifecycle/set-enabled.js +5 -0
- package/dist/lifecycle/trusted-plugin-files.d.ts +38 -0
- package/dist/lifecycle/trusted-plugin-files.js +18 -0
- package/dist/lifecycle/types.d.ts +14 -0
- package/dist/lifecycle/uninstall.d.ts +29 -0
- package/dist/lifecycle/uninstall.js +204 -4
- package/dist/lifecycle/yauzl-archive-reader.d.ts +0 -1
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.js +3 -2
- package/package.json +22 -8
- package/persistent-state.js +2 -260
- package/ui-ux-design/plugin.json +15 -1
package/dist/lifecycle/index.js
CHANGED
|
@@ -13,12 +13,14 @@ import { createResolveAgentPluginRefsModule } from './resolve-agent-plugin-refs.
|
|
|
13
13
|
import { createSearchModule } from './search.js';
|
|
14
14
|
import { createSetEnabledModule } from './set-enabled.js';
|
|
15
15
|
import { createTrustedPluginFilesModule } from './trusted-plugin-files.js';
|
|
16
|
+
import { createPluginContributionLoader } from './contribution-loader.js';
|
|
16
17
|
import { createUninstallModule } from './uninstall.js';
|
|
17
18
|
export * from './ports.js';
|
|
18
19
|
export * from './layout.js';
|
|
19
20
|
export * from './manifest.js';
|
|
20
21
|
export * from './fetch-archive.js';
|
|
21
22
|
export * from './mcp-provisioning.js';
|
|
23
|
+
export * from './contribution-loader.js';
|
|
22
24
|
export { createAgentPluginActivations, ACTIVATIONS_FILENAME, ACTIVATIONS_LOCK_FILENAME, AgentPluginActivationsBusyError, AgentPluginActivationsUnreadableError } from './activation.js';
|
|
23
25
|
export { BUNDLED_DIGESTS_FILENAME } from './bundled-digests.js';
|
|
24
26
|
export { AgentPluginInstallError } from './install.js';
|
|
@@ -46,8 +48,14 @@ export function createAgentPluginLifecycle(required, optional = {}) {
|
|
|
46
48
|
const search = createSearchModule(context);
|
|
47
49
|
const enabled = createSetEnabledModule(context);
|
|
48
50
|
const trusted = createTrustedPluginFilesModule(context);
|
|
51
|
+
const contributions = createPluginContributionLoader({
|
|
52
|
+
findPackages: input => trusted.findTrustedPluginPackages(input),
|
|
53
|
+
readFile: ({ plugin, filename }) => trusted.readTrustedPluginFile(plugin, filename),
|
|
54
|
+
importModule: ({ plugin, modulePath }) => trusted.importContainedModule(plugin, modulePath),
|
|
55
|
+
});
|
|
49
56
|
const uninstall = createUninstallModule(context);
|
|
50
57
|
return {
|
|
58
|
+
...contributions,
|
|
51
59
|
fetchAgentPluginArchive: (input, optional = {}) => fetchAgentPluginArchive({ ...input, fetch: context.fetch, outboundGuard: context.outboundGuard }, optional),
|
|
52
60
|
maxAgentPluginArchiveBytes,
|
|
53
61
|
seedBundledAgentPlugins: createSeedBundledModule(context).seedBundledAgentPlugins,
|
|
@@ -64,6 +72,9 @@ export function createAgentPluginLifecycle(required, optional = {}) {
|
|
|
64
72
|
findTrustedPluginPackages: (input, optional = {}) => trusted.findTrustedPluginPackages({ ...input, ...optional }),
|
|
65
73
|
previewAgentPluginUninstall: uninstall.previewAgentPluginUninstall,
|
|
66
74
|
uninstallAgentPlugin: uninstall.uninstallAgentPlugin,
|
|
75
|
+
stageForRemoval: (input, _optional = {}) => uninstall.stageForRemoval(input.packagesDir, input.packageRoot),
|
|
76
|
+
restoreStagedTrees: (input, _optional = {}) => uninstall.restoreStagedTrees(input.staged, input.cause),
|
|
77
|
+
removeFrozenPackageTree: (input, _optional = {}) => uninstall.removeFrozenPackageTree(input.root),
|
|
67
78
|
isAgentPluginActive: (input) => activation.isAgentPluginActive(input.activations, input.pluginId),
|
|
68
79
|
filterActiveAgentPlugins: (input) => activation.filterActiveAgentPlugins(input.activations, input.items, item => input.pluginIdOf({ item })),
|
|
69
80
|
readAgentPluginActivations: (input) => activation.readAgentPluginActivations(input.workspaceRoot),
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Historical lifecycle rationale, carried with the implementation during consumer adoption.
|
|
3
2
|
*
|
|
4
|
-
*
|
|
3
|
+
* Caller integrity constraints:
|
|
5
4
|
* - A digest-only preview downloads and reports SHA-256 without installing. Hashing those bytes
|
|
6
5
|
* does not establish provenance; the host must make that distinction visible to the operator.
|
|
7
6
|
* - A real install requires exactly one explicit integrity decision: an independent pinned digest
|
|
8
7
|
* or trust-on-first-use. Missing or conflicting choices must be refused before execution, with
|
|
9
8
|
* diagnostics naming the choice the operator needs to supply.
|
|
10
|
-
* - Keep pure token classification separate from argument validation and exit/reporting effects,
|
|
11
|
-
* so both parsing and legal combinations are directly testable without a network or process exit.
|
|
12
9
|
* - The CLI composes shared download/extraction operations; digest verification, expansion bounds,
|
|
13
10
|
* path containment and tenant isolation belong in those operations rather than a CLI-only guard.
|
|
14
11
|
* @file The one composition step that turns "here is an address" into "the plugin is on this
|
|
15
12
|
* machine": {@link fetchAgentPluginArchive} -> `installAgentPlugin`.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
14
|
+
* It adds no extraction, path, or
|
|
18
15
|
* manifest logic of its own — every one of those guarantees still comes from `install.ts`, reached
|
|
19
16
|
* with bytes instead of a URL.
|
|
20
17
|
*
|
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Historical lifecycle rationale, carried with the implementation during consumer adoption.
|
|
3
2
|
*
|
|
4
|
-
*
|
|
3
|
+
* Caller integrity constraints:
|
|
5
4
|
* - A digest-only preview downloads and reports SHA-256 without installing. Hashing those bytes
|
|
6
5
|
* does not establish provenance; the host must make that distinction visible to the operator.
|
|
7
6
|
* - A real install requires exactly one explicit integrity decision: an independent pinned digest
|
|
8
7
|
* or trust-on-first-use. Missing or conflicting choices must be refused before execution, with
|
|
9
8
|
* diagnostics naming the choice the operator needs to supply.
|
|
10
|
-
* - Keep pure token classification separate from argument validation and exit/reporting effects,
|
|
11
|
-
* so both parsing and legal combinations are directly testable without a network or process exit.
|
|
12
9
|
* - The CLI composes shared download/extraction operations; digest verification, expansion bounds,
|
|
13
10
|
* path containment and tenant isolation belong in those operations rather than a CLI-only guard.
|
|
14
11
|
* @file The one composition step that turns "here is an address" into "the plugin is on this
|
|
15
12
|
* machine": {@link fetchAgentPluginArchive} -> `installAgentPlugin`.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
14
|
+
* It adds no extraction, path, or
|
|
18
15
|
* manifest logic of its own — every one of those guarantees still comes from `install.ts`, reached
|
|
19
16
|
* with bytes instead of a URL.
|
|
20
17
|
*
|
|
@@ -2,7 +2,10 @@ 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
|
-
|
|
5
|
+
/** One extraction/install failure reason. A caller (an admin route, a future marketplace installer)
|
|
6
|
+
* branches on `code` rather than parsing `message` — this codebase's own convention
|
|
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";
|
|
6
9
|
export declare class AgentPluginInstallError extends Error {
|
|
7
10
|
readonly code: AgentPluginInstallErrorCode;
|
|
8
11
|
constructor({ code, message }: {
|
|
@@ -12,9 +15,14 @@ export declare class AgentPluginInstallError extends Error {
|
|
|
12
15
|
cause?: unknown;
|
|
13
16
|
});
|
|
14
17
|
}
|
|
18
|
+
/** One archive entry. A symlink is modeled as its OWN kind rather than folded into "file" — the
|
|
19
|
+
* zip-slip research this module's header cites is explicit that the symlink-entry vector is
|
|
20
|
+
* distinct from the lexical-traversal one, and the two need independent tests and independent
|
|
21
|
+
* handling (see the "symlink entries are rejected outright" rule below). */
|
|
15
22
|
export type AgentPluginArchiveEntry = {
|
|
16
23
|
readonly kind: "file";
|
|
17
24
|
readonly entryPath: string;
|
|
25
|
+
/** The archive format's own claimed size. Never trusted alone — see `DECOMPRESSION_BOMB`. */
|
|
18
26
|
readonly declaredSize?: (number) | undefined;
|
|
19
27
|
readonly executable?: (boolean) | undefined;
|
|
20
28
|
readonly openReadStream: (required: Record<string, never>) => AsyncIterable<Uint8Array>;
|
|
@@ -30,6 +38,7 @@ export type AgentPluginArchiveEntry = {
|
|
|
30
38
|
* `entries` must be a pure function of the archive bytes: an install iterates it twice, once to
|
|
31
39
|
* check entry-count limits from metadata alone and once to extract.
|
|
32
40
|
*/
|
|
41
|
+
/** `entries` is a pure function of archive bytes: metadata preflight and extraction iterate it twice. */
|
|
33
42
|
export interface AgentPluginArchiveReaderPort {
|
|
34
43
|
entries(required: {
|
|
35
44
|
readonly archive: Uint8Array;
|
|
@@ -37,16 +46,62 @@ export interface AgentPluginArchiveReaderPort {
|
|
|
37
46
|
}
|
|
38
47
|
export interface InstallAgentPluginRequired {
|
|
39
48
|
readonly archive: Uint8Array;
|
|
49
|
+
/** Lowercase hex SHA-256 the caller expects `archive` to hash to — a real marketplace flow would
|
|
50
|
+
* supply this from the server's own metadata; verified here, never trusted from the archive
|
|
51
|
+
* itself. */
|
|
40
52
|
readonly expectedSha256: string;
|
|
41
53
|
readonly archiveReader: AgentPluginArchiveReaderPort;
|
|
54
|
+
/**
|
|
55
|
+
* SECURITY (2026-08-13, security pass Finding 2 — tenant isolation, `ADS-memory/reports/security/
|
|
56
|
+
* 2026-08-13-post-session-security-pass.md`): this field used to be an already-resolved
|
|
57
|
+
* `AgentPluginWorkspaceLayout` (`{ root, packages, staging, pluginDataDir }`) — a plain interface
|
|
58
|
+
* of four independently-typed string/function fields with no tag identifying which workspace it
|
|
59
|
+
* came from. Nothing stopped a caller from hand-assembling one from two DIFFERENT real
|
|
60
|
+
* `forWorkspace()` results (wrong variable capture, a stale cached layout, a future code path that
|
|
61
|
+
* builds one by hand instead of calling `forWorkspace`) — such a value type-checked identically to
|
|
62
|
+
* a correctly-resolved one, and `installAgentPlugin` published into whatever `packages`/`staging`
|
|
63
|
+
* it was handed with no consistency check. Proven at the unit level in `install.unit.test.ts`
|
|
64
|
+
* (a layout stitching workspace A's `root`/`pluginDataDir` onto workspace B's real `packages`/
|
|
65
|
+
* `staging` published into workspace B's real, on-disk store with no error).
|
|
66
|
+
*
|
|
67
|
+
* The fix: `installAgentPlugin` no longer accepts a pre-resolved workspace layout as input AT ALL.
|
|
68
|
+
* It takes the RAW MATERIALS instead — the INSTANCE-level layout (`resolveAgentPluginLayout()`,
|
|
69
|
+
* carries no workspace-scoped path of its own) plus one `workspaceId` — and calls
|
|
70
|
+
* `layout.forWorkspace(workspaceId)` itself, exactly once, internally. There is no longer any
|
|
71
|
+
* `AgentPluginWorkspaceLayout`-shaped parameter here for a caller to stitch fields into: the one
|
|
72
|
+
* and only path from these two inputs to `packages`/`staging`/`pluginDataDir` is the real
|
|
73
|
+
* `forWorkspace()` closure, called atomically, so the four resulting paths can never disagree with
|
|
74
|
+
* each other about which workspace they belong to. Passing a hand-built `{ packages, staging, ... }`
|
|
75
|
+
* literal where this field is expected is now a compile-time type error, not a runtime hazard.
|
|
76
|
+
*
|
|
77
|
+
* `workspaceId` must still come from the authenticated principal's own request context, never
|
|
78
|
+
* derived from `layout` itself (a hand-built or malicious `AgentPluginLayout` could fake a
|
|
79
|
+
* `forWorkspace` implementation identically) — that half of the guarantee is a caller obligation
|
|
80
|
+
* this type cannot enforce, the same way `archiveReader` above is a fully caller-trusted port. What
|
|
81
|
+
* IS closed is the specific, demonstrated bug class: stitching or hand-assembling an
|
|
82
|
+
* already-resolved workspace layout from parts.
|
|
83
|
+
*/
|
|
42
84
|
readonly layout: AgentPluginLayout;
|
|
85
|
+
/** The workspace this install is for. Combined with {@link layout} via `layout.forWorkspace(workspaceId)`
|
|
86
|
+
* — see the SECURITY note on {@link layout} above. */
|
|
43
87
|
readonly workspaceId: string;
|
|
44
88
|
}
|
|
45
|
-
export
|
|
89
|
+
export interface InstallAgentPluginOptional {
|
|
90
|
+
/** Full bounded extraction/validation in staging, with no package or memory publication. */
|
|
91
|
+
readonly previewOnly?: boolean;
|
|
92
|
+
/** Host policy transaction inside the per-plugin lock after validation. Call publish exactly
|
|
93
|
+
* once to install; returning an existing package without calling it is a deduplicated no-op. */
|
|
94
|
+
/** Host policy transaction, called inside the existing per-plugin lock after validation.
|
|
95
|
+
* It must invoke publish exactly once to install. Shared lifecycle counterpart is in chat-tools.jini.patch. */
|
|
96
|
+
readonly publishGuard?: (required: {
|
|
97
|
+
readonly plugin: InstalledAgentPlugin;
|
|
98
|
+
readonly publish: () => Promise<InstalledAgentPlugin>;
|
|
99
|
+
}, optional: Record<string, never>) => Promise<InstalledAgentPlugin>;
|
|
100
|
+
}
|
|
46
101
|
/** Internal composition; one state/queue instance per injected host context. */
|
|
47
102
|
export declare function createInstallModule(ports: AgentPluginLifecyclePorts): {
|
|
48
103
|
maxAgentPluginInstallArchiveBytes: () => number;
|
|
49
|
-
installAgentPlugin: (required: InstallAgentPluginRequired,
|
|
104
|
+
installAgentPlugin: (required: InstallAgentPluginRequired, optional?: InstallAgentPluginOptional) => Promise<InstalledAgentPlugin>;
|
|
50
105
|
indexInstalledRoot: (packageRoot: string, archiveDigest: string) => Promise<InstalledAgentPlugin>;
|
|
51
106
|
};
|
|
52
107
|
//# sourceMappingURL=install.d.ts.map
|