@jini-ai/agent-plugins 0.4.7 → 0.6.10
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 +24 -2
- package/README.md +46 -0
- package/dist/admin-surfaces/action-dispatcher.d.ts +37 -0
- package/dist/admin-surfaces/action-dispatcher.d.ts.map +1 -0
- package/dist/admin-surfaces/action-dispatcher.js +69 -0
- package/dist/admin-surfaces/action-dispatcher.js.map +1 -0
- package/dist/admin-surfaces/action-schema.d.ts +56 -0
- package/dist/admin-surfaces/action-schema.d.ts.map +1 -0
- package/dist/admin-surfaces/action-schema.js +167 -0
- package/dist/admin-surfaces/action-schema.js.map +1 -0
- package/dist/admin-surfaces/action-tools.d.ts +40 -0
- package/dist/admin-surfaces/action-tools.d.ts.map +1 -0
- package/dist/admin-surfaces/action-tools.js +52 -0
- package/dist/admin-surfaces/action-tools.js.map +1 -0
- package/dist/admin-surfaces/callback-field-inbox.d.ts +35 -0
- package/dist/admin-surfaces/callback-field-inbox.d.ts.map +1 -0
- package/dist/admin-surfaces/callback-field-inbox.js +53 -0
- package/dist/admin-surfaces/callback-field-inbox.js.map +1 -0
- package/dist/admin-surfaces/capability-declarations.d.ts +41 -0
- package/dist/admin-surfaces/capability-declarations.d.ts.map +1 -0
- package/dist/admin-surfaces/capability-declarations.js +102 -0
- package/dist/admin-surfaces/capability-declarations.js.map +1 -0
- package/dist/admin-surfaces/contribution.d.ts +49 -0
- package/dist/admin-surfaces/contribution.d.ts.map +1 -0
- package/dist/admin-surfaces/contribution.js +30 -0
- package/dist/admin-surfaces/contribution.js.map +1 -0
- package/dist/admin-surfaces/delivery-bindings.d.ts +76 -0
- package/dist/admin-surfaces/delivery-bindings.d.ts.map +1 -0
- package/dist/admin-surfaces/delivery-bindings.js +99 -0
- package/dist/admin-surfaces/delivery-bindings.js.map +1 -0
- package/dist/admin-surfaces/descriptor.d.ts +69 -0
- package/dist/admin-surfaces/descriptor.d.ts.map +1 -0
- package/dist/admin-surfaces/descriptor.js +196 -0
- package/dist/admin-surfaces/descriptor.js.map +1 -0
- package/dist/admin-surfaces/external-links.d.ts +45 -0
- package/dist/admin-surfaces/external-links.d.ts.map +1 -0
- package/dist/admin-surfaces/external-links.js +70 -0
- package/dist/admin-surfaces/external-links.js.map +1 -0
- package/dist/admin-surfaces/host-validator.d.ts +62 -0
- package/dist/admin-surfaces/host-validator.d.ts.map +1 -0
- package/dist/admin-surfaces/host-validator.js +87 -0
- package/dist/admin-surfaces/host-validator.js.map +1 -0
- package/dist/admin-surfaces/index.d.ts +23 -0
- package/dist/admin-surfaces/index.d.ts.map +1 -0
- package/dist/admin-surfaces/index.js +21 -0
- package/dist/admin-surfaces/index.js.map +1 -0
- package/dist/admin-surfaces/protocol.d.ts +103 -0
- package/dist/admin-surfaces/protocol.d.ts.map +1 -0
- package/dist/admin-surfaces/protocol.js +25 -0
- package/dist/admin-surfaces/protocol.js.map +1 -0
- package/dist/admin-surfaces/sandbox.d.ts +78 -0
- package/dist/admin-surfaces/sandbox.d.ts.map +1 -0
- package/dist/admin-surfaces/sandbox.js +92 -0
- package/dist/admin-surfaces/sandbox.js.map +1 -0
- package/dist/admin-surfaces/settings-value.d.ts +28 -0
- package/dist/admin-surfaces/settings-value.d.ts.map +1 -0
- package/dist/admin-surfaces/settings-value.js +45 -0
- package/dist/admin-surfaces/settings-value.js.map +1 -0
- package/dist/admin-surfaces/surface-capabilities.d.ts +167 -0
- package/dist/admin-surfaces/surface-capabilities.d.ts.map +1 -0
- package/dist/admin-surfaces/surface-capabilities.js +215 -0
- package/dist/admin-surfaces/surface-capabilities.js.map +1 -0
- package/dist/admin-surfaces/surface-client.d.ts +116 -0
- package/dist/admin-surfaces/surface-client.d.ts.map +1 -0
- package/dist/admin-surfaces/surface-client.js +219 -0
- package/dist/admin-surfaces/surface-client.js.map +1 -0
- package/dist/admin-surfaces/surface-handlers.d.ts +111 -0
- package/dist/admin-surfaces/surface-handlers.d.ts.map +1 -0
- package/dist/admin-surfaces/surface-handlers.js +172 -0
- package/dist/admin-surfaces/surface-handlers.js.map +1 -0
- package/dist/admin-surfaces/theme.d.ts +59 -0
- package/dist/admin-surfaces/theme.d.ts.map +1 -0
- package/dist/admin-surfaces/theme.js +57 -0
- package/dist/admin-surfaces/theme.js.map +1 -0
- package/dist/folder-zip-node.d.ts.map +1 -0
- package/dist/folder-zip-node.js +59 -49
- package/dist/folder-zip-node.js.map +1 -0
- package/dist/folder-zip.d.ts.map +1 -0
- package/dist/folder-zip.js +9 -7
- package/dist/folder-zip.js.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/activation.d.ts.map +1 -0
- package/dist/lifecycle/activation.js.map +1 -0
- package/dist/lifecycle/automatic-use.d.ts +79 -0
- package/dist/lifecycle/automatic-use.d.ts.map +1 -0
- package/dist/lifecycle/automatic-use.js +165 -0
- package/dist/lifecycle/automatic-use.js.map +1 -0
- package/dist/lifecycle/bundled-digests.d.ts.map +1 -0
- package/dist/lifecycle/bundled-digests.js.map +1 -0
- package/dist/lifecycle/bundled-source-archive.d.ts.map +1 -0
- package/dist/lifecycle/bundled-source-archive.js.map +1 -0
- package/dist/lifecycle/capability-projection.d.ts.map +1 -0
- package/dist/lifecycle/capability-projection.js.map +1 -0
- package/dist/lifecycle/compatibility-metadata.d.ts +19 -0
- package/dist/lifecycle/compatibility-metadata.d.ts.map +1 -0
- package/dist/lifecycle/compatibility-metadata.js +45 -0
- package/dist/lifecycle/compatibility-metadata.js.map +1 -0
- package/dist/lifecycle/compatibility.d.ts +52 -0
- package/dist/lifecycle/compatibility.d.ts.map +1 -0
- package/dist/lifecycle/compatibility.js +60 -0
- package/dist/lifecycle/compatibility.js.map +1 -0
- package/dist/lifecycle/contribution-loader.d.ts.map +1 -0
- package/dist/lifecycle/contribution-loader.js.map +1 -0
- package/dist/lifecycle/fetch-archive.d.ts.map +1 -0
- package/dist/lifecycle/fetch-archive.js +26 -24
- package/dist/lifecycle/fetch-archive.js.map +1 -0
- package/dist/lifecycle/index.d.ts +14 -2
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +8 -1
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/lifecycle/install-errors.d.ts.map +1 -0
- package/dist/lifecycle/install-errors.js.map +1 -0
- package/dist/lifecycle/install-from-url.d.ts.map +1 -0
- package/dist/lifecycle/install-from-url.js.map +1 -0
- package/dist/lifecycle/install.d.ts.map +1 -0
- package/dist/lifecycle/install.js +7 -0
- package/dist/lifecycle/install.js.map +1 -0
- package/dist/lifecycle/installed-package-index.d.ts.map +1 -0
- package/dist/lifecycle/installed-package-index.js +41 -29
- package/dist/lifecycle/installed-package-index.js.map +1 -0
- package/dist/lifecycle/installed-package-roots.d.ts.map +1 -0
- package/dist/lifecycle/installed-package-roots.js +28 -20
- package/dist/lifecycle/installed-package-roots.js.map +1 -0
- package/dist/lifecycle/layout.d.ts.map +1 -0
- package/dist/lifecycle/layout.js.map +1 -0
- package/dist/lifecycle/manifest.d.ts +13 -0
- package/dist/lifecycle/manifest.d.ts.map +1 -0
- package/dist/lifecycle/manifest.js +115 -101
- package/dist/lifecycle/manifest.js.map +1 -0
- package/dist/lifecycle/mcp-provisioning.d.ts.map +1 -0
- package/dist/lifecycle/mcp-provisioning.js.map +1 -0
- package/dist/lifecycle/node.d.ts.map +1 -0
- package/dist/lifecycle/node.js.map +1 -0
- package/dist/lifecycle/package-paths.d.ts.map +1 -0
- package/dist/lifecycle/package-paths.js +3 -3
- package/dist/lifecycle/package-paths.js.map +1 -0
- package/dist/lifecycle/persistent-state.d.ts.map +1 -0
- package/dist/lifecycle/persistent-state.js.map +1 -0
- package/dist/lifecycle/ports.d.ts +1 -1
- package/dist/lifecycle/ports.d.ts.map +1 -0
- package/dist/lifecycle/ports.js.map +1 -0
- package/dist/lifecycle/provider-contribution.d.ts +65 -0
- package/dist/lifecycle/provider-contribution.d.ts.map +1 -0
- package/dist/lifecycle/provider-contribution.js +93 -0
- package/dist/lifecycle/provider-contribution.js.map +1 -0
- package/dist/lifecycle/resolve-agent-plugin-refs.d.ts.map +1 -0
- package/dist/lifecycle/resolve-agent-plugin-refs.js +15 -2
- package/dist/lifecycle/resolve-agent-plugin-refs.js.map +1 -0
- package/dist/lifecycle/retire-bundled.d.ts.map +1 -0
- package/dist/lifecycle/retire-bundled.js +20 -18
- package/dist/lifecycle/retire-bundled.js.map +1 -0
- package/dist/lifecycle/search.d.ts +6 -1
- package/dist/lifecycle/search.d.ts.map +1 -0
- package/dist/lifecycle/search.js +45 -7
- package/dist/lifecycle/search.js.map +1 -0
- package/dist/lifecycle/seed-bundled.d.ts.map +1 -0
- package/dist/lifecycle/seed-bundled.js.map +1 -0
- package/dist/lifecycle/set-enabled.d.ts +2 -1
- package/dist/lifecycle/set-enabled.d.ts.map +1 -0
- package/dist/lifecycle/set-enabled.js +52 -8
- package/dist/lifecycle/set-enabled.js.map +1 -0
- package/dist/lifecycle/skill-file-reader.d.ts.map +1 -0
- package/dist/lifecycle/skill-file-reader.js +85 -71
- package/dist/lifecycle/skill-file-reader.js.map +1 -0
- package/dist/lifecycle/tools/capabilities.d.ts +199 -0
- package/dist/lifecycle/tools/capabilities.d.ts.map +1 -0
- package/dist/lifecycle/tools/capabilities.js +213 -0
- package/dist/lifecycle/tools/capabilities.js.map +1 -0
- package/dist/lifecycle/tools/definition.d.ts +32 -0
- package/dist/lifecycle/tools/definition.d.ts.map +1 -0
- package/dist/lifecycle/tools/definition.js +33 -0
- package/dist/lifecycle/tools/definition.js.map +1 -0
- package/dist/lifecycle/tools/index.d.ts +10 -0
- package/dist/lifecycle/tools/index.d.ts.map +1 -0
- package/dist/lifecycle/tools/index.js +10 -0
- package/dist/lifecycle/tools/index.js.map +1 -0
- package/dist/lifecycle/tools/manifest.d.ts +33 -0
- package/dist/lifecycle/tools/manifest.d.ts.map +1 -0
- package/dist/lifecycle/tools/manifest.js +89 -0
- package/dist/lifecycle/tools/manifest.js.map +1 -0
- package/dist/lifecycle/tools/projection.d.ts +90 -0
- package/dist/lifecycle/tools/projection.d.ts.map +1 -0
- package/dist/lifecycle/tools/projection.js +283 -0
- package/dist/lifecycle/tools/projection.js.map +1 -0
- package/dist/lifecycle/trusted-plugin-files.d.ts.map +1 -0
- package/dist/lifecycle/trusted-plugin-files.js.map +1 -0
- package/dist/lifecycle/types.d.ts +2 -0
- package/dist/lifecycle/types.d.ts.map +1 -0
- package/dist/lifecycle/types.js.map +1 -0
- package/dist/lifecycle/uninstall.d.ts +3 -1
- package/dist/lifecycle/uninstall.d.ts.map +1 -0
- package/dist/lifecycle/uninstall.js +51 -27
- package/dist/lifecycle/uninstall.js.map +1 -0
- package/dist/lifecycle/update-plan.d.ts +18 -0
- package/dist/lifecycle/update-plan.d.ts.map +1 -0
- package/dist/lifecycle/update-plan.js +22 -0
- package/dist/lifecycle/update-plan.js.map +1 -0
- package/dist/lifecycle/update-store.d.ts +71 -0
- package/dist/lifecycle/update-store.d.ts.map +1 -0
- package/dist/lifecycle/update-store.js +150 -0
- package/dist/lifecycle/update-store.js.map +1 -0
- package/dist/lifecycle/update.d.ts +55 -0
- package/dist/lifecycle/update.d.ts.map +1 -0
- package/dist/lifecycle/update.js +321 -0
- package/dist/lifecycle/update.js.map +1 -0
- package/dist/lifecycle/yauzl-archive-reader.d.ts.map +1 -0
- package/dist/lifecycle/yauzl-archive-reader.js.map +1 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js.map +1 -0
- package/package.json +39 -7
- package/ui-ux-design/plugin.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.10
|
|
4
|
+
|
|
5
|
+
## 0.4.8 — 2026-10-22
|
|
6
|
+
|
|
7
|
+
- 2026-10-22: Provider contributions refuse failures by default (core peer floor ^0.4.9), durable update transaction for operator-installed plugins, coverage.
|
|
8
|
+
|
|
9
|
+
- `./lifecycle`: `defineProviderContribution()` wraps `defineExecutablePluginContribution` so a bundled plugin can contribute a provider for `@jini-ai/core/provider-registry` (id checked before import), and `contributeProviders()` merges loader and registry refusals. Needs `@jini-ai/core` with `./provider-registry`: the peer range is raised to `^0.4.9` (the first core release carrying that subpath), so release core 0.4.9 before this. Without recovery policies, an unreadable manifest, a throwing module/validator/parser or a hostile descriptor is now a refusal line (never a throw), so one broken plugin no longer discards the healthy contributions loaded with it.
|
|
10
|
+
|
|
11
|
+
## Unreleased — compatibility and advisory requirements
|
|
12
|
+
|
|
13
|
+
- Uses `semver`, the same library as site plugins (`@jini-ai/plugins` checks `sdkRange` with
|
|
14
|
+
`semver.satisfies`), for `hostRange` and update ordering. Declared the way site plugins declare it
|
|
15
|
+
(optional peer + dev dependency, as `pnpm guard`'s subpath isolation requires): hosts that parse
|
|
16
|
+
manifests or use `./lifecycle` must install `semver@^7.6.2`.
|
|
17
|
+
- `extensions.<ns>.hostRange` is checked against `AGENT_PLUGIN_CONTRACT_VERSION` (the host's Agent
|
|
18
|
+
Plugin contract, not an app version). A malformed range is listable with a parse warning but is
|
|
19
|
+
incompatible: `setAgentPluginEnabled({ enabled: true })` throws `AgentPluginHostIncompatibleError`
|
|
20
|
+
(`PLUGIN_HOST_INCOMPATIBLE`). Disabling is never refused.
|
|
21
|
+
- `extensions.<ns>.requires` (capability ids) is advisory and computed live from other active
|
|
22
|
+
plugins: enabling returns `warnings: ['Needs <cap>']`; disabling a provider returns
|
|
23
|
+
`'<dependent> needs <cap>'`. Never blocks.
|
|
24
|
+
- `setAgentPluginEnabled` now runs under the per-plugin state lock shared with install/update/uninstall.
|
|
25
|
+
- `planAgentPluginUpdate()`: manifest semver orders releases, the digest identifies bytes.
|
|
26
|
+
|
|
3
27
|
## 0.4.7 — 2026-10-15 — folder uploads share one ZIP encoder
|
|
4
28
|
|
|
5
29
|
- New `./folder-zip` (universal): `zipFolderFiles()`, the binary-safe stored-mode folder encoder,
|
|
@@ -66,7 +90,6 @@
|
|
|
66
90
|
rationale alongside the canonical implementations; move the URL-install real-HTTP/ZIP
|
|
67
91
|
integration suite into the package and add paired metadata-translation regression cases.
|
|
68
92
|
|
|
69
|
-
|
|
70
93
|
## Unreleased — lifecycle test compilation
|
|
71
94
|
|
|
72
95
|
- Preserve copied test timeouts and conditional skips through Vitest registration.
|
|
@@ -94,7 +117,6 @@
|
|
|
94
117
|
- Native liveness treats only ESRCH as death; unknown probe failures never authorize stale-owner eviction.
|
|
95
118
|
- Archive-error specification now matches safe generic transport/body causes and redacted URL diagnostics.
|
|
96
119
|
|
|
97
|
-
|
|
98
120
|
## Unreleased
|
|
99
121
|
|
|
100
122
|
- Add the host-injected plugin lifecycle on `./lifecycle`: archive installation, activation and locks, bundled digests and seeding/retirement, trusted files, reference resolution and local ranking.
|
package/README.md
CHANGED
|
@@ -108,11 +108,40 @@ Catalog format) that this package does not implement.
|
|
|
108
108
|
| `./lifecycle/yauzl` | node | `createYauzlAgentPluginArchiveReader({ yauzl })`; the host supplies the optional peer library. |
|
|
109
109
|
| `./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
110
|
| `./folder-zip/node` | node | `zipLocalFolder({ path, maxBytes })`: the same encoder over a local folder; refuses symlinks/special files, bounds entries and bytes. |
|
|
111
|
+
| `./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
|
+
| `./admin-surfaces/client` | universal | `createAdminSurfaceClient(...)`: the self-contained surface-side protocol client a plugin's UI imports or copies. |
|
|
111
113
|
| `./ui-ux-design/*` | — | Raw files of the bundled `ui-ux-design` plugin — static JSON/Markdown, not run through the TS build. |
|
|
112
114
|
|
|
113
115
|
The universal root handles structural validation. The separate Node lifecycle entry implements
|
|
114
116
|
local archive installation and host-owned activation policy; it does not implement remote catalog discovery.
|
|
115
117
|
|
|
118
|
+
## Admin surfaces
|
|
119
|
+
|
|
120
|
+
A plugin can ship its own admin UI as static files the host renders in an
|
|
121
|
+
`<iframe sandbox="allow-scripts">` (never `allow-same-origin`). The surface has no server access:
|
|
122
|
+
it posts `action-request` messages, the host page checks origin (`"null"` for a sandboxed frame),
|
|
123
|
+
frame window, per-mount nonce, declared action and input schema, and forwards the request to a host
|
|
124
|
+
route whose dispatcher runs a host-provided handler. Full format, protocol and API:
|
|
125
|
+
[`src/admin-surfaces/README.md`](src/admin-surfaces/README.md).
|
|
126
|
+
|
|
127
|
+
Contribution file (the host names it, e.g. `<product>-admin-surfaces.json`, and passes it to
|
|
128
|
+
`defineAdminSurfaceContribution({ filename })`):
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{ "schemaVersion": 1, "surfaces": [ {
|
|
132
|
+
"id": "connection-settings", "title": "Connection settings",
|
|
133
|
+
"entry": "surface/index.html", "files": ["surface/app.mjs"],
|
|
134
|
+
"actions": [ { "name": "save-selection", "input": { "type": "object", "properties": { "resourceId": { "type": "string" } }, "required": ["resourceId"] } } ],
|
|
135
|
+
"csp": { "connect-src": ["https://api.example.com"] }
|
|
136
|
+
} ] }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Messages (all carry `protocol: "jini.admin-surface", version: 1`):
|
|
140
|
+
`init { mountId, bootstrap? }` host → frame once; `action-request { mountId, requestId, action, payload }`
|
|
141
|
+
frame → host; `action-response { mountId, requestId, ok, result | error: { code, message, retryable } }`
|
|
142
|
+
host → frame. Bootstrap data is short-lived (`expiresAt`, one minute) and served with
|
|
143
|
+
`Cache-Control: no-store`.
|
|
144
|
+
|
|
116
145
|
## Not this package: `@jini-ai/plugins` (`./host`)
|
|
117
146
|
|
|
118
147
|
`@jini-ai/plugins` is a **different, sibling package** — Jini's own host-extension plugin format
|
|
@@ -280,3 +309,20 @@ as plugin ids so package state cannot overlap migration inputs or installation s
|
|
|
280
309
|
|
|
281
310
|
The `dev.tovu.memory` proposal is consumer policy and is not implemented by this neutral entry.
|
|
282
311
|
No version bump or publishing accompanies this workspace implementation.
|
|
312
|
+
|
|
313
|
+
Admin-screen actions can also be assistant tools. Declare `effect: "read" | "write" |
|
|
314
|
+
"destructive"` on each action in the host-named admin-surfaces contribution file;
|
|
315
|
+
omitting it means `write`. `buildAdminSurfaceActionTools` projects every declared
|
|
316
|
+
action as `<surface>_<action>` (hyphens become underscores); pass the returned
|
|
317
|
+
`additionalTools` to `createAgentPluginToolProjection.sync` alongside executable
|
|
318
|
+
tools. The host's existing plugin registry supplies the `p_<plugin>__` namespace.
|
|
319
|
+
Calls accept `{ payload: <declared input> }`, so object and scalar actions share
|
|
320
|
+
one tool envelope. The existing screen dispatcher validates input/results and
|
|
321
|
+
calls the same admitted handler host with the same scoped capabilities.
|
|
322
|
+
|
|
323
|
+
Bind the adapter's authorization port to the screen action permission and a fresh
|
|
324
|
+
active/trusted surface lookup, then use the host's existing confirmation policy
|
|
325
|
+
for writes and destructive actions. Set discovery `readOnly`/`sideEffects` from
|
|
326
|
+
the effect. Sync after activation changes: absent actions are withdrawn and held
|
|
327
|
+
handlers are revoked. A host may collect its issued sign-in URLs around dispatch;
|
|
328
|
+
return those URLs for the assistant to show. This adapter never opens a browser.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AdminSurfaceDescriptor } from './descriptor.js';
|
|
2
|
+
import type { AdminSurfaceActionOutcome } from './protocol.js';
|
|
3
|
+
/** Thrown by a handler to send a specific, surface-safe failure. Any other throw becomes `action-failed`. */
|
|
4
|
+
export declare class AdminSurfaceActionError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly retryable: boolean;
|
|
7
|
+
constructor(required: {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
}, optional?: {
|
|
11
|
+
readonly retryable?: boolean;
|
|
12
|
+
readonly cause?: unknown;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/** A host-provided implementation of one declared action. `context` is whatever the host route
|
|
16
|
+
* established (workspace, signed-in user); the surface never supplies it. */
|
|
17
|
+
export type AdminSurfaceActionHandler<C> = (required: {
|
|
18
|
+
readonly payload: unknown;
|
|
19
|
+
readonly context: C;
|
|
20
|
+
}) => Promise<unknown>;
|
|
21
|
+
export declare function createAdminSurfaceActionDispatcher<C>(required: {
|
|
22
|
+
readonly descriptor: Pick<AdminSurfaceDescriptor, 'id' | 'actions'>;
|
|
23
|
+
readonly handlers: Readonly<Record<string, AdminSurfaceActionHandler<C>>>;
|
|
24
|
+
}, optional?: {
|
|
25
|
+
readonly onHandlerError?: (input: {
|
|
26
|
+
readonly surfaceId: string;
|
|
27
|
+
readonly action: string;
|
|
28
|
+
readonly error: unknown;
|
|
29
|
+
}) => void;
|
|
30
|
+
}): {
|
|
31
|
+
dispatch: (input: {
|
|
32
|
+
readonly action: string;
|
|
33
|
+
readonly payload: unknown;
|
|
34
|
+
readonly context: C;
|
|
35
|
+
}) => Promise<AdminSurfaceActionOutcome>;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=action-dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-dispatcher.d.ts","sourceRoot":"","sources":["../../src/admin-surfaces/action-dispatcher.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,KAAK,EAA6B,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE1F,6GAA6G;AAC7G,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAChB,QAAQ,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,QAAQ,GAAE;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAMrJ;AAED;6EAC6E;AAC7E,MAAM,MAAM,yBAAyB,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;CAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAuB9H,wBAAgB,kCAAkC,CAAC,CAAC,EAClD,QAAQ,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,sBAAsB,EAAE,IAAI,GAAG,SAAS,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;CAAE,EAC5J,QAAQ,GAAE;IAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;CAAO;sBAI/G;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,KAAG,OAAO,CAAC,yBAAyB,CAAC;EAmBhJ"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The server-side mediator between a surface's action requests and the host's handlers.
|
|
3
|
+
*
|
|
4
|
+
* Why it exists: a plugin surface never talks to the server directly; the host page forwards each
|
|
5
|
+
* validated request to a host route, and that route calls this dispatcher. It is the authoritative
|
|
6
|
+
* check (the browser-side gate can be bypassed by calling the route directly): only declared actions
|
|
7
|
+
* run, only with schema-valid input, and a handler's raw error never reaches the surface.
|
|
8
|
+
*/
|
|
9
|
+
import { exceedsAdminSurfacePayloadLimit, validateAdminSurfaceActionValue } from './action-schema.js';
|
|
10
|
+
/** Thrown by a handler to send a specific, surface-safe failure. Any other throw becomes `action-failed`. */
|
|
11
|
+
export class AdminSurfaceActionError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
retryable;
|
|
14
|
+
constructor(required, optional = {}) {
|
|
15
|
+
super(required.message, optional.cause === undefined ? undefined : { cause: optional.cause });
|
|
16
|
+
this.name = 'AdminSurfaceActionError';
|
|
17
|
+
this.code = required.code;
|
|
18
|
+
this.retryable = optional.retryable ?? false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const failure = (code, message, retryable = false) => ({ ok: false, error: { code, message, retryable } });
|
|
22
|
+
/** Maps a handler throw to a surface-safe failure. @complexity O(1). */
|
|
23
|
+
function handlerFailure(error) {
|
|
24
|
+
if (error instanceof AdminSurfaceActionError)
|
|
25
|
+
return { code: error.code, message: error.message, retryable: error.retryable };
|
|
26
|
+
return { code: 'action-failed', message: 'The action failed. Try again; if it keeps failing, check the server log.', retryable: true };
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Binds one surface's declared actions to host handlers.
|
|
30
|
+
* @param required.handlers - Keyed by action name; a declared action without a handler answers `action-unavailable`.
|
|
31
|
+
* @param optional.onHandlerError - Hears every handler throw (with its cause) for server-side logging.
|
|
32
|
+
* @complexity O(p) per dispatch in the payload and result sizes, plus the handler's own cost.
|
|
33
|
+
*/
|
|
34
|
+
/** Why `payload` cannot reach the handler (too large, or outside the action's input), or null. @complexity O(payload size). */
|
|
35
|
+
function payloadRefusal(schema, payload) {
|
|
36
|
+
if (exceedsAdminSurfacePayloadLimit({ payload }))
|
|
37
|
+
return failure('invalid-payload', 'The request is too large.');
|
|
38
|
+
const violation = validateAdminSurfaceActionValue({ schema, value: payload });
|
|
39
|
+
return violation === null ? null : failure('invalid-payload', `The request does not match the action's input: ${violation}.`);
|
|
40
|
+
}
|
|
41
|
+
export function createAdminSurfaceActionDispatcher(required, optional = {}) {
|
|
42
|
+
const { descriptor, handlers } = required;
|
|
43
|
+
/** Runs one request. Never throws for a handler fault. */
|
|
44
|
+
async function dispatch(input) {
|
|
45
|
+
const action = descriptor.actions.find((declared) => declared.name === input.action);
|
|
46
|
+
if (action === undefined)
|
|
47
|
+
return failure('action-undeclared', 'This surface did not declare that action.');
|
|
48
|
+
const handler = Object.hasOwn(handlers, action.name) ? handlers[action.name] : undefined;
|
|
49
|
+
if (handler === undefined)
|
|
50
|
+
return failure('action-unavailable', 'This action is not available on this host.');
|
|
51
|
+
const refused = payloadRefusal(action.input, input.payload);
|
|
52
|
+
if (refused !== null)
|
|
53
|
+
return refused;
|
|
54
|
+
let result;
|
|
55
|
+
try {
|
|
56
|
+
result = await handler({ payload: input.payload, context: input.context });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
optional.onHandlerError?.({ surfaceId: descriptor.id, action: action.name, error });
|
|
60
|
+
return { ok: false, error: handlerFailure(error) };
|
|
61
|
+
}
|
|
62
|
+
if (action.result !== undefined && validateAdminSurfaceActionValue({ schema: action.result, value: result }) !== null) {
|
|
63
|
+
return failure('invalid-result', 'The host returned a result this surface did not declare.');
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, result };
|
|
66
|
+
}
|
|
67
|
+
return { dispatch };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=action-dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-dispatcher.js","sourceRoot":"","sources":["../../src/admin-surfaces/action-dispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,+BAA+B,EAAE,+BAA+B,EAAE,MAAM,oBAAoB,CAAC;AAItG,6GAA6G;AAC7G,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACvC,IAAI,CAAS;IACb,SAAS,CAAU;IAC5B,YAAY,QAA6D,EAAE,WAAuE,EAAE;QAClJ,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,KAAK,CAAC;IAC/C,CAAC;CACF;AAMD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,SAAS,GAAG,KAAK,EAA6B,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;AAEtJ,wEAAwE;AACxE,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,KAAK,YAAY,uBAAuB;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;IAC9H,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,0EAA0E,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACzI,CAAC;AAED;;;;;GAKG;AACH,+HAA+H;AAC/H,SAAS,cAAc,CAAC,MAA0D,EAAE,OAAgB;IAClG,IAAI,+BAA+B,CAAC,EAAE,OAAO,EAAE,CAAC;QAAE,OAAO,OAAO,CAAC,iBAAiB,EAAE,2BAA2B,CAAC,CAAC;IACjH,MAAM,SAAS,GAAG,+BAA+B,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9E,OAAO,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,kDAAkD,SAAS,GAAG,CAAC,CAAC;AAChI,CAAC;AAED,MAAM,UAAU,kCAAkC,CAChD,QAA4J,EAC5J,WAA4I,EAAE;IAE9I,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;IAC1C,0DAA0D;IAC1D,KAAK,UAAU,QAAQ,CAAC,KAAkF;QACxG,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC;QACrF,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,mBAAmB,EAAE,2CAA2C,CAAC,CAAC;QAC3G,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzF,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,oBAAoB,EAAE,4CAA4C,CAAC,CAAC;QAC9G,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC;QACrC,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YAAC,MAAM,GAAG,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAAC,CAAC;QACnF,OAAO,KAAK,EAAE,CAAC;YACb,QAAQ,CAAC,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACpF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QACrD,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,+BAA+B,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;YACtH,OAAO,OAAO,CAAC,gBAAgB,EAAE,0DAA0D,CAAC,CAAC;QAC/F,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC9B,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file A deliberately small, closed subset of JSON Schema for admin-surface action inputs and
|
|
3
|
+
* results: checked once when the descriptor is parsed, then used to validate every payload.
|
|
4
|
+
*
|
|
5
|
+
* Why not full JSON Schema: a surface's payload crosses a trust boundary (plugin UI -> host
|
|
6
|
+
* handler), so the schema language is fail-closed. An unknown keyword refuses the descriptor
|
|
7
|
+
* instead of being ignored (a plugin cannot rely on `pattern` or `format` that would silently pass),
|
|
8
|
+
* objects are closed (no undeclared keys reach a handler), and every node must name its `type`.
|
|
9
|
+
* Violation messages name the path only, never the value, so a rejected payload cannot echo a secret.
|
|
10
|
+
*/
|
|
11
|
+
export type AdminSurfaceActionSchema = {
|
|
12
|
+
readonly type: 'object';
|
|
13
|
+
readonly properties?: Readonly<Record<string, AdminSurfaceActionSchema>>;
|
|
14
|
+
readonly required?: readonly string[];
|
|
15
|
+
readonly additionalProperties?: false;
|
|
16
|
+
} | {
|
|
17
|
+
readonly type: 'array';
|
|
18
|
+
readonly items: AdminSurfaceActionSchema;
|
|
19
|
+
readonly maxItems?: number;
|
|
20
|
+
} | {
|
|
21
|
+
readonly type: 'string';
|
|
22
|
+
readonly minLength?: number;
|
|
23
|
+
readonly maxLength?: number;
|
|
24
|
+
readonly enum?: readonly string[];
|
|
25
|
+
} | {
|
|
26
|
+
readonly type: 'number' | 'integer';
|
|
27
|
+
readonly minimum?: number;
|
|
28
|
+
readonly maximum?: number;
|
|
29
|
+
} | {
|
|
30
|
+
readonly type: 'boolean' | 'null';
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Why a declared schema is unusable, or `null` when it is a valid member of the subset.
|
|
34
|
+
* @complexity O(n) schema nodes; recursion bounded by {@link MAX_DEPTH}.
|
|
35
|
+
*/
|
|
36
|
+
export declare function checkAdminSurfaceActionSchema(required: {
|
|
37
|
+
readonly schema: unknown;
|
|
38
|
+
}, optional?: {
|
|
39
|
+
readonly depth?: number;
|
|
40
|
+
}): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* The first place `value` violates a schema that passed {@link checkAdminSurfaceActionSchema}, or
|
|
43
|
+
* `null` when it conforms. The message names a path such as `$.rows[2]`, never the offending value.
|
|
44
|
+
* @complexity O(v) values visited.
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateAdminSurfaceActionValue(required: {
|
|
47
|
+
readonly schema: AdminSurfaceActionSchema;
|
|
48
|
+
readonly value: unknown;
|
|
49
|
+
}, optional?: {
|
|
50
|
+
readonly path?: string;
|
|
51
|
+
}): string | null;
|
|
52
|
+
/** True when `payload` serializes to more than 64 KiB of JSON text, or cannot be serialized. @complexity O(payload size). */
|
|
53
|
+
export declare function exceedsAdminSurfacePayloadLimit(required: {
|
|
54
|
+
readonly payload: unknown;
|
|
55
|
+
}, _optional?: Record<string, never>): boolean;
|
|
56
|
+
//# sourceMappingURL=action-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-schema.d.ts","sourceRoot":"","sources":["../../src/admin-surfaces/action-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,wBAAwB,GAChC;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,KAAK,CAAA;CAAE,GACnL;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAChG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACxH;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7F;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,CAAC;AA0B1C;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,EAAE,QAAQ,GAAE;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,GAAG,IAAI,CAU/I;AAsCD;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,QAAQ,GAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,GAAG,IAAI,CAG1L;AAuDD,6HAA6H;AAC7H,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAAE,EAAE,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAM,GAAG,OAAO,CAEvI"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file A deliberately small, closed subset of JSON Schema for admin-surface action inputs and
|
|
3
|
+
* results: checked once when the descriptor is parsed, then used to validate every payload.
|
|
4
|
+
*
|
|
5
|
+
* Why not full JSON Schema: a surface's payload crosses a trust boundary (plugin UI -> host
|
|
6
|
+
* handler), so the schema language is fail-closed. An unknown keyword refuses the descriptor
|
|
7
|
+
* instead of being ignored (a plugin cannot rely on `pattern` or `format` that would silently pass),
|
|
8
|
+
* objects are closed (no undeclared keys reach a handler), and every node must name its `type`.
|
|
9
|
+
* Violation messages name the path only, never the value, so a rejected payload cannot echo a secret.
|
|
10
|
+
*/
|
|
11
|
+
const MAX_DEPTH = 8;
|
|
12
|
+
/** Applies when an array schema declares no `maxItems`, so no payload is unbounded. */
|
|
13
|
+
const DEFAULT_MAX_ITEMS = 1000;
|
|
14
|
+
const ANNOTATIONS = ['type', 'title', 'description'];
|
|
15
|
+
const KEYWORDS = {
|
|
16
|
+
object: ['properties', 'required', 'additionalProperties'],
|
|
17
|
+
array: ['items', 'maxItems'],
|
|
18
|
+
string: ['minLength', 'maxLength', 'enum'],
|
|
19
|
+
number: ['minimum', 'maximum'],
|
|
20
|
+
integer: ['minimum', 'maximum'],
|
|
21
|
+
boolean: [],
|
|
22
|
+
null: [],
|
|
23
|
+
};
|
|
24
|
+
function isPlainObject(value) {
|
|
25
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
26
|
+
return false;
|
|
27
|
+
const proto = Object.getPrototypeOf(value);
|
|
28
|
+
return proto === Object.prototype || proto === null;
|
|
29
|
+
}
|
|
30
|
+
const isCount = (value) => value === undefined || (Number.isInteger(value) && value >= 0);
|
|
31
|
+
const isBound = (value) => value === undefined || (typeof value === 'number' && Number.isFinite(value));
|
|
32
|
+
/**
|
|
33
|
+
* Why a declared schema is unusable, or `null` when it is a valid member of the subset.
|
|
34
|
+
* @complexity O(n) schema nodes; recursion bounded by {@link MAX_DEPTH}.
|
|
35
|
+
*/
|
|
36
|
+
export function checkAdminSurfaceActionSchema(required, optional = {}) {
|
|
37
|
+
const { schema } = required;
|
|
38
|
+
const depth = optional.depth ?? 0;
|
|
39
|
+
if (!isPlainObject(schema))
|
|
40
|
+
return 'must be an object';
|
|
41
|
+
if (depth > MAX_DEPTH)
|
|
42
|
+
return `nests deeper than ${MAX_DEPTH} levels`;
|
|
43
|
+
const type = schema.type;
|
|
44
|
+
if (typeof type !== 'string' || !Object.hasOwn(KEYWORDS, type))
|
|
45
|
+
return 'must declare a supported type';
|
|
46
|
+
const unknown = Object.keys(schema).find((key) => !ANNOTATIONS.includes(key) && !KEYWORDS[type].includes(key));
|
|
47
|
+
if (unknown !== undefined)
|
|
48
|
+
return `uses unsupported keyword '${unknown}'`;
|
|
49
|
+
return checkKeywords(schema, type, depth);
|
|
50
|
+
}
|
|
51
|
+
/** @complexity O(n) child schema nodes. */
|
|
52
|
+
function checkKeywords(schema, type, depth) {
|
|
53
|
+
if (type === 'object')
|
|
54
|
+
return checkObject(schema, depth);
|
|
55
|
+
if (type === 'array')
|
|
56
|
+
return checkArray(schema, depth);
|
|
57
|
+
if (type === 'string')
|
|
58
|
+
return checkString(schema);
|
|
59
|
+
if (type === 'number' || type === 'integer')
|
|
60
|
+
return isBound(schema.minimum) && isBound(schema.maximum) ? null : 'minimum/maximum must be finite numbers';
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function checkObject(schema, depth) {
|
|
64
|
+
const properties = schema.properties ?? {};
|
|
65
|
+
if (!isPlainObject(properties))
|
|
66
|
+
return 'properties must be an object';
|
|
67
|
+
if (schema.additionalProperties !== undefined && schema.additionalProperties !== false)
|
|
68
|
+
return 'additionalProperties may only be false (objects are closed)';
|
|
69
|
+
if (!listsDeclaredKeys(schema.required ?? [], properties))
|
|
70
|
+
return 'required must list declared properties';
|
|
71
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
72
|
+
const reason = checkAdminSurfaceActionSchema({ schema: child }, { depth: depth + 1 });
|
|
73
|
+
if (reason !== null)
|
|
74
|
+
return `property '${key}' ${reason}`;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const listsDeclaredKeys = (required, properties) => Array.isArray(required) && required.every((key) => typeof key === 'string' && Object.hasOwn(properties, key));
|
|
79
|
+
function checkArray(schema, depth) {
|
|
80
|
+
if (!isCount(schema.maxItems))
|
|
81
|
+
return 'maxItems must be a non-negative integer';
|
|
82
|
+
const reason = checkAdminSurfaceActionSchema({ schema: schema.items }, { depth: depth + 1 });
|
|
83
|
+
return reason === null ? null : `items ${reason}`;
|
|
84
|
+
}
|
|
85
|
+
function checkString(schema) {
|
|
86
|
+
if (!isCount(schema.minLength) || !isCount(schema.maxLength))
|
|
87
|
+
return 'minLength/maxLength must be non-negative integers';
|
|
88
|
+
const values = schema.enum;
|
|
89
|
+
if (values === undefined)
|
|
90
|
+
return null;
|
|
91
|
+
return Array.isArray(values) && values.length > 0 && values.every((value) => typeof value === 'string') ? null : 'enum must be a non-empty list of strings';
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The first place `value` violates a schema that passed {@link checkAdminSurfaceActionSchema}, or
|
|
95
|
+
* `null` when it conforms. The message names a path such as `$.rows[2]`, never the offending value.
|
|
96
|
+
* @complexity O(v) values visited.
|
|
97
|
+
*/
|
|
98
|
+
export function validateAdminSurfaceActionValue(required, optional = {}) {
|
|
99
|
+
const validate = VALIDATORS[required.schema.type];
|
|
100
|
+
return validate(required.schema, required.value, optional.path ?? '$');
|
|
101
|
+
}
|
|
102
|
+
function validateObject(schema, value, path) {
|
|
103
|
+
if (!isPlainObject(value))
|
|
104
|
+
return `${path} must be an object`;
|
|
105
|
+
const properties = schema.properties ?? {};
|
|
106
|
+
const extra = Object.keys(value).find((key) => !Object.hasOwn(properties, key));
|
|
107
|
+
if (extra !== undefined)
|
|
108
|
+
return `${path} has an undeclared property ${JSON.stringify(extra.slice(0, 64))}`;
|
|
109
|
+
const missing = (schema.required ?? []).find((key) => !Object.hasOwn(value, key));
|
|
110
|
+
if (missing !== undefined)
|
|
111
|
+
return `${path}.${missing} is required`;
|
|
112
|
+
for (const [key, child] of Object.entries(value)) {
|
|
113
|
+
const reason = validateAdminSurfaceActionValue({ schema: properties[key], value: child }, { path: `${path}.${key}` });
|
|
114
|
+
if (reason !== null)
|
|
115
|
+
return reason;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function validateArray(schema, value, path) {
|
|
120
|
+
if (!Array.isArray(value))
|
|
121
|
+
return `${path} must be an array`;
|
|
122
|
+
if (value.length > (schema.maxItems ?? DEFAULT_MAX_ITEMS))
|
|
123
|
+
return `${path} has too many items`;
|
|
124
|
+
for (const [index, item] of value.entries()) {
|
|
125
|
+
const reason = validateAdminSurfaceActionValue({ schema: schema.items, value: item }, { path: `${path}[${index}]` });
|
|
126
|
+
if (reason !== null)
|
|
127
|
+
return reason;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function validateString(schema, value, path) {
|
|
132
|
+
if (typeof value !== 'string')
|
|
133
|
+
return `${path} must be a string`;
|
|
134
|
+
if (value.length < (schema.minLength ?? 0) || value.length > (schema.maxLength ?? Infinity))
|
|
135
|
+
return `${path} has the wrong length`;
|
|
136
|
+
return schema.enum === undefined || schema.enum.includes(value) ? null : `${path} is not one of the allowed values`;
|
|
137
|
+
}
|
|
138
|
+
function validateNumber(schema, value, path) {
|
|
139
|
+
const ok = typeof value === 'number' && Number.isFinite(value) && (schema.type === 'number' || Number.isInteger(value));
|
|
140
|
+
if (!ok)
|
|
141
|
+
return `${path} must be ${schema.type === 'integer' ? 'an integer' : 'a number'}`;
|
|
142
|
+
return inRange(value, schema.minimum, schema.maximum) ? null : `${path} is out of range`;
|
|
143
|
+
}
|
|
144
|
+
const inRange = (value, minimum, maximum) => value >= (minimum ?? -Infinity) && value <= (maximum ?? Infinity);
|
|
145
|
+
/** One validator per declared `type`; the schema was checked at parse time, so the lookup always hits. */
|
|
146
|
+
const VALIDATORS = {
|
|
147
|
+
object: validateObject,
|
|
148
|
+
array: validateArray,
|
|
149
|
+
string: validateString,
|
|
150
|
+
number: validateNumber,
|
|
151
|
+
integer: validateNumber,
|
|
152
|
+
boolean: (_schema, value, path) => (typeof value === 'boolean' ? null : `${path} must be a boolean`),
|
|
153
|
+
null: (_schema, value, path) => (value === null ? null : `${path} must be null`),
|
|
154
|
+
};
|
|
155
|
+
/** One request-size cap for the browser-side host check and the server-side dispatcher (the
|
|
156
|
+
* authoritative one: string schemas carry no default maxLength). */
|
|
157
|
+
const MAX_PAYLOAD_CHARS = 64 * 1024;
|
|
158
|
+
/** True when `payload` serializes to more than 64 KiB of JSON text, or cannot be serialized. @complexity O(payload size). */
|
|
159
|
+
export function exceedsAdminSurfacePayloadLimit(required, _optional = {}) {
|
|
160
|
+
try {
|
|
161
|
+
return (JSON.stringify(required.payload)?.length ?? Infinity) > MAX_PAYLOAD_CHARS;
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=action-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-schema.js","sourceRoot":"","sources":["../../src/admin-surfaces/action-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Assistant adapter over the screen's dispatcher and admitted handler host. No module is reloaded here. */
|
|
2
|
+
import type { AdminSurfaceActionOutcome } from './protocol.js';
|
|
3
|
+
import type { AgentPluginAdditionalTool } from '../lifecycle/tools/projection.js';
|
|
4
|
+
import { type AdminSurfaceActionHandler } from './action-dispatcher.js';
|
|
5
|
+
import type { AdminSurfaceDescriptor } from './descriptor.js';
|
|
6
|
+
import type { AdminSurfaceHandlerContext } from './surface-handlers.js';
|
|
7
|
+
/** Admin tools always act for an authenticated principal, supplied by the registry. */
|
|
8
|
+
export interface AdminSurfaceToolContext extends AdminSurfaceHandlerContext {
|
|
9
|
+
readonly principalId: string;
|
|
10
|
+
}
|
|
11
|
+
/** The host binds this from active, trusted surface contributions and its existing handler host. */
|
|
12
|
+
export interface AdminSurfaceToolSource {
|
|
13
|
+
readonly pluginId: string;
|
|
14
|
+
readonly descriptor: AdminSurfaceDescriptor;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Projects every declared surface action into the existing plugin-tool contract. All actions use a
|
|
18
|
+
* `payload` envelope (including scalar/array actions), and the SAME dispatcher and handler host as
|
|
19
|
+
* the screen. The host supplies fresh authorization/admission before each call, including after a
|
|
20
|
+
* confirmation wait. Browser flows return their result and any host-issued URLs; this seam cannot open a browser.
|
|
21
|
+
* @param required.authorize - Must enforce the screen action permission and fresh trusted admission.
|
|
22
|
+
* @param optional.run - Request-local result decoration, e.g. collecting host-issued sign-in URLs.
|
|
23
|
+
* @returns Contributions for `createAgentPluginToolProjection.sync({ additionalTools })`.
|
|
24
|
+
* @complexity O(s + a) surfaces and actions, bounded by the descriptor parser; dispatch O(p) + handler.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildAdminSurfaceActionTools(required: {
|
|
27
|
+
readonly surfaces: readonly AdminSurfaceToolSource[];
|
|
28
|
+
readonly handlersFor: (input: {
|
|
29
|
+
readonly pluginId: string;
|
|
30
|
+
readonly surfaceId: string;
|
|
31
|
+
}) => Readonly<Record<string, AdminSurfaceActionHandler<AdminSurfaceToolContext>>>;
|
|
32
|
+
readonly authorize: (input: {
|
|
33
|
+
readonly context: AdminSurfaceToolContext;
|
|
34
|
+
}) => Promise<void>;
|
|
35
|
+
}, optional?: {
|
|
36
|
+
readonly run?: (input: {
|
|
37
|
+
readonly dispatch: () => Promise<AdminSurfaceActionOutcome>;
|
|
38
|
+
}) => Promise<unknown>;
|
|
39
|
+
}): readonly AgentPluginAdditionalTool[];
|
|
40
|
+
//# sourceMappingURL=action-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-tools.d.ts","sourceRoot":"","sources":["../../src/admin-surfaces/action-tools.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAC5G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,yBAAyB,EAA6B,MAAM,kCAAkC,CAAC;AAC7G,OAAO,EAAsC,KAAK,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAE5G,OAAO,KAAK,EAAsB,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AAExE,uFAAuF;AACvF,MAAM,WAAW,uBAAwB,SAAQ,0BAA0B;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,oGAAoG;AACpG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAC;CAC7C;AAcD;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE;IACrD,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACrD,QAAQ,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC;IACzK,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7F,EAAE,QAAQ,GAAE;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,yBAAyB,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAAO,GAAG,SAAS,yBAAyB,EAAE,CAyBvK"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ToolInputError, requireInputRecord } from '@jini-ai/core';
|
|
2
|
+
import { createAdminSurfaceActionDispatcher } from './action-dispatcher.js';
|
|
3
|
+
import { exceedsAdminSurfacePayloadLimit, validateAdminSurfaceActionValue } from './action-schema.js';
|
|
4
|
+
/** Checks the tool envelope and delegates payload validation to the screen's schema owner.
|
|
5
|
+
* @throws ToolInputError without echoing input values. @complexity O(p) payload size. */
|
|
6
|
+
function checkedPayload(required, _optional = {}) {
|
|
7
|
+
const { input, action } = required;
|
|
8
|
+
const record = requireInputRecord({ input });
|
|
9
|
+
if (Object.keys(record).length !== 1 || !Object.hasOwn(record, 'payload') || exceedsAdminSurfacePayloadLimit({ payload: record.payload })
|
|
10
|
+
|| validateAdminSurfaceActionValue({ schema: action.input, value: record.payload }) !== null) {
|
|
11
|
+
throw new ToolInputError({ message: 'Supply payload matching the declared admin action schema.' });
|
|
12
|
+
}
|
|
13
|
+
return record.payload;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Projects every declared surface action into the existing plugin-tool contract. All actions use a
|
|
17
|
+
* `payload` envelope (including scalar/array actions), and the SAME dispatcher and handler host as
|
|
18
|
+
* the screen. The host supplies fresh authorization/admission before each call, including after a
|
|
19
|
+
* confirmation wait. Browser flows return their result and any host-issued URLs; this seam cannot open a browser.
|
|
20
|
+
* @param required.authorize - Must enforce the screen action permission and fresh trusted admission.
|
|
21
|
+
* @param optional.run - Request-local result decoration, e.g. collecting host-issued sign-in URLs.
|
|
22
|
+
* @returns Contributions for `createAgentPluginToolProjection.sync({ additionalTools })`.
|
|
23
|
+
* @complexity O(s + a) surfaces and actions, bounded by the descriptor parser; dispatch O(p) + handler.
|
|
24
|
+
*/
|
|
25
|
+
export function buildAdminSurfaceActionTools(required, optional = {}) {
|
|
26
|
+
return required.surfaces.flatMap(({ pluginId, descriptor }) => descriptor.actions.map((action) => {
|
|
27
|
+
const name = `${descriptor.id.replace(/-/g, '_')}_${action.name.replace(/-/g, '_')}`;
|
|
28
|
+
return {
|
|
29
|
+
pluginId,
|
|
30
|
+
tool: {
|
|
31
|
+
name, description: `${descriptor.title}: ${action.name}. Browser sign-in actions return a URL to show the user.`,
|
|
32
|
+
effect: action.effect ?? 'write',
|
|
33
|
+
inputSchema: { type: 'object', properties: { payload: action.input }, required: ['payload'], additionalProperties: false },
|
|
34
|
+
async handler({ auth, input }) {
|
|
35
|
+
if (!auth.principalId)
|
|
36
|
+
throw new ToolInputError({ message: 'Admin actions require an authenticated principal.' });
|
|
37
|
+
const context = { ...auth, principalId: auth.principalId, pluginId, surfaceId: descriptor.id };
|
|
38
|
+
await required.authorize({ context });
|
|
39
|
+
const payload = checkedPayload({ input, action });
|
|
40
|
+
const dispatcher = createAdminSurfaceActionDispatcher({ descriptor, handlers: required.handlersFor({ pluginId, surfaceId: descriptor.id }) });
|
|
41
|
+
const dispatch = () => dispatcher.dispatch({ action: action.name, payload, context });
|
|
42
|
+
return optional.run ? optional.run({ dispatch }) : dispatch();
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
describeAffectedResources({ input }) {
|
|
46
|
+
checkedPayload({ input, action });
|
|
47
|
+
return [`${descriptor.title}: ${action.name}`];
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=action-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-tools.js","sourceRoot":"","sources":["../../src/admin-surfaces/action-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file A one-time, bounded holding place for the extra fields a provider returns on a completed
|
|
3
|
+
* sign-in (for example the id of a resource the person picked during consent), between the host's
|
|
4
|
+
* callback route and the plugin handler that asked for the sign-in.
|
|
5
|
+
*
|
|
6
|
+
* Why it exists: the callback arrives on a host route the plugin never sees, but the plugin's
|
|
7
|
+
* surface handler is the one that must read and verify those fields. The host deposits them only
|
|
8
|
+
* after the sign-in completed successfully; the plugin's `connections.takeCallbackFields` takes them
|
|
9
|
+
* exactly once, and only under the same workspace, plugin, connection and attempt id, so another
|
|
10
|
+
* plugin or a second read gets `null`. Entries expire and the inbox is capped, so an abandoned
|
|
11
|
+
* sign-in cannot grow memory. It is in-process: a host that completes sign-ins on another process
|
|
12
|
+
* implements the same two methods over its own store.
|
|
13
|
+
*/
|
|
14
|
+
export interface AdminSurfaceCallbackFieldKey {
|
|
15
|
+
readonly workspaceId: string;
|
|
16
|
+
readonly pluginId: string;
|
|
17
|
+
readonly connectionId: string;
|
|
18
|
+
readonly attemptId: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @param required.now - Clock in epoch milliseconds (injected so expiry is testable).
|
|
22
|
+
* @returns `deposit` (host callback route, after a successful completion) and `take` (consume-once).
|
|
23
|
+
* @complexity deposit is O(e) to sweep expired entries; take is O(1).
|
|
24
|
+
*/
|
|
25
|
+
export declare function createAdminSurfaceCallbackInbox(required: {
|
|
26
|
+
readonly now: () => number;
|
|
27
|
+
}, _optional?: Record<string, never>): {
|
|
28
|
+
/** Holds a completed sign-in's callback fields (call it only after the exchange succeeded). */
|
|
29
|
+
deposit(input: AdminSurfaceCallbackFieldKey & {
|
|
30
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
31
|
+
}, _options?: Record<string, never>): void;
|
|
32
|
+
/** The fields, removed on the first read; `null` when never deposited, already taken, expired or keyed differently. */
|
|
33
|
+
take(input: AdminSurfaceCallbackFieldKey, _options?: Record<string, never>): Readonly<Record<string, string>> | null;
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=callback-field-inbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-field-inbox.d.ts","sourceRoot":"","sources":["../../src/admin-surfaces/callback-field-inbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAUD;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAA;CAAE,EACxC,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAM;IAWnC,+FAA+F;mBAChF,4BAA4B,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;KAAE,aAAY,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAQ,IAAI;IAKhJ,uHAAuH;gBAC3G,4BAA4B,aAAY,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAQ,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI;EAQ3H"}
|