@agent-native/core 0.80.11 → 0.81.0
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 +47 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/design-connect.ts +419 -19
- package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
- package/corpus/core/src/client/chat-view-transition.ts +33 -2
- package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
- package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
- package/corpus/core/src/db/client.ts +29 -2
- package/corpus/core/src/provider-api/index.ts +29 -0
- package/corpus/core/src/server/core-routes-plugin.ts +58 -20
- package/corpus/core/src/styles/agent-native.css +54 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
- package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
- package/corpus/templates/analytics/app/global.css +8 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
- package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
- package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
- package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
- package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
- package/corpus/templates/assets/app/global.css +15 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +90 -16
- package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
- package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
- package/corpus/templates/design/AGENTS.md +27 -0
- package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
- package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
- package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
- package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
- package/corpus/templates/design/actions/capture-design-state.ts +27 -4
- package/corpus/templates/design/actions/connect-localhost.ts +59 -22
- package/corpus/templates/design/actions/export-zip.ts +23 -8
- package/corpus/templates/design/actions/generate-design.ts +53 -1
- package/corpus/templates/design/actions/get-component-details.ts +5 -19
- package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
- package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
- package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
- package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
- package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
- package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
- package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
- package/corpus/templates/design/actions/navigate.ts +18 -3
- package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
- package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
- package/corpus/templates/design/actions/provider-api-request.ts +3 -3
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
- package/corpus/templates/design/actions/run-design-audit.ts +53 -4
- package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
- package/corpus/templates/design/actions/view-screen.ts +1 -1
- package/corpus/templates/design/actions/write-local-file.ts +213 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
- package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
- package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
- package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
- package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
- package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
- package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
- package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/global.css +7 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/design/app/i18n-data.ts +358 -0
- package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
- package/corpus/templates/design/app/pages/Index.tsx +5 -1
- package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
- package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +168 -65
- package/corpus/templates/design/package.json +3 -1
- package/corpus/templates/design/server/db/schema.ts +23 -0
- package/corpus/templates/design/server/handlers/uploads.ts +22 -1
- package/corpus/templates/design/server/lib/provider-api.ts +7 -4
- package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
- package/corpus/templates/design/server/plugins/db.ts +39 -0
- package/corpus/templates/design/server/register-secrets.ts +38 -0
- package/corpus/templates/design/shared/board-file.ts +228 -0
- package/corpus/templates/design/shared/board-objects.ts +288 -0
- package/corpus/templates/design/shared/code-layer.ts +52 -0
- package/corpus/templates/design/shared/motion-compiler.ts +58 -7
- package/corpus/templates/design/shared/motion-timeline.ts +2 -2
- package/corpus/templates/design/tsconfig.json +2 -1
- package/corpus/templates/slides/AGENTS.md +9 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
- package/corpus/templates/slides/app/vite-env.d.ts +18 -0
- package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/vite.config.ts +1 -0
- package/dist/cli/design-connect.d.ts +25 -0
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +357 -18
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +1 -0
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +5 -5
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat-view-transition.d.ts +6 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -1
- package/dist/client/chat-view-transition.js +23 -2
- package/dist/client/chat-view-transition.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +3 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -4
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.js +3 -3
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
- package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
- package/dist/client/use-agent-chat-home-handoff.js +20 -6
- package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +28 -2
- package/dist/db/client.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/index.d.ts +5 -4
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -0
- package/dist/provider-api/index.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/core-routes-plugin.d.ts +25 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +43 -18
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +54 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -41,6 +41,41 @@ function normalizeUrl(value: string, label: string): string {
|
|
|
41
41
|
return parsed.toString().replace(/\/$/, "");
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
function isLoopbackHostname(hostname: string): boolean {
|
|
45
|
+
const normalized = hostname.toLowerCase();
|
|
46
|
+
if (
|
|
47
|
+
normalized === "localhost" ||
|
|
48
|
+
normalized === "::1" ||
|
|
49
|
+
normalized === "[::1]"
|
|
50
|
+
) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
const parts = normalized.split(".");
|
|
54
|
+
return (
|
|
55
|
+
parts.length === 4 &&
|
|
56
|
+
parts[0] === "127" &&
|
|
57
|
+
parts.every((part) => /^\d+$/.test(part) && Number(part) <= 255)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeBridgeUrl(value: string): string {
|
|
62
|
+
const normalized = normalizeUrl(value, "bridgeUrl");
|
|
63
|
+
const parsed = new URL(normalized);
|
|
64
|
+
if (parsed.username || parsed.password) {
|
|
65
|
+
throw new Error("bridgeUrl must not include credentials");
|
|
66
|
+
}
|
|
67
|
+
if (parsed.pathname !== "/" && parsed.pathname !== "") {
|
|
68
|
+
throw new Error("bridgeUrl must not include a path");
|
|
69
|
+
}
|
|
70
|
+
if (!isLoopbackHostname(parsed.hostname)) {
|
|
71
|
+
throw new Error("bridgeUrl must use localhost or a loopback IP address");
|
|
72
|
+
}
|
|
73
|
+
parsed.search = "";
|
|
74
|
+
parsed.hash = "";
|
|
75
|
+
parsed.pathname = "";
|
|
76
|
+
return parsed.toString().replace(/\/$/, "");
|
|
77
|
+
}
|
|
78
|
+
|
|
44
79
|
export default defineAction({
|
|
45
80
|
description:
|
|
46
81
|
"Register or refresh a localhost Design source connection produced by `agent-native design connect`. Stores the dev server URL, bridge URL, route manifest, and operation capabilities so the UI can later list local-code artboards.",
|
|
@@ -77,6 +112,12 @@ export default defineAction({
|
|
|
77
112
|
.array(capabilitySchema)
|
|
78
113
|
.optional()
|
|
79
114
|
.describe("Bridge operation capabilities."),
|
|
115
|
+
bridgeToken: z
|
|
116
|
+
.string()
|
|
117
|
+
.optional()
|
|
118
|
+
.describe(
|
|
119
|
+
"The bridge's real auth token minted at bridge start. Stored on the connection so grant-localhost-write-consent can read it without minting its own.",
|
|
120
|
+
),
|
|
80
121
|
status: z
|
|
81
122
|
.enum(["connected", "detected", "manual", "error"])
|
|
82
123
|
.optional()
|
|
@@ -91,7 +132,7 @@ export default defineAction({
|
|
|
91
132
|
const db = getDb();
|
|
92
133
|
const devServerUrl = normalizeUrl(args.devServerUrl, "devServerUrl");
|
|
93
134
|
const bridgeUrl = args.bridgeUrl
|
|
94
|
-
?
|
|
135
|
+
? normalizeBridgeUrl(args.bridgeUrl)
|
|
95
136
|
: undefined;
|
|
96
137
|
const rawRoutes = args.routeManifest?.routes ?? args.routes ?? [];
|
|
97
138
|
const routes = rawRoutes.map((route) => ({
|
|
@@ -115,18 +156,24 @@ export default defineAction({
|
|
|
115
156
|
args.capabilities ??
|
|
116
157
|
DESIGN_BRIDGE_OPERATIONS.map((operation) => ({
|
|
117
158
|
operation,
|
|
118
|
-
status:
|
|
119
|
-
operation === "readFile" ||
|
|
120
|
-
operation === "applyEdit" ||
|
|
121
|
-
operation === "writeFile"
|
|
122
|
-
? ("planned" as const)
|
|
123
|
-
: ("available" as const),
|
|
124
|
-
reason:
|
|
125
|
-
operation === "writeFile"
|
|
126
|
-
? "Local file writes require the next bridge hardening pass."
|
|
127
|
-
: undefined,
|
|
159
|
+
status: "available" as const,
|
|
128
160
|
}));
|
|
129
161
|
|
|
162
|
+
const existing = await db
|
|
163
|
+
.select({
|
|
164
|
+
id: schema.designLocalhostConnections.id,
|
|
165
|
+
bridgeToken: schema.designLocalhostConnections.bridgeToken,
|
|
166
|
+
})
|
|
167
|
+
.from(schema.designLocalhostConnections)
|
|
168
|
+
.where(
|
|
169
|
+
and(
|
|
170
|
+
eq(schema.designLocalhostConnections.id, id),
|
|
171
|
+
eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
.limit(1);
|
|
175
|
+
|
|
176
|
+
const nextBridgeToken = args.bridgeToken?.trim() || undefined;
|
|
130
177
|
const values = {
|
|
131
178
|
id,
|
|
132
179
|
name: args.name ?? new URL(devServerUrl).host,
|
|
@@ -136,6 +183,7 @@ export default defineAction({
|
|
|
136
183
|
rootPath: routeManifest.rootPath ?? null,
|
|
137
184
|
routeManifest: JSON.stringify(routeManifest),
|
|
138
185
|
capabilities: JSON.stringify(capabilities),
|
|
186
|
+
bridgeToken: nextBridgeToken ?? existing[0]?.bridgeToken ?? null,
|
|
139
187
|
status: args.status,
|
|
140
188
|
lastSeenAt: now,
|
|
141
189
|
ownerEmail,
|
|
@@ -143,17 +191,6 @@ export default defineAction({
|
|
|
143
191
|
updatedAt: now,
|
|
144
192
|
};
|
|
145
193
|
|
|
146
|
-
const existing = await db
|
|
147
|
-
.select({ id: schema.designLocalhostConnections.id })
|
|
148
|
-
.from(schema.designLocalhostConnections)
|
|
149
|
-
.where(
|
|
150
|
-
and(
|
|
151
|
-
eq(schema.designLocalhostConnections.id, id),
|
|
152
|
-
eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
|
|
153
|
-
),
|
|
154
|
-
)
|
|
155
|
-
.limit(1);
|
|
156
|
-
|
|
157
194
|
if (existing[0]) {
|
|
158
195
|
await db
|
|
159
196
|
.update(schema.designLocalhostConnections)
|
|
@@ -10,6 +10,17 @@ import {
|
|
|
10
10
|
} from "../server/lib/design-export.js";
|
|
11
11
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
12
12
|
|
|
13
|
+
const METADATA_ARCHIVE_DIR = "agent-native-metadata";
|
|
14
|
+
|
|
15
|
+
function safeArchivePath(filename: string, fallback: string): string {
|
|
16
|
+
const normalized = filename
|
|
17
|
+
.replace(/\\/g, "/")
|
|
18
|
+
.split("/")
|
|
19
|
+
.filter((part) => part && part !== "." && part !== "..")
|
|
20
|
+
.join("/");
|
|
21
|
+
return normalized || fallback;
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
export default defineAction({
|
|
14
25
|
description:
|
|
15
26
|
"Export a design project as a ZIP file containing all design files and a README. " +
|
|
@@ -34,7 +45,8 @@ export default defineAction({
|
|
|
34
45
|
const JSZip = (await import("jszip")).default;
|
|
35
46
|
const zip = new JSZip();
|
|
36
47
|
|
|
37
|
-
// Add
|
|
48
|
+
// Add generated metadata under a reserved folder so valid design files named
|
|
49
|
+
// README.md or design-data.json can still export at the project root.
|
|
38
50
|
const readme = [
|
|
39
51
|
`# ${row.title}`,
|
|
40
52
|
"",
|
|
@@ -48,18 +60,21 @@ export default defineAction({
|
|
|
48
60
|
...files.map((f) => `- ${f.filename} (${f.fileType})`),
|
|
49
61
|
].join("\n");
|
|
50
62
|
|
|
51
|
-
zip.file(
|
|
63
|
+
zip.file(`${METADATA_ARCHIVE_DIR}/README.md`, readme);
|
|
52
64
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
// Preserve design-relative paths so exported HTML keeps working with
|
|
66
|
+
// sibling CSS/assets. Strip traversal segments defensively for legacy rows.
|
|
67
|
+
for (const [index, file] of files.entries()) {
|
|
68
|
+
const filename = safeArchivePath(
|
|
69
|
+
file.filename,
|
|
70
|
+
`design-file-${index + 1}.txt`,
|
|
71
|
+
);
|
|
72
|
+
zip.file(filename, file.content ?? "");
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
// Add design data if present
|
|
61
76
|
if (row.data) {
|
|
62
|
-
zip.file(
|
|
77
|
+
zip.file(`${METADATA_ARCHIVE_DIR}/design-data.json`, row.data);
|
|
63
78
|
}
|
|
64
79
|
|
|
65
80
|
// Generate ZIP
|
|
@@ -66,7 +66,48 @@ async function updateGenerationSessionForSavedFiles(
|
|
|
66
66
|
await writeAppState(key, nextSession as unknown as Record<string, unknown>);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
const generateDesignAgentParameters = {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
designId: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Existing design project ID to save generated content to.",
|
|
75
|
+
},
|
|
76
|
+
prompt: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "The user's generation prompt.",
|
|
79
|
+
},
|
|
80
|
+
files: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description:
|
|
83
|
+
"JSON array of files to save. Pass one compact, complete, renderable index.html first, e.g. " +
|
|
84
|
+
'[{"filename":"index.html","fileType":"html","content":"<!doctype html>..."}].',
|
|
85
|
+
},
|
|
86
|
+
designSystemId: {
|
|
87
|
+
type: ["string", "null"],
|
|
88
|
+
description:
|
|
89
|
+
"Optional design system ID used for generation. Pass null to unlink.",
|
|
90
|
+
},
|
|
91
|
+
projectType: {
|
|
92
|
+
type: "string",
|
|
93
|
+
enum: ["prototype", "other"],
|
|
94
|
+
description: "Optional project type hint.",
|
|
95
|
+
},
|
|
96
|
+
tweaks: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description:
|
|
99
|
+
"Optional JSON array of tweak definitions. Omit unless the HTML uses matching CSS variables.",
|
|
100
|
+
},
|
|
101
|
+
canvasFrames: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description:
|
|
104
|
+
"Optional JSON array of overview-canvas placements keyed by filename or fileId.",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ["designId", "prompt", "files"],
|
|
108
|
+
} as const;
|
|
109
|
+
|
|
110
|
+
const generateDesignAction = defineAction({
|
|
70
111
|
description:
|
|
71
112
|
"Save generated design content to a design project. " +
|
|
72
113
|
"The agent calls this after generating HTML/CSS/JSX content to persist it " +
|
|
@@ -438,3 +479,14 @@ export default defineAction({
|
|
|
438
479
|
};
|
|
439
480
|
},
|
|
440
481
|
});
|
|
482
|
+
|
|
483
|
+
// Keep rich Zod validation for every runtime caller, but present a lean
|
|
484
|
+
// string-JSON schema to native LLM tools. Anthropic models are prone to empty
|
|
485
|
+
// object calls against this action's deeply nested array/object schema.
|
|
486
|
+
export default {
|
|
487
|
+
...generateDesignAction,
|
|
488
|
+
tool: {
|
|
489
|
+
...generateDesignAction.tool,
|
|
490
|
+
parameters: generateDesignAgentParameters,
|
|
491
|
+
},
|
|
492
|
+
};
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { defineAction } from "@agent-native/core";
|
|
18
|
-
import { getText, hasCollabState } from "@agent-native/core/collab";
|
|
19
18
|
import { accessFilter, resolveAccess } from "@agent-native/core/sharing";
|
|
20
19
|
import { and, eq } from "drizzle-orm";
|
|
21
20
|
import { z } from "zod";
|
|
@@ -34,23 +33,6 @@ import {
|
|
|
34
33
|
import { hasCapability } from "../shared/design-source-capabilities.js";
|
|
35
34
|
import { normalizeDesignSourceType } from "../shared/source-mode.js";
|
|
36
35
|
|
|
37
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
async function liveContent(
|
|
40
|
-
fileId: string,
|
|
41
|
-
storedContent: string,
|
|
42
|
-
): Promise<string> {
|
|
43
|
-
try {
|
|
44
|
-
if (await hasCollabState(fileId)) {
|
|
45
|
-
const live = await getText(fileId, "content");
|
|
46
|
-
if (typeof live === "string") return live;
|
|
47
|
-
}
|
|
48
|
-
} catch {
|
|
49
|
-
// Collab reads are best-effort; SQL content is the fallback.
|
|
50
|
-
}
|
|
51
|
-
return storedContent;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
36
|
function parseJson<T>(raw: string | null | undefined, fallback: T): T {
|
|
55
37
|
if (!raw) return fallback;
|
|
56
38
|
try {
|
|
@@ -148,7 +130,11 @@ export default defineAction({
|
|
|
148
130
|
|
|
149
131
|
if (!file) throw new Error("Design HTML file not found.");
|
|
150
132
|
|
|
151
|
-
|
|
133
|
+
// Use the durable SQL source for component prop reads. A connected editor
|
|
134
|
+
// can briefly hold an older Yjs text snapshot while server-side prop writes
|
|
135
|
+
// have already updated SQL; preferring collab here makes the inspector
|
|
136
|
+
// rehydrate stale props even though the canvas renders the saved source.
|
|
137
|
+
const html = file.content ?? "";
|
|
152
138
|
|
|
153
139
|
// ── Projection lookup ────────────────────────────────────────────────────
|
|
154
140
|
const codeLayerSource: CodeLayerSource = {
|
|
@@ -1,17 +1,109 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getText, hasCollabState } from "@agent-native/core/collab";
|
|
2
3
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
3
|
-
import { and, eq } from "drizzle-orm";
|
|
4
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
|
|
6
7
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
8
|
import "../server/db/index.js"; // ensure registerShareableResource runs
|
|
9
|
+
import {
|
|
10
|
+
extractManagedMotionCss,
|
|
11
|
+
hashCss,
|
|
12
|
+
parse,
|
|
13
|
+
} from "../shared/motion-compiler.js";
|
|
14
|
+
import type { MotionTrack } from "../shared/motion-timeline.js";
|
|
15
|
+
|
|
16
|
+
type TimelineSource = "stored" | "recovered-css" | "stored-css-drift";
|
|
17
|
+
|
|
18
|
+
interface TimelineResult {
|
|
19
|
+
id: string | null;
|
|
20
|
+
designId: string;
|
|
21
|
+
sourceRef: string | null;
|
|
22
|
+
filePath: string | null;
|
|
23
|
+
tracks: unknown;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
defaultEase: string;
|
|
26
|
+
compiledHash: string | null;
|
|
27
|
+
cssHash?: string | null;
|
|
28
|
+
source: TimelineSource;
|
|
29
|
+
createdAt: string | null;
|
|
30
|
+
updatedAt: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function liveFileContent(
|
|
34
|
+
fileId: string,
|
|
35
|
+
storedContent: string,
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
try {
|
|
38
|
+
if (await hasCollabState(fileId)) {
|
|
39
|
+
const live = await getText(fileId, "content");
|
|
40
|
+
if (typeof live === "string") return live;
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Best-effort; SQL is the fallback.
|
|
44
|
+
}
|
|
45
|
+
return storedContent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseTrackJson(raw: string | null): MotionTrack[] {
|
|
49
|
+
try {
|
|
50
|
+
const parsed: unknown = JSON.parse(raw ?? "[]");
|
|
51
|
+
return Array.isArray(parsed) ? (parsed as MotionTrack[]) : [];
|
|
52
|
+
} catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function readManagedCssForSource(args: {
|
|
58
|
+
designId: string;
|
|
59
|
+
sourceRef?: string;
|
|
60
|
+
}): Promise<{ css: string; hash: string; tracks: MotionTrack[] } | null> {
|
|
61
|
+
if (!args.sourceRef) return null;
|
|
62
|
+
|
|
63
|
+
const db = getDb();
|
|
64
|
+
const [file] = await db
|
|
65
|
+
.select({
|
|
66
|
+
id: schema.designFiles.id,
|
|
67
|
+
content: schema.designFiles.content,
|
|
68
|
+
})
|
|
69
|
+
.from(schema.designFiles)
|
|
70
|
+
.innerJoin(
|
|
71
|
+
schema.designs,
|
|
72
|
+
eq(schema.designFiles.designId, schema.designs.id),
|
|
73
|
+
)
|
|
74
|
+
.where(
|
|
75
|
+
and(
|
|
76
|
+
accessFilter(schema.designs, schema.designShares),
|
|
77
|
+
eq(schema.designFiles.designId, args.designId),
|
|
78
|
+
eq(schema.designFiles.id, args.sourceRef),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
.limit(1);
|
|
82
|
+
|
|
83
|
+
if (!file) return null;
|
|
84
|
+
|
|
85
|
+
const content = await liveFileContent(file.id, file.content ?? "");
|
|
86
|
+
const css = extractManagedMotionCss(content);
|
|
87
|
+
if (!css) return null;
|
|
88
|
+
|
|
89
|
+
const tracks = parse(css).filter(
|
|
90
|
+
(track) =>
|
|
91
|
+
track.keyframes.length > 0 &&
|
|
92
|
+
track.keyframes.every((keyframe) => keyframe.value.trim().length > 0),
|
|
93
|
+
);
|
|
94
|
+
if (tracks.length === 0) return null;
|
|
95
|
+
|
|
96
|
+
return { css, hash: hashCss(css), tracks };
|
|
97
|
+
}
|
|
8
98
|
|
|
9
99
|
export default defineAction({
|
|
10
100
|
description:
|
|
11
101
|
"Read one or all motion timelines for a design. " +
|
|
12
102
|
"Returns timeline metadata (id, sourceRef, filePath, durationMs, " +
|
|
13
103
|
"defaultEase, compiledHash) and the full tracks array (each track has " +
|
|
14
|
-
"targetNodeId, property, and keyframes).
|
|
104
|
+
"targetNodeId, property, and keyframes). If sourceRef points at a design " +
|
|
105
|
+
"file and the metadata is missing or stale, recovers editable tracks from " +
|
|
106
|
+
"the managed <style data-agent-native-motion> block. Read-only.",
|
|
15
107
|
readOnly: true,
|
|
16
108
|
http: { method: "GET" },
|
|
17
109
|
schema: z.object({
|
|
@@ -64,29 +156,62 @@ export default defineAction({
|
|
|
64
156
|
eq(schema.motionTimeline.designId, schema.designs.id),
|
|
65
157
|
)
|
|
66
158
|
.where(and(...conditions))
|
|
159
|
+
.orderBy(desc(schema.motionTimeline.updatedAt))
|
|
67
160
|
.limit(timelineId ? 1 : 100);
|
|
68
161
|
|
|
69
|
-
const timelines = rows.map((row) => {
|
|
70
|
-
let tracks: unknown = [];
|
|
71
|
-
try {
|
|
72
|
-
tracks = JSON.parse(row.tracks ?? "[]");
|
|
73
|
-
} catch {
|
|
74
|
-
tracks = [];
|
|
75
|
-
}
|
|
162
|
+
const timelines: TimelineResult[] = rows.map((row) => {
|
|
76
163
|
return {
|
|
77
164
|
id: row.id,
|
|
78
165
|
designId: row.designId,
|
|
79
166
|
sourceRef: row.sourceRef ?? null,
|
|
80
167
|
filePath: row.filePath ?? null,
|
|
81
|
-
tracks,
|
|
168
|
+
tracks: parseTrackJson(row.tracks),
|
|
82
169
|
durationMs: row.durationMs,
|
|
83
170
|
defaultEase: row.defaultEase,
|
|
84
171
|
compiledHash: row.compiledHash ?? null,
|
|
172
|
+
cssHash: null,
|
|
173
|
+
source: "stored",
|
|
85
174
|
createdAt: row.createdAt,
|
|
86
175
|
updatedAt: row.updatedAt,
|
|
87
176
|
};
|
|
88
177
|
});
|
|
89
178
|
|
|
179
|
+
const managedCss = timelineId
|
|
180
|
+
? null
|
|
181
|
+
: await readManagedCssForSource({ designId, sourceRef });
|
|
182
|
+
|
|
183
|
+
if (managedCss) {
|
|
184
|
+
const [first] = timelines;
|
|
185
|
+
if (!first) {
|
|
186
|
+
timelines.push({
|
|
187
|
+
id: null,
|
|
188
|
+
designId,
|
|
189
|
+
sourceRef: sourceRef ?? null,
|
|
190
|
+
filePath: null,
|
|
191
|
+
tracks: managedCss.tracks,
|
|
192
|
+
durationMs: 1000,
|
|
193
|
+
defaultEase: "ease",
|
|
194
|
+
compiledHash: managedCss.hash,
|
|
195
|
+
cssHash: managedCss.hash,
|
|
196
|
+
source: "recovered-css",
|
|
197
|
+
createdAt: null,
|
|
198
|
+
updatedAt: null,
|
|
199
|
+
});
|
|
200
|
+
} else if (first.compiledHash !== managedCss.hash) {
|
|
201
|
+
timelines[0] = {
|
|
202
|
+
...first,
|
|
203
|
+
tracks: managedCss.tracks,
|
|
204
|
+
cssHash: managedCss.hash,
|
|
205
|
+
source: "stored-css-drift",
|
|
206
|
+
};
|
|
207
|
+
} else {
|
|
208
|
+
timelines[0] = {
|
|
209
|
+
...first,
|
|
210
|
+
cssHash: managedCss.hash,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
90
215
|
if (timelineId) {
|
|
91
216
|
if (timelines.length === 0) {
|
|
92
217
|
throw new Error(`Motion timeline not found: ${timelineId}`);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server/request-context";
|
|
6
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
7
|
+
import { and, eq } from "drizzle-orm";
|
|
8
|
+
import { nanoid } from "nanoid";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
12
|
+
|
|
13
|
+
/** Grant expiry: 8 hours from mint time. */
|
|
14
|
+
const GRANT_TTL_MS = 8 * 60 * 60 * 1000;
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
description:
|
|
18
|
+
"Record the user's explicit consent to allow the agent to write local files " +
|
|
19
|
+
"for a specific design + localhost connection. The grant scopes writes to the " +
|
|
20
|
+
"connection's rootPath and expires after 8 hours. Requires editor access on the design. " +
|
|
21
|
+
"The LocalhostWriteConsentDialog calls this after the user clicks 'Allow writes'.",
|
|
22
|
+
schema: z.object({
|
|
23
|
+
designId: z.string().describe("Design ID."),
|
|
24
|
+
connectionId: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe("Localhost connection ID (from list-localhost-connections)."),
|
|
27
|
+
}),
|
|
28
|
+
run: async ({ designId, connectionId }) => {
|
|
29
|
+
await assertAccess("design", designId, "editor");
|
|
30
|
+
|
|
31
|
+
const ownerEmail = getRequestUserEmail();
|
|
32
|
+
if (!ownerEmail) throw new Error("no authenticated user");
|
|
33
|
+
const orgId = getRequestOrgId() ?? null;
|
|
34
|
+
|
|
35
|
+
const db = getDb();
|
|
36
|
+
|
|
37
|
+
// Fetch the connection to get rootPath and the real bridgeToken that the
|
|
38
|
+
// CLI registered when it started the bridge process.
|
|
39
|
+
const [connection] = await db
|
|
40
|
+
.select()
|
|
41
|
+
.from(schema.designLocalhostConnections)
|
|
42
|
+
.where(
|
|
43
|
+
and(
|
|
44
|
+
eq(schema.designLocalhostConnections.id, connectionId),
|
|
45
|
+
eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
.limit(1);
|
|
49
|
+
|
|
50
|
+
if (!connection) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Localhost connection "${connectionId}" not found for the current user.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rootPath = connection.rootPath;
|
|
57
|
+
if (!rootPath) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Connection "${connectionId}" has no rootPath. ` +
|
|
60
|
+
"Re-run `npx @agent-native/core@latest design connect` to record the root path.",
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const bridgeToken = connection.bridgeToken;
|
|
65
|
+
if (!bridgeToken) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Connection "${connectionId}" has no bridge token. ` +
|
|
68
|
+
"Re-run `npx @agent-native/core@latest design connect` so the CLI can register the real bridge token.",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const now = new Date();
|
|
73
|
+
const grantId = nanoid();
|
|
74
|
+
const grantedUntil = new Date(now.getTime() + GRANT_TTL_MS).toISOString();
|
|
75
|
+
const nowIso = now.toISOString();
|
|
76
|
+
|
|
77
|
+
// Upsert: if a grant already exists for this design+connection+user, replace it.
|
|
78
|
+
const [existing] = await db
|
|
79
|
+
.select({ id: schema.designLocalhostWriteGrants.id })
|
|
80
|
+
.from(schema.designLocalhostWriteGrants)
|
|
81
|
+
.where(
|
|
82
|
+
and(
|
|
83
|
+
eq(schema.designLocalhostWriteGrants.designId, designId),
|
|
84
|
+
eq(schema.designLocalhostWriteGrants.connectionId, connectionId),
|
|
85
|
+
eq(schema.designLocalhostWriteGrants.ownerEmail, ownerEmail),
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
.limit(1);
|
|
89
|
+
|
|
90
|
+
if (existing) {
|
|
91
|
+
await db
|
|
92
|
+
.update(schema.designLocalhostWriteGrants)
|
|
93
|
+
.set({ bridgeToken, grantedUntil, rootPath })
|
|
94
|
+
.where(eq(schema.designLocalhostWriteGrants.id, existing.id));
|
|
95
|
+
} else {
|
|
96
|
+
await db.insert(schema.designLocalhostWriteGrants).values({
|
|
97
|
+
id: grantId,
|
|
98
|
+
designId,
|
|
99
|
+
connectionId,
|
|
100
|
+
rootPath,
|
|
101
|
+
bridgeToken,
|
|
102
|
+
grantedUntil,
|
|
103
|
+
ownerEmail,
|
|
104
|
+
orgId,
|
|
105
|
+
createdAt: nowIso,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
grantId: existing?.id ?? grantId,
|
|
111
|
+
bridgeToken,
|
|
112
|
+
rootPath,
|
|
113
|
+
grantedUntil,
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
});
|