@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
@@ -22,11 +22,18 @@ function optionalLibraryTab(params: URLSearchParams) {
22
22
  }
23
23
 
24
24
  function navigationFromPath(pathname: string, search = "") {
25
+ const params = new URLSearchParams(search);
26
+ const chat = pathname.match(/^\/chat\/([^/]+)/);
27
+ if (chat) {
28
+ return {
29
+ view: "create",
30
+ threadId: decodePathParam(chat[1]),
31
+ };
32
+ }
25
33
  // The "library" view is the brand-kit detail page (route /brand-kits/:id).
26
34
  // Keep the internal view key stable for the agent/MCP contract.
27
35
  const library = pathname.match(/^\/brand-kits\/([^/]+)/);
28
36
  if (library) {
29
- const params = new URLSearchParams(search);
30
37
  return {
31
38
  view: "library",
32
39
  libraryId: library[1],
@@ -37,10 +44,13 @@ function navigationFromPath(pathname: string, search = "") {
37
44
  if (asset) return { view: "asset", assetId: asset[1] };
38
45
  const image = pathname.match(/^\/image\/([^/]+)/);
39
46
  if (image) return { view: "asset", assetId: image[1] };
40
- if (pathname === "/") return { view: "create" };
47
+ if (pathname === "/") {
48
+ return {
49
+ view: "create",
50
+ };
51
+ }
41
52
  // The "picker" view is the image Library browser (route /library).
42
53
  if (pathname === "/library") {
43
- const params = new URLSearchParams(search);
44
54
  return {
45
55
  view: "picker",
46
56
  mediaType:
@@ -93,7 +103,12 @@ function pathFromCommand(command: any): string | null {
93
103
  }
94
104
  if (command.view === "audit") return "/audit";
95
105
  if (command.view === "settings") return "/settings";
96
- if (command.view === "create") return "/";
106
+ if (command.view === "create") {
107
+ if (typeof command.threadId === "string" && command.threadId.trim()) {
108
+ return `/chat/${encodeURIComponent(command.threadId.trim())}`;
109
+ }
110
+ return "/";
111
+ }
97
112
  if (command.view === "picker") {
98
113
  const params = new URLSearchParams();
99
114
  if (command.mediaType === "image" || command.mediaType === "video") {
@@ -131,7 +146,10 @@ export function useNavigationState() {
131
146
  navigationFromPath(pathname, search),
132
147
  getCommandPath: (command) => pathFromCommand(command),
133
148
  onNavigate: (_command, path) => {
134
- if (location.pathname === "/" && pathnameFromPath(path) !== "/") {
149
+ if (
150
+ isCreatePath(location.pathname) &&
151
+ !isCreatePath(pathnameFromPath(path))
152
+ ) {
135
153
  markAgentChatHomeHandoff(ASSETS_CHAT_STORAGE_KEY);
136
154
  }
137
155
  },
@@ -141,3 +159,15 @@ export function useNavigationState() {
141
159
  function pathnameFromPath(path: string): string {
142
160
  return path.split(/[?#]/, 1)[0] || "/";
143
161
  }
162
+
163
+ function decodePathParam(value: string): string {
164
+ try {
165
+ return decodeURIComponent(value);
166
+ } catch {
167
+ return value;
168
+ }
169
+ }
170
+
171
+ function isCreatePath(pathname: string): boolean {
172
+ return pathname === "/" || pathname.startsWith("/chat/");
173
+ }
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
+ import { useNavigate, useParams } from "react-router";
2
3
  import {
3
4
  AgentChatSurface,
4
5
  getBrowserTabId,
@@ -61,7 +62,13 @@ export function meta() {
61
62
  ];
62
63
  }
63
64
 
65
+ function chatThreadPath(threadId: string | null) {
66
+ return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/";
67
+ }
68
+
64
69
  export default function CreatePage() {
70
+ const { threadId } = useParams();
71
+ const navigate = useNavigate();
65
72
  const [imageModel, setImageModel] = useState<string>(DEFAULT_IMAGE_MODEL);
66
73
 
67
74
  useEffect(() => {
@@ -113,6 +120,11 @@ export default function CreatePage() {
113
120
  className="assets-create-chat-panel"
114
121
  defaultMode="chat"
115
122
  storageKey={ASSETS_CHAT_STORAGE_KEY}
123
+ threadUrlSync={{
124
+ routeThreadId: threadId ?? null,
125
+ getPath: chatThreadPath,
126
+ navigate,
127
+ }}
116
128
  browserTabId={getBrowserTabId()}
117
129
  imageModelMenu={{
118
130
  value: imageModel,
@@ -0,0 +1 @@
1
+ export { default, meta } from "./_index";
@@ -9,6 +9,7 @@ import {
9
9
  appPath,
10
10
  getBrowserTabId,
11
11
  getEmbedAuthToken,
12
+ isEmbedMcpChatBridgeActive,
12
13
  isEmbedAuthActive,
13
14
  sendMcpAppHostMessage,
14
15
  updateMcpAppModelContext,
@@ -53,6 +54,7 @@ import {
53
54
  DialogTitle,
54
55
  } from "@/components/ui/dialog";
55
56
  import { DEFAULT_LIBRARY_PRESETS } from "../../shared/library-presets";
57
+ import type { ImageQualityTier, StyleStrength } from "../../shared/api";
56
58
 
57
59
  type AssetTab = "all" | "generated" | "references";
58
60
 
@@ -127,12 +129,44 @@ type HostConfig = {
127
129
  prompt?: string;
128
130
  query?: string;
129
131
  libraryId?: string;
132
+ libraryHint?: string;
130
133
  aspectRatio?: string;
131
134
  presetId?: string;
132
135
  count?: number;
136
+ tier?: ImageQualityTier;
137
+ styleStrength?: StyleStrength;
138
+ includeLogo?: boolean;
139
+ callerAppId?: string;
133
140
  autoGenerate?: boolean;
141
+ candidateRunIds?: string[];
134
142
  };
135
143
 
144
+ // Preselect the library whose title/description best matches a free-text brand
145
+ // or use-case hint. Falls back to no match (caller uses the first library).
146
+ function matchLibraryByHint(
147
+ libraries: Library[],
148
+ hint: string | undefined,
149
+ ): string | undefined {
150
+ const needle = hint?.trim().toLowerCase();
151
+ if (!needle) return undefined;
152
+ const terms = needle.split(/\s+/).filter(Boolean);
153
+ let best: { id: string; score: number } | null = null;
154
+ for (const library of libraries) {
155
+ const haystack =
156
+ `${library.title ?? ""} ${library.description ?? ""}`.toLowerCase();
157
+ if (!haystack.trim()) continue;
158
+ let score = 0;
159
+ if (haystack.includes(needle)) score += 10;
160
+ for (const term of terms) {
161
+ if (haystack.includes(term)) score += 1;
162
+ }
163
+ if (score > 0 && (!best || score > best.score)) {
164
+ best = { id: library.id, score };
165
+ }
166
+ }
167
+ return best?.id;
168
+ }
169
+
136
170
  function isEmbeddedWindow() {
137
171
  if (typeof window === "undefined") return false;
138
172
  try {
@@ -154,6 +188,40 @@ function normalizeMediaType(value: unknown): PickerMediaType {
154
188
  return value === "video" ? "video" : "image";
155
189
  }
156
190
 
191
+ function normalizeBoolean(value: unknown): boolean | undefined {
192
+ if (typeof value === "boolean") return value;
193
+ if (typeof value !== "string") return undefined;
194
+ const normalized = value.trim().toLowerCase();
195
+ if (["1", "true", "yes", "on"].includes(normalized)) return true;
196
+ if (["", "0", "false", "no", "off"].includes(normalized)) return false;
197
+ return undefined;
198
+ }
199
+
200
+ function normalizeTier(value: unknown): ImageQualityTier | undefined {
201
+ return value === "auto" || value === "fast" || value === "best"
202
+ ? value
203
+ : undefined;
204
+ }
205
+
206
+ function normalizeStyleStrength(value: unknown): StyleStrength | undefined {
207
+ return value === "subtle" || value === "balanced" || value === "strong"
208
+ ? value
209
+ : undefined;
210
+ }
211
+
212
+ function normalizeCandidateRunIds(value: unknown): string[] | undefined {
213
+ if (value === null || value === undefined) return undefined;
214
+ const raw = Array.isArray(value)
215
+ ? value
216
+ : typeof value === "string"
217
+ ? value.split(",")
218
+ : [];
219
+ const ids = raw
220
+ .map((item) => (typeof item === "string" ? item.trim() : ""))
221
+ .filter(Boolean);
222
+ return ids;
223
+ }
224
+
157
225
  function normalizeHostConfig(value: unknown): HostConfig {
158
226
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
159
227
  const record = value as Record<string, unknown>;
@@ -166,17 +234,22 @@ function normalizeHostConfig(value: unknown): HostConfig {
166
234
  query: typeof record.query === "string" ? record.query : undefined,
167
235
  libraryId:
168
236
  typeof record.libraryId === "string" ? record.libraryId : undefined,
237
+ libraryHint:
238
+ typeof record.libraryHint === "string" ? record.libraryHint : undefined,
169
239
  aspectRatio:
170
240
  typeof record.aspectRatio === "string" ? record.aspectRatio : undefined,
171
241
  presetId: typeof record.presetId === "string" ? record.presetId : undefined,
172
242
  count:
173
243
  record.count === undefined ? undefined : normalizeCount(record.count),
244
+ tier: normalizeTier(record.tier),
245
+ styleStrength: normalizeStyleStrength(record.styleStrength),
246
+ includeLogo: normalizeBoolean(record.includeLogo),
247
+ callerAppId:
248
+ typeof record.callerAppId === "string" ? record.callerAppId : undefined,
249
+ candidateRunIds: normalizeCandidateRunIds(record.candidateRunIds),
174
250
  };
175
251
  if (Object.prototype.hasOwnProperty.call(record, "autoGenerate")) {
176
- config.autoGenerate =
177
- record.autoGenerate === true ||
178
- record.autoGenerate === "true" ||
179
- record.autoGenerate === "1";
252
+ config.autoGenerate = normalizeBoolean(record.autoGenerate) ?? false;
180
253
  }
181
254
  return config;
182
255
  }
@@ -440,32 +513,53 @@ function blobToBase64(blob: Blob): Promise<string> {
440
513
  });
441
514
  }
442
515
 
516
+ const MCP_IMAGE_CONTENT_MAX_BYTES = 4 * 1024 * 1024;
517
+
518
+ function base64ByteLength(data: string) {
519
+ const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
520
+ return Math.floor((data.length * 3) / 4) - padding;
521
+ }
522
+
523
+ async function fetchImageContent(url: string, fallbackMimeType: string) {
524
+ const credentials =
525
+ typeof window !== "undefined" &&
526
+ new URL(url, window.location.href).origin === window.location.origin
527
+ ? "same-origin"
528
+ : "omit";
529
+ const response = await fetch(url, { credentials });
530
+ if (!response.ok) return null;
531
+ const blob = await response.blob();
532
+ const detectedMimeType = blob.type.startsWith("image/")
533
+ ? blob.type
534
+ : fallbackMimeType;
535
+ return {
536
+ type: "image",
537
+ data: await blobToBase64(blob),
538
+ mimeType: detectedMimeType,
539
+ };
540
+ }
541
+
443
542
  async function imageContentForAsset(payload: ReturnType<typeof assetPayload>) {
444
543
  const url = payload.url ?? payload.downloadUrl ?? payload.previewUrl;
445
544
  const mimeType = payload.mimeType?.startsWith("image/")
446
545
  ? payload.mimeType
447
546
  : "image/png";
448
547
  if (!url || payload.mediaType !== "image") return null;
449
- try {
450
- const credentials =
451
- typeof window !== "undefined" &&
452
- new URL(url, window.location.href).origin === window.location.origin
453
- ? "same-origin"
454
- : "omit";
455
- const response = await fetch(url, { credentials });
456
- if (!response.ok) return null;
457
- const blob = await response.blob();
458
- const detectedMimeType = blob.type.startsWith("image/")
459
- ? blob.type
460
- : mimeType;
461
- return {
462
- type: "image",
463
- data: await blobToBase64(blob),
464
- mimeType: detectedMimeType,
465
- };
466
- } catch {
467
- return null;
548
+ const sources = uniqueSources([url, payload.thumbnailUrl]);
549
+ for (const source of sources) {
550
+ try {
551
+ const content = await fetchImageContent(source, mimeType);
552
+ if (
553
+ content &&
554
+ base64ByteLength(content.data) <= MCP_IMAGE_CONTENT_MAX_BYTES
555
+ ) {
556
+ return content;
557
+ }
558
+ } catch {
559
+ // Try the next smaller source.
560
+ }
468
561
  }
562
+ return null;
469
563
  }
470
564
 
471
565
  function notifyMcpHost(payload: ReturnType<typeof assetPayload>) {
@@ -490,16 +584,18 @@ function notifyMcpHost(payload: ReturnType<typeof assetPayload>) {
490
584
  }) || false,
491
585
  )
492
586
  .catch(() => false)
493
- .then(() =>
494
- Promise.resolve(
587
+ .then((contextOk) => {
588
+ return Promise.resolve(
495
589
  sendMcpAppHostMessage({
496
590
  message,
497
591
  context: JSON.stringify(context, null, 2),
498
592
  content: chatContent,
499
593
  structuredContent: context,
500
594
  }) || false,
501
- ).catch(() => false),
502
- );
595
+ )
596
+ .catch(() => false)
597
+ .then((chatOk) => contextOk || chatOk);
598
+ });
503
599
  });
504
600
  }
505
601
 
@@ -619,6 +715,9 @@ function AssetOverlayImage({ asset }: { asset: Asset }) {
619
715
  export default function AssetPicker() {
620
716
  const [searchParams] = useSearchParams();
621
717
  const searchParamsKey = searchParams.toString();
718
+ const mcpChatBridgeActive =
719
+ searchParams.get("__an_mcp_chat_bridge") === "1" ||
720
+ isEmbedMcpChatBridgeActive();
622
721
  const urlHostConfig = useMemo(() => {
623
722
  const params = new URLSearchParams(searchParamsKey);
624
723
  return {
@@ -626,12 +725,20 @@ export default function AssetPicker() {
626
725
  prompt: params.get("prompt") ?? undefined,
627
726
  query: params.get("q") ?? undefined,
628
727
  libraryId: params.get("libraryId") ?? undefined,
728
+ libraryHint: params.get("libraryHint") ?? undefined,
629
729
  aspectRatio: params.get("aspectRatio") ?? undefined,
630
730
  presetId: params.get("presetId") ?? undefined,
631
731
  count: normalizeCount(params.get("count")),
632
- autoGenerate:
633
- params.get("autoGenerate") === "1" ||
634
- params.get("autoGenerate") === "true",
732
+ tier: normalizeTier(params.get("tier")),
733
+ styleStrength: normalizeStyleStrength(params.get("styleStrength")),
734
+ includeLogo: normalizeBoolean(params.get("includeLogo")),
735
+ callerAppId: params.get("callerAppId") ?? undefined,
736
+ candidateRunIds: normalizeCandidateRunIds(
737
+ params.getAll("candidateRunIds").length > 0
738
+ ? params.getAll("candidateRunIds")
739
+ : params.get("candidateRunIds"),
740
+ ),
741
+ autoGenerate: normalizeBoolean(params.get("autoGenerate")) ?? false,
635
742
  } satisfies HostConfig;
636
743
  }, [searchParamsKey]);
637
744
  const bridgeRef = useRef<EmbeddedAppBridge | null>(null);
@@ -655,6 +762,9 @@ export default function AssetPicker() {
655
762
  useState<Library | null>(null);
656
763
  const autoGenerateKeyRef = useRef<string | null>(null);
657
764
  const autoCreateLibraryRef = useRef(false);
765
+ const [visibleCandidateRunIds, setVisibleCandidateRunIds] = useState<
766
+ string[]
767
+ >(() => hostConfig.candidateRunIds ?? []);
658
768
 
659
769
  useEffect(() => {
660
770
  setHostConfig((current) => ({ ...current, ...urlHostConfig }));
@@ -665,6 +775,7 @@ export default function AssetPicker() {
665
775
  setAspectRatio(urlHostConfig.aspectRatio ?? "16:9");
666
776
  setPresetId(urlHostConfig.presetId ?? "none");
667
777
  setCount(urlHostConfig.count ?? 3);
778
+ setVisibleCandidateRunIds(urlHostConfig.candidateRunIds ?? []);
668
779
  }, [urlHostConfig]);
669
780
 
670
781
  const librariesQuery = useActionQuery("list-libraries", {
@@ -693,7 +804,10 @@ export default function AssetPicker() {
693
804
  const firstLibraryId = displayLibraries[0]?.id;
694
805
  if (!firstLibraryId) return;
695
806
  if (!selectedLibraryId) {
696
- setSelectedLibraryId(firstLibraryId);
807
+ setSelectedLibraryId(
808
+ matchLibraryByHint(displayLibraries, hostConfig.libraryHint) ??
809
+ firstLibraryId,
810
+ );
697
811
  return;
698
812
  }
699
813
  if (!libraryListReady) return;
@@ -703,7 +817,12 @@ export default function AssetPicker() {
703
817
  if (!selectedLibraryExists) {
704
818
  setSelectedLibraryId(firstLibraryId);
705
819
  }
706
- }, [displayLibraries, libraryListReady, selectedLibraryId]);
820
+ }, [
821
+ displayLibraries,
822
+ hostConfig.libraryHint,
823
+ libraryListReady,
824
+ selectedLibraryId,
825
+ ]);
707
826
 
708
827
  const { data: config } = useActionQuery(
709
828
  "get-image-generation-config",
@@ -742,9 +861,6 @@ export default function AssetPicker() {
742
861
  Boolean(selectedLibraryId) &&
743
862
  (presetsLoading || presetsFetching || presetsPending || !selectedPreset);
744
863
  const mediaLabel = mediaType === "video" ? "video" : "image";
745
- const [visibleCandidateRunIds, setVisibleCandidateRunIds] = useState<
746
- string[]
747
- >([]);
748
864
  const generateBatch = useActionMutation(
749
865
  "generate-image-batch" as any,
750
866
  {
@@ -876,12 +992,28 @@ export default function AssetPicker() {
876
992
  [],
877
993
  );
878
994
 
995
+ const postEmbeddedSelectionMessage = useCallback(
996
+ (
997
+ name: "chooseAsset" | "chooseImage",
998
+ payload: ReturnType<typeof assetPayload>,
999
+ ) => {
1000
+ try {
1001
+ return bridgeRef.current?.postMessage(name, payload) ?? false;
1002
+ } catch {
1003
+ return false;
1004
+ }
1005
+ },
1006
+ [],
1007
+ );
1008
+
879
1009
  const chooseAsset = (asset: Asset) => {
880
1010
  const payload = assetPayload(asset, mediaType);
881
1011
  if (embedded) {
882
- bridgeRef.current?.postMessage("chooseAsset", payload);
883
- if (payload.mediaType === "image") {
884
- bridgeRef.current?.postMessage("chooseImage", payload);
1012
+ if (!mcpChatBridgeActive) {
1013
+ postEmbeddedSelectionMessage("chooseAsset", payload);
1014
+ if (payload.mediaType === "image") {
1015
+ postEmbeddedSelectionMessage("chooseImage", payload);
1016
+ }
885
1017
  }
886
1018
  void notifyMcpHost(payload).then((ok) => {
887
1019
  if (ok) {
@@ -912,6 +1044,9 @@ export default function AssetPicker() {
912
1044
  setQuery("");
913
1045
  },
914
1046
  onError: (error: Error) => {
1047
+ // Allow the auto-create effect to retry after a transient failure;
1048
+ // otherwise the picker stays stuck on "Preparing..." until reload.
1049
+ autoCreateLibraryRef.current = false;
915
1050
  toast.error(error.message || "Could not prepare an image library");
916
1051
  },
917
1052
  } as any,
@@ -931,12 +1066,20 @@ export default function AssetPicker() {
931
1066
  imageSize: selectedPreset?.imageSize || "2K",
932
1067
  dismissible: false,
933
1068
  })),
1069
+ tier: hostConfig.tier,
1070
+ styleStrength: hostConfig.styleStrength ?? "balanced",
1071
+ includeLogo: hostConfig.includeLogo ?? false,
934
1072
  source: "ui",
1073
+ callerAppId: hostConfig.callerAppId,
935
1074
  } as any);
936
1075
  }, [
937
1076
  count,
938
1077
  effectiveAspectRatio,
939
1078
  generateBatch,
1079
+ hostConfig.callerAppId,
1080
+ hostConfig.includeLogo,
1081
+ hostConfig.styleStrength,
1082
+ hostConfig.tier,
940
1083
  prompt,
941
1084
  setVisibleCandidateRunIds,
942
1085
  selectedLibraryId,
@@ -945,8 +1088,35 @@ export default function AssetPicker() {
945
1088
  ]);
946
1089
 
947
1090
  useEffect(() => {
1091
+ const hostCandidateFilterMatches =
1092
+ hostConfig.candidateRunIds?.length &&
1093
+ normalizeMediaType(hostConfig.mediaType) === mediaType &&
1094
+ (hostConfig.libraryId ?? "") === (selectedLibraryId ?? "") &&
1095
+ (hostConfig.prompt ?? "") === prompt &&
1096
+ (hostConfig.aspectRatio ?? "16:9") === aspectRatio &&
1097
+ (hostConfig.presetId ?? "none") === presetId &&
1098
+ (hostConfig.count ?? 3) === count;
1099
+ if (hostCandidateFilterMatches) return;
948
1100
  setVisibleCandidateRunIds([]);
949
- }, [aspectRatio, count, mediaType, presetId, prompt, selectedLibraryId]);
1101
+ }, [
1102
+ aspectRatio,
1103
+ count,
1104
+ hostConfig.aspectRatio,
1105
+ hostConfig.candidateRunIds,
1106
+ hostConfig.count,
1107
+ hostConfig.callerAppId,
1108
+ hostConfig.includeLogo,
1109
+ hostConfig.libraryId,
1110
+ hostConfig.mediaType,
1111
+ hostConfig.presetId,
1112
+ hostConfig.prompt,
1113
+ hostConfig.styleStrength,
1114
+ hostConfig.tier,
1115
+ mediaType,
1116
+ presetId,
1117
+ prompt,
1118
+ selectedLibraryId,
1119
+ ]);
950
1120
 
951
1121
  useEffect(() => {
952
1122
  const bridge = createEmbeddedAppBridge({
@@ -961,6 +1131,9 @@ export default function AssetPicker() {
961
1131
  if (next.aspectRatio !== undefined) setAspectRatio(next.aspectRatio);
962
1132
  if (next.presetId !== undefined) setPresetId(next.presetId || "none");
963
1133
  if (next.count !== undefined) setCount(next.count);
1134
+ if (next.candidateRunIds !== undefined) {
1135
+ setVisibleCandidateRunIds(next.candidateRunIds);
1136
+ }
964
1137
  },
965
1138
  });
966
1139
  bridgeRef.current = bridge;
@@ -1016,6 +1189,31 @@ export default function AssetPicker() {
1016
1189
  usingStarterLibrary;
1017
1190
  const preparingGenerationLibrary =
1018
1191
  needsGenerationLibrary && createPickerLibrary.isPending;
1192
+ const waitingForLibraries = mediaType === "image" && !libraryListReady;
1193
+ const preparingAutoLibrary =
1194
+ mediaType === "image" &&
1195
+ Boolean(prompt.trim()) &&
1196
+ (waitingForLibraries ||
1197
+ needsGenerationLibrary ||
1198
+ preparingGenerationLibrary);
1199
+ const generationButtonLabel = generateBatch.isPending
1200
+ ? "Generating..."
1201
+ : waitingForRequestedPreset
1202
+ ? "Loading preset..."
1203
+ : setupNeeded
1204
+ ? "Setup needed"
1205
+ : preparingAutoLibrary
1206
+ ? "Preparing..."
1207
+ : "Generate";
1208
+ const generationStatus = generateBatch.isPending
1209
+ ? "Generating candidates..."
1210
+ : preparingAutoLibrary
1211
+ ? waitingForLibraries
1212
+ ? "Checking image libraries..."
1213
+ : "Preparing an image library..."
1214
+ : waitingForRequestedPreset
1215
+ ? "Loading the requested preset..."
1216
+ : null;
1019
1217
 
1020
1218
  useEffect(() => {
1021
1219
  if (!selectedPreset?.aspectRatio) return;
@@ -1213,9 +1411,14 @@ export default function AssetPicker() {
1213
1411
  disabled={!canGenerate}
1214
1412
  onClick={runGenerate}
1215
1413
  >
1216
- {generateBatch.isPending ? "Generating..." : "Generate"}
1414
+ {generationButtonLabel}
1217
1415
  </Button>
1218
1416
  </div>
1417
+ {generationStatus && (
1418
+ <div className="-mt-1 px-3 pb-2 text-[11px] leading-4 text-muted-foreground">
1419
+ {generationStatus}
1420
+ </div>
1421
+ )}
1219
1422
  </div>
1220
1423
  ) : null}
1221
1424
 
@@ -10,6 +10,7 @@
10
10
  * Options:
11
11
  * --view View name to navigate to
12
12
  * --path URL path to navigate to
13
+ * --threadId Chat thread ID to open on the chat route
13
14
  */
14
15
 
15
16
  import { defineAction } from "@agent-native/core/action";
@@ -22,6 +23,7 @@ export default defineAction({
22
23
  schema: z.object({
23
24
  view: z.string().optional().describe("View name to navigate to"),
24
25
  path: z.string().optional().describe("URL path to navigate to"),
26
+ threadId: z.string().optional().describe("Chat thread ID to open"),
25
27
  }),
26
28
  http: false,
27
29
  run: async (args) => {
@@ -31,6 +33,7 @@ export default defineAction({
31
33
  const nav: Record<string, string> = {};
32
34
  if (args.view) nav.view = args.view;
33
35
  if (args.path) nav.path = args.path;
36
+ if (args.threadId) nav.threadId = args.threadId;
34
37
  nav._writeId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
35
38
  await writeAppState("navigate", nav);
36
39
  return `Navigating to ${args.view || args.path}`;
@@ -35,6 +35,7 @@ const SIDEBAR_COLLAPSE_KEY = "chat.sidebar.collapsed";
35
35
  function routeOwnsToolbar(pathname: string): boolean {
36
36
  return (
37
37
  pathname === "/" ||
38
+ pathname.startsWith("/chat/") ||
38
39
  pathname === "/database" ||
39
40
  pathname.startsWith("/extensions")
40
41
  );
@@ -45,13 +46,17 @@ export function Layout({ children }: LayoutProps) {
45
46
  const navigate = useNavigate();
46
47
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
47
48
  const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
48
- const isChatRoute = location.pathname === "/";
49
+ const isChatRoute =
50
+ location.pathname === "/" || location.pathname.startsWith("/chat/");
49
51
  const chatHomeHandoffActive = useAgentChatHomeHandoff({
50
52
  storageKey: "chat",
51
53
  activePath: location.pathname,
52
54
  enabled: !isChatRoute,
53
55
  });
54
- useAgentChatHomeHandoffLinks({ storageKey: "chat", chatPath: "/" });
56
+ useAgentChatHomeHandoffLinks({
57
+ storageKey: "chat",
58
+ isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
59
+ });
55
60
 
56
61
  useEffect(() => {
57
62
  setMobileSidebarOpen(false);