@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.
Files changed (152) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +48 -0
  3. package/dist/admin-surfaces/action-dispatcher.d.ts +37 -0
  4. package/dist/admin-surfaces/action-dispatcher.js +69 -0
  5. package/dist/admin-surfaces/action-schema.d.ts +56 -0
  6. package/dist/admin-surfaces/action-schema.js +167 -0
  7. package/dist/admin-surfaces/action-tools.d.ts +40 -0
  8. package/dist/admin-surfaces/action-tools.js +52 -0
  9. package/dist/admin-surfaces/callback-field-inbox.d.ts +35 -0
  10. package/dist/admin-surfaces/callback-field-inbox.js +53 -0
  11. package/dist/admin-surfaces/capability-declarations.d.ts +41 -0
  12. package/dist/admin-surfaces/capability-declarations.js +102 -0
  13. package/dist/admin-surfaces/contribution.d.ts +49 -0
  14. package/dist/admin-surfaces/contribution.js +30 -0
  15. package/dist/admin-surfaces/delivery-bindings.d.ts +76 -0
  16. package/dist/admin-surfaces/delivery-bindings.js +99 -0
  17. package/dist/admin-surfaces/descriptor.d.ts +69 -0
  18. package/dist/admin-surfaces/descriptor.js +196 -0
  19. package/dist/admin-surfaces/external-links.d.ts +45 -0
  20. package/dist/admin-surfaces/external-links.js +70 -0
  21. package/dist/admin-surfaces/host-validator.d.ts +62 -0
  22. package/dist/admin-surfaces/host-validator.js +87 -0
  23. package/dist/admin-surfaces/index.d.ts +23 -0
  24. package/dist/admin-surfaces/index.js +21 -0
  25. package/dist/admin-surfaces/protocol.d.ts +103 -0
  26. package/dist/admin-surfaces/protocol.js +25 -0
  27. package/dist/admin-surfaces/sandbox.d.ts +78 -0
  28. package/dist/admin-surfaces/sandbox.js +92 -0
  29. package/dist/admin-surfaces/settings-value.d.ts +28 -0
  30. package/dist/admin-surfaces/settings-value.js +45 -0
  31. package/dist/admin-surfaces/surface-capabilities.d.ts +167 -0
  32. package/dist/admin-surfaces/surface-capabilities.js +215 -0
  33. package/dist/admin-surfaces/surface-client.d.ts +116 -0
  34. package/dist/admin-surfaces/surface-client.js +219 -0
  35. package/dist/admin-surfaces/surface-handlers.d.ts +111 -0
  36. package/dist/admin-surfaces/surface-handlers.js +172 -0
  37. package/dist/admin-surfaces/theme.d.ts +59 -0
  38. package/dist/admin-surfaces/theme.js +57 -0
  39. package/dist/folder-zip-node.d.ts +11 -0
  40. package/dist/folder-zip-node.js +84 -0
  41. package/dist/folder-zip.d.ts +38 -0
  42. package/dist/folder-zip.js +134 -0
  43. package/dist/lifecycle/automatic-use.d.ts +79 -0
  44. package/dist/lifecycle/automatic-use.js +165 -0
  45. package/dist/lifecycle/compatibility-metadata.d.ts +19 -0
  46. package/dist/lifecycle/compatibility-metadata.js +45 -0
  47. package/dist/lifecycle/compatibility.d.ts +52 -0
  48. package/dist/lifecycle/compatibility.js +60 -0
  49. package/dist/lifecycle/fetch-archive.js +26 -24
  50. package/dist/lifecycle/index.d.ts +14 -2
  51. package/dist/lifecycle/index.js +8 -1
  52. package/dist/lifecycle/install-errors.d.ts +14 -0
  53. package/dist/lifecycle/install-errors.js +9 -0
  54. package/dist/lifecycle/install.d.ts +2 -13
  55. package/dist/lifecycle/install.js +12 -123
  56. package/dist/lifecycle/installed-package-index.d.ts +15 -0
  57. package/dist/lifecycle/installed-package-index.js +128 -0
  58. package/dist/lifecycle/installed-package-roots.js +28 -20
  59. package/dist/lifecycle/manifest.d.ts +14 -0
  60. package/dist/lifecycle/manifest.js +115 -100
  61. package/dist/lifecycle/package-paths.js +3 -3
  62. package/dist/lifecycle/ports.d.ts +1 -1
  63. package/dist/lifecycle/provider-contribution.d.ts +65 -0
  64. package/dist/lifecycle/provider-contribution.js +93 -0
  65. package/dist/lifecycle/resolve-agent-plugin-refs.js +19 -6
  66. package/dist/lifecycle/retire-bundled.js +20 -18
  67. package/dist/lifecycle/search.d.ts +6 -1
  68. package/dist/lifecycle/search.js +45 -7
  69. package/dist/lifecycle/set-enabled.d.ts +2 -1
  70. package/dist/lifecycle/set-enabled.js +52 -8
  71. package/dist/lifecycle/skill-file-reader.js +85 -71
  72. package/dist/lifecycle/tools/capabilities.d.ts +199 -0
  73. package/dist/lifecycle/tools/capabilities.js +213 -0
  74. package/dist/lifecycle/tools/definition.d.ts +32 -0
  75. package/dist/lifecycle/tools/definition.js +33 -0
  76. package/dist/lifecycle/tools/index.d.ts +10 -0
  77. package/dist/lifecycle/tools/index.js +10 -0
  78. package/dist/lifecycle/tools/manifest.d.ts +33 -0
  79. package/dist/lifecycle/tools/manifest.js +89 -0
  80. package/dist/lifecycle/tools/projection.d.ts +90 -0
  81. package/dist/lifecycle/tools/projection.js +283 -0
  82. package/dist/lifecycle/types.d.ts +2 -0
  83. package/dist/lifecycle/uninstall.d.ts +3 -1
  84. package/dist/lifecycle/uninstall.js +51 -27
  85. package/dist/lifecycle/update-plan.d.ts +18 -0
  86. package/dist/lifecycle/update-plan.js +22 -0
  87. package/dist/lifecycle/update-store.d.ts +71 -0
  88. package/dist/lifecycle/update-store.js +150 -0
  89. package/dist/lifecycle/update.d.ts +55 -0
  90. package/dist/lifecycle/update.js +321 -0
  91. package/dist/manifest.d.ts +2 -1
  92. package/dist/manifest.js +2 -1
  93. package/package.json +50 -6
  94. package/ui-ux-design/plugin.json +8 -2
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/index.js.map +0 -1
  97. package/dist/lifecycle/activation.d.ts.map +0 -1
  98. package/dist/lifecycle/activation.js.map +0 -1
  99. package/dist/lifecycle/bundled-digests.d.ts.map +0 -1
  100. package/dist/lifecycle/bundled-digests.js.map +0 -1
  101. package/dist/lifecycle/bundled-source-archive.d.ts.map +0 -1
  102. package/dist/lifecycle/bundled-source-archive.js.map +0 -1
  103. package/dist/lifecycle/capability-projection.d.ts.map +0 -1
  104. package/dist/lifecycle/capability-projection.js.map +0 -1
  105. package/dist/lifecycle/contribution-loader.d.ts.map +0 -1
  106. package/dist/lifecycle/contribution-loader.js.map +0 -1
  107. package/dist/lifecycle/fetch-archive.d.ts.map +0 -1
  108. package/dist/lifecycle/fetch-archive.js.map +0 -1
  109. package/dist/lifecycle/index.d.ts.map +0 -1
  110. package/dist/lifecycle/index.js.map +0 -1
  111. package/dist/lifecycle/install-from-url.d.ts.map +0 -1
  112. package/dist/lifecycle/install-from-url.js.map +0 -1
  113. package/dist/lifecycle/install.d.ts.map +0 -1
  114. package/dist/lifecycle/install.js.map +0 -1
  115. package/dist/lifecycle/installed-package-roots.d.ts.map +0 -1
  116. package/dist/lifecycle/installed-package-roots.js.map +0 -1
  117. package/dist/lifecycle/layout.d.ts.map +0 -1
  118. package/dist/lifecycle/layout.js.map +0 -1
  119. package/dist/lifecycle/manifest.d.ts.map +0 -1
  120. package/dist/lifecycle/manifest.js.map +0 -1
  121. package/dist/lifecycle/mcp-provisioning.d.ts.map +0 -1
  122. package/dist/lifecycle/mcp-provisioning.js.map +0 -1
  123. package/dist/lifecycle/node.d.ts.map +0 -1
  124. package/dist/lifecycle/node.js.map +0 -1
  125. package/dist/lifecycle/package-paths.d.ts.map +0 -1
  126. package/dist/lifecycle/package-paths.js.map +0 -1
  127. package/dist/lifecycle/persistent-state.d.ts.map +0 -1
  128. package/dist/lifecycle/persistent-state.js.map +0 -1
  129. package/dist/lifecycle/ports.d.ts.map +0 -1
  130. package/dist/lifecycle/ports.js.map +0 -1
  131. package/dist/lifecycle/resolve-agent-plugin-refs.d.ts.map +0 -1
  132. package/dist/lifecycle/resolve-agent-plugin-refs.js.map +0 -1
  133. package/dist/lifecycle/retire-bundled.d.ts.map +0 -1
  134. package/dist/lifecycle/retire-bundled.js.map +0 -1
  135. package/dist/lifecycle/search.d.ts.map +0 -1
  136. package/dist/lifecycle/search.js.map +0 -1
  137. package/dist/lifecycle/seed-bundled.d.ts.map +0 -1
  138. package/dist/lifecycle/seed-bundled.js.map +0 -1
  139. package/dist/lifecycle/set-enabled.d.ts.map +0 -1
  140. package/dist/lifecycle/set-enabled.js.map +0 -1
  141. package/dist/lifecycle/skill-file-reader.d.ts.map +0 -1
  142. package/dist/lifecycle/skill-file-reader.js.map +0 -1
  143. package/dist/lifecycle/trusted-plugin-files.d.ts.map +0 -1
  144. package/dist/lifecycle/trusted-plugin-files.js.map +0 -1
  145. package/dist/lifecycle/types.d.ts.map +0 -1
  146. package/dist/lifecycle/types.js.map +0 -1
  147. package/dist/lifecycle/uninstall.d.ts.map +0 -1
  148. package/dist/lifecycle/uninstall.js.map +0 -1
  149. package/dist/lifecycle/yauzl-archive-reader.d.ts.map +0 -1
  150. package/dist/lifecycle/yauzl-archive-reader.js.map +0 -1
  151. package/dist/manifest.d.ts.map +0 -1
  152. package/dist/manifest.js.map +0 -1
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @file The host's design tokens for plugin surfaces: which `--jini-*` custom properties a host
3
+ * shares with every surface frame, how it reads their current values, and the envelope that sends
4
+ * a change after init.
5
+ *
6
+ * Why: a surface is a sandboxed frame without `allow-same-origin`, so it cannot see the admin's
7
+ * stylesheets, and its CSP allows only its own files. Values posted in a message are the only way
8
+ * its screens can match the admin and follow a light/dark or admin-theme change. The surface client
9
+ * applies them as custom properties on its root (CSSOM, which the CSP allows; never a style
10
+ * attribute or a stylesheet), so a surface writes `var(--jini-primary, <fallback>)` and looks right
11
+ * with or without a host that sends tokens.
12
+ *
13
+ * Values are text a host computed from its own CSS; anything that could fetch (`url(`, `image-set(`)
14
+ * or leave a declaration (`;`, braces) is dropped here and again in the surface client.
15
+ */
16
+ import { ADMIN_SURFACE_PROTOCOL, ADMIN_SURFACE_PROTOCOL_VERSION } from './protocol.js';
17
+ /** The shared vocabulary: colours, type, radius, spacing, depth and focus. A host may send fewer. */
18
+ export declare const ADMIN_SURFACE_THEME_TOKENS: readonly ["--jini-bg", "--jini-surface", "--jini-bg-subtle", "--jini-text", "--jini-text-muted", "--jini-border", "--jini-border-strong", "--jini-primary", "--jini-primary-ink", "--jini-link", "--jini-danger", "--jini-danger-bg", "--jini-success", "--jini-success-bg", "--jini-warning", "--jini-warning-bg", "--jini-font-ui", "--jini-font-mono", "--jini-font-size-xs", "--jini-font-size-sm", "--jini-font-size-base", "--jini-font-size-md", "--jini-radius-sm", "--jini-radius", "--jini-radius-md", "--jini-radius-pill", "--jini-space-1", "--jini-space-2", "--jini-space-3", "--jini-space-4", "--jini-shadow-xs", "--jini-shadow-sm", "--jini-control-bg", "--jini-control-border", "--jini-control-shadow-focus", "--jini-overlay-tooltip-bg", "--jini-overlay-tooltip-text", "--jini-overlay-tooltip-border"];
19
+ export type AdminSurfaceThemeToken = (typeof ADMIN_SURFACE_THEME_TOKENS)[number];
20
+ export type AdminSurfaceColorScheme = 'light' | 'dark';
21
+ /** What a surface receives: the scheme for native controls plus the token values. */
22
+ export interface AdminSurfaceTheme {
23
+ readonly scheme: AdminSurfaceColorScheme;
24
+ readonly tokens: Readonly<Partial<Record<AdminSurfaceThemeToken, string>>>;
25
+ }
26
+ /** Host page -> surface after init, whenever the admin's theme or colour scheme changes. */
27
+ export interface AdminSurfaceThemeMessage {
28
+ readonly protocol: typeof ADMIN_SURFACE_PROTOCOL;
29
+ readonly version: typeof ADMIN_SURFACE_PROTOCOL_VERSION;
30
+ readonly kind: 'theme';
31
+ readonly mountId: string;
32
+ readonly theme: AdminSurfaceTheme;
33
+ }
34
+ /** Whether `value` is plain declaration text that cannot fetch anything or end its declaration.
35
+ * @complexity O(n) in the value length. */
36
+ export declare function isSafeAdminSurfaceThemeValue(value: unknown): value is string;
37
+ /**
38
+ * Reads the shared tokens from the host's computed style.
39
+ * @param required.style - Usually `getComputedStyle(element)` for the element the admin theme is on.
40
+ * @param optional.sources - Where to read a token when the host draws with its own name for it
41
+ * (e.g. `{ '--jini-text': '--fg' }`); unnamed tokens are read under their own name.
42
+ * @returns Only the tokens with a safe, non-empty value.
43
+ * @complexity O(t) tokens.
44
+ */
45
+ export declare function readAdminSurfaceTheme(required: {
46
+ readonly style: {
47
+ getPropertyValue(name: string): string;
48
+ };
49
+ readonly scheme: AdminSurfaceColorScheme;
50
+ }, optional?: {
51
+ readonly sources?: Readonly<Partial<Record<AdminSurfaceThemeToken, string>>>;
52
+ }): AdminSurfaceTheme;
53
+ /** Builds the envelope that sends a changed theme to one mount's frame.
54
+ * @complexity O(1). */
55
+ export declare function adminSurfaceThemeMessage(required: {
56
+ readonly mountId: string;
57
+ readonly theme: AdminSurfaceTheme;
58
+ }, _optional?: Record<string, never>): AdminSurfaceThemeMessage;
59
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @file The host's design tokens for plugin surfaces: which `--jini-*` custom properties a host
3
+ * shares with every surface frame, how it reads their current values, and the envelope that sends
4
+ * a change after init.
5
+ *
6
+ * Why: a surface is a sandboxed frame without `allow-same-origin`, so it cannot see the admin's
7
+ * stylesheets, and its CSP allows only its own files. Values posted in a message are the only way
8
+ * its screens can match the admin and follow a light/dark or admin-theme change. The surface client
9
+ * applies them as custom properties on its root (CSSOM, which the CSP allows; never a style
10
+ * attribute or a stylesheet), so a surface writes `var(--jini-primary, <fallback>)` and looks right
11
+ * with or without a host that sends tokens.
12
+ *
13
+ * Values are text a host computed from its own CSS; anything that could fetch (`url(`, `image-set(`)
14
+ * or leave a declaration (`;`, braces) is dropped here and again in the surface client.
15
+ */
16
+ import { ADMIN_SURFACE_PROTOCOL, ADMIN_SURFACE_PROTOCOL_VERSION } from './protocol.js';
17
+ /** The shared vocabulary: colours, type, radius, spacing, depth and focus. A host may send fewer. */
18
+ export const ADMIN_SURFACE_THEME_TOKENS = Object.freeze([
19
+ '--jini-bg', '--jini-surface', '--jini-bg-subtle', '--jini-text', '--jini-text-muted',
20
+ '--jini-border', '--jini-border-strong', '--jini-primary', '--jini-primary-ink', '--jini-link',
21
+ '--jini-danger', '--jini-danger-bg', '--jini-success', '--jini-success-bg', '--jini-warning', '--jini-warning-bg',
22
+ '--jini-font-ui', '--jini-font-mono', '--jini-font-size-xs', '--jini-font-size-sm', '--jini-font-size-base', '--jini-font-size-md',
23
+ '--jini-radius-sm', '--jini-radius', '--jini-radius-md', '--jini-radius-pill',
24
+ '--jini-space-1', '--jini-space-2', '--jini-space-3', '--jini-space-4',
25
+ '--jini-shadow-xs', '--jini-shadow-sm', '--jini-control-bg', '--jini-control-border', '--jini-control-shadow-focus',
26
+ '--jini-overlay-tooltip-bg', '--jini-overlay-tooltip-text', '--jini-overlay-tooltip-border',
27
+ ]);
28
+ const MAX_VALUE_LENGTH = 200;
29
+ const UNSAFE_VALUE = /url\(|image\(|image-set\(|[;{}<>\\@]/i;
30
+ /** Whether `value` is plain declaration text that cannot fetch anything or end its declaration.
31
+ * @complexity O(n) in the value length. */
32
+ export function isSafeAdminSurfaceThemeValue(value) {
33
+ return typeof value === 'string' && value.length > 0 && value.length <= MAX_VALUE_LENGTH && !UNSAFE_VALUE.test(value);
34
+ }
35
+ /**
36
+ * Reads the shared tokens from the host's computed style.
37
+ * @param required.style - Usually `getComputedStyle(element)` for the element the admin theme is on.
38
+ * @param optional.sources - Where to read a token when the host draws with its own name for it
39
+ * (e.g. `{ '--jini-text': '--fg' }`); unnamed tokens are read under their own name.
40
+ * @returns Only the tokens with a safe, non-empty value.
41
+ * @complexity O(t) tokens.
42
+ */
43
+ export function readAdminSurfaceTheme(required, optional = {}) {
44
+ const tokens = {};
45
+ for (const token of ADMIN_SURFACE_THEME_TOKENS) {
46
+ const value = required.style.getPropertyValue(optional.sources?.[token] ?? token).trim();
47
+ if (isSafeAdminSurfaceThemeValue(value))
48
+ tokens[token] = value;
49
+ }
50
+ return { scheme: required.scheme, tokens };
51
+ }
52
+ /** Builds the envelope that sends a changed theme to one mount's frame.
53
+ * @complexity O(1). */
54
+ export function adminSurfaceThemeMessage(required, _optional = {}) {
55
+ return { protocol: ADMIN_SURFACE_PROTOCOL, version: ADMIN_SURFACE_PROTOCOL_VERSION, kind: 'theme', mountId: required.mountId, theme: required.theme };
56
+ }
57
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1,11 @@
1
+ /** Reads a local folder into the browser upload encoder. Refuses links/special files and bounds
2
+ * entries and bytes before allocation; the resulting ZIP still requires the install validator.
3
+ * @throws {FolderZipError} Empty, unreadable, linked, or over-limit folders.
4
+ * @complexity O(e log e + b) time for e entries and b bytes; O(e + b) space,
5
+ * bounded to 4096 entries and maxBytes. No network I/O.
6
+ */
7
+ export declare function zipLocalFolder(required: {
8
+ path: string;
9
+ maxBytes: number;
10
+ }, _optional?: Record<string, never>): Promise<Uint8Array>;
11
+ //# sourceMappingURL=folder-zip-node.d.ts.map
@@ -0,0 +1,84 @@
1
+ import * as fs from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import path from "node:path";
4
+ import { FOLDER_MAX_FILES, FOLDER_UNREADABLE, FolderZipError, zipFolderFiles } from "./folder-zip.js";
5
+ async function readLocalFile(required, _optional = {}) {
6
+ const { filesystem, root, filename, name, maxBytes } = required;
7
+ if (await filesystem.realpath(filename) !== filename)
8
+ throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
9
+ // O_NOFOLLOW refuses a file replaced with a symlink between enumeration and opening.
10
+ const file = await filesystem.open(filename, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
11
+ try {
12
+ const info = await file.stat();
13
+ if (!info.isFile())
14
+ throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
15
+ const total = required.total + info.size;
16
+ if (total > maxBytes)
17
+ throw new FolderZipError("too-large", "Folder is over the upload limit.");
18
+ // One extra byte detects growth without an unbounded readFile allocation.
19
+ const bytes = Buffer.alloc(info.size + 1);
20
+ let length = 0;
21
+ while (length < bytes.length) {
22
+ const read = await file.read(bytes, length, bytes.length - length, null);
23
+ if (read.bytesRead === 0)
24
+ break;
25
+ length += read.bytesRead;
26
+ }
27
+ if (length !== info.size)
28
+ throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
29
+ const picked = new File([new Uint8Array(bytes.subarray(0, length))], name);
30
+ Object.defineProperty(picked, "webkitRelativePath", { value: `folder/${path.relative(root, filename).split(path.sep).join("/")}` });
31
+ return { file: picked, total };
32
+ }
33
+ finally {
34
+ await file.close();
35
+ }
36
+ }
37
+ async function collectDirectory(required, _optional = {}) {
38
+ const { filesystem, root, directory, collection, maxBytes } = required;
39
+ if (!(await filesystem.lstat(directory)).isDirectory() || await filesystem.realpath(directory) !== directory)
40
+ throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
41
+ const handle = await filesystem.opendir(directory);
42
+ // Mutate the traversal's owned buffers so visiting directories does not copy accumulated files.
43
+ for await (const entry of handle) {
44
+ if (++collection.entries > FOLDER_MAX_FILES)
45
+ throw new FolderZipError("too-many", `More than ${FOLDER_MAX_FILES} entries.`);
46
+ const filename = path.join(directory, entry.name);
47
+ if (entry.isDirectory()) {
48
+ collection.pending.push(filename);
49
+ continue;
50
+ }
51
+ if (!entry.isFile())
52
+ throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
53
+ const result = await readLocalFile({ filesystem, root, filename, name: entry.name, total: collection.total, maxBytes });
54
+ collection.total = result.total;
55
+ collection.files.push(result.file);
56
+ }
57
+ }
58
+ /** Reads a local folder into the browser upload encoder. Refuses links/special files and bounds
59
+ * entries and bytes before allocation; the resulting ZIP still requires the install validator.
60
+ * @throws {FolderZipError} Empty, unreadable, linked, or over-limit folders.
61
+ * @complexity O(e log e + b) time for e entries and b bytes; O(e + b) space,
62
+ * bounded to 4096 entries and maxBytes. No network I/O.
63
+ */
64
+ export async function zipLocalFolder(required, _optional = {}) {
65
+ const collection = { files: [], pending: [], entries: 0, total: 0 };
66
+ try {
67
+ const root = await fs.realpath(required.path);
68
+ collection.pending.push(root);
69
+ while (collection.pending.length) {
70
+ const directory = collection.pending.pop();
71
+ await collectDirectory({ filesystem: fs, root, directory, collection, maxBytes: required.maxBytes });
72
+ }
73
+ const { files } = collection;
74
+ // Stable order gives repeat local-folder installs identical ZIP digests.
75
+ files.sort((a, b) => a.webkitRelativePath < b.webkitRelativePath ? -1 : a.webkitRelativePath > b.webkitRelativePath ? 1 : 0);
76
+ return new Uint8Array(await (await zipFolderFiles({ files, maxBytes: required.maxBytes })).arrayBuffer());
77
+ }
78
+ catch (error) {
79
+ if (error instanceof FolderZipError)
80
+ throw error;
81
+ throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
82
+ }
83
+ }
84
+ //# sourceMappingURL=folder-zip-node.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @file Packs a folder the operator picked (`<input type=file webkitdirectory>`) into one `.zip`
3
+ * `File`, so "Upload a folder" travels the exact path a `.zip` upload does: same size limit, same
4
+ * server-side archive checks, same Preview trust review. The install routes only take
5
+ * `application/zip`, so zipping here keeps one server path instead of adding a second upload format.
6
+ *
7
+ * Stored mode (no compression): the upload limit is on the archive, and a plugin folder is small
8
+ * text, so deflate would add a dependency for little gain. `@jini-ai/ui` has a stored-mode
9
+ * `buildZip`, but it is text-only (it would corrupt an image) and not exported, so this is the
10
+ * binary-safe twin, extracted to Jini so browser and local-folder uploads use the same encoder.
11
+ */
12
+ /** Refusals, as dictionary keys the caller translates. */
13
+ export declare const FOLDER_EMPTY = "That folder is empty.";
14
+ export declare const FOLDER_UNREADABLE = "Could not read that folder. Try again.";
15
+ /** The archive readers' entry cap (`install-archive.ts`, the Agent Plugins reader): refused here
16
+ * before any bytes are read. */
17
+ export declare const FOLDER_MAX_FILES = 4096;
18
+ export declare class FolderZipError extends Error {
19
+ readonly reason: "empty" | "too-large" | "too-many" | "unreadable";
20
+ constructor(reason: "empty" | "too-large" | "too-many" | "unreadable", message: string);
21
+ }
22
+ /** @complexity O(n) in the byte count. */
23
+ export declare function crc32(bytes: Uint8Array): number;
24
+ /**
25
+ * Zips the files of one picked folder.
26
+ *
27
+ * @param required.files - Every file the folder picker returned.
28
+ * @param required.maxBytes - The upload limit; the folder's raw size alone over it is refused before
29
+ * reading, and the finished zip is checked again (headers add a little).
30
+ * @returns `<folder>.zip`, typed `application/zip`.
31
+ * @throws {FolderZipError} empty folder, too many files, too large, or a file that could not be read.
32
+ * @complexity O(total bytes).
33
+ */
34
+ export declare function zipFolderFiles(required: {
35
+ files: readonly File[];
36
+ maxBytes: number;
37
+ }, _optional?: Record<string, never>): Promise<File>;
38
+ //# sourceMappingURL=folder-zip.d.ts.map
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @file Packs a folder the operator picked (`<input type=file webkitdirectory>`) into one `.zip`
3
+ * `File`, so "Upload a folder" travels the exact path a `.zip` upload does: same size limit, same
4
+ * server-side archive checks, same Preview trust review. The install routes only take
5
+ * `application/zip`, so zipping here keeps one server path instead of adding a second upload format.
6
+ *
7
+ * Stored mode (no compression): the upload limit is on the archive, and a plugin folder is small
8
+ * text, so deflate would add a dependency for little gain. `@jini-ai/ui` has a stored-mode
9
+ * `buildZip`, but it is text-only (it would corrupt an image) and not exported, so this is the
10
+ * binary-safe twin, extracted to Jini so browser and local-folder uploads use the same encoder.
11
+ */
12
+ /** Refusals, as dictionary keys the caller translates. */
13
+ export const FOLDER_EMPTY = "That folder is empty.";
14
+ export const FOLDER_UNREADABLE = "Could not read that folder. Try again.";
15
+ /** The archive readers' entry cap (`install-archive.ts`, the Agent Plugins reader): refused here
16
+ * before any bytes are read. */
17
+ export const FOLDER_MAX_FILES = 4096;
18
+ export class FolderZipError extends Error {
19
+ reason;
20
+ constructor(reason, message) {
21
+ super(message);
22
+ this.reason = reason;
23
+ }
24
+ }
25
+ /** 1980-01-01, the earliest DOS date: a fixed stamp, so the same folder always zips to the same
26
+ * bytes (an all-zero date is month 0, which some readers refuse). */
27
+ const DOS_DATE_1980 = (1 << 5) | 1;
28
+ const CRC_TABLE = (() => {
29
+ const table = new Uint32Array(256);
30
+ for (let n = 0; n < 256; n++) {
31
+ let c = n;
32
+ for (let k = 0; k < 8; k++)
33
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
34
+ table[n] = c >>> 0;
35
+ }
36
+ return table;
37
+ })();
38
+ /** @complexity O(n) in the byte count. */
39
+ export function crc32(bytes) {
40
+ let c = 0xffffffff;
41
+ for (let i = 0; i < bytes.length; i++)
42
+ c = CRC_TABLE[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);
43
+ return (c ^ 0xffffffff) >>> 0;
44
+ }
45
+ /** A picked file's path inside the picked folder: `webkitRelativePath` minus the folder's own name,
46
+ * so the package's files sit at the top of the zip — what a server-path install of that same
47
+ * folder reads, and where both install routes look for the manifest. */
48
+ function entryPath(file) {
49
+ const relative = (file.webkitRelativePath || file.name).replaceAll("\\", "/");
50
+ const slash = relative.indexOf("/");
51
+ return slash < 0 ? { folder: "", path: relative } : { folder: relative.slice(0, slash), path: relative.slice(slash + 1) };
52
+ }
53
+ async function readFolderFile(required, _optional = {}) {
54
+ try {
55
+ return new Uint8Array(await required.file.arrayBuffer());
56
+ }
57
+ catch {
58
+ throw new FolderZipError("unreadable", FOLDER_UNREADABLE);
59
+ }
60
+ }
61
+ /**
62
+ * Zips the files of one picked folder.
63
+ *
64
+ * @param required.files - Every file the folder picker returned.
65
+ * @param required.maxBytes - The upload limit; the folder's raw size alone over it is refused before
66
+ * reading, and the finished zip is checked again (headers add a little).
67
+ * @returns `<folder>.zip`, typed `application/zip`.
68
+ * @throws {FolderZipError} empty folder, too many files, too large, or a file that could not be read.
69
+ * @complexity O(total bytes).
70
+ */
71
+ export async function zipFolderFiles(required, _optional = {}) {
72
+ const { files, maxBytes } = required;
73
+ if (files.length === 0)
74
+ throw new FolderZipError("empty", FOLDER_EMPTY);
75
+ if (files.length > FOLDER_MAX_FILES)
76
+ throw new FolderZipError("too-many", `More than ${FOLDER_MAX_FILES} files.`);
77
+ if (files.reduce((sum, file) => sum + file.size, 0) > maxBytes)
78
+ throw new FolderZipError("too-large", "Folder is over the upload limit.");
79
+ const encoder = new TextEncoder();
80
+ const chunks = [];
81
+ const central = [];
82
+ let offset = 0;
83
+ let folder = "";
84
+ for (const file of files) {
85
+ const entry = entryPath(file);
86
+ folder ||= entry.folder;
87
+ const data = await readFolderFile({ file });
88
+ const name = encoder.encode(entry.path);
89
+ const crc = crc32(data);
90
+ // Bit 11: the name is UTF-8, so a reader never decodes it as CP437.
91
+ const flags = name.some((b) => b > 0x7f) ? 0x0800 : 0;
92
+ const local = new Uint8Array(30 + name.length);
93
+ const lv = new DataView(local.buffer);
94
+ lv.setUint32(0, 0x04034b50, true);
95
+ lv.setUint16(4, 20, true);
96
+ lv.setUint16(6, flags, true);
97
+ lv.setUint16(12, DOS_DATE_1980, true);
98
+ lv.setUint32(14, crc, true);
99
+ lv.setUint32(18, data.length, true);
100
+ lv.setUint32(22, data.length, true);
101
+ lv.setUint16(26, name.length, true);
102
+ local.set(name, 30);
103
+ // Version-made-by 20 (MS-DOS, external attributes 0): a plain file to every reader, so the
104
+ // server's Unix special-entry guard never sees a mode to refuse.
105
+ const header = new Uint8Array(46 + name.length);
106
+ const cv = new DataView(header.buffer);
107
+ cv.setUint32(0, 0x02014b50, true);
108
+ cv.setUint16(4, 20, true);
109
+ cv.setUint16(6, 20, true);
110
+ cv.setUint16(8, flags, true);
111
+ cv.setUint16(14, DOS_DATE_1980, true);
112
+ cv.setUint32(16, crc, true);
113
+ cv.setUint32(20, data.length, true);
114
+ cv.setUint32(24, data.length, true);
115
+ cv.setUint16(28, name.length, true);
116
+ cv.setUint32(42, offset, true);
117
+ header.set(name, 46);
118
+ chunks.push(local, data);
119
+ central.push(header);
120
+ offset += local.length + data.length;
121
+ }
122
+ const centralSize = central.reduce((sum, part) => sum + part.length, 0);
123
+ const end = new Uint8Array(22);
124
+ const ev = new DataView(end.buffer);
125
+ ev.setUint32(0, 0x06054b50, true);
126
+ ev.setUint16(8, files.length, true);
127
+ ev.setUint16(10, files.length, true);
128
+ ev.setUint32(12, centralSize, true);
129
+ ev.setUint32(16, offset, true);
130
+ if (offset + centralSize + end.length > maxBytes)
131
+ throw new FolderZipError("too-large", "Folder is over the upload limit.");
132
+ return new File([...chunks, ...central, end], `${folder || "folder"}.zip`, { type: "application/zip" });
133
+ }
134
+ //# sourceMappingURL=folder-zip.js.map
@@ -0,0 +1,79 @@
1
+ /** Automatic-use policy for one execution context. Catalog, trust, consent and presentation are ports. */
2
+ import type { AgentPluginSearchCandidate, AgentPluginSearchMatch } from './search.js';
3
+ export type ChatAgentPlugins = 'auto' | 'ask' | 'off';
4
+ export declare const DEFAULT_CHAT_AGENT_PLUGINS: ChatAgentPlugins;
5
+ /** Browser-owned, per-conversation state. Clearing it deliberately permits a fresh question. */
6
+ export interface ChatAgentPluginState {
7
+ decision: {
8
+ selected: readonly string[];
9
+ asked?: boolean;
10
+ } | null;
11
+ excluded: readonly string[];
12
+ }
13
+ export interface AutomaticAgentPluginMatch {
14
+ id: string;
15
+ name: string;
16
+ why: string;
17
+ }
18
+ export interface AutomaticAgentPluginPorts {
19
+ list(input: Record<string, never>, options?: Record<string, never>): Promise<readonly AgentPluginSearchCandidate[]>;
20
+ rank(input: {
21
+ query: string;
22
+ candidates: readonly AgentPluginSearchCandidate[];
23
+ limit: number;
24
+ }, options?: Record<string, never>): readonly AgentPluginSearchMatch[];
25
+ /** Must recheck activation/trust and return real guidance, never a pointer to unopened guidance. */
26
+ resolve(input: {
27
+ pluginId: string;
28
+ }, options?: Record<string, never>): Promise<string>;
29
+ /** Only a principal-bound human exchange may return action=auto. */
30
+ ask(input: {
31
+ matches: readonly AutomaticAgentPluginMatch[];
32
+ signal?: AbortSignal;
33
+ }, options?: Record<string, never>): Promise<{
34
+ action: 'use' | 'skip' | 'auto';
35
+ selected: readonly string[];
36
+ }>;
37
+ saveAuto(input: Record<string, never>, options?: Record<string, never>): Promise<void>;
38
+ publish(input: {
39
+ state: ChatAgentPluginState;
40
+ plugins: readonly {
41
+ id: string;
42
+ name: string;
43
+ }[];
44
+ }, options?: Record<string, never>): Promise<void>;
45
+ }
46
+ /** Validate the request's small browser state; malformed state means no previous decision.
47
+ * @returns A fresh state; no caller input is mutated. @complexity O(n) in saved identities.
48
+ */
49
+ export declare function readChatAgentPluginState({ value }: {
50
+ value: unknown;
51
+ }, _options?: {}): ChatAgentPluginState;
52
+ /** Create one run's preparation owner. Calls serialize so parallel tool writes share one question.
53
+ * @param required Host effects and the person's mode; browser state is advisory selection only.
54
+ * @returns prepare(): a relevance note or capability guidance; load(): one plugin's real guidance.
55
+ * @complexity O(p*t) ranking plus at most one resolve per admitted plugin; relevance, not a count, bounds it.
56
+ * @example const use = createAutomaticAgentPluginUse({ mode: 'auto', ports });
57
+ */
58
+ export declare function createAutomaticAgentPluginUse({ mode, state: initialState, ports }: {
59
+ mode: ChatAgentPlugins;
60
+ state?: ChatAgentPluginState | undefined;
61
+ ports: AutomaticAgentPluginPorts;
62
+ }, _options?: {}): {
63
+ /** Suggest relevant metadata for a query; capability calls load only their providers. */
64
+ prepare: (input: {
65
+ query?: string;
66
+ capabilities?: readonly string[];
67
+ loaded?: readonly string[];
68
+ signal?: AbortSignal;
69
+ }, _options?: {}) => Promise<string>;
70
+ /** Resolve one active, permitted id and publish usage only after real guidance is read.
71
+ * Returns an empty string for denied, unavailable, or already-delivered guidance.
72
+ * @example await controller.load({ pluginId: 'my-plugin' });
73
+ */
74
+ load: (input: {
75
+ pluginId: string;
76
+ signal?: AbortSignal;
77
+ }, _options?: {}) => Promise<string>;
78
+ };
79
+ //# sourceMappingURL=automatic-use.d.ts.map
@@ -0,0 +1,165 @@
1
+ export const DEFAULT_CHAT_AGENT_PLUGINS = 'auto';
2
+ /** Use the same one-line metadata reason in consent and progressive disclosure. */
3
+ function describeMatch({ plugin }, _options = {}) {
4
+ return { id: plugin.pluginId, name: (plugin.displayName ?? plugin.pluginId).replace(/\s+/g, ' '),
5
+ why: (plugin.summary ?? plugin.description ?? `Provides ${(plugin.provides ?? plugin.keywords).join(', ')}`).split(/[\n.]/)[0].replace(/\s+/g, ' ').slice(0, 180) };
6
+ }
7
+ /** Present metadata only. A byte is a conservative upper bound on a UTF-8 text token, so
8
+ * this stays below 600 tokens without a model-specific tokenizer or a plugin-count limit.
9
+ * @returns A bounded note in relevance order, with discovery for every omitted match.
10
+ * @complexity O(p) metadata formatting; output is bounded independently of plugin count.
11
+ */
12
+ function relevantPluginNote({ matches }, _options = {}) {
13
+ if (!matches.length)
14
+ return '';
15
+ const header = 'Relevant agent plugins for this request:\n';
16
+ const footer = '\nLoad one with load_agent_plugin({ pluginId }) before acting on that part.';
17
+ const more = (remaining) => remaining ? `\n+${remaining} more relevant: search_agent_plugin_local({ query: <same request>, offset: 0 }); page with nextOffset` : '';
18
+ const lines = [];
19
+ const bytes = (text) => new TextEncoder().encode(text).length;
20
+ for (const match of matches) {
21
+ const { id, name, why } = describeMatch({ plugin: match });
22
+ const line = `${name} (${id}) — ${why}`;
23
+ if (bytes(header + [...lines, line].join('\n') + more(matches.length - lines.length - 1) + footer) > 600)
24
+ break;
25
+ lines.push(line);
26
+ }
27
+ return header + lines.join('\n') + more(matches.length - lines.length) + footer;
28
+ }
29
+ /** An explicit id or a guarded capability admits its providers outright; a query is ranked for relevance. */
30
+ function rankCandidates({ candidates, input, ports }, _options = {}) {
31
+ const { pluginId, capabilities } = input;
32
+ const admit = (plugins) => plugins.map(plugin => ({ ...plugin, score: 4 }));
33
+ if (pluginId !== undefined)
34
+ return admit(candidates.filter(plugin => plugin.pluginId === pluginId));
35
+ if (capabilities)
36
+ return admit(candidates.filter(plugin => plugin.provides?.some(capability => capabilities.includes(capability))));
37
+ return ports.rank({ query: input.query ?? '', candidates, limit: candidates.length }, {});
38
+ }
39
+ /** Validate the request's small browser state; malformed state means no previous decision.
40
+ * @returns A fresh state; no caller input is mutated. @complexity O(n) in saved identities.
41
+ */
42
+ export function readChatAgentPluginState({ value }, _options = {}) {
43
+ const empty = { decision: null, excluded: [] };
44
+ if (!value || typeof value !== 'object')
45
+ return empty;
46
+ const record = value;
47
+ const ids = (raw) => Array.isArray(raw) && raw.every(id => typeof id === 'string' && id.length > 0 && id.length <= 160);
48
+ if (!ids(record.excluded))
49
+ return empty;
50
+ const decision = record.decision;
51
+ if (decision !== null && (!decision || !ids(decision.selected)))
52
+ return empty;
53
+ return { decision: decision ? { selected: [...new Set(decision.selected)], ...(typeof decision.asked === 'boolean' ? { asked: decision.asked } : {}) } : null, excluded: [...new Set(record.excluded)] };
54
+ }
55
+ /** Create one run's preparation owner. Calls serialize so parallel tool writes share one question.
56
+ * @param required Host effects and the person's mode; browser state is advisory selection only.
57
+ * @returns prepare(): a relevance note or capability guidance; load(): one plugin's real guidance.
58
+ * @complexity O(p*t) ranking plus at most one resolve per admitted plugin; relevance, not a count, bounds it.
59
+ * @example const use = createAutomaticAgentPluginUse({ mode: 'auto', ports });
60
+ */
61
+ export function createAutomaticAgentPluginUse({ mode, state: initialState, ports }, _options = {}) {
62
+ let state = readChatAgentPluginState({ value: initialState });
63
+ const delivered = new Map();
64
+ const alreadyLoaded = new Set();
65
+ let publishedSize;
66
+ let queue = Promise.resolve();
67
+ /** Ask mode admits only consented matches; the first non-empty match set asks the person once. */
68
+ async function applyConsent({ matches }, { signal } = {}) {
69
+ if (mode !== 'ask')
70
+ return matches;
71
+ const { decision } = state;
72
+ if (decision === null || decision.asked === false)
73
+ return matches.length ? askConsent({ matches }, { signal }) : matches;
74
+ // Consent survives cold runs; relevance still comes from this request or write capability.
75
+ // Later matches do not inherit consent, and exclusions were applied before ranking.
76
+ return matches.filter(plugin => decision.selected.includes(plugin.pluginId));
77
+ }
78
+ async function askConsent({ matches }, { signal } = {}) {
79
+ const offered = matches.map(plugin => describeMatch({ plugin }));
80
+ const answer = await ports.ask({ matches: offered, ...(signal ? { signal } : {}) }, {});
81
+ const selected = answer.action === 'skip' ? [] : answer.selected.filter(id => matches.some(plugin => plugin.pluginId === id));
82
+ state = { decision: { selected, asked: true }, excluded: [...new Set([...state.excluded, ...matches.filter(plugin => !selected.includes(plugin.pluginId)).map(plugin => plugin.pluginId)])] };
83
+ if (answer.action === 'auto') {
84
+ await ports.saveAuto({}, {});
85
+ mode = 'auto';
86
+ }
87
+ // Consent changes only this person's preference, never a workspace default.
88
+ return matches.filter(plugin => selected.includes(plugin.pluginId));
89
+ }
90
+ async function resolveGuidance({ matches }, { signal } = {}) {
91
+ const guidance = [];
92
+ for (const match of matches) {
93
+ if (signal?.aborted)
94
+ break;
95
+ try {
96
+ const text = await ports.resolve({ pluginId: match.pluginId }, {});
97
+ if (!text.trim())
98
+ continue;
99
+ delivered.set(match.pluginId, match.displayName ?? match.pluginId);
100
+ guidance.push(text);
101
+ }
102
+ catch {
103
+ // Automatic guidance is advisory; unavailable plugins must not block the run.
104
+ }
105
+ }
106
+ return guidance;
107
+ }
108
+ /** Auto mode records everything delivered as selected, keeping any earlier selection and asked flag. */
109
+ function rememberAutoSelection() {
110
+ if (mode !== 'auto' || !delivered.size)
111
+ return;
112
+ const { decision } = state;
113
+ state = { ...state, decision: { selected: [...new Set([...(decision?.selected ?? []), ...delivered.keys()])], asked: decision?.asked ?? false } };
114
+ }
115
+ /** Initialize usage after a decision (including Skip), then publish only loaded-set changes.
116
+ * Empty discovery must not consume that initial publication: a later Ask answer must persist.
117
+ */
118
+ async function publishUsage() {
119
+ if (publishedSize === delivered.size || (delivered.size === 0 && state.decision === null))
120
+ return;
121
+ publishedSize = delivered.size;
122
+ try {
123
+ await ports.publish({ state, plugins: [...delivered].map(([id, name]) => ({ id, name })) }, {});
124
+ }
125
+ catch {
126
+ // Usage publication is advisory; losing its event must not discard resolved model guidance.
127
+ }
128
+ }
129
+ /** Query preflight suggests; only an explicit id or guarded capability resolves guidance.
130
+ * Effects serialize with loads so a suggestion never consumes a backstop's delivery.
131
+ */
132
+ async function prepare(input) {
133
+ if (mode === 'off' || input.signal?.aborted)
134
+ return '';
135
+ for (const id of input.loaded ?? [])
136
+ alreadyLoaded.add(id);
137
+ const candidates = (await ports.list({}, {})).filter(plugin => plugin.enabled && !state.excluded.includes(plugin.pluginId));
138
+ const ranked = rankCandidates({ candidates, input, ports });
139
+ // Owner decision 2026-10-18: no per-chat plugin count; the relevance threshold and vendor gate decide.
140
+ const relevant = ranked.filter(plugin => !delivered.has(plugin.pluginId) && !alreadyLoaded.has(plugin.pluginId));
141
+ const matches = await applyConsent({ matches: relevant }, { signal: input.signal });
142
+ // Relevance is disclosure, never evidence that the model has read any guidance.
143
+ const shouldLoad = input.pluginId !== undefined || !!input.capabilities?.length;
144
+ const result = shouldLoad ? (await resolveGuidance({ matches }, { signal: input.signal })).join('\n\n') : relevantPluginNote({ matches });
145
+ rememberAutoSelection();
146
+ await publishUsage();
147
+ return result;
148
+ }
149
+ /** Share one queue between preflight, explicit reads and simultaneous guarded writes. */
150
+ function enqueue(input) {
151
+ const next = queue.then(() => prepare(input));
152
+ queue = next.catch(() => undefined);
153
+ return next;
154
+ }
155
+ return {
156
+ /** Suggest relevant metadata for a query; capability calls load only their providers. */
157
+ prepare: (input, _options = {}) => enqueue(input),
158
+ /** Resolve one active, permitted id and publish usage only after real guidance is read.
159
+ * Returns an empty string for denied, unavailable, or already-delivered guidance.
160
+ * @example await controller.load({ pluginId: 'my-plugin' });
161
+ */
162
+ load: (input, _options = {}) => enqueue(input),
163
+ };
164
+ }
165
+ //# sourceMappingURL=automatic-use.js.map
@@ -0,0 +1,19 @@
1
+ /** Stored in place of a present-but-unusable range so the plugin stays listable but never enables. */
2
+ export declare const INVALID_AGENT_PLUGIN_HOST_RANGE = "<invalid>";
3
+ /** A blank string is a semver wildcard, so blank is rejected here rather than silently allowed.
4
+ * The same holds for a blank `||` alternative: semver reads '>=99 ||' as '*', i.e. allow-all. */
5
+ export declare function isValidAgentPluginHostRange({ range }: {
6
+ readonly range: string;
7
+ }, _optional?: Record<string, never>): boolean;
8
+ /** Reads metadata in the host-selected namespace. A present malformed range stays incompatible;
9
+ * malformed `requires` is advisory only, so it is dropped with a warning.
10
+ * @returns Normalized advisory metadata and parse warnings. @complexity O(r).
11
+ */
12
+ export declare function readAgentPluginCompatibilityMetadata({ extension }: {
13
+ readonly extension: Readonly<Record<string, unknown>>;
14
+ }, _optional?: Record<string, never>): {
15
+ requires?: readonly string[];
16
+ hostRange?: string;
17
+ warnings: string[];
18
+ };
19
+ //# sourceMappingURL=compatibility-metadata.d.ts.map