@jini-ai/agent-plugins 0.4.7 → 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 +21 -0
- package/README.md +46 -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.js +59 -49
- package/dist/folder-zip.js +9 -7
- 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.js +7 -0
- package/dist/lifecycle/installed-package-index.js +41 -29
- package/dist/lifecycle/installed-package-roots.js +28 -20
- package/dist/lifecycle/manifest.d.ts +13 -0
- package/dist/lifecycle/manifest.js +115 -101
- 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 +15 -2
- 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/package.json +38 -6
- package/ui-ux-design/plugin.json +7 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Host-mediated external links: a surface declares the https origins it may link to
|
|
3
|
+
* (`linkOrigins`, parsed in `./descriptor.ts`), and asks the host page to open a URL with an `open-link` message; the host page
|
|
4
|
+
* passes that message to `openAdminSurfaceLink`, which opens it through the host's own opener only
|
|
5
|
+
* when it comes from this mount and names a declared https origin.
|
|
6
|
+
*
|
|
7
|
+
* Why the host opens links: a surface frame is sandboxed without `allow-popups` or top navigation,
|
|
8
|
+
* so it cannot open anything itself, and must not: the host decides HOW (a new tab with `noopener`
|
|
9
|
+
* on the web, the system browser on desktop). Why the gate is this strict: a link the host opens
|
|
10
|
+
* leaves the sandbox entirely, so the destination is limited to what the shipped, digest-checked
|
|
11
|
+
* surfaces file declared, and the opener receives the URL parser's normalized form so it never
|
|
12
|
+
* re-parses a string this gate judged differently.
|
|
13
|
+
*/
|
|
14
|
+
import { type AdminSurfaceMessageEvent, type AdminSurfaceMountRecord } from './host-validator.js';
|
|
15
|
+
import { type AdminSurfaceActionResponse } from './protocol.js';
|
|
16
|
+
/** Host port: opens an already-checked https URL however the host decides (new tab with `noopener`, system browser). */
|
|
17
|
+
export type AdminSurfaceOpenLinkPort = (required: {
|
|
18
|
+
readonly url: string;
|
|
19
|
+
}, optional?: Record<string, never>) => void | Promise<void>;
|
|
20
|
+
/** `ignored`: not this mount's open-link message; drop silently. Otherwise post `response` back to the frame. */
|
|
21
|
+
export type AdminSurfaceLinkVerdict = {
|
|
22
|
+
readonly ok: true;
|
|
23
|
+
readonly url: string;
|
|
24
|
+
readonly response: AdminSurfaceActionResponse;
|
|
25
|
+
} | {
|
|
26
|
+
readonly ok: false;
|
|
27
|
+
readonly ignored: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly ok: false;
|
|
30
|
+
readonly refused: string;
|
|
31
|
+
readonly response: AdminSurfaceActionResponse;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Judges one `message` event as an open-link request for one mount and, when it passes, opens it
|
|
35
|
+
* through the host's port. Pass every message event here as well as to `validateAdminSurfaceMessage`;
|
|
36
|
+
* each ignores the other's messages.
|
|
37
|
+
* @param required.openLink - The host's opener; called at most once, with the normalized URL.
|
|
38
|
+
* @complexity O(n) in the URL length, plus the opener.
|
|
39
|
+
*/
|
|
40
|
+
export declare function openAdminSurfaceLink(required: {
|
|
41
|
+
readonly event: AdminSurfaceMessageEvent;
|
|
42
|
+
readonly mount: AdminSurfaceMountRecord;
|
|
43
|
+
readonly openLink: AdminSurfaceOpenLinkPort;
|
|
44
|
+
}, _optional?: Record<string, never>): Promise<AdminSurfaceLinkVerdict>;
|
|
45
|
+
//# sourceMappingURL=external-links.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Host-mediated external links: a surface declares the https origins it may link to
|
|
3
|
+
* (`linkOrigins`, parsed in `./descriptor.ts`), and asks the host page to open a URL with an `open-link` message; the host page
|
|
4
|
+
* passes that message to `openAdminSurfaceLink`, which opens it through the host's own opener only
|
|
5
|
+
* when it comes from this mount and names a declared https origin.
|
|
6
|
+
*
|
|
7
|
+
* Why the host opens links: a surface frame is sandboxed without `allow-popups` or top navigation,
|
|
8
|
+
* so it cannot open anything itself, and must not: the host decides HOW (a new tab with `noopener`
|
|
9
|
+
* on the web, the system browser on desktop). Why the gate is this strict: a link the host opens
|
|
10
|
+
* leaves the sandbox entirely, so the destination is limited to what the shipped, digest-checked
|
|
11
|
+
* surfaces file declared, and the opener receives the URL parser's normalized form so it never
|
|
12
|
+
* re-parses a string this gate judged differently.
|
|
13
|
+
*/
|
|
14
|
+
import { frameTransportRefusal, REQUEST_ID_PATTERN } from './host-validator.js';
|
|
15
|
+
import { ADMIN_SURFACE_PROTOCOL, ADMIN_SURFACE_PROTOCOL_VERSION, adminSurfaceActionResponse, } from './protocol.js';
|
|
16
|
+
/** Longer URLs are refused, not cut: a truncated link would open somewhere the plugin did not mean. */
|
|
17
|
+
const MAX_LINK_URL_LENGTH = 2048;
|
|
18
|
+
function isOpenLinkEnvelope(data) {
|
|
19
|
+
if (typeof data !== 'object' || data === null)
|
|
20
|
+
return false;
|
|
21
|
+
const message = data;
|
|
22
|
+
const isProtocol = message.protocol === ADMIN_SURFACE_PROTOCOL && message.version === ADMIN_SURFACE_PROTOCOL_VERSION && message.kind === 'open-link';
|
|
23
|
+
return isProtocol && typeof message.mountId === 'string' && typeof message.requestId === 'string' && REQUEST_ID_PATTERN.test(message.requestId) && typeof message.url === 'string';
|
|
24
|
+
}
|
|
25
|
+
/** The normalized URL when `raw` may be opened, or why not. Messages never echo the URL. @complexity O(n) in the URL length. */
|
|
26
|
+
function checkLink(raw, origins) {
|
|
27
|
+
if (raw.length > MAX_LINK_URL_LENGTH)
|
|
28
|
+
return { code: 'link-too-long', message: 'The link is too long.' };
|
|
29
|
+
if (!URL.canParse(raw))
|
|
30
|
+
return { code: 'link-invalid', message: 'That link is not a valid web address.' };
|
|
31
|
+
const parsed = new URL(raw);
|
|
32
|
+
if (parsed.protocol !== 'https:')
|
|
33
|
+
return { code: 'link-not-https', message: 'Only https links can be opened.' };
|
|
34
|
+
// Credentials in a link are a phishing shape (`https://trusted@elsewhere`) and never needed to open a page.
|
|
35
|
+
if (parsed.username !== '' || parsed.password !== '')
|
|
36
|
+
return { code: 'link-invalid', message: 'That link is not a valid web address.' };
|
|
37
|
+
if (!origins.includes(parsed.origin))
|
|
38
|
+
return { code: 'link-undeclared', message: 'This surface did not declare that link destination.' };
|
|
39
|
+
return { url: parsed.href };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Judges one `message` event as an open-link request for one mount and, when it passes, opens it
|
|
43
|
+
* through the host's port. Pass every message event here as well as to `validateAdminSurfaceMessage`;
|
|
44
|
+
* each ignores the other's messages.
|
|
45
|
+
* @param required.openLink - The host's opener; called at most once, with the normalized URL.
|
|
46
|
+
* @complexity O(n) in the URL length, plus the opener.
|
|
47
|
+
*/
|
|
48
|
+
export async function openAdminSurfaceLink(required, _optional = {}) {
|
|
49
|
+
const { event, mount, openLink } = required;
|
|
50
|
+
const transport = frameTransportRefusal({ event, mount });
|
|
51
|
+
if (transport !== null)
|
|
52
|
+
return { ok: false, ignored: transport };
|
|
53
|
+
if (!isOpenLinkEnvelope(event.data))
|
|
54
|
+
return { ok: false, ignored: 'not-an-open-link' };
|
|
55
|
+
const request = event.data;
|
|
56
|
+
if (request.mountId !== mount.mountId)
|
|
57
|
+
return { ok: false, ignored: 'mount-mismatch' };
|
|
58
|
+
const refuse = (refusal, retryable = false) => ({ ok: false, refused: refusal.code, response: adminSurfaceActionResponse({ request, outcome: { ok: false, error: { ...refusal, retryable } } }) });
|
|
59
|
+
const checked = checkLink(request.url, mount.descriptor.linkOrigins ?? []);
|
|
60
|
+
if (!('url' in checked))
|
|
61
|
+
return refuse(checked);
|
|
62
|
+
try {
|
|
63
|
+
await openLink({ url: checked.url });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return refuse({ code: 'link-failed', message: 'The host could not open the link. Try again.' }, true);
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, url: checked.url, response: adminSurfaceActionResponse({ request, outcome: { ok: true, result: null } }) };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=external-links.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AdminSurfaceDescriptor } from './descriptor.js';
|
|
2
|
+
import { type AdminSurfaceActionRequest, type AdminSurfaceActionResponse } from './protocol.js';
|
|
3
|
+
/** The fields of a `MessageEvent` the gate reads; a real event satisfies it structurally. */
|
|
4
|
+
export interface AdminSurfaceMessageEvent {
|
|
5
|
+
readonly origin: string;
|
|
6
|
+
readonly source: unknown;
|
|
7
|
+
readonly data: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface AdminSurfaceMountRecord {
|
|
10
|
+
readonly mountId: string;
|
|
11
|
+
/** The frame's `contentWindow`; the event's `source` must be this exact object. */
|
|
12
|
+
readonly frame: unknown;
|
|
13
|
+
/** `linkOrigins` matters only to `openAdminSurfaceLink`; a record without it opens no links. */
|
|
14
|
+
readonly descriptor: Pick<AdminSurfaceDescriptor, 'actions' | 'linkOrigins'>;
|
|
15
|
+
}
|
|
16
|
+
/** `ignored`: not addressed to this mount; drop silently. `refused`: this mount's request was invalid;
|
|
17
|
+
* post `response` back so the surface can show why. */
|
|
18
|
+
export type AdminSurfaceMessageVerdict = {
|
|
19
|
+
readonly ok: true;
|
|
20
|
+
readonly request: AdminSurfaceActionRequest;
|
|
21
|
+
} | {
|
|
22
|
+
readonly ok: false;
|
|
23
|
+
readonly ignored: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly ok: false;
|
|
26
|
+
readonly refused: string;
|
|
27
|
+
readonly response: AdminSurfaceActionResponse;
|
|
28
|
+
};
|
|
29
|
+
export declare const REQUEST_ID_PATTERN: RegExp;
|
|
30
|
+
/** Character length of the payload as JSON, or Infinity when it is not JSON-serializable. */
|
|
31
|
+
/** Whether `event` comes from `mount`'s frame at all (origin and window), for every inbound message kind.
|
|
32
|
+
* @returns The failed check, or `null`. @complexity O(1). */
|
|
33
|
+
export declare function frameTransportRefusal(required: {
|
|
34
|
+
readonly event: AdminSurfaceMessageEvent;
|
|
35
|
+
readonly mount: Pick<AdminSurfaceMountRecord, 'frame'>;
|
|
36
|
+
}, _optional?: Record<string, never>): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Judges one `message` event against one mount.
|
|
39
|
+
* @complexity O(p) in the payload size (one JSON measure plus one schema walk).
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateAdminSurfaceMessage(required: {
|
|
42
|
+
readonly event: AdminSurfaceMessageEvent;
|
|
43
|
+
readonly mount: AdminSurfaceMountRecord;
|
|
44
|
+
}, _optional?: Record<string, never>): AdminSurfaceMessageVerdict;
|
|
45
|
+
/** Frame heights a host accepts by default (CSS px): tall enough for a notice, short enough that a
|
|
46
|
+
* surface cannot push the admin page out of reach. */
|
|
47
|
+
export declare const ADMIN_SURFACE_MIN_HEIGHT = 96;
|
|
48
|
+
export declare const ADMIN_SURFACE_MAX_HEIGHT = 4000;
|
|
49
|
+
/**
|
|
50
|
+
* The frame height `event` asks for when it is `mount`'s own `resize` message (same origin, window and
|
|
51
|
+
* mount as every other inbound message), clamped to `[min, max]`; null for anything else, which the
|
|
52
|
+
* caller ignores. A size is cosmetic, so a bad one is dropped rather than answered.
|
|
53
|
+
* @complexity O(1).
|
|
54
|
+
*/
|
|
55
|
+
export declare function readAdminSurfaceResize(required: {
|
|
56
|
+
readonly event: AdminSurfaceMessageEvent;
|
|
57
|
+
readonly mount: Pick<AdminSurfaceMountRecord, 'mountId' | 'frame'>;
|
|
58
|
+
}, optional?: {
|
|
59
|
+
readonly min?: number;
|
|
60
|
+
readonly max?: number;
|
|
61
|
+
}): number | null;
|
|
62
|
+
//# sourceMappingURL=host-validator.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The host page's gate for messages arriving from a mounted admin surface frame.
|
|
3
|
+
*
|
|
4
|
+
* Why every check is needed: all sandboxed frames share the origin "null", so the origin check only
|
|
5
|
+
* proves "some sandboxed frame"; the window identity proves it is THIS mount's
|
|
6
|
+
* frame; the nonce proves the frame still runs the document the host initialized (a navigated frame
|
|
7
|
+
* never received it). Action and schema checks here are an early refusal for the person using the
|
|
8
|
+
* surface; the server-side dispatcher repeats them because a host route can be called directly.
|
|
9
|
+
*/
|
|
10
|
+
import { exceedsAdminSurfacePayloadLimit, validateAdminSurfaceActionValue } from './action-schema.js';
|
|
11
|
+
import { ADMIN_SURFACE_PROTOCOL, ADMIN_SURFACE_PROTOCOL_VERSION, SANDBOXED_FRAME_ORIGIN, adminSurfaceActionResponse, } from './protocol.js';
|
|
12
|
+
export const REQUEST_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
13
|
+
function isRequestEnvelope(data) {
|
|
14
|
+
if (typeof data !== 'object' || data === null)
|
|
15
|
+
return false;
|
|
16
|
+
const message = data;
|
|
17
|
+
const isProtocol = message.protocol === ADMIN_SURFACE_PROTOCOL && message.version === ADMIN_SURFACE_PROTOCOL_VERSION && message.kind === 'action-request';
|
|
18
|
+
return isProtocol && hasRequestFields(message);
|
|
19
|
+
}
|
|
20
|
+
function hasRequestFields(message) {
|
|
21
|
+
return typeof message.mountId === 'string' && typeof message.requestId === 'string' && REQUEST_ID_PATTERN.test(message.requestId)
|
|
22
|
+
&& typeof message.action === 'string' && Object.hasOwn(message, 'payload');
|
|
23
|
+
}
|
|
24
|
+
/** Character length of the payload as JSON, or Infinity when it is not JSON-serializable. */
|
|
25
|
+
/** Whether `event` comes from `mount`'s frame at all (origin and window), for every inbound message kind.
|
|
26
|
+
* @returns The failed check, or `null`. @complexity O(1). */
|
|
27
|
+
export function frameTransportRefusal(required, _optional = {}) {
|
|
28
|
+
const { event, mount } = required;
|
|
29
|
+
// "null" is the origin of every frame sandboxed with `allow-scripts` only; nothing else may speak for a mount.
|
|
30
|
+
if (event.origin !== SANDBOXED_FRAME_ORIGIN)
|
|
31
|
+
return 'origin-mismatch';
|
|
32
|
+
return event.source === mount.frame ? null : 'source-mismatch';
|
|
33
|
+
}
|
|
34
|
+
/** Which transport check `event` fails for `mount`, or `null` when it is this mount's well-formed request. */
|
|
35
|
+
function transportRefusal(event, mount) {
|
|
36
|
+
const frame = frameTransportRefusal({ event, mount });
|
|
37
|
+
if (frame !== null)
|
|
38
|
+
return frame;
|
|
39
|
+
if (!isRequestEnvelope(event.data))
|
|
40
|
+
return 'not-an-action-request';
|
|
41
|
+
return event.data.mountId === mount.mountId ? null : 'mount-mismatch';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Judges one `message` event against one mount.
|
|
45
|
+
* @complexity O(p) in the payload size (one JSON measure plus one schema walk).
|
|
46
|
+
*/
|
|
47
|
+
export function validateAdminSurfaceMessage(required, _optional = {}) {
|
|
48
|
+
const { event, mount } = required;
|
|
49
|
+
const ignored = transportRefusal(event, mount);
|
|
50
|
+
if (ignored !== null)
|
|
51
|
+
return { ok: false, ignored };
|
|
52
|
+
const request = event.data;
|
|
53
|
+
const refuse = (code, message) => ({ ok: false, refused: code, response: adminSurfaceActionResponse({ request, outcome: { ok: false, error: { code, message, retryable: false } } }) });
|
|
54
|
+
const action = mount.descriptor.actions.find((declared) => declared.name === request.action);
|
|
55
|
+
if (action === undefined)
|
|
56
|
+
return refuse('action-undeclared', 'This surface did not declare that action.');
|
|
57
|
+
if (exceedsAdminSurfacePayloadLimit({ payload: request.payload }))
|
|
58
|
+
return refuse('invalid-payload', 'The request is too large.');
|
|
59
|
+
const violation = validateAdminSurfaceActionValue({ schema: action.input, value: request.payload });
|
|
60
|
+
if (violation !== null)
|
|
61
|
+
return refuse('invalid-payload', `The request does not match the action's input: ${violation}.`);
|
|
62
|
+
return { ok: true, request };
|
|
63
|
+
}
|
|
64
|
+
/** Frame heights a host accepts by default (CSS px): tall enough for a notice, short enough that a
|
|
65
|
+
* surface cannot push the admin page out of reach. */
|
|
66
|
+
export const ADMIN_SURFACE_MIN_HEIGHT = 96;
|
|
67
|
+
export const ADMIN_SURFACE_MAX_HEIGHT = 4000;
|
|
68
|
+
function isResizeForMount({ message, mountId }, _options = {}) {
|
|
69
|
+
const isResize = message.protocol === ADMIN_SURFACE_PROTOCOL && message.version === ADMIN_SURFACE_PROTOCOL_VERSION && message.kind === 'resize';
|
|
70
|
+
return isResize && message.mountId === mountId;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The frame height `event` asks for when it is `mount`'s own `resize` message (same origin, window and
|
|
74
|
+
* mount as every other inbound message), clamped to `[min, max]`; null for anything else, which the
|
|
75
|
+
* caller ignores. A size is cosmetic, so a bad one is dropped rather than answered.
|
|
76
|
+
* @complexity O(1).
|
|
77
|
+
*/
|
|
78
|
+
export function readAdminSurfaceResize(required, optional = {}) {
|
|
79
|
+
const { event, mount } = required;
|
|
80
|
+
if (frameTransportRefusal({ event, mount }) !== null || typeof event.data !== 'object' || event.data === null)
|
|
81
|
+
return null;
|
|
82
|
+
const message = event.data;
|
|
83
|
+
if (!isResizeForMount({ message, mountId: mount.mountId }) || typeof message.height !== 'number' || !Number.isFinite(message.height))
|
|
84
|
+
return null;
|
|
85
|
+
return Math.min(optional.max ?? ADMIN_SURFACE_MAX_HEIGHT, Math.max(optional.min ?? ADMIN_SURFACE_MIN_HEIGHT, Math.ceil(message.height)));
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=host-validator.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Public barrel for `@jini-ai/agent-plugins/admin-surfaces`: plugin-owned admin surfaces
|
|
3
|
+
* (sandboxed iframes) and the host-mediated action protocol. See `./README.md`.
|
|
4
|
+
*
|
|
5
|
+
* Explicit on purpose: only what a host composes is public. The parsers, checkers and message
|
|
6
|
+
* constants behind these entry points stay internal, so they can change without a host noticing.
|
|
7
|
+
*/
|
|
8
|
+
export { adminSurfaceInitMessage, adminSurfaceActionResponse, type AdminSurfaceBootstrap, type AdminSurfaceInitMessage, type AdminSurfaceActionRequest, type AdminSurfaceActionFailure, type AdminSurfaceActionOutcome, type AdminSurfaceActionResponse, type AdminSurfaceOpenLinkRequest, type AdminSurfaceResizeMessage, } from './protocol.js';
|
|
9
|
+
export { readAdminSurfaceTheme, adminSurfaceThemeMessage, ADMIN_SURFACE_THEME_TOKENS, type AdminSurfaceTheme, type AdminSurfaceThemeToken, type AdminSurfaceColorScheme, type AdminSurfaceThemeMessage, } from './theme.js';
|
|
10
|
+
export type { AdminSurfaceActionSchema } from './action-schema.js';
|
|
11
|
+
export { parseAdminSurfacesFile, type AdminSurfaceCspDirective, type AdminSurfaceAction, type AdminSurfaceDescriptor } from './descriptor.js';
|
|
12
|
+
export { adminSurfaceFrameAttributes, createAdminSurfaceMount, createAdminSurfaceBootstrap, adminSurfaceBootstrapHeaders } from './sandbox.js';
|
|
13
|
+
export { validateAdminSurfaceMessage, readAdminSurfaceResize, ADMIN_SURFACE_MIN_HEIGHT, ADMIN_SURFACE_MAX_HEIGHT, type AdminSurfaceMessageEvent, type AdminSurfaceMountRecord, type AdminSurfaceMessageVerdict, } from './host-validator.js';
|
|
14
|
+
export { createAdminSurfaceActionDispatcher, AdminSurfaceActionError, type AdminSurfaceActionHandler } from './action-dispatcher.js';
|
|
15
|
+
export { defineAdminSurfaceContribution, readAdminSurfaceFile, type LoadedAdminSurface } from './contribution.js';
|
|
16
|
+
export { defineAdminSurfaceHandlerContribution, createAdminSurfaceHandlerHost, type AdminSurfaceHandlerContext, type AdminSurfacePluginContext, type AdminSurfaceBootstrapCall, type AdminSurfaceHandlerCall, type AdminSurfaceHandlerModule, type LoadedAdminSurfaceHandlers, type AdminSurfaceHostKind, } from './surface-handlers.js';
|
|
17
|
+
export type { AdminSurfaceConnectionDeclaration, AdminSurfaceCapabilityDeclarations } from './capability-declarations.js';
|
|
18
|
+
export { ADMIN_SURFACE_ACTION_INTENTS, ADMIN_SURFACE_STATE_LIMITS, type AdminSurfaceConnectionState, type AdminSurfaceConnectionAction, type AdminSurfaceRevocationOutcome, type AdminSurfaceConnectionStatus, type AdminSurfaceSignInAttempt, type AdminSurfaceAuthorizationStart, type AdminSurfaceConnections, type AdminSurfaceStateValue, type AdminSurfaceState, type AdminSurfacePublicConfig, type AdminSurfaceForm, type AdminSurfaceForms, type AdminSurfaceCapabilities, type AdminSurfaceCapabilitiesPort, } from './surface-capabilities.js';
|
|
19
|
+
export type { AdminSurfaceDeliveryBinding, AdminSurfaceDeliveryBindingDraft, AdminSurfaceDeliveryBindingForms, AdminSurfaceDeliveryBindings, } from './delivery-bindings.js';
|
|
20
|
+
export { openAdminSurfaceLink, type AdminSurfaceOpenLinkPort, type AdminSurfaceLinkVerdict } from './external-links.js';
|
|
21
|
+
export { createAdminSurfaceCallbackInbox, type AdminSurfaceCallbackFieldKey } from './callback-field-inbox.js';
|
|
22
|
+
export { buildAdminSurfaceActionTools, type AdminSurfaceToolSource, type AdminSurfaceToolContext } from './action-tools.js';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Public barrel for `@jini-ai/agent-plugins/admin-surfaces`: plugin-owned admin surfaces
|
|
3
|
+
* (sandboxed iframes) and the host-mediated action protocol. See `./README.md`.
|
|
4
|
+
*
|
|
5
|
+
* Explicit on purpose: only what a host composes is public. The parsers, checkers and message
|
|
6
|
+
* constants behind these entry points stay internal, so they can change without a host noticing.
|
|
7
|
+
*/
|
|
8
|
+
export { adminSurfaceInitMessage, adminSurfaceActionResponse, } from './protocol.js';
|
|
9
|
+
export { readAdminSurfaceTheme, adminSurfaceThemeMessage, ADMIN_SURFACE_THEME_TOKENS, } from './theme.js';
|
|
10
|
+
export { parseAdminSurfacesFile } from './descriptor.js';
|
|
11
|
+
export { adminSurfaceFrameAttributes, createAdminSurfaceMount, createAdminSurfaceBootstrap, adminSurfaceBootstrapHeaders } from './sandbox.js';
|
|
12
|
+
export { validateAdminSurfaceMessage, readAdminSurfaceResize, ADMIN_SURFACE_MIN_HEIGHT, ADMIN_SURFACE_MAX_HEIGHT, } from './host-validator.js';
|
|
13
|
+
// AdminSurfaceActionError stays public: a host's OWN handlers throw it to send a code the surface can show.
|
|
14
|
+
export { createAdminSurfaceActionDispatcher, AdminSurfaceActionError } from './action-dispatcher.js';
|
|
15
|
+
export { defineAdminSurfaceContribution, readAdminSurfaceFile } from './contribution.js';
|
|
16
|
+
export { defineAdminSurfaceHandlerContribution, createAdminSurfaceHandlerHost, } from './surface-handlers.js';
|
|
17
|
+
export { ADMIN_SURFACE_ACTION_INTENTS, ADMIN_SURFACE_STATE_LIMITS, } from './surface-capabilities.js';
|
|
18
|
+
export { openAdminSurfaceLink } from './external-links.js';
|
|
19
|
+
export { createAdminSurfaceCallbackInbox } from './callback-field-inbox.js';
|
|
20
|
+
export { buildAdminSurfaceActionTools } from './action-tools.js';
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The wire vocabulary shared by the host page, the host server and a plugin's admin surface:
|
|
3
|
+
* protocol constants and the message envelopes (init, action request, open-link request, response).
|
|
4
|
+
*
|
|
5
|
+
* Why a separate file: the host-side validator, the server-side dispatcher and the docs all name the
|
|
6
|
+
* same envelope, and it must stay one definition. The surface-side client (`./surface-client.ts`)
|
|
7
|
+
* deliberately repeats the constants instead of importing them so a plugin can copy that one file;
|
|
8
|
+
* the vertical-slice test proves both sides still agree.
|
|
9
|
+
*/
|
|
10
|
+
import type { AdminSurfaceTheme } from './theme.js';
|
|
11
|
+
/** Discriminates this protocol's messages from anything else posted to the same window. */
|
|
12
|
+
export declare const ADMIN_SURFACE_PROTOCOL = "jini.admin-surface";
|
|
13
|
+
export declare const ADMIN_SURFACE_PROTOCOL_VERSION = 1;
|
|
14
|
+
/** The ONLY sandbox token a surface frame gets. Adding `allow-same-origin` next to `allow-scripts`
|
|
15
|
+
* lets the frame remove its own sandbox, so it is never granted. */
|
|
16
|
+
export declare const ADMIN_SURFACE_SANDBOX = "allow-scripts";
|
|
17
|
+
/** A frame sandboxed without `allow-same-origin` has an opaque origin, which `MessageEvent.origin`
|
|
18
|
+
* serializes as the string "null". Every sandboxed frame shares it, so origin alone never identifies
|
|
19
|
+
* a mount: the per-mount nonce and the frame's window identity do. */
|
|
20
|
+
export declare const SANDBOXED_FRAME_ORIGIN = "null";
|
|
21
|
+
/** Short-lived data the host server issues for one surface render (e.g. a scoped access token plus
|
|
22
|
+
* public config). Never cached (see `adminSurfaceBootstrapHeaders`), refused once `expiresAt` passes. */
|
|
23
|
+
export interface AdminSurfaceBootstrap<D = unknown> {
|
|
24
|
+
readonly surfaceId: string;
|
|
25
|
+
readonly issuedAt: number;
|
|
26
|
+
readonly expiresAt: number;
|
|
27
|
+
readonly data: D;
|
|
28
|
+
}
|
|
29
|
+
/** Host page -> surface, once per mount, after the frame loads. Carries the mount nonce and, when
|
|
30
|
+
* the host shares them, its design tokens (`./theme.ts`; later changes arrive as `theme` messages). */
|
|
31
|
+
export interface AdminSurfaceInitMessage {
|
|
32
|
+
readonly protocol: typeof ADMIN_SURFACE_PROTOCOL;
|
|
33
|
+
readonly version: typeof ADMIN_SURFACE_PROTOCOL_VERSION;
|
|
34
|
+
readonly kind: 'init';
|
|
35
|
+
readonly mountId: string;
|
|
36
|
+
readonly bootstrap?: AdminSurfaceBootstrap;
|
|
37
|
+
readonly theme?: AdminSurfaceTheme;
|
|
38
|
+
}
|
|
39
|
+
/** Surface -> host page. `mountId` must echo the nonce from init. */
|
|
40
|
+
export interface AdminSurfaceActionRequest {
|
|
41
|
+
readonly protocol: typeof ADMIN_SURFACE_PROTOCOL;
|
|
42
|
+
readonly version: typeof ADMIN_SURFACE_PROTOCOL_VERSION;
|
|
43
|
+
readonly kind: 'action-request';
|
|
44
|
+
readonly mountId: string;
|
|
45
|
+
readonly requestId: string;
|
|
46
|
+
readonly action: string;
|
|
47
|
+
readonly payload: unknown;
|
|
48
|
+
}
|
|
49
|
+
/** Surface -> host page: asks the host to open `url` outside the sandbox (`./external-links.ts`).
|
|
50
|
+
* Answered with an `action-response` for `requestId`, like an action. */
|
|
51
|
+
export interface AdminSurfaceOpenLinkRequest {
|
|
52
|
+
readonly protocol: typeof ADMIN_SURFACE_PROTOCOL;
|
|
53
|
+
readonly version: typeof ADMIN_SURFACE_PROTOCOL_VERSION;
|
|
54
|
+
readonly kind: 'open-link';
|
|
55
|
+
readonly mountId: string;
|
|
56
|
+
readonly requestId: string;
|
|
57
|
+
readonly url: string;
|
|
58
|
+
}
|
|
59
|
+
/** Surface-safe failure: `message` is written for the person using the surface and never carries a
|
|
60
|
+
* handler's raw error text, a token or a submitted value. */
|
|
61
|
+
/** Surface -> host: the surface's content height in CSS pixels, so the host can size the frame to fit. */
|
|
62
|
+
export interface AdminSurfaceResizeMessage {
|
|
63
|
+
readonly protocol: typeof ADMIN_SURFACE_PROTOCOL;
|
|
64
|
+
readonly version: typeof ADMIN_SURFACE_PROTOCOL_VERSION;
|
|
65
|
+
readonly kind: 'resize';
|
|
66
|
+
readonly mountId: string;
|
|
67
|
+
readonly height: number;
|
|
68
|
+
}
|
|
69
|
+
export interface AdminSurfaceActionFailure {
|
|
70
|
+
readonly code: string;
|
|
71
|
+
readonly message: string;
|
|
72
|
+
readonly retryable: boolean;
|
|
73
|
+
}
|
|
74
|
+
export type AdminSurfaceActionOutcome = {
|
|
75
|
+
readonly ok: true;
|
|
76
|
+
readonly result: unknown;
|
|
77
|
+
} | {
|
|
78
|
+
readonly ok: false;
|
|
79
|
+
readonly error: AdminSurfaceActionFailure;
|
|
80
|
+
};
|
|
81
|
+
/** Host page -> surface, answering exactly one request. */
|
|
82
|
+
export type AdminSurfaceActionResponse = {
|
|
83
|
+
readonly protocol: typeof ADMIN_SURFACE_PROTOCOL;
|
|
84
|
+
readonly version: typeof ADMIN_SURFACE_PROTOCOL_VERSION;
|
|
85
|
+
readonly kind: 'action-response';
|
|
86
|
+
readonly mountId: string;
|
|
87
|
+
readonly requestId: string;
|
|
88
|
+
} & AdminSurfaceActionOutcome;
|
|
89
|
+
/** Builds the init envelope the host page posts into a freshly loaded frame.
|
|
90
|
+
* @complexity O(1). */
|
|
91
|
+
export declare function adminSurfaceInitMessage(required: {
|
|
92
|
+
readonly mountId: string;
|
|
93
|
+
}, optional?: {
|
|
94
|
+
readonly bootstrap?: AdminSurfaceBootstrap;
|
|
95
|
+
readonly theme?: AdminSurfaceTheme;
|
|
96
|
+
}): AdminSurfaceInitMessage;
|
|
97
|
+
/** Wraps a dispatcher outcome as the response to one validated request.
|
|
98
|
+
* @complexity O(1). */
|
|
99
|
+
export declare function adminSurfaceActionResponse(required: {
|
|
100
|
+
readonly request: Pick<AdminSurfaceActionRequest, 'mountId' | 'requestId'>;
|
|
101
|
+
readonly outcome: AdminSurfaceActionOutcome;
|
|
102
|
+
}, _optional?: Record<string, never>): AdminSurfaceActionResponse;
|
|
103
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Discriminates this protocol's messages from anything else posted to the same window. */
|
|
2
|
+
export const ADMIN_SURFACE_PROTOCOL = 'jini.admin-surface';
|
|
3
|
+
export const ADMIN_SURFACE_PROTOCOL_VERSION = 1;
|
|
4
|
+
/** The ONLY sandbox token a surface frame gets. Adding `allow-same-origin` next to `allow-scripts`
|
|
5
|
+
* lets the frame remove its own sandbox, so it is never granted. */
|
|
6
|
+
export const ADMIN_SURFACE_SANDBOX = 'allow-scripts';
|
|
7
|
+
/** A frame sandboxed without `allow-same-origin` has an opaque origin, which `MessageEvent.origin`
|
|
8
|
+
* serializes as the string "null". Every sandboxed frame shares it, so origin alone never identifies
|
|
9
|
+
* a mount: the per-mount nonce and the frame's window identity do. */
|
|
10
|
+
export const SANDBOXED_FRAME_ORIGIN = 'null';
|
|
11
|
+
/** Builds the init envelope the host page posts into a freshly loaded frame.
|
|
12
|
+
* @complexity O(1). */
|
|
13
|
+
export function adminSurfaceInitMessage(required, optional = {}) {
|
|
14
|
+
return {
|
|
15
|
+
protocol: ADMIN_SURFACE_PROTOCOL, version: ADMIN_SURFACE_PROTOCOL_VERSION, kind: 'init', mountId: required.mountId,
|
|
16
|
+
...(optional.bootstrap ? { bootstrap: optional.bootstrap } : {}), ...(optional.theme ? { theme: optional.theme } : {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Wraps a dispatcher outcome as the response to one validated request.
|
|
20
|
+
* @complexity O(1). */
|
|
21
|
+
export function adminSurfaceActionResponse(required, _optional = {}) {
|
|
22
|
+
const { mountId, requestId } = required.request;
|
|
23
|
+
return { protocol: ADMIN_SURFACE_PROTOCOL, version: ADMIN_SURFACE_PROTOCOL_VERSION, kind: 'action-response', mountId, requestId, ...required.outcome };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The isolation contract a host applies when it renders and serves an admin surface: the
|
|
3
|
+
* iframe attributes, the Content-Security-Policy and headers for each served surface file, the mount
|
|
4
|
+
* nonce, and the short-lived, never-cached bootstrap envelope.
|
|
5
|
+
*
|
|
6
|
+
* Why these are pure helpers instead of host code: every host must apply the SAME strict values, and
|
|
7
|
+
* the values are the security boundary (no same-origin, no inline script, no direct network access
|
|
8
|
+
* unless the descriptor declared and validation accepted an https origin). Hosts call these and only
|
|
9
|
+
* choose routes.
|
|
10
|
+
*/
|
|
11
|
+
import type { AdminSurfaceDescriptor } from './descriptor.js';
|
|
12
|
+
import { type AdminSurfaceBootstrap } from './protocol.js';
|
|
13
|
+
/**
|
|
14
|
+
* The attributes for the `<iframe>` that renders a surface. `sandbox` is exactly `allow-scripts`:
|
|
15
|
+
* the frame gets an opaque origin, so it cannot read the host's cookies, storage or DOM.
|
|
16
|
+
* @complexity O(1).
|
|
17
|
+
*/
|
|
18
|
+
export declare function adminSurfaceFrameAttributes(required: {
|
|
19
|
+
readonly src: string;
|
|
20
|
+
readonly title: string;
|
|
21
|
+
}, _optional?: Record<string, never>): {
|
|
22
|
+
readonly src: string;
|
|
23
|
+
readonly title: string;
|
|
24
|
+
readonly sandbox: "allow-scripts";
|
|
25
|
+
readonly referrerPolicy: "no-referrer";
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The CSP for every file of one surface. Starts from `default-src 'none'`; scripts, styles, images
|
|
29
|
+
* and fonts may come from the surface's own files; `connect-src` and `frame-src` are closed unless the
|
|
30
|
+
* descriptor declared validated https origins. The `sandbox` directive keeps the document sandboxed
|
|
31
|
+
* even when opened directly rather than through the host's iframe.
|
|
32
|
+
* @complexity O(c) declared CSP sources.
|
|
33
|
+
*/
|
|
34
|
+
export declare function adminSurfaceContentSecurityPolicy(required: {
|
|
35
|
+
readonly descriptor: Pick<AdminSurfaceDescriptor, 'csp'>;
|
|
36
|
+
}, _optional?: Record<string, never>): string;
|
|
37
|
+
/**
|
|
38
|
+
* Response headers for one served surface file. Script files carry `Access-Control-Allow-Origin: *`
|
|
39
|
+
* because a module script requested by an opaque-origin document is a cross-origin CORS fetch; the
|
|
40
|
+
* files are static plugin code fetched without credentials, so they must never contain secrets.
|
|
41
|
+
* @complexity O(c) declared CSP sources.
|
|
42
|
+
*/
|
|
43
|
+
export declare function adminSurfaceFileHeaders(required: {
|
|
44
|
+
readonly descriptor: Pick<AdminSurfaceDescriptor, 'csp'>;
|
|
45
|
+
readonly path: string;
|
|
46
|
+
}, _optional?: Record<string, never>): Readonly<Record<string, string>>;
|
|
47
|
+
/** One rendered surface: the nonce its messages must echo (they must also come from origin "null").
|
|
48
|
+
* @complexity O(1). */
|
|
49
|
+
export declare function createAdminSurfaceMount(required: {
|
|
50
|
+
readonly surfaceId: string;
|
|
51
|
+
}, optional?: {
|
|
52
|
+
readonly randomBytes?: (input: {
|
|
53
|
+
readonly length: number;
|
|
54
|
+
}) => Uint8Array;
|
|
55
|
+
}): {
|
|
56
|
+
surfaceId: string;
|
|
57
|
+
mountId: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The envelope a host server returns for one surface render. Its lifetime is fixed at
|
|
61
|
+
* {@link BOOTSTRAP_TTL_MS}, not caller-chosen: bootstrap data may include a short-lived access token.
|
|
62
|
+
* @complexity O(1).
|
|
63
|
+
*/
|
|
64
|
+
export declare function createAdminSurfaceBootstrap<D>(required: {
|
|
65
|
+
readonly surfaceId: string;
|
|
66
|
+
readonly data: D;
|
|
67
|
+
readonly nowMs: number;
|
|
68
|
+
}, _optional?: Record<string, never>): AdminSurfaceBootstrap<D>;
|
|
69
|
+
/** Headers for the bootstrap response: never stored by the browser or any intermediary cache.
|
|
70
|
+
* @complexity O(1). */
|
|
71
|
+
export declare function adminSurfaceBootstrapHeaders(_required?: Record<string, never>, _optional?: Record<string, never>): Readonly<Record<string, string>>;
|
|
72
|
+
/** True while the bootstrap may still be used.
|
|
73
|
+
* @complexity O(1). */
|
|
74
|
+
export declare function isAdminSurfaceBootstrapCurrent(required: {
|
|
75
|
+
readonly bootstrap: Pick<AdminSurfaceBootstrap, 'expiresAt'>;
|
|
76
|
+
readonly nowMs: number;
|
|
77
|
+
}, _optional?: Record<string, never>): boolean;
|
|
78
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ADMIN_SURFACE_SANDBOX } from './protocol.js';
|
|
2
|
+
/** Bootstrap data is meant to live for one render: one minute, fixed so no host can lengthen it. */
|
|
3
|
+
const BOOTSTRAP_TTL_MS = 60_000;
|
|
4
|
+
const CONTENT_TYPES = {
|
|
5
|
+
html: 'text/html; charset=utf-8',
|
|
6
|
+
mjs: 'text/javascript; charset=utf-8',
|
|
7
|
+
js: 'text/javascript; charset=utf-8',
|
|
8
|
+
css: 'text/css; charset=utf-8',
|
|
9
|
+
json: 'application/json; charset=utf-8',
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The attributes for the `<iframe>` that renders a surface. `sandbox` is exactly `allow-scripts`:
|
|
13
|
+
* the frame gets an opaque origin, so it cannot read the host's cookies, storage or DOM.
|
|
14
|
+
* @complexity O(1).
|
|
15
|
+
*/
|
|
16
|
+
export function adminSurfaceFrameAttributes(required, _optional = {}) {
|
|
17
|
+
return { src: required.src, title: required.title, sandbox: ADMIN_SURFACE_SANDBOX, referrerPolicy: 'no-referrer' };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The CSP for every file of one surface. Starts from `default-src 'none'`; scripts, styles, images
|
|
21
|
+
* and fonts may come from the surface's own files; `connect-src` and `frame-src` are closed unless the
|
|
22
|
+
* descriptor declared validated https origins. The `sandbox` directive keeps the document sandboxed
|
|
23
|
+
* even when opened directly rather than through the host's iframe.
|
|
24
|
+
* @complexity O(c) declared CSP sources.
|
|
25
|
+
*/
|
|
26
|
+
export function adminSurfaceContentSecurityPolicy(required, _optional = {}) {
|
|
27
|
+
const extra = (directive) => required.descriptor.csp[directive] ?? [];
|
|
28
|
+
const own = (directive) => [`'self'`, ...extra(directive)].join(' ');
|
|
29
|
+
const closed = (directive) => (extra(directive).length ? extra(directive).join(' ') : `'none'`);
|
|
30
|
+
return [
|
|
31
|
+
`default-src 'none'`,
|
|
32
|
+
`script-src ${own('script-src')}`,
|
|
33
|
+
`style-src ${own('style-src')}`,
|
|
34
|
+
`img-src ${own('img-src')}`,
|
|
35
|
+
`font-src ${own('font-src')}`,
|
|
36
|
+
`connect-src ${closed('connect-src')}`,
|
|
37
|
+
`frame-src ${closed('frame-src')}`,
|
|
38
|
+
`form-action 'none'`,
|
|
39
|
+
`base-uri 'none'`,
|
|
40
|
+
`frame-ancestors 'self'`,
|
|
41
|
+
`sandbox ${ADMIN_SURFACE_SANDBOX}`,
|
|
42
|
+
].join('; ');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Response headers for one served surface file. Script files carry `Access-Control-Allow-Origin: *`
|
|
46
|
+
* because a module script requested by an opaque-origin document is a cross-origin CORS fetch; the
|
|
47
|
+
* files are static plugin code fetched without credentials, so they must never contain secrets.
|
|
48
|
+
* @complexity O(c) declared CSP sources.
|
|
49
|
+
*/
|
|
50
|
+
export function adminSurfaceFileHeaders(required, _optional = {}) {
|
|
51
|
+
const extension = required.path.slice(required.path.lastIndexOf('.') + 1);
|
|
52
|
+
const script = extension === 'mjs' || extension === 'js';
|
|
53
|
+
return {
|
|
54
|
+
'Content-Type': CONTENT_TYPES[extension] ?? 'application/octet-stream',
|
|
55
|
+
'Content-Security-Policy': adminSurfaceContentSecurityPolicy({ descriptor: required.descriptor }),
|
|
56
|
+
'X-Content-Type-Options': 'nosniff',
|
|
57
|
+
'Referrer-Policy': 'no-referrer',
|
|
58
|
+
'Cache-Control': 'no-cache',
|
|
59
|
+
...(script ? { 'Access-Control-Allow-Origin': '*' } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** One rendered surface: the nonce its messages must echo (they must also come from origin "null").
|
|
63
|
+
* @complexity O(1). */
|
|
64
|
+
export function createAdminSurfaceMount(required, optional = {}) {
|
|
65
|
+
const randomBytes = optional.randomBytes ?? (({ length }) => globalThis.crypto.getRandomValues(new Uint8Array(length)));
|
|
66
|
+
const mountId = Array.from(randomBytes({ length: 16 }), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
67
|
+
return { surfaceId: required.surfaceId, mountId };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The envelope a host server returns for one surface render. Its lifetime is fixed at
|
|
71
|
+
* {@link BOOTSTRAP_TTL_MS}, not caller-chosen: bootstrap data may include a short-lived access token.
|
|
72
|
+
* @complexity O(1).
|
|
73
|
+
*/
|
|
74
|
+
export function createAdminSurfaceBootstrap(required, _optional = {}) {
|
|
75
|
+
return { surfaceId: required.surfaceId, issuedAt: required.nowMs, expiresAt: required.nowMs + BOOTSTRAP_TTL_MS, data: required.data };
|
|
76
|
+
}
|
|
77
|
+
/** Headers for the bootstrap response: never stored by the browser or any intermediary cache.
|
|
78
|
+
* @complexity O(1). */
|
|
79
|
+
export function adminSurfaceBootstrapHeaders(_required = {}, _optional = {}) {
|
|
80
|
+
return {
|
|
81
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
82
|
+
'Cache-Control': 'no-store',
|
|
83
|
+
Pragma: 'no-cache',
|
|
84
|
+
'X-Content-Type-Options': 'nosniff',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** True while the bootstrap may still be used.
|
|
88
|
+
* @complexity O(1). */
|
|
89
|
+
export function isAdminSurfaceBootstrapCurrent(required, _optional = {}) {
|
|
90
|
+
return required.nowMs < required.bootstrap.expiresAt;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=sandbox.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The one check every plugin-written settings value passes before a host stores it: plugin
|
|
3
|
+
* state (`./surface-capabilities.ts`) and a delivery binding's config (`./delivery-bindings.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Why it is shared: both are plugin-chosen JSON the host persists and later hands back (to the
|
|
6
|
+
* plugin's tools, or to the delivery adapter), so both need the same guarantees — plain JSON only,
|
|
7
|
+
* bounded depth and size, and never a token the plugin obtained — and must not drift apart.
|
|
8
|
+
*/
|
|
9
|
+
import { type AgentPluginCapabilityGate, type AgentPluginStateValue } from '../lifecycle/tools/capabilities.js';
|
|
10
|
+
export declare const ADMIN_SURFACE_STATE_LIMITS: Readonly<{
|
|
11
|
+
maxKeyLength: 128;
|
|
12
|
+
maxValueBytes: number;
|
|
13
|
+
maxDepth: 16;
|
|
14
|
+
}>;
|
|
15
|
+
export declare const STATE_CREDENTIAL_MESSAGE = "The plugin tried to save a connection credential in its settings; nothing was saved.";
|
|
16
|
+
/**
|
|
17
|
+
* The value the host stores: serialized once, scanned and size-checked on that text, then parsed
|
|
18
|
+
* back, so what is stored is exactly what was checked (the plugin cannot change it afterwards).
|
|
19
|
+
* @param required.gate - The call's capability gate (its token memory drives the scan).
|
|
20
|
+
* @param required.value - The plugin's value, unchecked.
|
|
21
|
+
* @throws Error when the value is not plain JSON, is too large, or mentions an obtained token.
|
|
22
|
+
* @complexity O(n) in the serialized size.
|
|
23
|
+
*/
|
|
24
|
+
export declare function storedStateValue(required: {
|
|
25
|
+
readonly gate: AgentPluginCapabilityGate;
|
|
26
|
+
readonly value: unknown;
|
|
27
|
+
}, _optional?: Record<string, never>): AgentPluginStateValue;
|
|
28
|
+
//# sourceMappingURL=settings-value.d.ts.map
|