@jini-ai/agent-plugins 0.6.15-rc.2 → 0.6.16
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 +18 -0
- package/README.md +21 -50
- package/dist/admin-surfaces/action-tools.js +1 -1
- package/dist/admin-surfaces/external-links.js +2 -1
- package/dist/admin-surfaces/host-validator.js +2 -2
- package/dist/admin-surfaces/settings-value.js +3 -1
- package/dist/admin-surfaces/surface-client.js +13 -5
- package/dist/core/plugin-name.d.ts +11 -0
- package/dist/core/plugin-name.js +19 -0
- package/dist/folder-zip-node.d.ts +21 -0
- package/dist/folder-zip-node.js +85 -3
- package/dist/lifecycle/activation.js +2 -2
- package/dist/lifecycle/automatic-use.js +12 -7
- package/dist/lifecycle/bundled-digests.js +3 -8
- package/dist/lifecycle/bundled-source-archive.d.ts +8 -2
- package/dist/lifecycle/bundled-source-archive.js +17 -8
- package/dist/lifecycle/contribution-loader.js +4 -5
- package/dist/lifecycle/index.d.ts +1 -2
- package/dist/lifecycle/install.js +59 -53
- package/dist/lifecycle/installed-package-index.js +4 -2
- package/dist/lifecycle/manifest.d.ts +1 -9
- package/dist/lifecycle/manifest.js +6 -21
- package/dist/lifecycle/node.js +4 -2
- package/dist/lifecycle/resolve-agent-plugin-refs.js +1 -3
- package/dist/lifecycle/search-projection.js +7 -6
- package/dist/lifecycle/seed-bundled.js +1 -3
- package/dist/lifecycle/skill-file-reader.js +15 -5
- package/dist/lifecycle/tools/capabilities.js +5 -3
- package/dist/lifecycle/tools/projection.js +3 -4
- package/dist/lifecycle/update.js +4 -2
- package/dist/lifecycle/yauzl-archive-reader.js +21 -23
- package/package.json +11 -8
- package/dist/admin-surfaces/action-dispatcher.d.ts.map +0 -1
- package/dist/admin-surfaces/action-dispatcher.js.map +0 -1
- package/dist/admin-surfaces/action-schema.d.ts.map +0 -1
- package/dist/admin-surfaces/action-schema.js.map +0 -1
- package/dist/admin-surfaces/action-tools.d.ts.map +0 -1
- package/dist/admin-surfaces/action-tools.js.map +0 -1
- package/dist/admin-surfaces/callback-field-inbox.d.ts.map +0 -1
- package/dist/admin-surfaces/callback-field-inbox.js.map +0 -1
- package/dist/admin-surfaces/capability-declarations.d.ts.map +0 -1
- package/dist/admin-surfaces/capability-declarations.js.map +0 -1
- package/dist/admin-surfaces/contribution.d.ts.map +0 -1
- package/dist/admin-surfaces/contribution.js.map +0 -1
- package/dist/admin-surfaces/delivery-bindings.d.ts.map +0 -1
- package/dist/admin-surfaces/delivery-bindings.js.map +0 -1
- package/dist/admin-surfaces/descriptor.d.ts.map +0 -1
- package/dist/admin-surfaces/descriptor.js.map +0 -1
- package/dist/admin-surfaces/external-links.d.ts.map +0 -1
- package/dist/admin-surfaces/external-links.js.map +0 -1
- package/dist/admin-surfaces/host-validator.d.ts.map +0 -1
- package/dist/admin-surfaces/host-validator.js.map +0 -1
- package/dist/admin-surfaces/index.d.ts.map +0 -1
- package/dist/admin-surfaces/index.js.map +0 -1
- package/dist/admin-surfaces/protocol.d.ts.map +0 -1
- package/dist/admin-surfaces/protocol.js.map +0 -1
- package/dist/admin-surfaces/sandbox.d.ts.map +0 -1
- package/dist/admin-surfaces/sandbox.js.map +0 -1
- package/dist/admin-surfaces/settings-value.d.ts.map +0 -1
- package/dist/admin-surfaces/settings-value.js.map +0 -1
- package/dist/admin-surfaces/surface-capabilities.d.ts.map +0 -1
- package/dist/admin-surfaces/surface-capabilities.js.map +0 -1
- package/dist/admin-surfaces/surface-client.d.ts.map +0 -1
- package/dist/admin-surfaces/surface-client.js.map +0 -1
- package/dist/admin-surfaces/surface-handlers.d.ts.map +0 -1
- package/dist/admin-surfaces/surface-handlers.js.map +0 -1
- package/dist/admin-surfaces/theme.d.ts.map +0 -1
- package/dist/admin-surfaces/theme.js.map +0 -1
- package/dist/contracts/admin-surface-action-schema.d.ts.map +0 -1
- package/dist/contracts/admin-surface-action-schema.js.map +0 -1
- package/dist/folder-zip-node.d.ts.map +0 -1
- package/dist/folder-zip-node.js.map +0 -1
- package/dist/folder-zip.d.ts.map +0 -1
- package/dist/folder-zip.js.map +0 -1
- 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/automatic-use.d.ts.map +0 -1
- package/dist/lifecycle/automatic-use.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/compatibility-metadata.d.ts.map +0 -1
- package/dist/lifecycle/compatibility-metadata.js.map +0 -1
- package/dist/lifecycle/compatibility.d.ts.map +0 -1
- package/dist/lifecycle/compatibility.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/exports.d.ts.map +0 -1
- package/dist/lifecycle/exports.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-errors.d.ts.map +0 -1
- package/dist/lifecycle/install-errors.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-index.d.ts.map +0 -1
- package/dist/lifecycle/installed-package-index.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/provider-contribution.d.ts.map +0 -1
- package/dist/lifecycle/provider-contribution.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-projection.d.ts.map +0 -1
- package/dist/lifecycle/search-projection.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/tools/capabilities.d.ts.map +0 -1
- package/dist/lifecycle/tools/capabilities.js.map +0 -1
- package/dist/lifecycle/tools/definition.d.ts.map +0 -1
- package/dist/lifecycle/tools/definition.js.map +0 -1
- package/dist/lifecycle/tools/index.d.ts.map +0 -1
- package/dist/lifecycle/tools/index.js.map +0 -1
- package/dist/lifecycle/tools/manifest.d.ts.map +0 -1
- package/dist/lifecycle/tools/manifest.js.map +0 -1
- package/dist/lifecycle/tools/projection.d.ts.map +0 -1
- package/dist/lifecycle/tools/projection.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/update-plan.d.ts.map +0 -1
- package/dist/lifecycle/update-plan.js.map +0 -1
- package/dist/lifecycle/update-store.d.ts.map +0 -1
- package/dist/lifecycle/update-store.js.map +0 -1
- package/dist/lifecycle/update.d.ts.map +0 -1
- package/dist/lifecycle/update.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
- package/ui-ux-design/com.anthropic.claude-code/hooks/hooks.json +0 -4
- package/ui-ux-design/mcp.json +0 -4
- package/ui-ux-design/plugin.json +0 -25
- package/ui-ux-design/skills/frontend-accessibility/SKILL.md +0 -60
- package/ui-ux-design/skills/gstack-design/SKILL.md +0 -53
- package/ui-ux-design/skills/gstack-design/references/consultation.md +0 -30
- package/ui-ux-design/skills/gstack-design/references/html.md +0 -29
- package/ui-ux-design/skills/gstack-design/references/review.md +0 -28
- package/ui-ux-design/skills/gstack-design/references/shotgun.md +0 -33
- package/ui-ux-design/skills/gstack-design/references/upstream-notes.md +0 -23
- package/ui-ux-design/skills/interface-design/ORIGINAL.md +0 -16
- package/ui-ux-design/skills/interface-design/SKILL.md +0 -41
- package/ui-ux-design/skills/interface-design/references/commands/audit.md +0 -40
- package/ui-ux-design/skills/interface-design/references/commands/critique.md +0 -21
- package/ui-ux-design/skills/interface-design/references/commands/extract.md +0 -34
- package/ui-ux-design/skills/interface-design/references/commands/init.md +0 -31
- package/ui-ux-design/skills/interface-design/references/commands/status.md +0 -22
- package/ui-ux-design/skills/interface-design/references/examples/system-precision.md +0 -29
- package/ui-ux-design/skills/interface-design/references/examples/system-warmth.md +0 -29
- package/ui-ux-design/skills/interface-design/references/system-template.md +0 -57
- package/ui-ux-design/skills/shadcn-ui/README.md +0 -244
- package/ui-ux-design/skills/shadcn-ui/SKILL.md +0 -326
- package/ui-ux-design/skills/shadcn-ui/examples/auth-layout.tsx +0 -177
- package/ui-ux-design/skills/shadcn-ui/examples/data-table.tsx +0 -313
- package/ui-ux-design/skills/shadcn-ui/examples/form-pattern.tsx +0 -177
- package/ui-ux-design/skills/shadcn-ui/resources/component-catalog.md +0 -481
- package/ui-ux-design/skills/shadcn-ui/resources/customization-guide.md +0 -516
- package/ui-ux-design/skills/shadcn-ui/resources/migration-guide.md +0 -463
- package/ui-ux-design/skills/shadcn-ui/resources/setup-guide.md +0 -412
- package/ui-ux-design/skills/shadcn-ui/scripts/verify-setup.sh +0 -134
- package/ui-ux-design/skills/ui-ux-design/SKILL.md +0 -77
- package/ui-ux-design/skills/ui-ux-design/references/brand-and-voice.md +0 -72
- package/ui-ux-design/skills/ui-ux-design/references/components-and-states.md +0 -107
- package/ui-ux-design/skills/ui-ux-design/references/delight-and-motion.md +0 -65
- package/ui-ux-design/skills/ui-ux-design/references/foundations.md +0 -144
- package/ui-ux-design/skills/ui-ux-design/references/inclusive-ai-imagery.md +0 -57
- package/ui-ux-design/skills/ui-ux-design/references/kole-jain-uiux-concepts.md +0 -102
- package/ui-ux-design/skills/ui-ux-design/references/openai-frontend-skill.md +0 -139
- package/ui-ux-design/skills/ui-ux-design/references/premium-ui.md +0 -63
- package/ui-ux-design/skills/ui-ux-design/references/research-and-validation.md +0 -78
- package/ui-ux-design/skills/ui-ux-design/references/sam-crawford-premium-websites.md +0 -93
- package/ui-ux-design/skills/ui-ux-design/references/self-made-web-designer-core-skills.md +0 -123
- package/ui-ux-design/skills/ui-ux-design/references/visual-storytelling.md +0 -57
- package/ui-ux-design/skills/vercel-web-design-guidelines/SKILL.md +0 -39
- package/ui-ux-design/skills/web-compliance/SKILL.md +0 -129
- package/ui-ux-design/skills/web-compliance/references/audit-evidence.md +0 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.15
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Add opt-in plugin source suffix exclusions and test-file inclusion. Explicit allow-paths and their ancestors now override name exclusions, while excluded descendants and suffix exclusions still skip. Default packing stays unchanged when the new options and conflicting allow/name rules are absent.
|
|
8
|
+
|
|
9
|
+
Export ResponseLimitExceededError with limitBytes from platform/fetch-with-timeout and the platform root. Guarded response-text overflow throws this RangeError subclass; invalid caps keep plain RangeError and existing truncation behavior is preserved.
|
|
10
|
+
|
|
11
|
+
Accept jsdom 26 and 27 as optional CMS peers alongside 25; media uses DOM APIs shared across these versions.
|
|
12
|
+
|
|
13
|
+
- Breaking package-surface change for the fixed 0.6.15 release: remove the bundled
|
|
14
|
+
ui-ux-design files and wildcard export from @jini-ai/agent-plugins. Hosts must
|
|
15
|
+
explicitly install @jini-ai/plugin-ui-ux-design and resolve its ./plugin.json
|
|
16
|
+
export, stage that payload and seed through the existing Agent Plugin lifecycle.
|
|
17
|
+
The lifecycle implementation remains in @jini-ai/agent-plugins. Inner plugin
|
|
18
|
+
version 0.4.6, deterministic packing, digest selection and operator activation
|
|
19
|
+
decisions are preserved. Tovu wording reconciliation and cutover follow later.
|
|
20
|
+
|
|
3
21
|
## 0.6.13
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ decides its own install mechanism, permission model, and sandboxing.
|
|
|
29
29
|
|
|
30
30
|
## What a plugin looks like on disk
|
|
31
31
|
|
|
32
|
-
A plugin is a directory. Example (
|
|
32
|
+
A plugin is a directory. Example (`@jini-ai/plugin-ui-ux-design` publishes this payload):
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
ui-ux-design/
|
|
@@ -104,13 +104,13 @@ Catalog format) that this package does not implement.
|
|
|
104
104
|
| `.` (root) | universal | `PluginManifest`/`McpManifest` types, `validatePluginManifest({ value })`/`validateMcpManifest({ value })` structural validators (plus compatible `isPluginManifest(value)`/`isMcpManifest(value)` wrappers), and the known path constants (`PLUGIN_MANIFEST_FILENAME`, `PLUGIN_SKILLS_DIRNAME`, `PLUGIN_MCP_MANIFEST_FILENAME`). No filesystem access, no DOM. |
|
|
105
105
|
| `./manifest` | universal | Strict manifest/MCP parsing and typed namespace readers; object structural validators are also re-exported here. |
|
|
106
106
|
| `./lifecycle` | node | Host-injected installation, activation, locks, digests, bundles, trusted files, reference resolution, ranking and MCP provisioning helpers. |
|
|
107
|
+
| `./lifecycle` packing | node | `createAgentPluginLifecycle(ports).packAgentPluginDirectory({ sourceDir }, { allowPaths, excludeNames, excludeSuffixes, includeTests })`; explicit allow-paths and their ancestors beat name exclusions; excluded descendants still skip. Suffix exclusions always win; test skips require `includeTests: true` to disable. |
|
|
107
108
|
| `./lifecycle/node` | node | `createNodeAgentPluginEffects({})` opt-in native effects. |
|
|
108
109
|
| `./lifecycle/yauzl` | node | `createYauzlAgentPluginArchiveReader({ yauzl })`; the host supplies the optional peer library. |
|
|
109
110
|
| `./folder-zip` | universal | `zipFolderFiles({ files, maxBytes })`: packs picked folder `File`s into one stored-mode, binary-safe `.zip` `File` (the admin "Upload a folder" encoder). |
|
|
110
111
|
| `./folder-zip/node` | node | `zipLocalFolder({ path, maxBytes })`: the same encoder over a local folder; refuses symlinks/special files, bounds entries and bytes. |
|
|
111
112
|
| `./admin-surfaces` | universal | Plugin-owned admin surfaces: the contribution definition (host-named file), sandboxed-iframe/CSP/bootstrap helpers, the host-page message gate and the server-side action dispatcher. See "Admin surfaces" below. |
|
|
112
113
|
| `./admin-surfaces/client` | universal | `createAdminSurfaceClient(...)`: the self-contained surface-side protocol client a plugin's UI imports or copies. |
|
|
113
|
-
| `./ui-ux-design/*` | — | Raw files of the bundled `ui-ux-design` plugin — static JSON/Markdown, not run through the TS build. |
|
|
114
114
|
|
|
115
115
|
The universal root handles structural validation. The separate Node lifecycle entry implements
|
|
116
116
|
local archive installation and host-owned activation policy; it does not implement remote catalog discovery.
|
|
@@ -150,42 +150,19 @@ package supports. Do not confuse the two — "agent plugins" (this package, a
|
|
|
150
150
|
public spec) and "Jini plugins" (`@jini-ai/plugins`'s `./host`, Jini's own unbuilt format) share
|
|
151
151
|
the word "plugin" and nothing else.
|
|
152
152
|
|
|
153
|
-
##
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
Bundles the AI-Dev-Shop Web Design agent's full skill set (per its `agents/web-design/skills.md`
|
|
167
|
-
persona and the `framework/routing/skills-registry.md` ownership mapping) into one portable plugin:
|
|
168
|
-
|
|
169
|
-
- `ui-ux-design` — design-foundations (tokens, typography, spacing, breakpoints, component state
|
|
170
|
-
matrix) *and* first-impression polish, scanning hierarchy, conversion-focused visual signals —
|
|
171
|
-
one skill, not two. AI-Dev-Shop merged its former separate `ux-design` and `premium-ui` skills
|
|
172
|
-
into this single `ui-ux-design` skill; this sample tracks that merge instead of carrying the two
|
|
173
|
-
predecessor skills as stale copies (see git history for the 2026-08-12 consolidation).
|
|
174
|
-
- `interface-design` — repeatable, memory-consistent visual systems for dashboards/admin panels/apps
|
|
175
|
-
- `gstack-design` — manual four-mode workflow (consultation, shotgun, html, review)
|
|
176
|
-
- `frontend-accessibility` — WCAG 2.1 AA checklist
|
|
177
|
-
- `vercel-web-design-guidelines` — Vercel Web Interface Guidelines auditor
|
|
178
|
-
- `shadcn-ui` — shadcn/ui (Radix + Tailwind) component discovery/integration guidance
|
|
179
|
-
- `web-compliance` — legal/compliance checkpoints for public-facing UX flows
|
|
180
|
-
|
|
181
|
-
Each skill directory is a verbatim copy of the corresponding `AI-Dev-Shop/skills/<name>/` tree —
|
|
182
|
-
copied rather than referenced, because the whole point of a plugin is that it is self-contained and
|
|
183
|
-
portable to a host that has never heard of AI-Dev-Shop. `AI-Dev-Shop/agents/web-design/skills.md`
|
|
184
|
-
itself (the persona that composes these skills into one role) is not bundled — it is ADS-specific
|
|
185
|
-
routing glue, not a portable skill.
|
|
186
|
-
|
|
187
|
-
Consumers may read bundled raw assets through the public wildcard subpath. Product-specific
|
|
188
|
-
plugin assets are owned and shipped by their product, outside this library.
|
|
153
|
+
## Independently packaged plugins
|
|
154
|
+
|
|
155
|
+
Starting with the breaking 0.6.16 release, this library ships manifest and lifecycle code only.
|
|
156
|
+
Install `@jini-ai/plugin-ui-ux-design` explicitly for the design payload. Resolve
|
|
157
|
+
`@jini-ai/plugin-ui-ux-design/plugin.json`; its containing directory holds the portable assets.
|
|
158
|
+
Stage that directory beneath the host's bundled-source root, then call the existing
|
|
159
|
+
`seedBundledAgentPlugins({ layout, workspaceId, sourceRoot })`. Read contributions through trusted
|
|
160
|
+
installed-package readers. Seeding preserves digest selection and operator activation decisions.
|
|
161
|
+
|
|
162
|
+
The editable design source remains at `ui-ux-design/` here, outside `src/`: its `.tsx` examples
|
|
163
|
+
are illustrative skill content and must not enter the TypeScript compile root. Assembly generates
|
|
164
|
+
only the standalone package's payload. The old `@jini-ai/agent-plugins/ui-ux-design/*` export is
|
|
165
|
+
removed; product-specific assets remain owned by their product.
|
|
189
166
|
|
|
190
167
|
## Scripts
|
|
191
168
|
|
|
@@ -198,18 +175,12 @@ pnpm --filter @jini-ai/agent-plugins test
|
|
|
198
175
|
The build and typecheck include lifecycle test sources. Copied Node test options are
|
|
199
176
|
translated to Vitest registration, preserving per-test timeouts and platform skip conditions.
|
|
200
177
|
|
|
201
|
-
## Adding another
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`examples/`, `scripts/` the skill needs).
|
|
208
|
-
3. Register it in `package.json`: an `exports` key `"./<plugin-name>/*": "./<plugin-name>/*"`, a
|
|
209
|
-
matching `jini.entries` key (the R8 guard requires every export to have one), and a
|
|
210
|
-
`files` entry.
|
|
211
|
-
4. Add coverage in `src/__tests__/manifest.test.ts` following the `ui-ux-design plugin` block — assert the manifest validates and the expected skill
|
|
212
|
-
directories exist.
|
|
178
|
+
## Adding another plugin
|
|
179
|
+
|
|
180
|
+
Create a separate `@jini-ai/plugin-<id>` workspace package with its own reviewed manifest,
|
|
181
|
+
README, LICENSE and NOTICE. Generate only that plugin's payload; do not add plugin assets or
|
|
182
|
+
plugin dependencies to this lifecycle library. Keep skill trees outside TypeScript compile roots,
|
|
183
|
+
validate the plugin manifest and test installation through this package's existing lifecycle.
|
|
213
184
|
|
|
214
185
|
## Lifecycle API and ports
|
|
215
186
|
|
|
@@ -40,7 +40,7 @@ export function buildAdminSurfaceActionTools(required, optional = {}) {
|
|
|
40
40
|
await required.authorize({ context });
|
|
41
41
|
const payload = checkedPayload({ input, action });
|
|
42
42
|
const dispatcher = createAdminSurfaceActionDispatcher({ descriptor, handlers: required.handlersFor({ pluginId, surfaceId: descriptor.id }) });
|
|
43
|
-
const dispatch =
|
|
43
|
+
const dispatch = dispatcher.dispatch.bind(dispatcher, { action: action.name, payload, context });
|
|
44
44
|
return optional.run ? optional.run({ dispatch }) : dispatch();
|
|
45
45
|
},
|
|
46
46
|
},
|
|
@@ -20,7 +20,8 @@ function isOpenLinkEnvelope(data) {
|
|
|
20
20
|
return false;
|
|
21
21
|
const message = data;
|
|
22
22
|
const isProtocol = message.protocol === ADMIN_SURFACE_PROTOCOL && message.version === ADMIN_SURFACE_PROTOCOL_VERSION && message.kind === 'open-link';
|
|
23
|
-
|
|
23
|
+
const hasRequestId = typeof message.requestId === 'string' && REQUEST_ID_PATTERN.test(message.requestId);
|
|
24
|
+
return isProtocol && typeof message.mountId === 'string' && hasRequestId && typeof message.url === 'string';
|
|
24
25
|
}
|
|
25
26
|
/** The normalized URL when `raw` may be opened, or why not. Messages never echo the URL. @complexity O(n) in the URL length. */
|
|
26
27
|
function checkLink(raw, origins) {
|
|
@@ -18,8 +18,8 @@ function isRequestEnvelope(data) {
|
|
|
18
18
|
return isProtocol && hasRequestFields(message);
|
|
19
19
|
}
|
|
20
20
|
function hasRequestFields(message) {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const hasRequestId = typeof message.requestId === 'string' && REQUEST_ID_PATTERN.test(message.requestId);
|
|
22
|
+
return typeof message.mountId === 'string' && hasRequestId && typeof message.action === 'string' && Object.hasOwn(message, 'payload');
|
|
23
23
|
}
|
|
24
24
|
/** Character length of the payload as JSON, or Infinity when it is not JSON-serializable. */
|
|
25
25
|
/** Whether `event` comes from `mount`'s frame at all (origin and window), for every inbound message kind.
|
|
@@ -10,7 +10,9 @@ import { PLUGIN_STATE_MAX_KEY_LENGTH } from '../lifecycle/tools/capabilities.js'
|
|
|
10
10
|
export const ADMIN_SURFACE_STATE_LIMITS = Object.freeze({ maxKeyLength: PLUGIN_STATE_MAX_KEY_LENGTH, maxValueBytes: 16 * 1024, maxDepth: 16 });
|
|
11
11
|
export const STATE_CREDENTIAL_MESSAGE = 'The plugin tried to save a connection credential in its settings; nothing was saved.';
|
|
12
12
|
function isStateScalar(value) {
|
|
13
|
-
|
|
13
|
+
if (typeof value === 'number')
|
|
14
|
+
return Number.isFinite(value);
|
|
15
|
+
return value === null || typeof value === 'string' || typeof value === 'boolean';
|
|
14
16
|
}
|
|
15
17
|
/** True for plain JSON data only: no undefined, functions, NaN, class instances or deep nesting. @complexity O(n) nodes. */
|
|
16
18
|
function isStateValue(value, depth) {
|
|
@@ -43,6 +43,12 @@ function isFailure(value) {
|
|
|
43
43
|
const error = value;
|
|
44
44
|
return typeof error.code === 'string' && typeof error.message === 'string' && typeof error.retryable === 'boolean';
|
|
45
45
|
}
|
|
46
|
+
/** A non-empty, bounded token value with nothing that could escape a CSS custom property. */
|
|
47
|
+
function isSafeThemeValue(token) {
|
|
48
|
+
if (typeof token !== 'string')
|
|
49
|
+
return false;
|
|
50
|
+
return token.length > 0 && token.length <= 200 && !UNSAFE_THEME_VALUE.test(token);
|
|
51
|
+
}
|
|
46
52
|
/** The host's theme with every unknown name or unsafe value dropped; undefined when it is not a theme. */
|
|
47
53
|
function acceptedTheme(value) {
|
|
48
54
|
if (typeof value !== 'object' || value === null)
|
|
@@ -50,8 +56,7 @@ function acceptedTheme(value) {
|
|
|
50
56
|
const { scheme, tokens } = value;
|
|
51
57
|
if ((scheme !== 'light' && scheme !== 'dark') || typeof tokens !== 'object' || tokens === null)
|
|
52
58
|
return undefined;
|
|
53
|
-
const safe = Object.entries(tokens).slice(0, MAX_THEME_TOKENS).filter(([name, token]) => THEME_TOKEN.test(name)
|
|
54
|
-
&& typeof token === 'string' && token.length > 0 && token.length <= 200 && !UNSAFE_THEME_VALUE.test(token));
|
|
59
|
+
const safe = Object.entries(tokens).slice(0, MAX_THEME_TOKENS).filter(([name, token]) => THEME_TOKEN.test(name) && isSafeThemeValue(token));
|
|
55
60
|
return { scheme, tokens: Object.fromEntries(safe) };
|
|
56
61
|
}
|
|
57
62
|
/** The browser root when there is a document (none in a server-side test). */
|
|
@@ -158,13 +163,16 @@ export function createAdminSurfaceClient(required, optional = {}) {
|
|
|
158
163
|
const unsubscribe = required.listen((event) => {
|
|
159
164
|
if (event.source !== required.parent)
|
|
160
165
|
return;
|
|
161
|
-
if (isMessage(event.data, 'init'))
|
|
166
|
+
if (isMessage(event.data, 'init')) {
|
|
162
167
|
onInit(event, event.data);
|
|
163
|
-
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (isMessage(event.data, 'theme')) {
|
|
164
171
|
if (event.origin === init?.origin && event.data.mountId === init.mountId)
|
|
165
172
|
applyTheme(event.data.theme);
|
|
173
|
+
return;
|
|
166
174
|
}
|
|
167
|
-
|
|
175
|
+
if (isMessage(event.data, 'action-response'))
|
|
168
176
|
onResponse(event, event.data);
|
|
169
177
|
});
|
|
170
178
|
/** Posts one request of `kind` to the host origin and waits for its `action-response`. */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const MAX_NAME_LENGTH = 64;
|
|
2
|
+
/**
|
|
3
|
+
* True when `value` is a valid Agent Plugin id: `plugin.json`'s `name` grammar (§5.5, see
|
|
4
|
+
* `NAME_PATTERN`), 1-64 characters. The one owner of that grammar: a host checks an id taken from a
|
|
5
|
+
* URL or a tool input here before it names anything on disk, instead of restating the pattern.
|
|
6
|
+
* @complexity O(n) in the value's length.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isAgentPluginName({ value }: {
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
}, _optional?: Record<string, never>): boolean;
|
|
11
|
+
//# sourceMappingURL=plugin-name.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §5.5's grammar as one regex: alnum runs separated by single `-`/`.` delimiters. This shape alone
|
|
3
|
+
* guarantees start/end are alphanumeric (the regex can only start and end on `[a-z0-9]+`) AND that
|
|
4
|
+
* no two delimiters are ever adjacent (a delimiter is always followed by at least one alnum char
|
|
5
|
+
* before the next one can appear) — so "no leading/trailing hyphen" and "no consecutive delimiters"
|
|
6
|
+
* fall out of one pattern rather than needing separate checks that could disagree.
|
|
7
|
+
*/
|
|
8
|
+
const NAME_PATTERN = /^[a-z0-9]+(?:[-.][a-z0-9]+)*$/;
|
|
9
|
+
export const MAX_NAME_LENGTH = 64;
|
|
10
|
+
/**
|
|
11
|
+
* True when `value` is a valid Agent Plugin id: `plugin.json`'s `name` grammar (§5.5, see
|
|
12
|
+
* `NAME_PATTERN`), 1-64 characters. The one owner of that grammar: a host checks an id taken from a
|
|
13
|
+
* URL or a tool input here before it names anything on disk, instead of restating the pattern.
|
|
14
|
+
* @complexity O(n) in the value's length.
|
|
15
|
+
*/
|
|
16
|
+
export function isAgentPluginName({ value }, _optional = {}) {
|
|
17
|
+
return typeof value === "string" && value.length <= MAX_NAME_LENGTH && NAME_PATTERN.test(value);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=plugin-name.js.map
|
|
@@ -8,4 +8,25 @@ export declare function zipLocalFolder(required: {
|
|
|
8
8
|
path: string;
|
|
9
9
|
maxBytes: number;
|
|
10
10
|
}, _optional?: Record<string, never>): Promise<Uint8Array>;
|
|
11
|
+
export interface CustomizeAgentPluginFolderInput {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly maxBytes: number;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly identityNamespace: string;
|
|
16
|
+
readonly edit: {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly oldString: string;
|
|
19
|
+
readonly newString: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Fork a captured Agent Plugin package with one exact text edit and a fresh identity.
|
|
24
|
+
* Uses the folder upload reader/encoder; never writes into an immutable installed package.
|
|
25
|
+
* @param required Source folder, byte ceiling, new local id, host namespace and literal edit.
|
|
26
|
+
* @returns ZIP bytes for the existing upload preview/installer. Does not grant bundled trust.
|
|
27
|
+
* @throws {FolderZipError} Unsafe/missing paths, links, non-unique text, identity or size refusal.
|
|
28
|
+
* @complexity O(e log e + b) time and O(e + b) space, bounded by upload entry/byte ceilings.
|
|
29
|
+
* @example zipCustomizedAgentPluginFolder({ path, maxBytes, name: 'my-plugin', identityNamespace: 'host', edit })
|
|
30
|
+
*/
|
|
31
|
+
export declare function zipCustomizedAgentPluginFolder(required: CustomizeAgentPluginFolderInput, _optional?: Record<string, never>): Promise<Uint8Array>;
|
|
11
32
|
//# sourceMappingURL=folder-zip-node.d.ts.map
|
package/dist/folder-zip-node.js
CHANGED
|
@@ -2,6 +2,13 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { FOLDER_MAX_FILES, FOLDER_UNREADABLE, FolderZipError, zipFolderFiles } from "./folder-zip.js";
|
|
5
|
+
import { isAgentPluginName } from './core/plugin-name.js';
|
|
6
|
+
/** Code-unit order, independent of locale. */
|
|
7
|
+
function compareCodeUnits(a, b) {
|
|
8
|
+
if (a === b)
|
|
9
|
+
return 0;
|
|
10
|
+
return a < b ? -1 : 1;
|
|
11
|
+
}
|
|
5
12
|
async function readLocalFile(required, _optional = {}) {
|
|
6
13
|
const { filesystem, root, filename, name, maxBytes } = required;
|
|
7
14
|
if (await filesystem.realpath(filename) !== filename)
|
|
@@ -41,7 +48,8 @@ async function collectDirectory(required, _optional = {}) {
|
|
|
41
48
|
const handle = await filesystem.opendir(directory);
|
|
42
49
|
// Mutate the traversal's owned buffers so visiting directories does not copy accumulated files.
|
|
43
50
|
for await (const entry of handle) {
|
|
44
|
-
|
|
51
|
+
collection.entries += 1;
|
|
52
|
+
if (collection.entries > FOLDER_MAX_FILES)
|
|
45
53
|
throw new FolderZipError("too-many", `More than ${FOLDER_MAX_FILES} entries.`);
|
|
46
54
|
const filename = path.join(directory, entry.name);
|
|
47
55
|
if (entry.isDirectory()) {
|
|
@@ -62,6 +70,10 @@ async function collectDirectory(required, _optional = {}) {
|
|
|
62
70
|
* bounded to 4096 entries and maxBytes. No network I/O.
|
|
63
71
|
*/
|
|
64
72
|
export async function zipLocalFolder(required, _optional = {}) {
|
|
73
|
+
return encodeLocalFiles({ files: await collectLocalFiles(required), maxBytes: required.maxBytes });
|
|
74
|
+
}
|
|
75
|
+
/** Shared bounded, no-link directory reader for uploads and customized copies. No writes. */
|
|
76
|
+
async function collectLocalFiles(required) {
|
|
65
77
|
const collection = { files: [], pending: [], entries: 0, total: 0 };
|
|
66
78
|
try {
|
|
67
79
|
const root = await fs.realpath(required.path);
|
|
@@ -72,8 +84,8 @@ export async function zipLocalFolder(required, _optional = {}) {
|
|
|
72
84
|
}
|
|
73
85
|
const { files } = collection;
|
|
74
86
|
// Stable order gives repeat local-folder installs identical ZIP digests.
|
|
75
|
-
files.sort((a, b) => a.webkitRelativePath
|
|
76
|
-
return
|
|
87
|
+
files.sort((a, b) => compareCodeUnits(a.webkitRelativePath, b.webkitRelativePath));
|
|
88
|
+
return files;
|
|
77
89
|
}
|
|
78
90
|
catch (error) {
|
|
79
91
|
if (error instanceof FolderZipError)
|
|
@@ -81,4 +93,74 @@ export async function zipLocalFolder(required, _optional = {}) {
|
|
|
81
93
|
throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
|
|
82
94
|
}
|
|
83
95
|
}
|
|
96
|
+
/** Encode through the upload owner, rechecking the byte budget after any transformation. */
|
|
97
|
+
async function encodeLocalFiles({ files, maxBytes }) {
|
|
98
|
+
return new Uint8Array(await (await zipFolderFiles({ files, maxBytes })).arrayBuffer());
|
|
99
|
+
}
|
|
100
|
+
/** Replace captured bytes while preserving the upload encoder's relative path. */
|
|
101
|
+
function replaceCapturedFile({ file, text }) {
|
|
102
|
+
const next = new File([text], file.name);
|
|
103
|
+
Object.defineProperty(next, 'webkitRelativePath', { value: file.webkitRelativePath });
|
|
104
|
+
return next;
|
|
105
|
+
}
|
|
106
|
+
/** Non-UTF-8 or NUL-bearing bytes are binary; refuse them as a named error, not a decoder TypeError. */
|
|
107
|
+
function decodeEditableText(bytes) {
|
|
108
|
+
let text;
|
|
109
|
+
try {
|
|
110
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
text = '\0';
|
|
114
|
+
}
|
|
115
|
+
if (text.includes('\0'))
|
|
116
|
+
throw new FolderZipError('unreadable', 'Binary files cannot be edited as text.');
|
|
117
|
+
return text;
|
|
118
|
+
}
|
|
119
|
+
/** Require exactly one literal match; `$&` and other replacement syntax remain plain text. */
|
|
120
|
+
function customizeText({ text, edit }) {
|
|
121
|
+
if (!edit.oldString)
|
|
122
|
+
throw new FolderZipError('unreadable', 'oldString must not be empty.');
|
|
123
|
+
const index = text.indexOf(edit.oldString);
|
|
124
|
+
if (index < 0 || text.indexOf(edit.oldString, index + edit.oldString.length) >= 0) {
|
|
125
|
+
throw new FolderZipError('unreadable', 'oldString must match exactly once. Read the file and send more context.');
|
|
126
|
+
}
|
|
127
|
+
return text.slice(0, index) + edit.newString + text.slice(index + edit.oldString.length);
|
|
128
|
+
}
|
|
129
|
+
/** A fork must not retain its publisher identity or historical aliases and supersession claims. */
|
|
130
|
+
function customizeManifest({ text, name, identityNamespace }) {
|
|
131
|
+
const manifest = JSON.parse(text);
|
|
132
|
+
if (!isAgentPluginName({ value: name }) || manifest.name === name) {
|
|
133
|
+
throw new FolderZipError('unreadable', 'Choose a valid new Agent Plugin id different from the source.');
|
|
134
|
+
}
|
|
135
|
+
manifest.name = name;
|
|
136
|
+
const extension = manifest.extensions?.[identityNamespace];
|
|
137
|
+
if (extension) {
|
|
138
|
+
extension.id = `local/${name}`;
|
|
139
|
+
delete extension.renamedFrom;
|
|
140
|
+
delete extension.supersedes;
|
|
141
|
+
}
|
|
142
|
+
return JSON.stringify(manifest, null, 2) + '\n';
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Fork a captured Agent Plugin package with one exact text edit and a fresh identity.
|
|
146
|
+
* Uses the folder upload reader/encoder; never writes into an immutable installed package.
|
|
147
|
+
* @param required Source folder, byte ceiling, new local id, host namespace and literal edit.
|
|
148
|
+
* @returns ZIP bytes for the existing upload preview/installer. Does not grant bundled trust.
|
|
149
|
+
* @throws {FolderZipError} Unsafe/missing paths, links, non-unique text, identity or size refusal.
|
|
150
|
+
* @complexity O(e log e + b) time and O(e + b) space, bounded by upload entry/byte ceilings.
|
|
151
|
+
* @example zipCustomizedAgentPluginFolder({ path, maxBytes, name: 'my-plugin', identityNamespace: 'host', edit })
|
|
152
|
+
*/
|
|
153
|
+
export async function zipCustomizedAgentPluginFolder(required, _optional = {}) {
|
|
154
|
+
const files = await collectLocalFiles(required);
|
|
155
|
+
// Match captured package paths, never join a model-supplied edit path onto the filesystem.
|
|
156
|
+
const target = files.findIndex(file => file.webkitRelativePath === `folder/${required.edit.path}`);
|
|
157
|
+
const manifest = files.findIndex(file => file.webkitRelativePath === 'folder/plugin.json');
|
|
158
|
+
if (target < 0 || manifest < 0 || target === manifest) {
|
|
159
|
+
throw new FolderZipError('unreadable', 'Choose an existing package-relative text file other than plugin.json.');
|
|
160
|
+
}
|
|
161
|
+
const text = decodeEditableText(new Uint8Array(await files[target].arrayBuffer()));
|
|
162
|
+
files[target] = replaceCapturedFile({ file: files[target], text: customizeText({ text, edit: required.edit }) });
|
|
163
|
+
files[manifest] = replaceCapturedFile({ file: files[manifest], text: customizeManifest({ text: await files[manifest].text(), name: required.name, identityNamespace: required.identityNamespace }) });
|
|
164
|
+
return encodeLocalFiles({ files, maxBytes: required.maxBytes });
|
|
165
|
+
}
|
|
84
166
|
//# sourceMappingURL=folder-zip-node.js.map
|
|
@@ -87,10 +87,10 @@ export class AgentPluginActivationsBusyError extends Error {
|
|
|
87
87
|
holder;
|
|
88
88
|
lost;
|
|
89
89
|
constructor({ lockPath, holder, lost }) {
|
|
90
|
+
const holderNote = holder !== undefined ? ` — held by pid ${holder.pid} on ${holder.hostname} since ${holder.acquiredAt}` : " — holder unknown";
|
|
90
91
|
super((lost
|
|
91
92
|
? `agent-plugin activation: lost the write lock (${lockPath}) before committing — another process judged it stale`
|
|
92
|
-
: `agent-plugin activation: could not take the write lock (${lockPath}) within 15s` +
|
|
93
|
-
(holder !== undefined ? ` — held by pid ${holder.pid} on ${holder.hostname} since ${holder.acquiredAt}` : " — holder unknown")) +
|
|
93
|
+
: `agent-plugin activation: could not take the write lock (${lockPath}) within 15s` + holderNote) +
|
|
94
94
|
". Nothing was written, so no recorded enable/disable decision was changed. This clears by itself when the other " +
|
|
95
95
|
"host process (server, agent daemon, or the agent-plugin:activation CLI) finishes; a lock left by a crashed " +
|
|
96
96
|
"process is removed automatically once that process is gone or the lock is older than 10s. If it persists, stop " +
|
|
@@ -116,20 +116,25 @@ export function createAutomaticAgentPluginUse({ mode, state: initialState, ports
|
|
|
116
116
|
*/
|
|
117
117
|
async function resolveGuidance({ matches }, { signal } = {}) {
|
|
118
118
|
const guidance = [];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
break;
|
|
119
|
+
// Unloaded plugins contribute nothing; oversized guidance becomes a pointer instead of the text.
|
|
120
|
+
const guidanceFor = async (match) => {
|
|
122
121
|
const result = await resolveOneGuidance({ plugin: match }, { signal });
|
|
123
122
|
if (result.status !== 'loaded')
|
|
124
|
-
|
|
123
|
+
return undefined;
|
|
125
124
|
if (new TextEncoder().encode(result.guidance).length > MAX_AUTOMATIC_GUIDANCE_BYTES) {
|
|
126
125
|
const pointer = ports.formatPluginToolPointer?.({ pluginId: match.pluginId, skill: match.pluginId, file: 'SKILL.md' });
|
|
127
126
|
const howToLoad = pointer === undefined ? '' : ` Load the full instructions with ${pointer}; use the same call with file '<relative path>' for supporting references.`;
|
|
128
|
-
|
|
129
|
-
continue;
|
|
127
|
+
return `Agent Plugin '${match.pluginId}' guidance is deferred because it exceeds the automatic byte limit.${howToLoad}`;
|
|
130
128
|
}
|
|
131
129
|
recordDelivery({ plugin: match, result });
|
|
132
|
-
|
|
130
|
+
return result.guidance;
|
|
131
|
+
};
|
|
132
|
+
for (const match of matches) {
|
|
133
|
+
if (signal?.aborted)
|
|
134
|
+
break;
|
|
135
|
+
const text = await guidanceFor(match);
|
|
136
|
+
if (text !== undefined)
|
|
137
|
+
guidance.push(text);
|
|
133
138
|
}
|
|
134
139
|
return guidance;
|
|
135
140
|
}
|
|
@@ -115,14 +115,9 @@ function buildModule(ports) {
|
|
|
115
115
|
return EMPTY_LEDGER;
|
|
116
116
|
const digests = new Map();
|
|
117
117
|
for (const [pluginId, entry] of Object.entries(value.plugins)) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
continue;
|
|
122
|
-
const { archiveDigest } = entry;
|
|
123
|
-
if (typeof archiveDigest !== "string" || !SHA256_DIGEST_PATTERN.test(archiveDigest))
|
|
124
|
-
continue;
|
|
125
|
-
digests.set(pluginId, archiveDigest);
|
|
118
|
+
const archiveDigest = SAFE_PLUGIN_ID_PATTERN.test(pluginId) && isPlainObject(entry) ? entry.archiveDigest : undefined;
|
|
119
|
+
if (typeof archiveDigest === "string" && SHA256_DIGEST_PATTERN.test(archiveDigest))
|
|
120
|
+
digests.set(pluginId, archiveDigest);
|
|
126
121
|
}
|
|
127
122
|
return digests;
|
|
128
123
|
}
|
|
@@ -13,10 +13,16 @@ export interface PackedAgentPluginArchive {
|
|
|
13
13
|
}
|
|
14
14
|
/** Opt-in source selection, resource bounds and archive identity. Omission preserves bundled-v1. */
|
|
15
15
|
export interface PackAgentPluginDirectoryOptional {
|
|
16
|
-
/** Exact package-relative files or directory subtrees. Empty selects nothing
|
|
16
|
+
/** Exact package-relative files or directory subtrees. Empty selects nothing. An explicit path
|
|
17
|
+
* and its ancestors override excludeNames; excluded descendants of a selected subtree still skip.
|
|
18
|
+
* Suffix exclusions and default test skips win even over explicit paths. */
|
|
17
19
|
readonly allowPaths?: readonly string[];
|
|
18
|
-
/** Entry basenames excluded at every depth
|
|
20
|
+
/** Entry basenames excluded at every depth unless explicitly allowed (or needed to reach one). */
|
|
19
21
|
readonly excludeNames?: readonly string[];
|
|
22
|
+
/** Case-sensitive path suffixes excluded at every depth, including directories and explicit paths. */
|
|
23
|
+
readonly excludeSuffixes?: readonly string[];
|
|
24
|
+
/** Include __tests__ directories and .test./.spec. files. Defaults to false; other filters still apply. */
|
|
25
|
+
readonly includeTests?: boolean;
|
|
20
26
|
/** Nonnegative safe integers; root depth is zero. Supplied bounds fail rather than truncate. */
|
|
21
27
|
readonly limits?: {
|
|
22
28
|
readonly maxEntries: number;
|
|
@@ -262,10 +262,11 @@ function buildModule(ports) {
|
|
|
262
262
|
}
|
|
263
263
|
/**
|
|
264
264
|
* Lists runtime regular files under `dir`, as POSIX-style package-relative paths.
|
|
265
|
-
*
|
|
265
|
+
* By default, test directories and test/spec files are excluded so test-only edits cannot change installed
|
|
266
266
|
* runtime contents or their content-addressed digest.
|
|
267
267
|
*
|
|
268
|
-
* @complexity O(e * a) selection checks for e
|
|
268
|
+
* @complexity O(e * (a + s)) selection checks for e entries, a allowlist paths and s suffixes.
|
|
269
|
+
* Path comparison cost scales with path length; selection uses O(1) auxiliary space per entry.
|
|
269
270
|
*/
|
|
270
271
|
async function listRegularFiles({ dir, prefix, depth, state }) {
|
|
271
272
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -274,9 +275,8 @@ function buildModule(ports) {
|
|
|
274
275
|
const files = [];
|
|
275
276
|
for (const entry of entries) {
|
|
276
277
|
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (!selectPath({ relative, directory: entry.isDirectory(), state }))
|
|
278
|
+
const directory = entry.isDirectory();
|
|
279
|
+
if (isNameExcluded({ name: entry.name, relative, directory, state }) || !selectPath({ relative, directory, state }))
|
|
280
280
|
continue;
|
|
281
281
|
if (entry.isDirectory()) {
|
|
282
282
|
files.push(...await directoryEntryFiles({ name: entry.name, dir, relative, depth, state }));
|
|
@@ -289,13 +289,13 @@ function buildModule(ports) {
|
|
|
289
289
|
return files;
|
|
290
290
|
}
|
|
291
291
|
async function directoryEntryFiles({ name, dir, relative, depth, state }) {
|
|
292
|
-
if (name === '__tests__')
|
|
292
|
+
if (name === '__tests__' && !state.options.includeTests)
|
|
293
293
|
return [];
|
|
294
294
|
assertPackingLimit({ state, name: 'maxDepth', value: depth + 1 });
|
|
295
295
|
return listRegularFiles({ dir: path.join(dir, name), prefix: relative, depth: depth + 1, state });
|
|
296
296
|
}
|
|
297
297
|
function regularEntryFiles({ entry, relative, state }) {
|
|
298
|
-
if (entry.isFile() && !/\.(test|spec)\./.test(entry.name)) {
|
|
298
|
+
if (entry.isFile() && (state.options.includeTests || !/\.(test|spec)\./.test(entry.name))) {
|
|
299
299
|
recordFile({ relative, state });
|
|
300
300
|
return [relative];
|
|
301
301
|
}
|
|
@@ -307,8 +307,17 @@ function buildModule(ports) {
|
|
|
307
307
|
state.files.add(relative);
|
|
308
308
|
assertPackingLimit({ state, name: 'maxFiles', value: state.files.size });
|
|
309
309
|
}
|
|
310
|
-
/**
|
|
310
|
+
/** An exact allow-path reopens excluded ancestors without reopening unrelated siblings.
|
|
311
|
+
* A selected subtree does not implicitly override name exclusions farther down that subtree.
|
|
312
|
+
* @complexity O(a * p) time for a allow-paths of length p, O(1) space. No I/O or mutation. */
|
|
313
|
+
function isNameExcluded({ name, relative, directory, state }) {
|
|
314
|
+
return state.excluded.has(name) && !state.allowPaths?.some(allowed => relative === allowed || (directory && allowed.startsWith(`${relative}/`)));
|
|
315
|
+
}
|
|
316
|
+
/** Ancestors of an allowed path must be traversed, but their unrelated siblings are pruned.
|
|
317
|
+
* Suffix exclusions always win. O((a + s) * p) time, O(1) space; no I/O or mutation. */
|
|
311
318
|
function selectPath({ relative, directory, state }) {
|
|
319
|
+
if (state.options.excludeSuffixes?.some(suffix => relative.endsWith(suffix)))
|
|
320
|
+
return false;
|
|
312
321
|
return state.allowPaths === undefined || state.allowPaths.some(allowed => relative === allowed || relative.startsWith(`${allowed}/`) || (directory && allowed.startsWith(`${relative}/`)));
|
|
313
322
|
}
|
|
314
323
|
function refuseEntry({ state, entryPath, symlink }) {
|
|
@@ -49,12 +49,11 @@ export function createPluginContributionLoader(required, _optional = {}) {
|
|
|
49
49
|
refusals.push(verdict.refusal);
|
|
50
50
|
continue;
|
|
51
51
|
}
|
|
52
|
+
// A host package policy refusal stands in for the plugin's contributions.
|
|
52
53
|
const refusal = packageRefusal?.({ plugin: verdict.trusted });
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
const loaded = await loadPluginContributionsFromSource({ plugin: verdict.trusted, definition });
|
|
54
|
+
const loaded = refusal === undefined
|
|
55
|
+
? await loadPluginContributionsFromSource({ plugin: verdict.trusted, definition })
|
|
56
|
+
: { items: [], refusals: [refusal] };
|
|
58
57
|
items.push(...loaded.items);
|
|
59
58
|
refusals.push(...loaded.refusals);
|
|
60
59
|
}
|
|
@@ -7,7 +7,6 @@ import { maxAgentPluginArchiveBytes } from './fetch-archive.js';
|
|
|
7
7
|
import type { FetchAgentPluginArchiveOptional } from './fetch-archive.js';
|
|
8
8
|
import type { PackAgentPluginDirectoryOptional } from './bundled-source-archive.js';
|
|
9
9
|
import type { AgentPluginLifecycleRequired, AgentPluginLifecycleOptional } from './ports.js';
|
|
10
|
-
import type { AgentPluginDeliveryMode } from './resolve-agent-plugin-refs.js';
|
|
11
10
|
import type { InstalledDigestIdentity } from './bundled-digests.js';
|
|
12
11
|
export type { SeededAgentPluginOutcome, SeedBundledAgentPluginsRequired, SeedBundledAgentPluginsOptional, SeedBundledAgentPluginsResult, PackingForSource } from './seed-bundled.js';
|
|
13
12
|
export type { RetireBundledAgentPluginsRequired, RetireBundledAgentPluginsOptional, RetiredAgentPluginOutcome, RetiredAgentPluginSuccessorOutcome } from './retire-bundled.js';
|
|
@@ -167,7 +166,7 @@ export declare function createAgentPluginLifecycle(required: AgentPluginLifecycl
|
|
|
167
166
|
pluginRefIds: readonly string[];
|
|
168
167
|
workspaceLayout: Pick<import("./layout.js").AgentPluginWorkspaceLayout, "packages" | "root">;
|
|
169
168
|
}, { deliveryMode, ...request }?: import("./resolve-agent-plugin-refs.js").AgentPluginSkillRequest & {
|
|
170
|
-
deliveryMode?: AgentPluginDeliveryMode;
|
|
169
|
+
deliveryMode?: import("./types.js").AgentPluginDeliveryMode;
|
|
171
170
|
}) => Promise<import("./resolve-agent-plugin-refs.js").ResolveAgentPluginRefsResult>;
|
|
172
171
|
pluginMemory: (input: {
|
|
173
172
|
readonly workspaceId: string;
|