@agent-native/core 0.80.11 → 0.81.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.
Files changed (230) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +53 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/design-connect.ts +419 -19
  5. package/corpus/core/src/client/AgentPanel.tsx +33 -1
  6. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  7. package/corpus/core/src/client/chat/index.ts +1 -0
  8. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  9. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  10. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  11. package/corpus/core/src/client/index.ts +1 -0
  12. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  13. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  14. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  15. package/corpus/core/src/db/client.ts +29 -2
  16. package/corpus/core/src/provider-api/index.ts +29 -0
  17. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  18. package/corpus/core/src/styles/agent-native.css +54 -0
  19. package/corpus/core/src/usage/store.ts +2 -2
  20. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  21. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  22. package/corpus/templates/analytics/app/global.css +8 -0
  23. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  24. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  25. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  26. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  27. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  28. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  29. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  34. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  35. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  36. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  37. package/corpus/templates/assets/app/global.css +15 -0
  38. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  39. package/corpus/templates/assets/app/routes/library.tsx +90 -16
  40. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  41. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  42. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  43. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  44. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  45. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  46. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  47. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  48. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  49. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  50. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  51. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  52. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  53. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  54. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  55. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  56. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  57. package/corpus/templates/design/AGENTS.md +27 -0
  58. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  59. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  60. package/corpus/templates/design/actions/apply-component-prop-edit.ts +63 -20
  61. package/corpus/templates/design/actions/apply-motion-edit.ts +67 -69
  62. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  63. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  64. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  65. package/corpus/templates/design/actions/export-zip.ts +23 -8
  66. package/corpus/templates/design/actions/generate-design.ts +53 -1
  67. package/corpus/templates/design/actions/get-component-details.ts +5 -19
  68. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  69. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  70. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  71. package/corpus/templates/design/actions/index-design-tokens.ts +10 -1
  72. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  73. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  74. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  75. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  76. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  77. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  78. package/corpus/templates/design/actions/navigate.ts +18 -3
  79. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  80. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  81. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  82. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  83. package/corpus/templates/design/actions/run-design-audit.ts +53 -4
  84. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  85. package/corpus/templates/design/actions/view-screen.ts +1 -1
  86. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  87. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +230 -3308
  88. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  89. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +776 -287
  90. package/corpus/templates/design/app/components/design/EditPanel.tsx +212 -208
  91. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -2
  92. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  93. package/corpus/templates/design/app/components/design/MotionDock.tsx +235 -266
  94. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +490 -55
  95. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +43 -62
  96. package/corpus/templates/design/app/components/design/StatesPanel.tsx +34 -61
  97. package/corpus/templates/design/app/components/design/TokensPanel.tsx +251 -0
  98. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  99. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  100. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  101. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  102. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  103. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  104. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  105. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  106. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  107. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  108. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  109. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  110. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  111. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  112. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  113. package/corpus/templates/design/app/global.css +7 -0
  114. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  115. package/corpus/templates/design/app/i18n/zh-TW.ts +21 -0
  116. package/corpus/templates/design/app/i18n-data.ts +358 -0
  117. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  118. package/corpus/templates/design/app/pages/DesignEditor.tsx +2402 -499
  119. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  120. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  121. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  122. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  123. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  124. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  125. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  126. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  127. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  128. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  129. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  130. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  131. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  132. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  133. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  134. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  135. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  136. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  137. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  138. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  144. package/corpus/templates/design/e2e/helpers.ts +168 -65
  145. package/corpus/templates/design/package.json +3 -1
  146. package/corpus/templates/design/server/db/schema.ts +23 -0
  147. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  148. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  149. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  150. package/corpus/templates/design/server/plugins/db.ts +39 -0
  151. package/corpus/templates/design/server/register-secrets.ts +38 -0
  152. package/corpus/templates/design/shared/board-file.ts +228 -0
  153. package/corpus/templates/design/shared/board-objects.ts +288 -0
  154. package/corpus/templates/design/shared/code-layer.ts +52 -0
  155. package/corpus/templates/design/shared/motion-compiler.ts +58 -7
  156. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  157. package/corpus/templates/design/tsconfig.json +2 -1
  158. package/corpus/templates/slides/AGENTS.md +9 -0
  159. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  160. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  161. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  162. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  163. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  164. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  165. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  166. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  167. package/corpus/templates/slides/package.json +1 -0
  168. package/corpus/templates/slides/vite.config.ts +1 -0
  169. package/dist/cli/design-connect.d.ts +25 -0
  170. package/dist/cli/design-connect.d.ts.map +1 -1
  171. package/dist/cli/design-connect.js +357 -18
  172. package/dist/cli/design-connect.js.map +1 -1
  173. package/dist/client/AgentPanel.d.ts +1 -1
  174. package/dist/client/AgentPanel.d.ts.map +1 -1
  175. package/dist/client/AgentPanel.js +18 -3
  176. package/dist/client/AgentPanel.js.map +1 -1
  177. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  178. package/dist/client/ConnectBuilderCard.js +1 -0
  179. package/dist/client/ConnectBuilderCard.js.map +1 -1
  180. package/dist/client/chat/index.d.ts +1 -1
  181. package/dist/client/chat/index.d.ts.map +1 -1
  182. package/dist/client/chat/index.js +1 -1
  183. package/dist/client/chat/index.js.map +1 -1
  184. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  185. package/dist/client/chat/run-recovery.js +5 -5
  186. package/dist/client/chat/run-recovery.js.map +1 -1
  187. package/dist/client/chat-view-transition.d.ts +6 -0
  188. package/dist/client/chat-view-transition.d.ts.map +1 -1
  189. package/dist/client/chat-view-transition.js +23 -2
  190. package/dist/client/chat-view-transition.js.map +1 -1
  191. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  192. package/dist/client/composer/PromptComposer.js +3 -2
  193. package/dist/client/composer/PromptComposer.js.map +1 -1
  194. package/dist/client/index.d.ts +1 -1
  195. package/dist/client/index.d.ts.map +1 -1
  196. package/dist/client/index.js +1 -1
  197. package/dist/client/index.js.map +1 -1
  198. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  199. package/dist/client/org/OrgSwitcher.js +11 -4
  200. package/dist/client/org/OrgSwitcher.js.map +1 -1
  201. package/dist/client/sharing/ShareButton.js +3 -3
  202. package/dist/client/sharing/ShareButton.js.map +1 -1
  203. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  204. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  205. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  206. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  207. package/dist/collab/routes.d.ts +1 -1
  208. package/dist/db/client.d.ts +1 -0
  209. package/dist/db/client.d.ts.map +1 -1
  210. package/dist/db/client.js +28 -2
  211. package/dist/db/client.js.map +1 -1
  212. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  213. package/dist/notifications/routes.d.ts +2 -2
  214. package/dist/observability/routes.d.ts +8 -8
  215. package/dist/progress/routes.d.ts +1 -1
  216. package/dist/provider-api/index.d.ts +5 -4
  217. package/dist/provider-api/index.d.ts.map +1 -1
  218. package/dist/provider-api/index.js +28 -0
  219. package/dist/provider-api/index.js.map +1 -1
  220. package/dist/resources/handlers.d.ts +3 -3
  221. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  222. package/dist/server/core-routes-plugin.d.ts +25 -0
  223. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  224. package/dist/server/core-routes-plugin.js +43 -18
  225. package/dist/server/core-routes-plugin.js.map +1 -1
  226. package/dist/server/transcribe-voice.d.ts +1 -1
  227. package/dist/styles/agent-native.css +54 -0
  228. package/dist/usage/store.js +2 -2
  229. package/dist/usage/store.js.map +1 -1
  230. 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
- ? normalizeUrl(args.bridgeUrl, "bridgeUrl")
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 README
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("README.md", readme);
63
+ zip.file(`${METADATA_ARCHIVE_DIR}/README.md`, readme);
52
64
 
53
- // Add all design files organized by type
54
- for (const file of files) {
55
- const folder =
56
- file.fileType === "asset" ? "assets" : (file.fileType ?? "html");
57
- zip.file(`${folder}/${file.filename}`, file.content ?? "");
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("design-data.json", row.data);
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
- export default defineAction({
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
- const html = await liveContent(file.id, file.content ?? "");
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). Read-only.",
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
+ });