@jini-ai/agent-plugins 0.4.6 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +48 -0
- package/dist/admin-surfaces/action-dispatcher.d.ts +37 -0
- package/dist/admin-surfaces/action-dispatcher.js +69 -0
- package/dist/admin-surfaces/action-schema.d.ts +56 -0
- package/dist/admin-surfaces/action-schema.js +167 -0
- package/dist/admin-surfaces/action-tools.d.ts +40 -0
- package/dist/admin-surfaces/action-tools.js +52 -0
- package/dist/admin-surfaces/callback-field-inbox.d.ts +35 -0
- package/dist/admin-surfaces/callback-field-inbox.js +53 -0
- package/dist/admin-surfaces/capability-declarations.d.ts +41 -0
- package/dist/admin-surfaces/capability-declarations.js +102 -0
- package/dist/admin-surfaces/contribution.d.ts +49 -0
- package/dist/admin-surfaces/contribution.js +30 -0
- package/dist/admin-surfaces/delivery-bindings.d.ts +76 -0
- package/dist/admin-surfaces/delivery-bindings.js +99 -0
- package/dist/admin-surfaces/descriptor.d.ts +69 -0
- package/dist/admin-surfaces/descriptor.js +196 -0
- package/dist/admin-surfaces/external-links.d.ts +45 -0
- package/dist/admin-surfaces/external-links.js +70 -0
- package/dist/admin-surfaces/host-validator.d.ts +62 -0
- package/dist/admin-surfaces/host-validator.js +87 -0
- package/dist/admin-surfaces/index.d.ts +23 -0
- package/dist/admin-surfaces/index.js +21 -0
- package/dist/admin-surfaces/protocol.d.ts +103 -0
- package/dist/admin-surfaces/protocol.js +25 -0
- package/dist/admin-surfaces/sandbox.d.ts +78 -0
- package/dist/admin-surfaces/sandbox.js +92 -0
- package/dist/admin-surfaces/settings-value.d.ts +28 -0
- package/dist/admin-surfaces/settings-value.js +45 -0
- package/dist/admin-surfaces/surface-capabilities.d.ts +167 -0
- package/dist/admin-surfaces/surface-capabilities.js +215 -0
- package/dist/admin-surfaces/surface-client.d.ts +116 -0
- package/dist/admin-surfaces/surface-client.js +219 -0
- package/dist/admin-surfaces/surface-handlers.d.ts +111 -0
- package/dist/admin-surfaces/surface-handlers.js +172 -0
- package/dist/admin-surfaces/theme.d.ts +59 -0
- package/dist/admin-surfaces/theme.js +57 -0
- package/dist/folder-zip-node.d.ts +11 -0
- package/dist/folder-zip-node.js +84 -0
- package/dist/folder-zip.d.ts +38 -0
- package/dist/folder-zip.js +134 -0
- package/dist/lifecycle/automatic-use.d.ts +79 -0
- package/dist/lifecycle/automatic-use.js +165 -0
- package/dist/lifecycle/compatibility-metadata.d.ts +19 -0
- package/dist/lifecycle/compatibility-metadata.js +45 -0
- package/dist/lifecycle/compatibility.d.ts +52 -0
- package/dist/lifecycle/compatibility.js +60 -0
- package/dist/lifecycle/fetch-archive.js +26 -24
- package/dist/lifecycle/index.d.ts +14 -2
- package/dist/lifecycle/index.js +8 -1
- package/dist/lifecycle/install-errors.d.ts +14 -0
- package/dist/lifecycle/install-errors.js +9 -0
- package/dist/lifecycle/install.d.ts +2 -13
- package/dist/lifecycle/install.js +12 -123
- package/dist/lifecycle/installed-package-index.d.ts +15 -0
- package/dist/lifecycle/installed-package-index.js +128 -0
- package/dist/lifecycle/installed-package-roots.js +28 -20
- package/dist/lifecycle/manifest.d.ts +14 -0
- package/dist/lifecycle/manifest.js +115 -100
- package/dist/lifecycle/package-paths.js +3 -3
- package/dist/lifecycle/ports.d.ts +1 -1
- package/dist/lifecycle/provider-contribution.d.ts +65 -0
- package/dist/lifecycle/provider-contribution.js +93 -0
- package/dist/lifecycle/resolve-agent-plugin-refs.js +19 -6
- package/dist/lifecycle/retire-bundled.js +20 -18
- package/dist/lifecycle/search.d.ts +6 -1
- package/dist/lifecycle/search.js +45 -7
- package/dist/lifecycle/set-enabled.d.ts +2 -1
- package/dist/lifecycle/set-enabled.js +52 -8
- package/dist/lifecycle/skill-file-reader.js +85 -71
- package/dist/lifecycle/tools/capabilities.d.ts +199 -0
- package/dist/lifecycle/tools/capabilities.js +213 -0
- package/dist/lifecycle/tools/definition.d.ts +32 -0
- package/dist/lifecycle/tools/definition.js +33 -0
- package/dist/lifecycle/tools/index.d.ts +10 -0
- package/dist/lifecycle/tools/index.js +10 -0
- package/dist/lifecycle/tools/manifest.d.ts +33 -0
- package/dist/lifecycle/tools/manifest.js +89 -0
- package/dist/lifecycle/tools/projection.d.ts +90 -0
- package/dist/lifecycle/tools/projection.js +283 -0
- package/dist/lifecycle/types.d.ts +2 -0
- package/dist/lifecycle/uninstall.d.ts +3 -1
- package/dist/lifecycle/uninstall.js +51 -27
- package/dist/lifecycle/update-plan.d.ts +18 -0
- package/dist/lifecycle/update-plan.js +22 -0
- package/dist/lifecycle/update-store.d.ts +71 -0
- package/dist/lifecycle/update-store.js +150 -0
- package/dist/lifecycle/update.d.ts +55 -0
- package/dist/lifecycle/update.js +321 -0
- package/dist/manifest.d.ts +2 -1
- package/dist/manifest.js +2 -1
- package/package.json +50 -6
- package/ui-ux-design/plugin.json +8 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lifecycle/activation.d.ts.map +0 -1
- package/dist/lifecycle/activation.js.map +0 -1
- package/dist/lifecycle/bundled-digests.d.ts.map +0 -1
- package/dist/lifecycle/bundled-digests.js.map +0 -1
- package/dist/lifecycle/bundled-source-archive.d.ts.map +0 -1
- package/dist/lifecycle/bundled-source-archive.js.map +0 -1
- package/dist/lifecycle/capability-projection.d.ts.map +0 -1
- package/dist/lifecycle/capability-projection.js.map +0 -1
- package/dist/lifecycle/contribution-loader.d.ts.map +0 -1
- package/dist/lifecycle/contribution-loader.js.map +0 -1
- package/dist/lifecycle/fetch-archive.d.ts.map +0 -1
- package/dist/lifecycle/fetch-archive.js.map +0 -1
- package/dist/lifecycle/index.d.ts.map +0 -1
- package/dist/lifecycle/index.js.map +0 -1
- package/dist/lifecycle/install-from-url.d.ts.map +0 -1
- package/dist/lifecycle/install-from-url.js.map +0 -1
- package/dist/lifecycle/install.d.ts.map +0 -1
- package/dist/lifecycle/install.js.map +0 -1
- package/dist/lifecycle/installed-package-roots.d.ts.map +0 -1
- package/dist/lifecycle/installed-package-roots.js.map +0 -1
- package/dist/lifecycle/layout.d.ts.map +0 -1
- package/dist/lifecycle/layout.js.map +0 -1
- package/dist/lifecycle/manifest.d.ts.map +0 -1
- package/dist/lifecycle/manifest.js.map +0 -1
- package/dist/lifecycle/mcp-provisioning.d.ts.map +0 -1
- package/dist/lifecycle/mcp-provisioning.js.map +0 -1
- package/dist/lifecycle/node.d.ts.map +0 -1
- package/dist/lifecycle/node.js.map +0 -1
- package/dist/lifecycle/package-paths.d.ts.map +0 -1
- package/dist/lifecycle/package-paths.js.map +0 -1
- package/dist/lifecycle/persistent-state.d.ts.map +0 -1
- package/dist/lifecycle/persistent-state.js.map +0 -1
- package/dist/lifecycle/ports.d.ts.map +0 -1
- package/dist/lifecycle/ports.js.map +0 -1
- package/dist/lifecycle/resolve-agent-plugin-refs.d.ts.map +0 -1
- package/dist/lifecycle/resolve-agent-plugin-refs.js.map +0 -1
- package/dist/lifecycle/retire-bundled.d.ts.map +0 -1
- package/dist/lifecycle/retire-bundled.js.map +0 -1
- package/dist/lifecycle/search.d.ts.map +0 -1
- package/dist/lifecycle/search.js.map +0 -1
- package/dist/lifecycle/seed-bundled.d.ts.map +0 -1
- package/dist/lifecycle/seed-bundled.js.map +0 -1
- package/dist/lifecycle/set-enabled.d.ts.map +0 -1
- package/dist/lifecycle/set-enabled.js.map +0 -1
- package/dist/lifecycle/skill-file-reader.d.ts.map +0 -1
- package/dist/lifecycle/skill-file-reader.js.map +0 -1
- package/dist/lifecycle/trusted-plugin-files.d.ts.map +0 -1
- package/dist/lifecycle/trusted-plugin-files.js.map +0 -1
- package/dist/lifecycle/types.d.ts.map +0 -1
- package/dist/lifecycle/types.js.map +0 -1
- package/dist/lifecycle/uninstall.d.ts.map +0 -1
- package/dist/lifecycle/uninstall.js.map +0 -1
- package/dist/lifecycle/yauzl-archive-reader.d.ts.map +0 -1
- package/dist/lifecycle/yauzl-archive-reader.js.map +0 -1
- package/dist/manifest.d.ts.map +0 -1
- package/dist/manifest.js.map +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Parses the capability declarations a surface with a `handlerModule` may make: which of the
|
|
3
|
+
* plugin's own connections its handlers administer (with the per-start parameters and callback
|
|
4
|
+
* fields each may use), which public configuration values the host may hand its handlers, and which
|
|
5
|
+
* of the plugin's delivery adapters' form-delivery bindings they manage (`./delivery-bindings.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Why they are declared, not requested at run time: the surfaces file is part of the shipped,
|
|
8
|
+
* digest-checked package, so these lists are the trusted allowlists the admin-surface capability
|
|
9
|
+
* session (`./surface-capabilities.ts`) enforces. A handler can never reach a connection, a start
|
|
10
|
+
* parameter, a callback field or a config value its surface did not declare. Config names that look
|
|
11
|
+
* like secrets are refused here, so a secret can never become "public config" by a naming slip.
|
|
12
|
+
*/
|
|
13
|
+
/** One connection a surface's handlers may administer. `id` is the plugin's own connection id, never a ref. */
|
|
14
|
+
export interface AdminSurfaceConnectionDeclaration {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
/** Authorization parameters a handler may set for one sign-in (the connection must also allow them). */
|
|
17
|
+
readonly startParams: readonly string[];
|
|
18
|
+
/** Provider callback fields a handler may take once after a completed sign-in. */
|
|
19
|
+
readonly callbackFields: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
export interface AdminSurfaceCapabilityDeclarations {
|
|
22
|
+
readonly connections?: readonly AdminSurfaceConnectionDeclaration[];
|
|
23
|
+
readonly publicConfig?: readonly string[];
|
|
24
|
+
/** Delivery adapter ids whose bindings the handlers manage. Jini checks only the shape; the host
|
|
25
|
+
* checks that each is an adapter this same plugin contributes. */
|
|
26
|
+
readonly deliveryAdapters?: readonly string[];
|
|
27
|
+
/** Read-only form discovery; no form contents, submissions or mutation access. */
|
|
28
|
+
readonly forms?: readonly 'list'[];
|
|
29
|
+
}
|
|
30
|
+
type Entry = Readonly<Record<string, unknown>>;
|
|
31
|
+
/**
|
|
32
|
+
* The surface's capability declarations, with keys present only when declared (an undeclared list
|
|
33
|
+
* adds no key, so existing descriptors parse unchanged), or why the file is refused.
|
|
34
|
+
* @complexity O(c * f + n + d) connections with their fields, config names and delivery adapters; every list is capped.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseAdminSurfaceCapabilityDeclarations(required: {
|
|
37
|
+
readonly entry: Entry;
|
|
38
|
+
readonly at: string;
|
|
39
|
+
}, _optional?: Record<string, never>): AdminSurfaceCapabilityDeclarations | string;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=capability-declarations.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Parses the capability declarations a surface with a `handlerModule` may make: which of the
|
|
3
|
+
* plugin's own connections its handlers administer (with the per-start parameters and callback
|
|
4
|
+
* fields each may use), which public configuration values the host may hand its handlers, and which
|
|
5
|
+
* of the plugin's delivery adapters' form-delivery bindings they manage (`./delivery-bindings.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Why they are declared, not requested at run time: the surfaces file is part of the shipped,
|
|
8
|
+
* digest-checked package, so these lists are the trusted allowlists the admin-surface capability
|
|
9
|
+
* session (`./surface-capabilities.ts`) enforces. A handler can never reach a connection, a start
|
|
10
|
+
* parameter, a callback field or a config value its surface did not declare. Config names that look
|
|
11
|
+
* like secrets are refused here, so a secret can never become "public config" by a naming slip.
|
|
12
|
+
*/
|
|
13
|
+
/** Same shapes the API-connection descriptor accepts for connection ids and parameter names. */
|
|
14
|
+
const CONNECTION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
15
|
+
const FIELD_NAME_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
16
|
+
/**
|
|
17
|
+
* Copy of the OAuth core's `isCallbackFieldName` pattern (`@jini-ai/oauth` loopback-callback.ts is
|
|
18
|
+
* the source of truth; agent-plugins does not depend on it). The descriptor applies the full check,
|
|
19
|
+
* including the reserved protocol names, so a field passing here but reserved there is never declared.
|
|
20
|
+
*/
|
|
21
|
+
const CALLBACK_FIELD_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
|
|
22
|
+
/** Environment-style names, so a host can map them to its own config without translation. */
|
|
23
|
+
const CONFIG_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
24
|
+
/** Name parts that mark a credential. A plugin's public values never need one of them. */
|
|
25
|
+
const SECRET_NAME_PATTERN = /SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|COOKIE|SESSION|SIGNING/;
|
|
26
|
+
/** The host's destination-id shape (lowercase hyphenated, at most 64), so a declared id can match one. */
|
|
27
|
+
const ADAPTER_ID_PATTERN = /^(?=.{1,64}$)[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
28
|
+
const MAX_CONNECTIONS = 4;
|
|
29
|
+
const MAX_DELIVERY_ADAPTERS = 8;
|
|
30
|
+
const MAX_FIELDS = 8;
|
|
31
|
+
const MAX_CONFIG_NAMES = 16;
|
|
32
|
+
const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
+
/** A bounded, de-duplicated list of names matching `pattern`, or why not. @complexity O(n) names. */
|
|
34
|
+
function parseNames(value, required) {
|
|
35
|
+
const { pattern, max, at } = required;
|
|
36
|
+
if (!Array.isArray(value) || value.length > max)
|
|
37
|
+
return `${at} must be an array of at most ${max}`;
|
|
38
|
+
const bad = value.findIndex((name, index) => typeof name !== 'string' || !pattern.test(name) || value.indexOf(name) !== index);
|
|
39
|
+
return bad === -1 ? value : `${at}[${bad}] must be a unique name matching ${pattern.source}`;
|
|
40
|
+
}
|
|
41
|
+
function parseConnection(entry, at) {
|
|
42
|
+
if (!isPlainObject(entry))
|
|
43
|
+
return `${at} must be an object`;
|
|
44
|
+
if (typeof entry.id !== 'string' || !CONNECTION_ID_PATTERN.test(entry.id))
|
|
45
|
+
return `${at}.id must be a short connection id`;
|
|
46
|
+
const startParams = parseNames(entry.startParams ?? [], { pattern: FIELD_NAME_PATTERN, max: MAX_FIELDS, at: `${at}.startParams` });
|
|
47
|
+
if (typeof startParams === 'string')
|
|
48
|
+
return startParams;
|
|
49
|
+
const callbackFields = parseNames(entry.callbackFields ?? [], { pattern: CALLBACK_FIELD_NAME_PATTERN, max: MAX_FIELDS, at: `${at}.callbackFields` });
|
|
50
|
+
return typeof callbackFields === 'string' ? callbackFields : { id: entry.id, startParams, callbackFields };
|
|
51
|
+
}
|
|
52
|
+
function parseConnections(value, at) {
|
|
53
|
+
if (!Array.isArray(value) || value.length > MAX_CONNECTIONS)
|
|
54
|
+
return `${at} must be an array of at most ${MAX_CONNECTIONS}`;
|
|
55
|
+
const connections = [];
|
|
56
|
+
for (const [index, entry] of value.entries()) {
|
|
57
|
+
const parsed = parseConnection(entry, `${at}[${index}]`);
|
|
58
|
+
if (typeof parsed === 'string')
|
|
59
|
+
return parsed;
|
|
60
|
+
if (connections.some((seen) => seen.id === parsed.id))
|
|
61
|
+
return `${at}[${index}].id '${parsed.id}' is declared twice`;
|
|
62
|
+
connections.push(parsed);
|
|
63
|
+
}
|
|
64
|
+
return connections;
|
|
65
|
+
}
|
|
66
|
+
function parsePublicConfig(value, at) {
|
|
67
|
+
const names = parseNames(value, { pattern: CONFIG_NAME_PATTERN, max: MAX_CONFIG_NAMES, at });
|
|
68
|
+
if (typeof names === 'string')
|
|
69
|
+
return names;
|
|
70
|
+
const secret = names.findIndex((name) => SECRET_NAME_PATTERN.test(name));
|
|
71
|
+
return secret === -1 ? names : `${at}[${secret}] names a secret; public config may only hold values that are safe to show`;
|
|
72
|
+
}
|
|
73
|
+
function parseDeliveryAdapters(value, at) {
|
|
74
|
+
return parseNames(value, { pattern: ADAPTER_ID_PATTERN, max: MAX_DELIVERY_ADAPTERS, at });
|
|
75
|
+
}
|
|
76
|
+
/** Only the read operation can be granted; unknown operations fail admission. */
|
|
77
|
+
function parseForms(value, at) {
|
|
78
|
+
return parseNames(value, { pattern: /^list$/, max: 1, at });
|
|
79
|
+
}
|
|
80
|
+
/** Each declaration's parser, in the order refusals are reported. */
|
|
81
|
+
const DECLARATION_PARSERS = [
|
|
82
|
+
['connections', parseConnections], ['publicConfig', parsePublicConfig], ['deliveryAdapters', parseDeliveryAdapters], ['forms', parseForms],
|
|
83
|
+
];
|
|
84
|
+
/**
|
|
85
|
+
* The surface's capability declarations, with keys present only when declared (an undeclared list
|
|
86
|
+
* adds no key, so existing descriptors parse unchanged), or why the file is refused.
|
|
87
|
+
* @complexity O(c * f + n + d) connections with their fields, config names and delivery adapters; every list is capped.
|
|
88
|
+
*/
|
|
89
|
+
export function parseAdminSurfaceCapabilityDeclarations(required, _optional = {}) {
|
|
90
|
+
const { entry, at } = required;
|
|
91
|
+
const declarations = {};
|
|
92
|
+
for (const [name, parse] of DECLARATION_PARSERS) {
|
|
93
|
+
if (entry[name] === undefined)
|
|
94
|
+
continue;
|
|
95
|
+
const parsed = parse(entry[name], `${at}.${name}`);
|
|
96
|
+
if (typeof parsed === 'string')
|
|
97
|
+
return parsed;
|
|
98
|
+
declarations[name] = parsed;
|
|
99
|
+
}
|
|
100
|
+
return declarations;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=capability-declarations.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Plugs admin surfaces into the existing plugin contribution loader
|
|
3
|
+
* (`../lifecycle/contribution-loader.ts`) and serves their declared files.
|
|
4
|
+
*
|
|
5
|
+
* Why the filename is a parameter: the host names its own contribution file (one host might use
|
|
6
|
+
* `<product>-admin-surfaces.json`), exactly as it does for every other seam; Jini never hardcodes a
|
|
7
|
+
* product prefix. Surfaces are a DATA contribution: nothing is imported into the server process. The
|
|
8
|
+
* loader still applies the shared trust gate (active + shipped digest), and file reads go through
|
|
9
|
+
* the lifecycle's `readTrustedPluginFile`, which resolves symlinks and refuses anything outside the
|
|
10
|
+
* package root.
|
|
11
|
+
*/
|
|
12
|
+
import type { PluginContributionDefinition } from '../lifecycle/contribution-loader.js';
|
|
13
|
+
import type { TrustedPluginPackage } from '../lifecycle/trusted-plugin-files.js';
|
|
14
|
+
import { type AdminSurfaceDescriptor } from './descriptor.js';
|
|
15
|
+
/** One surface the trust gate admitted, with the package its files are read from. */
|
|
16
|
+
export interface LoadedAdminSurface {
|
|
17
|
+
readonly plugin: TrustedPluginPackage;
|
|
18
|
+
readonly descriptor: AdminSurfaceDescriptor;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The contribution definition for a host-chosen filename. Pass it to the lifecycle's
|
|
22
|
+
* `loadPluginContributions({ workspaceId, definition })`.
|
|
23
|
+
* @complexity O(1) composition; parsing is O(file size).
|
|
24
|
+
*/
|
|
25
|
+
export declare function defineAdminSurfaceContribution(required: {
|
|
26
|
+
readonly filename: string;
|
|
27
|
+
}, _optional?: Record<string, never>): PluginContributionDefinition<AdminSurfaceDescriptor, LoadedAdminSurface>;
|
|
28
|
+
/**
|
|
29
|
+
* Reads one file of a loaded surface for serving, with the headers it must be served with. Only the
|
|
30
|
+
* entry and the declared `files` are ever read; any other path answers `not-declared` without I/O.
|
|
31
|
+
* @param required.readFile - The lifecycle's `readTrustedPluginFile` (on-disk containment check).
|
|
32
|
+
* @complexity O(f) declared files plus the read.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readAdminSurfaceFile(required: {
|
|
35
|
+
readonly surface: LoadedAdminSurface;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly readFile: (input: {
|
|
38
|
+
readonly plugin: TrustedPluginPackage;
|
|
39
|
+
readonly filename: string;
|
|
40
|
+
}) => Promise<string>;
|
|
41
|
+
}, _optional?: Record<string, never>): Promise<{
|
|
42
|
+
readonly ok: true;
|
|
43
|
+
readonly body: string;
|
|
44
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
45
|
+
} | {
|
|
46
|
+
readonly ok: false;
|
|
47
|
+
readonly reason: 'not-declared';
|
|
48
|
+
}>;
|
|
49
|
+
//# sourceMappingURL=contribution.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseAdminSurfacesFile } from './descriptor.js';
|
|
2
|
+
import { adminSurfaceFileHeaders } from './sandbox.js';
|
|
3
|
+
/**
|
|
4
|
+
* The contribution definition for a host-chosen filename. Pass it to the lifecycle's
|
|
5
|
+
* `loadPluginContributions({ workspaceId, definition })`.
|
|
6
|
+
* @complexity O(1) composition; parsing is O(file size).
|
|
7
|
+
*/
|
|
8
|
+
export function defineAdminSurfaceContribution(required, _optional = {}) {
|
|
9
|
+
return {
|
|
10
|
+
filename: required.filename,
|
|
11
|
+
contribution: 'admin surfaces',
|
|
12
|
+
kind: 'data',
|
|
13
|
+
parse: ({ raw }) => parseAdminSurfacesFile({ raw }),
|
|
14
|
+
load: ({ plugin, descriptor }) => ({ plugin, descriptor }),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reads one file of a loaded surface for serving, with the headers it must be served with. Only the
|
|
19
|
+
* entry and the declared `files` are ever read; any other path answers `not-declared` without I/O.
|
|
20
|
+
* @param required.readFile - The lifecycle's `readTrustedPluginFile` (on-disk containment check).
|
|
21
|
+
* @complexity O(f) declared files plus the read.
|
|
22
|
+
*/
|
|
23
|
+
export async function readAdminSurfaceFile(required, _optional = {}) {
|
|
24
|
+
const { surface, path } = required;
|
|
25
|
+
if (path !== surface.descriptor.entry && !surface.descriptor.files.includes(path))
|
|
26
|
+
return { ok: false, reason: 'not-declared' };
|
|
27
|
+
const body = await required.readFile({ plugin: surface.plugin, filename: path });
|
|
28
|
+
return { ok: true, body, headers: adminSurfaceFileHeaders({ descriptor: surface.descriptor, path }) };
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=contribution.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The admin-surface `deliveryBindings` port: a plugin's destination screen lists, saves and
|
|
3
|
+
* removes the workspace's form-delivery bindings for the delivery adapters its surface declares, so an
|
|
4
|
+
* admin no longer copies a destination's config by hand.
|
|
5
|
+
*
|
|
6
|
+
* Why the checks sit here and not only in the host: the surface's `deliveryAdapters` and `connections`
|
|
7
|
+
* are the shipped, digest-checked allowlists. So before the host sees a call, the session refuses an
|
|
8
|
+
* adapter or connection the surface did not declare, a config that is not bounded plain JSON or that
|
|
9
|
+
* mentions a token the plugin obtained (`./settings-value.ts`, the same check as plugin state), and any
|
|
10
|
+
* binding id that is not one of the surface's own bindings (so another adapter's binding can be neither
|
|
11
|
+
* overwritten nor removed); and it filters and re-shapes what the host lists. The host still owns
|
|
12
|
+
* ownership itself (that the adapter ids are ones this plugin contributes), storage, the binding ids,
|
|
13
|
+
* and mapping a declared connection id to its own connection for the scope's plugin and workspace.
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentPluginCapabilityGate, AgentPluginStateValue } from '../lifecycle/tools/capabilities.js';
|
|
16
|
+
/**
|
|
17
|
+
* Which forms a binding delivers, always stated: the listed form ids (`[]` = none), or `allForms: true`
|
|
18
|
+
* for every current and future form. There is no implicit "every form": submissions carry personal
|
|
19
|
+
* data, so a plugin bug that forgets the field must fail, not widen the routing.
|
|
20
|
+
*/
|
|
21
|
+
export type AdminSurfaceDeliveryBindingForms = {
|
|
22
|
+
readonly formIds: readonly string[];
|
|
23
|
+
readonly allForms?: never;
|
|
24
|
+
} | {
|
|
25
|
+
readonly allForms: true;
|
|
26
|
+
readonly formIds?: never;
|
|
27
|
+
};
|
|
28
|
+
interface DeliveryBindingFields {
|
|
29
|
+
readonly adapterId: string;
|
|
30
|
+
readonly connectionId: string;
|
|
31
|
+
readonly enabled: boolean;
|
|
32
|
+
/** Adapter-owned, non-secret configuration. */
|
|
33
|
+
readonly config: Readonly<Record<string, AgentPluginStateValue>>;
|
|
34
|
+
}
|
|
35
|
+
/** One binding as a surface sees it: plugin-owned config, the plugin's own connection id (never a ref). */
|
|
36
|
+
export type AdminSurfaceDeliveryBinding = DeliveryBindingFields & AdminSurfaceDeliveryBindingForms & {
|
|
37
|
+
readonly bindingId: string;
|
|
38
|
+
};
|
|
39
|
+
/** What `save` takes: a new binding (the host assigns its id) or, with `bindingId`, one of the surface's own. */
|
|
40
|
+
export type AdminSurfaceDeliveryBindingDraft = DeliveryBindingFields & AdminSurfaceDeliveryBindingForms & {
|
|
41
|
+
readonly bindingId?: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The port. The host implements it scoped to the call's plugin and workspace, listing only the plugin's
|
|
45
|
+
* own adapters' bindings; the session hands the plugin the same interface, checked.
|
|
46
|
+
*/
|
|
47
|
+
export interface AdminSurfaceDeliveryBindings {
|
|
48
|
+
list(required?: Record<string, never>, optional?: Record<string, never>): Promise<readonly AdminSurfaceDeliveryBinding[]>;
|
|
49
|
+
save(required: AdminSurfaceDeliveryBindingDraft, optional?: Record<string, never>): Promise<AdminSurfaceDeliveryBinding>;
|
|
50
|
+
remove(required: {
|
|
51
|
+
readonly bindingId: string;
|
|
52
|
+
}, optional?: Record<string, never>): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export declare const DELIVERY_BINDING_WITHHELD_MESSAGE = "A delivery binding held a connection credential, so it was withheld. Remove it and save it again.";
|
|
55
|
+
interface Allowlists {
|
|
56
|
+
readonly adapterIds: readonly string[];
|
|
57
|
+
readonly connectionIds: readonly string[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Wraps the host's port for one admin-surface call.
|
|
61
|
+
* @param required.gate - The call's capability gate (revocation and token scans).
|
|
62
|
+
* @param required.host - The host's port, already scoped to the plugin and workspace.
|
|
63
|
+
* @param required.adapterIds - The surface's declared delivery adapters.
|
|
64
|
+
* @param required.connectionIds - The surface's declared connection ids.
|
|
65
|
+
* @param optional.actorId - The signed-in admin; every call is refused without one.
|
|
66
|
+
* @returns The checked port.
|
|
67
|
+
* @complexity Each call is one host list (plus one save or remove), O(b) over the listed bindings.
|
|
68
|
+
*/
|
|
69
|
+
export declare function guardDeliveryBindings(required: {
|
|
70
|
+
readonly gate: AgentPluginCapabilityGate;
|
|
71
|
+
readonly host: AdminSurfaceDeliveryBindings;
|
|
72
|
+
} & Allowlists, optional?: {
|
|
73
|
+
readonly actorId?: string | undefined;
|
|
74
|
+
}): AdminSurfaceDeliveryBindings;
|
|
75
|
+
export {};
|
|
76
|
+
//# sourceMappingURL=delivery-bindings.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { storedStateValue } from './settings-value.js';
|
|
2
|
+
export const DELIVERY_BINDING_WITHHELD_MESSAGE = 'A delivery binding held a connection credential, so it was withheld. Remove it and save it again.';
|
|
3
|
+
const FORM_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
4
|
+
const MAX_FORM_IDS = 64;
|
|
5
|
+
/** Names in messages come from plugin code: cut them so a message stays a message. */
|
|
6
|
+
const named = (value) => String(value).slice(0, 64);
|
|
7
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
function checkedFormIds(formIds) {
|
|
9
|
+
const valid = Array.isArray(formIds) && formIds.length <= MAX_FORM_IDS
|
|
10
|
+
&& formIds.every((id, index) => typeof id === 'string' && FORM_ID_PATTERN.test(id) && formIds.indexOf(id) === index);
|
|
11
|
+
if (!valid)
|
|
12
|
+
throw new Error(`formIds must be at most ${MAX_FORM_IDS} unique short form ids.`);
|
|
13
|
+
return [...formIds];
|
|
14
|
+
}
|
|
15
|
+
/** Exactly one of a valid `formIds` list or `allForms: true`. @complexity O(f) form ids. */
|
|
16
|
+
function checkedForms(formIds, allForms) {
|
|
17
|
+
if (allForms === undefined && formIds !== undefined)
|
|
18
|
+
return { formIds: checkedFormIds(formIds) };
|
|
19
|
+
if (allForms === true && formIds === undefined)
|
|
20
|
+
return { allForms: true };
|
|
21
|
+
throw new Error('A delivery binding must name its forms: formIds (a list; [] for none) or allForms: true for every current and future form, not both.');
|
|
22
|
+
}
|
|
23
|
+
/** The forms a host-listed binding states; a binding stating neither is a host bug, never "every form". */
|
|
24
|
+
function listedForms(binding) {
|
|
25
|
+
if (Array.isArray(binding.formIds))
|
|
26
|
+
return { formIds: [...binding.formIds] };
|
|
27
|
+
if (binding.allForms === true)
|
|
28
|
+
return { allForms: true };
|
|
29
|
+
throw new Error(`The host listed delivery binding '${named(binding.bindingId)}' without formIds or allForms; fix the host's binding store.`);
|
|
30
|
+
}
|
|
31
|
+
function checkedConfig(gate, config) {
|
|
32
|
+
if (!isRecord(config))
|
|
33
|
+
throw new Error("A delivery binding's config must be a JSON object.");
|
|
34
|
+
return storedStateValue({ gate, value: config });
|
|
35
|
+
}
|
|
36
|
+
/** The draft the host receives: only the known fields, each checked, the config a stored copy. @complexity O(f + n) form ids plus config size. */
|
|
37
|
+
function checkedDraft(gate, allow, draft) {
|
|
38
|
+
if (!isRecord(draft))
|
|
39
|
+
throw new Error('A delivery binding must be an object.');
|
|
40
|
+
const { bindingId, adapterId, connectionId, enabled, formIds, allForms, config } = draft;
|
|
41
|
+
if (!allow.adapterIds.includes(adapterId))
|
|
42
|
+
throw new Error(`This admin surface does not declare delivery adapter '${named(adapterId)}', so its handlers cannot manage its bindings. Declare it in the surface's deliveryAdapters.`);
|
|
43
|
+
if (!allow.connectionIds.includes(connectionId))
|
|
44
|
+
throw new Error(`Delivery bindings may only use this surface's declared connections; '${named(connectionId)}' is not one of them.`);
|
|
45
|
+
if (typeof enabled !== 'boolean')
|
|
46
|
+
throw new Error('A delivery binding needs enabled: true or false.');
|
|
47
|
+
return {
|
|
48
|
+
...(bindingId !== undefined && { bindingId: bindingId }),
|
|
49
|
+
adapterId: adapterId, connectionId: connectionId, enabled,
|
|
50
|
+
...checkedForms(formIds, allForms),
|
|
51
|
+
config: checkedConfig(gate, config),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** The port shape, whatever else the host's object carried (a connection ref), refused when it mentions an obtained token. */
|
|
55
|
+
function toBinding(gate, binding) {
|
|
56
|
+
const { bindingId, adapterId, connectionId, enabled, config } = binding;
|
|
57
|
+
const shaped = { bindingId, adapterId, connectionId, enabled, ...listedForms(binding), config };
|
|
58
|
+
if (gate.mentionsToken({ value: shaped }))
|
|
59
|
+
throw new Error(DELIVERY_BINDING_WITHHELD_MESSAGE);
|
|
60
|
+
return shaped;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Wraps the host's port for one admin-surface call.
|
|
64
|
+
* @param required.gate - The call's capability gate (revocation and token scans).
|
|
65
|
+
* @param required.host - The host's port, already scoped to the plugin and workspace.
|
|
66
|
+
* @param required.adapterIds - The surface's declared delivery adapters.
|
|
67
|
+
* @param required.connectionIds - The surface's declared connection ids.
|
|
68
|
+
* @param optional.actorId - The signed-in admin; every call is refused without one.
|
|
69
|
+
* @returns The checked port.
|
|
70
|
+
* @complexity Each call is one host list (plus one save or remove), O(b) over the listed bindings.
|
|
71
|
+
*/
|
|
72
|
+
export function guardDeliveryBindings(required,
|
|
73
|
+
// `| undefined`: the session forwards its own optional actor as is.
|
|
74
|
+
optional = {}) {
|
|
75
|
+
const { gate, host } = required;
|
|
76
|
+
const forActor = (call) => gate.guard(async (...args) => {
|
|
77
|
+
if (optional.actorId === undefined)
|
|
78
|
+
throw new Error('Delivery settings need a signed-in admin. Open the surface from an authenticated admin page and retry.');
|
|
79
|
+
return call(...args);
|
|
80
|
+
});
|
|
81
|
+
const ours = async () => (await host.list()).filter((binding) => required.adapterIds.includes(binding.adapterId));
|
|
82
|
+
/** The id when it names one of this surface's bindings; a plugin never reaches another adapter's binding. */
|
|
83
|
+
const ownId = async (bindingId) => {
|
|
84
|
+
if (!(await ours()).some((binding) => binding.bindingId === bindingId))
|
|
85
|
+
throw new Error(`No delivery binding '${named(bindingId)}' belongs to this surface's delivery adapters; list the bindings and retry.`);
|
|
86
|
+
return bindingId;
|
|
87
|
+
};
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
list: forActor(async () => (await ours()).map((binding) => toBinding(gate, binding))),
|
|
90
|
+
save: forActor(async (draft) => {
|
|
91
|
+
const checked = checkedDraft(gate, required, draft);
|
|
92
|
+
if (checked.bindingId !== undefined)
|
|
93
|
+
await ownId(checked.bindingId);
|
|
94
|
+
return toBinding(gate, await host.save(checked));
|
|
95
|
+
}),
|
|
96
|
+
remove: forActor(async ({ bindingId }) => { await host.remove({ bindingId: await ownId(bindingId) }); }),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=delivery-bindings.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Parses a plugin's admin-surface contribution file: which surfaces it ships, the contained
|
|
3
|
+
* files each one serves, the actions it may ask the host to run (with their schemas), the few
|
|
4
|
+
* extra Content-Security-Policy sources it needs and, optionally, the server-side handler module that
|
|
5
|
+
* answers those actions (`./surface-handlers.ts`) with the schema of the bootstrap data it provides
|
|
6
|
+
* and the connections and public config its handlers may use (`./capability-declarations.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Why parsing is strict: everything here is later trusted by the host. A file path that is not a
|
|
9
|
+
* plain relative path, an action schema outside the supported subset, or a CSP source that is not a
|
|
10
|
+
* bare `https://` origin refuses the WHOLE file, so a half-valid declaration never widens what a
|
|
11
|
+
* surface can reach. Containment on disk (symlinks) is the lifecycle's job (`readTrustedPluginFile`);
|
|
12
|
+
* this file only guarantees the declared strings cannot name anything outside the package root.
|
|
13
|
+
*/
|
|
14
|
+
import type { AgentPluginToolEffect } from '../lifecycle/tools/manifest.js';
|
|
15
|
+
import { type AdminSurfaceActionSchema } from './action-schema.js';
|
|
16
|
+
import { type AdminSurfaceCapabilityDeclarations } from './capability-declarations.js';
|
|
17
|
+
/** The CSP directives a descriptor may extend. Everything else stays at the strict default. */
|
|
18
|
+
export declare const ADMIN_SURFACE_CSP_DIRECTIVES: readonly ["script-src", "style-src", "img-src", "font-src", "connect-src", "frame-src"];
|
|
19
|
+
export type AdminSurfaceCspDirective = (typeof ADMIN_SURFACE_CSP_DIRECTIVES)[number];
|
|
20
|
+
export interface AdminSurfaceAction {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
/** Assistant confirmation class; omitted declarations are writes. */
|
|
23
|
+
readonly effect?: AgentPluginToolEffect;
|
|
24
|
+
readonly input: AdminSurfaceActionSchema;
|
|
25
|
+
/** When declared, the dispatcher refuses a handler result that does not conform. */
|
|
26
|
+
readonly result?: AdminSurfaceActionSchema;
|
|
27
|
+
}
|
|
28
|
+
export interface AdminSurfaceDescriptor extends AdminSurfaceCapabilityDeclarations {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly title: string;
|
|
31
|
+
/** The `.html` document the frame loads, relative to the plugin root. */
|
|
32
|
+
readonly entry: string;
|
|
33
|
+
/** Other files the entry loads (`.mjs`, `.js`, `.css`, `.json`); nothing else is ever served. */
|
|
34
|
+
readonly files: readonly string[];
|
|
35
|
+
readonly actions: readonly AdminSurfaceAction[];
|
|
36
|
+
readonly csp: Readonly<Partial<Record<AdminSurfaceCspDirective, readonly string[]>>>;
|
|
37
|
+
/** A server-side `.mjs` module exporting this surface's action handlers and optional bootstrap
|
|
38
|
+
* provider (`./surface-handlers.ts`). Loaded only through the executable trust gate; never served. */
|
|
39
|
+
readonly handlerModule?: string;
|
|
40
|
+
/** The schema bootstrap data from `handlerModule` must match before it reaches the frame. */
|
|
41
|
+
readonly bootstrap?: AdminSurfaceActionSchema;
|
|
42
|
+
/** Bare https origins the host may open links to for this surface (`./external-links.ts`); present only when declared. */
|
|
43
|
+
readonly linkOrigins?: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
/** True when `value` is a relative POSIX path whose every segment is plain and that ends in one of
|
|
46
|
+
* `extensions`. @complexity O(n) in the path length. */
|
|
47
|
+
export declare function isContainedAdminSurfacePath(required: {
|
|
48
|
+
readonly value: unknown;
|
|
49
|
+
readonly extensions: readonly string[];
|
|
50
|
+
}, _optional?: Record<string, never>): boolean;
|
|
51
|
+
/** True when `value` is a CSP source this protocol allows a plugin to add.
|
|
52
|
+
* @complexity O(n) in the source length. */
|
|
53
|
+
export declare function isAllowedAdminSurfaceCspSource(required: {
|
|
54
|
+
readonly value: unknown;
|
|
55
|
+
}, _optional?: Record<string, never>): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Parses the contribution file. One invalid surface refuses the whole file (the loader reports it).
|
|
58
|
+
* @complexity O(s * (f + a + c)) surfaces with their files, actions and CSP sources; input bounded to 64 KiB.
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseAdminSurfacesFile(required: {
|
|
61
|
+
readonly raw: string;
|
|
62
|
+
}, _optional?: Record<string, never>): {
|
|
63
|
+
readonly ok: true;
|
|
64
|
+
readonly descriptors: readonly AdminSurfaceDescriptor[];
|
|
65
|
+
} | {
|
|
66
|
+
readonly ok: false;
|
|
67
|
+
readonly reason: string;
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=descriptor.d.ts.map
|