@agent-native/core 0.72.2 → 0.72.4

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 (169) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/package.json +6 -6
  4. package/corpus/core/src/a2a/client.ts +7 -3
  5. package/corpus/core/src/client/AgentPanel.tsx +12 -1
  6. package/corpus/core/src/client/MultiTabAssistantChat.tsx +263 -13
  7. package/corpus/core/src/client/api-path.ts +28 -0
  8. package/corpus/core/src/client/embed-auth.ts +26 -0
  9. package/corpus/core/src/client/mcp-app-host.ts +6 -0
  10. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
  11. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  12. package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
  13. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +16 -4
  14. package/corpus/core/src/client/use-chat-threads.ts +86 -5
  15. package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
  16. package/corpus/core/src/embedding/agent.ts +6 -1
  17. package/corpus/core/src/embedding/bridge.ts +34 -1
  18. package/corpus/core/src/mcp/build-server.ts +38 -10
  19. package/corpus/core/src/mcp/embed-app.ts +120 -45
  20. package/corpus/core/src/mcp/index.ts +5 -0
  21. package/corpus/core/src/mcp/org-directory.ts +70 -11
  22. package/corpus/core/src/mcp/server.ts +14 -0
  23. package/corpus/core/src/mcp-client/config.ts +12 -0
  24. package/corpus/core/src/mcp-client/index.ts +2 -0
  25. package/corpus/core/src/mcp-client/manager.ts +268 -9
  26. package/corpus/core/src/mcp-client/remote-store.ts +154 -0
  27. package/corpus/core/src/mcp-client/routes.ts +2 -0
  28. package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
  29. package/corpus/core/src/server/index.ts +1 -0
  30. package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
  31. package/corpus/core/src/vite/client.ts +53 -0
  32. package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
  33. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
  34. package/corpus/templates/assets/AGENTS.md +7 -5
  35. package/corpus/templates/assets/actions/generate-asset.ts +161 -0
  36. package/corpus/templates/assets/actions/list-assets.ts +4 -2
  37. package/corpus/templates/assets/actions/navigate.ts +2 -1
  38. package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
  39. package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -2
  40. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +27 -4
  41. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +35 -5
  42. package/corpus/templates/assets/app/routes/_index.tsx +12 -0
  43. package/corpus/templates/assets/app/routes/chat.$threadId.tsx +1 -0
  44. package/corpus/templates/assets/app/routes/library.tsx +243 -40
  45. package/corpus/templates/chat/actions/navigate.ts +3 -0
  46. package/corpus/templates/chat/app/components/layout/Layout.tsx +7 -2
  47. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +27 -4
  48. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +38 -6
  49. package/corpus/templates/chat/app/routes/_index.tsx +13 -0
  50. package/corpus/templates/chat/app/routes/chat.$threadId.tsx +1 -0
  51. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
  52. package/corpus/templates/design/AGENTS.md +10 -5
  53. package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
  54. package/corpus/templates/design/actions/insert-asset.ts +219 -0
  55. package/corpus/templates/design/actions/view-screen.ts +3 -1
  56. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
  57. package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
  58. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
  59. package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
  60. package/corpus/templates/dispatch/app/routes/chat.$threadId.tsx +26 -0
  61. package/dist/a2a/client.d.ts +1 -0
  62. package/dist/a2a/client.d.ts.map +1 -1
  63. package/dist/a2a/client.js +5 -3
  64. package/dist/a2a/client.js.map +1 -1
  65. package/dist/client/AgentPanel.d.ts +6 -2
  66. package/dist/client/AgentPanel.d.ts.map +1 -1
  67. package/dist/client/AgentPanel.js +4 -4
  68. package/dist/client/AgentPanel.js.map +1 -1
  69. package/dist/client/MultiTabAssistantChat.d.ts +22 -1
  70. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  71. package/dist/client/MultiTabAssistantChat.js +215 -13
  72. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  73. package/dist/client/api-path.d.ts.map +1 -1
  74. package/dist/client/api-path.js +27 -0
  75. package/dist/client/api-path.js.map +1 -1
  76. package/dist/client/embed-auth.d.ts.map +1 -1
  77. package/dist/client/embed-auth.js +27 -0
  78. package/dist/client/embed-auth.js.map +1 -1
  79. package/dist/client/mcp-app-host.d.ts.map +1 -1
  80. package/dist/client/mcp-app-host.js +5 -0
  81. package/dist/client/mcp-app-host.js.map +1 -1
  82. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  83. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  84. package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
  85. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  86. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  87. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  88. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  89. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  90. package/dist/client/settings/DemoModeSection.js +3 -1
  91. package/dist/client/settings/DemoModeSection.js.map +1 -1
  92. package/dist/client/use-agent-chat-home-handoff.d.ts +7 -1
  93. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  94. package/dist/client/use-agent-chat-home-handoff.js +9 -5
  95. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  96. package/dist/client/use-chat-threads.d.ts +6 -0
  97. package/dist/client/use-chat-threads.d.ts.map +1 -1
  98. package/dist/client/use-chat-threads.js +79 -5
  99. package/dist/client/use-chat-threads.js.map +1 -1
  100. package/dist/demo/fetch-interceptor.js +1 -1
  101. package/dist/demo/fetch-interceptor.js.map +1 -1
  102. package/dist/embedding/agent.d.ts +1 -1
  103. package/dist/embedding/agent.d.ts.map +1 -1
  104. package/dist/embedding/agent.js +1 -1
  105. package/dist/embedding/agent.js.map +1 -1
  106. package/dist/embedding/bridge.d.ts.map +1 -1
  107. package/dist/embedding/bridge.js +27 -1
  108. package/dist/embedding/bridge.js.map +1 -1
  109. package/dist/extensions/schema.d.ts +2 -2
  110. package/dist/mcp/build-server.d.ts +2 -0
  111. package/dist/mcp/build-server.d.ts.map +1 -1
  112. package/dist/mcp/build-server.js +32 -9
  113. package/dist/mcp/build-server.js.map +1 -1
  114. package/dist/mcp/embed-app.d.ts.map +1 -1
  115. package/dist/mcp/embed-app.js +120 -45
  116. package/dist/mcp/embed-app.js.map +1 -1
  117. package/dist/mcp/index.d.ts +1 -0
  118. package/dist/mcp/index.d.ts.map +1 -1
  119. package/dist/mcp/index.js +1 -0
  120. package/dist/mcp/index.js.map +1 -1
  121. package/dist/mcp/org-directory.d.ts +1 -0
  122. package/dist/mcp/org-directory.d.ts.map +1 -1
  123. package/dist/mcp/org-directory.js +59 -11
  124. package/dist/mcp/org-directory.js.map +1 -1
  125. package/dist/mcp/server.d.ts.map +1 -1
  126. package/dist/mcp/server.js +9 -0
  127. package/dist/mcp/server.js.map +1 -1
  128. package/dist/mcp-client/config.d.ts +12 -0
  129. package/dist/mcp-client/config.d.ts.map +1 -1
  130. package/dist/mcp-client/config.js.map +1 -1
  131. package/dist/mcp-client/index.d.ts +1 -1
  132. package/dist/mcp-client/index.d.ts.map +1 -1
  133. package/dist/mcp-client/index.js +1 -1
  134. package/dist/mcp-client/index.js.map +1 -1
  135. package/dist/mcp-client/manager.d.ts +2 -0
  136. package/dist/mcp-client/manager.d.ts.map +1 -1
  137. package/dist/mcp-client/manager.js +201 -9
  138. package/dist/mcp-client/manager.js.map +1 -1
  139. package/dist/mcp-client/remote-store.d.ts +27 -0
  140. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  141. package/dist/mcp-client/remote-store.js +105 -0
  142. package/dist/mcp-client/remote-store.js.map +1 -1
  143. package/dist/mcp-client/routes.d.ts +1 -0
  144. package/dist/mcp-client/routes.d.ts.map +1 -1
  145. package/dist/mcp-client/routes.js +1 -0
  146. package/dist/mcp-client/routes.js.map +1 -1
  147. package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
  148. package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
  149. package/dist/provider-api/corpus-jobs.d.ts +8 -8
  150. package/dist/server/agent-chat-plugin.d.ts +2 -0
  151. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  152. package/dist/server/agent-chat-plugin.js +8 -0
  153. package/dist/server/agent-chat-plugin.js.map +1 -1
  154. package/dist/server/index.d.ts +1 -1
  155. package/dist/server/index.d.ts.map +1 -1
  156. package/dist/server/index.js +1 -1
  157. package/dist/server/index.js.map +1 -1
  158. package/dist/shared/mcp-embed-headers.d.ts +3 -1
  159. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  160. package/dist/shared/mcp-embed-headers.js +40 -3
  161. package/dist/shared/mcp-embed-headers.js.map +1 -1
  162. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  163. package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
  164. package/dist/sharing/actions/share-resource.d.ts +1 -1
  165. package/dist/sharing/schema.d.ts +1 -1
  166. package/dist/vite/client.d.ts.map +1 -1
  167. package/dist/vite/client.js +44 -0
  168. package/dist/vite/client.js.map +1 -1
  169. package/package.json +6 -6
@@ -954,6 +954,52 @@ const VITE_RUNTIME_PATH_PREFIXES = [
954
954
  "/src/",
955
955
  ];
956
956
 
957
+ const EMBED_DEV_STATIC_ASSET_PATH_PREFIXES = [
958
+ ...VITE_RUNTIME_PATH_PREFIXES,
959
+ "/assets/",
960
+ "/library-presets/",
961
+ ];
962
+
963
+ const EMBED_DEV_STATIC_ASSET_PATHS = new Set([
964
+ "/favicon.ico",
965
+ "/favicon.svg",
966
+ "/manifest.json",
967
+ ]);
968
+
969
+ function mountedPathCandidates(
970
+ reqUrl: string | undefined,
971
+ base: string | undefined,
972
+ ): string[] {
973
+ if (!reqUrl) return [];
974
+ let pathname: string;
975
+ try {
976
+ pathname = new URL(reqUrl, "http://agent-native.local").pathname;
977
+ } catch {
978
+ return [];
979
+ }
980
+ if (base && base !== "/") {
981
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
982
+ if (pathname.startsWith(normalizedBase)) {
983
+ return [pathname.slice(normalizedBase.length - 1) || "/"];
984
+ }
985
+ }
986
+ return [pathname];
987
+ }
988
+
989
+ function isEmbedDevStaticAssetRequest(
990
+ reqUrl: string | undefined,
991
+ base: string | undefined,
992
+ ): boolean {
993
+ return mountedPathCandidates(reqUrl, base).some((pathname) => {
994
+ if (EMBED_DEV_STATIC_ASSET_PATHS.has(pathname)) return true;
995
+ if (/^\/icon-[^/]+\.svg$/i.test(pathname)) return true;
996
+ if (/^\/agent-native-[^/]+\.svg$/i.test(pathname)) return true;
997
+ return EMBED_DEV_STATIC_ASSET_PATH_PREFIXES.some((prefix) =>
998
+ pathname.startsWith(prefix),
999
+ );
1000
+ });
1001
+ }
1002
+
957
1003
  function cookieValue(req: IncomingMessage, name: string): string | undefined {
958
1004
  const header = req.headers.cookie;
959
1005
  if (typeof header !== "string" || !header) return undefined;
@@ -1166,6 +1212,13 @@ function embedDevFrameHeaders(): Plugin {
1166
1212
  configureServer(server) {
1167
1213
  server.middlewares.use((req, res, next) => {
1168
1214
  const origin = String(req.headers.origin ?? "");
1215
+ if (isEmbedDevStaticAssetRequest(req.url, server.config?.base)) {
1216
+ for (const [name, value] of Object.entries(
1217
+ MCP_EMBED_STATIC_ASSET_HEADERS,
1218
+ )) {
1219
+ res.setHeader(name, value);
1220
+ }
1221
+ }
1169
1222
  if (isMcpEmbedCorsOrigin(origin)) {
1170
1223
  res.setHeader("Access-Control-Allow-Origin", origin);
1171
1224
  res.setHeader("Vary", "Origin");
@@ -1,20 +1,26 @@
1
1
  ---
2
2
  name: a2a-assets
3
- description: Call the Assets agent from other agent-native apps to generate, refine, export, and insert brand images or videos.
3
+ description: Use Assets from other apps or MCP hosts to generate, refine, export, and insert brand images or videos.
4
4
  ---
5
5
 
6
- # A2A Assets
6
+ # Assets MCP Tool
7
7
 
8
- Use A2A when another app needs brand imagery, video, or reusable source media and Assets owns the library.
8
+ Use the Assets MCP tool surface when another app or external host needs brand
9
+ imagery, video, or reusable source media and Assets owns the library. Prefer the
10
+ pure MCP `generate-asset` flow for human-in-the-loop generation because it can
11
+ return the inline picker. A2A remains useful for unattended cross-app
12
+ delegation, but A2A replies cannot render MCP App pickers.
9
13
 
10
14
  ## Caller Flow
11
15
 
12
- 1. Call `match-library` or `list-libraries` on the Assets agent when the library
13
- is ambiguous.
14
- 2. Call `generate-image-batch` with one slot per destination, such as one hero
15
- per slide. Always pass `source: "a2a"` and `callerAppId` with the calling
16
- app id (`slides`, `design`, `content`, `mail`) so the Assets audit log can
17
- group cross-agent generations.
16
+ 1. For human selection, call `generate-asset` with the brief, `callerAppId`, and
17
+ `libraryId` when known. It will match the library, generate candidates, and
18
+ return the Assets picker filtered to the new run IDs.
19
+ 2. For unattended A2A-style work, call `match-library` or `list-libraries` when
20
+ the library is ambiguous, then call `generate-image-batch` with one slot per
21
+ destination, such as one hero per slide. Pass `source: "a2a"` and
22
+ `callerAppId` with the calling app id (`slides`, `design`, `content`,
23
+ `mail`) so the Assets audit log can group cross-agent generations.
18
24
  3. Treat image batches as complete when the action returns. Use returned
19
25
  successful `images` entries directly; only regenerate slots that returned
20
26
  `ok: false`.
@@ -25,7 +31,8 @@ Use A2A when another app needs brand imagery, video, or reusable source media an
25
31
  6. For video, call `generate-video` and then `refresh-generation-run` until the run completes.
26
32
  7. Preserve returned `assetId`, `runId`, `previewUrl`, `downloadUrl`, and
27
33
  `embedPath` exactly.
28
- 8. Insert exported URLs into the caller's artifact.
34
+ 8. Insert chosen/exported URLs into the caller's artifact. Design callers should
35
+ call `insert-asset` after the picker returns a selected asset.
29
36
  9. On feedback, call `refine-image` with the prior `assetId`, `source: "a2a"`,
30
37
  and the same `callerAppId`, then replace only the affected destination.
31
38
 
@@ -16,9 +16,14 @@ or generated image/video assets that another app can reference by ID and URL.
16
16
 
17
17
  ## Choose The Path
18
18
 
19
- - Use `open-asset-picker` when a person should browse, search, generate, and
20
- select an asset in UI. Pass `mediaType: "image"` by default, or
21
- `mediaType: "video"` for video libraries.
19
+ - Use `generate-asset` when a person should get newly generated, on-brand image
20
+ candidates and choose the winner in the inline picker. It matches a library
21
+ when `libraryId` is omitted, generates candidates, returns the picker filtered
22
+ to those run IDs, and works in in-app chat plus external MCP hosts.
23
+ - Use `open-asset-picker` when a person should browse, search, or select an
24
+ existing asset in UI, or when you want the picker to handle generation itself.
25
+ Pass `mediaType: "image"` by default, or `mediaType: "video"` for video
26
+ libraries.
22
27
  - Use unattended actions when the agent already knows what to do:
23
28
  `search-assets`, `list-assets`, `generate-image`, `generate-image-batch`,
24
29
  `generate-video`, `refresh-generation-run`, and `export-asset`.
@@ -41,7 +46,9 @@ or generated image/video assets that another app can reference by ID and URL.
41
46
 
42
47
  ## Image Workflows
43
48
 
44
- 1. Pick or match the library with `list-libraries` or `match-library`.
49
+ 1. For human-in-the-loop generation, call `generate-asset` first and preserve
50
+ the returned picker/candidate metadata. For unattended generation, pick or
51
+ match the library with `list-libraries` or `match-library`.
45
52
  If the user wants a default look rather than a brand library, call
46
53
  `list-library-presets` and then `create-library-from-preset`; the resulting
47
54
  library is editable and reusable like any other library.
@@ -85,8 +92,10 @@ deferred.
85
92
  Do not put shared secrets in skill files.
86
93
  - Local customization: run `npx @agent-native/core@latest app-skill launch --local` from the
87
94
  Assets app-skill manifest, or pass `--into <path>` for editable source.
88
- - For A2A or MCP callers, include exact `assetId`, `runId`, media type, and
89
- URLs in the final response so the caller can attach or embed the media.
95
+ - For MCP callers, `generate-asset` is the portable first choice because the
96
+ same MCP App picker renders inline in Agent-Native chat, ChatGPT, and Claude
97
+ when the host supports MCP Apps. Include exact `assetId`, `runId`, media type,
98
+ and URLs in the final response so the caller can attach or embed the media.
90
99
  Include `presetId` and `sessionId` when present.
91
100
 
92
101
  ## Don't
@@ -1,8 +1,9 @@
1
1
  # Assets — Agent Guide
2
2
 
3
3
  Assets is an agent-native asset library and generation workspace. The agent
4
- manages libraries, images, generated assets, inline embeds, notifications,
5
- collaboration, and A2A asset requests through actions and SQL state.
4
+ manages libraries, images, generated assets, inline MCP App pickers,
5
+ notifications, collaboration, and portable asset requests through actions and
6
+ SQL state.
6
7
 
7
8
  Detailed library, generation, image, embed, and engine rules live in
8
9
  `.agents/skills/`.
@@ -61,9 +62,10 @@ Read the relevant skill before deeper work:
61
62
  plain image-generation requests. Keep `npx @agent-native/core@latest connect` running until
62
63
  browser authorization finishes, restart the client if tools are not visible,
63
64
  and redact any MCP auth headers or tokens when debugging local config.
64
- - For human-in-the-loop image creation, call `open-asset-picker` with `prompt`,
65
- `autoGenerate: true`, and `count: 3` so the picker opens with candidates to
66
- preview, tweak by preset/aspect/count, and choose.
65
+ - For human-in-the-loop image creation, prefer `generate-asset` so Assets
66
+ matches the library, generates candidates, and returns the inline picker
67
+ filtered to those candidates. Use `open-asset-picker` when the user only needs
68
+ to browse/search/pick or when you want the picker to handle generation itself.
67
69
  - If the picker opens as a browser fallback instead of inline, selecting an
68
70
  asset copies a handoff summary; ask the caller to paste it back into chat.
69
71
  - Treat Codex, Claude Code, and Claude Desktop Code as link-out hosts for MCP
@@ -0,0 +1,161 @@
1
+ import { defineAction, embedApp } from "@agent-native/core";
2
+ import { z } from "zod";
3
+ import openAssetPicker from "./open-asset-picker.js";
4
+ import {
5
+ ASPECT_RATIOS,
6
+ IMAGE_QUALITY_TIERS,
7
+ STYLE_STRENGTHS,
8
+ } from "../shared/api.js";
9
+
10
+ const booleanParam = z.preprocess((value) => {
11
+ if (typeof value === "boolean") return value;
12
+ if (typeof value !== "string") return value;
13
+ const normalized = value.trim().toLowerCase();
14
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
15
+ if (["", "0", "false", "no", "off"].includes(normalized)) return false;
16
+ return value;
17
+ }, z.boolean());
18
+
19
+ const schema = z.object({
20
+ mediaType: z.enum(["image", "video"]).default("image"),
21
+ prompt: z
22
+ .string()
23
+ .min(1)
24
+ .describe("The asset brief to generate or prepare in the picker."),
25
+ libraryId: z
26
+ .string()
27
+ .optional()
28
+ .describe("Known Assets library ID. Omit to match the best library."),
29
+ libraryHint: z
30
+ .string()
31
+ .optional()
32
+ .describe(
33
+ "Brand, campaign, or use-case hint used when matching a library.",
34
+ ),
35
+ aspectRatio: z.enum(ASPECT_RATIOS).default("16:9"),
36
+ presetId: z
37
+ .string()
38
+ .optional()
39
+ .describe("Optional generation preset ID from the selected library."),
40
+ count: z.coerce
41
+ .number()
42
+ .int()
43
+ .min(1)
44
+ .max(6)
45
+ .default(3)
46
+ .describe("Number of image candidates to generate in the picker."),
47
+ tier: z.enum(IMAGE_QUALITY_TIERS).optional(),
48
+ styleStrength: z.enum(STYLE_STRENGTHS).default("balanced"),
49
+ includeLogo: booleanParam.default(false),
50
+ callerAppId: z
51
+ .string()
52
+ .optional()
53
+ .describe("Calling app id, for audit grouping, e.g. design."),
54
+ });
55
+
56
+ type ActionWithToolParameters = {
57
+ tool: { parameters?: { properties?: Record<string, any> } };
58
+ };
59
+
60
+ function allowStringifiedCountInToolSchema<T extends ActionWithToolParameters>(
61
+ action: T,
62
+ ): T {
63
+ const parameters = action.tool.parameters;
64
+ const count = parameters?.properties?.count;
65
+ if (count && typeof count === "object" && !Array.isArray(count)) {
66
+ parameters!.properties!.count = {
67
+ description: count.description,
68
+ default: count.default,
69
+ anyOf: [
70
+ count,
71
+ {
72
+ type: "string",
73
+ pattern: "^[1-6]$",
74
+ description: count.description,
75
+ },
76
+ ],
77
+ };
78
+ }
79
+ return action;
80
+ }
81
+
82
+ const action = defineAction({
83
+ description:
84
+ "Open the real Assets picker and start on-brand image generation there so the tool returns immediately and the user can choose the final image/video. Use this for human-in-the-loop brand media requests from Design, Slides, ChatGPT, Claude, or Codex. Preserve returned asset IDs and URLs exactly after the user picks.",
85
+ schema,
86
+ publicAgent: {
87
+ expose: true,
88
+ readOnly: false,
89
+ requiresAuth: true,
90
+ title: "Generate an on-brand asset",
91
+ description:
92
+ "Start brand-consistent image generation in the Assets picker.",
93
+ },
94
+ mcpApp: {
95
+ compactCatalog: true,
96
+ resource: embedApp({
97
+ title: "Generate Assets",
98
+ description:
99
+ "Generate image candidates and choose the final asset in the real Assets Library picker.",
100
+ iframeTitle: "Agent-Native Assets",
101
+ openLabel: "Open Assets picker",
102
+ height: 760,
103
+ connectDomains: ["https://cdn.builder.io"],
104
+ resourceDomains: ["https://cdn.builder.io"],
105
+ }),
106
+ },
107
+ link: ({ result }) => {
108
+ const url =
109
+ result && typeof result === "object"
110
+ ? (result as { url?: unknown }).url
111
+ : null;
112
+ return {
113
+ url: typeof url === "string" && url ? url : "/library",
114
+ label: "Open Assets picker",
115
+ view: "picker",
116
+ };
117
+ },
118
+ run: async (args) => {
119
+ if (args.mediaType === "video") {
120
+ const picker = (await openAssetPicker.run({
121
+ mediaType: "video",
122
+ prompt: args.prompt,
123
+ libraryId: args.libraryId,
124
+ libraryHint: args.libraryHint,
125
+ count: args.count,
126
+ callerAppId: args.callerAppId,
127
+ })) as Record<string, unknown>;
128
+ return {
129
+ ...picker,
130
+ generated: false,
131
+ message:
132
+ "Assets video picker is ready. Generate or select the video in the picker, then choose the asset to send it back.",
133
+ };
134
+ }
135
+
136
+ const picker = (await openAssetPicker.run({
137
+ mediaType: "image",
138
+ prompt: args.prompt,
139
+ libraryId: args.libraryId,
140
+ libraryHint: args.libraryHint,
141
+ aspectRatio: args.aspectRatio,
142
+ presetId: args.presetId,
143
+ count: args.count,
144
+ autoGenerate: true,
145
+ tier: args.tier,
146
+ styleStrength: args.styleStrength,
147
+ includeLogo: args.includeLogo,
148
+ callerAppId: args.callerAppId,
149
+ })) as Record<string, unknown>;
150
+ return {
151
+ ...picker,
152
+ generated: false,
153
+ generationStarted: false,
154
+ generationMode: "picker-auto-generate",
155
+ message:
156
+ "Assets picker is ready and will start image generation inside the picker. If this org has no libraries yet, the picker will create a starter library first. Ask the user to pick one candidate; preserve the chosen assetId, runId, and URLs exactly.",
157
+ };
158
+ },
159
+ });
160
+
161
+ export default allowStringifiedCountInToolSchema(action);
@@ -95,13 +95,15 @@ export default defineAction({
95
95
  .filter((asset) =>
96
96
  shouldIncludeAssetInLibraryResults(
97
97
  asset,
98
- includeCandidates || status === "candidate",
98
+ includeCandidates ||
99
+ status === "candidate" ||
100
+ candidateRunIdSet.size > 0,
99
101
  ),
100
102
  )
101
103
  .filter((asset) => {
102
104
  if (!candidateRunIdSet.size) return true;
103
105
  if (!(asset.role === "generated" && asset.status === "candidate")) {
104
- return true;
106
+ return false;
105
107
  }
106
108
  return Boolean(
107
109
  asset.generationRunId && candidateRunIdSet.has(asset.generationRunId),
@@ -4,7 +4,7 @@ import { z } from "zod";
4
4
 
5
5
  export default defineAction({
6
6
  description:
7
- "Navigate the Assets UI. Views (internal keys, with the surface they open): create, picker (the image Library browser), libraries (the Brand Kits list), library (a single Brand Kit), asset, generation-session, generation-run, extensions, audit, settings. Use libraryId, assetId, sessionId, runId, or extensionId where appropriate.",
7
+ "Navigate the Assets UI. Views (internal keys, with the surface they open): create, picker (the image Library browser), libraries (the Brand Kits list), library (a single Brand Kit), asset, generation-session, generation-run, extensions, audit, settings. Use threadId to open a specific create/chat thread; use libraryId, assetId, sessionId, runId, or extensionId where appropriate.",
8
8
  schema: z.object({
9
9
  view: z
10
10
  .enum([
@@ -25,6 +25,7 @@ export default defineAction({
25
25
  assetId: z.string().optional(),
26
26
  sessionId: z.string().optional(),
27
27
  runId: z.string().optional(),
28
+ threadId: z.string().optional(),
28
29
  presetId: z.string().optional(),
29
30
  mediaType: z.enum(["image", "video"]).optional(),
30
31
  query: z.string().optional(),
@@ -1,7 +1,16 @@
1
1
  import { defineAction, embedApp } from "@agent-native/core";
2
2
  import { z } from "zod";
3
+ import { IMAGE_QUALITY_TIERS, STYLE_STRENGTHS } from "../shared/api.js";
3
4
 
4
5
  const mediaTypeSchema = z.enum(["image", "video"]);
6
+ const booleanParam = z.preprocess((value) => {
7
+ if (typeof value === "boolean") return value;
8
+ if (typeof value !== "string") return value;
9
+ const normalized = value.trim().toLowerCase();
10
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
11
+ if (["", "0", "false", "no", "off"].includes(normalized)) return false;
12
+ return value;
13
+ }, z.boolean());
5
14
 
6
15
  const schema = z.object({
7
16
  mediaType: mediaTypeSchema.default("image"),
@@ -17,6 +26,12 @@ const schema = z.object({
17
26
  .string()
18
27
  .optional()
19
28
  .describe("Optional asset library to open in the picker."),
29
+ libraryHint: z
30
+ .string()
31
+ .optional()
32
+ .describe(
33
+ "Brand, campaign, or use-case hint used to preselect the best-matching library when libraryId is omitted.",
34
+ ),
20
35
  aspectRatio: z
21
36
  .string()
22
37
  .optional()
@@ -32,16 +47,62 @@ const schema = z.object({
32
47
  .max(6)
33
48
  .default(3)
34
49
  .describe("Number of image candidates to generate in the picker."),
35
- autoGenerate: z.coerce
36
- .boolean()
50
+ candidateRunIds: z
51
+ .array(z.string())
52
+ .optional()
53
+ .describe(
54
+ "Generation run IDs to show as the candidate set when returning from a tool-generated batch.",
55
+ ),
56
+ autoGenerate: booleanParam
37
57
  .default(false)
38
58
  .describe(
39
59
  "When true and prompt is provided, generate candidates as soon as the picker opens.",
40
60
  ),
61
+ tier: z
62
+ .enum(IMAGE_QUALITY_TIERS)
63
+ .optional()
64
+ .describe("Optional image quality tier to use for auto-generation."),
65
+ styleStrength: z
66
+ .enum(STYLE_STRENGTHS)
67
+ .default("balanced")
68
+ .describe("How strongly to follow the library style during generation."),
69
+ includeLogo: booleanParam
70
+ .default(false)
71
+ .describe("Whether auto-generation should include the library logo."),
72
+ callerAppId: z
73
+ .string()
74
+ .optional()
75
+ .describe("Calling app id, for audit grouping, e.g. design."),
41
76
  });
42
77
 
43
78
  type OpenAssetPickerArgs = z.infer<typeof schema>;
44
79
 
80
+ type ActionWithToolParameters = {
81
+ tool: { parameters?: { properties?: Record<string, any> } };
82
+ };
83
+
84
+ function allowStringifiedCountInToolSchema<T extends ActionWithToolParameters>(
85
+ action: T,
86
+ ): T {
87
+ const parameters = action.tool.parameters;
88
+ const count = parameters?.properties?.count;
89
+ if (count && typeof count === "object" && !Array.isArray(count)) {
90
+ parameters!.properties!.count = {
91
+ description: count.description,
92
+ default: count.default,
93
+ anyOf: [
94
+ count,
95
+ {
96
+ type: "string",
97
+ pattern: "^[1-6]$",
98
+ description: count.description,
99
+ },
100
+ ],
101
+ };
102
+ }
103
+ return action;
104
+ }
105
+
45
106
  const FALLBACK_INSTRUCTIONS =
46
107
  'If the picker opens in a normal browser tab instead of inline, the user has two ways to choose: (1) click an asset — the picker auto-copies a short handoff summary they paste back into chat, or (2) just tell you which one in words (e.g. "use the second image"). Either way, continue with the chosen asset. In Codex, Claude Code, and other code-editor chats, do not expect MCP Apps to render inline; provide the asset link, and if the final answer needs an inline image preview, download the selected image URL locally and embed the absolute local file path because remote CDN markdown previews may not render there.';
47
108
 
@@ -51,16 +112,28 @@ function pickerPath(args: Partial<OpenAssetPickerArgs>): string {
51
112
  if (args.prompt?.trim()) params.set("prompt", args.prompt.trim());
52
113
  if (args.query?.trim()) params.set("q", args.query.trim());
53
114
  if (args.libraryId?.trim()) params.set("libraryId", args.libraryId.trim());
115
+ if (args.libraryHint?.trim()) {
116
+ params.set("libraryHint", args.libraryHint.trim());
117
+ }
54
118
  if (args.aspectRatio?.trim()) {
55
119
  params.set("aspectRatio", args.aspectRatio.trim());
56
120
  }
57
121
  if (args.presetId?.trim()) params.set("presetId", args.presetId.trim());
58
122
  if (args.count && args.count !== 3) params.set("count", String(args.count));
123
+ if (args.tier) params.set("tier", args.tier);
124
+ if (args.styleStrength && args.styleStrength !== "balanced") {
125
+ params.set("styleStrength", args.styleStrength);
126
+ }
127
+ if (args.includeLogo) params.set("includeLogo", "1");
128
+ if (args.callerAppId?.trim()) params.set("callerAppId", args.callerAppId);
129
+ for (const runId of args.candidateRunIds ?? []) {
130
+ if (runId.trim()) params.append("candidateRunIds", runId.trim());
131
+ }
59
132
  if (args.autoGenerate) params.set("autoGenerate", "1");
60
133
  return `/library?${params.toString()}`;
61
134
  }
62
135
 
63
- export default defineAction({
136
+ const action = defineAction({
64
137
  description:
65
138
  'Open the image Library picker inline so a person can browse, search, generate, and select an image or video asset. When the user asks to create a specific image and choose the best one, pass prompt, autoGenerate: true, and count: 3 so the Library opens with generated candidates. If the host can only open a browser link (e.g. a CLI or code editor), surface that link: after the user picks, the page auto-copies a short paste-back summary — or the user can simply tell you which candidate they want (e.g. "use image A"). Use search-assets, generate-image, generate-video, and export-asset for unattended flows.',
66
139
  schema,
@@ -122,10 +195,18 @@ export default defineAction({
122
195
  query: args.query ?? null,
123
196
  prompt: args.prompt ?? null,
124
197
  libraryId: args.libraryId ?? null,
198
+ libraryHint: args.libraryHint ?? null,
125
199
  aspectRatio: args.aspectRatio ?? null,
126
200
  presetId: args.presetId ?? null,
127
201
  count: args.count,
202
+ tier: args.tier ?? null,
203
+ styleStrength: args.styleStrength,
204
+ includeLogo: args.includeLogo,
205
+ callerAppId: args.callerAppId ?? null,
206
+ candidateRunIds: args.candidateRunIds ?? [],
128
207
  autoGenerate: args.autoGenerate,
129
208
  };
130
209
  },
131
210
  });
211
+
212
+ export default allowStringifiedCountInToolSchema(action);
@@ -36,7 +36,8 @@ export function Layout({ children }: LayoutProps) {
36
36
  const location = useLocation();
37
37
  const navigate = useNavigate();
38
38
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
39
- const isCreateRoute = location.pathname === "/";
39
+ const isCreateRoute =
40
+ location.pathname === "/" || location.pathname.startsWith("/chat/");
40
41
  const chatHomeHandoffActive = useAgentChatHomeHandoff({
41
42
  storageKey: ASSETS_CHAT_STORAGE_KEY,
42
43
  activePath: location.pathname,
@@ -44,7 +45,7 @@ export function Layout({ children }: LayoutProps) {
44
45
  });
45
46
  useAgentChatHomeHandoffLinks({
46
47
  storageKey: ASSETS_CHAT_STORAGE_KEY,
47
- chatPath: "/",
48
+ isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
48
49
  });
49
50
 
50
51
  useEffect(() => {
@@ -100,8 +100,24 @@ function persistedActiveThreadId() {
100
100
  }
101
101
  }
102
102
 
103
+ function threadIdFromPath(pathname: string) {
104
+ const match = pathname.match(/^\/chat\/([^/]+)/);
105
+ if (!match) return null;
106
+ try {
107
+ const value = decodeURIComponent(match[1]).trim();
108
+ return value || null;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+
114
+ function chatThreadPath(threadId: string) {
115
+ return `/chat/${encodeURIComponent(threadId)}`;
116
+ }
117
+
103
118
  function AssetsChatsSection() {
104
119
  const navigate = useNavigate();
120
+ const location = useLocation();
105
121
  const {
106
122
  threads,
107
123
  activeThreadId,
@@ -159,7 +175,10 @@ function AssetsChatsSection() {
159
175
 
160
176
  function openThread(threadId: string, options?: { isNew?: boolean }) {
161
177
  switchThread(threadId);
162
- navigateWithAgentChatViewTransition(navigate, "/");
178
+ navigateWithAgentChatViewTransition(
179
+ navigate,
180
+ options?.isNew ? "/" : chatThreadPath(threadId),
181
+ );
163
182
  window.requestAnimationFrame(() => {
164
183
  window.dispatchEvent(
165
184
  new CustomEvent("agent-chat:open-thread", {
@@ -261,7 +280,10 @@ function AssetsChatsSection() {
261
280
  </div>
262
281
  <div className="grid gap-0.5">
263
282
  {visibleThreads.map((thread) => {
264
- const isActive = thread.id === activeThreadId;
283
+ const isActive =
284
+ thread.id ===
285
+ (threadIdFromPath(location.pathname) ??
286
+ (location.pathname === "/" ? null : activeThreadId));
265
287
  const isRenaming = thread.id === renamingThreadId;
266
288
  return (
267
289
  <div
@@ -367,7 +389,8 @@ function AssetsChatsSection() {
367
389
  export function Sidebar() {
368
390
  const location = useLocation();
369
391
  const navigate = useNavigate();
370
- const isCreateRoute = location.pathname === "/";
392
+ const isCreateRoute =
393
+ location.pathname === "/" || location.pathname.startsWith("/chat/");
371
394
  const { data: auditAdmin } = useActionQuery("is-audit-admin", {}, {
372
395
  refetchInterval: 30_000,
373
396
  } as any) as { data: { allowed?: boolean } | undefined };
@@ -453,7 +476,7 @@ export function Sidebar() {
453
476
  const Icon = item.icon;
454
477
  const isActive =
455
478
  item.href === "/"
456
- ? location.pathname === "/"
479
+ ? isCreateRoute
457
480
  : item.href === "/brand-kits"
458
481
  ? location.pathname === "/brand-kits" ||
459
482
  location.pathname.startsWith("/brand-kits/") ||