@agent-native/core 0.206.0-nightly-20261009124314 → 0.206.0

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 (74) hide show
  1. package/dist/action.d.ts +7 -1
  2. package/dist/app-config/extension-display-sources.d.ts +31 -0
  3. package/dist/app-config/extension-display-sources.js +45 -0
  4. package/dist/app-config/extensions.d.ts +7 -0
  5. package/dist/app-config/extensions.js +39 -0
  6. package/dist/app-config/index.d.ts +1 -0
  7. package/dist/app-config/index.js +1 -0
  8. package/dist/app-config/schema.d.ts +4 -0
  9. package/dist/app-config/schema.js +2 -0
  10. package/dist/client/agent-chat/index.d.ts +1 -1
  11. package/dist/client/agent-chat/index.js +1 -1
  12. package/dist/client/embed-auth.d.ts +2 -0
  13. package/dist/client/embed-auth.js +160 -12
  14. package/dist/client/host/index.d.ts +2 -2
  15. package/dist/client/host/index.js +2 -2
  16. package/dist/client/mcp-app-host.d.ts +1 -0
  17. package/dist/client/mcp-app-host.js +4 -1
  18. package/dist/client/session-replay.js +70 -6
  19. package/dist/collab/routes.d.ts +1 -1
  20. package/dist/db/client.js +2 -0
  21. package/dist/db/drizzle-config.js +2 -0
  22. package/dist/event-bus/bus.d.ts +6 -0
  23. package/dist/event-bus/bus.js +24 -1
  24. package/dist/event-bus/index.d.ts +1 -1
  25. package/dist/event-bus/index.js +1 -1
  26. package/dist/extensions/html-shell.d.ts +35 -2
  27. package/dist/extensions/html-shell.js +62 -5
  28. package/dist/extensions/iframe-csp.d.ts +20 -0
  29. package/dist/extensions/iframe-csp.js +30 -0
  30. package/dist/extensions/routes.js +17 -5
  31. package/dist/mcp/build-server.d.ts +15 -0
  32. package/dist/mcp/build-server.js +307 -91
  33. package/dist/mcp/connect-route.js +1 -0
  34. package/dist/mcp/embed-app.js +291 -31
  35. package/dist/mcp/oauth-route.js +3 -0
  36. package/dist/mcp/oauth-store.d.ts +2 -1
  37. package/dist/mcp/oauth-store.js +5 -6
  38. package/dist/mcp/oauth-token.d.ts +5 -0
  39. package/dist/mcp/oauth-token.js +17 -0
  40. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  41. package/dist/mcp/server.d.ts +2 -2
  42. package/dist/mcp/server.js +2 -2
  43. package/dist/notifications/routes.d.ts +1 -1
  44. package/dist/observability/routes.d.ts +5 -5
  45. package/dist/org/index.d.ts +2 -2
  46. package/dist/org/workspace-app-access-request.d.ts +8 -0
  47. package/dist/org/workspace-app-access-request.js +20 -0
  48. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  49. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  50. package/dist/resources/store.d.ts +22 -0
  51. package/dist/resources/store.js +88 -5
  52. package/dist/secrets/routes.d.ts +11 -11
  53. package/dist/server/action-routes.d.ts +2 -0
  54. package/dist/server/action-routes.js +187 -31
  55. package/dist/server/agent-chat/mcp-options.d.ts +3 -1
  56. package/dist/server/agent-chat-plugin.js +26 -1
  57. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  58. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  59. package/dist/server/auth.js +11 -20
  60. package/dist/server/embed-route.js +5 -3
  61. package/dist/server/embed-session.d.ts +30 -1
  62. package/dist/server/embed-session.js +376 -30
  63. package/dist/server/realtime-token.d.ts +1 -1
  64. package/dist/server/request-context.d.ts +2 -0
  65. package/dist/server/request-context.js +25 -1
  66. package/dist/shared/embed-auth.d.ts +74 -0
  67. package/dist/shared/embed-auth.js +379 -10
  68. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  69. package/dist/triggers/dispatcher.d.ts +11 -0
  70. package/dist/triggers/dispatcher.js +239 -50
  71. package/dist/triggers/index.d.ts +1 -1
  72. package/dist/triggers/index.js +1 -1
  73. package/docs/content/template-design-developers.mdx +1 -0
  74. package/package.json +4 -4
@@ -13,8 +13,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
13
13
  };
14
14
  }>>;
15
15
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- ok?: undefined;
17
16
  error: string;
17
+ ok?: undefined;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
package/dist/db/client.js CHANGED
@@ -156,7 +156,9 @@ export function pgliteDataDirFromUrl(url) {
156
156
  if (!dataDir || dataDir === "/")
157
157
  return "./data/pglite";
158
158
  if (dataDir === "memory" ||
159
+ dataDir === "memory:" ||
159
160
  dataDir === "/memory" ||
161
+ dataDir === "/memory:" ||
160
162
  dataDir === ":memory:" ||
161
163
  dataDir === "/:memory:" ||
162
164
  dataDir === "memory://") {
@@ -20,7 +20,9 @@ function pgliteDataDirFromUrl(url) {
20
20
  if (!dataDir || dataDir === "/")
21
21
  return "./data/pglite";
22
22
  if (dataDir === "memory" ||
23
+ dataDir === "memory:" ||
23
24
  dataDir === "/memory" ||
25
+ dataDir === "/memory:" ||
24
26
  dataDir === ":memory:" ||
25
27
  dataDir === "/:memory:" ||
26
28
  dataDir === "memory://") {
@@ -1,6 +1,12 @@
1
1
  import type { EventMeta } from "./types.js";
2
2
  type Handler = (payload: unknown, meta: EventMeta) => void | Promise<void>;
3
+ type AnyEventHandler = (event: string, payload: unknown, meta: EventMeta) => void | Promise<void>;
3
4
  export declare function subscribe(event: string, handler: Handler): string;
5
+ /**
6
+ * Subscribe to every emitted event. For a subscriber whose interest is stored
7
+ * data it should not read until an event actually arrives.
8
+ */
9
+ export declare function subscribeAll(handler: AnyEventHandler): string;
4
10
  export declare function unsubscribe(id: string): boolean;
5
11
  export declare function emit(event: string, payload: unknown, meta?: Partial<EventMeta>): void;
6
12
  /** Emit an event and wait until every subscriber has accepted it. */
@@ -11,6 +11,11 @@ function getBus() {
11
11
  }
12
12
  return g[BUS_KEY];
13
13
  }
14
+ function getAnyEventSubscriptions() {
15
+ const bus = getBus();
16
+ bus.anyEventSubscriptions ??= new Map();
17
+ return bus.anyEventSubscriptions;
18
+ }
14
19
  export function subscribe(event, handler) {
15
20
  if (typeof event !== "string" || !event) {
16
21
  throw new Error("subscribe: event name is required");
@@ -24,7 +29,21 @@ export function subscribe(event, handler) {
24
29
  bus.emitter.on(event, handler);
25
30
  return id;
26
31
  }
32
+ /**
33
+ * Subscribe to every emitted event. For a subscriber whose interest is stored
34
+ * data it should not read until an event actually arrives.
35
+ */
36
+ export function subscribeAll(handler) {
37
+ if (typeof handler !== "function") {
38
+ throw new Error("subscribeAll: handler must be a function");
39
+ }
40
+ const id = randomUUID();
41
+ getAnyEventSubscriptions().set(id, handler);
42
+ return id;
43
+ }
27
44
  export function unsubscribe(id) {
45
+ if (getAnyEventSubscriptions().delete(id))
46
+ return true;
28
47
  const bus = getBus();
29
48
  const sub = bus.subscriptions.get(id);
30
49
  if (!sub)
@@ -95,7 +114,10 @@ function prepareDispatch(event, payload, meta, throwOnInvalid) {
95
114
  emittedAt: meta?.emittedAt ?? new Date().toISOString(),
96
115
  owner: meta?.owner,
97
116
  };
98
- const listeners = bus.emitter.listeners(event);
117
+ const listeners = [
118
+ ...bus.emitter.listeners(event),
119
+ ...[...getAnyEventSubscriptions().values()].map((handler) => (eventPayload, eventMeta) => handler(event, eventPayload, eventMeta)),
120
+ ];
99
121
  return { payload: validated, meta: fullMeta, listeners };
100
122
  }
101
123
  export function listSubscriptions(event) {
@@ -112,4 +134,5 @@ export function __resetEventBus() {
112
134
  const bus = getBus();
113
135
  bus.emitter.removeAllListeners();
114
136
  bus.subscriptions.clear();
137
+ getAnyEventSubscriptions().clear();
115
138
  }
@@ -1,3 +1,3 @@
1
1
  export { registerEvent, listEvents, getEvent } from "./registry.js";
2
- export { emit, emitAsync, subscribe, unsubscribe, listSubscriptions, } from "./bus.js";
2
+ export { emit, emitAsync, subscribe, subscribeAll, unsubscribe, listSubscriptions, } from "./bus.js";
3
3
  export type { EventDefinition, EventSubscription, EventMeta } from "./types.js";
@@ -1,2 +1,2 @@
1
1
  export { registerEvent, listEvents, getEvent } from "./registry.js";
2
- export { emit, emitAsync, subscribe, unsubscribe, listSubscriptions, } from "./bus.js";
2
+ export { emit, emitAsync, subscribe, subscribeAll, unsubscribe, listSubscriptions, } from "./bus.js";
@@ -1,6 +1,31 @@
1
+ /**
2
+ * Builds the iframe CSP from the img-src / media-src lists.
3
+ *
4
+ * SECURITY: both lists are validated again here, at the point they are
5
+ * interpolated, rather than trusted because they came from app config. The
6
+ * resolved config is a cached, mutable object, and the client renderers pass
7
+ * lists fetched from the server, so neither is guaranteed to still be what the
8
+ * schema accepted. A list that fails validation throws instead of producing a
9
+ * policy with an injected directive.
10
+ */
11
+ export declare function extensionIframeCspBase(imageSources: readonly string[], mediaSources: readonly string[]): string;
1
12
  export declare const EXTENSION_FRAME_ANCESTORS: string;
2
13
  export declare const EXTENSION_IFRAME_CSP: string;
3
- export declare const EXTENSION_IFRAME_META_CSP = "default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';";
14
+ export declare const EXTENSION_IFRAME_META_CSP: string;
15
+ /**
16
+ * Authenticated endpoint that returns the deployment's validated
17
+ * `{ imageSources, mediaSources }`, so the client-rendered `srcDoc` frames
18
+ * (`ExtensionViewer`, `InlineExtensionFrame`) can apply the same img-src /
19
+ * media-src policy as the server render route without importing app config.
20
+ */
21
+ export declare const EXTENSION_IFRAME_DISPLAY_SOURCES_PATH = "/_agent-native/extensions/iframe/display-sources";
22
+ /**
23
+ * Builds the meta CSP for a client-rendered frame from the
24
+ * `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH` response. Anything that is not a
25
+ * valid pair of source lists falls back to the default policy, which blocks
26
+ * every remote image and media source (fail closed).
27
+ */
28
+ export declare function extensionIframeMetaCspFromDisplaySources(value: unknown): string;
4
29
  /**
5
30
  * SECURITY — EXTENSION CONTENT IS UNTRUSTED.
6
31
  *
@@ -21,6 +46,14 @@ export declare const EXTENSION_IFRAME_META_CSP = "default-src 'none'; script-src
21
46
  * header sanitization, and method allowlist; do not relax those gates
22
47
  * for "convenience" in this file or any caller.
23
48
  *
49
+ * 3. `connect-src` stays `'self'` and stays out of app config. Remote
50
+ * images and media are `extensions.iframeImageSources` /
51
+ * `iframeMediaSources`, which reach only `img-src` / `media-src`.
52
+ * Each allowed remote origin is an explicit egress permission: the
53
+ * browser sends the URL, and extension script can encode data into
54
+ * it. API calls stay on the permission-gated host bridge. Token
55
+ * validation still stops a value from appending another directive.
56
+ *
24
57
  * For the trust model rationale, see audit 05-tools-sandbox.md (C1) and the
25
58
  * `extensions` skill. When in doubt, fail closed.
26
59
  *
@@ -58,4 +91,4 @@ export interface ExtensionRenderBinding {
58
91
  externalFetch?: boolean;
59
92
  };
60
93
  }
61
- export declare function buildExtensionHtml(content: string, themeVars: string, isDark: boolean, extensionId?: string, binding?: ExtensionRenderBinding): string;
94
+ export declare function buildExtensionHtml(content: string, themeVars: string, isDark: boolean, extensionId?: string, binding?: ExtensionRenderBinding, metaCsp?: string): string;
@@ -1,5 +1,31 @@
1
+ // Import-free on purpose: this shell is also built in the browser
2
+ // (ExtensionViewer, InlineExtensionFrame), so it must not import the
3
+ // app-config store. Configured sources are resolved server-side in
4
+ // ./iframe-csp.ts: the render route passes them in, and the client renderers
5
+ // fetch them from EXTENSION_IFRAME_DISPLAY_SOURCES_PATH.
6
+ import { DEFAULT_EXTENSION_DISPLAY_SOURCES, extensionDisplaySourcesError, } from "../app-config/extension-display-sources.js";
1
7
  import { buildSessionReplayIframeBootstrap } from "./session-replay-iframe.js";
2
- const EXTENSION_IFRAME_CSP_BASE = "default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src 'self' data: blob:; media-src 'self' data: blob:; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';";
8
+ /**
9
+ * Builds the iframe CSP from the img-src / media-src lists.
10
+ *
11
+ * SECURITY: both lists are validated again here, at the point they are
12
+ * interpolated, rather than trusted because they came from app config. The
13
+ * resolved config is a cached, mutable object, and the client renderers pass
14
+ * lists fetched from the server, so neither is guaranteed to still be what the
15
+ * schema accepted. A list that fails validation throws instead of producing a
16
+ * policy with an injected directive.
17
+ */
18
+ export function extensionIframeCspBase(imageSources, mediaSources) {
19
+ const imageError = extensionDisplaySourcesError(imageSources);
20
+ if (imageError) {
21
+ throw new TypeError(`Invalid extension iframe img-src: ${imageError}`);
22
+ }
23
+ const mediaError = extensionDisplaySourcesError(mediaSources);
24
+ if (mediaError) {
25
+ throw new TypeError(`Invalid extension iframe media-src: ${mediaError}`);
26
+ }
27
+ return `default-src 'none'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self'; img-src ${imageSources.join(" ")}; media-src ${mediaSources.join(" ")}; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';`;
28
+ }
3
29
  export const EXTENSION_FRAME_ANCESTORS = [
4
30
  "'self'",
5
31
  "https://agent-native.com",
@@ -10,9 +36,40 @@ export const EXTENSION_FRAME_ANCESTORS = [
10
36
  "https://*.claudemcpcontent.com",
11
37
  "https://*.web-sandbox.oaiusercontent.com",
12
38
  ].join(" ");
13
- export const EXTENSION_IFRAME_CSP = `${EXTENSION_IFRAME_CSP_BASE} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;
14
- export const EXTENSION_IFRAME_META_CSP = EXTENSION_IFRAME_CSP_BASE;
15
- export function buildExtensionHtml(content, themeVars, isDark, extensionId, binding) {
39
+ // The two constants below are the policy an app gets before it configures
40
+ // anything. `buildExtensionIframeCsp()` (server-only, ./iframe-csp.ts) resolves `extensions.iframeImageSources`
41
+ // and `extensions.iframeMediaSources` and is what the render route sets, so a
42
+ // deployment that widens img-src / media-src still gets the same
43
+ // `connect-src 'self'` as one that does not. A remote image or media
44
+ // origin is its own egress permission: the browser requests that URL.
45
+ export const EXTENSION_IFRAME_CSP = `${extensionIframeCspBase(DEFAULT_EXTENSION_DISPLAY_SOURCES, DEFAULT_EXTENSION_DISPLAY_SOURCES)} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;
46
+ export const EXTENSION_IFRAME_META_CSP = extensionIframeCspBase(DEFAULT_EXTENSION_DISPLAY_SOURCES, DEFAULT_EXTENSION_DISPLAY_SOURCES);
47
+ /**
48
+ * Authenticated endpoint that returns the deployment's validated
49
+ * `{ imageSources, mediaSources }`, so the client-rendered `srcDoc` frames
50
+ * (`ExtensionViewer`, `InlineExtensionFrame`) can apply the same img-src /
51
+ * media-src policy as the server render route without importing app config.
52
+ */
53
+ export const EXTENSION_IFRAME_DISPLAY_SOURCES_PATH = "/_agent-native/extensions/iframe/display-sources";
54
+ /**
55
+ * Builds the meta CSP for a client-rendered frame from the
56
+ * `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH` response. Anything that is not a
57
+ * valid pair of source lists falls back to the default policy, which blocks
58
+ * every remote image and media source (fail closed).
59
+ */
60
+ export function extensionIframeMetaCspFromDisplaySources(value) {
61
+ if (typeof value !== "object" || value === null) {
62
+ return EXTENSION_IFRAME_META_CSP;
63
+ }
64
+ const { imageSources, mediaSources } = value;
65
+ try {
66
+ return extensionIframeCspBase(imageSources, mediaSources);
67
+ }
68
+ catch {
69
+ return EXTENSION_IFRAME_META_CSP;
70
+ }
71
+ }
72
+ export function buildExtensionHtml(content, themeVars, isDark, extensionId, binding, metaCsp = EXTENSION_IFRAME_META_CSP) {
16
73
  const extensionIdJson = JSON.stringify(extensionId ?? "");
17
74
  const extensionIdAttr = escapeHtmlAttribute(extensionId ?? "");
18
75
  const bindingJson = JSON.stringify(binding ?? {
@@ -26,7 +83,7 @@ export function buildExtensionHtml(content, themeVars, isDark, extensionId, bind
26
83
  <head>
27
84
  <meta charset="utf-8" />
28
85
  <meta name="viewport" content="width=device-width, initial-scale=1" />
29
- <meta http-equiv="Content-Security-Policy" content="${EXTENSION_IFRAME_META_CSP}" />
86
+ <meta http-equiv="Content-Security-Policy" content="${metaCsp}" />
30
87
  ${binding && !binding.isAuthor ? `<meta name="agent-native-extension-author" content="${escapeHtmlAttribute(binding.authorEmail)}" />` : ""}
31
88
  <link rel="preconnect" href="https://fonts.googleapis.com" />
32
89
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Server-only: resolves `extensions.iframeImageSources` /
3
+ * `extensions.iframeMediaSources` from app config. Kept out of
4
+ * `html-shell.ts` because that shell is also built in the browser, where the
5
+ * app-config store must not be imported.
6
+ */
7
+ export declare function buildExtensionIframeMetaCsp(): string;
8
+ export interface ExtensionIframeDisplaySources {
9
+ imageSources: string[];
10
+ mediaSources: string[];
11
+ }
12
+ /**
13
+ * The configured img-src / media-src lists for the client-rendered frames,
14
+ * served at `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH`. Validated through the same
15
+ * CSP builder before they leave the server, and copied so the response cannot
16
+ * alias the cached resolved config.
17
+ */
18
+ export declare function getExtensionIframeDisplaySources(): ExtensionIframeDisplaySources;
19
+ /** The header CSP the render route sets: the meta CSP plus `frame-ancestors`. */
20
+ export declare function buildExtensionIframeCsp(): string;
@@ -0,0 +1,30 @@
1
+ import { getAppConfig } from "../app-config/index.js";
2
+ import { EXTENSION_FRAME_ANCESTORS, extensionIframeCspBase, } from "./html-shell.js";
3
+ /**
4
+ * Server-only: resolves `extensions.iframeImageSources` /
5
+ * `extensions.iframeMediaSources` from app config. Kept out of
6
+ * `html-shell.ts` because that shell is also built in the browser, where the
7
+ * app-config store must not be imported.
8
+ */
9
+ export function buildExtensionIframeMetaCsp() {
10
+ const { iframeImageSources, iframeMediaSources } = getAppConfig().extensions;
11
+ return extensionIframeCspBase(iframeImageSources, iframeMediaSources);
12
+ }
13
+ /**
14
+ * The configured img-src / media-src lists for the client-rendered frames,
15
+ * served at `EXTENSION_IFRAME_DISPLAY_SOURCES_PATH`. Validated through the same
16
+ * CSP builder before they leave the server, and copied so the response cannot
17
+ * alias the cached resolved config.
18
+ */
19
+ export function getExtensionIframeDisplaySources() {
20
+ const { iframeImageSources, iframeMediaSources } = getAppConfig().extensions;
21
+ extensionIframeCspBase(iframeImageSources, iframeMediaSources);
22
+ return {
23
+ imageSources: [...iframeImageSources],
24
+ mediaSources: [...iframeMediaSources],
25
+ };
26
+ }
27
+ /** The header CSP the render route sets: the meta CSP plus `frame-ancestors`. */
28
+ export function buildExtensionIframeCsp() {
29
+ return `${buildExtensionIframeMetaCsp()} frame-ancestors ${EXTENSION_FRAME_ANCESTORS};`;
30
+ }
@@ -9,7 +9,8 @@ import { runWithRequestContext, getRequestOrgId, } from "../server/request-conte
9
9
  import { ForbiddenError, resolveAccess } from "../sharing/access.js";
10
10
  import { ROLE_RANK } from "../sharing/schema.js";
11
11
  import { ExtensionContentEditError } from "./content-patch.js";
12
- import { buildExtensionHtml, EXTENSION_IFRAME_CSP } from "./html-shell.js";
12
+ import { buildExtensionHtml } from "./html-shell.js";
13
+ import { buildExtensionIframeCsp, buildExtensionIframeMetaCsp, getExtensionIframeDisplaySources, } from "./iframe-csp.js";
13
14
  import { getLocalExtension, isLocalExtensionRow, listLocalExtensions, } from "./local.js";
14
15
  import { collectSecretValues, normalizeExtensionProxyMethod, readResponseTextWithLimit, redactSecrets, redactString, sanitizeOutboundHeaders, } from "./proxy-security.js";
15
16
  import { listExtensions, getExtension, getExtensionHistoryVersion, listExtensionHistory, createExtension, updateExtension, updateExtensionContent, restoreExtensionHistoryVersion, deleteExtension, hideExtension, unhideExtension, globalHideExtension, globalUnhideExtension, ensureExtensionsTables, } from "./store.js";
@@ -51,6 +52,17 @@ export function createExtensionsHandler(options = {}) {
51
52
  setResponseStatus(event, 403);
52
53
  return { error: "Extension creation is disabled for this app" };
53
54
  }
55
+ // The configured img-src / media-src for client-rendered srcDoc frames
56
+ // (EXTENSION_IFRAME_DISPLAY_SOURCES_PATH). It only reads validated app
57
+ // config, so it is answered before the extension tables are set up.
58
+ // Two segments, and no extension route uses `display-sources`, so this
59
+ // cannot shadow an extension id.
60
+ if (method === "GET" &&
61
+ parts.length === 2 &&
62
+ parts[0] === "iframe" &&
63
+ parts[1] === "display-sources") {
64
+ return getExtensionIframeDisplaySources();
65
+ }
54
66
  await ensureExtensionsTables();
55
67
  return dispatch(event, method, parts, userEmail);
56
68
  });
@@ -130,9 +142,9 @@ async function dispatch(event, method, parts, userEmail) {
130
142
  role: "viewer",
131
143
  source: "local-files",
132
144
  permissions: localExtension.source.permissions,
133
- });
145
+ }, buildExtensionIframeMetaCsp());
134
146
  setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
135
- setResponseHeader(event, "Content-Security-Policy", EXTENSION_IFRAME_CSP);
147
+ setResponseHeader(event, "Content-Security-Policy", buildExtensionIframeCsp());
136
148
  setResponseHeader(event, "X-Content-Type-Options", "nosniff");
137
149
  setResponseHeader(event, "Referrer-Policy", "no-referrer");
138
150
  return html;
@@ -153,11 +165,11 @@ async function dispatch(event, method, parts, userEmail) {
153
165
  viewerEmail: userEmail,
154
166
  isAuthor,
155
167
  role: renderRole,
156
- });
168
+ }, buildExtensionIframeMetaCsp());
157
169
  // Security headers per render. `frame-ancestors` in the CSP must be set as
158
170
  // an HTTP header to be enforced; meta-CSP can't set it per spec.
159
171
  setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
160
- setResponseHeader(event, "Content-Security-Policy", EXTENSION_IFRAME_CSP);
172
+ setResponseHeader(event, "Content-Security-Policy", buildExtensionIframeCsp());
161
173
  setResponseHeader(event, "X-Content-Type-Options", "nosniff");
162
174
  setResponseHeader(event, "Referrer-Policy", "no-referrer");
163
175
  return html;
@@ -34,6 +34,8 @@ export interface MCPConfig {
34
34
  productionActions?: Record<string, ActionEntry>;
35
35
  /** Unlisted from this MCP surface; used only when minting scoped widget tickets. */
36
36
  widgetReadActions?: Record<string, ActionEntry>;
37
+ /** Unlisted from this MCP surface; editor mutations callable only by scoped widget grants. */
38
+ widgetWriteActions?: Record<string, ActionEntry>;
37
39
  askAgent?: (message: string) => Promise<string>;
38
40
  builtinCrossAppTools?: boolean;
39
41
  /**
@@ -55,7 +57,15 @@ export interface MCPConfig {
55
57
  widgets?: boolean;
56
58
  widgetDomain?: string;
57
59
  widgetTargets?: Record<string, (args: Record<string, unknown>, result: unknown) => McpDirectoryWidgetTarget | null>;
60
+ authorizeWidgetWrite?: (input: {
61
+ toolName: string;
62
+ args: Record<string, unknown>;
63
+ result: unknown;
64
+ target: McpDirectoryWidgetTarget;
65
+ identity: MCPCallerIdentity;
66
+ }) => boolean | Promise<boolean>;
58
67
  widgetReadActionArguments?: Record<string, Record<string, McpDirectoryWidgetReadArgument>>;
68
+ widgetWriteActionArguments?: Record<string, Record<string, McpDirectoryWidgetReadArgument>>;
59
69
  /** Actions whose capability-backed `mcp-widget` execution is strictly read-only. */
60
70
  widgetReadOnlyActions?: readonly string[];
61
71
  /** Unlisted public reads that are available only through a scoped widget ticket. */
@@ -79,10 +89,14 @@ export interface MCPConfig {
79
89
  export interface McpDirectoryWidgetTarget {
80
90
  targetPath: string;
81
91
  resourceIds: Record<string, string>;
92
+ /** Mutations available to this artifact's editor, selected from the profile allowlist. */
93
+ writeActions?: readonly string[];
82
94
  }
83
95
  export interface MCPCallerIdentity {
84
96
  userEmail: string | undefined;
85
97
  identityAssurance?: "user" | "organization" | "service";
98
+ /** Issue time, in milliseconds, from a verified app-issued MCP credential. */
99
+ mcpCredentialIssuedAtMs?: number;
86
100
  orgId?: string | null;
87
101
  orgDomain: string | undefined;
88
102
  oauthScopes?: string[];
@@ -108,6 +122,7 @@ export declare class McpDirectoryProfileValidationError extends Error {
108
122
  }
109
123
  export declare function selectMcpActionSurface(config: MCPConfig, requestMeta?: MCPRequestMeta): Record<string, ActionEntry>;
110
124
  export declare function selectMcpDirectoryWidgetReadActions(profile: MCPConfig["directoryProfile"] | undefined, actions: Record<string, ActionEntry>): Record<string, ActionEntry> | undefined;
125
+ export declare function selectMcpDirectoryWidgetWriteActions(profile: MCPConfig["directoryProfile"] | undefined, actions: Record<string, ActionEntry>): Record<string, ActionEntry> | undefined;
111
126
  export declare function getConfiguredMcpOwnerEmail(): string | undefined;
112
127
  export declare function validateMcpDirectoryProfile(config: MCPConfig, sourceActions?: Record<string, ActionEntry>): void;
113
128
  export declare function validateMcpDirectoryWidgetDomain(domain: string | undefined): void;