@agent-native/core 0.92.7 → 0.92.9

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 (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
  4. package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  5. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  6. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  7. package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  8. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  9. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  10. package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  11. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  12. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  13. package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  14. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  15. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  16. package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  17. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  18. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  19. package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  20. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  21. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  22. package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  23. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  24. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  25. package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  26. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  27. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  28. package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  29. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  30. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  31. package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  32. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  33. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  34. package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
  35. package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
  36. package/corpus/core/package.json +1 -1
  37. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  38. package/corpus/core/src/agent/model-config.ts +30 -30
  39. package/corpus/core/src/agent/production-agent.ts +129 -7
  40. package/corpus/core/src/cli/design-connect.ts +438 -83
  41. package/corpus/core/src/cli/recap.ts +17 -3
  42. package/corpus/core/src/client/AgentPanel.tsx +8 -1
  43. package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
  44. package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
  45. package/corpus/core/src/client/index.ts +4 -0
  46. package/corpus/core/src/client/org/TeamPage.tsx +41 -26
  47. package/corpus/core/src/client/use-action.ts +113 -1
  48. package/corpus/core/src/db/client.ts +12 -2
  49. package/corpus/core/src/db/create-get-db.ts +4 -1
  50. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  51. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  52. package/corpus/core/src/server/ssr-handler.ts +7 -0
  53. package/corpus/core/src/server/transcribe-voice.ts +1 -1
  54. package/corpus/core/src/usage/store.ts +13 -4
  55. package/corpus/core/src/vite/client.ts +38 -1
  56. package/corpus/templates/analytics/AGENTS.md +3 -0
  57. package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
  58. package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
  59. package/corpus/templates/analytics/app/global.css +11 -0
  60. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
  61. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  62. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  63. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
  64. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
  65. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
  66. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
  67. package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
  71. package/corpus/templates/analytics/server/db/index.ts +6 -0
  72. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
  73. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
  74. package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
  75. package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
  76. package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
  77. package/corpus/templates/content/app/i18n-data.ts +28 -0
  78. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
  79. package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
  80. package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
  81. package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
  82. package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
  83. package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
  84. package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
  85. package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
  86. package/corpus/templates/design/actions/capture-design-state.ts +2 -2
  87. package/corpus/templates/design/actions/connect-localhost.ts +58 -12
  88. package/corpus/templates/design/actions/create-component.ts +2 -18
  89. package/corpus/templates/design/actions/create-design-branch.ts +38 -18
  90. package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
  91. package/corpus/templates/design/actions/delete-file.ts +19 -34
  92. package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
  93. package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
  94. package/corpus/templates/design/actions/export-pdf.ts +2 -1
  95. package/corpus/templates/design/actions/export-zip.ts +9 -3
  96. package/corpus/templates/design/actions/generate-design.ts +107 -70
  97. package/corpus/templates/design/actions/get-component-details.ts +2 -18
  98. package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
  99. package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
  100. package/corpus/templates/design/actions/get-design.ts +2 -1
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
  102. package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
  103. package/corpus/templates/design/actions/index-components.ts +2 -18
  104. package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
  105. package/corpus/templates/design/actions/list-local-files.ts +9 -2
  106. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
  107. package/corpus/templates/design/actions/open-component-source.ts +2 -18
  108. package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
  109. package/corpus/templates/design/actions/present-design-variants.ts +70 -70
  110. package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
  111. package/corpus/templates/design/actions/read-local-file.ts +9 -2
  112. package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
  113. package/corpus/templates/design/actions/rename-screen.ts +425 -0
  114. package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
  115. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
  116. package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
  117. package/corpus/templates/design/actions/update-design.ts +376 -61
  118. package/corpus/templates/design/actions/update-file.ts +455 -188
  119. package/corpus/templates/design/actions/write-local-file.ts +10 -2
  120. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
  121. package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
  122. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
  123. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
  124. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
  125. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
  126. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
  127. package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
  128. package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
  129. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
  130. package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
  131. package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
  132. package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
  133. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
  134. package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
  135. package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
  136. package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
  137. package/corpus/templates/design/app/components/design/index.ts +2 -2
  138. package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
  139. package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
  140. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
  141. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
  142. package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
  143. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
  144. package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
  145. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
  146. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
  147. package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
  148. package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
  149. package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
  150. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
  151. package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
  152. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
  153. package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
  154. package/corpus/templates/design/app/i18n-data.ts +223 -0
  155. package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
  156. package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
  157. package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
  158. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
  159. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
  160. package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
  161. package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
  162. package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
  163. package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
  164. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
  165. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
  166. package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
  167. package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
  168. package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
  169. package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
  170. package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
  171. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
  172. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
  173. package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
  174. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
  175. package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
  176. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
  177. package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
  178. package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
  179. package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
  180. package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
  181. package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
  182. package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
  183. package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
  184. package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
  185. package/corpus/templates/design/server/db/index.ts +2 -52
  186. package/corpus/templates/design/server/db/schema.ts +16 -0
  187. package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
  188. package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
  189. package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
  190. package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
  191. package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
  192. package/corpus/templates/design/server/plugins/db.ts +19 -0
  193. package/corpus/templates/design/server/source-workspace.ts +12 -13
  194. package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
  195. package/corpus/templates/design/shared/screen-rename.ts +42 -0
  196. package/corpus/templates/design/shared/source-mode.ts +84 -4
  197. package/dist/agent/engine/builder-engine.d.ts +1 -1
  198. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  199. package/dist/agent/engine/builtin.js +1 -1
  200. package/dist/agent/engine/builtin.js.map +1 -1
  201. package/dist/agent/model-config.d.ts +9 -9
  202. package/dist/agent/model-config.d.ts.map +1 -1
  203. package/dist/agent/model-config.js +30 -30
  204. package/dist/agent/model-config.js.map +1 -1
  205. package/dist/agent/production-agent.d.ts +60 -1
  206. package/dist/agent/production-agent.d.ts.map +1 -1
  207. package/dist/agent/production-agent.js +106 -7
  208. package/dist/agent/production-agent.js.map +1 -1
  209. package/dist/cli/design-connect.d.ts +30 -1
  210. package/dist/cli/design-connect.d.ts.map +1 -1
  211. package/dist/cli/design-connect.js +359 -75
  212. package/dist/cli/design-connect.js.map +1 -1
  213. package/dist/cli/recap.d.ts.map +1 -1
  214. package/dist/cli/recap.js +13 -3
  215. package/dist/cli/recap.js.map +1 -1
  216. package/dist/client/AgentPanel.d.ts.map +1 -1
  217. package/dist/client/AgentPanel.js +6 -1
  218. package/dist/client/AgentPanel.js.map +1 -1
  219. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  220. package/dist/client/agent-chat-adapter.js +10 -5
  221. package/dist/client/agent-chat-adapter.js.map +1 -1
  222. package/dist/client/composer/TiptapComposer.js +1 -1
  223. package/dist/client/composer/TiptapComposer.js.map +1 -1
  224. package/dist/client/index.d.ts +1 -1
  225. package/dist/client/index.d.ts.map +1 -1
  226. package/dist/client/index.js +1 -1
  227. package/dist/client/index.js.map +1 -1
  228. package/dist/client/org/TeamPage.d.ts.map +1 -1
  229. package/dist/client/org/TeamPage.js +6 -5
  230. package/dist/client/org/TeamPage.js.map +1 -1
  231. package/dist/client/use-action.d.ts +32 -0
  232. package/dist/client/use-action.d.ts.map +1 -1
  233. package/dist/client/use-action.js +68 -1
  234. package/dist/client/use-action.js.map +1 -1
  235. package/dist/collab/struct-routes.d.ts +1 -1
  236. package/dist/db/client.d.ts +3 -0
  237. package/dist/db/client.d.ts.map +1 -1
  238. package/dist/db/client.js +7 -2
  239. package/dist/db/client.js.map +1 -1
  240. package/dist/db/create-get-db.d.ts.map +1 -1
  241. package/dist/db/create-get-db.js +2 -2
  242. package/dist/db/create-get-db.js.map +1 -1
  243. package/dist/notifications/routes.d.ts +3 -3
  244. package/dist/observability/routes.d.ts +1 -1
  245. package/dist/resources/handlers.d.ts +3 -3
  246. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  247. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  248. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  249. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  250. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  251. package/dist/server/ssr-handler.d.ts.map +1 -1
  252. package/dist/server/ssr-handler.js +7 -0
  253. package/dist/server/ssr-handler.js.map +1 -1
  254. package/dist/server/transcribe-voice.js +1 -1
  255. package/dist/server/transcribe-voice.js.map +1 -1
  256. package/dist/usage/store.d.ts.map +1 -1
  257. package/dist/usage/store.js +13 -4
  258. package/dist/usage/store.js.map +1 -1
  259. package/dist/vite/client.d.ts.map +1 -1
  260. package/dist/vite/client.js +36 -1
  261. package/dist/vite/client.js.map +1 -1
  262. package/docs/content/a2a-protocol.mdx +12 -0
  263. package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  264. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  265. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  266. package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  267. package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  268. package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  269. package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  270. package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  271. package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  272. package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  273. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  274. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  275. package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  276. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  277. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  278. package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  279. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  280. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  281. package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  282. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  283. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  284. package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  285. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  286. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  287. package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  288. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  289. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  290. package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  291. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  292. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  293. package/docs/content/pr-visual-recap.mdx +3 -3
  294. package/docs/content/pure-agent-apps.mdx +22 -0
  295. package/package.json +2 -2
@@ -39,6 +39,19 @@ const MANIFEST_CAPABILITIES = {
39
39
  readTextFiles: true,
40
40
  writeTextFiles: true,
41
41
  };
42
+ const PREVIEW_TOKEN_DOMAIN = "agent-native-design-preview-v1\0";
43
+ /**
44
+ * Derive a read-only preview credential from the stronger filesystem token.
45
+ * The one-way hash keeps old `--bridge-token` launch commands compatible while
46
+ * ensuring a leaked preview credential cannot be promoted into write access.
47
+ */
48
+ export function deriveDesignPreviewToken(bridgeToken) {
49
+ return crypto
50
+ .createHash("sha256")
51
+ .update(PREVIEW_TOKEN_DOMAIN)
52
+ .update(bridgeToken)
53
+ .digest("hex");
54
+ }
42
55
  function stringFlagValue(argv, index, flag) {
43
56
  const value = argv[index + 1];
44
57
  if (!value || value.startsWith("-")) {
@@ -117,6 +130,13 @@ export function parseDesignConnectArgs(argv) {
117
130
  else if (arg.startsWith("--bridge-token=")) {
118
131
  parsed.bridgeToken = arg.slice("--bridge-token=".length);
119
132
  }
133
+ else if (arg === "--preview-token") {
134
+ parsed.previewToken = stringFlagValue(args, index, arg);
135
+ index += 1;
136
+ }
137
+ else if (arg.startsWith("--preview-token=")) {
138
+ parsed.previewToken = arg.slice("--preview-token=".length);
139
+ }
120
140
  else if (arg === "--json") {
121
141
  parsed.json = true;
122
142
  parsed.once = true;
@@ -149,14 +169,34 @@ export function parseDesignConnectArgs(argv) {
149
169
  return parsed;
150
170
  }
151
171
  function routeId(routePath) {
152
- if (routePath === "/")
153
- return "route-root";
154
- const slug = routePath
172
+ const normalized = routePath.trim() || "/";
173
+ const slug = normalized
155
174
  .replace(/^\/+/, "")
175
+ .replace(/\*/g, "w")
176
+ .replace(/:/g, "p")
177
+ .replace(/[[\]]/g, "")
156
178
  .replace(/[^a-zA-Z0-9]+/g, "-")
157
179
  .replace(/^-+|-+$/g, "")
158
180
  .toLowerCase();
159
- return slug ? `route-${slug}` : "route-wildcard";
181
+ // Slugs are intentionally readable but necessarily lossy: `/foo/bar`,
182
+ // `/foo-bar`, and `/foo_bar` all collapse to the same text, while `/` and
183
+ // `/*` can collide with literal `/root` and `/wildcard` paths. Suffix every
184
+ // route with a stable hash of its normalized path so URL/query states and
185
+ // router patterns can never silently replace one another in the manifest.
186
+ const readable = normalized === "/"
187
+ ? "root"
188
+ : /^\/\*+$/.test(normalized) || !slug
189
+ ? "wildcard"
190
+ : slug;
191
+ return `route-${readable}-${stableRoutePathHash(normalized)}`;
192
+ }
193
+ function stableRoutePathHash(value) {
194
+ let hash = 0xcbf29ce484222325n;
195
+ for (const character of value) {
196
+ hash ^= BigInt(character.codePointAt(0));
197
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
198
+ }
199
+ return hash.toString(36);
160
200
  }
161
201
  function titleFromRoutePath(routePath) {
162
202
  if (routePath === "/")
@@ -315,8 +355,11 @@ function isDesignConnectManifest(value) {
315
355
  typeof manifest.bridgeUrl === "string" &&
316
356
  typeof manifest.rootPath === "string");
317
357
  }
318
- async function fetchRunningBridgeManifest(bridgeUrl) {
319
- const manifestUrl = new URL("/manifest.json", bridgeUrl).toString();
358
+ async function fetchRunningBridgeManifest(bridgeUrl, previewToken) {
359
+ const manifestUrl = new URL("/manifest.json", bridgeUrl);
360
+ if (previewToken) {
361
+ manifestUrl.searchParams.set("previewToken", previewToken);
362
+ }
320
363
  const controller = new AbortController();
321
364
  const timeout = setTimeout(() => controller.abort(), 800);
322
365
  try {
@@ -349,6 +392,46 @@ export function designConnectManifestsTargetSameApp(running, requested) {
349
392
  return (runningUrl === requestedUrl &&
350
393
  path.resolve(running.rootPath) === path.resolve(requested.rootPath));
351
394
  }
395
+ /** Non-sensitive stable identifier used by /health so daemon reruns can detect
396
+ * an already-running bridge for the same app without exposing its root path or
397
+ * route manifest. */
398
+ export function designConnectAppFingerprint(manifest) {
399
+ let devServerUrl = manifest.devServerUrl;
400
+ try {
401
+ devServerUrl = normalizeHttpUrl(devServerUrl);
402
+ }
403
+ catch {
404
+ // Hash the original string for malformed legacy manifests.
405
+ }
406
+ return crypto
407
+ .createHash("sha256")
408
+ .update(`${devServerUrl}\n${path.resolve(manifest.rootPath)}`)
409
+ .digest("base64url")
410
+ .slice(0, 24);
411
+ }
412
+ async function fetchRunningBridgeFingerprint(bridgeUrl) {
413
+ const healthUrl = new URL("/health", bridgeUrl).toString();
414
+ const controller = new AbortController();
415
+ const timeout = setTimeout(() => controller.abort(), 800);
416
+ try {
417
+ const response = await fetch(healthUrl, {
418
+ method: "GET",
419
+ signal: controller.signal,
420
+ });
421
+ if (!response.ok)
422
+ return null;
423
+ const body = (await response.json());
424
+ return typeof body["appFingerprint"] === "string"
425
+ ? body["appFingerprint"]
426
+ : null;
427
+ }
428
+ catch {
429
+ return null;
430
+ }
431
+ finally {
432
+ clearTimeout(timeout);
433
+ }
434
+ }
352
435
  async function resolveDevServerUrl(url) {
353
436
  if (url)
354
437
  return normalizeHttpUrl(url);
@@ -363,7 +446,65 @@ async function ensureRouteManifest(options) {
363
446
  ? options.routeManifestPath
364
447
  : path.join(options.root, options.routeManifestPath);
365
448
  if (fsSync.existsSync(manifestPath)) {
366
- return { path: manifestPath, created: false };
449
+ try {
450
+ const parsed = JSON.parse(await fs.readFile(manifestPath, "utf8"));
451
+ const savedRoutes = Array.isArray(parsed.routes)
452
+ ? parsed.routes.flatMap((value) => {
453
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
454
+ return [];
455
+ }
456
+ const route = value;
457
+ if (typeof route.path !== "string" || !route.path.trim())
458
+ return [];
459
+ const sourceKind = route.sourceKind === "react-router" ||
460
+ route.sourceKind === "html" ||
461
+ route.sourceKind === "manual"
462
+ ? route.sourceKind
463
+ : "manual";
464
+ return [
465
+ {
466
+ id: typeof route.id === "string" && route.id.trim()
467
+ ? route.id
468
+ : routeId(route.path),
469
+ path: route.path,
470
+ title: typeof route.title === "string" && route.title.trim()
471
+ ? route.title
472
+ : titleFromRoutePath(route.path),
473
+ sourceFile: typeof route.sourceFile === "string"
474
+ ? route.sourceFile
475
+ : undefined,
476
+ sourceKind,
477
+ screenshotUrl: typeof route.screenshotUrl === "string"
478
+ ? route.screenshotUrl
479
+ : undefined,
480
+ metadata: route.metadata &&
481
+ typeof route.metadata === "object" &&
482
+ !Array.isArray(route.metadata)
483
+ ? route.metadata
484
+ : undefined,
485
+ },
486
+ ];
487
+ })
488
+ : [];
489
+ if (savedRoutes.length > 0) {
490
+ const savedPaths = new Set(savedRoutes.map((route) => route.path));
491
+ return {
492
+ path: manifestPath,
493
+ created: false,
494
+ // Keep manual/custom route order and metadata while still surfacing
495
+ // newly discovered routes on subsequent bridge starts.
496
+ routes: [
497
+ ...savedRoutes,
498
+ ...options.routes.filter((route) => !savedPaths.has(route.path)),
499
+ ],
500
+ };
501
+ }
502
+ }
503
+ catch {
504
+ // Never overwrite a malformed/custom file. Route discovery remains a
505
+ // safe runtime fallback and the user can repair the manifest in place.
506
+ }
507
+ return { path: manifestPath, created: false, routes: options.routes };
367
508
  }
368
509
  if (!options.dryRun) {
369
510
  await fs.mkdir(path.dirname(manifestPath), { recursive: true });
@@ -376,22 +517,27 @@ async function ensureRouteManifest(options) {
376
517
  generatedAt: new Date().toISOString(),
377
518
  }, null, 2)}\n`, "utf8");
378
519
  }
379
- return { path: manifestPath, created: !options.dryRun };
520
+ return {
521
+ path: manifestPath,
522
+ created: !options.dryRun,
523
+ routes: options.routes,
524
+ };
380
525
  }
381
526
  export async function prepareDesignConnectManifest(options = {}) {
382
527
  const root = path.resolve(options.root ?? process.cwd());
383
528
  const port = options.port ?? DEFAULT_BRIDGE_PORT;
384
529
  const devServerUrl = await resolveDevServerUrl(options.url);
385
530
  const bridgeUrl = `http://127.0.0.1:${port}`;
386
- const routes = discoverDesignRoutes(root);
531
+ const discoveredRoutes = discoverDesignRoutes(root);
387
532
  const routeManifest = await ensureRouteManifest({
388
533
  root,
389
534
  routeManifestPath: options.routeManifest ?? ROUTE_MANIFEST_FILE,
390
- routes,
535
+ routes: discoveredRoutes,
391
536
  devServerUrl,
392
537
  dryRun: Boolean(options.dryRun),
393
538
  });
394
539
  const generatedAt = new Date().toISOString();
540
+ const routes = routeManifest.routes;
395
541
  return {
396
542
  version: 1,
397
543
  source: "agent-native-design-connect",
@@ -420,32 +566,69 @@ export async function prepareDesignConnectManifest(options = {}) {
420
566
  })),
421
567
  };
422
568
  }
569
+ const BRIDGE_CORS_HEADERS = Symbol("agent-native-design-bridge-cors");
570
+ function isLoopbackOrigin(parsed) {
571
+ const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
572
+ return (hostname === "localhost" ||
573
+ hostname === "::1" ||
574
+ /^127(?:\.\d{1,3}){3}$/.test(hostname));
575
+ }
576
+ function isApprovedDesignOrigin(rawOrigin, configuredOrigins) {
577
+ let parsed;
578
+ try {
579
+ parsed = new URL(rawOrigin);
580
+ }
581
+ catch {
582
+ return false;
583
+ }
584
+ if (parsed.origin !== rawOrigin)
585
+ return false;
586
+ if (configuredOrigins.has(parsed.origin))
587
+ return true;
588
+ if ((parsed.protocol === "http:" || parsed.protocol === "https:") &&
589
+ isLoopbackOrigin(parsed)) {
590
+ return true;
591
+ }
592
+ return (parsed.protocol === "https:" &&
593
+ (parsed.hostname === "design.agent-native.com" ||
594
+ parsed.hostname.endsWith(".design.agent-native.com")));
595
+ }
596
+ function configureBridgeCors(req, res, configuredOrigins) {
597
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : "";
598
+ const approved = origin
599
+ ? isApprovedDesignOrigin(origin, configuredOrigins)
600
+ : false;
601
+ res[BRIDGE_CORS_HEADERS] = approved
602
+ ? {
603
+ "access-control-allow-origin": origin,
604
+ "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
605
+ "access-control-allow-headers": "content-type, x-bridge-token, x-design-preview-token",
606
+ "access-control-allow-private-network": "true",
607
+ vary: "Origin",
608
+ }
609
+ : {};
610
+ return approved;
611
+ }
612
+ function bridgeCorsHeaders(res) {
613
+ return res[BRIDGE_CORS_HEADERS] ?? {};
614
+ }
423
615
  function sendJson(res, statusCode, body) {
424
616
  res.writeHead(statusCode, {
425
617
  "content-type": "application/json; charset=utf-8",
426
- "access-control-allow-origin": "*",
427
- "access-control-allow-methods": "GET, POST, OPTIONS",
428
- "access-control-allow-headers": "content-type, x-bridge-token",
429
- "access-control-allow-private-network": "true",
618
+ ...bridgeCorsHeaders(res),
430
619
  });
431
620
  res.end(`${JSON.stringify(body, null, 2)}\n`);
432
621
  }
433
622
  function sendText(res, statusCode, body, contentType) {
434
623
  res.writeHead(statusCode, {
435
624
  "content-type": contentType,
436
- "access-control-allow-origin": "*",
437
- "access-control-allow-methods": "GET, POST, OPTIONS",
438
- "access-control-allow-headers": "content-type, x-bridge-token",
439
- "access-control-allow-private-network": "true",
625
+ ...bridgeCorsHeaders(res),
440
626
  });
441
627
  res.end(body);
442
628
  }
443
629
  function sendBytes(res, statusCode, body, headers) {
444
630
  const responseHeaders = {
445
- "access-control-allow-origin": "*",
446
- "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
447
- "access-control-allow-headers": "content-type, x-bridge-token",
448
- "access-control-allow-private-network": "true",
631
+ ...bridgeCorsHeaders(res),
449
632
  };
450
633
  for (const name of [
451
634
  "content-type",
@@ -468,6 +651,19 @@ function sameOrigin(a, b) {
468
651
  return false;
469
652
  }
470
653
  }
654
+ function constantTimeTokenMatches(providedToken, expectedToken) {
655
+ try {
656
+ return (providedToken.length === expectedToken.length &&
657
+ crypto.timingSafeEqual(Buffer.from(providedToken, "utf8"), Buffer.from(expectedToken, "utf8")));
658
+ }
659
+ catch {
660
+ return false;
661
+ }
662
+ }
663
+ function readHeader(req, name) {
664
+ const value = req.headers[name];
665
+ return typeof value === "string" ? value : "";
666
+ }
471
667
  function resolvePreviewSnapshotUrl(devServerUrl, rawUrl) {
472
668
  const base = normalizeHttpUrl(devServerUrl);
473
669
  const parsed = new URL(rawUrl?.trim() || "/", `${base}/`);
@@ -1011,27 +1207,68 @@ async function walkBridgeFiles(rootPath) {
1011
1207
  await walk(resolvedRoot, "");
1012
1208
  return { files, truncated };
1013
1209
  }
1014
- export async function startDesignConnectBridge(manifest, seedToken) {
1210
+ export async function startDesignConnectBridge(manifest, seedOrOptions) {
1015
1211
  // Shared secret the browser sends (x-bridge-token) to unlock live-edit/read/
1016
1212
  // write. Bridge and the user's connection row must agree on it. Adopt a
1017
1213
  // server-minted seed when given (MCP flow); otherwise mint one and rely on
1018
1214
  // --app-url self-registration to push it up. Kept in-process, never served.
1019
- const bridgeToken = seedToken ||
1215
+ const options = typeof seedOrOptions === "string"
1216
+ ? { bridgeToken: seedOrOptions }
1217
+ : (seedOrOptions ?? {});
1218
+ const bridgeToken = options.bridgeToken ||
1020
1219
  process.env["AGENT_NATIVE_BRIDGE_TOKEN"] ||
1021
1220
  crypto.randomBytes(32).toString("hex");
1221
+ const previewToken = options.previewToken ||
1222
+ process.env["AGENT_NATIVE_PREVIEW_TOKEN"] ||
1223
+ deriveDesignPreviewToken(bridgeToken);
1224
+ const configuredOrigins = new Set((options.allowedOrigins ?? []).flatMap((raw) => {
1225
+ try {
1226
+ return [new URL(raw).origin];
1227
+ }
1228
+ catch {
1229
+ return [];
1230
+ }
1231
+ }));
1022
1232
  let liveEditBridgeScript = "";
1233
+ // One bridge process serves every URL-backed screen in an overview. The
1234
+ // editor script carries screen-specific state (notably screenId), so a
1235
+ // single global slot lets parallel iframe registrations overwrite each
1236
+ // other and boot a frame with another frame's identity. Keep keyed scripts
1237
+ // for modern clients while retaining the unkeyed slot for older clients.
1238
+ const liveEditBridgeScripts = new Map();
1023
1239
  const server = http.createServer((req, res) => {
1240
+ const corsApproved = configureBridgeCors(req, res, configuredOrigins);
1024
1241
  if (req.method === "OPTIONS") {
1025
- sendJson(res, 204, {});
1242
+ sendJson(res, corsApproved ? 204 : 403, corsApproved
1243
+ ? {}
1244
+ : { ok: false, error: "origin is not allowed by this bridge" });
1026
1245
  return;
1027
1246
  }
1028
- const pathname = new URL(req.url ?? "/", manifest.bridgeUrl).pathname;
1029
- // ── Public read-only routes (no token required) ──────────────────────
1247
+ const requestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
1248
+ const pathname = requestUrl.pathname;
1249
+ const providedPreviewToken = readHeader(req, "x-design-preview-token") ||
1250
+ requestUrl.searchParams.get("previewToken") ||
1251
+ "";
1252
+ const previewTokenValid = constantTimeTokenMatches(providedPreviewToken, previewToken);
1253
+ const rejectInvalidPreviewToken = () => {
1254
+ if (previewTokenValid)
1255
+ return false;
1256
+ sendJson(res, 401, {
1257
+ ok: false,
1258
+ error: "invalid or missing preview token",
1259
+ });
1260
+ return true;
1261
+ };
1262
+ // ── Read-only preview routes (preview token required) ────────────────
1030
1263
  if (pathname === "/" || pathname === "/manifest.json") {
1264
+ if (rejectInvalidPreviewToken())
1265
+ return;
1031
1266
  sendJson(res, 200, manifest);
1032
1267
  return;
1033
1268
  }
1034
1269
  if (pathname === "/routes.json") {
1270
+ if (rejectInvalidPreviewToken())
1271
+ return;
1035
1272
  sendJson(res, 200, {
1036
1273
  version: 1,
1037
1274
  sourceType: "localhost",
@@ -1043,7 +1280,11 @@ export async function startDesignConnectBridge(manifest, seedToken) {
1043
1280
  return;
1044
1281
  }
1045
1282
  if (pathname === "/health") {
1046
- sendJson(res, 200, { ok: true, source: manifest.source });
1283
+ sendJson(res, 200, {
1284
+ ok: true,
1285
+ source: manifest.source,
1286
+ appFingerprint: designConnectAppFingerprint(manifest),
1287
+ });
1047
1288
  return;
1048
1289
  }
1049
1290
  if (pathname === "/live-edit-bridge") {
@@ -1051,38 +1292,50 @@ export async function startDesignConnectBridge(manifest, seedToken) {
1051
1292
  sendJson(res, 405, { ok: false, error: "method not allowed" });
1052
1293
  return;
1053
1294
  }
1054
- const tokenHeader = req.headers["x-bridge-token"];
1055
- const providedToken = typeof tokenHeader === "string" ? tokenHeader : "";
1056
- let tokenValid = false;
1057
- try {
1058
- tokenValid =
1059
- providedToken.length === bridgeToken.length &&
1060
- crypto.timingSafeEqual(Buffer.from(providedToken, "utf8"), Buffer.from(bridgeToken, "utf8"));
1061
- }
1062
- catch {
1063
- tokenValid = false;
1064
- }
1065
- if (!tokenValid) {
1066
- sendJson(res, 401, {
1067
- ok: false,
1068
- error: "invalid or missing bridge token",
1069
- });
1295
+ if (rejectInvalidPreviewToken())
1070
1296
  return;
1071
- }
1072
1297
  void (async () => {
1073
1298
  try {
1074
1299
  const raw = await readRequestBody(req);
1075
1300
  const body = JSON.parse(raw);
1076
1301
  const script = typeof body["script"] === "string" ? body["script"] : "";
1077
- if (!script.includes("agent-native:editor-chrome-ready")) {
1302
+ const bridgeKey = typeof body["bridgeKey"] === "string"
1303
+ ? body["bridgeKey"].trim()
1304
+ : "";
1305
+ const installsSupportedDesignBridge = script.includes("agent-native:editor-chrome-ready") ||
1306
+ script.includes("embedded-canvas-pan");
1307
+ if (!installsSupportedDesignBridge) {
1078
1308
  sendJson(res, 400, {
1079
1309
  ok: false,
1080
- error: "script must install the Agent Native editor bridge",
1310
+ error: "script must install an approved Agent Native editor or canvas-pan bridge",
1311
+ });
1312
+ return;
1313
+ }
1314
+ if (bridgeKey &&
1315
+ (!/^[a-zA-Z0-9:._-]+$/.test(bridgeKey) || bridgeKey.length > 128)) {
1316
+ sendJson(res, 400, {
1317
+ ok: false,
1318
+ error: "bridgeKey must be 1-128 safe identifier characters",
1081
1319
  });
1082
1320
  return;
1083
1321
  }
1084
1322
  liveEditBridgeScript = script;
1085
- sendJson(res, 200, { ok: true });
1323
+ if (bridgeKey) {
1324
+ liveEditBridgeScripts.delete(bridgeKey);
1325
+ liveEditBridgeScripts.set(bridgeKey, script);
1326
+ // Bound the in-memory cache. Normal editor usage has one key per
1327
+ // visible screen; 128 also leaves ample room for mode changes.
1328
+ while (liveEditBridgeScripts.size > 128) {
1329
+ const oldest = liveEditBridgeScripts.keys().next().value;
1330
+ if (typeof oldest !== "string")
1331
+ break;
1332
+ liveEditBridgeScripts.delete(oldest);
1333
+ }
1334
+ }
1335
+ sendJson(res, 200, {
1336
+ ok: true,
1337
+ ...(bridgeKey ? { bridgeKey } : {}),
1338
+ });
1086
1339
  }
1087
1340
  catch (err) {
1088
1341
  sendJson(res, 400, {
@@ -1098,19 +1351,33 @@ export async function startDesignConnectBridge(manifest, seedToken) {
1098
1351
  sendJson(res, 405, { ok: false, error: "method not allowed" });
1099
1352
  return;
1100
1353
  }
1354
+ if (rejectInvalidPreviewToken())
1355
+ return;
1101
1356
  void (async () => {
1102
1357
  try {
1103
- const requestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
1104
1358
  const targetUrl = resolvePreviewSnapshotUrl(manifest.devServerUrl, requestUrl.searchParams.get("url") ??
1105
1359
  requestUrl.searchParams.get("path"));
1106
1360
  const snapshot = await fetchPreviewSnapshot(manifest.devServerUrl, targetUrl);
1107
1361
  const includeEditorBridge = requestUrl.searchParams.get("bridge") !== "0";
1362
+ const requestedBridgeKey = requestUrl.searchParams.get("bridgeKey")?.trim() ?? "";
1363
+ const editorBridgeScript = requestedBridgeKey
1364
+ ? (liveEditBridgeScripts.get(requestedBridgeKey) ?? "")
1365
+ : liveEditBridgeScript;
1366
+ if (includeEditorBridge &&
1367
+ requestedBridgeKey &&
1368
+ !editorBridgeScript) {
1369
+ sendJson(res, 409, {
1370
+ ok: false,
1371
+ error: "The requested live-edit bridge script is not registered. Reload the Design frame to register it again.",
1372
+ });
1373
+ return;
1374
+ }
1108
1375
  // The dev server route the SPA must boot on (e.g. "/todo"), taken
1109
1376
  // from the resolved snapshot target rather than the bridge's own
1110
1377
  // "/live-edit" request path.
1111
1378
  const targetParsed = new URL(targetUrl);
1112
1379
  const targetPath = `${targetParsed.pathname}${targetParsed.search}` || "/";
1113
- const html = injectLiveEditBridge(snapshot.html, new URL("/", manifest.bridgeUrl).toString(), includeEditorBridge ? liveEditBridgeScript : "", targetPath);
1380
+ const html = injectLiveEditBridge(snapshot.html, new URL("/", manifest.bridgeUrl).toString(), includeEditorBridge ? editorBridgeScript : "", targetPath);
1114
1381
  sendText(res, snapshot.status >= 400 ? snapshot.status : 200, html, snapshot.contentType.includes("html")
1115
1382
  ? snapshot.contentType
1116
1383
  : "text/html; charset=utf-8");
@@ -1129,9 +1396,10 @@ export async function startDesignConnectBridge(manifest, seedToken) {
1129
1396
  sendJson(res, 405, { ok: false, error: "method not allowed" });
1130
1397
  return;
1131
1398
  }
1399
+ if (rejectInvalidPreviewToken())
1400
+ return;
1132
1401
  void (async () => {
1133
1402
  try {
1134
- const requestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
1135
1403
  const targetUrl = resolvePreviewSnapshotUrl(manifest.devServerUrl, requestUrl.searchParams.get("url") ??
1136
1404
  requestUrl.searchParams.get("path"));
1137
1405
  const snapshot = await fetchPreviewSnapshot(manifest.devServerUrl, targetUrl);
@@ -1163,17 +1431,8 @@ export async function startDesignConnectBridge(manifest, seedToken) {
1163
1431
  return;
1164
1432
  }
1165
1433
  // Authenticate with constant-time comparison to prevent timing attacks.
1166
- const tokenHeader = req.headers["x-bridge-token"];
1167
- const providedToken = typeof tokenHeader === "string" ? tokenHeader : "";
1168
- let tokenValid = false;
1169
- try {
1170
- tokenValid =
1171
- providedToken.length === bridgeToken.length &&
1172
- crypto.timingSafeEqual(Buffer.from(providedToken, "utf8"), Buffer.from(bridgeToken, "utf8"));
1173
- }
1174
- catch {
1175
- tokenValid = false;
1176
- }
1434
+ const providedToken = readHeader(req, "x-bridge-token");
1435
+ const tokenValid = constantTimeTokenMatches(providedToken, bridgeToken);
1177
1436
  if (!tokenValid) {
1178
1437
  sendJson(res, 401, {
1179
1438
  ok: false,
@@ -1366,6 +1625,14 @@ export async function startDesignConnectBridge(manifest, seedToken) {
1366
1625
  return;
1367
1626
  }
1368
1627
  if (req.method === "GET" || req.method === "HEAD") {
1628
+ const sameOriginPreviewSubresource = readHeader(req, "sec-fetch-site") === "same-origin";
1629
+ if (!previewTokenValid && !sameOriginPreviewSubresource) {
1630
+ sendJson(res, 401, {
1631
+ ok: false,
1632
+ error: "invalid or missing preview token",
1633
+ });
1634
+ return;
1635
+ }
1369
1636
  void (async () => {
1370
1637
  try {
1371
1638
  const targetUrl = resolvePreviewProxyUrl(manifest.devServerUrl, req.url);
@@ -1390,7 +1657,7 @@ export async function startDesignConnectBridge(manifest, seedToken) {
1390
1657
  resolve();
1391
1658
  });
1392
1659
  });
1393
- return { server, manifest, bridgeToken };
1660
+ return { server, manifest, bridgeToken, previewToken };
1394
1661
  }
1395
1662
  /**
1396
1663
  * Resolve the design app URL from an explicit value or environment variables.
@@ -1441,7 +1708,7 @@ function resolveAuthToken() {
1441
1708
  */
1442
1709
  export async function registerConnectionWithServer(appUrl, bridge, authToken) {
1443
1710
  const endpoint = `${appUrl}/_agent-native/actions/connect-localhost`;
1444
- const { manifest, bridgeToken } = bridge;
1711
+ const { manifest, bridgeToken, previewToken } = bridge;
1445
1712
  const payload = {
1446
1713
  devServerUrl: manifest.devServerUrl,
1447
1714
  bridgeUrl: manifest.bridgeUrl,
@@ -1459,6 +1726,7 @@ export async function registerConnectionWithServer(appUrl, bridge, authToken) {
1459
1726
  // row. grant-localhost-write-consent then reads it from the row instead of
1460
1727
  // minting its own unrelated token, which would always produce a 401.
1461
1728
  bridgeToken,
1729
+ previewToken,
1462
1730
  status: "connected",
1463
1731
  };
1464
1732
  const headers = {
@@ -1507,6 +1775,10 @@ Options:
1507
1775
  /visual-edit flow so the bridge and the user's stored
1508
1776
  connection token match with no self-registration.
1509
1777
  (also reads AGENT_NATIVE_BRIDGE_TOKEN env)
1778
+ --preview-token <token> Adopt the paired read-only browser preview token.
1779
+ Optional when --bridge-token is present: compatible
1780
+ clients derive the same one-way token automatically.
1781
+ (also reads AGENT_NATIVE_PREVIEW_TOKEN env)
1510
1782
  --daemon Start the bridge detached, wait for /health, then exit
1511
1783
  --json Print the manifest JSON and exit
1512
1784
  --once Prepare/scaffold the manifest and exit
@@ -1553,13 +1825,18 @@ function resolveCurrentCliInvocation(argv) {
1553
1825
  args: [...cliPrefix, "design", ...removeDaemonFlag(argv)],
1554
1826
  };
1555
1827
  }
1556
- async function startDetachedDesignBridge(argv, manifest) {
1828
+ async function startDetachedDesignBridge(argv, manifest, previewToken) {
1557
1829
  if (await waitForBridgeHealth(manifest.bridgeUrl, 800)) {
1558
- const runningManifest = await fetchRunningBridgeManifest(manifest.bridgeUrl);
1559
- if (runningManifest &&
1560
- designConnectManifestsTargetSameApp(runningManifest, manifest)) {
1830
+ const [runningManifest, runningFingerprint] = await Promise.all([
1831
+ fetchRunningBridgeManifest(manifest.bridgeUrl, previewToken),
1832
+ fetchRunningBridgeFingerprint(manifest.bridgeUrl),
1833
+ ]);
1834
+ const fingerprintMatches = runningFingerprint === designConnectAppFingerprint(manifest);
1835
+ if ((runningManifest &&
1836
+ designConnectManifestsTargetSameApp(runningManifest, manifest)) ||
1837
+ fingerprintMatches) {
1561
1838
  console.error(`Design localhost bridge already running at ${manifest.bridgeUrl}`);
1562
- console.log(JSON.stringify(runningManifest, null, 2));
1839
+ console.log(JSON.stringify(runningManifest ?? manifest, null, 2));
1563
1840
  return 0;
1564
1841
  }
1565
1842
  console.error([
@@ -1607,21 +1884,29 @@ export async function runDesign(argv) {
1607
1884
  return 0;
1608
1885
  }
1609
1886
  const manifest = await prepareDesignConnectManifest(parsed);
1887
+ const seedBridgeToken = parsed.bridgeToken || process.env["AGENT_NATIVE_BRIDGE_TOKEN"] || undefined;
1888
+ const seedPreviewToken = parsed.previewToken ||
1889
+ process.env["AGENT_NATIVE_PREVIEW_TOKEN"] ||
1890
+ (seedBridgeToken ? deriveDesignPreviewToken(seedBridgeToken) : undefined);
1891
+ const appUrl = resolveAppUrl(parsed.appUrl);
1610
1892
  if (parsed.daemon) {
1611
- return startDetachedDesignBridge(argv, manifest);
1893
+ return startDetachedDesignBridge(argv, manifest, seedPreviewToken);
1612
1894
  }
1613
1895
  if (parsed.json || parsed.once || parsed.dryRun) {
1614
1896
  console.log(JSON.stringify(manifest, null, 2));
1615
1897
  return 0;
1616
1898
  }
1617
- const seedToken = parsed.bridgeToken || process.env["AGENT_NATIVE_BRIDGE_TOKEN"] || undefined;
1618
- const bridge = await startDesignConnectBridge(manifest, seedToken);
1899
+ const bridge = await startDesignConnectBridge(manifest, {
1900
+ bridgeToken: seedBridgeToken,
1901
+ previewToken: seedPreviewToken,
1902
+ allowedOrigins: appUrl ? [appUrl] : [],
1903
+ });
1619
1904
  console.error("Design localhost bridge running");
1620
1905
  console.error(`Bridge: ${manifest.bridgeUrl}`);
1621
1906
  console.error(`Manifest: ${manifest.bridgeUrl}/manifest.json`);
1622
1907
  console.error(`Routes: ${manifest.routeCount}`);
1623
1908
  console.error(`Dev URL: ${manifest.devServerUrl}`);
1624
- if (seedToken) {
1909
+ if (seedBridgeToken) {
1625
1910
  // Server already stored this token on the row; bridge matches it, so no
1626
1911
  // self-registration needed. Zero-config path for the remote-MCP flow.
1627
1912
  console.error("[design connect] Using server-provided bridge token; skipping self-registration.");
@@ -1629,13 +1914,12 @@ export async function runDesign(argv) {
1629
1914
  else {
1630
1915
  // No seed: fall back to self-registration — POST the minted token to
1631
1916
  // connect-localhost. Needs an auth token in env or it 401s (the old gap).
1632
- const appUrl = resolveAppUrl(parsed.appUrl);
1633
1917
  if (appUrl) {
1634
1918
  void registerConnectionWithServer(appUrl, bridge, resolveAuthToken());
1635
1919
  }
1636
1920
  else {
1637
1921
  // No token source at all — warn rather than 401 silently at edit time.
1638
- console.error("[design connect] No bridge token or app URL resolved (pass --bridge-token, or --app-url / AGENT_NATIVE_URL); skipping self-registration — live-edit will fail to authorize.");
1922
+ console.error("[design connect] No bridge token or app URL resolved (pass --bridge-token, or --app-url / AGENT_NATIVE_URL); skipping self-registration — browser preview and live-edit will fail to authorize.");
1639
1923
  }
1640
1924
  }
1641
1925
  return await new Promise((resolve) => {