@jini-ai/agent-plugins 0.4.0 → 0.4.2
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 +11 -0
- package/README.md +19 -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 +156 -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 +34 -4
- package/dist/lifecycle/index.js +19 -1
- 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 +57 -2
- package/dist/lifecycle/install.js +228 -13
- package/dist/lifecycle/layout.d.ts +10 -0
- package/dist/lifecycle/layout.js +9 -1
- package/dist/lifecycle/manifest.d.ts +3 -9
- package/dist/lifecycle/manifest.js +4 -11
- package/dist/lifecycle/package-paths.d.ts +2 -0
- package/dist/lifecycle/package-paths.js +62 -0
- package/dist/lifecycle/persistent-state.d.ts +24 -0
- package/dist/lifecycle/persistent-state.js +32 -0
- package/dist/lifecycle/ports.d.ts +15 -3
- package/dist/lifecycle/resolve-agent-plugin-refs.d.ts +14 -2
- package/dist/lifecycle/resolve-agent-plugin-refs.js +173 -10
- package/dist/lifecycle/retire-bundled.d.ts +50 -1
- package/dist/lifecycle/retire-bundled.js +10 -3
- package/dist/lifecycle/search.d.ts +63 -0
- package/dist/lifecycle/search.js +29 -0
- package/dist/lifecycle/seed-bundled.d.ts +18 -1
- package/dist/lifecycle/seed-bundled.js +139 -1
- package/dist/lifecycle/set-enabled.d.ts +7 -0
- package/dist/lifecycle/set-enabled.js +6 -1
- package/dist/lifecycle/trusted-plugin-files.d.ts +38 -0
- package/dist/lifecycle/trusted-plugin-files.js +19 -1
- package/dist/lifecycle/types.d.ts +12 -0
- package/dist/lifecycle/uninstall.d.ts +30 -0
- package/dist/lifecycle/uninstall.js +232 -4
- package/dist/lifecycle/yauzl-archive-reader.d.ts +0 -1
- package/dist/lifecycle/yauzl-archive-reader.js +5 -4
- package/package.json +34 -9
- package/persistent-state.d.ts +25 -0
- package/persistent-state.js +2 -0
|
@@ -1,3 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The per-workspace ledger of WHICH installed digest each bundled Agent Plugin currently
|
|
3
|
+
* ships as — written by `seed-bundled.ts` on every boot, read by the surfaces that have to pick one
|
|
4
|
+
* installed package for a plugin id.
|
|
5
|
+
*
|
|
6
|
+
* ---------------------------------------------------------------------------
|
|
7
|
+
* The gap this closes
|
|
8
|
+
* ---------------------------------------------------------------------------
|
|
9
|
+
* `packages/sha256/<digest>/` is content-addressed and nothing retires an entry, so shipping a NEW
|
|
10
|
+
* VERSION of an already-installed bundled plugin lands it ALONGSIDE the old one. Both consumption
|
|
11
|
+
* surfaces then refuse that plugin outright as ambiguous — `resolve-agent-plugin-refs.ts`'s
|
|
12
|
+
* "refusing to guess which one to use" and `tool-registrations.ts`'s `poisonedPluginIds`. A product
|
|
13
|
+
* upgrade therefore broke every enabled bundled plugin whose bytes it touched, silently, on the next
|
|
14
|
+
* boot. Observed live: `higgsfield-media` (2 digests, enabled, tools gone), `supabase` (3 digests,
|
|
15
|
+
* dormant), and `github` one boot away from the same fate.
|
|
16
|
+
*
|
|
17
|
+
* ---------------------------------------------------------------------------
|
|
18
|
+
* Why a ledger rather than deleting, or than "prefer the newest"
|
|
19
|
+
* ---------------------------------------------------------------------------
|
|
20
|
+
* The ambiguity refusal itself is deliberate and stays exactly as it was — `resolve-agent-plugin-
|
|
21
|
+
* refs.ts`'s header argues at length that silently picking one (the lexicographically-last, the
|
|
22
|
+
* newest mtime, whatever hashed later) would be a WORSE failure mode than an explicit error, because
|
|
23
|
+
* a wrong plugin's content would reach the agent and nothing about the run would look wrong. That
|
|
24
|
+
* reasoning is untouched here. What changes is that for a BUNDLED plugin there is now something
|
|
25
|
+
* authoritative to consult instead of guessing: the build's own seed recorded which digest it
|
|
26
|
+
* published. A digest wins because the host shipped it, never because it sorted first.
|
|
27
|
+
*
|
|
28
|
+
* Three consequences worth stating plainly, because each one is a refusal this file deliberately
|
|
29
|
+
* keeps:
|
|
30
|
+
*
|
|
31
|
+
* 1. **An id with no ledger entry is still ambiguous.** Two digests of a plugin the seeder never
|
|
32
|
+
* seeded — an operator's own installs — remain an explicit error. Nothing here has any authority
|
|
33
|
+
* over a package the host did not put on disk.
|
|
34
|
+
* 2. **A ledger entry that names a digest which is not installed decides nothing.** The named digest
|
|
35
|
+
* has to be present, and be exactly one of the candidates, or the ambiguity stands.
|
|
36
|
+
* 3. **An unreadable ledger reads as an empty one.** That is fail-CLOSED here, unlike
|
|
37
|
+
* `activation.ts`'s own lenient reader: an empty ledger names no winner, so every ambiguity stays
|
|
38
|
+
* an error. This file can only ever resolve an ambiguity, never create one.
|
|
39
|
+
*
|
|
40
|
+
* ---------------------------------------------------------------------------
|
|
41
|
+
* Nothing is deleted, moved, or renamed
|
|
42
|
+
* ---------------------------------------------------------------------------
|
|
43
|
+
* A superseded digest's bytes stay exactly where they are. Retiring by moving directories out of a
|
|
44
|
+
* live `packages/sha256/` at boot was the other candidate design and was rejected: it mutates the
|
|
45
|
+
* operator's package store on a code path whose whole job is supposed to be idempotent, it races
|
|
46
|
+
* anything mid-read, and — since a digest's provenance is NOT recorded per-digest anywhere — a
|
|
47
|
+
* workspace that already held an operator's own install under a bundled plugin's id could have had
|
|
48
|
+
* that operator's bytes moved aside by an inference. Preferring a digest at READ time disturbs
|
|
49
|
+
* nothing and is undone by deleting one small JSON file. The cost is disk: superseded packages
|
|
50
|
+
* accumulate until an operator prunes them, which is their call to make and reversible either way.
|
|
51
|
+
*
|
|
52
|
+
* Architectural role:
|
|
53
|
+
* Owns one small JSON file and one pure selection rule. No knowledge of activation, installation, or
|
|
54
|
+
* tools — `seed-bundled.ts` is the only writer (plus `retire-bundled.ts`, which it calls in the same
|
|
55
|
+
* boot step), and the read side is two call sites that already
|
|
56
|
+
* hold both a workspace root and a list of installed packages.
|
|
57
|
+
*/
|
|
58
|
+
/** Where a workspace's bundled-digest ledger lives, beside `activation.ts`'s `activations.json` and
|
|
59
|
+
* for the same reason: it is a fact about this workspace, not about a package. */
|
|
60
|
+
/** The same 64-lowercase-hex shape `resolve-agent-plugin-refs.ts` requires of an installed package
|
|
61
|
+
* directory name. A ledger entry that does not match it could never name a real install. */
|
|
62
|
+
/** The Agent Plugins name grammar, as `activation.ts` and `layout.ts` both already enforce it. */
|
|
1
63
|
/** Extracted from the host plugin lifecycle. Effects are supplied per host context. */
|
|
2
64
|
import path from "node:path";
|
|
3
65
|
export const BUNDLED_DIGESTS_FILENAME = "bundled-digests.json";
|
|
@@ -10,6 +72,17 @@ function buildModule(ports) {
|
|
|
10
72
|
const SHA256_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
|
|
11
73
|
const SAFE_PLUGIN_ID_PATTERN = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/;
|
|
12
74
|
const EMPTY_LEDGER = new Map();
|
|
75
|
+
/**
|
|
76
|
+
* Reads one workspace's bundled-digest ledger.
|
|
77
|
+
*
|
|
78
|
+
* @param workspaceRoot - `AgentPluginWorkspaceLayout.root`, never an instance-level path.
|
|
79
|
+
* @returns The recorded plugin-id -> digest map, or an EMPTY map for a missing, unreadable,
|
|
80
|
+
* malformed, or wrong-version file. Malformed INDIVIDUAL entries are skipped rather than discarding
|
|
81
|
+
* the whole ledger, matching `activation.ts`'s own per-entry discipline.
|
|
82
|
+
* @throws Nothing. An empty answer is always safe: it names no winner, so every ambiguity this
|
|
83
|
+
* module could have resolved simply stays the explicit error it already was.
|
|
84
|
+
* @complexity One file read plus O(p) in the recorded plugin count.
|
|
85
|
+
*/
|
|
13
86
|
async function readBundledAgentPluginDigests(workspaceRoot) {
|
|
14
87
|
let raw;
|
|
15
88
|
try {
|
|
@@ -27,6 +100,12 @@ function buildModule(ports) {
|
|
|
27
100
|
}
|
|
28
101
|
return normalizeBundledDigests(parsed);
|
|
29
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Validates an already-`JSON.parse`d ledger value, dropping anything that does not match the shape.
|
|
105
|
+
* Pure, so the parsing rules are assertable without touching a filesystem.
|
|
106
|
+
*
|
|
107
|
+
* @complexity O(p) in the entry count.
|
|
108
|
+
*/
|
|
30
109
|
function normalizeBundledDigests(value) {
|
|
31
110
|
if (!isPlainObject(value) || value.schemaVersion !== 1 || !isPlainObject(value.plugins))
|
|
32
111
|
return EMPTY_LEDGER;
|
|
@@ -43,6 +122,27 @@ function buildModule(ports) {
|
|
|
43
122
|
}
|
|
44
123
|
return digests;
|
|
45
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Records the digest this boot seeded for each bundled plugin, merged over whatever was already
|
|
127
|
+
* recorded.
|
|
128
|
+
*
|
|
129
|
+
* MERGED, not replaced, for failure isolation (the property `seed-bundled.ts`'s header names): a
|
|
130
|
+
* plugin that failed to seed this boot — an unreadable source directory, a transient I/O fault —
|
|
131
|
+
* must not lose the perfectly good entry an earlier boot wrote for it, which is what a wholesale
|
|
132
|
+
* rewrite of only this boot's successes would do.
|
|
133
|
+
*
|
|
134
|
+
* A plugin id seeded TWICE in one run under two different digests (two bundled source directories
|
|
135
|
+
* declaring the same manifest `name`) is dropped from the ledger entirely, prior entry included:
|
|
136
|
+
* the build itself is ambiguous about what it ships, so there is nothing authoritative to record and
|
|
137
|
+
* the explicit ambiguity refusal downstream is the correct outcome.
|
|
138
|
+
*
|
|
139
|
+
* @param seeded - One entry per successfully seeded plugin. An empty list writes nothing at all —
|
|
140
|
+
* not even an empty file — so a boot that seeded nothing cannot create a workspace directory or
|
|
141
|
+
* truncate an existing ledger.
|
|
142
|
+
* @throws {Error} Any filesystem fault from the write. The caller (`seed-bundled.ts`) captures it
|
|
143
|
+
* into its report rather than failing the boot.
|
|
144
|
+
* @complexity One file read and one atomic write, O(p) in the recorded plugin count.
|
|
145
|
+
*/
|
|
46
146
|
async function recordBundledAgentPluginDigests(required) {
|
|
47
147
|
if (required.seeded.length === 0)
|
|
48
148
|
return;
|
|
@@ -58,6 +158,15 @@ function buildModule(ports) {
|
|
|
58
158
|
const plugins = Object.fromEntries([...merged].map(([pluginId, archiveDigest]) => [pluginId, { archiveDigest, seededAt }]));
|
|
59
159
|
await writeLedgerAtomically(required.workspaceRoot, plugins);
|
|
60
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* This boot's verdict per plugin id: the digest it seeded, or `undefined` when the run seeded the
|
|
163
|
+
* same id under two different digests and therefore has no authoritative answer for it.
|
|
164
|
+
*
|
|
165
|
+
* Split out as a pure step so the "a self-contradictory build records NOTHING" rule is assertable
|
|
166
|
+
* without a filesystem, and so {@link recordBundledAgentPluginDigests} reads as merge-then-write.
|
|
167
|
+
*
|
|
168
|
+
* @complexity O(n) in the seeded-entry count.
|
|
169
|
+
*/
|
|
61
170
|
function resolveThisBootsDigests(seeded) {
|
|
62
171
|
const thisBoot = new Map();
|
|
63
172
|
for (const { pluginId, archiveDigest } of seeded) {
|
|
@@ -70,6 +179,19 @@ function buildModule(ports) {
|
|
|
70
179
|
}
|
|
71
180
|
return thisBoot;
|
|
72
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Drops one plugin id from the ledger — for `retire-bundled.ts`, once a retired bundled plugin's
|
|
184
|
+
* packages are gone and no build will ever seed it again.
|
|
185
|
+
*
|
|
186
|
+
* Every other entry is kept byte-for-byte, `seededAt` included: this rewrites the raw `plugins` bag
|
|
187
|
+
* rather than the normalized map, so it cannot lose a field a later schema adds. A missing,
|
|
188
|
+
* unreadable, malformed, or wrong-version ledger, or one with no entry for `pluginId`, writes
|
|
189
|
+
* nothing — there is nothing to drop, and a fail-closed reader already treats such a file as empty.
|
|
190
|
+
*
|
|
191
|
+
* @returns Whether an entry was removed.
|
|
192
|
+
* @throws {Error} Any filesystem fault from the write. `retire-bundled.ts` captures it into its report.
|
|
193
|
+
* @complexity One file read and at most one atomic write, O(p) in the recorded plugin count.
|
|
194
|
+
*/
|
|
73
195
|
async function removeBundledAgentPluginDigest(required) {
|
|
74
196
|
let parsed;
|
|
75
197
|
try {
|
|
@@ -87,6 +209,11 @@ function buildModule(ports) {
|
|
|
87
209
|
await writeLedgerAtomically(required.workspaceRoot, remaining);
|
|
88
210
|
return true;
|
|
89
211
|
}
|
|
212
|
+
/** Temp-file-then-`rename`, so a reader never observes a half-written ledger and a crash mid-write
|
|
213
|
+
* leaves the previous one intact — the same durability shape `activation.ts`'s
|
|
214
|
+
* `writeActivationsAtomically` uses, minus its cross-process lock: this file has exactly one writer
|
|
215
|
+
* (the boot seeder, retirement included), and two concurrent boots of the SAME build write byte-identical content.
|
|
216
|
+
* @complexity One write and one rename. */
|
|
90
217
|
async function writeLedgerAtomically(workspaceRoot, plugins) {
|
|
91
218
|
const finalPath = path.join(workspaceRoot, BUNDLED_DIGESTS_FILENAME);
|
|
92
219
|
const tempPath = `${finalPath}.tmp-${ports.process.pid}-${randomUUID()}`;
|
|
@@ -106,6 +233,24 @@ function buildModule(ports) {
|
|
|
106
233
|
throw error;
|
|
107
234
|
}
|
|
108
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Drops the SUPERSEDED installs of any bundled plugin id from a list of installed packages, leaving
|
|
238
|
+
* every other entry — and every other plugin id — exactly as it was.
|
|
239
|
+
*
|
|
240
|
+
* An id is narrowed only when all three hold: it has more than one installed digest, the ledger
|
|
241
|
+
* records a digest for it, and exactly one of its installed digests is that recorded one. Anything
|
|
242
|
+
* else passes through untouched, so the caller's own ambiguity refusal still fires for every case
|
|
243
|
+
* this module has no authority over (this file's header, consequences 1 and 2).
|
|
244
|
+
*
|
|
245
|
+
* Applied at READ time by the surfaces that must pick one package per plugin id. Deliberately NOT
|
|
246
|
+
* folded into `listInstalledPlugins` itself: `uninstall.ts` needs to see EVERY digest of a plugin to
|
|
247
|
+
* remove them, and a filter hidden inside the shared walk would make superseded bytes unremovable.
|
|
248
|
+
*
|
|
249
|
+
* @returns A list in the input's own order, with superseded entries removed. The input is never
|
|
250
|
+
* mutated.
|
|
251
|
+
* @complexity O(n) in the installed-package count, over one O(n) pre-pass that counts digests per
|
|
252
|
+
* plugin id.
|
|
253
|
+
*/
|
|
109
254
|
function preferBundledAgentPluginDigests(installed, bundled) {
|
|
110
255
|
if (bundled.size === 0)
|
|
111
256
|
return installed;
|
|
@@ -114,6 +259,12 @@ function buildModule(ports) {
|
|
|
114
259
|
return installed;
|
|
115
260
|
return installed.filter((plugin) => !supersededIds.has(plugin.pluginId) || plugin.archiveDigest === bundled.get(plugin.pluginId));
|
|
116
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* The plugin ids whose installed digests the ledger can authoritatively narrow to one — every id
|
|
264
|
+
* that is installed under several digests, exactly one of which is the digest this build seeded.
|
|
265
|
+
*
|
|
266
|
+
* @complexity O(n) in the installed-package count.
|
|
267
|
+
*/
|
|
117
268
|
function idsNarrowableToTheirBundledDigest(installed, bundled) {
|
|
118
269
|
const digestsByPluginId = new Map();
|
|
119
270
|
for (const plugin of installed) {
|
|
@@ -131,6 +282,11 @@ function buildModule(ports) {
|
|
|
131
282
|
}
|
|
132
283
|
return narrowable;
|
|
133
284
|
}
|
|
285
|
+
/** Whether `value` is a plain (non-null, non-array) object — the shape both this file's JSON-adjacent
|
|
286
|
+
* values must have before their own fields are worth looking at. Duplicated from `activation.ts`'s
|
|
287
|
+
* private helper of the same name rather than widening that module's public surface, matching this
|
|
288
|
+
* codebase's own precedent for small private formatting/shape helpers (see `resolve-agent-plugin-
|
|
289
|
+
* refs.ts`'s note on `humanize()`). */
|
|
134
290
|
function isPlainObject(value) {
|
|
135
291
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
136
292
|
}
|
|
@@ -2,7 +2,10 @@ import type { AgentPluginArchiveReaderPort } from "./install.js";
|
|
|
2
2
|
import type { AgentPluginLifecyclePorts } from "./ports.js";
|
|
3
3
|
export interface PackedAgentPluginArchive {
|
|
4
4
|
readonly bytes: Uint8Array;
|
|
5
|
+
/** Lowercase hex SHA-256 of {@link bytes} — passed straight to `installAgentPlugin`'s
|
|
6
|
+
* `expectedSha256`, which recomputes and compares it rather than trusting it. */
|
|
5
7
|
readonly sha256: string;
|
|
8
|
+
/** Package-relative POSIX paths included, sorted — the same order they appear in `bytes`. */
|
|
6
9
|
readonly files: readonly string[];
|
|
7
10
|
}
|
|
8
11
|
/** Internal composition; one state/queue instance per injected host context. */
|
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Turns a bundled Agent Plugin's SOURCE DIRECTORY (tracked in this repo, shipped in the
|
|
3
|
+
* product) into the `{ archive: Uint8Array, expectedSha256, archiveReader }` triple
|
|
4
|
+
* `installAgentPlugin` requires — so a pre-placed package goes through the exact same install path
|
|
5
|
+
* as one downloaded from a URL, rather than around it.
|
|
6
|
+
*
|
|
7
|
+
* ---------------------------------------------------------------------------
|
|
8
|
+
* Why not just copy the directory
|
|
9
|
+
* ---------------------------------------------------------------------------
|
|
10
|
+
* Because `installAgentPlugin` is where every guarantee lives: lexical and symlink zip-slip
|
|
11
|
+
* refusal, entry/file/total-byte caps, atomic staging-then-publish, `chmod 0o555` freezing,
|
|
12
|
+
* content-addressed dedup, and the tenant-scoped `forWorkspace()` resolution that makes it
|
|
13
|
+
* impossible to publish into another workspace's tree. A `cp -R` in a seeder would reimplement
|
|
14
|
+
* none of them, and every one it skipped would be a guarantee that silently applies to
|
|
15
|
+
* operator-installed plugins and not to the one the host ships itself.
|
|
16
|
+
*
|
|
17
|
+
* The obstacle is only that `installAgentPlugin` speaks archives, and a directory is not one.
|
|
18
|
+
* {@link packAgentPluginDirectory} therefore serialises the directory into a tiny, deterministic
|
|
19
|
+
* archive format, and {@link createBundledSourceArchiveReader} reads it back — a round trip, not a
|
|
20
|
+
* bypass. `yauzl`/`yazl` were the obvious alternative and are wrong here: `yazl` is a
|
|
21
|
+
* devDependency used only by dev tooling and test fixtures (`development/scripts/package-agent-plugin.ts`
|
|
22
|
+
* says so in its own header, "must never be imported by production code"), and DEFLATE would make
|
|
23
|
+
* the digest depend on a compressor's version rather than on the bytes.
|
|
24
|
+
*
|
|
25
|
+
* ---------------------------------------------------------------------------
|
|
26
|
+
* The digest is a real content digest
|
|
27
|
+
* ---------------------------------------------------------------------------
|
|
28
|
+
* `installAgentPlugin` verifies `sha256(archive) === expectedSha256` before extracting anything, and
|
|
29
|
+
* uses that digest as the content address. Because this format is byte-deterministic — sorted
|
|
30
|
+
* paths, explicit lengths, no timestamps, no permissions, no compression — the digest is a genuine
|
|
31
|
+
* function of the package's own file contents and names. Two builds of the same source tree produce
|
|
32
|
+
* the same digest, which is what makes seeding idempotent (`install.ts`'s `alreadyPublished` fast
|
|
33
|
+
* path) instead of re-extracting on every boot.
|
|
34
|
+
*
|
|
35
|
+
* Architectural role:
|
|
36
|
+
* One directory walk (the only I/O) plus a pure encoder/decoder pair. `seed-bundled.ts` composes it
|
|
37
|
+
* with `installAgentPlugin`.
|
|
38
|
+
*/
|
|
39
|
+
/** Magic + version line. A reader that does not see this refuses rather than guessing. */
|
|
40
|
+
/** Guards a caller that points this at the wrong directory (a `node_modules`, a whole repo). Well
|
|
41
|
+
* above any real plugin; below `install.ts`'s own `maxEntries` of 4096 so the friendlier error
|
|
42
|
+
* comes from here. */
|
|
1
43
|
/** Extracted from the host plugin lifecycle. Effects are supplied per host context. */
|
|
2
44
|
import { createHash } from "node:crypto";
|
|
3
45
|
import path from "node:path";
|
|
@@ -6,6 +48,20 @@ function buildModule(ports) {
|
|
|
6
48
|
const readFile = ports.filesystem.readFile.bind(ports.filesystem);
|
|
7
49
|
const MAGIC = ports.bundledArchiveMagic;
|
|
8
50
|
const MAX_SOURCE_FILES = 2048;
|
|
51
|
+
/**
|
|
52
|
+
* Serialises every regular file under `sourceDir` into one deterministic archive.
|
|
53
|
+
*
|
|
54
|
+
* Symlinks and any other non-regular entry are skipped rather than followed: a bundled package is
|
|
55
|
+
* this repository's own tracked content, so a symlink in it would be a mistake, and following one
|
|
56
|
+
* is the exact vector `install.ts` refuses on the extraction side. Skipping keeps the two sides
|
|
57
|
+
* agreeing instead of producing an archive the installer would then reject.
|
|
58
|
+
*
|
|
59
|
+
* @throws {Error} If the tree holds more than {@link MAX_SOURCE_FILES} files.
|
|
60
|
+
* @throws Propagates `readdir`/`readFile` errors — a bundled package that cannot be read is a build
|
|
61
|
+
* defect, not a runtime condition to degrade around.
|
|
62
|
+
* @complexity O(f) files, O(b) total bytes; both bounded by the cap above and by `install.ts`'s own
|
|
63
|
+
* `LIMITS` on the extraction side.
|
|
64
|
+
*/
|
|
9
65
|
async function packAgentPluginDirectory(sourceDir) {
|
|
10
66
|
const relativePaths = (await listRegularFiles(sourceDir, "")).sort();
|
|
11
67
|
if (relativePaths.length > MAX_SOURCE_FILES) {
|
|
@@ -24,6 +80,16 @@ function buildModule(ports) {
|
|
|
24
80
|
files: relativePaths,
|
|
25
81
|
};
|
|
26
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Reads the format {@link packAgentPluginDirectory} writes, as an `AgentPluginArchiveReaderPort`.
|
|
85
|
+
*
|
|
86
|
+
* Yields only `kind: "file"` entries — the format has no directory, symlink, or device entries to
|
|
87
|
+
* represent, which is itself a small safety property: the whole class of entry `install.ts` rejects
|
|
88
|
+
* cannot be expressed in an archive this reader produces.
|
|
89
|
+
*
|
|
90
|
+
* @complexity O(n) in the archive's byte length; each file's bytes are yielded once, not copied
|
|
91
|
+
* into an intermediate list.
|
|
92
|
+
*/
|
|
27
93
|
function createBundledSourceArchiveReader() {
|
|
28
94
|
return {
|
|
29
95
|
entries({ archive }) {
|
|
@@ -64,9 +130,16 @@ function buildModule(ports) {
|
|
|
64
130
|
offset = contentEnd;
|
|
65
131
|
}
|
|
66
132
|
}
|
|
133
|
+
/** One-shot async iterable over a single chunk — the shape `AgentPluginArchiveEntry.openReadStream`
|
|
134
|
+
* wants, without pulling in a stream implementation for an already-in-memory buffer. */
|
|
67
135
|
async function* onceAsyncIterable(chunk) {
|
|
68
136
|
yield chunk;
|
|
69
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Lists every regular file under `dir`, as POSIX-style package-relative paths.
|
|
140
|
+
*
|
|
141
|
+
* @complexity O(f) in filesystem entries under `dir`.
|
|
142
|
+
*/
|
|
70
143
|
async function listRegularFiles(dir, prefix) {
|
|
71
144
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
72
145
|
const files = [];
|
|
@@ -78,6 +151,7 @@ function buildModule(ports) {
|
|
|
78
151
|
else if (entry.isFile()) {
|
|
79
152
|
files.push(relative);
|
|
80
153
|
}
|
|
154
|
+
// Symlinks and everything else: skipped. See this function's own doc.
|
|
81
155
|
}
|
|
82
156
|
return files;
|
|
83
157
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { TrustedPluginPackage, TrustedPluginPackagesQuery, TrustedPluginVerdict } from './trusted-plugin-files.js';
|
|
2
|
+
export type PluginContributionParseResult<T> = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly descriptors: readonly T[];
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
readonly reason: string;
|
|
8
|
+
};
|
|
9
|
+
export interface PluginContributionResult<T> {
|
|
10
|
+
readonly items: readonly T[];
|
|
11
|
+
readonly refusals: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export type PluginModuleImportPort = (input: {
|
|
14
|
+
readonly plugin: TrustedPluginPackage;
|
|
15
|
+
readonly modulePath: string;
|
|
16
|
+
}) => Promise<{
|
|
17
|
+
readonly exported: unknown;
|
|
18
|
+
} | string>;
|
|
19
|
+
export interface PluginContributionDefinition<D, T> {
|
|
20
|
+
readonly filename: string;
|
|
21
|
+
readonly contribution: string;
|
|
22
|
+
readonly kind: 'data' | 'executable';
|
|
23
|
+
readonly parse: (input: {
|
|
24
|
+
readonly raw: string;
|
|
25
|
+
}) => PluginContributionParseResult<D>;
|
|
26
|
+
/** A string is the complete refusal line; successful values retain declaration order. */
|
|
27
|
+
readonly load: (input: {
|
|
28
|
+
readonly plugin: TrustedPluginPackage;
|
|
29
|
+
readonly descriptor: D;
|
|
30
|
+
readonly importModule: PluginModuleImportPort;
|
|
31
|
+
}) => T | string | Promise<T | string>;
|
|
32
|
+
/** Undefined propagates the fault; [] deliberately tolerates a missing source manifest. */
|
|
33
|
+
readonly onReadError?: (input: {
|
|
34
|
+
readonly plugin: TrustedPluginPackage;
|
|
35
|
+
readonly error: unknown;
|
|
36
|
+
}) => readonly string[] | undefined;
|
|
37
|
+
}
|
|
38
|
+
export interface LoadedPluginContribution<D, M> {
|
|
39
|
+
readonly descriptor: D;
|
|
40
|
+
readonly pluginId: string;
|
|
41
|
+
readonly module: M;
|
|
42
|
+
}
|
|
43
|
+
export interface PluginContributionPorts {
|
|
44
|
+
readonly findPackages: (input: TrustedPluginPackagesQuery) => Promise<readonly TrustedPluginVerdict[]>;
|
|
45
|
+
readonly readFile: (input: {
|
|
46
|
+
readonly plugin: TrustedPluginPackage;
|
|
47
|
+
readonly filename: string;
|
|
48
|
+
}) => Promise<string>;
|
|
49
|
+
readonly importModule: PluginModuleImportPort;
|
|
50
|
+
}
|
|
51
|
+
export interface LoadPluginContributionsOptional {
|
|
52
|
+
/** Executable definitions always require activation, even if this option is false. */
|
|
53
|
+
readonly requireActive?: boolean;
|
|
54
|
+
readonly orderByPluginId?: boolean;
|
|
55
|
+
readonly onInactive?: TrustedPluginPackagesQuery['onInactive'];
|
|
56
|
+
readonly packageRefusal?: (input: {
|
|
57
|
+
readonly plugin: TrustedPluginPackage;
|
|
58
|
+
}) => string | undefined;
|
|
59
|
+
}
|
|
60
|
+
/** Bind traversal to the lifecycle's existing trust/read/import owner. Parser and error policies
|
|
61
|
+
* remain contribution ports: manifests and refusal contracts do not change.
|
|
62
|
+
* @param required - Trusted-file effects, injectable for direct failure and ordering tests.
|
|
63
|
+
* @returns Installed and explicitly trusted source loaders; reads are fresh on each call.
|
|
64
|
+
* @complexity O(p + d) packages and declarations, plus their file/import I/O; O(d + r) output space.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createPluginContributionLoader(required: PluginContributionPorts, _optional?: Record<string, never>): {
|
|
67
|
+
loadPluginContributions: <D, T>(input: {
|
|
68
|
+
readonly workspaceId: string;
|
|
69
|
+
readonly definition: PluginContributionDefinition<D, T>;
|
|
70
|
+
}, options?: LoadPluginContributionsOptional) => Promise<PluginContributionResult<T>>;
|
|
71
|
+
loadPluginContributionsFromSource: <D, T>(input: {
|
|
72
|
+
readonly plugin: TrustedPluginPackage;
|
|
73
|
+
readonly definition: PluginContributionDefinition<D, T>;
|
|
74
|
+
}, _options?: Record<string, never>) => Promise<PluginContributionResult<T>>;
|
|
75
|
+
};
|
|
76
|
+
/** Define executable materialization once; each domain owns export validation and refusal wording.
|
|
77
|
+
* @param required - Existing manifest parser, module path, validator and complete refusal formatter.
|
|
78
|
+
* @param optional - Established read/import recovery policy; absent policies propagate I/O faults.
|
|
79
|
+
* @returns A definition whose installed loader always applies activation before importing code.
|
|
80
|
+
* @complexity O(1) composition; each load performs one contained import and one validation.
|
|
81
|
+
*/
|
|
82
|
+
export declare function defineExecutablePluginContribution<D, M>(required: {
|
|
83
|
+
readonly filename: string;
|
|
84
|
+
readonly contribution: string;
|
|
85
|
+
readonly parse: PluginContributionDefinition<D, LoadedPluginContribution<D, M>>['parse'];
|
|
86
|
+
readonly modulePath: (input: {
|
|
87
|
+
readonly descriptor: D;
|
|
88
|
+
}) => string;
|
|
89
|
+
readonly validate: (input: {
|
|
90
|
+
readonly exported: unknown;
|
|
91
|
+
}) => M | string;
|
|
92
|
+
readonly refusal: (input: {
|
|
93
|
+
readonly plugin: TrustedPluginPackage;
|
|
94
|
+
readonly descriptor: D;
|
|
95
|
+
readonly reason: string;
|
|
96
|
+
}) => string;
|
|
97
|
+
}, optional?: {
|
|
98
|
+
readonly onReadError?: PluginContributionDefinition<D, LoadedPluginContribution<D, M>>['onReadError'];
|
|
99
|
+
readonly importRefusalReason?: string;
|
|
100
|
+
readonly importErrorReason?: string;
|
|
101
|
+
}): PluginContributionDefinition<D, LoadedPluginContribution<D, M>>;
|
|
102
|
+
//# sourceMappingURL=contribution-loader.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Bind traversal to the lifecycle's existing trust/read/import owner. Parser and error policies
|
|
2
|
+
* remain contribution ports: manifests and refusal contracts do not change.
|
|
3
|
+
* @param required - Trusted-file effects, injectable for direct failure and ordering tests.
|
|
4
|
+
* @returns Installed and explicitly trusted source loaders; reads are fresh on each call.
|
|
5
|
+
* @complexity O(p + d) packages and declarations, plus their file/import I/O; O(d + r) output space.
|
|
6
|
+
*/
|
|
7
|
+
export function createPluginContributionLoader(required, _optional = {}) {
|
|
8
|
+
/** Source bypass is explicit: only the caller can establish that a source directory is trusted.
|
|
9
|
+
* Read faults propagate unless the definition supplies its established recovery policy. */
|
|
10
|
+
async function loadPluginContributionsFromSource(input, _options = {}) {
|
|
11
|
+
const { plugin, definition } = input;
|
|
12
|
+
let raw;
|
|
13
|
+
try {
|
|
14
|
+
raw = await required.readFile({ plugin, filename: definition.filename });
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
const refusals = definition.onReadError?.({ plugin, error });
|
|
18
|
+
if (refusals === undefined)
|
|
19
|
+
throw error;
|
|
20
|
+
return { items: [], refusals };
|
|
21
|
+
}
|
|
22
|
+
const parsed = definition.parse({ raw });
|
|
23
|
+
if (!parsed.ok)
|
|
24
|
+
return { items: [], refusals: [`${definition.contribution} from '${plugin.pluginId}' were not loaded: ${definition.filename} is invalid: ${parsed.reason}`] };
|
|
25
|
+
const items = [];
|
|
26
|
+
const refusals = [];
|
|
27
|
+
for (const descriptor of parsed.descriptors) {
|
|
28
|
+
const loaded = await definition.load({ plugin, descriptor, importModule: required.importModule });
|
|
29
|
+
if (typeof loaded === 'string')
|
|
30
|
+
refusals.push(loaded);
|
|
31
|
+
else
|
|
32
|
+
items.push(loaded);
|
|
33
|
+
}
|
|
34
|
+
return { items, refusals };
|
|
35
|
+
}
|
|
36
|
+
/** Discover before loading, then interleave gate and manifest refusals in verdict order.
|
|
37
|
+
* Never fall back to source or bypass the bundled-digest gate for installed packages. */
|
|
38
|
+
async function loadPluginContributions(input, options = {}) {
|
|
39
|
+
const { definition } = input;
|
|
40
|
+
const { packageRefusal, requireActive = true, ...discovery } = options;
|
|
41
|
+
const verdicts = await required.findPackages({
|
|
42
|
+
...discovery, workspaceId: input.workspaceId, filename: definition.filename,
|
|
43
|
+
contribution: definition.contribution, requireActive: definition.kind === 'executable' || requireActive,
|
|
44
|
+
});
|
|
45
|
+
const items = [];
|
|
46
|
+
const refusals = [];
|
|
47
|
+
for (const verdict of verdicts) {
|
|
48
|
+
if ('refusal' in verdict) {
|
|
49
|
+
refusals.push(verdict.refusal);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const refusal = packageRefusal?.({ plugin: verdict.trusted });
|
|
53
|
+
if (refusal !== undefined) {
|
|
54
|
+
refusals.push(refusal);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const loaded = await loadPluginContributionsFromSource({ plugin: verdict.trusted, definition });
|
|
58
|
+
items.push(...loaded.items);
|
|
59
|
+
refusals.push(...loaded.refusals);
|
|
60
|
+
}
|
|
61
|
+
return { items, refusals };
|
|
62
|
+
}
|
|
63
|
+
return { loadPluginContributions, loadPluginContributionsFromSource };
|
|
64
|
+
}
|
|
65
|
+
/** Define executable materialization once; each domain owns export validation and refusal wording.
|
|
66
|
+
* @param required - Existing manifest parser, module path, validator and complete refusal formatter.
|
|
67
|
+
* @param optional - Established read/import recovery policy; absent policies propagate I/O faults.
|
|
68
|
+
* @returns A definition whose installed loader always applies activation before importing code.
|
|
69
|
+
* @complexity O(1) composition; each load performs one contained import and one validation.
|
|
70
|
+
*/
|
|
71
|
+
export function defineExecutablePluginContribution(required, optional = {}) {
|
|
72
|
+
return {
|
|
73
|
+
filename: required.filename, contribution: required.contribution, kind: 'executable', parse: required.parse,
|
|
74
|
+
...(optional.onReadError ? { onReadError: optional.onReadError } : {}),
|
|
75
|
+
load: async ({ plugin, descriptor, importModule }) => {
|
|
76
|
+
let module;
|
|
77
|
+
try {
|
|
78
|
+
const imported = await importModule({ plugin, modulePath: required.modulePath({ descriptor }) });
|
|
79
|
+
module = typeof imported === 'string' ? optional.importRefusalReason ?? imported : required.validate({ exported: imported.exported });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (optional.importErrorReason === undefined)
|
|
83
|
+
throw error;
|
|
84
|
+
module = optional.importErrorReason;
|
|
85
|
+
}
|
|
86
|
+
if (typeof module === 'string')
|
|
87
|
+
return required.refusal({ plugin, descriptor, reason: module });
|
|
88
|
+
return { descriptor, pluginId: plugin.pluginId, module };
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=contribution-loader.js.map
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @file The missing front half of the Agent Plugin install pipeline: URL -> verified bytes.
|
|
2
|
+
* @file The network front half of the Agent Plugin install pipeline: URL -> verified bytes.
|
|
4
3
|
*
|
|
5
4
|
* `install.ts` deliberately takes `archive: Uint8Array` and never a URL — it is a pure
|
|
6
5
|
* verify/extract/publish step with no network surface at all. This module is the one place that
|
|
@@ -4,12 +4,13 @@ import type { AgentPluginLifecycleRequired, AgentPluginLifecycleOptional } from
|
|
|
4
4
|
import type { AgentPluginDeliveryMode } from './resolve-agent-plugin-refs.js';
|
|
5
5
|
import type { InstalledDigestIdentity } from './bundled-digests.js';
|
|
6
6
|
export type { SeededAgentPluginOutcome, SeedBundledAgentPluginsRequired, SeedBundledAgentPluginsResult } from './seed-bundled.js';
|
|
7
|
-
export type { RetireBundledAgentPluginsRequired, RetireBundledAgentPluginsOptional, RetiredAgentPluginOutcome } from './retire-bundled.js';
|
|
7
|
+
export type { RetireBundledAgentPluginsRequired, RetireBundledAgentPluginsOptional, RetiredAgentPluginOutcome, RetiredAgentPluginSuccessorOutcome } from './retire-bundled.js';
|
|
8
8
|
export * from './ports.js';
|
|
9
9
|
export * from './layout.js';
|
|
10
10
|
export * from './manifest.js';
|
|
11
11
|
export * from './fetch-archive.js';
|
|
12
12
|
export * from './mcp-provisioning.js';
|
|
13
|
+
export * from './contribution-loader.js';
|
|
13
14
|
export { createAgentPluginActivations, ACTIVATIONS_FILENAME, ACTIVATIONS_LOCK_FILENAME, AgentPluginActivationsBusyError, AgentPluginActivationsUnreadableError } from './activation.js';
|
|
14
15
|
export { BUNDLED_DIGESTS_FILENAME } from './bundled-digests.js';
|
|
15
16
|
export { AgentPluginInstallError } from './install.js';
|
|
@@ -23,7 +24,7 @@ export type { AgentPluginArchiveEntry, AgentPluginArchiveReaderPort, AgentPlugin
|
|
|
23
24
|
export type { AgentPluginIntegrity, InstallAgentPluginFromUrlRequired, InstallAgentPluginFromUrlOptional, InstalledAgentPluginFromUrl } from './install-from-url.js';
|
|
24
25
|
export type { AgentPluginDeliveryMode, ResolveAgentPluginRefsResult } from './resolve-agent-plugin-refs.js';
|
|
25
26
|
export type { SetAgentPluginEnabledInput, SetAgentPluginEnabledResult } from './set-enabled.js';
|
|
26
|
-
export type { UninstallAgentPluginRequired, UninstallAgentPluginOptional, UninstallAgentPluginResult, AgentPluginUninstallPreview } from './uninstall.js';
|
|
27
|
+
export type { UninstallAgentPluginRequired, UninstallAgentPluginOptional, UninstallAgentPluginResult, AgentPluginUninstallPreview, StagedTree } from './uninstall.js';
|
|
27
28
|
export type { TrustedPluginPackage, TrustedPluginPackagesQuery, TrustedPluginVerdict } from './trusted-plugin-files.js';
|
|
28
29
|
export type { AgentPluginSearchSkill, AgentPluginSearchCandidate, AgentPluginSearchMatch } from './search.js';
|
|
29
30
|
/** One host context owns queues and effects. Keep this object and its ports stable for its lifetime.
|
|
@@ -67,12 +68,23 @@ export declare function createAgentPluginLifecycle(required: AgentPluginLifecycl
|
|
|
67
68
|
readonly workspaceRoot: string;
|
|
68
69
|
readonly pluginId: string;
|
|
69
70
|
}) => Promise<boolean>;
|
|
70
|
-
installAgentPlugin: (required: import("./install.js").InstallAgentPluginRequired,
|
|
71
|
+
installAgentPlugin: (required: import("./install.js").InstallAgentPluginRequired, optional?: import("./install.js").InstallAgentPluginOptional) => Promise<import("./types.js").InstalledAgentPlugin>;
|
|
71
72
|
installAgentPluginFromUrl: (required: import("./install-from-url.js").InstallAgentPluginFromUrlRequired, optional?: import("./install-from-url.js").InstallAgentPluginFromUrlOptional) => Promise<import("./install-from-url.js").InstalledAgentPluginFromUrl>;
|
|
72
73
|
setAgentPluginEnabled: (input: import("./set-enabled.js").SetAgentPluginEnabledInput) => Promise<import("./set-enabled.js").SetAgentPluginEnabledResult>;
|
|
73
74
|
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[]>;
|
|
74
75
|
previewAgentPluginUninstall: (required: import("./uninstall.js").UninstallAgentPluginRequired) => Promise<import("./uninstall.js").AgentPluginUninstallPreview>;
|
|
75
76
|
uninstallAgentPlugin: (required: import("./uninstall.js").UninstallAgentPluginRequired, optional?: import("./uninstall.js").UninstallAgentPluginOptional) => Promise<import("./uninstall.js").UninstallAgentPluginResult>;
|
|
77
|
+
stageForRemoval: (input: {
|
|
78
|
+
readonly packagesDir: string;
|
|
79
|
+
readonly packageRoot: string;
|
|
80
|
+
}, _optional?: Record<string, never>) => Promise<import("./uninstall.js").StagedTree>;
|
|
81
|
+
restoreStagedTrees: (input: {
|
|
82
|
+
readonly staged: Parameters<(staged: readonly import("./uninstall.js").StagedTree[], cause: unknown) => Promise<unknown>>[0];
|
|
83
|
+
readonly cause: unknown;
|
|
84
|
+
}, _optional?: Record<string, never>) => Promise<unknown>;
|
|
85
|
+
removeFrozenPackageTree: (input: {
|
|
86
|
+
readonly root: string;
|
|
87
|
+
}, _optional?: Record<string, never>) => Promise<void>;
|
|
76
88
|
isAgentPluginActive: (input: {
|
|
77
89
|
readonly activations: Parameters<(activations: import("./activation.js").AgentPluginActivations, pluginId: string) => boolean>[0];
|
|
78
90
|
readonly pluginId: string;
|
|
@@ -137,8 +149,18 @@ export declare function createAgentPluginLifecycle(required: AgentPluginLifecycl
|
|
|
137
149
|
}, optional?: {
|
|
138
150
|
readonly deliveryMode?: AgentPluginDeliveryMode;
|
|
139
151
|
}) => Promise<import("./resolve-agent-plugin-refs.js").ResolveAgentPluginRefsResult>;
|
|
152
|
+
pluginMemory: (input: {
|
|
153
|
+
readonly workspaceId: string;
|
|
154
|
+
readonly pluginId: string;
|
|
155
|
+
}) => import("../../core/persistent-state.js").PluginMemory;
|
|
156
|
+
migratePluginLayout: (input: {
|
|
157
|
+
readonly workspaceId: string;
|
|
158
|
+
}) => Promise<{
|
|
159
|
+
complete: boolean;
|
|
160
|
+
moved: number;
|
|
161
|
+
}>;
|
|
140
162
|
listInstalledPlugins: (input: {
|
|
141
|
-
readonly
|
|
163
|
+
readonly workspaceRoot: string;
|
|
142
164
|
}) => Promise<readonly import("./types.js").InstalledAgentPlugin[]>;
|
|
143
165
|
isInstalledDigestPresent: (input: {
|
|
144
166
|
readonly packagesDir: string;
|
|
@@ -170,5 +192,13 @@ export declare function createAgentPluginLifecycle(required: AgentPluginLifecycl
|
|
|
170
192
|
readonly sourceDir: string;
|
|
171
193
|
}) => Promise<import("./bundled-source-archive.js").PackedAgentPluginArchive>;
|
|
172
194
|
createBundledSourceArchiveReader: (_input: Record<string, never>) => import("./install.js").AgentPluginArchiveReaderPort;
|
|
195
|
+
loadPluginContributions: <D, T>(input: {
|
|
196
|
+
readonly workspaceId: string;
|
|
197
|
+
readonly definition: import("./contribution-loader.js").PluginContributionDefinition<D, T>;
|
|
198
|
+
}, options?: import("./contribution-loader.js").LoadPluginContributionsOptional) => Promise<import("./contribution-loader.js").PluginContributionResult<T>>;
|
|
199
|
+
loadPluginContributionsFromSource: <D, T>(input: {
|
|
200
|
+
readonly plugin: import("./trusted-plugin-files.js").TrustedPluginPackage;
|
|
201
|
+
readonly definition: import("./contribution-loader.js").PluginContributionDefinition<D, T>;
|
|
202
|
+
}, _options?: Record<string, never>) => Promise<import("./contribution-loader.js").PluginContributionResult<T>>;
|
|
173
203
|
};
|
|
174
204
|
//# sourceMappingURL=index.d.ts.map
|