@agent-native/toolkit 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 (32) hide show
  1. package/dist/app/auth/entry.d.ts +2 -2
  2. package/dist/app/auth/entry.d.ts.map +1 -1
  3. package/dist/app/auth/entry.js +9 -2
  4. package/dist/app/auth/entry.js.map +1 -1
  5. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  6. package/dist/app/chat/chat/run-recovery.js +7 -5
  7. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  8. package/dist/app/extensions/ExtensionViewer.d.ts.map +1 -1
  9. package/dist/app/extensions/ExtensionViewer.js +16 -6
  10. package/dist/app/extensions/ExtensionViewer.js.map +1 -1
  11. package/dist/app/extensions/InlineExtensionFrame.d.ts.map +1 -1
  12. package/dist/app/extensions/InlineExtensionFrame.js +9 -5
  13. package/dist/app/extensions/InlineExtensionFrame.js.map +1 -1
  14. package/dist/app/extensions/iframe-display-sources.d.ts +9 -0
  15. package/dist/app/extensions/iframe-display-sources.d.ts.map +1 -0
  16. package/dist/app/extensions/iframe-display-sources.js +56 -0
  17. package/dist/app/extensions/iframe-display-sources.js.map +1 -0
  18. package/dist/app/progress/RunsTray.js +1 -1
  19. package/dist/app/progress/RunsTray.js.map +1 -1
  20. package/dist/app/styles/agent-native.css +22 -4
  21. package/package.json +4 -4
  22. package/src/app/auth/entry.spec.tsx +31 -1
  23. package/src/app/auth/entry.tsx +13 -1
  24. package/src/app/chat/chat/run-recovery.tsx +7 -5
  25. package/src/app/extensions/ExtensionViewer.spec.tsx +68 -2
  26. package/src/app/extensions/ExtensionViewer.tsx +15 -4
  27. package/src/app/extensions/InlineExtensionFrame.spec.tsx +17 -2
  28. package/src/app/extensions/InlineExtensionFrame.tsx +13 -3
  29. package/src/app/extensions/iframe-display-sources.spec.ts +74 -0
  30. package/src/app/extensions/iframe-display-sources.ts +61 -0
  31. package/src/app/progress/RunsTray.tsx +1 -1
  32. package/src/app/styles/agent-native.css +22 -4
@@ -11,8 +11,20 @@ vi.mock("@agent-native/core/client/agent-chat", () => ({
11
11
  }));
12
12
 
13
13
  vi.mock("@agent-native/core/extensions/html-shell", () => ({
14
- buildExtensionHtml: (content: string) =>
15
- `<!doctype html><html><body>${content}</body></html>`,
14
+ buildExtensionHtml: (
15
+ content: string,
16
+ _themeVars: string,
17
+ _isDark: boolean,
18
+ _extensionId?: string,
19
+ _binding?: unknown,
20
+ metaCsp?: string,
21
+ ) =>
22
+ `<!doctype html><html><head><meta http-equiv="Content-Security-Policy" content="${metaCsp ?? ""}" /></head><body>${content}</body></html>`,
23
+ }));
24
+
25
+ vi.mock("./iframe-display-sources.js", () => ({
26
+ useExtensionIframeMetaCsp: (enabled = true) =>
27
+ enabled ? "img-src 'self' https://cdn.example.com;" : undefined,
16
28
  }));
17
29
 
18
30
  describe("InlineExtensionFrame", () => {
@@ -54,6 +66,9 @@ describe("InlineExtensionFrame", () => {
54
66
  "allow-scripts allow-forms allow-popups allow-downloads",
55
67
  );
56
68
  expect(iframe?.getAttribute("srcdoc")).toContain("Send choice");
69
+ expect(iframe?.getAttribute("srcdoc")).toContain(
70
+ `content="img-src 'self' https://cdn.example.com;"`,
71
+ );
57
72
 
58
73
  await act(async () => {
59
74
  window.dispatchEvent(
@@ -13,6 +13,7 @@ import {
13
13
  type BridgePolicyContext,
14
14
  type ExtensionBridgeRole,
15
15
  } from "./iframe-bridge.js";
16
+ import { useExtensionIframeMetaCsp } from "./iframe-display-sources.js";
16
17
  import { normalizeAgentNativeExtensionSandbox } from "./portable-extension.js";
17
18
 
18
19
  const THEME_CSS_VARS = [
@@ -300,6 +301,7 @@ async function handleTransientExtensionData(
300
301
  function buildTransientSrcDoc(
301
302
  extension: InlineExtensionDefinition,
302
303
  isDark: boolean,
304
+ metaCsp: string,
303
305
  ): string {
304
306
  return buildExtensionHtml(
305
307
  extension.content ?? "",
@@ -312,6 +314,7 @@ function buildTransientSrcDoc(
312
314
  isAuthor: true,
313
315
  role: "owner",
314
316
  },
317
+ metaCsp,
315
318
  );
316
319
  }
317
320
 
@@ -397,10 +400,17 @@ export function InlineExtensionFrame({
397
400
  );
398
401
  }, [extension?.updatedAt, isTransient, resolvedId, slotId]);
399
402
 
403
+ // Transient frames are built here, so they fetch the deployment's configured
404
+ // img-src / media-src from the server and wait for it before rendering.
405
+ const iframeMetaCsp = useExtensionIframeMetaCsp(!!extension && isTransient);
400
406
  const srcDoc = useMemo(() => {
401
- if (!extension || !isTransient) return undefined;
402
- return buildTransientSrcDoc(extension, initialDarkRef.current);
403
- }, [extension, isTransient]);
407
+ if (!extension || !isTransient || !iframeMetaCsp) return undefined;
408
+ return buildTransientSrcDoc(
409
+ extension,
410
+ initialDarkRef.current,
411
+ iframeMetaCsp,
412
+ );
413
+ }, [extension, iframeMetaCsp, isTransient]);
404
414
 
405
415
  useEffect(() => {
406
416
  bridgeContextRef.current = isTransient
@@ -0,0 +1,74 @@
1
+ import { EXTENSION_IFRAME_META_CSP } from "@agent-native/core/extensions/html-shell";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ import {
5
+ loadExtensionIframeMetaCsp,
6
+ resetExtensionIframeMetaCspForTests,
7
+ } from "./iframe-display-sources.js";
8
+
9
+ describe("loadExtensionIframeMetaCsp", () => {
10
+ beforeEach(() => {
11
+ resetExtensionIframeMetaCspForTests();
12
+ });
13
+
14
+ afterEach(() => {
15
+ vi.unstubAllGlobals();
16
+ });
17
+
18
+ it("builds the meta CSP from the server's configured sources once", async () => {
19
+ const fetchMock = vi.fn(async () =>
20
+ Response.json({
21
+ imageSources: ["'self'", "https:"],
22
+ mediaSources: ["'self'", "blob:"],
23
+ }),
24
+ );
25
+ vi.stubGlobal("fetch", fetchMock);
26
+
27
+ const csp = await loadExtensionIframeMetaCsp();
28
+ expect(csp).toContain("img-src 'self' https:;");
29
+ expect(csp).toContain("media-src 'self' blob:;");
30
+ expect(csp).toContain("connect-src 'self';");
31
+ await expect(loadExtensionIframeMetaCsp()).resolves.toBe(csp);
32
+ expect(fetchMock).toHaveBeenCalledTimes(1);
33
+ expect(String(fetchMock.mock.calls[0]?.[0])).toContain(
34
+ "/_agent-native/extensions/iframe/display-sources",
35
+ );
36
+ });
37
+
38
+ it("falls back to the default policy for an invalid source list", async () => {
39
+ vi.stubGlobal(
40
+ "fetch",
41
+ vi.fn(async () =>
42
+ Response.json({
43
+ imageSources: ["'self'; connect-src *"],
44
+ mediaSources: ["'self'"],
45
+ }),
46
+ ),
47
+ );
48
+
49
+ await expect(loadExtensionIframeMetaCsp()).resolves.toBe(
50
+ EXTENSION_IFRAME_META_CSP,
51
+ );
52
+ });
53
+
54
+ it("falls back to the default policy on a failed request and retries later", async () => {
55
+ const fetchMock = vi
56
+ .fn()
57
+ .mockResolvedValueOnce(new Response("nope", { status: 500 }))
58
+ .mockResolvedValueOnce(
59
+ Response.json({
60
+ imageSources: ["'self'", "https:"],
61
+ mediaSources: ["'self'"],
62
+ }),
63
+ );
64
+ vi.stubGlobal("fetch", fetchMock);
65
+
66
+ await expect(loadExtensionIframeMetaCsp()).resolves.toBe(
67
+ EXTENSION_IFRAME_META_CSP,
68
+ );
69
+ await expect(loadExtensionIframeMetaCsp()).resolves.toContain(
70
+ "img-src 'self' https:;",
71
+ );
72
+ expect(fetchMock).toHaveBeenCalledTimes(2);
73
+ });
74
+ });
@@ -0,0 +1,61 @@
1
+ import { agentNativePath } from "@agent-native/core/client/api-path";
2
+ import {
3
+ EXTENSION_IFRAME_DISPLAY_SOURCES_PATH,
4
+ EXTENSION_IFRAME_META_CSP,
5
+ extensionIframeMetaCspFromDisplaySources,
6
+ } from "@agent-native/core/extensions/html-shell";
7
+ import { useEffect, useState } from "react";
8
+
9
+ /**
10
+ * Client-rendered extension frames (`ExtensionViewer` srcDoc,
11
+ * `InlineExtensionFrame` transient frames) build their HTML in the browser, so
12
+ * they read the deployment's `extensions.iframeImageSources` /
13
+ * `iframeMediaSources` from the server instead of from app config. The lists
14
+ * are validated again by `extensionIframeMetaCspFromDisplaySources`, and any
15
+ * failure (network, non-OK status, malformed body, invalid source) falls back
16
+ * to the default policy, which blocks remote images and media.
17
+ */
18
+ let pending: Promise<string> | null = null;
19
+
20
+ export function loadExtensionIframeMetaCsp(): Promise<string> {
21
+ if (!pending) {
22
+ const request = fetch(
23
+ agentNativePath(EXTENSION_IFRAME_DISPLAY_SOURCES_PATH),
24
+ )
25
+ .then(async (res) => {
26
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
27
+ return extensionIframeMetaCspFromDisplaySources(await res.json());
28
+ })
29
+ .catch(() => {
30
+ // Do not cache a failure: the next frame retries.
31
+ if (pending === request) pending = null;
32
+ return EXTENSION_IFRAME_META_CSP;
33
+ });
34
+ pending = request;
35
+ }
36
+ return pending;
37
+ }
38
+
39
+ /**
40
+ * The meta CSP for a client-rendered frame, or `undefined` while it loads.
41
+ * Callers wait for it rather than rendering with the default policy first, so
42
+ * a frame is built once with the policy it keeps.
43
+ */
44
+ export function useExtensionIframeMetaCsp(enabled = true): string | undefined {
45
+ const [metaCsp, setMetaCsp] = useState<string | undefined>(undefined);
46
+ useEffect(() => {
47
+ if (!enabled) return;
48
+ let cancelled = false;
49
+ void loadExtensionIframeMetaCsp().then((csp) => {
50
+ if (!cancelled) setMetaCsp(csp);
51
+ });
52
+ return () => {
53
+ cancelled = true;
54
+ };
55
+ }, [enabled]);
56
+ return enabled ? metaCsp : undefined;
57
+ }
58
+
59
+ export function resetExtensionIframeMetaCspForTests(): void {
60
+ pending = null;
61
+ }
@@ -353,7 +353,7 @@ export function RunsTray({
353
353
  open={open}
354
354
  onOpenChange={setOpen}
355
355
  trigger={
356
- <span className="contents">
356
+ <span className="inline-flex shrink-0">
357
357
  <DesignSystemTooltip
358
358
  trigger={trigger}
359
359
  content={triggerLabel}
@@ -1061,10 +1061,18 @@
1061
1061
  }
1062
1062
 
1063
1063
  .agent-builder-setup-card--attached {
1064
+ inline-size: min(
1065
+ calc(100% - var(--agent-kit-composer-adjacent-inline-inset) - 0.5rem),
1066
+ var(--agent-kit-conversation-max-width)
1067
+ );
1068
+ min-inline-size: 0;
1069
+ max-inline-size: min(100%, var(--agent-kit-conversation-max-width));
1064
1070
  position: relative;
1065
1071
  z-index: 1;
1072
+ margin-inline: auto;
1066
1073
  margin-bottom: -1.875rem;
1067
1074
  padding-bottom: 0 !important;
1075
+ padding-inline: 0 !important;
1068
1076
  animation: agent-builder-setup-card-enter 220ms var(--ease-out-strong) both;
1069
1077
  }
1070
1078
 
@@ -1098,10 +1106,20 @@
1098
1106
  padding-bottom: calc(0.625rem + 1.875rem);
1099
1107
  }
1100
1108
 
1101
- .agent-builder-setup-card--attached:not(.agent-builder-setup-card--sidebar) {
1102
- max-inline-size: min(100%, var(--agent-kit-conversation-max-width));
1103
- margin-inline: auto;
1104
- padding-inline: 0 !important;
1109
+ .agent-sidebar-panel .agent-builder-setup-card--attached {
1110
+ inline-size: min(
1111
+ calc(100% - 0.5rem),
1112
+ var(--agent-kit-conversation-max-width)
1113
+ );
1114
+ }
1115
+
1116
+ @media (max-width: 40rem) {
1117
+ .agentkit-composer-stack .agent-builder-setup-card--attached {
1118
+ inline-size: min(
1119
+ calc(100% - 0.5rem),
1120
+ var(--agent-kit-conversation-max-width)
1121
+ );
1122
+ }
1105
1123
  }
1106
1124
 
1107
1125
  .agent-builder-setup-inline--attached {