@agent-native/core 0.118.1 → 0.119.1
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 +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +15 -0
- package/corpus/core/docs/content/template-chat.mdx +1 -1
- package/corpus/core/docs/content/template-plan.mdx +3 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +371 -0
- package/corpus/core/src/a2a/client.ts +3 -1
- package/corpus/core/src/a2a/index.ts +21 -0
- package/corpus/core/src/a2a/types.ts +8 -0
- package/corpus/core/src/agent/model-config.ts +19 -23
- package/corpus/core/src/agent/production-agent.ts +167 -100
- package/corpus/core/src/agent/types.ts +16 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +190 -35
- package/corpus/core/src/client/chat-model-groups.ts +67 -27
- package/corpus/core/src/client/sharing/ShareButton.tsx +1 -27
- package/corpus/core/src/client/sharing/ShareDialog.tsx +0 -16
- package/corpus/core/src/client/sharing/useShareButtonController.ts +0 -7
- package/corpus/core/src/client/sharing/useShareDialogController.ts +0 -2
- package/corpus/core/src/client/sse-event-processor.ts +85 -1
- package/corpus/core/src/ingestion/figma-node-to-html.ts +20 -99
- package/corpus/core/src/ingestion/figma-paint-math.ts +371 -0
- package/corpus/core/src/ingestion/index.ts +20 -0
- package/corpus/core/src/localization/default-messages.ts +6 -0
- package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/corpus/core/src/scripts/call-agent.ts +49 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +50 -10
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/templates/assets/app/routes/library.tsx +17 -9
- package/corpus/templates/clips/changelog/2026-07-23-fixed-recordings-failing-to-save-with-a-cancelled-or-cleanup.md +6 -0
- package/corpus/templates/clips/server/plugins/db.ts +9 -1
- package/corpus/templates/design/AGENTS.md +18 -5
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +25 -12
- package/corpus/templates/design/actions/hydrate-figma-paste-images.ts +115 -0
- package/corpus/templates/design/actions/import-figma-clipboard.ts +83 -18
- package/corpus/templates/design/actions/import-figma-frame.ts +34 -21
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +28 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +106 -11
- package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +273 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +7 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +57 -9
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +10 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +8 -1
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +150 -2
- package/corpus/templates/design/app/lib/design-file-upload.ts +85 -0
- package/corpus/templates/design/app/lib/design-import.ts +19 -1
- package/corpus/templates/design/app/lib/figma-clipboard.ts +110 -10
- package/corpus/templates/design/changelog/2026-07-20-paste-figma-frames-directly-onto-the-canvas-no-token-needed.md +6 -0
- package/corpus/templates/design/changelog/2026-07-21-fig-upload-frame-url-and-rate-limit-errors.md +5 -0
- package/corpus/templates/design/changelog/2026-07-22-connecting-a-figma-token-now-actually-fills-in-a-pasted-desi.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-figma-line-arrow-vectors-and-stroked-icons-now-render-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-fill-a-no-token-figma-paste-s-images-by-dropping-the-origina.md +6 -0
- package/corpus/templates/design/changelog/2026-07-22-imported-and-pasted-figma-frames-now-render-faithfully-fixed.md +6 -0
- package/corpus/templates/design/server/handlers/import-design-file.ts +50 -0
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +90 -49
- package/corpus/templates/design/server/lib/fig-file-import.ts +19 -5
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +583 -78
- package/corpus/templates/design/server/lib/figma-clipboard-local-decode.ts +238 -0
- package/corpus/templates/design/server/lib/figma-image-hydration.ts +389 -0
- package/corpus/templates/design/server/lib/figma-node-import.ts +136 -36
- package/corpus/templates/design/server/register-secrets.ts +14 -2
- package/dist/a2a/activity.d.ts +50 -0
- package/dist/a2a/activity.d.ts.map +1 -0
- package/dist/a2a/activity.js +236 -0
- package/dist/a2a/activity.js.map +1 -0
- package/dist/a2a/client.d.ts +2 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +1 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/index.d.ts +2 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -0
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/types.d.ts +1 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts +2 -2
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/model-config.d.ts +16 -16
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +19 -23
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +26 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +137 -80
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +63 -14
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +44 -21
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -4
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/client/sharing/ShareDialog.js +1 -2
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/client/sharing/useShareButtonController.d.ts +0 -1
- package/dist/client/sharing/useShareButtonController.d.ts.map +1 -1
- package/dist/client/sharing/useShareButtonController.js +0 -6
- package/dist/client/sharing/useShareButtonController.js.map +1 -1
- package/dist/client/sharing/useShareDialogController.d.ts +0 -1
- package/dist/client/sharing/useShareDialogController.d.ts.map +1 -1
- package/dist/client/sharing/useShareDialogController.js +0 -1
- package/dist/client/sharing/useShareDialogController.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +12 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +63 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/ingestion/figma-node-to-html.d.ts.map +1 -1
- package/dist/ingestion/figma-node-to-html.js +14 -88
- package/dist/ingestion/figma-node-to-html.js.map +1 -1
- package/dist/ingestion/figma-paint-math.d.ts +157 -0
- package/dist/ingestion/figma-paint-math.d.ts.map +1 -0
- package/dist/ingestion/figma-paint-math.js +274 -0
- package/dist/ingestion/figma-paint-math.js.map +1 -0
- package/dist/ingestion/index.d.ts +1 -0
- package/dist/ingestion/index.d.ts.map +1 -1
- package/dist/ingestion/index.js +1 -0
- package/dist/ingestion/index.js.map +1 -1
- package/dist/localization/default-messages.d.ts +6 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +6 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +1 -18
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +49 -6
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +38 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/docs/content/a2a-protocol.mdx +15 -0
- package/docs/content/template-chat.mdx +1 -1
- package/docs/content/template-plan.mdx +3 -3
- package/package.json +1 -1
- package/src/a2a/activity.ts +371 -0
- package/src/a2a/client.ts +3 -1
- package/src/a2a/index.ts +21 -0
- package/src/a2a/types.ts +8 -0
- package/src/agent/model-config.ts +19 -23
- package/src/agent/production-agent.ts +167 -100
- package/src/agent/types.ts +16 -1
- package/src/client/chat/tool-call-display.tsx +190 -35
- package/src/client/chat-model-groups.ts +67 -27
- package/src/client/sharing/ShareButton.tsx +1 -27
- package/src/client/sharing/ShareDialog.tsx +0 -16
- package/src/client/sharing/useShareButtonController.ts +0 -7
- package/src/client/sharing/useShareDialogController.ts +0 -2
- package/src/client/sse-event-processor.ts +85 -1
- package/src/ingestion/figma-node-to-html.ts +20 -99
- package/src/ingestion/figma-paint-math.ts +371 -0
- package/src/ingestion/index.ts +20 -0
- package/src/localization/default-messages.ts +6 -0
- package/src/scripts/agent-engines/list-agent-engines.ts +1 -18
- package/src/scripts/call-agent.ts +49 -6
- package/src/server/agent-chat-plugin.ts +50 -10
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -0
- package/corpus/core/src/observability/hosted-model-experiment.ts +0 -118
- package/dist/observability/hosted-model-experiment.d.ts +0 -39
- package/dist/observability/hosted-model-experiment.d.ts.map +0 -1
- package/dist/observability/hosted-model-experiment.js +0 -90
- package/dist/observability/hosted-model-experiment.js.map +0 -1
- package/src/observability/hosted-model-experiment.ts +0 -118
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
type H3Event,
|
|
14
14
|
} from "h3";
|
|
15
15
|
|
|
16
|
+
import {
|
|
17
|
+
applyA2AAgentActivityEvent,
|
|
18
|
+
buildA2AAgentActivityPart,
|
|
19
|
+
createA2AAgentActivityState,
|
|
20
|
+
} from "../a2a/activity.js";
|
|
16
21
|
import {
|
|
17
22
|
appendA2AArtifactLinks,
|
|
18
23
|
buildA2ARecoverableArtifactMessage,
|
|
@@ -400,6 +405,8 @@ export function createSerializedA2ATaskStatusWriter(
|
|
|
400
405
|
};
|
|
401
406
|
}
|
|
402
407
|
|
|
408
|
+
const A2A_ACTIVITY_CHECKPOINT_MIN_INTERVAL_MS = 2_000;
|
|
409
|
+
|
|
403
410
|
export async function resolveA2ARecoverableArtifactSecret(
|
|
404
411
|
orgId: string | null | undefined = getRequestOrgId(),
|
|
405
412
|
): Promise<string | undefined> {
|
|
@@ -1656,10 +1663,36 @@ export function createAgentChatPlugin(
|
|
|
1656
1663
|
const a2aEvents: AgentChatEvent[] = [];
|
|
1657
1664
|
const a2aToolResults: A2AToolResultSummary[] = [];
|
|
1658
1665
|
let lastRecoverableArtifactText = "";
|
|
1666
|
+
let activityState = createA2AAgentActivityState();
|
|
1667
|
+
let lastActivityCheckpointAt = 0;
|
|
1659
1668
|
const recoverableArtifactSecret =
|
|
1660
1669
|
await resolveA2ARecoverableArtifactSecret();
|
|
1661
1670
|
const recoverableArtifactStatusWriter =
|
|
1662
1671
|
createSerializedA2ATaskStatusWriter(context.taskId);
|
|
1672
|
+
const activityStatusMessage = (): A2AMessage => ({
|
|
1673
|
+
role: "agent",
|
|
1674
|
+
...(lastRecoverableArtifactText
|
|
1675
|
+
? { metadata: { agentNativeRecoverableArtifacts: true } }
|
|
1676
|
+
: {}),
|
|
1677
|
+
parts: [
|
|
1678
|
+
buildA2AAgentActivityPart(activityState),
|
|
1679
|
+
...(lastRecoverableArtifactText
|
|
1680
|
+
? [{ type: "text" as const, text: lastRecoverableArtifactText }]
|
|
1681
|
+
: []),
|
|
1682
|
+
],
|
|
1683
|
+
});
|
|
1684
|
+
const checkpointActivity = (force = false) => {
|
|
1685
|
+
const now = Date.now();
|
|
1686
|
+
if (
|
|
1687
|
+
!force &&
|
|
1688
|
+
now - lastActivityCheckpointAt <
|
|
1689
|
+
A2A_ACTIVITY_CHECKPOINT_MIN_INTERVAL_MS
|
|
1690
|
+
) {
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
lastActivityCheckpointAt = now;
|
|
1694
|
+
recoverableArtifactStatusWriter.enqueue(activityStatusMessage());
|
|
1695
|
+
};
|
|
1663
1696
|
const controller = new AbortController();
|
|
1664
1697
|
const correlation = sanitizeA2ACorrelationMetadata(context.metadata);
|
|
1665
1698
|
const telemetryThreadId =
|
|
@@ -1697,6 +1730,12 @@ export function createAgentChatPlugin(
|
|
|
1697
1730
|
turnId: context.taskId,
|
|
1698
1731
|
send: (event) => {
|
|
1699
1732
|
a2aEvents.push(event);
|
|
1733
|
+
const nextActivityState = applyA2AAgentActivityEvent(
|
|
1734
|
+
activityState,
|
|
1735
|
+
event,
|
|
1736
|
+
);
|
|
1737
|
+
const activityChanged = nextActivityState !== activityState;
|
|
1738
|
+
activityState = nextActivityState;
|
|
1700
1739
|
if (event.type === "tool_start") {
|
|
1701
1740
|
console.log(`[A2A] Tool call: ${event.tool}`);
|
|
1702
1741
|
} else if (event.type === "tool_done") {
|
|
@@ -1727,22 +1766,20 @@ export function createAgentChatPlugin(
|
|
|
1727
1766
|
recoverableArtifactText !== lastRecoverableArtifactText
|
|
1728
1767
|
) {
|
|
1729
1768
|
lastRecoverableArtifactText = recoverableArtifactText;
|
|
1730
|
-
recoverableArtifactStatusWriter.enqueue({
|
|
1731
|
-
role: "agent",
|
|
1732
|
-
metadata: { agentNativeRecoverableArtifacts: true },
|
|
1733
|
-
parts: [
|
|
1734
|
-
{
|
|
1735
|
-
type: "text",
|
|
1736
|
-
text: recoverableArtifactText,
|
|
1737
|
-
},
|
|
1738
|
-
],
|
|
1739
|
-
});
|
|
1740
1769
|
}
|
|
1741
1770
|
} else if (event.type === "error") {
|
|
1742
1771
|
console.error(`[A2A] Error: ${event.error}`);
|
|
1743
1772
|
} else if (event.type === "done") {
|
|
1744
1773
|
console.log(`[A2A] Done. Events: ${a2aEvents.length}`);
|
|
1745
1774
|
}
|
|
1775
|
+
if (activityChanged) {
|
|
1776
|
+
checkpointActivity(
|
|
1777
|
+
event.type === "tool_start" ||
|
|
1778
|
+
event.type === "tool_done" ||
|
|
1779
|
+
event.type === "error" ||
|
|
1780
|
+
event.type === "done",
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1746
1783
|
},
|
|
1747
1784
|
signal: controller.signal,
|
|
1748
1785
|
},
|
|
@@ -1779,6 +1816,7 @@ export function createAgentChatPlugin(
|
|
|
1779
1816
|
|
|
1780
1817
|
// The continuation can observe terminal output immediately, so make
|
|
1781
1818
|
// its latest mutation checkpoint durable first.
|
|
1819
|
+
checkpointActivity(true);
|
|
1782
1820
|
await recoverableArtifactStatusWriter.flush();
|
|
1783
1821
|
|
|
1784
1822
|
const approval = [...a2aEvents]
|
|
@@ -1817,6 +1855,7 @@ export function createAgentChatPlugin(
|
|
|
1817
1855
|
},
|
|
1818
1856
|
},
|
|
1819
1857
|
parts: [
|
|
1858
|
+
buildA2AAgentActivityPart(activityState),
|
|
1820
1859
|
{
|
|
1821
1860
|
type: "text" as const,
|
|
1822
1861
|
text:
|
|
@@ -1851,6 +1890,7 @@ export function createAgentChatPlugin(
|
|
|
1851
1890
|
yield {
|
|
1852
1891
|
role: "agent" as const,
|
|
1853
1892
|
parts: [
|
|
1893
|
+
buildA2AAgentActivityPart(activityState),
|
|
1854
1894
|
{
|
|
1855
1895
|
type: "text" as const,
|
|
1856
1896
|
text: finalText || "(no response)",
|
|
@@ -195,6 +195,22 @@ for await (const update of client.stream({
|
|
|
195
195
|
}
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
### Agent activity in delegated chat
|
|
199
|
+
|
|
200
|
+
Agent Native peers attach a bounded `data` part with
|
|
201
|
+
`kind: "agent-native/agent-activity"` to in-progress and terminal task status
|
|
202
|
+
messages. It contains the same user-visible reasoning summaries shown in the
|
|
203
|
+
receiving app, tool names and completion states, elapsed time, and progressive
|
|
204
|
+
response text. It never includes tool inputs, tool results, credentials, or
|
|
205
|
+
hidden provider reasoning.
|
|
206
|
+
|
|
207
|
+
`call-agent` reads this optional part while it polls an asynchronous task and
|
|
208
|
+
renders the remote work as a nested agent run. Unknown A2A peers do not need to
|
|
209
|
+
implement the extension: their ordinary status and final text still render in
|
|
210
|
+
the same nested block. Treat activity data as untrusted presentation content;
|
|
211
|
+
never use it for identity, authorization, approval, routing, or artifact
|
|
212
|
+
validation.
|
|
213
|
+
|
|
198
214
|
### Carrying explicit chat authorization
|
|
199
215
|
|
|
200
216
|
When the authenticated caller has an exact consequential action that the user
|
|
@@ -60,7 +60,6 @@ import {
|
|
|
60
60
|
} from "react-router";
|
|
61
61
|
import { toast } from "sonner";
|
|
62
62
|
|
|
63
|
-
import { CreateLibraryDialog } from "@/components/library/CreateLibraryDialog";
|
|
64
63
|
import { LibraryPresetGrid } from "@/components/library/LibraryPresetGrid";
|
|
65
64
|
import { Badge } from "@/components/ui/badge";
|
|
66
65
|
import { Button } from "@/components/ui/button";
|
|
@@ -2087,7 +2086,7 @@ export function LibraryWorkspace({
|
|
|
2087
2086
|
const t = useT();
|
|
2088
2087
|
const navigate = useNavigate();
|
|
2089
2088
|
const routeSelectedLibraryId = useLibraryRouteSelectedId(selectedLibraryId);
|
|
2090
|
-
const
|
|
2089
|
+
const createLibrary = useActionMutation("create-library");
|
|
2091
2090
|
const { data, isLoading, isError, isFetching, refetch } = useActionQuery(
|
|
2092
2091
|
"list-libraries",
|
|
2093
2092
|
{
|
|
@@ -2131,6 +2130,20 @@ export function LibraryWorkspace({
|
|
|
2131
2130
|
});
|
|
2132
2131
|
}, [currentLibrary?.title, routeSelectedLibraryId]);
|
|
2133
2132
|
|
|
2133
|
+
const handleCreateKit = useCallback(() => {
|
|
2134
|
+
createLibrary.mutate(
|
|
2135
|
+
{ title: t("brandKits.newBrandKit") },
|
|
2136
|
+
{
|
|
2137
|
+
onSuccess: (library: any) => {
|
|
2138
|
+
void navigate(`/brand-kits/${library.id}/settings`);
|
|
2139
|
+
},
|
|
2140
|
+
onError: (error: Error) => {
|
|
2141
|
+
toast.error(error.message);
|
|
2142
|
+
},
|
|
2143
|
+
},
|
|
2144
|
+
);
|
|
2145
|
+
}, [createLibrary, navigate, t]);
|
|
2146
|
+
|
|
2134
2147
|
return (
|
|
2135
2148
|
<div className="flex h-full min-h-0 flex-col bg-background text-foreground">
|
|
2136
2149
|
<section className="min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
@@ -2139,7 +2152,7 @@ export function LibraryWorkspace({
|
|
|
2139
2152
|
selectedLibraryId={routeSelectedLibraryId}
|
|
2140
2153
|
libraries={libraries}
|
|
2141
2154
|
isLoading={isLoading}
|
|
2142
|
-
onCreateKit={
|
|
2155
|
+
onCreateKit={handleCreateKit}
|
|
2143
2156
|
/>
|
|
2144
2157
|
{isError ? (
|
|
2145
2158
|
<div className="flex min-h-80 flex-col items-center justify-center gap-3 px-6 text-center">
|
|
@@ -2168,15 +2181,10 @@ export function LibraryWorkspace({
|
|
|
2168
2181
|
)}
|
|
2169
2182
|
</div>
|
|
2170
2183
|
) : (
|
|
2171
|
-
<EmptyLibraryStarter onCreateBlank={
|
|
2184
|
+
<EmptyLibraryStarter onCreateBlank={handleCreateKit} />
|
|
2172
2185
|
)}
|
|
2173
2186
|
</div>
|
|
2174
2187
|
</section>
|
|
2175
|
-
<CreateLibraryDialog
|
|
2176
|
-
open={createOpen}
|
|
2177
|
-
onOpenChange={setCreateOpen}
|
|
2178
|
-
onCreated={(library) => navigate(`/library/${library.id}`)}
|
|
2179
|
-
/>
|
|
2180
2188
|
</div>
|
|
2181
2189
|
);
|
|
2182
2190
|
}
|
|
@@ -1333,8 +1333,16 @@ async function sweepOrphanedResumableSessions(): Promise<void> {
|
|
|
1333
1333
|
if (!key.startsWith(prefix)) continue;
|
|
1334
1334
|
const recordingId = key.slice(prefix.length);
|
|
1335
1335
|
if (!recordingId) continue;
|
|
1336
|
+
// `application_state.updated_at` is epoch-ms (a JS number on SQLite, a
|
|
1337
|
+
// BIGINT string on Postgres), but the staleness cutoffs below are ISO-8601.
|
|
1338
|
+
// Normalize to ISO before comparing — a raw epoch-ms value ("1753…") always
|
|
1339
|
+
// sorts lexically below any "2…" ISO date, which would flag every live
|
|
1340
|
+
// upload as stale and force-fail it.
|
|
1341
|
+
const sessionUpdatedAtMs = Number(row.updated_at);
|
|
1336
1342
|
const sessionUpdatedAt =
|
|
1337
|
-
|
|
1343
|
+
Number.isFinite(sessionUpdatedAtMs) && sessionUpdatedAtMs > 0
|
|
1344
|
+
? new Date(sessionUpdatedAtMs).toISOString()
|
|
1345
|
+
: "";
|
|
1338
1346
|
|
|
1339
1347
|
let shouldSweep = false;
|
|
1340
1348
|
try {
|
|
@@ -109,8 +109,11 @@ ladder.
|
|
|
109
109
|
`design-systems` skill's "Import from Figma" section.
|
|
110
110
|
- Uploading a raw `.fig` file in the Design editor's Import panel decodes the
|
|
111
111
|
container/Kiwi document locally into editable screens — no Builder
|
|
112
|
-
connection needed — and
|
|
113
|
-
|
|
112
|
+
connection needed — and accepts an optional Figma frame URL alongside the
|
|
113
|
+
file. When the URL contains a `node-id`, only that frame or its containing
|
|
114
|
+
top-level frame is imported; a mismatch falls back to all frames. This path
|
|
115
|
+
uses no Figma REST API quota and is scoped to screens only; it never creates
|
|
116
|
+
or updates a design system. This is separate from uploading `.fig` on the
|
|
114
117
|
Design System Setup page, which still indexes tokens/brand-kit data through
|
|
115
118
|
Builder and does not parse `.fig` locally. See the `design-systems` skill
|
|
116
119
|
for both paths.
|
|
@@ -118,9 +121,19 @@ ladder.
|
|
|
118
121
|
`figmeta.selectedNodeData`; `import-figma-clipboard` uses those before any
|
|
119
122
|
heuristic matching and supports multi-selection. Clipboard metadata is not a
|
|
120
123
|
public Figma contract, so a copied frame link remains the stable exact path
|
|
121
|
-
if Figma changes that field. Without a token,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
if Figma changes that field. Without a token, `import-figma-clipboard` falls
|
|
125
|
+
back to a local Kiwi binary decode: geometry, auto-layout, text, solid fills,
|
|
126
|
+
and strokes are editable immediately; image fills become annotated
|
|
127
|
+
placeholders (`data-figma-image-ref="<sha1>"`) that can be filled in later two
|
|
128
|
+
ways. (1) Token-free: upload the original `.fig` — the paste-result dialog's
|
|
129
|
+
"Fill images from .fig" option, or `hydrateFileIds` on the `.fig` upload route
|
|
130
|
+
(`/api/import-design-file`) — which matches each placeholder's SHA-1 hash to
|
|
131
|
+
the `.fig`'s embedded `images/` bytes, mirrors them to durable storage, and
|
|
132
|
+
needs no Figma API. (2) With a token: `hydrate-figma-paste-images` resolves the
|
|
133
|
+
same placeholders through Figma's REST image endpoint. This is not a
|
|
134
|
+
full-fidelity import — report which image fills are still unresolved and offer
|
|
135
|
+
to hydrate them. Clipboard paste is the fast no-quota path; the original `.fig`
|
|
136
|
+
is what carries the real image bytes for both upload and paste-hydration.
|
|
124
137
|
- For "what's in this Figma file/frame?" or "show me a screenshot of this
|
|
125
138
|
frame" without importing anything, use `get-figma-design-context` — no
|
|
126
139
|
`nodeId` lists pages/top-level frames (like the official Figma MCP's
|
|
@@ -15,18 +15,19 @@ product must report them instead of claiming success.
|
|
|
15
15
|
|
|
16
16
|
## Capability matrix
|
|
17
17
|
|
|
18
|
-
| Workflow or feature | Current behavior
|
|
19
|
-
| ---------------------------- |
|
|
20
|
-
| Figma frame URL / file key | Reads the exact node through `file_content:read`, converts it to a new Design screen, mirrors expiring images into durable storage, and returns a per-node fidelity report.
|
|
21
|
-
| Figma URL without a node id | Imports the first top-level object on the first page. A specific frame URL is recommended for deterministic results.
|
|
22
|
-
| Figma branch URL | Uses the branch key and imports that branch's node.
|
|
23
|
-
| Figma clipboard to Design | Uses private `figmeta.selectedNodeData` ids when present, then the same REST converter.
|
|
24
|
-
| `.fig` upload | Bounded best-effort decoding of known Kiwi/ZIP variants into editable HTML. Embedded images are moved to durable storage.
|
|
25
|
-
|
|
|
26
|
-
| Design
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
18
|
+
| Workflow or feature | Current behavior | Fidelity | Required verification |
|
|
19
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| Figma frame URL / file key | Reads the exact node through `file_content:read`, converts it to a new Design screen, mirrors expiring images into durable storage, and returns a per-node fidelity report. | Mixed; see node matrix below. | REST fixture, authenticated file, screenshot comparison. |
|
|
21
|
+
| Figma URL without a node id | Imports the first top-level object on the first page. A specific frame URL is recommended for deterministic results. | Same as node import. | Multi-page and empty-page fixtures. |
|
|
22
|
+
| Figma branch URL | Uses the branch key and imports that branch's node. | Same as node import. | Main/branch pair with divergent content. |
|
|
23
|
+
| Figma clipboard to Design | Uses private `figmeta.selectedNodeData` ids when present, then the same REST converter. With a token: full fidelity matching `import-figma-frame`. Without a token: local Kiwi binary decode — geometry, auto-layout, text, solid fills, and strokes are editable; image fills are stamped with `data-figma-image-ref="<sha1>"` placeholders and can be resolved retroactively two ways: token-free by uploading the original `.fig` (the paste dialog's "Fill images from .fig" / `hydrateFileIds` on the `.fig` upload route), which matches each placeholder hash to the `.fig`'s embedded image bytes; or with a token via `hydrate-figma-paste-images`. | Exact selection identity while Figma's private metadata shape remains compatible; node fidelity is mixed. No-token imports resolve images retroactively — from the `.fig` (no quota) or a connected token. | Real Chrome copy from single, multi, nested, and 100+ node selections; token-less copy followed by `.fig` hydration and by deferred token connect, verifying image resolution both ways. |
|
|
24
|
+
| `.fig` upload | Bounded best-effort decoding of known Kiwi/ZIP variants into editable HTML. Embedded images are moved to durable storage. Optionally accepts a Figma frame URL: when its `node-id` matches the decoded file, Design imports only that top-level frame (or its ancestor for a nested node); a mismatch imports all frames with a warning. No Figma REST API calls are made. | Experimental. The format is proprietary and has no compatibility guarantee. | Corpus of real files from multiple Figma versions; never only generated containers. |
|
|
25
|
+
| `.fig` upload + frame URL | Accepts an optional Figma frame link. Normalizes the node-id and matches the decoded .fig GUID (`sessionID:localID`) to the matching top-level frame. Nested node IDs resolve to their top-level frame. On mismatch, all frames are imported. No Figma API quota is used. | Best-effort. The GUID mapping is reliable for frames in the same file but undocumented — test with real files before relying on it. | Real .fig/frame-link pairs from Figma across file versions. |
|
|
26
|
+
| Design to Figma clipboard | Copies an SVG built from the live rendered DOM. Figma imports supported SVG primitives as editable layers. | Visual/vector handoff, not a native semantic round trip. Auto layout, variables, components, prototypes, HTML state, and code identity are not recreated by SVG. | Paste into real Figma and inspect layer types, text, images, effects, clipping, and bounds. |
|
|
27
|
+
| Design SVG download | Same conversion as clipboard, with a server-render fallback when a live DOM is unavailable. | Same SVG limits; the export report lists approximations and omissions. | Live and server paths, selected layer and whole screen. |
|
|
28
|
+
| Native Design to Figma write | Use Figma's official MCP `use_figma` write-to-canvas path when the connected client/account supports it. | Native Figma structures, subject to Figma MCP beta limitations and permissions. | Full-seat/edit-permission account and a real destination file. |
|
|
29
|
+
| `.fig` download | Not supported. There is no documented public `.fig` authoring contract. | Unsupported. | Do not label SVG/ZIP as `.fig`. |
|
|
30
|
+
| Open-ended Figma chat | Provider catalog/docs/request expose the REST surface allowed by the user's scoped token; non-read calls require approval. Native canvas authoring requires official Figma MCP, not a personal access token alone. | Endpoint-dependent. | Read scopes, expired/revoked token, rate limiting, Enterprise-variable permissions, MCP connection. |
|
|
30
31
|
|
|
31
32
|
## REST node conversion matrix
|
|
32
33
|
|
|
@@ -54,6 +55,18 @@ product must report them instead of claiming success.
|
|
|
54
55
|
| Videos, emoji paints, FigJam-only and unknown node types | Rendered fallback when Figma can render the node. | Visual fallback only. |
|
|
55
56
|
| Hidden or 0%-opacity subtrees | Omitted without downloading their assets. | Visually exact and avoids unnecessary work. |
|
|
56
57
|
|
|
58
|
+
## Figma REST rate limits
|
|
59
|
+
|
|
60
|
+
- Viewer and Collab seats may receive up to 6 Tier 1 requests per month for
|
|
61
|
+
file, node, and image endpoints. The actual limit may be lower.
|
|
62
|
+
- Dev and Full seats receive 10–20 Tier 1 requests per minute, depending on the
|
|
63
|
+
resource's plan.
|
|
64
|
+
- On HTTP 429, Figma returns `Retry-After` in seconds plus
|
|
65
|
+
`X-Figma-Plan-Tier`, `X-Figma-Rate-Limit-Type`, and
|
|
66
|
+
`X-Figma-Upgrade-Link` metadata.
|
|
67
|
+
- Figma does not expose a requests-remaining counter.
|
|
68
|
+
- Clipboard paste and `.fig` upload are zero-quota local alternatives.
|
|
69
|
+
|
|
57
70
|
## Safety and scale limits
|
|
58
71
|
|
|
59
72
|
- REST responses are capped at 4 MB. Multi-selection requests split
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retroactively resolve unresolved Figma image fills for a screen that was
|
|
3
|
+
* imported via the local-kiwi clipboard path, using Figma's REST image
|
|
4
|
+
* endpoint (requires a saved FIGMA_ACCESS_TOKEN).
|
|
5
|
+
*
|
|
6
|
+
* When a Figma paste is imported without an access token, IMAGE fills render as
|
|
7
|
+
* `url("about:blank")` placeholders. The originating elements are annotated
|
|
8
|
+
* with `data-figma-image-ref="hash1 hash2 …"` so this action can find them
|
|
9
|
+
* without a full re-parse. Each Nth hash in the attribute maps to the Nth
|
|
10
|
+
* `url("about:blank")` occurrence in the element's style attribute.
|
|
11
|
+
*
|
|
12
|
+
* The read → collect → resolve → persist pipeline lives in
|
|
13
|
+
* `server/lib/figma-image-hydration.ts` and is shared with the token-free
|
|
14
|
+
* `.fig` hydration path (see `hydrateFileImagesFromFig`). This action only
|
|
15
|
+
* supplies the REST resolver.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { defineAction } from "@agent-native/core";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
applyHydration,
|
|
23
|
+
collectImageRefHashes,
|
|
24
|
+
hydrateImageRefsInHtml,
|
|
25
|
+
loadHydratableFile,
|
|
26
|
+
} from "../server/lib/figma-image-hydration.js";
|
|
27
|
+
import { resolveImageFillRefs } from "../server/lib/figma-node-import.js";
|
|
28
|
+
import { readLiveSourceFile } from "../server/source-workspace.js";
|
|
29
|
+
|
|
30
|
+
// Re-exported for direct unit testing of the pure HTML helpers.
|
|
31
|
+
export { collectImageRefHashes, hydrateImageRefsInHtml };
|
|
32
|
+
|
|
33
|
+
export default defineAction({
|
|
34
|
+
description:
|
|
35
|
+
'Retroactively resolve unresolved Figma image fills for a screen imported via the no-token local-kiwi path (import-figma-clipboard returned strategy:"localKiwi"). Requires a saved FIGMA_ACCESS_TOKEN. Fetches CDN URLs from Figma\'s /files/:key/images endpoint, mirrors them to durable blob storage, and replaces every url("about:blank") placeholder stamped by the local-kiwi decoder with the real durable URL. Fully resolved elements have their data-figma-image-ref annotation removed; partially resolved elements retain it for a future retry. Returns resolved/missing/skipped counts. Call after connecting Figma in Settings to fill in images from a no-token paste. For a token-free path when the original .fig file is available, upload the .fig with hydrateFileIds via /api/import-design-file instead.',
|
|
36
|
+
schema: z.object({
|
|
37
|
+
fileId: z
|
|
38
|
+
.string()
|
|
39
|
+
.describe(
|
|
40
|
+
"ID of the design_files row to hydrate. Use the fileId returned by import-figma-clipboard.",
|
|
41
|
+
),
|
|
42
|
+
}),
|
|
43
|
+
run: async ({ fileId }) => {
|
|
44
|
+
const { workspaceFile, designId, figmaFileKey } =
|
|
45
|
+
await loadHydratableFile(fileId);
|
|
46
|
+
|
|
47
|
+
if (!figmaFileKey) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`No Figma file key found for file ${fileId}. This file may not have been imported via a Figma clipboard paste.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const live = await readLiveSourceFile(workspaceFile);
|
|
54
|
+
|
|
55
|
+
const hashesToResolve = collectImageRefHashes(live.content);
|
|
56
|
+
if (hashesToResolve.length === 0) {
|
|
57
|
+
return {
|
|
58
|
+
fileId,
|
|
59
|
+
resolved: 0,
|
|
60
|
+
missing: 0,
|
|
61
|
+
skipped: 0,
|
|
62
|
+
message: "No unresolved image refs found in this file.",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let resolvedUrls: Map<string, string>;
|
|
67
|
+
try {
|
|
68
|
+
resolvedUrls = await resolveImageFillRefs(figmaFileKey, hashesToResolve);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
71
|
+
if (/quota cooldown|provider.*quota/i.test(msg)) {
|
|
72
|
+
const retryAfterSeconds =
|
|
73
|
+
(err as { retryAfterSeconds?: number }).retryAfterSeconds ?? 0;
|
|
74
|
+
const waitHint =
|
|
75
|
+
retryAfterSeconds > 0
|
|
76
|
+
? retryAfterSeconds >= 60
|
|
77
|
+
? `${Math.ceil(retryAfterSeconds / 60)} min`
|
|
78
|
+
: `${retryAfterSeconds}s`
|
|
79
|
+
: "~1 min";
|
|
80
|
+
throw Object.assign(
|
|
81
|
+
new Error(`Figma API rate limited — try again in ${waitHint}.`),
|
|
82
|
+
{ statusCode: 429 },
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (resolvedUrls.size === 0) {
|
|
89
|
+
return {
|
|
90
|
+
fileId,
|
|
91
|
+
resolved: 0,
|
|
92
|
+
missing: hashesToResolve.length,
|
|
93
|
+
skipped: 0,
|
|
94
|
+
message: `Figma returned no image URLs for ${hashesToResolve.length} hash${hashesToResolve.length === 1 ? "" : "es"}. The images may have been deleted from the Figma file or the access token lacks file_content:read scope.`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const result = await applyHydration({
|
|
99
|
+
file: workspaceFile,
|
|
100
|
+
designId,
|
|
101
|
+
fileId,
|
|
102
|
+
liveContent: live.content,
|
|
103
|
+
liveVersionHash: live.versionHash,
|
|
104
|
+
requestedHashes: hashesToResolve,
|
|
105
|
+
resolvedUrls,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
fileId,
|
|
110
|
+
resolved: result.resolved,
|
|
111
|
+
missing: result.missing,
|
|
112
|
+
skipped: result.skipped,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
});
|