@agent-native/core 0.80.10 → 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.
Files changed (267) 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/agent/production-agent.ts +108 -15
  5. package/corpus/core/src/cli/design-connect.ts +419 -19
  6. package/corpus/core/src/client/AssistantChat.tsx +123 -26
  7. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  8. package/corpus/core/src/client/chat/index.ts +1 -0
  9. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  10. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  11. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  12. package/corpus/core/src/client/index.ts +1 -0
  13. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  14. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  15. package/corpus/core/src/client/sse-event-processor.ts +9 -0
  16. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  17. package/corpus/core/src/db/client.ts +29 -2
  18. package/corpus/core/src/provider-api/index.ts +29 -0
  19. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  20. package/corpus/core/src/styles/agent-native.css +54 -0
  21. package/corpus/core/src/usage/store.ts +2 -2
  22. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  23. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  24. package/corpus/templates/analytics/app/global.css +8 -0
  25. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  26. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  27. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  29. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  30. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  31. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  36. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  37. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  38. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
  39. package/corpus/templates/assets/AGENTS.md +4 -0
  40. package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
  41. package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
  42. package/corpus/templates/assets/actions/generate-image.ts +118 -67
  43. package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
  44. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  45. package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
  46. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
  47. package/corpus/templates/assets/app/global.css +15 -0
  48. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  49. package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
  50. package/corpus/templates/assets/app/i18n-data.ts +61 -0
  51. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  52. package/corpus/templates/assets/app/routes/library.tsx +233 -52
  53. package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
  54. package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
  55. package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
  56. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  57. package/corpus/templates/assets/server/lib/generation.ts +135 -11
  58. package/corpus/templates/assets/shared/api.ts +4 -0
  59. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  60. package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
  61. package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
  62. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  63. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  64. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  65. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  66. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  67. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  68. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  69. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  70. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  71. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  72. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  73. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  74. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  75. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  76. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  77. package/corpus/templates/design/AGENTS.md +27 -0
  78. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  79. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  80. package/corpus/templates/design/actions/apply-component-prop-edit.ts +76 -25
  81. package/corpus/templates/design/actions/apply-design-state.ts +17 -4
  82. package/corpus/templates/design/actions/apply-motion-edit.ts +76 -115
  83. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  84. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  85. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  86. package/corpus/templates/design/actions/delete-design.ts +20 -0
  87. package/corpus/templates/design/actions/export-zip.ts +23 -8
  88. package/corpus/templates/design/actions/generate-design.ts +53 -1
  89. package/corpus/templates/design/actions/get-component-details.ts +21 -26
  90. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  91. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  92. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  93. package/corpus/templates/design/actions/index-design-tokens.ts +18 -5
  94. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  95. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  96. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  97. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  98. package/corpus/templates/design/actions/list-design-states.ts +19 -1
  99. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  100. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  101. package/corpus/templates/design/actions/navigate.ts +18 -3
  102. package/corpus/templates/design/actions/open-component-source.ts +7 -2
  103. package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
  104. package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
  105. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  106. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  107. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  108. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  109. package/corpus/templates/design/actions/run-design-audit.ts +74 -10
  110. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  111. package/corpus/templates/design/actions/view-screen.ts +1 -1
  112. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  113. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +244 -3197
  114. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  115. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +795 -289
  116. package/corpus/templates/design/app/components/design/EditPanel.tsx +635 -234
  117. package/corpus/templates/design/app/components/design/LayersPanel.tsx +124 -32
  118. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  119. package/corpus/templates/design/app/components/design/MotionDock.tsx +244 -266
  120. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +491 -56
  121. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +55 -67
  122. package/corpus/templates/design/app/components/design/StatesPanel.tsx +38 -63
  123. package/corpus/templates/design/app/components/design/TokensPanel.tsx +264 -8
  124. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  125. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  126. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  127. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  128. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  129. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  131. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  132. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  133. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  134. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  135. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  136. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  137. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  138. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  139. package/corpus/templates/design/app/global.css +7 -0
  140. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  141. package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
  142. package/corpus/templates/design/app/i18n-data.ts +369 -0
  143. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  144. package/corpus/templates/design/app/pages/DesignEditor.tsx +3024 -616
  145. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  146. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  147. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  165. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  166. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  167. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  168. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  169. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  170. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  171. package/corpus/templates/design/e2e/global-setup.ts +94 -2
  172. package/corpus/templates/design/e2e/helpers.ts +168 -65
  173. package/corpus/templates/design/package.json +3 -1
  174. package/corpus/templates/design/server/db/schema.ts +23 -0
  175. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  176. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  177. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  178. package/corpus/templates/design/server/plugins/db.ts +39 -0
  179. package/corpus/templates/design/server/register-secrets.ts +38 -0
  180. package/corpus/templates/design/shared/board-file.ts +228 -0
  181. package/corpus/templates/design/shared/board-objects.ts +288 -0
  182. package/corpus/templates/design/shared/code-layer.ts +52 -0
  183. package/corpus/templates/design/shared/component-model.ts +35 -0
  184. package/corpus/templates/design/shared/motion-compiler.ts +137 -15
  185. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  186. package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
  187. package/corpus/templates/design/tsconfig.json +2 -1
  188. package/corpus/templates/slides/AGENTS.md +9 -0
  189. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  190. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  191. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  192. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  193. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  194. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  195. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  196. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  197. package/corpus/templates/slides/package.json +1 -0
  198. package/corpus/templates/slides/vite.config.ts +1 -0
  199. package/dist/agent/production-agent.d.ts.map +1 -1
  200. package/dist/agent/production-agent.js +94 -10
  201. package/dist/agent/production-agent.js.map +1 -1
  202. package/dist/cli/design-connect.d.ts +25 -0
  203. package/dist/cli/design-connect.d.ts.map +1 -1
  204. package/dist/cli/design-connect.js +357 -18
  205. package/dist/cli/design-connect.js.map +1 -1
  206. package/dist/client/AssistantChat.d.ts +14 -0
  207. package/dist/client/AssistantChat.d.ts.map +1 -1
  208. package/dist/client/AssistantChat.js +115 -22
  209. package/dist/client/AssistantChat.js.map +1 -1
  210. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  211. package/dist/client/ConnectBuilderCard.js +1 -0
  212. package/dist/client/ConnectBuilderCard.js.map +1 -1
  213. package/dist/client/chat/index.d.ts +1 -1
  214. package/dist/client/chat/index.d.ts.map +1 -1
  215. package/dist/client/chat/index.js +1 -1
  216. package/dist/client/chat/index.js.map +1 -1
  217. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  218. package/dist/client/chat/run-recovery.js +5 -5
  219. package/dist/client/chat/run-recovery.js.map +1 -1
  220. package/dist/client/chat-view-transition.d.ts +6 -0
  221. package/dist/client/chat-view-transition.d.ts.map +1 -1
  222. package/dist/client/chat-view-transition.js +23 -2
  223. package/dist/client/chat-view-transition.js.map +1 -1
  224. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  225. package/dist/client/composer/PromptComposer.js +3 -2
  226. package/dist/client/composer/PromptComposer.js.map +1 -1
  227. package/dist/client/index.d.ts +1 -1
  228. package/dist/client/index.d.ts.map +1 -1
  229. package/dist/client/index.js +1 -1
  230. package/dist/client/index.js.map +1 -1
  231. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  232. package/dist/client/org/OrgSwitcher.js +11 -4
  233. package/dist/client/org/OrgSwitcher.js.map +1 -1
  234. package/dist/client/sharing/ShareButton.js +3 -3
  235. package/dist/client/sharing/ShareButton.js.map +1 -1
  236. package/dist/client/sse-event-processor.d.ts.map +1 -1
  237. package/dist/client/sse-event-processor.js +10 -0
  238. package/dist/client/sse-event-processor.js.map +1 -1
  239. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  240. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  241. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  242. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  243. package/dist/collab/awareness.d.ts +2 -2
  244. package/dist/collab/awareness.d.ts.map +1 -1
  245. package/dist/collab/routes.d.ts +2 -2
  246. package/dist/db/client.d.ts +1 -0
  247. package/dist/db/client.d.ts.map +1 -1
  248. package/dist/db/client.js +28 -2
  249. package/dist/db/client.js.map +1 -1
  250. package/dist/notifications/routes.d.ts +3 -3
  251. package/dist/observability/routes.d.ts +3 -3
  252. package/dist/progress/routes.d.ts +1 -1
  253. package/dist/provider-api/index.d.ts +5 -4
  254. package/dist/provider-api/index.d.ts.map +1 -1
  255. package/dist/provider-api/index.js +28 -0
  256. package/dist/provider-api/index.js.map +1 -1
  257. package/dist/resources/handlers.d.ts +3 -3
  258. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  259. package/dist/server/core-routes-plugin.d.ts +25 -0
  260. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  261. package/dist/server/core-routes-plugin.js +43 -18
  262. package/dist/server/core-routes-plugin.js.map +1 -1
  263. package/dist/server/transcribe-voice.d.ts +1 -1
  264. package/dist/styles/agent-native.css +54 -0
  265. package/dist/usage/store.js +2 -2
  266. package/dist/usage/store.js.map +1 -1
  267. 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)
@@ -16,6 +16,26 @@ export default defineAction({
16
16
 
17
17
  const db = getDb();
18
18
 
19
+ await db
20
+ .delete(schema.designShares)
21
+ .where(eq(schema.designShares.resourceId, id));
22
+
23
+ await db
24
+ .delete(schema.componentIndex)
25
+ .where(eq(schema.componentIndex.designId, id));
26
+
27
+ await db
28
+ .delete(schema.motionTimeline)
29
+ .where(eq(schema.motionTimeline.designId, id));
30
+
31
+ await db
32
+ .delete(schema.designState)
33
+ .where(eq(schema.designState.designId, id));
34
+
35
+ await db
36
+ .delete(schema.designReviewSnapshot)
37
+ .where(eq(schema.designReviewSnapshot.designId, id));
38
+
19
39
  // Delete associated files first
20
40
  await db
21
41
  .delete(schema.designFiles)
@@ -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";
@@ -27,29 +26,13 @@ import { buildCodeLayerProjection } from "../shared/code-layer.js";
27
26
  import type { CodeLayerSource } from "../shared/code-layer.js";
28
27
  import {
29
28
  componentNameFor,
29
+ componentNodeIdMatches,
30
30
  extractProps,
31
31
  type ComponentInstance,
32
32
  } from "../shared/component-model.js";
33
33
  import { hasCapability } from "../shared/design-source-capabilities.js";
34
34
  import { normalizeDesignSourceType } from "../shared/source-mode.js";
35
35
 
36
- // ─── Helpers ──────────────────────────────────────────────────────────────────
37
-
38
- async function liveContent(
39
- fileId: string,
40
- storedContent: string,
41
- ): Promise<string> {
42
- try {
43
- if (await hasCollabState(fileId)) {
44
- const live = await getText(fileId, "content");
45
- if (typeof live === "string") return live;
46
- }
47
- } catch {
48
- // Collab reads are best-effort; SQL content is the fallback.
49
- }
50
- return storedContent;
51
- }
52
-
53
36
  function parseJson<T>(raw: string | null | undefined, fallback: T): T {
54
37
  if (!raw) return fallback;
55
38
  try {
@@ -112,6 +95,14 @@ export default defineAction({
112
95
  const caps = resolveSourceCapabilities(sourceType);
113
96
  const canResolveToFile = hasCapability(caps, "resolveNodeToFile");
114
97
  const hasFullIndex = hasCapability(caps, "indexComponents");
98
+ const canEditProps =
99
+ sourceType === "inline" || hasCapability(caps, "applyEdit");
100
+ const ctaRequired = !hasFullIndex || !canEditProps;
101
+ const ctaMessage = !hasFullIndex
102
+ ? "Full prop controls (TypeScript prop types, cva variants, Storybook stories) require a connected Builder app. Connect Builder to unlock."
103
+ : !canEditProps
104
+ ? "Prop write-back requires the bridge applyEdit capability. Preview controls remain available until source write hardening is enabled."
105
+ : undefined;
115
106
 
116
107
  // ── Fetch design file ────────────────────────────────────────────────────
117
108
  const conditions = [
@@ -139,7 +130,11 @@ export default defineAction({
139
130
 
140
131
  if (!file) throw new Error("Design HTML file not found.");
141
132
 
142
- 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 ?? "";
143
138
 
144
139
  // ── Projection lookup ────────────────────────────────────────────────────
145
140
  const codeLayerSource: CodeLayerSource = {
@@ -153,7 +148,9 @@ export default defineAction({
153
148
  source: codeLayerSource,
154
149
  });
155
150
 
156
- const node = projection.nodes.find((n) => n.id === nodeId);
151
+ const node = projection.nodes.find((n) =>
152
+ componentNodeIdMatches(n, nodeId),
153
+ );
157
154
  if (!node) {
158
155
  throw new Error(
159
156
  `Node "${nodeId}" not found in projection. Run get-code-layer-projection to list current node ids.`,
@@ -180,7 +177,7 @@ export default defineAction({
180
177
  props: observedProps,
181
178
  alpineData,
182
179
  selector: node.selector,
183
- nodeId: node.id,
180
+ nodeId,
184
181
  };
185
182
 
186
183
  // ── Lookup persisted component_index row ─────────────────────────────────
@@ -231,11 +228,9 @@ export default defineAction({
231
228
  capabilities: {
232
229
  canResolveToFile,
233
230
  hasFullIndex,
234
- canEditProps: true, // preview-only on inline; persists on real-app
235
- ctaRequired: !hasFullIndex,
236
- ctaMessage: !hasFullIndex
237
- ? "Full prop controls (TypeScript prop types, cva variants, Storybook stories) require a connected Builder app. Connect Builder to unlock."
238
- : undefined,
231
+ canEditProps,
232
+ ctaRequired,
233
+ ctaMessage,
239
234
  },
240
235
  };
241
236
  },
@@ -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
+ });