@agent-native/core 0.72.3 → 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 (136) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +10 -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/api-path.ts +28 -0
  6. package/corpus/core/src/client/embed-auth.ts +26 -0
  7. package/corpus/core/src/client/mcp-app-host.ts +6 -0
  8. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
  9. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  10. package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
  11. package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
  12. package/corpus/core/src/embedding/agent.ts +6 -1
  13. package/corpus/core/src/embedding/bridge.ts +34 -1
  14. package/corpus/core/src/mcp/build-server.ts +38 -10
  15. package/corpus/core/src/mcp/embed-app.ts +120 -45
  16. package/corpus/core/src/mcp/index.ts +5 -0
  17. package/corpus/core/src/mcp/org-directory.ts +70 -11
  18. package/corpus/core/src/mcp/server.ts +14 -0
  19. package/corpus/core/src/mcp-client/config.ts +12 -0
  20. package/corpus/core/src/mcp-client/index.ts +2 -0
  21. package/corpus/core/src/mcp-client/manager.ts +268 -9
  22. package/corpus/core/src/mcp-client/remote-store.ts +154 -0
  23. package/corpus/core/src/mcp-client/routes.ts +2 -0
  24. package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
  25. package/corpus/core/src/server/index.ts +1 -0
  26. package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
  27. package/corpus/core/src/vite/client.ts +53 -0
  28. package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
  29. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
  30. package/corpus/templates/assets/AGENTS.md +7 -5
  31. package/corpus/templates/assets/actions/generate-asset.ts +161 -0
  32. package/corpus/templates/assets/actions/list-assets.ts +4 -2
  33. package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
  34. package/corpus/templates/assets/app/routes/library.tsx +243 -40
  35. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
  36. package/corpus/templates/design/AGENTS.md +10 -5
  37. package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
  38. package/corpus/templates/design/actions/insert-asset.ts +219 -0
  39. package/corpus/templates/design/actions/view-screen.ts +3 -1
  40. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
  41. package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
  42. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
  43. package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
  44. package/dist/a2a/client.d.ts +1 -0
  45. package/dist/a2a/client.d.ts.map +1 -1
  46. package/dist/a2a/client.js +5 -3
  47. package/dist/a2a/client.js.map +1 -1
  48. package/dist/client/api-path.d.ts.map +1 -1
  49. package/dist/client/api-path.js +27 -0
  50. package/dist/client/api-path.js.map +1 -1
  51. package/dist/client/embed-auth.d.ts.map +1 -1
  52. package/dist/client/embed-auth.js +27 -0
  53. package/dist/client/embed-auth.js.map +1 -1
  54. package/dist/client/mcp-app-host.d.ts.map +1 -1
  55. package/dist/client/mcp-app-host.js +5 -0
  56. package/dist/client/mcp-app-host.js.map +1 -1
  57. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  58. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  59. package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
  60. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  61. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  62. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  63. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  64. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  65. package/dist/client/settings/DemoModeSection.js +3 -1
  66. package/dist/client/settings/DemoModeSection.js.map +1 -1
  67. package/dist/demo/fetch-interceptor.js +1 -1
  68. package/dist/demo/fetch-interceptor.js.map +1 -1
  69. package/dist/embedding/agent.d.ts +1 -1
  70. package/dist/embedding/agent.d.ts.map +1 -1
  71. package/dist/embedding/agent.js +1 -1
  72. package/dist/embedding/agent.js.map +1 -1
  73. package/dist/embedding/bridge.d.ts.map +1 -1
  74. package/dist/embedding/bridge.js +27 -1
  75. package/dist/embedding/bridge.js.map +1 -1
  76. package/dist/extensions/schema.d.ts +2 -2
  77. package/dist/mcp/build-server.d.ts +2 -0
  78. package/dist/mcp/build-server.d.ts.map +1 -1
  79. package/dist/mcp/build-server.js +32 -9
  80. package/dist/mcp/build-server.js.map +1 -1
  81. package/dist/mcp/embed-app.d.ts.map +1 -1
  82. package/dist/mcp/embed-app.js +120 -45
  83. package/dist/mcp/embed-app.js.map +1 -1
  84. package/dist/mcp/index.d.ts +1 -0
  85. package/dist/mcp/index.d.ts.map +1 -1
  86. package/dist/mcp/index.js +1 -0
  87. package/dist/mcp/index.js.map +1 -1
  88. package/dist/mcp/org-directory.d.ts +1 -0
  89. package/dist/mcp/org-directory.d.ts.map +1 -1
  90. package/dist/mcp/org-directory.js +59 -11
  91. package/dist/mcp/org-directory.js.map +1 -1
  92. package/dist/mcp/server.d.ts.map +1 -1
  93. package/dist/mcp/server.js +9 -0
  94. package/dist/mcp/server.js.map +1 -1
  95. package/dist/mcp-client/config.d.ts +12 -0
  96. package/dist/mcp-client/config.d.ts.map +1 -1
  97. package/dist/mcp-client/config.js.map +1 -1
  98. package/dist/mcp-client/index.d.ts +1 -1
  99. package/dist/mcp-client/index.d.ts.map +1 -1
  100. package/dist/mcp-client/index.js +1 -1
  101. package/dist/mcp-client/index.js.map +1 -1
  102. package/dist/mcp-client/manager.d.ts +2 -0
  103. package/dist/mcp-client/manager.d.ts.map +1 -1
  104. package/dist/mcp-client/manager.js +201 -9
  105. package/dist/mcp-client/manager.js.map +1 -1
  106. package/dist/mcp-client/remote-store.d.ts +27 -0
  107. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  108. package/dist/mcp-client/remote-store.js +105 -0
  109. package/dist/mcp-client/remote-store.js.map +1 -1
  110. package/dist/mcp-client/routes.d.ts +1 -0
  111. package/dist/mcp-client/routes.d.ts.map +1 -1
  112. package/dist/mcp-client/routes.js +1 -0
  113. package/dist/mcp-client/routes.js.map +1 -1
  114. package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
  115. package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
  116. package/dist/provider-api/corpus-jobs.d.ts +8 -8
  117. package/dist/server/agent-chat-plugin.d.ts +2 -0
  118. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  119. package/dist/server/agent-chat-plugin.js +8 -0
  120. package/dist/server/agent-chat-plugin.js.map +1 -1
  121. package/dist/server/index.d.ts +1 -1
  122. package/dist/server/index.d.ts.map +1 -1
  123. package/dist/server/index.js +1 -1
  124. package/dist/server/index.js.map +1 -1
  125. package/dist/shared/mcp-embed-headers.d.ts +3 -1
  126. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  127. package/dist/shared/mcp-embed-headers.js +40 -3
  128. package/dist/shared/mcp-embed-headers.js.map +1 -1
  129. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  130. package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
  131. package/dist/sharing/actions/share-resource.d.ts +1 -1
  132. package/dist/sharing/schema.d.ts +1 -1
  133. package/dist/vite/client.d.ts.map +1 -1
  134. package/dist/vite/client.js +44 -0
  135. package/dist/vite/client.js.map +1 -1
  136. package/package.json +6 -6
@@ -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
 
@@ -597,6 +597,15 @@ pub(crate) mod macos {
597
597
  mic_device_label.as_deref(),
598
598
  )?;
599
599
  let input_node = unsafe { engine.inputNode() };
600
+ // Finalize input-device format negotiation before reading the format.
601
+ // When `configure_engine_input_device` pins a specific mic, the AUHAL's
602
+ // current device is switched; the input node's `outputFormatForBus(0)`
603
+ // only reflects the new device after `prepare()` runs. Reading it first
604
+ // returns a stale format, so the tap gets installed with a mismatched
605
+ // format and forwards no usable audio to the recognizer — the user
606
+ // speaks but SFSpeechRecognizer reports "No speech detected". Mirrors
607
+ // the working `start_raw_mic_capture` ordering.
608
+ unsafe { engine.prepare() };
600
609
  let format = unsafe { input_node.outputFormatForBus(0) };
601
610
 
602
611
  // Install a tap that forwards every PCM buffer into the recognition
@@ -26,11 +26,16 @@ patterns live in `.agents/skills/`.
26
26
  - Generated files must be complete, standalone HTML unless the user asks for a
27
27
  different export format. They should render in the iframe without a build step.
28
28
  - For raster image generation, restyling, or editing existing screenshots/photos,
29
- use the first-party Assets app via `call-agent` with agent `"assets"` when
30
- available instead of claiming Design has no image tools. If the user attached
31
- an image, use its hosted chat-attachment URL or call `upload-image` to create
32
- one before delegating. If no image/upload provider is configured, say that
33
- specific setup is needed and continue any non-image Design work separately.
29
+ use the available first-party Assets MCP tool such as `generate-asset` instead
30
+ of placeholders or generic stock imagery. When the Assets picker returns a
31
+ selected asset, preserve `assetId`, `runId`, and URLs verbatim; if a design is
32
+ open, call `insert-asset` with the chosen URL/id, then refine placement with
33
+ `get-design-snapshot` and `edit-design` as needed. If no Assets MCP tool is
34
+ available, use the first-party Assets app via `call-agent` with agent
35
+ `"assets"` when available. If the user attached an image, use its hosted
36
+ chat-attachment URL or call `upload-image` to create one before delegating. If
37
+ no image/upload provider is configured, say that specific setup is needed and
38
+ continue any non-image Design work separately.
34
39
  - Use Alpine.js and Tailwind CDN for interactive prototypes. Prefer Alpine
35
40
  directives over raw inline event handlers.
36
41
  - Navigate between prototype screens with Alpine state (`x-show`), a
@@ -0,0 +1,131 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { getDbExec } from "@agent-native/core/db";
3
+ import {
4
+ getRequestOrgId,
5
+ getRequestUserEmail,
6
+ } from "@agent-native/core/server/request-context";
7
+ import {
8
+ addFirstPartyRemoteServer,
9
+ isFirstPartyRemoteEndpointTrusted,
10
+ listRemoteServers,
11
+ removeRemoteServer,
12
+ } from "@agent-native/core/mcp-client";
13
+ import { fetchOrgApps } from "@agent-native/core/mcp";
14
+ import { refreshGlobalMcpManager } from "@agent-native/core/server";
15
+ import { z } from "zod";
16
+
17
+ const SERVER_NAME = "assets";
18
+
19
+ function assetsMcpUrl(appUrl: string): string {
20
+ return `${appUrl.replace(/\/+$/, "")}/_agent-native/mcp`;
21
+ }
22
+
23
+ function sameEndpoint(a: string, b: string): boolean {
24
+ try {
25
+ const urlA = new URL(a);
26
+ const urlB = new URL(b);
27
+ return (
28
+ urlA.origin === urlB.origin &&
29
+ urlA.pathname.replace(/\/+$/, "") === urlB.pathname.replace(/\/+$/, "")
30
+ );
31
+ } catch {
32
+ return a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
33
+ }
34
+ }
35
+
36
+ async function listAssetsServers(orgId: string) {
37
+ const servers = await listRemoteServers("org", orgId);
38
+ return servers.filter(
39
+ (server) => server.name === SERVER_NAME && server.firstParty === true,
40
+ );
41
+ }
42
+
43
+ async function assertCanManageOrgMcp(orgId: string, userEmail: string) {
44
+ const result = await getDbExec().execute({
45
+ sql: `SELECT role FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,
46
+ args: [orgId, userEmail.toLowerCase()],
47
+ });
48
+ const role = String(
49
+ (result.rows[0] as { role?: unknown } | undefined)?.role,
50
+ ).toLowerCase();
51
+ if (role !== "owner" && role !== "admin") {
52
+ throw new Error(
53
+ "Only organization owners and admins can connect org-scoped MCP servers.",
54
+ );
55
+ }
56
+ }
57
+
58
+ export default defineAction({
59
+ description:
60
+ "Connect the first-party Assets MCP server for the active Design organization. Use this explicit setup action instead of mutating onboarding status.",
61
+ schema: z.object({}),
62
+ publicAgent: { expose: true, readOnly: false, requiresAuth: true },
63
+ run: async () => {
64
+ const userEmail = getRequestUserEmail();
65
+ const orgId = getRequestOrgId();
66
+ if (!userEmail || !orgId) {
67
+ throw new Error("You must be signed in to an organization.");
68
+ }
69
+ await assertCanManageOrgMcp(orgId, userEmail);
70
+
71
+ const apps = await fetchOrgApps({ selfId: "design" });
72
+ const assets = apps.find((app) => app.id === "assets");
73
+ if (!assets?.url) {
74
+ throw new Error("Could not find the Assets app in this organization.");
75
+ }
76
+ const currentUrl = assetsMcpUrl(assets.url);
77
+
78
+ const existingServers = await listAssetsServers(orgId);
79
+ for (const existing of existingServers) {
80
+ const trust = await isFirstPartyRemoteEndpointTrusted(
81
+ orgId,
82
+ "assets",
83
+ existing.url,
84
+ );
85
+ if (trust.ok && sameEndpoint(existing.url, currentUrl)) {
86
+ const managerRefreshed = await refreshGlobalMcpManager();
87
+ return {
88
+ ok: true,
89
+ connected: true,
90
+ server: existing,
91
+ managerRefreshed,
92
+ message: "Assets MCP is already connected for this organization.",
93
+ };
94
+ }
95
+ }
96
+
97
+ // Verify the target endpoint is trusted before removing any existing
98
+ // server, so a transient org-directory/auth failure cannot turn a reconnect
99
+ // into a full disconnect for the org.
100
+ const targetTrust = await isFirstPartyRemoteEndpointTrusted(
101
+ orgId,
102
+ "assets",
103
+ currentUrl,
104
+ );
105
+ if (!targetTrust.ok) {
106
+ throw new Error(targetTrust.error);
107
+ }
108
+
109
+ for (const existing of existingServers) {
110
+ await removeRemoteServer("org", orgId, existing.id);
111
+ }
112
+
113
+ const result = await addFirstPartyRemoteServer(orgId, {
114
+ appId: "assets",
115
+ name: SERVER_NAME,
116
+ url: currentUrl,
117
+ description: "First-party Assets MCP server for on-brand media.",
118
+ });
119
+ if (result.ok !== true) {
120
+ throw new Error(result.error);
121
+ }
122
+ const managerRefreshed = await refreshGlobalMcpManager();
123
+ return {
124
+ ok: true,
125
+ connected: true,
126
+ server: result.server,
127
+ managerRefreshed,
128
+ message: "Assets MCP is connected for this organization.",
129
+ };
130
+ },
131
+ });