@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +10 -0
- package/corpus/core/package.json +6 -6
- package/corpus/core/src/a2a/client.ts +7 -3
- package/corpus/core/src/client/api-path.ts +28 -0
- package/corpus/core/src/client/embed-auth.ts +26 -0
- package/corpus/core/src/client/mcp-app-host.ts +6 -0
- package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
- package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
- package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
- package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
- package/corpus/core/src/embedding/agent.ts +6 -1
- package/corpus/core/src/embedding/bridge.ts +34 -1
- package/corpus/core/src/mcp/build-server.ts +38 -10
- package/corpus/core/src/mcp/embed-app.ts +120 -45
- package/corpus/core/src/mcp/index.ts +5 -0
- package/corpus/core/src/mcp/org-directory.ts +70 -11
- package/corpus/core/src/mcp/server.ts +14 -0
- package/corpus/core/src/mcp-client/config.ts +12 -0
- package/corpus/core/src/mcp-client/index.ts +2 -0
- package/corpus/core/src/mcp-client/manager.ts +268 -9
- package/corpus/core/src/mcp-client/remote-store.ts +154 -0
- package/corpus/core/src/mcp-client/routes.ts +2 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
- package/corpus/core/src/vite/client.ts +53 -0
- package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
- package/corpus/templates/assets/AGENTS.md +7 -5
- package/corpus/templates/assets/actions/generate-asset.ts +161 -0
- package/corpus/templates/assets/actions/list-assets.ts +4 -2
- package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
- package/corpus/templates/assets/app/routes/library.tsx +243 -40
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
- package/corpus/templates/design/AGENTS.md +10 -5
- package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
- package/corpus/templates/design/actions/insert-asset.ts +219 -0
- package/corpus/templates/design/actions/view-screen.ts +3 -1
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
- package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
- package/dist/a2a/client.d.ts +1 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +5 -3
- package/dist/a2a/client.js.map +1 -1
- package/dist/client/api-path.d.ts.map +1 -1
- package/dist/client/api-path.js +27 -0
- package/dist/client/api-path.js.map +1 -1
- package/dist/client/embed-auth.d.ts.map +1 -1
- package/dist/client/embed-auth.js +27 -0
- package/dist/client/embed-auth.js.map +1 -1
- package/dist/client/mcp-app-host.d.ts.map +1 -1
- package/dist/client/mcp-app-host.js +5 -0
- package/dist/client/mcp-app-host.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
- package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/client/resources/use-mcp-servers.d.ts +1 -0
- package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
- package/dist/client/resources/use-mcp-servers.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
- package/dist/client/settings/DemoModeSection.js +3 -1
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/demo/fetch-interceptor.js +1 -1
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/embedding/agent.d.ts +1 -1
- package/dist/embedding/agent.d.ts.map +1 -1
- package/dist/embedding/agent.js +1 -1
- package/dist/embedding/agent.js.map +1 -1
- package/dist/embedding/bridge.d.ts.map +1 -1
- package/dist/embedding/bridge.js +27 -1
- package/dist/embedding/bridge.js.map +1 -1
- package/dist/extensions/schema.d.ts +2 -2
- package/dist/mcp/build-server.d.ts +2 -0
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +32 -9
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +120 -45
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/org-directory.d.ts +1 -0
- package/dist/mcp/org-directory.d.ts.map +1 -1
- package/dist/mcp/org-directory.js +59 -11
- package/dist/mcp/org-directory.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +9 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp-client/config.d.ts +12 -0
- package/dist/mcp-client/config.d.ts.map +1 -1
- package/dist/mcp-client/config.js.map +1 -1
- package/dist/mcp-client/index.d.ts +1 -1
- package/dist/mcp-client/index.d.ts.map +1 -1
- package/dist/mcp-client/index.js +1 -1
- package/dist/mcp-client/index.js.map +1 -1
- package/dist/mcp-client/manager.d.ts +2 -0
- package/dist/mcp-client/manager.d.ts.map +1 -1
- package/dist/mcp-client/manager.js +201 -9
- package/dist/mcp-client/manager.js.map +1 -1
- package/dist/mcp-client/remote-store.d.ts +27 -0
- package/dist/mcp-client/remote-store.d.ts.map +1 -1
- package/dist/mcp-client/remote-store.js +105 -0
- package/dist/mcp-client/remote-store.js.map +1 -1
- package/dist/mcp-client/routes.d.ts +1 -0
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +1 -0
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
- package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +8 -8
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +8 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +3 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +40 -3
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
- package/dist/sharing/actions/share-resource.d.ts +1 -1
- package/dist/sharing/schema.d.ts +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +44 -0
- package/dist/vite/client.js.map +1 -1
- 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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
)
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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(
|
|
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
|
-
}, [
|
|
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
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
-
}, [
|
|
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
|
-
{
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
+
});
|