@agent-native/core 0.113.0 → 0.114.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1638) hide show
  1. package/agent-native.eject.json +164 -0
  2. package/corpus/README.md +3 -3
  3. package/corpus/core/CHANGELOG.md +20 -0
  4. package/corpus/core/README.md +61 -174
  5. package/corpus/core/agent-native.eject.json +164 -0
  6. package/corpus/core/docs/content/agent-native-toolkit.mdx +38 -0
  7. package/corpus/core/docs/content/package-lifecycle.mdx +54 -4
  8. package/corpus/core/package.json +15 -2
  9. package/corpus/core/src/cli/eject.ts +1594 -0
  10. package/corpus/core/src/cli/index.ts +16 -0
  11. package/corpus/core/src/client/build-compatibility.ts +77 -0
  12. package/corpus/core/src/client/index.ts +4 -0
  13. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +8 -2
  14. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +7 -2
  15. package/corpus/core/src/client/resources/ResourcesPanel.tsx +17 -3
  16. package/corpus/core/src/client/resources/index.ts +19 -0
  17. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +9 -1
  18. package/corpus/core/src/client/resources/runtime.ts +1 -0
  19. package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
  20. package/corpus/core/src/client/setup-connections/catalog.ts +6 -0
  21. package/corpus/core/src/client/setup-connections/index.ts +1 -0
  22. package/corpus/core/src/client/setup-connections/runtime.ts +1 -0
  23. package/corpus/core/src/client/use-action.ts +32 -1
  24. package/corpus/core/src/connections/catalog.ts +41 -24
  25. package/corpus/core/src/connections/index.ts +1 -0
  26. package/corpus/core/src/connections/runtime.ts +1 -0
  27. package/corpus/core/src/deploy/build.ts +4 -1
  28. package/corpus/core/src/eject/provider-api-definitions.ts +193 -0
  29. package/corpus/core/src/eject/remote-mcp-presets.ts +137 -0
  30. package/corpus/core/src/eject/setup-readiness-ui.tsx +29 -0
  31. package/corpus/core/src/eject/workspace-connections.ts +92 -0
  32. package/corpus/core/src/index.ts +1 -0
  33. package/corpus/core/src/integrations/adapter-overrides.ts +12 -0
  34. package/corpus/core/src/integrations/eject/messaging-adapters.ts +85 -0
  35. package/corpus/core/src/integrations/index.ts +5 -1
  36. package/corpus/core/src/integrations/plugin.ts +34 -12
  37. package/corpus/core/src/integrations/runtime.ts +1 -0
  38. package/corpus/core/src/integrations/types.ts +3 -1
  39. package/corpus/core/src/org/context.ts +56 -30
  40. package/corpus/core/src/package-lifecycle/eject-manifest.ts +345 -0
  41. package/corpus/core/src/package-lifecycle/index.ts +1 -0
  42. package/corpus/core/src/provider-api/index.ts +46 -12
  43. package/corpus/core/src/provider-api/runtime.ts +1 -0
  44. package/corpus/core/src/server/action-routes.ts +52 -2
  45. package/corpus/core/src/server/auth-plugin.ts +9 -4
  46. package/corpus/core/src/server/better-auth-instance.ts +5 -1
  47. package/corpus/core/src/setup-connections/index.ts +1 -0
  48. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  49. package/corpus/core/src/shared/merge-by-id.ts +14 -0
  50. package/corpus/core/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  51. package/corpus/core/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  52. package/corpus/core/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  53. package/corpus/core/src/templates/default/AGENTS.md +6 -5
  54. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  55. package/corpus/core/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  56. package/corpus/core/src/templates/headless/AGENTS.md +5 -4
  57. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  58. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  59. package/corpus/core/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  60. package/corpus/core/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  61. package/corpus/core/src/templates/workspace-core/AGENTS.md +4 -4
  62. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -4
  63. package/corpus/core/src/vite/client.ts +42 -2
  64. package/corpus/templates/analytics/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  65. package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +9 -6
  66. package/corpus/templates/assets/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  67. package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +9 -6
  68. package/corpus/templates/brain/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  69. package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +9 -6
  70. package/corpus/templates/brain/actions/remediate-legacy-capture-audiences.ts +167 -0
  71. package/corpus/templates/brain/app/lib/brain-chat-readiness.ts +6 -0
  72. package/corpus/templates/brain/app/routes/_index.tsx +8 -0
  73. package/corpus/templates/brain/app/routes.ts +1 -0
  74. package/corpus/templates/brain/changelog/2026-07-19-ask-starts-immediately-when-builder-s-managed-ai-connection-.md +6 -0
  75. package/corpus/templates/brain/changelog/2026-07-19-slack-and-granola-backfills-now-process-captures-concurrentl.md +6 -0
  76. package/corpus/templates/brain/changelog/2026-07-19-source-review-settings-now-control-knowledge-approval.md +6 -0
  77. package/corpus/templates/brain/changelog/2026-07-19-the-manage-agent-page-now-opens-correctly-from-brain-navigat.md +6 -0
  78. package/corpus/templates/brain/server/db/schema.ts +3 -0
  79. package/corpus/templates/brain/server/lib/brain.ts +41 -2
  80. package/corpus/templates/brain/server/lib/connectors.ts +379 -113
  81. package/corpus/templates/brain/server/lib/meeting-audience.ts +38 -0
  82. package/corpus/templates/brain/server/plugins/db.ts +10 -0
  83. package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +6 -4
  84. package/corpus/templates/calendar/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  85. package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +9 -6
  86. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  87. package/corpus/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  88. package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  89. package/corpus/templates/chat/AGENTS.md +3 -2
  90. package/corpus/templates/clips/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  91. package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +9 -6
  92. package/corpus/templates/content/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  93. package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +9 -6
  94. package/corpus/templates/content/AGENTS.md +2 -0
  95. package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
  96. package/corpus/templates/content/actions/_content-document-access.ts +21 -0
  97. package/corpus/templates/content/actions/_content-favorites.ts +91 -0
  98. package/corpus/templates/content/actions/_content-files.ts +79 -23
  99. package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
  100. package/corpus/templates/content/actions/_content-space-access.ts +15 -17
  101. package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
  102. package/corpus/templates/content/actions/_content-spaces.ts +364 -167
  103. package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
  104. package/corpus/templates/content/actions/_database-utils.ts +249 -15
  105. package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
  106. package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
  107. package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
  108. package/corpus/templates/content/actions/_property-utils.ts +18 -9
  109. package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
  110. package/corpus/templates/content/actions/configure-document-property.ts +3 -0
  111. package/corpus/templates/content/actions/create-content-database.ts +2 -2
  112. package/corpus/templates/content/actions/create-content-space.ts +34 -0
  113. package/corpus/templates/content/actions/create-document.ts +4 -1
  114. package/corpus/templates/content/actions/delete-content-space.ts +19 -0
  115. package/corpus/templates/content/actions/delete-database-items.ts +28 -1
  116. package/corpus/templates/content/actions/delete-document-property.ts +3 -0
  117. package/corpus/templates/content/actions/delete-document.ts +39 -2
  118. package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
  119. package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
  120. package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
  121. package/corpus/templates/content/actions/get-content-database.ts +11 -2
  122. package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
  123. package/corpus/templates/content/actions/get-document.ts +32 -7
  124. package/corpus/templates/content/actions/import-content-source.ts +33 -1
  125. package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
  126. package/corpus/templates/content/actions/list-documents.ts +39 -7
  127. package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
  128. package/corpus/templates/content/actions/set-document-property.ts +41 -23
  129. package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
  130. package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
  131. package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
  132. package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
  133. package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
  134. package/corpus/templates/content/actions/update-document.ts +158 -66
  135. package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
  136. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
  137. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
  138. package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
  139. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
  140. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
  141. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
  142. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
  143. package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
  144. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
  145. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
  146. package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
  147. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
  148. package/corpus/templates/content/app/entry.client.tsx +5 -1
  149. package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
  150. package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
  151. package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
  152. package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
  153. package/corpus/templates/content/app/i18n-data.ts +243 -20
  154. package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
  155. package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
  156. package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
  157. package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
  158. package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
  159. package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
  160. package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
  161. package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
  162. package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
  163. package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
  164. package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
  165. package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
  166. package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
  167. package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
  168. package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
  169. package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
  170. package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
  171. package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
  172. package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
  173. package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
  174. package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
  175. package/corpus/templates/content/e2e/global-setup.ts +3 -2
  176. package/corpus/templates/content/parity/matrix.md +28 -28
  177. package/corpus/templates/content/parity/matrix.ts +8 -2
  178. package/corpus/templates/content/server/db/index.ts +1 -0
  179. package/corpus/templates/content/server/db/schema.ts +10 -0
  180. package/corpus/templates/content/server/lib/documents.ts +12 -7
  181. package/corpus/templates/content/server/plugins/db.ts +17 -0
  182. package/corpus/templates/content/shared/api.ts +9 -0
  183. package/corpus/templates/content/shared/database-form.ts +11 -2
  184. package/corpus/templates/content/vite.config.ts +1 -0
  185. package/corpus/templates/design/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  186. package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +9 -6
  187. package/corpus/templates/dispatch/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  188. package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +9 -6
  189. package/corpus/templates/forms/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  190. package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +9 -6
  191. package/corpus/templates/macros/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  192. package/corpus/templates/mail/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  193. package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +9 -6
  194. package/corpus/templates/plan/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  195. package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +9 -6
  196. package/corpus/templates/slides/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  197. package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +9 -6
  198. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +15 -13
  199. package/corpus/templates/tasks/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  200. package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +9 -6
  201. package/corpus/toolkit/CHANGELOG.md +6 -0
  202. package/corpus/toolkit/agent-native.eject.json +411 -0
  203. package/corpus/toolkit/package.json +5 -2
  204. package/dist/cli/eject.d.ts +21 -0
  205. package/dist/cli/eject.d.ts.map +1 -0
  206. package/dist/cli/eject.js +1145 -0
  207. package/dist/cli/eject.js.map +1 -0
  208. package/dist/cli/index.js +15 -0
  209. package/dist/cli/index.js.map +1 -1
  210. package/dist/client/build-compatibility.d.ts +11 -0
  211. package/dist/client/build-compatibility.d.ts.map +1 -0
  212. package/dist/client/build-compatibility.js +56 -0
  213. package/dist/client/build-compatibility.js.map +1 -0
  214. package/dist/client/index.d.ts +1 -1
  215. package/dist/client/index.d.ts.map +1 -1
  216. package/dist/client/index.js +1 -1
  217. package/dist/client/index.js.map +1 -1
  218. package/dist/client/resources/McpConnectionSuggestion.d.ts +4 -3
  219. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  220. package/dist/client/resources/McpConnectionSuggestion.js +3 -3
  221. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  222. package/dist/client/resources/McpIntegrationDialog.d.ts +4 -3
  223. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  224. package/dist/client/resources/McpIntegrationDialog.js +2 -2
  225. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  226. package/dist/client/resources/ResourcesPanel.d.ts +4 -1
  227. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  228. package/dist/client/resources/ResourcesPanel.js +9 -7
  229. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  230. package/dist/client/resources/index.d.ts +3 -0
  231. package/dist/client/resources/index.d.ts.map +1 -1
  232. package/dist/client/resources/index.js +3 -0
  233. package/dist/client/resources/index.js.map +1 -1
  234. package/dist/client/resources/mcp-integration-catalog.d.ts +2 -1
  235. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  236. package/dist/client/resources/mcp-integration-catalog.js +6 -2
  237. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  238. package/dist/client/resources/runtime.d.ts +2 -0
  239. package/dist/client/resources/runtime.d.ts.map +1 -0
  240. package/dist/client/resources/runtime.js +2 -0
  241. package/dist/client/resources/runtime.js.map +1 -0
  242. package/dist/client/route-chunk-recovery/index.d.ts +1 -0
  243. package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
  244. package/dist/client/route-chunk-recovery/index.js +1 -0
  245. package/dist/client/route-chunk-recovery/index.js.map +1 -1
  246. package/dist/client/setup-connections/catalog.d.ts +2 -0
  247. package/dist/client/setup-connections/catalog.d.ts.map +1 -0
  248. package/dist/client/setup-connections/catalog.js +7 -0
  249. package/dist/client/setup-connections/catalog.js.map +1 -0
  250. package/dist/client/setup-connections/index.d.ts +1 -0
  251. package/dist/client/setup-connections/index.d.ts.map +1 -1
  252. package/dist/client/setup-connections/index.js +1 -0
  253. package/dist/client/setup-connections/index.js.map +1 -1
  254. package/dist/client/setup-connections/runtime.d.ts +2 -0
  255. package/dist/client/setup-connections/runtime.d.ts.map +1 -0
  256. package/dist/client/setup-connections/runtime.js +2 -0
  257. package/dist/client/setup-connections/runtime.js.map +1 -0
  258. package/dist/client/use-action.d.ts.map +1 -1
  259. package/dist/client/use-action.js +19 -1
  260. package/dist/client/use-action.js.map +1 -1
  261. package/dist/collab/awareness.d.ts +2 -2
  262. package/dist/collab/awareness.d.ts.map +1 -1
  263. package/dist/collab/struct-routes.d.ts +1 -1
  264. package/dist/connections/catalog.d.ts +5 -3
  265. package/dist/connections/catalog.d.ts.map +1 -1
  266. package/dist/connections/catalog.js +16 -8
  267. package/dist/connections/catalog.js.map +1 -1
  268. package/dist/connections/index.d.ts +1 -1
  269. package/dist/connections/index.d.ts.map +1 -1
  270. package/dist/connections/index.js +1 -1
  271. package/dist/connections/index.js.map +1 -1
  272. package/dist/connections/runtime.d.ts +2 -0
  273. package/dist/connections/runtime.d.ts.map +1 -0
  274. package/dist/connections/runtime.js +2 -0
  275. package/dist/connections/runtime.js.map +1 -0
  276. package/dist/deploy/build.js +2 -1
  277. package/dist/deploy/build.js.map +1 -1
  278. package/dist/eject/provider-api-definitions.d.ts +34 -0
  279. package/dist/eject/provider-api-definitions.d.ts.map +1 -0
  280. package/dist/eject/provider-api-definitions.js +88 -0
  281. package/dist/eject/provider-api-definitions.js.map +1 -0
  282. package/dist/eject/remote-mcp-presets.d.ts +17 -0
  283. package/dist/eject/remote-mcp-presets.d.ts.map +1 -0
  284. package/dist/eject/remote-mcp-presets.js +81 -0
  285. package/dist/eject/remote-mcp-presets.js.map +1 -0
  286. package/dist/eject/setup-readiness-ui.d.ts +7 -0
  287. package/dist/eject/setup-readiness-ui.d.ts.map +1 -0
  288. package/dist/eject/setup-readiness-ui.js +13 -0
  289. package/dist/eject/setup-readiness-ui.js.map +1 -0
  290. package/dist/eject/workspace-connections.d.ts +11 -0
  291. package/dist/eject/workspace-connections.d.ts.map +1 -0
  292. package/dist/eject/workspace-connections.js +47 -0
  293. package/dist/eject/workspace-connections.js.map +1 -0
  294. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  295. package/dist/index.d.ts +1 -1
  296. package/dist/index.d.ts.map +1 -1
  297. package/dist/index.js +1 -1
  298. package/dist/index.js.map +1 -1
  299. package/dist/integrations/adapter-overrides.d.ts +3 -0
  300. package/dist/integrations/adapter-overrides.d.ts.map +1 -0
  301. package/dist/integrations/adapter-overrides.js +7 -0
  302. package/dist/integrations/adapter-overrides.js.map +1 -0
  303. package/dist/integrations/eject/messaging-adapters.d.ts +12 -0
  304. package/dist/integrations/eject/messaging-adapters.d.ts.map +1 -0
  305. package/dist/integrations/eject/messaging-adapters.js +49 -0
  306. package/dist/integrations/eject/messaging-adapters.js.map +1 -0
  307. package/dist/integrations/index.d.ts +3 -2
  308. package/dist/integrations/index.d.ts.map +1 -1
  309. package/dist/integrations/index.js +2 -1
  310. package/dist/integrations/index.js.map +1 -1
  311. package/dist/integrations/plugin.d.ts +32 -1
  312. package/dist/integrations/plugin.d.ts.map +1 -1
  313. package/dist/integrations/plugin.js +19 -12
  314. package/dist/integrations/plugin.js.map +1 -1
  315. package/dist/integrations/runtime.d.ts +2 -0
  316. package/dist/integrations/runtime.d.ts.map +1 -0
  317. package/dist/integrations/runtime.js +2 -0
  318. package/dist/integrations/runtime.js.map +1 -0
  319. package/dist/integrations/types.d.ts +3 -1
  320. package/dist/integrations/types.d.ts.map +1 -1
  321. package/dist/integrations/types.js.map +1 -1
  322. package/dist/notifications/routes.d.ts +2 -2
  323. package/dist/observability/routes.d.ts +5 -5
  324. package/dist/org/context.d.ts +4 -2
  325. package/dist/org/context.d.ts.map +1 -1
  326. package/dist/org/context.js +44 -22
  327. package/dist/org/context.js.map +1 -1
  328. package/dist/package-lifecycle/eject-manifest.d.ts +43 -0
  329. package/dist/package-lifecycle/eject-manifest.d.ts.map +1 -0
  330. package/dist/package-lifecycle/eject-manifest.js +219 -0
  331. package/dist/package-lifecycle/eject-manifest.js.map +1 -0
  332. package/dist/package-lifecycle/index.d.ts +1 -0
  333. package/dist/package-lifecycle/index.d.ts.map +1 -1
  334. package/dist/package-lifecycle/index.js +1 -0
  335. package/dist/package-lifecycle/index.js.map +1 -1
  336. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  337. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  338. package/dist/provider-api/index.d.ts +6 -2
  339. package/dist/provider-api/index.d.ts.map +1 -1
  340. package/dist/provider-api/index.js +25 -12
  341. package/dist/provider-api/index.js.map +1 -1
  342. package/dist/provider-api/runtime.d.ts +2 -0
  343. package/dist/provider-api/runtime.d.ts.map +1 -0
  344. package/dist/provider-api/runtime.js +2 -0
  345. package/dist/provider-api/runtime.js.map +1 -0
  346. package/dist/resources/handlers.d.ts +1 -1
  347. package/dist/server/action-routes.d.ts.map +1 -1
  348. package/dist/server/action-routes.js +32 -2
  349. package/dist/server/action-routes.js.map +1 -1
  350. package/dist/server/auth-plugin.d.ts.map +1 -1
  351. package/dist/server/auth-plugin.js +9 -5
  352. package/dist/server/auth-plugin.js.map +1 -1
  353. package/dist/server/better-auth-instance.d.ts.map +1 -1
  354. package/dist/server/better-auth-instance.js +4 -1
  355. package/dist/server/better-auth-instance.js.map +1 -1
  356. package/dist/setup-connections/index.d.ts +1 -1
  357. package/dist/setup-connections/index.d.ts.map +1 -1
  358. package/dist/setup-connections/index.js +1 -1
  359. package/dist/setup-connections/index.js.map +1 -1
  360. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  361. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  362. package/dist/shared/mcp-embed-headers.js +1 -1
  363. package/dist/shared/mcp-embed-headers.js.map +1 -1
  364. package/dist/shared/merge-by-id.d.ts +5 -0
  365. package/dist/shared/merge-by-id.d.ts.map +1 -0
  366. package/dist/shared/merge-by-id.js +7 -0
  367. package/dist/shared/merge-by-id.js.map +1 -0
  368. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  369. package/dist/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  370. package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  371. package/dist/templates/chat/AGENTS.md +3 -2
  372. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  373. package/dist/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  374. package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  375. package/dist/templates/default/AGENTS.md +6 -5
  376. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  377. package/dist/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  378. package/dist/templates/headless/AGENTS.md +5 -4
  379. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  380. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  381. package/dist/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  382. package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  383. package/dist/templates/workspace-core/AGENTS.md +4 -4
  384. package/dist/templates/workspace-root/AGENTS.md +4 -4
  385. package/dist/vite/client.d.ts +6 -0
  386. package/dist/vite/client.d.ts.map +1 -1
  387. package/dist/vite/client.js +30 -2
  388. package/dist/vite/client.js.map +1 -1
  389. package/docs/content/agent-native-toolkit.mdx +38 -0
  390. package/docs/content/package-lifecycle.mdx +54 -4
  391. package/package.json +15 -2
  392. package/src/a2a/agent-card.ts +83 -0
  393. package/src/a2a/artifact-response.ts +1648 -0
  394. package/src/a2a/auth-policy.ts +66 -0
  395. package/src/a2a/caller-auth.ts +102 -0
  396. package/src/a2a/client.ts +876 -0
  397. package/src/a2a/correlation.ts +50 -0
  398. package/src/a2a/handlers.ts +1285 -0
  399. package/src/a2a/index.ts +50 -0
  400. package/src/a2a/invoke.ts +348 -0
  401. package/src/a2a/response-text.ts +53 -0
  402. package/src/a2a/server.ts +586 -0
  403. package/src/a2a/task-store.ts +917 -0
  404. package/src/a2a/types.ts +221 -0
  405. package/src/a2a-claims.ts +52 -0
  406. package/src/action-change-marker.ts +74 -0
  407. package/src/action-ui.ts +38 -0
  408. package/src/action.ts +1406 -0
  409. package/src/adapters/cli/index.ts +6 -0
  410. package/src/adapters/cli/registry.ts +73 -0
  411. package/src/adapters/cli/shell-adapter.ts +92 -0
  412. package/src/adapters/cli/types.ts +27 -0
  413. package/src/agent/action-continuation-guidance.ts +38 -0
  414. package/src/agent/app-model-defaults.ts +201 -0
  415. package/src/agent/context-xray/actions/context-evict.ts +56 -0
  416. package/src/agent/context-xray/actions/context-manifest-get.ts +75 -0
  417. package/src/agent/context-xray/actions/context-pin.ts +56 -0
  418. package/src/agent/context-xray/actions/context-preview-get.ts +134 -0
  419. package/src/agent/context-xray/actions/context-report.ts +75 -0
  420. package/src/agent/context-xray/actions/context-restore.ts +51 -0
  421. package/src/agent/context-xray/actions/errors.ts +40 -0
  422. package/src/agent/context-xray/apply-directives.ts +137 -0
  423. package/src/agent/context-xray/directives-store.ts +178 -0
  424. package/src/agent/context-xray/identity.ts +37 -0
  425. package/src/agent/context-xray/manifest.ts +284 -0
  426. package/src/agent/context-xray/migrations.ts +32 -0
  427. package/src/agent/context-xray/plugin.ts +23 -0
  428. package/src/agent/context-xray/schema.ts +17 -0
  429. package/src/agent/context-xray/segments.ts +194 -0
  430. package/src/agent/context-xray/tokenize.ts +93 -0
  431. package/src/agent/default-model.ts +5 -0
  432. package/src/agent/durable-background.ts +547 -0
  433. package/src/agent/engine/ai-sdk-engine.ts +565 -0
  434. package/src/agent/engine/anthropic-engine.ts +320 -0
  435. package/src/agent/engine/builder-engine.ts +1110 -0
  436. package/src/agent/engine/builder-gateway-headers.ts +44 -0
  437. package/src/agent/engine/builtin.ts +122 -0
  438. package/src/agent/engine/context-directives-transform.ts +73 -0
  439. package/src/agent/engine/continuation-dispatch-retry.ts +168 -0
  440. package/src/agent/engine/credential-errors.ts +69 -0
  441. package/src/agent/engine/index.ts +54 -0
  442. package/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  443. package/src/agent/engine/output-tokens.ts +181 -0
  444. package/src/agent/engine/provider-env-vars.ts +69 -0
  445. package/src/agent/engine/registry.ts +853 -0
  446. package/src/agent/engine/tool-call-journal-seed.ts +85 -0
  447. package/src/agent/engine/translate-ai-sdk.ts +407 -0
  448. package/src/agent/engine/translate-anthropic.ts +611 -0
  449. package/src/agent/engine/types.ts +310 -0
  450. package/src/agent/harness/acp-adapter.ts +893 -0
  451. package/src/agent/harness/acp-builtin.ts +70 -0
  452. package/src/agent/harness/ai-sdk-adapter.ts +509 -0
  453. package/src/agent/harness/background.ts +365 -0
  454. package/src/agent/harness/builtin.ts +50 -0
  455. package/src/agent/harness/index.ts +86 -0
  456. package/src/agent/harness/lifecycle.ts +498 -0
  457. package/src/agent/harness/registry.ts +97 -0
  458. package/src/agent/harness/runner.ts +182 -0
  459. package/src/agent/harness/store.ts +466 -0
  460. package/src/agent/harness/translate.ts +130 -0
  461. package/src/agent/harness/types.ts +129 -0
  462. package/src/agent/index.ts +32 -0
  463. package/src/agent/loop-settings.ts +186 -0
  464. package/src/agent/model-config.ts +363 -0
  465. package/src/agent/observational-memory/compactor.ts +78 -0
  466. package/src/agent/observational-memory/config.ts +78 -0
  467. package/src/agent/observational-memory/index.ts +79 -0
  468. package/src/agent/observational-memory/internal-run.ts +89 -0
  469. package/src/agent/observational-memory/message-text.ts +49 -0
  470. package/src/agent/observational-memory/migrations.ts +44 -0
  471. package/src/agent/observational-memory/observer.ts +128 -0
  472. package/src/agent/observational-memory/plugin.ts +35 -0
  473. package/src/agent/observational-memory/prompts.ts +53 -0
  474. package/src/agent/observational-memory/read.ts +131 -0
  475. package/src/agent/observational-memory/reflector.ts +119 -0
  476. package/src/agent/observational-memory/schema.ts +48 -0
  477. package/src/agent/observational-memory/store.ts +275 -0
  478. package/src/agent/observational-memory/types.ts +65 -0
  479. package/src/agent/processors.ts +209 -0
  480. package/src/agent/production-agent.ts +8254 -0
  481. package/src/agent/run-loop-with-resume.ts +394 -0
  482. package/src/agent/run-manager.ts +1825 -0
  483. package/src/agent/run-ownership.ts +75 -0
  484. package/src/agent/run-store.ts +2601 -0
  485. package/src/agent/runtime-context.ts +169 -0
  486. package/src/agent/thread-data-builder.ts +1584 -0
  487. package/src/agent/tool-call-journal.ts +368 -0
  488. package/src/agent/tool-error-redaction.ts +52 -0
  489. package/src/agent/tool-result-images.ts +178 -0
  490. package/src/agent/tool-search.ts +376 -0
  491. package/src/agent/types.ts +372 -0
  492. package/src/agent-native/index.ts +198 -0
  493. package/src/agent-web/config.ts +253 -0
  494. package/src/agent-web/generator.ts +382 -0
  495. package/src/agent-web/index.ts +36 -0
  496. package/src/appearance/actions/change-appearance.ts +35 -0
  497. package/src/application-state/emitter.ts +81 -0
  498. package/src/application-state/handlers.ts +121 -0
  499. package/src/application-state/index.ts +44 -0
  500. package/src/application-state/script-helpers.ts +155 -0
  501. package/src/application-state/store.ts +274 -0
  502. package/src/assets/branding/agent-native.icon/Assets/background.svg +3 -0
  503. package/src/assets/branding/agent-native.icon/Assets/foreground.svg +12 -0
  504. package/src/assets/branding/agent-native.icon/icon.json +49 -0
  505. package/src/assets/branding/favicon.png +0 -0
  506. package/src/assets/branding/favicon.svg +1 -0
  507. package/src/assets/branding/icon-on-dark.svg +10 -0
  508. package/src/assets/branding/icon-on-light.svg +10 -0
  509. package/src/assets/branding/mac-app-icon.svg +1 -0
  510. package/src/assets/branding/slack-bot/agent-native-1024.png +0 -0
  511. package/src/assets/branding/slack-bot/agent-native-512.png +0 -0
  512. package/src/assets/branding/tray-icon.svg +6 -0
  513. package/src/assets/branding/wordmark-on-dark.svg +21 -0
  514. package/src/assets/branding/wordmark-on-light.svg +21 -0
  515. package/src/assets/fonts/LICENSE_LIBERATION +96 -0
  516. package/src/assets/fonts/LICENSE_NOTO_NASKH_ARABIC +93 -0
  517. package/src/assets/fonts/LiberationSans-Bold.ttf +0 -0
  518. package/src/assets/fonts/LiberationSans-Regular.ttf +0 -0
  519. package/src/assets/fonts/NotoNaskhArabic-Variable.ttf +0 -0
  520. package/src/audit/actions/export-audit-events.ts +162 -0
  521. package/src/audit/actions/get-audit-event.ts +24 -0
  522. package/src/audit/actions/list-audit-events.ts +66 -0
  523. package/src/audit/cleanup-job.ts +100 -0
  524. package/src/audit/config.ts +90 -0
  525. package/src/audit/index.ts +43 -0
  526. package/src/audit/record.ts +180 -0
  527. package/src/audit/redact.ts +109 -0
  528. package/src/audit/store.ts +346 -0
  529. package/src/audit/types.ts +141 -0
  530. package/src/automation/index.ts +802 -0
  531. package/src/brand-kit/brand-signals.ts +124 -0
  532. package/src/brand-kit/fig/index.ts +64 -0
  533. package/src/brand-kit/index.ts +72 -0
  534. package/src/brand-kit/types.ts +113 -0
  535. package/src/browser-sessions/actions.ts +302 -0
  536. package/src/browser-sessions/routes.ts +234 -0
  537. package/src/browser-sessions/store.ts +679 -0
  538. package/src/browser-sessions/types.ts +84 -0
  539. package/src/changelog/parse.ts +328 -0
  540. package/src/chat-threads/emitter.ts +22 -0
  541. package/src/chat-threads/schema.ts +43 -0
  542. package/src/chat-threads/store.ts +1311 -0
  543. package/src/checkpoints/index.ts +15 -0
  544. package/src/checkpoints/service.ts +142 -0
  545. package/src/checkpoints/store.ts +153 -0
  546. package/src/cli/add.ts +425 -0
  547. package/src/cli/agent.ts +424 -0
  548. package/src/cli/agents.ts +122 -0
  549. package/src/cli/app-skill.ts +1455 -0
  550. package/src/cli/atomic-json-file.ts +324 -0
  551. package/src/cli/audit-agent-web.ts +324 -0
  552. package/src/cli/changelog.ts +199 -0
  553. package/src/cli/claude-code-participant.ts +515 -0
  554. package/src/cli/code-agent-commands.ts +270 -0
  555. package/src/cli/code-agent-connector.ts +1109 -0
  556. package/src/cli/code-agent-executor.ts +1970 -0
  557. package/src/cli/code-agent-output-smoother.ts +140 -0
  558. package/src/cli/code-agent-runs.concurrent-worker.ts +33 -0
  559. package/src/cli/code-agent-runs.ts +548 -0
  560. package/src/cli/code.ts +1805 -0
  561. package/src/cli/codex-cli-participant.ts +496 -0
  562. package/src/cli/connect.ts +2714 -0
  563. package/src/cli/content-local.ts +442 -0
  564. package/src/cli/context-xray-local.ts +2193 -0
  565. package/src/cli/create-workspace.ts +30 -0
  566. package/src/cli/create.ts +2247 -0
  567. package/src/cli/deploy-build.ts +50 -0
  568. package/src/cli/design-connect.ts +3187 -0
  569. package/src/cli/doctor.ts +472 -0
  570. package/src/cli/eject.ts +1594 -0
  571. package/src/cli/eval.ts +134 -0
  572. package/src/cli/gateway-helpers.ts +70 -0
  573. package/src/cli/index.ts +1217 -0
  574. package/src/cli/info.ts +105 -0
  575. package/src/cli/invoke.ts +288 -0
  576. package/src/cli/mcp-config-writers.ts +857 -0
  577. package/src/cli/mcp.ts +594 -0
  578. package/src/cli/migrate.ts +1813 -0
  579. package/src/cli/migration-codemod.ts +646 -0
  580. package/src/cli/multi-frontier-runs.ts +1952 -0
  581. package/src/cli/package-lifecycle.ts +823 -0
  582. package/src/cli/plan-blocks.ts +143 -0
  583. package/src/cli/plan-local.ts +3189 -0
  584. package/src/cli/plan-publish-store.ts +138 -0
  585. package/src/cli/pr-visual-recap-workflow.ts +2 -0
  586. package/src/cli/recap.ts +5 -0
  587. package/src/cli/setup-agents.ts +120 -0
  588. package/src/cli/skills-content/assets-skill.ts +83 -0
  589. package/src/cli/skills-content/canvas.ts +153 -0
  590. package/src/cli/skills-content/connection.ts +52 -0
  591. package/src/cli/skills-content/content-skill.ts +120 -0
  592. package/src/cli/skills-content/design-exploration-skill.ts +118 -0
  593. package/src/cli/skills-content/design-visual-edit-skill.ts +228 -0
  594. package/src/cli/skills-content/document-quality.ts +196 -0
  595. package/src/cli/skills-content/exemplar.ts +69 -0
  596. package/src/cli/skills-content/help.ts +77 -0
  597. package/src/cli/skills-content/index.ts +22 -0
  598. package/src/cli/skills-content/local-files.ts +100 -0
  599. package/src/cli/skills-content/plan-setup-auth.ts +80 -0
  600. package/src/cli/skills-content/visual-plan-skill.ts +493 -0
  601. package/src/cli/skills-content/visual-recap-skill.ts +553 -0
  602. package/src/cli/skills-content/visualize-repo-skill.ts +93 -0
  603. package/src/cli/skills-content/wireframe.ts +349 -0
  604. package/src/cli/skills.ts +4340 -0
  605. package/src/cli/sync-builder-starter-manifest.ts +631 -0
  606. package/src/cli/telemetry.ts +136 -0
  607. package/src/cli/templates-meta.ts +269 -0
  608. package/src/cli/upgrade.ts +956 -0
  609. package/src/cli/visualize-repo.ts +715 -0
  610. package/src/cli/workspace-dev.ts +1486 -0
  611. package/src/cli/workspacify.ts +258 -0
  612. package/src/client/AgentAskPopover.tsx +89 -0
  613. package/src/client/AgentChatHome.tsx +66 -0
  614. package/src/client/AgentNative.tsx +161 -0
  615. package/src/client/AgentNativeEmbedded.tsx +288 -0
  616. package/src/client/AgentNativeFrame.tsx +151 -0
  617. package/src/client/AgentPanel.tsx +3437 -0
  618. package/src/client/AgentTaskCard.tsx +273 -0
  619. package/src/client/AppearancePicker.tsx +79 -0
  620. package/src/client/AssistantChat.tsx +5793 -0
  621. package/src/client/ClientOnly.tsx +22 -0
  622. package/src/client/CommandMenu.tsx +726 -0
  623. package/src/client/ConnectBuilderCard.tsx +390 -0
  624. package/src/client/DefaultSpinner.tsx +47 -0
  625. package/src/client/ErrorBoundary.tsx +359 -0
  626. package/src/client/ErrorReportActions.tsx +69 -0
  627. package/src/client/FeedbackButton.tsx +553 -0
  628. package/src/client/HighlightedCodeBlock.tsx +173 -0
  629. package/src/client/IframeEmbed.tsx +151 -0
  630. package/src/client/KeepTabOpenNotice.tsx +181 -0
  631. package/src/client/MultiTabAssistantChat.tsx +2576 -0
  632. package/src/client/NewWorkspaceAppFlow.tsx +525 -0
  633. package/src/client/PoweredByBadge.tsx +284 -0
  634. package/src/client/RunStuckBanner.tsx +431 -0
  635. package/src/client/StarfieldBackground.tsx +466 -0
  636. package/src/client/Turnstile.tsx +125 -0
  637. package/src/client/active-run-state.ts +139 -0
  638. package/src/client/agent-chat/index.ts +279 -0
  639. package/src/client/agent-chat-adapter.ts +3692 -0
  640. package/src/client/agent-chat.ts +1233 -0
  641. package/src/client/agent-engine-key.ts +122 -0
  642. package/src/client/agent-page/AgentContextTab.tsx +487 -0
  643. package/src/client/agent-page/AgentEmptyState.tsx +44 -0
  644. package/src/client/agent-page/AgentJobsTab.tsx +441 -0
  645. package/src/client/agent-page/AgentTabFrame.tsx +39 -0
  646. package/src/client/agent-page/AgentTabsPage.tsx +1096 -0
  647. package/src/client/agent-page/types.ts +18 -0
  648. package/src/client/agent-page/use-jobs.ts +150 -0
  649. package/src/client/agent-sidebar-state.ts +166 -0
  650. package/src/client/analytics/index.ts +40 -0
  651. package/src/client/analytics-session.ts +70 -0
  652. package/src/client/analytics.ts +1757 -0
  653. package/src/client/api-path.ts +124 -0
  654. package/src/client/app-providers.tsx +230 -0
  655. package/src/client/appearance.ts +130 -0
  656. package/src/client/application-state.ts +154 -0
  657. package/src/client/assistant-ui-recovery.tsx +205 -0
  658. package/src/client/automation/index.ts +4 -0
  659. package/src/client/automation.ts +22 -0
  660. package/src/client/blocks/AiEditableField.tsx +30 -0
  661. package/src/client/blocks/BlockView.tsx +145 -0
  662. package/src/client/blocks/RegistryBlockDataProvider.tsx +121 -0
  663. package/src/client/blocks/SchemaBlockEditor.tsx +457 -0
  664. package/src/client/blocks/agent.ts +95 -0
  665. package/src/client/blocks/code-block-direction.ts +21 -0
  666. package/src/client/blocks/index.ts +308 -0
  667. package/src/client/blocks/library/AnnotatedCodeBlock.tsx +710 -0
  668. package/src/client/blocks/library/ApiEndpointBlock.tsx +1120 -0
  669. package/src/client/blocks/library/DataModelBlock.tsx +775 -0
  670. package/src/client/blocks/library/DiffBlock.tsx +1854 -0
  671. package/src/client/blocks/library/FileTreeBlock.tsx +868 -0
  672. package/src/client/blocks/library/HighlightedCode.tsx +260 -0
  673. package/src/client/blocks/library/JsonExplorerBlock.tsx +608 -0
  674. package/src/client/blocks/library/MermaidBlock.tsx +362 -0
  675. package/src/client/blocks/library/OpenApiSpecBlock.tsx +998 -0
  676. package/src/client/blocks/library/annotated-code.config.ts +101 -0
  677. package/src/client/blocks/library/annotation-rail.tsx +1020 -0
  678. package/src/client/blocks/library/api-endpoint.config.ts +207 -0
  679. package/src/client/blocks/library/block-copy.ts +399 -0
  680. package/src/client/blocks/library/callout.config.ts +57 -0
  681. package/src/client/blocks/library/callout.tsx +149 -0
  682. package/src/client/blocks/library/checklist.config.ts +56 -0
  683. package/src/client/blocks/library/checklist.tsx +208 -0
  684. package/src/client/blocks/library/code-filename-label.tsx +59 -0
  685. package/src/client/blocks/library/code-highlight.tsx +178 -0
  686. package/src/client/blocks/library/code-tabs.config.ts +70 -0
  687. package/src/client/blocks/library/code-tabs.tsx +606 -0
  688. package/src/client/blocks/library/code.config.ts +69 -0
  689. package/src/client/blocks/library/code.tsx +440 -0
  690. package/src/client/blocks/library/columns.config.ts +85 -0
  691. package/src/client/blocks/library/columns.tsx +334 -0
  692. package/src/client/blocks/library/data-model.config.ts +152 -0
  693. package/src/client/blocks/library/dev-doc-ui.tsx +191 -0
  694. package/src/client/blocks/library/diagram.config.ts +222 -0
  695. package/src/client/blocks/library/diagram.tsx +958 -0
  696. package/src/client/blocks/library/diff.config.ts +115 -0
  697. package/src/client/blocks/library/file-tree.config.ts +92 -0
  698. package/src/client/blocks/library/html.config.ts +79 -0
  699. package/src/client/blocks/library/html.tsx +258 -0
  700. package/src/client/blocks/library/json-explorer.config.ts +74 -0
  701. package/src/client/blocks/library/mermaid.config.ts +49 -0
  702. package/src/client/blocks/library/narrow-container.tsx +37 -0
  703. package/src/client/blocks/library/openapi-spec.config.ts +66 -0
  704. package/src/client/blocks/library/question-form.config.ts +127 -0
  705. package/src/client/blocks/library/question-form.tsx +944 -0
  706. package/src/client/blocks/library/sanitize-html.ts +343 -0
  707. package/src/client/blocks/library/server-specs.ts +338 -0
  708. package/src/client/blocks/library/specs.tsx +308 -0
  709. package/src/client/blocks/library/table.config.ts +61 -0
  710. package/src/client/blocks/library/table.tsx +449 -0
  711. package/src/client/blocks/library/tabs.config.ts +96 -0
  712. package/src/client/blocks/library/tabs.tsx +665 -0
  713. package/src/client/blocks/library/wireframe-icons.ts +210 -0
  714. package/src/client/blocks/library/wireframe-kit.tsx +1850 -0
  715. package/src/client/blocks/library/wireframe.config.ts +539 -0
  716. package/src/client/blocks/library/wireframe.tsx +535 -0
  717. package/src/client/blocks/mdx.ts +364 -0
  718. package/src/client/blocks/provider.tsx +51 -0
  719. package/src/client/blocks/registry.ts +83 -0
  720. package/src/client/blocks/schema-form/introspect.ts +201 -0
  721. package/src/client/blocks/server.ts +170 -0
  722. package/src/client/blocks/types.ts +415 -0
  723. package/src/client/browser-session-bridge.ts +548 -0
  724. package/src/client/browser-tab-id.ts +36 -0
  725. package/src/client/build-compatibility.ts +77 -0
  726. package/src/client/builder-frame.ts +194 -0
  727. package/src/client/builder-mark.tsx +21 -0
  728. package/src/client/changelog/Changelog.tsx +333 -0
  729. package/src/client/changelog/index.ts +1 -0
  730. package/src/client/chat/ChatHistoryList.tsx +7 -0
  731. package/src/client/chat/attachment-adapters.ts +426 -0
  732. package/src/client/chat/connectors.ts +1579 -0
  733. package/src/client/chat/index.ts +160 -0
  734. package/src/client/chat/markdown-renderer.tsx +693 -0
  735. package/src/client/chat/message-components.tsx +1326 -0
  736. package/src/client/chat/repo-helpers.ts +281 -0
  737. package/src/client/chat/run-recovery.tsx +994 -0
  738. package/src/client/chat/runtime.ts +1810 -0
  739. package/src/client/chat/tool-call-display.tsx +1270 -0
  740. package/src/client/chat/tool-render-registry.tsx +144 -0
  741. package/src/client/chat/use-agent-chat-lifecycle-tracking.ts +74 -0
  742. package/src/client/chat/use-reconnect-reader-owner.ts +28 -0
  743. package/src/client/chat/widgets/DataChartRenderer.tsx +242 -0
  744. package/src/client/chat/widgets/DataChartWidget.tsx +51 -0
  745. package/src/client/chat/widgets/DataInsightsWidget.tsx +59 -0
  746. package/src/client/chat/widgets/DataTableWidget.tsx +240 -0
  747. package/src/client/chat/widgets/InlineExtensionWidget.tsx +60 -0
  748. package/src/client/chat/widgets/builtin-tool-renderers.tsx +213 -0
  749. package/src/client/chat/widgets/data-widget-types.ts +1 -0
  750. package/src/client/chat/widgets/inline-extension-result.ts +86 -0
  751. package/src/client/chat-model-groups.ts +171 -0
  752. package/src/client/chat-thread-url.ts +11 -0
  753. package/src/client/chat-view-transition.ts +213 -0
  754. package/src/client/client-action.ts +21 -0
  755. package/src/client/client-bootstrap.ts +7 -0
  756. package/src/client/client-surface.ts +41 -0
  757. package/src/client/clipboard.ts +89 -0
  758. package/src/client/code-agent-chat-adapter.ts +537 -0
  759. package/src/client/collab/index.ts +60 -0
  760. package/src/client/command-navigation/index.ts +15 -0
  761. package/src/client/comments-review/index.ts +1 -0
  762. package/src/client/components/AgentPresenceChip.tsx +4 -0
  763. package/src/client/components/ApiKeySettings.tsx +309 -0
  764. package/src/client/components/CodeAgentIndicator.tsx +59 -0
  765. package/src/client/components/CodeRequiredDialog.tsx +470 -0
  766. package/src/client/components/LiveCursorOverlay.tsx +5 -0
  767. package/src/client/components/MissingKeyCard.tsx +61 -0
  768. package/src/client/components/PresenceBar.tsx +4 -0
  769. package/src/client/components/RecentEditHighlights.tsx +4 -0
  770. package/src/client/components/RemoteSelectionRings.tsx +5 -0
  771. package/src/client/components/icons/AgentNativeIcon.tsx +46 -0
  772. package/src/client/components/ui/dialog.tsx +1 -0
  773. package/src/client/components/ui/dropdown-menu.tsx +1 -0
  774. package/src/client/components/ui/hover-card.tsx +1 -0
  775. package/src/client/components/ui/message-scroller.tsx +131 -0
  776. package/src/client/components/ui/popover.tsx +1 -0
  777. package/src/client/components/ui/sheet.tsx +1 -0
  778. package/src/client/components/ui/tooltip.tsx +1 -0
  779. package/src/client/composer/index.ts +11 -0
  780. package/src/client/composer/runtime-adapters.tsx +123 -0
  781. package/src/client/composer/use-mention-search.ts +7 -0
  782. package/src/client/composer/wired-components.tsx +53 -0
  783. package/src/client/context-xray/ContextMeter.tsx +124 -0
  784. package/src/client/context-xray/ContextSegmentRow.tsx +27 -0
  785. package/src/client/context-xray/ContextTreemap.tsx +24 -0
  786. package/src/client/context-xray/ContextXRayPanel.tsx +59 -0
  787. package/src/client/context-xray/SegmentProvenancePopover.tsx +18 -0
  788. package/src/client/context-xray/format.ts +27 -0
  789. package/src/client/conversation/AgentConversation.tsx +555 -0
  790. package/src/client/conversation/code-agent-transcript.ts +283 -0
  791. package/src/client/conversation/index.ts +22 -0
  792. package/src/client/conversation/types.ts +85 -0
  793. package/src/client/conversation/use-near-bottom-autoscroll.ts +278 -0
  794. package/src/client/create-query-client.ts +86 -0
  795. package/src/client/db-admin/DataGrid.tsx +551 -0
  796. package/src/client/db-admin/DbAdminPage.tsx +218 -0
  797. package/src/client/db-admin/DevDatabaseLink.tsx +16 -0
  798. package/src/client/db-admin/EditableCell.tsx +451 -0
  799. package/src/client/db-admin/FilterBar.tsx +203 -0
  800. package/src/client/db-admin/ResultsGrid.tsx +114 -0
  801. package/src/client/db-admin/RowSidePanel.tsx +337 -0
  802. package/src/client/db-admin/SqlEditor.tsx +840 -0
  803. package/src/client/db-admin/TableBrowser.tsx +212 -0
  804. package/src/client/db-admin/TableEditor.tsx +760 -0
  805. package/src/client/db-admin/cell-format.ts +246 -0
  806. package/src/client/db-admin/changeset.ts +296 -0
  807. package/src/client/db-admin/export-utils.ts +78 -0
  808. package/src/client/db-admin/index.ts +33 -0
  809. package/src/client/db-admin/sql-storage.ts +134 -0
  810. package/src/client/db-admin/storage.ts +68 -0
  811. package/src/client/db-admin/useAgentSync.ts +226 -0
  812. package/src/client/db-admin/useDbAdmin.ts +294 -0
  813. package/src/client/dev-overlay/DevOverlay.tsx +576 -0
  814. package/src/client/dev-overlay/builtins.ts +40 -0
  815. package/src/client/dev-overlay/index.ts +23 -0
  816. package/src/client/dev-overlay/registry.ts +67 -0
  817. package/src/client/dev-overlay/types.ts +64 -0
  818. package/src/client/dev-overlay/use-dev-option.ts +78 -0
  819. package/src/client/dev-overlay/use-dev-overlay-shortcut.ts +31 -0
  820. package/src/client/dynamic-suggestions.ts +455 -0
  821. package/src/client/embed-auth.ts +544 -0
  822. package/src/client/embed.ts +54 -0
  823. package/src/client/error-capture.ts +690 -0
  824. package/src/client/error-format.ts +211 -0
  825. package/src/client/error-reporting.ts +100 -0
  826. package/src/client/extensions/AgentNativeExtensionFrame.e2e-host.tsx +136 -0
  827. package/src/client/extensions/AgentNativeExtensionFrame.tsx +708 -0
  828. package/src/client/extensions/EmbeddedExtension.tsx +604 -0
  829. package/src/client/extensions/ExtensionEditor.tsx +450 -0
  830. package/src/client/extensions/ExtensionQueryErrorState.tsx +47 -0
  831. package/src/client/extensions/ExtensionSlot.tsx +345 -0
  832. package/src/client/extensions/ExtensionViewer.tsx +1502 -0
  833. package/src/client/extensions/ExtensionViewerPage.tsx +32 -0
  834. package/src/client/extensions/ExtensionsListPage.tsx +432 -0
  835. package/src/client/extensions/ExtensionsSidebarSection.tsx +1259 -0
  836. package/src/client/extensions/InlineExtensionFrame.tsx +660 -0
  837. package/src/client/extensions/agent-native-extension-runtime.ts +495 -0
  838. package/src/client/extensions/delete-extension.ts +76 -0
  839. package/src/client/extensions/extension-load-error.ts +26 -0
  840. package/src/client/extensions/extension-order.ts +49 -0
  841. package/src/client/extensions/extension-popularity.ts +61 -0
  842. package/src/client/extensions/iframe-bridge.ts +377 -0
  843. package/src/client/extensions/index.ts +84 -0
  844. package/src/client/extensions/portable-extension.ts +712 -0
  845. package/src/client/feature-flags/FeatureFlagsPanel.tsx +519 -0
  846. package/src/client/feature-flags/helpers.ts +77 -0
  847. package/src/client/feature-flags/index.ts +13 -0
  848. package/src/client/feature-flags/types.ts +29 -0
  849. package/src/client/feature-flags/use-feature-flag.ts +26 -0
  850. package/src/client/feedback-context.ts +90 -0
  851. package/src/client/frame-protocol.ts +168 -0
  852. package/src/client/frame.ts +329 -0
  853. package/src/client/guided-questions.tsx +1152 -0
  854. package/src/client/history/VersionHistoryPanel.tsx +175 -0
  855. package/src/client/history/index.ts +21 -0
  856. package/src/client/history/use-history.ts +114 -0
  857. package/src/client/hooks/index.ts +46 -0
  858. package/src/client/host/index.ts +170 -0
  859. package/src/client/host-bridge.ts +1272 -0
  860. package/src/client/host-tools.ts +190 -0
  861. package/src/client/i18n.tsx +798 -0
  862. package/src/client/index.ts +370 -0
  863. package/src/client/integrations/IntegrationCard.tsx +186 -0
  864. package/src/client/integrations/IntegrationsPanel.tsx +633 -0
  865. package/src/client/integrations/api.ts +295 -0
  866. package/src/client/integrations/index.ts +29 -0
  867. package/src/client/integrations/useIntegrationStatus.ts +52 -0
  868. package/src/client/mcp-app-host.ts +720 -0
  869. package/src/client/mcp-apps/McpAppRenderer.tsx +760 -0
  870. package/src/client/navigation/index.ts +54 -0
  871. package/src/client/notifications/NotificationsBell.tsx +383 -0
  872. package/src/client/notifications/index.ts +1 -0
  873. package/src/client/observability/ObservabilityDashboard.tsx +890 -0
  874. package/src/client/observability/ThumbsFeedback.tsx +160 -0
  875. package/src/client/observability/index.ts +20 -0
  876. package/src/client/observability/useObservability.ts +266 -0
  877. package/src/client/onboarding/OnboardingBanner.tsx +73 -0
  878. package/src/client/onboarding/OnboardingPanel.tsx +1036 -0
  879. package/src/client/onboarding/SetupButton.tsx +64 -0
  880. package/src/client/onboarding/index.ts +25 -0
  881. package/src/client/onboarding/use-onboarding.ts +164 -0
  882. package/src/client/onboarding/use-preview-mode.ts +40 -0
  883. package/src/client/org/InvitationBanner.tsx +148 -0
  884. package/src/client/org/OrgSwitcher.tsx +754 -0
  885. package/src/client/org/RequireActiveOrg.tsx +337 -0
  886. package/src/client/org/TeamPage.tsx +1432 -0
  887. package/src/client/org/hooks.ts +359 -0
  888. package/src/client/org/index.ts +70 -0
  889. package/src/client/org/workspace-app-links.ts +367 -0
  890. package/src/client/org-team/index.ts +1 -0
  891. package/src/client/progress/RunsTray.tsx +680 -0
  892. package/src/client/progress/index.ts +1 -0
  893. package/src/client/require-session.tsx +142 -0
  894. package/src/client/resources/BuiltinCapabilityDetail.tsx +258 -0
  895. package/src/client/resources/McpConnectionSuggestion.tsx +276 -0
  896. package/src/client/resources/McpIntegrationDialog.tsx +808 -0
  897. package/src/client/resources/McpServerDetail.tsx +230 -0
  898. package/src/client/resources/ResourceEditor.tsx +872 -0
  899. package/src/client/resources/ResourceTree.tsx +860 -0
  900. package/src/client/resources/ResourcesPanel.tsx +1960 -0
  901. package/src/client/resources/index.ts +25 -0
  902. package/src/client/resources/mcp-integration-catalog.ts +771 -0
  903. package/src/client/resources/mcp-integration-logos.ts +108 -0
  904. package/src/client/resources/runtime.ts +1 -0
  905. package/src/client/resources/use-builtin-capabilities.ts +113 -0
  906. package/src/client/resources/use-mcp-servers.ts +217 -0
  907. package/src/client/resources/use-resources.ts +402 -0
  908. package/src/client/review/ReviewCommentComposer.tsx +123 -0
  909. package/src/client/review/ReviewStatusBadge.tsx +39 -0
  910. package/src/client/review/ReviewThreadPanel.tsx +625 -0
  911. package/src/client/review/index.ts +38 -0
  912. package/src/client/review/use-review.ts +186 -0
  913. package/src/client/route-chunk-recovery/index.ts +2 -0
  914. package/src/client/route-chunk-recovery.ts +331 -0
  915. package/src/client/route-state.ts +455 -0
  916. package/src/client/route-warmup.tsx +543 -0
  917. package/src/client/session-replay-iframe.e2e-host.tsx +100 -0
  918. package/src/client/session-replay.ts +3417 -0
  919. package/src/client/settings/AgentsSection.tsx +441 -0
  920. package/src/client/settings/AutomationsSection.tsx +516 -0
  921. package/src/client/settings/BackgroundAgentSection.tsx +189 -0
  922. package/src/client/settings/BrowserSection.tsx +110 -0
  923. package/src/client/settings/DemoModeSection.tsx +48 -0
  924. package/src/client/settings/SecretsSection.tsx +875 -0
  925. package/src/client/settings/SettingsPanel.tsx +3162 -0
  926. package/src/client/settings/SettingsSection.tsx +283 -0
  927. package/src/client/settings/SettingsTabsPage.tsx +592 -0
  928. package/src/client/settings/UsageSection.tsx +401 -0
  929. package/src/client/settings/VoiceTranscriptionSection.tsx +960 -0
  930. package/src/client/settings/agent-settings-search.ts +220 -0
  931. package/src/client/settings/index.ts +27 -0
  932. package/src/client/settings/useBuilderStatus.ts +1055 -0
  933. package/src/client/setup-connections/BuilderConnectCard.tsx +89 -0
  934. package/src/client/setup-connections/ProviderReadinessBadge.tsx +73 -0
  935. package/src/client/setup-connections/SetupConnectionsPage.tsx +94 -0
  936. package/src/client/setup-connections/catalog.ts +6 -0
  937. package/src/client/setup-connections/index.ts +47 -0
  938. package/src/client/setup-connections/runtime.ts +1 -0
  939. package/src/client/sharing/ShareButton.tsx +1748 -0
  940. package/src/client/sharing/ShareDialog.tsx +846 -0
  941. package/src/client/sharing/VisibilityBadge.tsx +4 -0
  942. package/src/client/sharing/index.ts +2 -0
  943. package/src/client/sse-event-processor.ts +2002 -0
  944. package/src/client/terminal/AgentTerminal.tsx +488 -0
  945. package/src/client/terminal/index.ts +7 -0
  946. package/src/client/theme.ts +27 -0
  947. package/src/client/tombstone/agent-presence-chip.ts +17 -0
  948. package/src/client/tombstone/editor.ts +265 -0
  949. package/src/client/tombstone/live-cursor-overlay.ts +21 -0
  950. package/src/client/tombstone/presence-bar.ts +17 -0
  951. package/src/client/tombstone/recent-edit-highlights.ts +17 -0
  952. package/src/client/tombstone/remote-selection-rings.ts +21 -0
  953. package/src/client/tombstone/rich-markdown-editor.ts +265 -0
  954. package/src/client/tombstone/ui-dialog.ts +49 -0
  955. package/src/client/tombstone/ui-dropdown-menu.ts +69 -0
  956. package/src/client/tombstone/ui-hover-card.ts +21 -0
  957. package/src/client/tombstone/ui-popover.ts +25 -0
  958. package/src/client/tombstone/ui-sheet.ts +49 -0
  959. package/src/client/tombstone/ui-tooltip.ts +29 -0
  960. package/src/client/tombstone/visual-style-controls.ts +61 -0
  961. package/src/client/tool-cells/BashCell.tsx +164 -0
  962. package/src/client/tool-cells/EditCell.tsx +303 -0
  963. package/src/client/tool-cells/FilesChangedSummary.tsx +208 -0
  964. package/src/client/tool-cells/WriteCell.tsx +145 -0
  965. package/src/client/tool-cells/index.ts +7 -0
  966. package/src/client/tool-display.ts +41 -0
  967. package/src/client/track.ts +57 -0
  968. package/src/client/transcription/BuilderTranscriptionCta.tsx +120 -0
  969. package/src/client/transcription/use-live-transcription.ts +292 -0
  970. package/src/client/ui/index.ts +55 -0
  971. package/src/client/uploads/index.ts +6 -0
  972. package/src/client/uploads/upload-editor-image.ts +79 -0
  973. package/src/client/uploads/use-upload-resource.ts +24 -0
  974. package/src/client/url-scrub.ts +51 -0
  975. package/src/client/use-action.ts +797 -0
  976. package/src/client/use-agent-chat-context.ts +63 -0
  977. package/src/client/use-agent-chat-home-handoff.ts +207 -0
  978. package/src/client/use-agent-chat.ts +53 -0
  979. package/src/client/use-agent-engine-configured.ts +211 -0
  980. package/src/client/use-avatar.ts +127 -0
  981. package/src/client/use-change-version.ts +150 -0
  982. package/src/client/use-chat-models.ts +252 -0
  983. package/src/client/use-chat-threads.ts +1262 -0
  984. package/src/client/use-db-sync.ts +1112 -0
  985. package/src/client/use-demo-mode-status.ts +30 -0
  986. package/src/client/use-dev-mode.ts +177 -0
  987. package/src/client/use-external-value.ts +40 -0
  988. package/src/client/use-pausing-interval.ts +65 -0
  989. package/src/client/use-pinch-zoom.ts +202 -0
  990. package/src/client/use-run-stuck-detection.ts +427 -0
  991. package/src/client/use-send-to-agent-chat.ts +85 -0
  992. package/src/client/use-session.ts +114 -0
  993. package/src/client/utils.ts +7 -0
  994. package/src/client/visual-style-controls.tsx +1 -0
  995. package/src/client/vite-dev-recovery-script.ts +226 -0
  996. package/src/client/voice-provider-status.ts +44 -0
  997. package/src/client/widgets/index.ts +41 -0
  998. package/src/code-agents/background-controller.ts +398 -0
  999. package/src/code-agents/background-run.ts +156 -0
  1000. package/src/code-agents/index.ts +95 -0
  1001. package/src/code-agents/prompt-attachments.ts +39 -0
  1002. package/src/code-agents/transcript-normalizer.ts +750 -0
  1003. package/src/code-agents/transcript-order.ts +73 -0
  1004. package/src/coding-tools/index.ts +742 -0
  1005. package/src/coding-tools/run-code.ts +1662 -0
  1006. package/src/coding-tools/sandbox/adapter.ts +82 -0
  1007. package/src/coding-tools/sandbox/background.ts +406 -0
  1008. package/src/coding-tools/sandbox/executions-store.ts +517 -0
  1009. package/src/coding-tools/sandbox/index.ts +132 -0
  1010. package/src/coding-tools/sandbox/local-child-process-adapter.ts +217 -0
  1011. package/src/collab/agent-identity.ts +5 -0
  1012. package/src/collab/agent-presence.ts +341 -0
  1013. package/src/collab/awareness-store.ts +196 -0
  1014. package/src/collab/awareness.ts +317 -0
  1015. package/src/collab/client-struct.ts +366 -0
  1016. package/src/collab/client.ts +1205 -0
  1017. package/src/collab/emitter.ts +31 -0
  1018. package/src/collab/follow-mode.ts +107 -0
  1019. package/src/collab/index.ts +151 -0
  1020. package/src/collab/json-to-yjs.ts +504 -0
  1021. package/src/collab/presence.ts +207 -0
  1022. package/src/collab/recent-edits.ts +215 -0
  1023. package/src/collab/routes.ts +191 -0
  1024. package/src/collab/storage.ts +228 -0
  1025. package/src/collab/struct-routes.ts +133 -0
  1026. package/src/collab/text-to-yjs.ts +79 -0
  1027. package/src/collab/undo.ts +432 -0
  1028. package/src/collab/xml-ops.ts +68 -0
  1029. package/src/collab/ydoc-manager.ts +544 -0
  1030. package/src/command-navigation/actions.ts +100 -0
  1031. package/src/command-navigation/index.ts +12 -0
  1032. package/src/comments-review/index.ts +1 -0
  1033. package/src/connections/catalog.ts +451 -0
  1034. package/src/connections/index.ts +48 -0
  1035. package/src/connections/reader.ts +911 -0
  1036. package/src/connections/runtime.ts +1 -0
  1037. package/src/credentials/index.ts +187 -0
  1038. package/src/data-programs/actions.ts +335 -0
  1039. package/src/data-programs/contract.ts +284 -0
  1040. package/src/data-programs/execute.ts +693 -0
  1041. package/src/data-programs/index.ts +72 -0
  1042. package/src/data-programs/schema.ts +148 -0
  1043. package/src/data-programs/store.ts +755 -0
  1044. package/src/data-widgets/index.ts +376 -0
  1045. package/src/db/client.ts +1627 -0
  1046. package/src/db/create-get-db.ts +579 -0
  1047. package/src/db/ddl-guard.ts +302 -0
  1048. package/src/db/drizzle-config.ts +190 -0
  1049. package/src/db/ensure-additive-columns.ts +430 -0
  1050. package/src/db/index.ts +57 -0
  1051. package/src/db/migrations.ts +691 -0
  1052. package/src/db/request-telemetry.ts +154 -0
  1053. package/src/db/runtime-diagnostics.ts +331 -0
  1054. package/src/db/schema.ts +121 -0
  1055. package/src/db/widen-columns.ts +75 -0
  1056. package/src/db-admin/agent-tools.ts +202 -0
  1057. package/src/db-admin/index.ts +26 -0
  1058. package/src/db-admin/operations.ts +803 -0
  1059. package/src/db-admin/routes.ts +174 -0
  1060. package/src/db-admin/types.ts +110 -0
  1061. package/src/demo/browser-state.ts +47 -0
  1062. package/src/demo/fetch-interceptor.ts +175 -0
  1063. package/src/demo/redact.ts +575 -0
  1064. package/src/deploy/build.ts +3938 -0
  1065. package/src/deploy/immutable-assets.ts +65 -0
  1066. package/src/deploy/route-discovery.ts +458 -0
  1067. package/src/deploy/workspace-core.ts +282 -0
  1068. package/src/deploy/workspace-deploy.ts +1488 -0
  1069. package/src/eject/provider-api-definitions.ts +193 -0
  1070. package/src/eject/remote-mcp-presets.ts +137 -0
  1071. package/src/eject/setup-readiness-ui.tsx +29 -0
  1072. package/src/eject/workspace-connections.ts +92 -0
  1073. package/src/embedding/agent.ts +159 -0
  1074. package/src/embedding/bridge.ts +297 -0
  1075. package/src/embedding/index.ts +43 -0
  1076. package/src/embedding/protocol.ts +185 -0
  1077. package/src/embedding/react.tsx +319 -0
  1078. package/src/embeddings/index.ts +233 -0
  1079. package/src/eval/agent-runner.ts +210 -0
  1080. package/src/eval/define-eval.ts +55 -0
  1081. package/src/eval/index.ts +49 -0
  1082. package/src/eval/report.ts +75 -0
  1083. package/src/eval/runner.ts +369 -0
  1084. package/src/eval/scorer.ts +244 -0
  1085. package/src/eval/types.ts +188 -0
  1086. package/src/event-bus/bus.ts +140 -0
  1087. package/src/event-bus/index.ts +3 -0
  1088. package/src/event-bus/registry.ts +79 -0
  1089. package/src/event-bus/types.ts +29 -0
  1090. package/src/extensions/actions.ts +1636 -0
  1091. package/src/extensions/change-marker.ts +54 -0
  1092. package/src/extensions/content-patch.ts +468 -0
  1093. package/src/extensions/fetch-tool.ts +461 -0
  1094. package/src/extensions/html-shell.ts +803 -0
  1095. package/src/extensions/local.ts +462 -0
  1096. package/src/extensions/path.ts +40 -0
  1097. package/src/extensions/proxy-security.ts +171 -0
  1098. package/src/extensions/routes.ts +1080 -0
  1099. package/src/extensions/schema.ts +269 -0
  1100. package/src/extensions/session-replay-iframe.ts +131 -0
  1101. package/src/extensions/slots/routes.ts +126 -0
  1102. package/src/extensions/slots/schema.ts +87 -0
  1103. package/src/extensions/slots/store.ts +450 -0
  1104. package/src/extensions/store.ts +1347 -0
  1105. package/src/extensions/theme.ts +114 -0
  1106. package/src/extensions/url-safety.ts +316 -0
  1107. package/src/extensions/web-content.ts +663 -0
  1108. package/src/extensions/web-search-tool.ts +437 -0
  1109. package/src/feature-flags/a2a-action-route.ts +56 -0
  1110. package/src/feature-flags/actions/get-feature-flags.ts +24 -0
  1111. package/src/feature-flags/actions/list-feature-flags.ts +69 -0
  1112. package/src/feature-flags/actions/set-feature-flag.ts +89 -0
  1113. package/src/feature-flags/index.ts +21 -0
  1114. package/src/feature-flags/permissions.ts +53 -0
  1115. package/src/feature-flags/plugin.ts +44 -0
  1116. package/src/feature-flags/registry.ts +96 -0
  1117. package/src/feature-flags/store.ts +176 -0
  1118. package/src/file-upload/actions/upload-image.ts +213 -0
  1119. package/src/file-upload/builder.ts +431 -0
  1120. package/src/file-upload/index.ts +24 -0
  1121. package/src/file-upload/pre-upload-attachments.ts +305 -0
  1122. package/src/file-upload/registry.ts +125 -0
  1123. package/src/file-upload/types.ts +89 -0
  1124. package/src/guards/db-tool-scoping.ts +129 -0
  1125. package/src/guards/index.ts +23 -0
  1126. package/src/guards/no-drizzle-push.ts +116 -0
  1127. package/src/guards/no-env-credentials.ts +229 -0
  1128. package/src/guards/no-env-mutation.ts +114 -0
  1129. package/src/guards/no-localhost-fallback.ts +124 -0
  1130. package/src/guards/no-unscoped-credentials.ts +227 -0
  1131. package/src/guards/no-unscoped-queries.ts +834 -0
  1132. package/src/guards/scan-utils.ts +105 -0
  1133. package/src/guards/types.ts +30 -0
  1134. package/src/history/actions/create-resource-version.ts +92 -0
  1135. package/src/history/actions/get-resource-version.ts +60 -0
  1136. package/src/history/actions/list-resource-history.ts +53 -0
  1137. package/src/history/actions/list-resource-versions.ts +43 -0
  1138. package/src/history/actions/restore-resource-version.ts +96 -0
  1139. package/src/history/index.ts +26 -0
  1140. package/src/history/registry.ts +111 -0
  1141. package/src/history/store.ts +379 -0
  1142. package/src/history/types.ts +69 -0
  1143. package/src/index.browser.ts +155 -0
  1144. package/src/index.ts +408 -0
  1145. package/src/ingestion/docx.ts +158 -0
  1146. package/src/ingestion/figma-node-to-html.ts +1860 -0
  1147. package/src/ingestion/figma.ts +548 -0
  1148. package/src/ingestion/index.ts +107 -0
  1149. package/src/ingestion/media.ts +264 -0
  1150. package/src/ingestion/notion.ts +121 -0
  1151. package/src/ingestion/office.ts +213 -0
  1152. package/src/ingestion/orchestration.ts +160 -0
  1153. package/src/ingestion/pptx.ts +331 -0
  1154. package/src/ingestion/selection.ts +81 -0
  1155. package/src/ingestion/website.ts +151 -0
  1156. package/src/integrations/a2a-continuation-marker.ts +2 -0
  1157. package/src/integrations/a2a-continuation-processor.ts +888 -0
  1158. package/src/integrations/a2a-continuations-store.ts +643 -0
  1159. package/src/integrations/adapter-overrides.ts +12 -0
  1160. package/src/integrations/adapters/discord.ts +364 -0
  1161. package/src/integrations/adapters/email.ts +1164 -0
  1162. package/src/integrations/adapters/google-docs.ts +371 -0
  1163. package/src/integrations/adapters/index.ts +29 -0
  1164. package/src/integrations/adapters/microsoft-teams.ts +413 -0
  1165. package/src/integrations/adapters/slack.ts +1914 -0
  1166. package/src/integrations/adapters/telegram.ts +386 -0
  1167. package/src/integrations/adapters/whatsapp.ts +358 -0
  1168. package/src/integrations/awaiting-input-store.ts +130 -0
  1169. package/src/integrations/catalog.ts +571 -0
  1170. package/src/integrations/computer-supervision-store.ts +423 -0
  1171. package/src/integrations/computer-supervision.ts +213 -0
  1172. package/src/integrations/config-store.ts +131 -0
  1173. package/src/integrations/controls-store.ts +187 -0
  1174. package/src/integrations/eject/messaging-adapters.ts +85 -0
  1175. package/src/integrations/google-docs-poller.ts +671 -0
  1176. package/src/integrations/identity-links-store.ts +210 -0
  1177. package/src/integrations/identity.ts +196 -0
  1178. package/src/integrations/index.ts +220 -0
  1179. package/src/integrations/installations-store.ts +677 -0
  1180. package/src/integrations/integration-memory.ts +187 -0
  1181. package/src/integrations/internal-token.ts +106 -0
  1182. package/src/integrations/pending-tasks-retry-job.ts +286 -0
  1183. package/src/integrations/pending-tasks-store.ts +468 -0
  1184. package/src/integrations/plugin.ts +3048 -0
  1185. package/src/integrations/remote-commands-store.ts +690 -0
  1186. package/src/integrations/remote-devices-store.ts +492 -0
  1187. package/src/integrations/remote-json-safety.ts +127 -0
  1188. package/src/integrations/remote-push-delivery-job.ts +48 -0
  1189. package/src/integrations/remote-push-delivery.ts +370 -0
  1190. package/src/integrations/remote-push-store.ts +664 -0
  1191. package/src/integrations/remote-retry-job.ts +55 -0
  1192. package/src/integrations/remote-run-events-store.ts +222 -0
  1193. package/src/integrations/remote-types.ts +186 -0
  1194. package/src/integrations/runtime.ts +1 -0
  1195. package/src/integrations/scope-store.ts +498 -0
  1196. package/src/integrations/slack-manifest.ts +78 -0
  1197. package/src/integrations/slack-oauth.ts +344 -0
  1198. package/src/integrations/task-queue-stats.ts +144 -0
  1199. package/src/integrations/thread-mapping-store.ts +134 -0
  1200. package/src/integrations/types.ts +485 -0
  1201. package/src/integrations/usage-budget-store.ts +906 -0
  1202. package/src/integrations/webhook-delivery.ts +55 -0
  1203. package/src/integrations/webhook-handler.ts +1939 -0
  1204. package/src/jobs/actions/list-recurring-jobs.ts +105 -0
  1205. package/src/jobs/actions/manage-recurring-job.ts +85 -0
  1206. package/src/jobs/cron.ts +126 -0
  1207. package/src/jobs/index.ts +9 -0
  1208. package/src/jobs/scheduler.ts +703 -0
  1209. package/src/jobs/tools.ts +367 -0
  1210. package/src/local-artifacts/index.ts +1372 -0
  1211. package/src/localization/actions/get-localization-preference.ts +24 -0
  1212. package/src/localization/actions/set-localization-preference.ts +32 -0
  1213. package/src/localization/default-messages.ts +1009 -0
  1214. package/src/localization/index.ts +2 -0
  1215. package/src/localization/server.ts +131 -0
  1216. package/src/localization/shared.ts +208 -0
  1217. package/src/mcp/actions/call-mcp-tool.ts +19 -0
  1218. package/src/mcp/actions/create-org-service-token.ts +87 -0
  1219. package/src/mcp/actions/list-mcp-tools.ts +18 -0
  1220. package/src/mcp/actions/list-org-service-tokens.ts +44 -0
  1221. package/src/mcp/actions/revoke-org-service-token.ts +30 -0
  1222. package/src/mcp/actions/service-token-access.ts +121 -0
  1223. package/src/mcp/ask-app-inline-tasks.ts +125 -0
  1224. package/src/mcp/build-server.ts +2356 -0
  1225. package/src/mcp/builtin-tools.ts +1506 -0
  1226. package/src/mcp/connect-route.ts +1460 -0
  1227. package/src/mcp/connect-store.ts +680 -0
  1228. package/src/mcp/embed-app.ts +2051 -0
  1229. package/src/mcp/embed-route.ts +84 -0
  1230. package/src/mcp/external-agent-policy.ts +18 -0
  1231. package/src/mcp/index.ts +47 -0
  1232. package/src/mcp/oauth-route.ts +1016 -0
  1233. package/src/mcp/oauth-store.ts +581 -0
  1234. package/src/mcp/oauth-token.ts +227 -0
  1235. package/src/mcp/org-directory.ts +343 -0
  1236. package/src/mcp/route-paths.ts +34 -0
  1237. package/src/mcp/screen-memory-stdio.ts +290 -0
  1238. package/src/mcp/server.ts +507 -0
  1239. package/src/mcp/stdio.ts +328 -0
  1240. package/src/mcp/workspace-resolve.ts +261 -0
  1241. package/src/mcp-client/app-api.ts +136 -0
  1242. package/src/mcp-client/app-result.ts +64 -0
  1243. package/src/mcp-client/builtin-capabilities.ts +146 -0
  1244. package/src/mcp-client/builtin-store.ts +93 -0
  1245. package/src/mcp-client/config.ts +281 -0
  1246. package/src/mcp-client/errors.ts +54 -0
  1247. package/src/mcp-client/hub-client.ts +172 -0
  1248. package/src/mcp-client/hub-routes.ts +198 -0
  1249. package/src/mcp-client/index.ts +504 -0
  1250. package/src/mcp-client/manager.ts +968 -0
  1251. package/src/mcp-client/oauth-client.ts +462 -0
  1252. package/src/mcp-client/oauth-routes.ts +307 -0
  1253. package/src/mcp-client/remote-store.ts +674 -0
  1254. package/src/mcp-client/remote-url.ts +97 -0
  1255. package/src/mcp-client/routes.ts +1093 -0
  1256. package/src/mcp-client/screen-memory-local.ts +461 -0
  1257. package/src/mcp-client/tool-policy.ts +182 -0
  1258. package/src/mcp-client/visibility.ts +56 -0
  1259. package/src/mcp-client/workspace-servers.ts +379 -0
  1260. package/src/navigation/index.ts +170 -0
  1261. package/src/notifications/actions.ts +151 -0
  1262. package/src/notifications/channels.ts +410 -0
  1263. package/src/notifications/index.ts +22 -0
  1264. package/src/notifications/registry.ts +216 -0
  1265. package/src/notifications/routes.ts +106 -0
  1266. package/src/notifications/store.ts +236 -0
  1267. package/src/notifications/types.ts +52 -0
  1268. package/src/oauth-tokens/google-refresh.ts +175 -0
  1269. package/src/oauth-tokens/index.ts +15 -0
  1270. package/src/oauth-tokens/store.ts +384 -0
  1271. package/src/observability/cleanup-job.ts +124 -0
  1272. package/src/observability/evals.ts +507 -0
  1273. package/src/observability/experiments.ts +359 -0
  1274. package/src/observability/feedback.ts +332 -0
  1275. package/src/observability/hosted-model-experiment.ts +118 -0
  1276. package/src/observability/index.ts +62 -0
  1277. package/src/observability/plugin.ts +31 -0
  1278. package/src/observability/routes.ts +431 -0
  1279. package/src/observability/sentiment.ts +261 -0
  1280. package/src/observability/store.ts +1347 -0
  1281. package/src/observability/traces.ts +789 -0
  1282. package/src/observability/tracing.ts +157 -0
  1283. package/src/observability/tracking-identity.ts +55 -0
  1284. package/src/observability/types.ts +211 -0
  1285. package/src/onboarding/default-steps.ts +428 -0
  1286. package/src/onboarding/index.ts +22 -0
  1287. package/src/onboarding/plugin.ts +262 -0
  1288. package/src/onboarding/registry.ts +47 -0
  1289. package/src/onboarding/types.ts +96 -0
  1290. package/src/org/accept-pending.ts +98 -0
  1291. package/src/org/auto-join-domain.ts +113 -0
  1292. package/src/org/context.ts +640 -0
  1293. package/src/org/free-email-providers.ts +124 -0
  1294. package/src/org/handlers.ts +1153 -0
  1295. package/src/org/index.ts +63 -0
  1296. package/src/org/migrations.ts +105 -0
  1297. package/src/org/permissions.ts +30 -0
  1298. package/src/org/plugin.ts +255 -0
  1299. package/src/org/schema.ts +28 -0
  1300. package/src/org/types.ts +57 -0
  1301. package/src/org-team/index.ts +1 -0
  1302. package/src/package-lifecycle/deprecated-imports.ts +179 -0
  1303. package/src/package-lifecycle/eject-manifest.ts +345 -0
  1304. package/src/package-lifecycle/index.ts +7 -0
  1305. package/src/package-lifecycle/manifest.ts +120 -0
  1306. package/src/package-lifecycle/migration-manifest.ts +159 -0
  1307. package/src/package-lifecycle/migration-message.ts +6 -0
  1308. package/src/package-lifecycle/tombstone.ts +58 -0
  1309. package/src/package-lifecycle/upgrade-error.ts +37 -0
  1310. package/src/private-blob/index.ts +18 -0
  1311. package/src/private-blob/registry.ts +242 -0
  1312. package/src/private-blob/types.ts +55 -0
  1313. package/src/progress/actions.ts +179 -0
  1314. package/src/progress/index.ts +16 -0
  1315. package/src/progress/registry.ts +127 -0
  1316. package/src/progress/routes.ts +89 -0
  1317. package/src/progress/store.ts +353 -0
  1318. package/src/progress/types.ts +59 -0
  1319. package/src/provider-api/actions/custom-provider-registration.ts +197 -0
  1320. package/src/provider-api/actions/delete-staged-dataset.ts +5 -0
  1321. package/src/provider-api/actions/github-repo-files.ts +266 -0
  1322. package/src/provider-api/actions/list-staged-datasets.ts +5 -0
  1323. package/src/provider-api/actions/provider-api-audit.ts +88 -0
  1324. package/src/provider-api/actions/provider-api.ts +460 -0
  1325. package/src/provider-api/actions/query-staged-dataset.ts +5 -0
  1326. package/src/provider-api/actions/staged-datasets.ts +359 -0
  1327. package/src/provider-api/corpus-jobs-store.ts +522 -0
  1328. package/src/provider-api/corpus-jobs.ts +1817 -0
  1329. package/src/provider-api/custom-registry.ts +456 -0
  1330. package/src/provider-api/github-repo.ts +675 -0
  1331. package/src/provider-api/gong.ts +108 -0
  1332. package/src/provider-api/index.ts +5444 -0
  1333. package/src/provider-api/quota-governor.ts +562 -0
  1334. package/src/provider-api/runtime.ts +1 -0
  1335. package/src/provider-api/staged-datasets-aggregate.ts +304 -0
  1336. package/src/provider-api/staged-datasets-store.ts +441 -0
  1337. package/src/provider-api/staging.ts +483 -0
  1338. package/src/resources/agents.ts +65 -0
  1339. package/src/resources/emitter.ts +54 -0
  1340. package/src/resources/handlers.ts +728 -0
  1341. package/src/resources/index.ts +4 -0
  1342. package/src/resources/metadata.ts +252 -0
  1343. package/src/resources/script-helpers.ts +160 -0
  1344. package/src/resources/store.ts +1618 -0
  1345. package/src/review/actions/consume-review-feedback.ts +48 -0
  1346. package/src/review/actions/create-review-comment.ts +108 -0
  1347. package/src/review/actions/delete-review-comment.ts +67 -0
  1348. package/src/review/actions/get-review-feedback.ts +49 -0
  1349. package/src/review/actions/list-review-comments.ts +87 -0
  1350. package/src/review/actions/reply-review-comment.ts +118 -0
  1351. package/src/review/actions/resolve-review-thread.ts +94 -0
  1352. package/src/review/actions/send-review-thread-to-agent.ts +62 -0
  1353. package/src/review/actions/set-review-status.ts +58 -0
  1354. package/src/review/identity.ts +101 -0
  1355. package/src/review/index.ts +45 -0
  1356. package/src/review/mentions.ts +44 -0
  1357. package/src/review/registry.ts +111 -0
  1358. package/src/review/store.ts +997 -0
  1359. package/src/review/types.ts +101 -0
  1360. package/src/router/index.ts +24 -0
  1361. package/src/scripts/agent-engines/list-agent-engines.ts +130 -0
  1362. package/src/scripts/agent-engines/manage-agent-engine.ts +197 -0
  1363. package/src/scripts/agent-engines/set-agent-engine.ts +84 -0
  1364. package/src/scripts/agent-engines/test-agent-engine.ts +174 -0
  1365. package/src/scripts/call-agent.ts +792 -0
  1366. package/src/scripts/chat/index.ts +8 -0
  1367. package/src/scripts/chat/open-chat.ts +56 -0
  1368. package/src/scripts/chat/search-chats.ts +121 -0
  1369. package/src/scripts/core-scripts.ts +22 -0
  1370. package/src/scripts/db/check-scoping.ts +212 -0
  1371. package/src/scripts/db/exec.ts +791 -0
  1372. package/src/scripts/db/index.ts +19 -0
  1373. package/src/scripts/db/migrate-encrypt-credentials.ts +223 -0
  1374. package/src/scripts/db/migrate-encrypt-oauth-tokens.ts +213 -0
  1375. package/src/scripts/db/migrate-user-api-keys.ts +257 -0
  1376. package/src/scripts/db/patch.ts +820 -0
  1377. package/src/scripts/db/query.ts +299 -0
  1378. package/src/scripts/db/reset-dev-owner.ts +285 -0
  1379. package/src/scripts/db/safety.ts +286 -0
  1380. package/src/scripts/db/schema.ts +364 -0
  1381. package/src/scripts/db/scoping.ts +341 -0
  1382. package/src/scripts/db/sqlite-client.ts +78 -0
  1383. package/src/scripts/db/tool-mode.ts +23 -0
  1384. package/src/scripts/db/tool-schemas.ts +31 -0
  1385. package/src/scripts/db/wipe-leaked-builder-keys.ts +201 -0
  1386. package/src/scripts/dev/index.ts +296 -0
  1387. package/src/scripts/dev/list-files.ts +119 -0
  1388. package/src/scripts/dev/read-file.ts +77 -0
  1389. package/src/scripts/dev/search-files.ts +151 -0
  1390. package/src/scripts/dev/shell.ts +111 -0
  1391. package/src/scripts/dev/write-file.ts +56 -0
  1392. package/src/scripts/dev-session.ts +96 -0
  1393. package/src/scripts/docs/index.ts +8 -0
  1394. package/src/scripts/docs/search.ts +274 -0
  1395. package/src/scripts/docs/source-search.ts +363 -0
  1396. package/src/scripts/index.ts +15 -0
  1397. package/src/scripts/manage-agent-loop-settings.ts +84 -0
  1398. package/src/scripts/parse-args.ts +46 -0
  1399. package/src/scripts/resources/delete-memory.ts +67 -0
  1400. package/src/scripts/resources/delete.ts +69 -0
  1401. package/src/scripts/resources/effective.ts +71 -0
  1402. package/src/scripts/resources/index.ts +18 -0
  1403. package/src/scripts/resources/list.ts +103 -0
  1404. package/src/scripts/resources/migrate-learnings.ts +40 -0
  1405. package/src/scripts/resources/read.ts +107 -0
  1406. package/src/scripts/resources/save-memory.ts +99 -0
  1407. package/src/scripts/resources/write.ts +158 -0
  1408. package/src/scripts/runner.ts +339 -0
  1409. package/src/scripts/utils.ts +130 -0
  1410. package/src/search/index.ts +413 -0
  1411. package/src/search-utils/index.ts +112 -0
  1412. package/src/secrets/crypto.ts +228 -0
  1413. package/src/secrets/index.ts +65 -0
  1414. package/src/secrets/onboarding.ts +101 -0
  1415. package/src/secrets/register-framework-secrets.ts +228 -0
  1416. package/src/secrets/register.ts +129 -0
  1417. package/src/secrets/routes.ts +697 -0
  1418. package/src/secrets/schema.ts +52 -0
  1419. package/src/secrets/storage.ts +561 -0
  1420. package/src/secrets/substitution.ts +305 -0
  1421. package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +150 -0
  1422. package/src/server/action-change.ts +66 -0
  1423. package/src/server/action-discovery.ts +774 -0
  1424. package/src/server/action-routes.ts +658 -0
  1425. package/src/server/agent-access.ts +72 -0
  1426. package/src/server/agent-chat/action-filters-a2a.ts +339 -0
  1427. package/src/server/agent-chat/browser-team-tools.ts +510 -0
  1428. package/src/server/agent-chat/context-tools.ts +599 -0
  1429. package/src/server/agent-chat/framework-prompts.ts +467 -0
  1430. package/src/server/agent-chat/lazy-fs.ts +11 -0
  1431. package/src/server/agent-chat/mcp-glue.ts +91 -0
  1432. package/src/server/agent-chat/plugin-options.ts +379 -0
  1433. package/src/server/agent-chat/process-run-failure.ts +62 -0
  1434. package/src/server/agent-chat/prompt-resources.ts +1004 -0
  1435. package/src/server/agent-chat/recurring-jobs-runtime.ts +80 -0
  1436. package/src/server/agent-chat/request-surface.ts +71 -0
  1437. package/src/server/agent-chat/run-code-tools.ts +67 -0
  1438. package/src/server/agent-chat/script-entries.ts +796 -0
  1439. package/src/server/agent-chat/shared-thread.ts +301 -0
  1440. package/src/server/agent-chat/skill-frontmatter.ts +41 -0
  1441. package/src/server/agent-chat-plugin.ts +5930 -0
  1442. package/src/server/agent-discovery.ts +706 -0
  1443. package/src/server/agent-engine-api-key-route.ts +236 -0
  1444. package/src/server/agent-run-context.ts +204 -0
  1445. package/src/server/agent-teams-run-queue.ts +344 -0
  1446. package/src/server/agent-teams.ts +2257 -0
  1447. package/src/server/agents-bundle.ts +464 -0
  1448. package/src/server/analytics.ts +105 -0
  1449. package/src/server/app-base-path.ts +79 -0
  1450. package/src/server/app-name.ts +53 -0
  1451. package/src/server/app-url.ts +184 -0
  1452. package/src/server/attachment-actions.ts +276 -0
  1453. package/src/server/attribution.ts +214 -0
  1454. package/src/server/auth-marketing.ts +260 -0
  1455. package/src/server/auth-plugin.ts +32 -0
  1456. package/src/server/auth.ts +3803 -0
  1457. package/src/server/better-auth-instance.ts +1201 -0
  1458. package/src/server/builder-browser.ts +1701 -0
  1459. package/src/server/builder-design-systems.ts +668 -0
  1460. package/src/server/builder-space.ts +176 -0
  1461. package/src/server/captcha.ts +63 -0
  1462. package/src/server/capture-error.ts +64 -0
  1463. package/src/server/cli-capture.ts +141 -0
  1464. package/src/server/collab-plugin.ts +391 -0
  1465. package/src/server/complete-text.ts +231 -0
  1466. package/src/server/cookie-namespace.ts +159 -0
  1467. package/src/server/core-routes-plugin.ts +4122 -0
  1468. package/src/server/cors-origins.ts +60 -0
  1469. package/src/server/create-server.ts +268 -0
  1470. package/src/server/credential-provider.ts +1202 -0
  1471. package/src/server/csrf.ts +229 -0
  1472. package/src/server/date-utils.ts +42 -0
  1473. package/src/server/deep-link.ts +119 -0
  1474. package/src/server/derived-secret.ts +32 -0
  1475. package/src/server/design-token-utils.ts +1032 -0
  1476. package/src/server/desktop-sso.ts +83 -0
  1477. package/src/server/edge.ts +69 -0
  1478. package/src/server/email-actions.ts +218 -0
  1479. package/src/server/email-template.ts +192 -0
  1480. package/src/server/email-templates.ts +148 -0
  1481. package/src/server/email.ts +219 -0
  1482. package/src/server/embed-route.ts +283 -0
  1483. package/src/server/embed-session.ts +805 -0
  1484. package/src/server/embedded.ts +252 -0
  1485. package/src/server/entry-server.tsx +154 -0
  1486. package/src/server/framework-request-handler.ts +848 -0
  1487. package/src/server/fusion-app.ts +447 -0
  1488. package/src/server/google-auth-mode.ts +34 -0
  1489. package/src/server/google-auth-plugin.ts +496 -0
  1490. package/src/server/google-oauth-credentials.ts +128 -0
  1491. package/src/server/google-oauth.ts +972 -0
  1492. package/src/server/google-realtime-session.ts +182 -0
  1493. package/src/server/h3-helpers.ts +174 -0
  1494. package/src/server/http-response-telemetry.ts +396 -0
  1495. package/src/server/identity-sso-store.ts +304 -0
  1496. package/src/server/identity-sso.ts +514 -0
  1497. package/src/server/index.ts +593 -0
  1498. package/src/server/missing-key.ts +36 -0
  1499. package/src/server/oauth-helpers.ts +44 -0
  1500. package/src/server/oauth-public-origin.ts +44 -0
  1501. package/src/server/oauth-return-url.ts +120 -0
  1502. package/src/server/og-fonts-data.ts +15 -0
  1503. package/src/server/og-fonts.ts +75 -0
  1504. package/src/server/onboarding-html.ts +4040 -0
  1505. package/src/server/open-route.ts +311 -0
  1506. package/src/server/org-admin.ts +28 -0
  1507. package/src/server/poll-events.ts +88 -0
  1508. package/src/server/poll.ts +1224 -0
  1509. package/src/server/prompts/framework-core-compact.ts +107 -0
  1510. package/src/server/prompts/framework-core.ts +143 -0
  1511. package/src/server/prompts/index.ts +16 -0
  1512. package/src/server/prompts/model-overlays.ts +54 -0
  1513. package/src/server/prompts/shared-rules.ts +104 -0
  1514. package/src/server/realtime-voice.ts +967 -0
  1515. package/src/server/recap-image-route.ts +242 -0
  1516. package/src/server/recap-image-store.ts +185 -0
  1517. package/src/server/request-context.ts +416 -0
  1518. package/src/server/request-origin.ts +67 -0
  1519. package/src/server/resources-plugin.ts +119 -0
  1520. package/src/server/schema-prompt.ts +429 -0
  1521. package/src/server/scoped-key-storage.ts +223 -0
  1522. package/src/server/security-headers.ts +170 -0
  1523. package/src/server/self-dispatch.ts +224 -0
  1524. package/src/server/sentry-config.ts +73 -0
  1525. package/src/server/sentry-plugin.ts +138 -0
  1526. package/src/server/sentry.ts +444 -0
  1527. package/src/server/short-lived-token.ts +170 -0
  1528. package/src/server/social-og-image.ts +454 -0
  1529. package/src/server/sse.ts +102 -0
  1530. package/src/server/ssr-handler.ts +466 -0
  1531. package/src/server/transcribe-voice.ts +1076 -0
  1532. package/src/server/voice-providers-status.ts +118 -0
  1533. package/src/server/workspace-oauth.ts +31 -0
  1534. package/src/server/workspace-provider-oauth.ts +970 -0
  1535. package/src/session-replay-iframe-protocol.ts +58 -0
  1536. package/src/settings/handlers.ts +44 -0
  1537. package/src/settings/index.ts +34 -0
  1538. package/src/settings/org-settings.ts +83 -0
  1539. package/src/settings/script-helpers.ts +24 -0
  1540. package/src/settings/store.ts +247 -0
  1541. package/src/settings/user-settings.ts +47 -0
  1542. package/src/setup-connections/index.ts +90 -0
  1543. package/src/setup-connections/onboarding.ts +47 -0
  1544. package/src/shared/agent-access.ts +112 -0
  1545. package/src/shared/agent-chat.ts +124 -0
  1546. package/src/shared/agent-env.ts +46 -0
  1547. package/src/shared/agent-readable-resource.ts +111 -0
  1548. package/src/shared/agent-sidebar-url.ts +39 -0
  1549. package/src/shared/cache-control.ts +25 -0
  1550. package/src/shared/cloudflare-globals.d.ts +34 -0
  1551. package/src/shared/context-xray.ts +172 -0
  1552. package/src/shared/embed-auth.ts +6 -0
  1553. package/src/shared/index.ts +83 -0
  1554. package/src/shared/llm-connection.ts +42 -0
  1555. package/src/shared/markdown-block-split.ts +129 -0
  1556. package/src/shared/mcp-connect-content.ts +130 -0
  1557. package/src/shared/mcp-embed-headers.ts +175 -0
  1558. package/src/shared/mcp-integration-config.ts +101 -0
  1559. package/src/shared/merge-by-id.ts +14 -0
  1560. package/src/shared/oauth-state.ts +39 -0
  1561. package/src/shared/reasoning-effort.ts +228 -0
  1562. package/src/shared/route-warmup-config.ts +128 -0
  1563. package/src/shared/runtime.ts +31 -0
  1564. package/src/shared/social-meta.ts +82 -0
  1565. package/src/shared/streaming-text-smoothing.ts +184 -0
  1566. package/src/shared/truncate.ts +13 -0
  1567. package/src/shared/workspace-app-audience.ts +177 -0
  1568. package/src/shared/workspace-app-id.ts +54 -0
  1569. package/src/sharing/access.ts +585 -0
  1570. package/src/sharing/actions/create-agent-resource-link.ts +91 -0
  1571. package/src/sharing/actions/extension-change.ts +22 -0
  1572. package/src/sharing/actions/list-resource-shares.ts +93 -0
  1573. package/src/sharing/actions/set-resource-visibility.ts +91 -0
  1574. package/src/sharing/actions/share-resource.ts +322 -0
  1575. package/src/sharing/actions/unshare-resource.ts +72 -0
  1576. package/src/sharing/index.ts +35 -0
  1577. package/src/sharing/registry.ts +201 -0
  1578. package/src/sharing/schema.ts +99 -0
  1579. package/src/styles/agent-conversation.css +766 -0
  1580. package/src/styles/agent-native.css +938 -0
  1581. package/src/styles/blocks.css +1717 -0
  1582. package/src/styles/chat-history-list.css +1 -0
  1583. package/src/styles/rich-markdown-editor.css +1 -0
  1584. package/src/tailwind.preset.ts +140 -0
  1585. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1586. package/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1587. package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1588. package/src/templates/chat/AGENTS.md +3 -2
  1589. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1590. package/src/templates/default/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1591. package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1592. package/src/templates/default/AGENTS.md +6 -5
  1593. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1594. package/src/templates/headless/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1595. package/src/templates/headless/AGENTS.md +5 -4
  1596. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +15 -13
  1597. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +3 -2
  1598. package/src/templates/workspace-core/.agents/skills/customizing-agent-native/SKILL.md +63 -20
  1599. package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +9 -6
  1600. package/src/templates/workspace-core/AGENTS.md +4 -4
  1601. package/src/templates/workspace-root/AGENTS.md +4 -4
  1602. package/src/terminal/cli-registry.ts +57 -0
  1603. package/src/terminal/index.ts +22 -0
  1604. package/src/terminal/pty-server.ts +389 -0
  1605. package/src/terminal/terminal-plugin.ts +265 -0
  1606. package/src/testing.ts +23 -0
  1607. package/src/tracking/index.ts +10 -0
  1608. package/src/tracking/providers.ts +422 -0
  1609. package/src/tracking/registry.ts +102 -0
  1610. package/src/tracking/route.ts +108 -0
  1611. package/src/tracking/types.ts +16 -0
  1612. package/src/transcription/builder-transcription.ts +118 -0
  1613. package/src/triggers/actions/list-automations.ts +99 -0
  1614. package/src/triggers/actions/manage-automation.ts +78 -0
  1615. package/src/triggers/actions.ts +333 -0
  1616. package/src/triggers/condition-evaluator.ts +163 -0
  1617. package/src/triggers/dispatcher.ts +536 -0
  1618. package/src/triggers/index.ts +13 -0
  1619. package/src/triggers/routes.ts +322 -0
  1620. package/src/triggers/types.ts +42 -0
  1621. package/src/types/pglite.d.ts +26 -0
  1622. package/src/usage/store.ts +698 -0
  1623. package/src/vite/action-types-plugin.ts +481 -0
  1624. package/src/vite/agent-web-plugin.ts +78 -0
  1625. package/src/vite/agents-bundle-plugin.ts +185 -0
  1626. package/src/vite/client.ts +3056 -0
  1627. package/src/vite/index.ts +21 -0
  1628. package/src/voice/index.ts +18 -0
  1629. package/src/voice/voice-cleanup-prompt.ts +38 -0
  1630. package/src/voice/voice-context.ts +217 -0
  1631. package/src/voice/voice-replacements.ts +75 -0
  1632. package/src/workspace-connections/credentials.ts +603 -0
  1633. package/src/workspace-connections/index.ts +72 -0
  1634. package/src/workspace-connections/lifecycle.ts +43 -0
  1635. package/src/workspace-connections/store.ts +1943 -0
  1636. package/src/workspace-files/index.ts +18 -0
  1637. package/src/workspace-files/store.ts +339 -0
  1638. package/src/workspace-files/tool.ts +266 -0
@@ -0,0 +1,3187 @@
1
+ import { spawn } from "node:child_process";
2
+ import crypto from "node:crypto";
3
+ import fsSync from "node:fs";
4
+ import fs from "node:fs/promises";
5
+ import type { FileHandle } from "node:fs/promises";
6
+ import http, {
7
+ type IncomingMessage,
8
+ type Server,
9
+ type ServerResponse,
10
+ } from "node:http";
11
+ import https from "node:https";
12
+ import path from "node:path";
13
+
14
+ const DEFAULT_BRIDGE_PORT = 7331;
15
+ const ROUTE_MANIFEST_FILE = path.join(".agent-native", "design-routes.json");
16
+ const DEFAULT_DEV_SERVER_CANDIDATES = [
17
+ "http://127.0.0.1:5173",
18
+ "http://localhost:5173",
19
+ "http://127.0.0.1:3000",
20
+ "http://localhost:3000",
21
+ "http://127.0.0.1:8080",
22
+ "http://localhost:8080",
23
+ ];
24
+
25
+ const BRIDGE_OPERATIONS = [
26
+ "select",
27
+ "resolveNodeToFile",
28
+ "readFile",
29
+ "applyEdit",
30
+ "writeFile",
31
+ "captureSnapshot",
32
+ "captureState",
33
+ "listFiles",
34
+ ] as const;
35
+
36
+ type BridgeOperation = (typeof BRIDGE_OPERATIONS)[number];
37
+
38
+ const SERVER_REGISTRATION_BRIDGE_OPERATIONS = new Set<BridgeOperation>([
39
+ "select",
40
+ "resolveNodeToFile",
41
+ "readFile",
42
+ "applyEdit",
43
+ "writeFile",
44
+ "captureSnapshot",
45
+ "captureState",
46
+ ]);
47
+
48
+ /** Additive manifest capability flags advertised alongside the operation list. */
49
+ const MANIFEST_CAPABILITIES = {
50
+ listFiles: true,
51
+ readTextFiles: true,
52
+ writeTextFiles: true,
53
+ } as const;
54
+
55
+ export interface DesignConnectArgs {
56
+ url?: string;
57
+ port: number;
58
+ root: string;
59
+ routeManifest?: string;
60
+ /** Optional deployed design app URL used to self-register the bridge on
61
+ * startup. When set (or when AGENT_NATIVE_URL / DESIGN_APP_URL env vars
62
+ * are present) the CLI POSTs to `/_agent-native/actions/connect-localhost`
63
+ * with the real bridge token so the server can store it for grant minting. */
64
+ appUrl?: string;
65
+ /** Server-minted bridge token to adopt instead of minting one, so the bridge
66
+ * matches the token already stored on the user's connection row (no
67
+ * self-registration). Also read from AGENT_NATIVE_BRIDGE_TOKEN. */
68
+ bridgeToken?: string;
69
+ /** Read-only token used by Design browser previews. This is deliberately
70
+ * distinct from `bridgeToken`, which unlocks local filesystem reads/writes.
71
+ * When omitted it is derived one-way from bridgeToken for compatibility
72
+ * with existing /visual-edit launch commands. */
73
+ previewToken?: string;
74
+ json: boolean;
75
+ once: boolean;
76
+ dryRun: boolean;
77
+ daemon: boolean;
78
+ help: boolean;
79
+ }
80
+
81
+ export interface DesignConnectRoute {
82
+ id: string;
83
+ path: string;
84
+ title: string;
85
+ sourceFile?: string;
86
+ sourceKind: "react-router" | "html" | "manual";
87
+ screenshotUrl?: string;
88
+ metadata?: Record<string, unknown>;
89
+ }
90
+
91
+ export interface DesignConnectManifest {
92
+ version: 1;
93
+ source: "agent-native-design-connect";
94
+ sourceType: "localhost";
95
+ localOnly: true;
96
+ devServerUrl: string;
97
+ bridgeUrl: string;
98
+ rootPath: string;
99
+ routeManifestPath: string;
100
+ routeManifestCreated: boolean;
101
+ routes: DesignConnectRoute[];
102
+ routeCount: number;
103
+ generatedAt: string;
104
+ capabilities: Array<{
105
+ operation: BridgeOperation;
106
+ status: "available" | "planned" | "disabled";
107
+ reason?: string;
108
+ }>;
109
+ /**
110
+ * Additive high-level capability flags beyond the low-level operation list.
111
+ * `connect-localhost` persists this so `list-design-source-capabilities` can
112
+ * reflect readFile/writeFile/listFiles availability for localhost sources.
113
+ */
114
+ manifestCapabilities: typeof MANIFEST_CAPABILITIES;
115
+ }
116
+
117
+ export interface DesignConnectBridge {
118
+ server: Server;
119
+ manifest: DesignConnectManifest;
120
+ /** Per-rootPath bridge token. Kept in-process only; never serialised into
121
+ * the manifest JSON so it is not exposed over the network via GET /manifest.
122
+ * The server-side grant action reads it from the running bridge instance. */
123
+ bridgeToken: string;
124
+ /** Read-only token accepted by manifest, route, snapshot, proxy, and editor
125
+ * bridge-registration endpoints. Safe to hand to the Design browser, but
126
+ * never accepted by filesystem endpoints. */
127
+ previewToken: string;
128
+ /** Random id minted fresh each time this bridge process boots. Also
129
+ * returned by `/health`, `/live-edit-bridge`, and the "unknown bridge key"
130
+ * 409 from `/live-edit` — a client can compare it across those responses
131
+ * to tell a restarted bridge process apart from a genuine registration
132
+ * bug. See the `bridgeInstanceId` doc comment in startDesignConnectBridge. */
133
+ bridgeInstanceId: string;
134
+ }
135
+
136
+ export interface DesignConnectBridgeOptions {
137
+ bridgeToken?: string;
138
+ previewToken?: string;
139
+ /** Extra exact browser origins allowed to make CORS requests to the bridge.
140
+ * The production Design origin and loopback development origins are always
141
+ * recognized; custom deployments should pass their app origin here. */
142
+ allowedOrigins?: string[];
143
+ }
144
+
145
+ const PREVIEW_TOKEN_DOMAIN = "agent-native-design-preview-v1\0";
146
+ const PREVIEW_SESSION_COOKIE_NAME = "agent-native-preview-token";
147
+
148
+ /**
149
+ * Derive a read-only preview credential from the stronger filesystem token.
150
+ * The one-way hash keeps old `--bridge-token` launch commands compatible while
151
+ * ensuring a leaked preview credential cannot be promoted into write access.
152
+ */
153
+ export function deriveDesignPreviewToken(bridgeToken: string): string {
154
+ return crypto
155
+ .createHash("sha256")
156
+ .update(PREVIEW_TOKEN_DOMAIN)
157
+ .update(bridgeToken)
158
+ .digest("hex");
159
+ }
160
+
161
+ function stringFlagValue(argv: string[], index: number, flag: string) {
162
+ const value = argv[index + 1];
163
+ if (!value || value.startsWith("-")) {
164
+ throw new Error(`${flag} requires a value`);
165
+ }
166
+ return value;
167
+ }
168
+
169
+ function normalizeSlash(value: string) {
170
+ return value.replace(/\\/g, "/");
171
+ }
172
+
173
+ function normalizeHttpUrl(value: string): string {
174
+ const raw = value.trim();
175
+ const withProtocol = /^[a-z]+:\/\//i.test(raw) ? raw : `http://${raw}`;
176
+ const parsed = new URL(withProtocol);
177
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
178
+ throw new Error("--url must be an http(s) URL");
179
+ }
180
+ parsed.hash = "";
181
+ return parsed.toString().replace(/\/$/, "");
182
+ }
183
+
184
+ export function parseDesignConnectArgs(argv: string[]): DesignConnectArgs {
185
+ const args = argv[0] === "connect" ? argv.slice(1) : argv;
186
+ const parsed: DesignConnectArgs = {
187
+ port: DEFAULT_BRIDGE_PORT,
188
+ root: process.cwd(),
189
+ json: false,
190
+ once: false,
191
+ dryRun: false,
192
+ daemon: false,
193
+ help: false,
194
+ };
195
+
196
+ for (let index = 0; index < args.length; index += 1) {
197
+ const arg = args[index]!;
198
+ if (arg === "help" || arg === "--help" || arg === "-h") {
199
+ parsed.help = true;
200
+ } else if (arg === "--url") {
201
+ parsed.url = stringFlagValue(args, index, arg);
202
+ index += 1;
203
+ } else if (arg.startsWith("--url=")) {
204
+ parsed.url = arg.slice("--url=".length);
205
+ } else if (arg === "--port") {
206
+ parsed.port = Number.parseInt(stringFlagValue(args, index, arg), 10);
207
+ index += 1;
208
+ } else if (arg.startsWith("--port=")) {
209
+ parsed.port = Number.parseInt(arg.slice("--port=".length), 10);
210
+ } else if (arg === "--root") {
211
+ parsed.root = stringFlagValue(args, index, arg);
212
+ index += 1;
213
+ } else if (arg.startsWith("--root=")) {
214
+ parsed.root = arg.slice("--root=".length);
215
+ } else if (arg === "--route-manifest") {
216
+ parsed.routeManifest = stringFlagValue(args, index, arg);
217
+ index += 1;
218
+ } else if (arg.startsWith("--route-manifest=")) {
219
+ parsed.routeManifest = arg.slice("--route-manifest=".length);
220
+ } else if (arg === "--app-url") {
221
+ parsed.appUrl = stringFlagValue(args, index, arg);
222
+ index += 1;
223
+ } else if (arg.startsWith("--app-url=")) {
224
+ parsed.appUrl = arg.slice("--app-url=".length);
225
+ } else if (arg === "--bridge-token") {
226
+ parsed.bridgeToken = stringFlagValue(args, index, arg);
227
+ index += 1;
228
+ } else if (arg.startsWith("--bridge-token=")) {
229
+ parsed.bridgeToken = arg.slice("--bridge-token=".length);
230
+ } else if (arg === "--preview-token") {
231
+ parsed.previewToken = stringFlagValue(args, index, arg);
232
+ index += 1;
233
+ } else if (arg.startsWith("--preview-token=")) {
234
+ parsed.previewToken = arg.slice("--preview-token=".length);
235
+ } else if (arg === "--json") {
236
+ parsed.json = true;
237
+ parsed.once = true;
238
+ } else if (arg === "--once") {
239
+ parsed.once = true;
240
+ } else if (arg === "--dry-run") {
241
+ parsed.dryRun = true;
242
+ parsed.once = true;
243
+ } else if (arg === "--daemon") {
244
+ parsed.daemon = true;
245
+ } else if (arg.startsWith("-")) {
246
+ throw new Error(`Unknown option: ${arg}`);
247
+ } else {
248
+ throw new Error(`Unexpected argument: ${arg}`);
249
+ }
250
+ }
251
+
252
+ if (!Number.isInteger(parsed.port) || parsed.port <= 0) {
253
+ throw new Error("--port must be a positive integer");
254
+ }
255
+ if (parsed.daemon && (parsed.json || parsed.once || parsed.dryRun)) {
256
+ throw new Error(
257
+ "--daemon cannot be combined with --json, --once, or --dry-run",
258
+ );
259
+ }
260
+ parsed.root = path.resolve(parsed.root);
261
+ parsed.url = parsed.url ? normalizeHttpUrl(parsed.url) : undefined;
262
+ return parsed;
263
+ }
264
+
265
+ function routeId(routePath: string): string {
266
+ const normalized = routePath.trim() || "/";
267
+ const slug = normalized
268
+ .replace(/^\/+/, "")
269
+ .replace(/\*/g, "w")
270
+ .replace(/:/g, "p")
271
+ .replace(/[[\]]/g, "")
272
+ .replace(/[^a-zA-Z0-9]+/g, "-")
273
+ .replace(/^-+|-+$/g, "")
274
+ .toLowerCase();
275
+ // Slugs are intentionally readable but necessarily lossy: `/foo/bar`,
276
+ // `/foo-bar`, and `/foo_bar` all collapse to the same text, while `/` and
277
+ // `/*` can collide with literal `/root` and `/wildcard` paths. Suffix every
278
+ // route with a stable hash of its normalized path so URL/query states and
279
+ // router patterns can never silently replace one another in the manifest.
280
+ const readable =
281
+ normalized === "/"
282
+ ? "root"
283
+ : /^\/\*+$/.test(normalized) || !slug
284
+ ? "wildcard"
285
+ : slug;
286
+ return `route-${readable}-${stableRoutePathHash(normalized)}`;
287
+ }
288
+
289
+ function stableRoutePathHash(value: string): string {
290
+ let hash = 0xcbf29ce484222325n;
291
+ for (const character of value) {
292
+ hash ^= BigInt(character.codePointAt(0)!);
293
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
294
+ }
295
+ return hash.toString(36);
296
+ }
297
+
298
+ function titleFromRoutePath(routePath: string): string {
299
+ if (routePath === "/") return "Home";
300
+ if (routePath === "/*" || routePath === "*") return "Wildcard";
301
+ return (
302
+ routePath
303
+ .replace(/^\/+/, "")
304
+ .replace(/[:$]/g, "")
305
+ .replace(/[-_/]+/g, " ")
306
+ .replace(/\s+/g, " ")
307
+ .trim()
308
+ .replace(/\b\w/g, (char) => char.toUpperCase()) || "Screen"
309
+ );
310
+ }
311
+
312
+ function walkFiles(dir: string, files: string[] = []): string[] {
313
+ if (!fsSync.existsSync(dir)) return files;
314
+ for (const entry of fsSync.readdirSync(dir, { withFileTypes: true })) {
315
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
316
+ const absolute = path.join(dir, entry.name);
317
+ if (entry.isDirectory()) {
318
+ walkFiles(absolute, files);
319
+ } else {
320
+ files.push(absolute);
321
+ }
322
+ }
323
+ return files;
324
+ }
325
+
326
+ function routePathFromReactRouterFile(filePath: string, routesDir: string) {
327
+ const withoutExt = normalizeSlash(path.relative(routesDir, filePath)).replace(
328
+ /\.[cm]?[jt]sx?$/,
329
+ "",
330
+ );
331
+ const parts: string[] = [];
332
+ for (const segment of withoutExt.split("/")) {
333
+ for (const token of segment.split(".")) {
334
+ if (!token || token === "route" || token === "index") continue;
335
+ if (token === "_index") continue;
336
+ if (token.startsWith("_")) continue;
337
+ const pathToken = token.endsWith("_") ? token.slice(0, -1) : token;
338
+ if (pathToken === "$") {
339
+ parts.push("*");
340
+ } else if (pathToken.startsWith("$")) {
341
+ const param = pathToken.slice(1) || "param";
342
+ parts.push(`:${param}`);
343
+ } else {
344
+ parts.push(pathToken);
345
+ }
346
+ }
347
+ }
348
+ return `/${parts.join("/")}`.replace(/\/+/g, "/");
349
+ }
350
+
351
+ export function discoverDesignRoutes(root: string): DesignConnectRoute[] {
352
+ const absoluteRoot = path.resolve(root);
353
+ const routeDirs = [
354
+ path.join(absoluteRoot, "app", "routes"),
355
+ path.join(absoluteRoot, "src", "routes"),
356
+ path.join(absoluteRoot, "pages"),
357
+ ].filter((dir) => fsSync.existsSync(dir));
358
+ const routes = new Map<string, DesignConnectRoute>();
359
+
360
+ for (const routeDir of routeDirs) {
361
+ for (const file of walkFiles(routeDir)) {
362
+ if (!/\.[cm]?[jt]sx?$/.test(file)) continue;
363
+ if (/\.test\.|\.spec\./.test(file)) continue;
364
+ const pathName = routePathFromReactRouterFile(file, routeDir);
365
+ routes.set(pathName, {
366
+ id: routeId(pathName),
367
+ path: pathName,
368
+ title: titleFromRoutePath(pathName),
369
+ sourceFile: normalizeSlash(path.relative(absoluteRoot, file)),
370
+ sourceKind: "react-router",
371
+ });
372
+ }
373
+ }
374
+
375
+ if (routes.size === 0) {
376
+ for (const dir of [absoluteRoot, path.join(absoluteRoot, "public")]) {
377
+ for (const file of walkFiles(dir)) {
378
+ if (!file.endsWith(".html")) continue;
379
+ const rel = normalizeSlash(path.relative(dir, file));
380
+ const pathName =
381
+ rel === "index.html" ? "/" : `/${rel.replace(/\.html$/, "")}`;
382
+ routes.set(pathName, {
383
+ id: routeId(pathName),
384
+ path: pathName,
385
+ title: titleFromRoutePath(pathName),
386
+ sourceFile: normalizeSlash(path.relative(absoluteRoot, file)),
387
+ sourceKind: "html",
388
+ });
389
+ }
390
+ }
391
+ }
392
+
393
+ return [...routes.values()].sort((a, b) => {
394
+ if (a.path === "/") return -1;
395
+ if (b.path === "/") return 1;
396
+ return a.path.localeCompare(b.path);
397
+ });
398
+ }
399
+
400
+ async function probeDevServer(url: string): Promise<boolean> {
401
+ const controller = new AbortController();
402
+ const timeout = setTimeout(() => controller.abort(), 800);
403
+ try {
404
+ const response = await fetch(url, {
405
+ method: "GET",
406
+ signal: controller.signal,
407
+ });
408
+ return response.status < 500;
409
+ } catch {
410
+ return false;
411
+ } finally {
412
+ clearTimeout(timeout);
413
+ }
414
+ }
415
+
416
+ async function waitForBridgeHealth(
417
+ bridgeUrl: string,
418
+ timeoutMs = 5_000,
419
+ ): Promise<boolean> {
420
+ const healthUrl = new URL("/health", bridgeUrl).toString();
421
+ const deadline = Date.now() + timeoutMs;
422
+ while (Date.now() < deadline) {
423
+ const controller = new AbortController();
424
+ const timeout = setTimeout(() => controller.abort(), 400);
425
+ try {
426
+ const response = await fetch(healthUrl, {
427
+ method: "GET",
428
+ signal: controller.signal,
429
+ });
430
+ if (response.ok) return true;
431
+ } catch {
432
+ // Keep polling until the detached process finishes binding the port.
433
+ } finally {
434
+ clearTimeout(timeout);
435
+ }
436
+ await new Promise((resolve) => setTimeout(resolve, 150));
437
+ }
438
+ return false;
439
+ }
440
+
441
+ function isDesignConnectManifest(
442
+ value: unknown,
443
+ ): value is DesignConnectManifest {
444
+ if (!value || typeof value !== "object") return false;
445
+ const manifest = value as Partial<DesignConnectManifest>;
446
+ return (
447
+ manifest.version === 1 &&
448
+ manifest.source === "agent-native-design-connect" &&
449
+ manifest.sourceType === "localhost" &&
450
+ manifest.localOnly === true &&
451
+ typeof manifest.devServerUrl === "string" &&
452
+ typeof manifest.bridgeUrl === "string" &&
453
+ typeof manifest.rootPath === "string"
454
+ );
455
+ }
456
+
457
+ async function fetchRunningBridgeManifest(
458
+ bridgeUrl: string,
459
+ previewToken?: string,
460
+ ): Promise<DesignConnectManifest | null> {
461
+ const manifestUrl = new URL("/manifest.json", bridgeUrl);
462
+ if (previewToken) {
463
+ manifestUrl.searchParams.set("previewToken", previewToken);
464
+ }
465
+ const controller = new AbortController();
466
+ const timeout = setTimeout(() => controller.abort(), 800);
467
+ try {
468
+ const response = await fetch(manifestUrl, {
469
+ method: "GET",
470
+ signal: controller.signal,
471
+ });
472
+ if (!response.ok) return null;
473
+ const body = (await response.json()) as unknown;
474
+ return isDesignConnectManifest(body) ? body : null;
475
+ } catch {
476
+ return null;
477
+ } finally {
478
+ clearTimeout(timeout);
479
+ }
480
+ }
481
+
482
+ export function designConnectManifestsTargetSameApp(
483
+ running: Pick<DesignConnectManifest, "devServerUrl" | "rootPath">,
484
+ requested: Pick<DesignConnectManifest, "devServerUrl" | "rootPath">,
485
+ ): boolean {
486
+ let runningUrl = running.devServerUrl;
487
+ let requestedUrl = requested.devServerUrl;
488
+ try {
489
+ runningUrl = normalizeHttpUrl(runningUrl);
490
+ requestedUrl = normalizeHttpUrl(requestedUrl);
491
+ } catch {
492
+ // Fall through to direct string comparison for malformed legacy manifests.
493
+ }
494
+ return (
495
+ runningUrl === requestedUrl &&
496
+ path.resolve(running.rootPath) === path.resolve(requested.rootPath)
497
+ );
498
+ }
499
+
500
+ /** Non-sensitive stable identifier used by /health so daemon reruns can detect
501
+ * an already-running bridge for the same app without exposing its root path or
502
+ * route manifest. */
503
+ export function designConnectAppFingerprint(
504
+ manifest: Pick<DesignConnectManifest, "devServerUrl" | "rootPath">,
505
+ ): string {
506
+ let devServerUrl = manifest.devServerUrl;
507
+ try {
508
+ devServerUrl = normalizeHttpUrl(devServerUrl);
509
+ } catch {
510
+ // Hash the original string for malformed legacy manifests.
511
+ }
512
+ return crypto
513
+ .createHash("sha256")
514
+ .update(`${devServerUrl}\n${path.resolve(manifest.rootPath)}`)
515
+ .digest("base64url")
516
+ .slice(0, 24);
517
+ }
518
+
519
+ async function fetchRunningBridgeFingerprint(
520
+ bridgeUrl: string,
521
+ ): Promise<string | null> {
522
+ const healthUrl = new URL("/health", bridgeUrl).toString();
523
+ const controller = new AbortController();
524
+ const timeout = setTimeout(() => controller.abort(), 800);
525
+ try {
526
+ const response = await fetch(healthUrl, {
527
+ method: "GET",
528
+ signal: controller.signal,
529
+ });
530
+ if (!response.ok) return null;
531
+ const body = (await response.json()) as Record<string, unknown>;
532
+ return typeof body["appFingerprint"] === "string"
533
+ ? body["appFingerprint"]
534
+ : null;
535
+ } catch {
536
+ return null;
537
+ } finally {
538
+ clearTimeout(timeout);
539
+ }
540
+ }
541
+
542
+ async function resolveDevServerUrl(url?: string): Promise<string> {
543
+ if (url) return normalizeHttpUrl(url);
544
+ for (const candidate of DEFAULT_DEV_SERVER_CANDIDATES) {
545
+ if (await probeDevServer(candidate)) return candidate;
546
+ }
547
+ return DEFAULT_DEV_SERVER_CANDIDATES[0]!;
548
+ }
549
+
550
+ async function ensureRouteManifest(options: {
551
+ root: string;
552
+ routeManifestPath: string;
553
+ routes: DesignConnectRoute[];
554
+ devServerUrl: string;
555
+ dryRun: boolean;
556
+ }): Promise<{ path: string; created: boolean; routes: DesignConnectRoute[] }> {
557
+ const manifestPath = path.isAbsolute(options.routeManifestPath)
558
+ ? options.routeManifestPath
559
+ : path.join(options.root, options.routeManifestPath);
560
+ if (fsSync.existsSync(manifestPath)) {
561
+ try {
562
+ const parsed = JSON.parse(
563
+ await fs.readFile(manifestPath, "utf8"),
564
+ ) as Record<string, unknown>;
565
+ const savedRoutes = Array.isArray(parsed.routes)
566
+ ? parsed.routes.flatMap((value): DesignConnectRoute[] => {
567
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
568
+ return [];
569
+ }
570
+ const route = value as Record<string, unknown>;
571
+ if (typeof route.path !== "string" || !route.path.trim()) return [];
572
+ const sourceKind =
573
+ route.sourceKind === "react-router" ||
574
+ route.sourceKind === "html" ||
575
+ route.sourceKind === "manual"
576
+ ? route.sourceKind
577
+ : "manual";
578
+ return [
579
+ {
580
+ id:
581
+ typeof route.id === "string" && route.id.trim()
582
+ ? route.id
583
+ : routeId(route.path),
584
+ path: route.path,
585
+ title:
586
+ typeof route.title === "string" && route.title.trim()
587
+ ? route.title
588
+ : titleFromRoutePath(route.path),
589
+ sourceFile:
590
+ typeof route.sourceFile === "string"
591
+ ? route.sourceFile
592
+ : undefined,
593
+ sourceKind,
594
+ screenshotUrl:
595
+ typeof route.screenshotUrl === "string"
596
+ ? route.screenshotUrl
597
+ : undefined,
598
+ metadata:
599
+ route.metadata &&
600
+ typeof route.metadata === "object" &&
601
+ !Array.isArray(route.metadata)
602
+ ? (route.metadata as Record<string, unknown>)
603
+ : undefined,
604
+ },
605
+ ];
606
+ })
607
+ : [];
608
+ if (savedRoutes.length > 0) {
609
+ const savedPaths = new Set(savedRoutes.map((route) => route.path));
610
+ return {
611
+ path: manifestPath,
612
+ created: false,
613
+ // Keep manual/custom route order and metadata while still surfacing
614
+ // newly discovered routes on subsequent bridge starts.
615
+ routes: [
616
+ ...savedRoutes,
617
+ ...options.routes.filter((route) => !savedPaths.has(route.path)),
618
+ ],
619
+ };
620
+ }
621
+ } catch {
622
+ // Never overwrite a malformed/custom file. Route discovery remains a
623
+ // safe runtime fallback and the user can repair the manifest in place.
624
+ }
625
+ return { path: manifestPath, created: false, routes: options.routes };
626
+ }
627
+ if (!options.dryRun) {
628
+ await fs.mkdir(path.dirname(manifestPath), { recursive: true });
629
+ await fs.writeFile(
630
+ manifestPath,
631
+ `${JSON.stringify(
632
+ {
633
+ version: 1,
634
+ sourceType: "localhost",
635
+ devServerUrl: options.devServerUrl,
636
+ rootPath: options.root,
637
+ routes: options.routes,
638
+ generatedAt: new Date().toISOString(),
639
+ },
640
+ null,
641
+ 2,
642
+ )}\n`,
643
+ "utf8",
644
+ );
645
+ }
646
+ return {
647
+ path: manifestPath,
648
+ created: !options.dryRun,
649
+ routes: options.routes,
650
+ };
651
+ }
652
+
653
+ export async function prepareDesignConnectManifest(
654
+ options: Partial<DesignConnectArgs> & { root?: string } = {},
655
+ ): Promise<DesignConnectManifest> {
656
+ const root = path.resolve(options.root ?? process.cwd());
657
+ const port = options.port ?? DEFAULT_BRIDGE_PORT;
658
+ const devServerUrl = await resolveDevServerUrl(options.url);
659
+ const bridgeUrl = `http://127.0.0.1:${port}`;
660
+ const discoveredRoutes = discoverDesignRoutes(root);
661
+ const routeManifest = await ensureRouteManifest({
662
+ root,
663
+ routeManifestPath: options.routeManifest ?? ROUTE_MANIFEST_FILE,
664
+ routes: discoveredRoutes,
665
+ devServerUrl,
666
+ dryRun: Boolean(options.dryRun),
667
+ });
668
+ const generatedAt = new Date().toISOString();
669
+ const routes = routeManifest.routes;
670
+
671
+ return {
672
+ version: 1,
673
+ source: "agent-native-design-connect",
674
+ sourceType: "localhost",
675
+ localOnly: true,
676
+ devServerUrl,
677
+ bridgeUrl,
678
+ rootPath: root,
679
+ routeManifestPath: routeManifest.path,
680
+ routeManifestCreated: routeManifest.created,
681
+ routes,
682
+ routeCount: routes.length,
683
+ generatedAt,
684
+ manifestCapabilities: MANIFEST_CAPABILITIES,
685
+ capabilities: BRIDGE_OPERATIONS.map((operation) => ({
686
+ operation,
687
+ status: "available" as const,
688
+ reason:
689
+ operation === "resolveNodeToFile"
690
+ ? // resolveNodeToFile maps a runtime DOM node id (from the editor's
691
+ // 'select' payload) to { file, line, component } provenance.
692
+ // React development builds expose jsxDEV call sites through the
693
+ // Fiber debug stack; other runtimes/builds can emit explicit DOM
694
+ // provenance attributes — see the help text below.
695
+ "React development builds resolve jsxDEV call sites automatically; other runtimes can emit data-source-file / data-source-line / data-component-name attributes."
696
+ : undefined,
697
+ })),
698
+ };
699
+ }
700
+
701
+ const BRIDGE_CORS_HEADERS = Symbol("agent-native-design-bridge-cors");
702
+
703
+ type CorsAwareResponse = ServerResponse & {
704
+ [BRIDGE_CORS_HEADERS]?: Record<string, string>;
705
+ };
706
+
707
+ function isLoopbackOrigin(parsed: URL): boolean {
708
+ const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
709
+ return (
710
+ hostname === "localhost" ||
711
+ hostname === "::1" ||
712
+ /^127(?:\.\d{1,3}){3}$/.test(hostname)
713
+ );
714
+ }
715
+
716
+ function isApprovedDesignOrigin(
717
+ rawOrigin: string,
718
+ configuredOrigins: ReadonlySet<string>,
719
+ ): boolean {
720
+ let parsed: URL;
721
+ try {
722
+ parsed = new URL(rawOrigin);
723
+ } catch {
724
+ return false;
725
+ }
726
+ if (parsed.origin !== rawOrigin) return false;
727
+ if (configuredOrigins.has(parsed.origin)) return true;
728
+ if (
729
+ (parsed.protocol === "http:" || parsed.protocol === "https:") &&
730
+ isLoopbackOrigin(parsed)
731
+ ) {
732
+ return true;
733
+ }
734
+ return (
735
+ parsed.protocol === "https:" &&
736
+ (parsed.hostname === "design.agent-native.com" ||
737
+ parsed.hostname.endsWith(".design.agent-native.com"))
738
+ );
739
+ }
740
+
741
+ function configureBridgeCors(
742
+ req: IncomingMessage,
743
+ res: ServerResponse,
744
+ configuredOrigins: ReadonlySet<string>,
745
+ ): boolean {
746
+ const origin =
747
+ typeof req.headers.origin === "string" ? req.headers.origin : "";
748
+ const approved = origin
749
+ ? isApprovedDesignOrigin(origin, configuredOrigins)
750
+ : false;
751
+ (res as CorsAwareResponse)[BRIDGE_CORS_HEADERS] = approved
752
+ ? {
753
+ "access-control-allow-origin": origin,
754
+ "access-control-allow-methods":
755
+ "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS",
756
+ "access-control-allow-headers":
757
+ "authorization, content-type, x-bridge-token, x-design-preview-token, x-csrf-token, x-xsrf-token, x-requested-with",
758
+ "access-control-allow-private-network": "true",
759
+ vary: "Origin",
760
+ }
761
+ : {};
762
+ return approved;
763
+ }
764
+
765
+ function bridgeCorsHeaders(res: ServerResponse): Record<string, string> {
766
+ return (res as CorsAwareResponse)[BRIDGE_CORS_HEADERS] ?? {};
767
+ }
768
+
769
+ function sendJson(
770
+ res: ServerResponse,
771
+ statusCode: number,
772
+ body: Record<string, unknown>,
773
+ ) {
774
+ res.writeHead(statusCode, {
775
+ "content-type": "application/json; charset=utf-8",
776
+ ...bridgeCorsHeaders(res),
777
+ });
778
+ res.end(`${JSON.stringify(body, null, 2)}\n`);
779
+ }
780
+
781
+ function sendText(
782
+ res: ServerResponse,
783
+ statusCode: number,
784
+ body: string,
785
+ contentType: string,
786
+ setCookieHeaders: string[] = [],
787
+ ) {
788
+ res.writeHead(statusCode, {
789
+ "content-type": contentType,
790
+ ...(setCookieHeaders.length > 0 ? { "set-cookie": setCookieHeaders } : {}),
791
+ ...bridgeCorsHeaders(res),
792
+ });
793
+ res.end(body);
794
+ }
795
+
796
+ function sendBytes(
797
+ res: ServerResponse,
798
+ statusCode: number,
799
+ body: Buffer,
800
+ headers: Headers,
801
+ contentLength = body.length,
802
+ setCookieHeaders: string[] = [],
803
+ ) {
804
+ const responseHeaders: Record<string, string | string[]> = {
805
+ ...bridgeCorsHeaders(res),
806
+ "content-length": String(contentLength),
807
+ ...(setCookieHeaders.length > 0 ? { "set-cookie": setCookieHeaders } : {}),
808
+ };
809
+ for (const name of [
810
+ "content-type",
811
+ "cache-control",
812
+ "etag",
813
+ "last-modified",
814
+ ]) {
815
+ const value = headers.get(name);
816
+ if (value) responseHeaders[name] = value;
817
+ }
818
+ res.writeHead(statusCode, responseHeaders);
819
+ res.end(body);
820
+ }
821
+
822
+ function sameOrigin(a: string, b: string): boolean {
823
+ try {
824
+ return new URL(a).origin === new URL(b).origin;
825
+ } catch {
826
+ return false;
827
+ }
828
+ }
829
+
830
+ function constantTimeTokenMatches(
831
+ providedToken: string,
832
+ expectedToken: string,
833
+ ): boolean {
834
+ try {
835
+ return (
836
+ providedToken.length === expectedToken.length &&
837
+ crypto.timingSafeEqual(
838
+ Buffer.from(providedToken, "utf8"),
839
+ Buffer.from(expectedToken, "utf8"),
840
+ )
841
+ );
842
+ } catch {
843
+ return false;
844
+ }
845
+ }
846
+
847
+ function readHeader(req: IncomingMessage, name: string): string {
848
+ const value = req.headers[name];
849
+ return typeof value === "string" ? value : "";
850
+ }
851
+
852
+ function readRequestCookie(req: IncomingMessage, name: string): string {
853
+ for (const rawPair of readHeader(req, "cookie").split(";")) {
854
+ const pair = rawPair.trim();
855
+ const separator = pair.indexOf("=");
856
+ if (separator <= 0 || pair.slice(0, separator).trim() !== name) continue;
857
+ return pair.slice(separator + 1).trim();
858
+ }
859
+ return "";
860
+ }
861
+
862
+ function previewSessionSetCookie(previewToken: string): string {
863
+ // Partitioned SameSite=None keeps the read-only bridge credential available
864
+ // to nested Design frames without granting an unpartitioned third-party
865
+ // cookie. Loopback origins are potentially trustworthy in Chromium, which
866
+ // permits Secure cookies for local development.
867
+ return `${PREVIEW_SESSION_COOKIE_NAME}=${previewToken}; HttpOnly; Path=/; SameSite=None; Secure; Partitioned`;
868
+ }
869
+
870
+ /**
871
+ * Preserve only browser metadata a local dev server needs to classify module
872
+ * requests and parse ordinary same-origin app mutations. Browser cookies and
873
+ * Authorization are forwarded only when Sec-Fetch-Site says the request came
874
+ * from the proxied app itself; cross-site callers cannot relay them. Bridge
875
+ * control tokens and referrers are never forwarded, and mutation origins are
876
+ * replaced with the connected dev-server origin. Cookie-backed login also
877
+ * uses the isolated in-memory upstream jar below so iframe cookie restrictions
878
+ * cannot split the session across URL-backed screens.
879
+ */
880
+ function previewProxyRequestHeaders(
881
+ req: IncomingMessage,
882
+ devServerUrl: string,
883
+ cookieHeader = "",
884
+ ): Record<string, string> {
885
+ const headers: Record<string, string> = {};
886
+ for (const name of [
887
+ "accept",
888
+ "accept-language",
889
+ "content-type",
890
+ "sec-fetch-dest",
891
+ "sec-fetch-mode",
892
+ "sec-fetch-site",
893
+ "x-csrf-token",
894
+ "x-xsrf-token",
895
+ "x-requested-with",
896
+ ] as const) {
897
+ const value = readHeader(req, name);
898
+ if (value) headers[name] = value;
899
+ }
900
+ const browserSameOrigin = readHeader(req, "sec-fetch-site") === "same-origin";
901
+ const browserCookie = browserSameOrigin ? readHeader(req, "cookie") : "";
902
+ const mergedCookie = mergePreviewCookieHeaders(cookieHeader, browserCookie);
903
+ if (mergedCookie) headers["cookie"] = mergedCookie;
904
+ // Same-origin app code may use localStorage-backed bearer auth. Forward it
905
+ // only from a browser request already classified as same-origin; cross-site
906
+ // callers still authenticate to the bridge with the separate preview token,
907
+ // which is never copied upstream.
908
+ if (browserSameOrigin) {
909
+ const authorization = readHeader(req, "authorization");
910
+ if (authorization && authorization.length <= 16 * 1024) {
911
+ headers["authorization"] = authorization;
912
+ }
913
+ }
914
+ if (req.method && !["GET", "HEAD"].includes(req.method)) {
915
+ headers["origin"] = new URL(devServerUrl).origin;
916
+ }
917
+ return headers;
918
+ }
919
+
920
+ function mergePreviewCookieHeaders(
921
+ jarCookieHeader: string,
922
+ browserCookieHeader: string,
923
+ ): string {
924
+ const values = new Map<string, string>();
925
+ const absorb = (header: string) => {
926
+ if (!header || Buffer.byteLength(header) > MAX_PREVIEW_COOKIE_BYTES) return;
927
+ for (const rawPair of header.split(";")) {
928
+ const pair = rawPair.trim();
929
+ const separator = pair.indexOf("=");
930
+ if (separator <= 0) continue;
931
+ const name = pair.slice(0, separator).trim();
932
+ const value = pair.slice(separator + 1).trim();
933
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) continue;
934
+ if (/^(?:x-)?agent-native-(?:bridge|preview)(?:-|$)/i.test(name)) {
935
+ continue;
936
+ }
937
+ values.set(name, value);
938
+ }
939
+ };
940
+ absorb(jarCookieHeader);
941
+ // A client-readable same-origin cookie is allowed to update the jar's stale
942
+ // value for this request. HttpOnly cookies arrive with the same value and
943
+ // remain effectively jar-owned.
944
+ absorb(browserCookieHeader);
945
+ return [...values].map(([name, value]) => `${name}=${value}`).join("; ");
946
+ }
947
+
948
+ const MAX_PREVIEW_SESSION_COOKIES = 128;
949
+ const MAX_PREVIEW_COOKIE_BYTES = 8 * 1024;
950
+
951
+ type PreviewSessionCookie = {
952
+ name: string;
953
+ value: string;
954
+ path: string;
955
+ secure: boolean;
956
+ expiresAt?: number;
957
+ };
958
+
959
+ function responseSetCookieHeaders(headers: Headers): string[] {
960
+ const withGetSetCookie = headers as Headers & {
961
+ getSetCookie?: () => string[];
962
+ };
963
+ if (typeof withGetSetCookie.getSetCookie === "function") {
964
+ return withGetSetCookie.getSetCookie();
965
+ }
966
+ const single = headers.get("set-cookie");
967
+ return single ? [single] : [];
968
+ }
969
+
970
+ function defaultCookiePath(url: URL): string {
971
+ const pathname = url.pathname || "/";
972
+ const lastSlash = pathname.lastIndexOf("/");
973
+ return lastSlash <= 0 ? "/" : pathname.slice(0, lastSlash);
974
+ }
975
+
976
+ function sanitizedBridgeSetCookie(raw: string): string {
977
+ return raw
978
+ .split(";")
979
+ .filter((part) => !/^\s*domain\s*=/i.test(part))
980
+ .join(";");
981
+ }
982
+
983
+ /**
984
+ * Ephemeral cookie session for one connected dev-server origin. Cookies never
985
+ * enter SQL, disk, logs, or another target origin, and disappear when the
986
+ * local bridge exits. This keeps authenticated URL screens in one bridge
987
+ * process on the same upstream session even when browser third-party-cookie
988
+ * rules would otherwise isolate or reject the nested iframe cookies.
989
+ */
990
+ class PreviewSessionCookieJar {
991
+ private readonly origin: string;
992
+ private readonly cookies = new Map<string, PreviewSessionCookie>();
993
+
994
+ constructor(devServerUrl: string) {
995
+ this.origin = new URL(devServerUrl).origin;
996
+ }
997
+
998
+ store(headers: Headers, responseUrl: string): string[] {
999
+ const parsedUrl = new URL(responseUrl);
1000
+ if (parsedUrl.origin !== this.origin) return [];
1001
+ const browserCookies: string[] = [];
1002
+ for (const raw of responseSetCookieHeaders(headers)) {
1003
+ if (!raw || Buffer.byteLength(raw) > MAX_PREVIEW_COOKIE_BYTES) continue;
1004
+ const parts = raw.split(";");
1005
+ const first = parts.shift()?.trim() ?? "";
1006
+ const separator = first.indexOf("=");
1007
+ if (separator <= 0) continue;
1008
+ const name = first.slice(0, separator).trim();
1009
+ const value = first.slice(separator + 1).trim();
1010
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) continue;
1011
+ let cookiePath = defaultCookiePath(parsedUrl);
1012
+ let secure = false;
1013
+ let expiresAt: number | undefined;
1014
+ let deleteCookie = false;
1015
+ for (const attribute of parts) {
1016
+ const trimmed = attribute.trim();
1017
+ const equals = trimmed.indexOf("=");
1018
+ const key = (equals < 0 ? trimmed : trimmed.slice(0, equals))
1019
+ .trim()
1020
+ .toLowerCase();
1021
+ const attributeValue =
1022
+ equals < 0 ? "" : trimmed.slice(equals + 1).trim();
1023
+ if (key === "path" && attributeValue.startsWith("/")) {
1024
+ cookiePath = attributeValue;
1025
+ } else if (key === "secure") {
1026
+ secure = true;
1027
+ } else if (key === "max-age") {
1028
+ const seconds = Number(attributeValue);
1029
+ if (Number.isFinite(seconds)) {
1030
+ deleteCookie = seconds <= 0;
1031
+ expiresAt = Date.now() + seconds * 1000;
1032
+ }
1033
+ } else if (key === "expires" && expiresAt === undefined) {
1034
+ const parsed = Date.parse(attributeValue);
1035
+ if (Number.isFinite(parsed)) expiresAt = parsed;
1036
+ }
1037
+ }
1038
+ const mapKey = `${cookiePath}\u0000${name}`;
1039
+ if (
1040
+ deleteCookie ||
1041
+ (expiresAt !== undefined && expiresAt <= Date.now())
1042
+ ) {
1043
+ this.cookies.delete(mapKey);
1044
+ } else {
1045
+ this.cookies.delete(mapKey);
1046
+ this.cookies.set(mapKey, {
1047
+ name,
1048
+ value,
1049
+ path: cookiePath,
1050
+ secure,
1051
+ ...(expiresAt !== undefined ? { expiresAt } : {}),
1052
+ });
1053
+ while (this.cookies.size > MAX_PREVIEW_SESSION_COOKIES) {
1054
+ const oldest = this.cookies.keys().next().value;
1055
+ if (typeof oldest !== "string") break;
1056
+ this.cookies.delete(oldest);
1057
+ }
1058
+ }
1059
+ browserCookies.push(sanitizedBridgeSetCookie(raw));
1060
+ }
1061
+ return browserCookies;
1062
+ }
1063
+
1064
+ headerFor(targetUrl: string): string {
1065
+ const parsed = new URL(targetUrl);
1066
+ if (parsed.origin !== this.origin) return "";
1067
+ const now = Date.now();
1068
+ const matches: PreviewSessionCookie[] = [];
1069
+ for (const [key, cookie] of this.cookies) {
1070
+ if (cookie.expiresAt !== undefined && cookie.expiresAt <= now) {
1071
+ this.cookies.delete(key);
1072
+ continue;
1073
+ }
1074
+ if (
1075
+ cookie.secure &&
1076
+ parsed.protocol !== "https:" &&
1077
+ !isLoopbackOrigin(parsed)
1078
+ ) {
1079
+ continue;
1080
+ }
1081
+ const pathMatches =
1082
+ cookie.path === "/" ||
1083
+ parsed.pathname === cookie.path ||
1084
+ (cookie.path.endsWith("/")
1085
+ ? parsed.pathname.startsWith(cookie.path)
1086
+ : parsed.pathname.startsWith(`${cookie.path}/`));
1087
+ if (!pathMatches) continue;
1088
+ matches.push(cookie);
1089
+ }
1090
+ matches.sort((a, b) => b.path.length - a.path.length);
1091
+ return matches.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
1092
+ }
1093
+ }
1094
+
1095
+ function resolvePreviewSnapshotUrl(
1096
+ devServerUrl: string,
1097
+ rawUrl: string | null,
1098
+ ): string {
1099
+ const base = normalizeHttpUrl(devServerUrl);
1100
+ const parsed = new URL(rawUrl?.trim() || "/", `${base}/`);
1101
+ parsed.hash = "";
1102
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1103
+ throw new Error("Snapshot URL must use http(s).");
1104
+ }
1105
+ if (!sameOrigin(parsed.toString(), base)) {
1106
+ throw new Error("Snapshot URL must stay on the connected dev server.");
1107
+ }
1108
+ return parsed.toString();
1109
+ }
1110
+
1111
+ function resolvePreviewProxyUrl(
1112
+ devServerUrl: string,
1113
+ requestUrl: string | undefined,
1114
+ ): string {
1115
+ const base = normalizeHttpUrl(devServerUrl);
1116
+ const parsedRequest = new URL(requestUrl ?? "/", base);
1117
+ const parsed = new URL(
1118
+ `${parsedRequest.pathname}${parsedRequest.search}`,
1119
+ `${base}/`,
1120
+ );
1121
+ parsed.hash = "";
1122
+ if (!sameOrigin(parsed.toString(), base)) {
1123
+ throw new Error("Proxy URL must stay on the connected dev server.");
1124
+ }
1125
+ return parsed.toString();
1126
+ }
1127
+
1128
+ async function fetchPreviewSnapshot(
1129
+ devServerUrl: string,
1130
+ targetUrl: string,
1131
+ cookieJar?: PreviewSessionCookieJar,
1132
+ redirects = 0,
1133
+ ): Promise<{
1134
+ url: string;
1135
+ status: number;
1136
+ contentType: string;
1137
+ html: string;
1138
+ setCookieHeaders: string[];
1139
+ }> {
1140
+ if (redirects > 5) {
1141
+ throw new Error("Too many redirects while fetching preview snapshot.");
1142
+ }
1143
+ const controller = new AbortController();
1144
+ const timeout = setTimeout(() => controller.abort(), 10_000);
1145
+ try {
1146
+ const response = await fetch(targetUrl, {
1147
+ method: "GET",
1148
+ redirect: "manual",
1149
+ signal: controller.signal,
1150
+ headers: {
1151
+ accept: "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8",
1152
+ ...(cookieJar?.headerFor(targetUrl)
1153
+ ? { cookie: cookieJar.headerFor(targetUrl) }
1154
+ : {}),
1155
+ },
1156
+ });
1157
+ const setCookieHeaders =
1158
+ cookieJar?.store(response.headers, targetUrl) ?? [];
1159
+ const location = response.headers.get("location");
1160
+ if (location && response.status >= 300 && response.status < 400) {
1161
+ const redirected = new URL(location, targetUrl);
1162
+ redirected.hash = "";
1163
+ if (!sameOrigin(redirected.toString(), devServerUrl)) {
1164
+ throw new Error("Snapshot redirect left the connected dev server.");
1165
+ }
1166
+ return fetchPreviewSnapshot(
1167
+ devServerUrl,
1168
+ redirected.toString(),
1169
+ cookieJar,
1170
+ redirects + 1,
1171
+ );
1172
+ }
1173
+ return {
1174
+ url: response.url || targetUrl,
1175
+ status: response.status,
1176
+ contentType: response.headers.get("content-type") ?? "",
1177
+ html: await response.text(),
1178
+ setCookieHeaders,
1179
+ };
1180
+ } finally {
1181
+ clearTimeout(timeout);
1182
+ }
1183
+ }
1184
+
1185
+ async function fetchPreviewProxyResource(
1186
+ devServerUrl: string,
1187
+ targetUrl: string,
1188
+ request: {
1189
+ method: string;
1190
+ headers?: Record<string, string>;
1191
+ body?: Buffer;
1192
+ },
1193
+ cookieJar?: PreviewSessionCookieJar,
1194
+ redirects = 0,
1195
+ ): Promise<{
1196
+ url: string;
1197
+ status: number;
1198
+ headers: Headers;
1199
+ body: Buffer;
1200
+ setCookieHeaders: string[];
1201
+ }> {
1202
+ if (redirects > 5) {
1203
+ throw new Error("Too many redirects while proxying preview resource.");
1204
+ }
1205
+ const controller = new AbortController();
1206
+ const timeout = setTimeout(() => controller.abort(), 10_000);
1207
+ try {
1208
+ const response = await fetch(targetUrl, {
1209
+ method: request.method,
1210
+ redirect: "manual",
1211
+ signal: controller.signal,
1212
+ headers: request.headers,
1213
+ body:
1214
+ request.method === "GET" || request.method === "HEAD"
1215
+ ? undefined
1216
+ : request.body,
1217
+ });
1218
+ const setCookieHeaders =
1219
+ cookieJar?.store(response.headers, targetUrl) ?? [];
1220
+ const location = response.headers.get("location");
1221
+ if (location && response.status >= 300 && response.status < 400) {
1222
+ const redirected = new URL(location, targetUrl);
1223
+ redirected.hash = "";
1224
+ if (!sameOrigin(redirected.toString(), devServerUrl)) {
1225
+ throw new Error("Proxy redirect left the connected dev server.");
1226
+ }
1227
+ const switchToGet =
1228
+ response.status === 303 ||
1229
+ ((response.status === 301 || response.status === 302) &&
1230
+ request.method === "POST");
1231
+ const redirectedResponse = await fetchPreviewProxyResource(
1232
+ devServerUrl,
1233
+ redirected.toString(),
1234
+ {
1235
+ method: switchToGet ? "GET" : request.method,
1236
+ headers: {
1237
+ ...(request.headers ?? {}),
1238
+ ...(cookieJar?.headerFor(redirected.toString())
1239
+ ? { cookie: cookieJar.headerFor(redirected.toString()) }
1240
+ : {}),
1241
+ },
1242
+ body: switchToGet ? undefined : request.body,
1243
+ },
1244
+ cookieJar,
1245
+ redirects + 1,
1246
+ );
1247
+ return {
1248
+ ...redirectedResponse,
1249
+ setCookieHeaders: [
1250
+ ...setCookieHeaders,
1251
+ ...redirectedResponse.setCookieHeaders,
1252
+ ],
1253
+ };
1254
+ }
1255
+ return {
1256
+ url: response.url || targetUrl,
1257
+ status: response.status,
1258
+ headers: response.headers,
1259
+ body: Buffer.from(await response.arrayBuffer()),
1260
+ setCookieHeaders,
1261
+ };
1262
+ } finally {
1263
+ clearTimeout(timeout);
1264
+ }
1265
+ }
1266
+
1267
+ function addLiveEditBaseHref(html: string, href: string): string {
1268
+ const escapedHref = href.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
1269
+ const baseTag = `<base href="${escapedHref}">`;
1270
+ if (/<base\b/i.test(html)) return html;
1271
+ if (/<head\b[^>]*>/i.test(html)) {
1272
+ return html.replace(/<head\b[^>]*>/i, (match) => `${match}${baseTag}`);
1273
+ }
1274
+ if (/<html\b[^>]*>/i.test(html)) {
1275
+ return html.replace(
1276
+ /<html\b[^>]*>/i,
1277
+ (match) => `${match}<head>${baseTag}</head>`,
1278
+ );
1279
+ }
1280
+ return `<!DOCTYPE html><html><head>${baseTag}<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body>${html}</body></html>`;
1281
+ }
1282
+
1283
+ /**
1284
+ * Rewrite the iframe path to the real target route before the proxied app's
1285
+ * bundle runs. The bridge serves snapshots from its own `/live-edit` path, so a
1286
+ * client-side-routed SPA would otherwise boot at "/live-edit", match no route,
1287
+ * and render its 404. A synchronous inline script in `<head>` runs during parse
1288
+ * (before deferred module bundles), so `history.replaceState` lands the SPA on
1289
+ * the intended route. Assets still resolve via the injected `<base href>`.
1290
+ */
1291
+ function injectPreBootLocationShim(html: string, targetPath: string): string {
1292
+ const path = targetPath.trim();
1293
+ if (!path || path === "/live-edit") return html;
1294
+ const shim = `<script data-agent-native-live-edit-location>
1295
+ (function(){try{var p=${JSON.stringify(path)};if(p&&(location.pathname+location.search)!==p){history.replaceState(null,"",p);}}catch(e){}})();
1296
+ </script>`;
1297
+ if (/<head\b[^>]*>/i.test(html)) {
1298
+ return html.replace(/<head\b[^>]*>/i, (match) => `${match}${shim}`);
1299
+ }
1300
+ if (/<html\b[^>]*>/i.test(html)) {
1301
+ return html.replace(
1302
+ /<html\b[^>]*>/i,
1303
+ (match) => `${match}<head>${shim}</head>`,
1304
+ );
1305
+ }
1306
+ return `${shim}${html}`;
1307
+ }
1308
+
1309
+ function injectLiveEditBridge(
1310
+ html: string,
1311
+ baseHref: string,
1312
+ script: string,
1313
+ targetPath: string,
1314
+ ) {
1315
+ const withBase = injectPreBootLocationShim(
1316
+ addLiveEditBaseHref(html, baseHref),
1317
+ targetPath,
1318
+ );
1319
+ if (!script) return withBase;
1320
+ if (withBase.includes("</body>")) {
1321
+ return withBase.replace("</body>", `${script}</body>`);
1322
+ }
1323
+ if (withBase.includes("</html>")) {
1324
+ return withBase.replace("</html>", `${script}</html>`);
1325
+ }
1326
+ return `${withBase}${script}`;
1327
+ }
1328
+
1329
+ /** Read the full request body as a UTF-8 string. */
1330
+ async function readRequestBody(req: IncomingMessage): Promise<string> {
1331
+ return new Promise<string>((resolve, reject) => {
1332
+ const chunks: Buffer[] = [];
1333
+ req.on("data", (chunk: Buffer) => chunks.push(chunk));
1334
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
1335
+ req.on("error", reject);
1336
+ });
1337
+ }
1338
+
1339
+ const MAX_PREVIEW_PROXY_REQUEST_BYTES = 8 * 1024 * 1024;
1340
+
1341
+ class PreviewProxyRequestTooLargeError extends Error {}
1342
+
1343
+ async function readPreviewProxyRequestBody(
1344
+ req: IncomingMessage,
1345
+ ): Promise<Buffer> {
1346
+ const declaredLength = Number(readHeader(req, "content-length"));
1347
+ if (
1348
+ Number.isFinite(declaredLength) &&
1349
+ declaredLength > MAX_PREVIEW_PROXY_REQUEST_BYTES
1350
+ ) {
1351
+ throw new PreviewProxyRequestTooLargeError(
1352
+ "Preview request body exceeds the 8 MB limit.",
1353
+ );
1354
+ }
1355
+ return new Promise<Buffer>((resolve, reject) => {
1356
+ const chunks: Buffer[] = [];
1357
+ let size = 0;
1358
+ let tooLarge = false;
1359
+ req.on("data", (chunk: Buffer) => {
1360
+ size += chunk.length;
1361
+ if (size > MAX_PREVIEW_PROXY_REQUEST_BYTES) {
1362
+ tooLarge = true;
1363
+ chunks.length = 0;
1364
+ return;
1365
+ }
1366
+ if (!tooLarge) chunks.push(chunk);
1367
+ });
1368
+ req.on("end", () => {
1369
+ if (tooLarge) {
1370
+ reject(
1371
+ new PreviewProxyRequestTooLargeError(
1372
+ "Preview request body exceeds the 8 MB limit.",
1373
+ ),
1374
+ );
1375
+ return;
1376
+ }
1377
+ resolve(Buffer.concat(chunks));
1378
+ });
1379
+ req.on("error", reject);
1380
+ });
1381
+ }
1382
+
1383
+ /**
1384
+ * Resolve `targetDir` under `rootPath` with realpath so that symlinks and
1385
+ * traversal sequences (../../etc) cannot escape the root. Throws if the
1386
+ * resolved path does not start with the resolved root.
1387
+ *
1388
+ * This also guards against a symlink LEAF inside root (e.g.
1389
+ * `rootPath/link.css` -> `/Users/me/.ssh/id_rsa`): the parent-directory
1390
+ * realpath check alone would pass confinement because the parent is inside
1391
+ * root, letting reads/writes silently follow the symlink outside root. After
1392
+ * the parent check we additionally lstat the target itself — if it exists and
1393
+ * is a symlink, or if its realpath resolves outside `resolvedRoot`, we reject.
1394
+ */
1395
+ async function assertPathInside(
1396
+ rootPath: string,
1397
+ targetPath: string,
1398
+ ): Promise<void> {
1399
+ const resolvedRoot = await fs.realpath(rootPath).catch(() => {
1400
+ throw new Error(`Bridge root path does not exist: ${rootPath}`);
1401
+ });
1402
+
1403
+ // Resolve the parent directory (the file itself may not exist yet for writes).
1404
+ const targetParent = path.dirname(path.resolve(rootPath, targetPath));
1405
+ const resolvedParent = await fs.realpath(targetParent).catch(async () => {
1406
+ // Parent may not exist yet; walk up until we find a real ancestor.
1407
+ let candidate = targetParent;
1408
+ for (let i = 0; i < 32; i++) {
1409
+ const up = path.dirname(candidate);
1410
+ if (up === candidate) break;
1411
+ candidate = up;
1412
+ try {
1413
+ return await fs.realpath(candidate);
1414
+ } catch {
1415
+ // keep walking
1416
+ }
1417
+ }
1418
+ throw new Error(`Cannot resolve parent directory: ${targetParent}`);
1419
+ });
1420
+
1421
+ if (
1422
+ !resolvedParent.startsWith(resolvedRoot + path.sep) &&
1423
+ resolvedParent !== resolvedRoot
1424
+ ) {
1425
+ throw new Error(`Path traversal detected: resolved target is outside root`);
1426
+ }
1427
+
1428
+ // Reject a symlink LEAF, even though its parent directory is confined to
1429
+ // root. A pre-existing symlink at the target path (file or directory) could
1430
+ // otherwise be followed straight out of root by the caller's subsequent
1431
+ // fs.readFile/fs.writeFile call.
1432
+ const targetAbsolute = path.resolve(rootPath, targetPath);
1433
+ const lstat = await fs.lstat(targetAbsolute).catch(() => null);
1434
+ if (lstat?.isSymbolicLink()) {
1435
+ throw new Error(
1436
+ `Path traversal detected: "${targetPath}" is a symlink, which is not allowed inside the connected root`,
1437
+ );
1438
+ }
1439
+ if (lstat) {
1440
+ const resolvedTarget = await fs.realpath(targetAbsolute).catch(() => null);
1441
+ if (
1442
+ resolvedTarget &&
1443
+ resolvedTarget !== resolvedRoot &&
1444
+ !resolvedTarget.startsWith(resolvedRoot + path.sep)
1445
+ ) {
1446
+ throw new Error(
1447
+ `Path traversal detected: "${targetPath}" resolves outside the connected root`,
1448
+ );
1449
+ }
1450
+ }
1451
+ }
1452
+
1453
+ interface SafeBridgeFileTarget {
1454
+ absolutePath: string;
1455
+ canonicalPath: string;
1456
+ }
1457
+
1458
+ /**
1459
+ * Resolve a bridge file to a stable lock key without following a leaf
1460
+ * symlink. Missing parent directories are represented beneath their nearest
1461
+ * existing real ancestor, so aliases through in-root directory symlinks share
1462
+ * one mutex while first-time file creation remains supported.
1463
+ */
1464
+ async function resolveSafeBridgeFileTarget(
1465
+ rootPath: string,
1466
+ targetPath: string,
1467
+ ): Promise<SafeBridgeFileTarget> {
1468
+ await assertPathInside(rootPath, targetPath);
1469
+ const resolvedRoot = await fs.realpath(rootPath);
1470
+ const absolutePath = path.resolve(rootPath, targetPath);
1471
+ let existingAncestor = path.dirname(absolutePath);
1472
+ let resolvedAncestor: string | null = null;
1473
+ for (let depth = 0; depth < 32; depth += 1) {
1474
+ try {
1475
+ resolvedAncestor = await fs.realpath(existingAncestor);
1476
+ break;
1477
+ } catch {
1478
+ const parent = path.dirname(existingAncestor);
1479
+ if (parent === existingAncestor) break;
1480
+ existingAncestor = parent;
1481
+ }
1482
+ }
1483
+ if (!resolvedAncestor) {
1484
+ throw new Error(`Cannot resolve parent directory: ${absolutePath}`);
1485
+ }
1486
+ // Existing regular files get their own realpath as the lock key. Besides
1487
+ // resolving in-root directory aliases, this canonicalizes case on the
1488
+ // default macOS filesystem so `Button.tsx` and `button.tsx` cannot acquire
1489
+ // separate mutexes for the same inode. assertPathInside already rejected a
1490
+ // symlink leaf before this point.
1491
+ const canonicalPath =
1492
+ (await fs.realpath(absolutePath).catch(() => null)) ??
1493
+ path.resolve(
1494
+ resolvedAncestor,
1495
+ path.relative(existingAncestor, absolutePath),
1496
+ );
1497
+ if (
1498
+ canonicalPath !== resolvedRoot &&
1499
+ !canonicalPath.startsWith(resolvedRoot + path.sep)
1500
+ ) {
1501
+ throw new Error("Path traversal detected: resolved target is outside root");
1502
+ }
1503
+ return { absolutePath, canonicalPath };
1504
+ }
1505
+
1506
+ const bridgeWriteLocks = new Map<string, Promise<void>>();
1507
+
1508
+ /** Serialize read-check-write sequences for one canonical local file. */
1509
+ async function withBridgeWriteLock<T>(
1510
+ canonicalPath: string,
1511
+ work: () => Promise<T>,
1512
+ ): Promise<T> {
1513
+ const previous = bridgeWriteLocks.get(canonicalPath) ?? Promise.resolve();
1514
+ let release!: () => void;
1515
+ const gate = new Promise<void>((resolve) => {
1516
+ release = resolve;
1517
+ });
1518
+ const queued = previous.catch(() => undefined).then(() => gate);
1519
+ bridgeWriteLocks.set(canonicalPath, queued);
1520
+ await previous.catch(() => undefined);
1521
+ try {
1522
+ return await work();
1523
+ } finally {
1524
+ release();
1525
+ if (bridgeWriteLocks.get(canonicalPath) === queued) {
1526
+ bridgeWriteLocks.delete(canonicalPath);
1527
+ }
1528
+ }
1529
+ }
1530
+
1531
+ interface BridgeFileSnapshot {
1532
+ content: string;
1533
+ versionHash: string;
1534
+ mode: number;
1535
+ }
1536
+
1537
+ function contentVersionHash(content: string | Buffer): string {
1538
+ return crypto.createHash("sha256").update(content).digest("hex");
1539
+ }
1540
+
1541
+ /** Read an existing regular file without ever following a leaf symlink. */
1542
+ async function readBridgeFileSnapshot(
1543
+ absolutePath: string,
1544
+ ): Promise<BridgeFileSnapshot | null> {
1545
+ const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
1546
+ let handle: FileHandle | null = null;
1547
+ try {
1548
+ handle = await fs.open(absolutePath, fsSync.constants.O_RDONLY | noFollow);
1549
+ const stat = await handle.stat();
1550
+ if (!stat.isFile()) {
1551
+ throw new Error(`Bridge target is not a regular file: ${absolutePath}`);
1552
+ }
1553
+ const bytes = await handle.readFile();
1554
+ return {
1555
+ content: bytes.toString("utf8"),
1556
+ versionHash: contentVersionHash(bytes),
1557
+ mode: stat.mode & 0o777,
1558
+ };
1559
+ } catch (error: unknown) {
1560
+ const code =
1561
+ error instanceof Error && "code" in error
1562
+ ? (error as NodeJS.ErrnoException).code
1563
+ : undefined;
1564
+ if (code === "ENOENT") return null;
1565
+ throw error;
1566
+ } finally {
1567
+ await handle?.close().catch(() => undefined);
1568
+ }
1569
+ }
1570
+
1571
+ class BridgeVersionConflictError extends Error {
1572
+ constructor(readonly currentVersionHash?: string) {
1573
+ super("version conflict");
1574
+ }
1575
+ }
1576
+
1577
+ class BridgePreconditionRequiredError extends Error {
1578
+ constructor() {
1579
+ super("expectedVersionHash is required");
1580
+ }
1581
+ }
1582
+
1583
+ function assertExpectedBridgeVersion(
1584
+ expectedVersionHash: string | undefined,
1585
+ currentVersionHash: string | undefined,
1586
+ requireExpectedVersionHash = false,
1587
+ ): void {
1588
+ // Preserve compatibility: callers that omit a hash retain the existing
1589
+ // last-write-wins behavior, including creation. Compiled-source callers opt
1590
+ // into exact compare-and-swap by sending the hash returned by read-file.
1591
+ if (requireExpectedVersionHash && expectedVersionHash === undefined) {
1592
+ throw new BridgePreconditionRequiredError();
1593
+ }
1594
+ if (
1595
+ expectedVersionHash !== undefined &&
1596
+ (currentVersionHash !== undefined
1597
+ ? currentVersionHash !== expectedVersionHash
1598
+ : requireExpectedVersionHash)
1599
+ ) {
1600
+ throw new BridgeVersionConflictError(currentVersionHash);
1601
+ }
1602
+ }
1603
+
1604
+ /**
1605
+ * Replace a file durably without exposing a partial write: create an
1606
+ * O_EXCL/O_NOFOLLOW temp sibling, fsync it, revalidate confinement and the
1607
+ * expected content hash, rename atomically, then fsync the parent directory.
1608
+ */
1609
+ async function atomicWriteBridgeFile(args: {
1610
+ rootPath: string;
1611
+ relPath: string;
1612
+ lockedCanonicalPath: string;
1613
+ content: string;
1614
+ expectedVersionHash?: string;
1615
+ requireExpectedVersionHash?: boolean;
1616
+ originalMode?: number;
1617
+ }): Promise<string> {
1618
+ const parent = path.dirname(path.resolve(args.rootPath, args.relPath));
1619
+ await fs.mkdir(parent, { recursive: true });
1620
+ const revalidated = await resolveSafeBridgeFileTarget(
1621
+ args.rootPath,
1622
+ args.relPath,
1623
+ );
1624
+ if (revalidated.canonicalPath !== args.lockedCanonicalPath) {
1625
+ throw new Error("Bridge target changed while waiting for the write lock");
1626
+ }
1627
+
1628
+ const basename = path.basename(revalidated.absolutePath);
1629
+ const tempPath = path.join(
1630
+ parent,
1631
+ `.${basename}.agent-native-${process.pid}-${crypto.randomBytes(8).toString("hex")}.tmp`,
1632
+ );
1633
+ const noFollow = fsSync.constants.O_NOFOLLOW ?? 0;
1634
+ let tempHandle: FileHandle | null = null;
1635
+ try {
1636
+ tempHandle = await fs.open(
1637
+ tempPath,
1638
+ fsSync.constants.O_CREAT |
1639
+ fsSync.constants.O_EXCL |
1640
+ fsSync.constants.O_WRONLY |
1641
+ noFollow,
1642
+ args.originalMode ?? 0o666,
1643
+ );
1644
+ await tempHandle.writeFile(args.content, "utf8");
1645
+ await tempHandle.sync();
1646
+ await tempHandle.close();
1647
+ tempHandle = null;
1648
+
1649
+ // Re-check after the potentially slow temp write/fsync. This catches a
1650
+ // parent/leaf symlink swap and an external content edit before rename.
1651
+ const beforeRenameTarget = await resolveSafeBridgeFileTarget(
1652
+ args.rootPath,
1653
+ args.relPath,
1654
+ );
1655
+ if (beforeRenameTarget.canonicalPath !== args.lockedCanonicalPath) {
1656
+ throw new Error("Bridge target changed during atomic write");
1657
+ }
1658
+ const current = await readBridgeFileSnapshot(
1659
+ beforeRenameTarget.absolutePath,
1660
+ );
1661
+ assertExpectedBridgeVersion(
1662
+ args.expectedVersionHash,
1663
+ current?.versionHash,
1664
+ args.requireExpectedVersionHash,
1665
+ );
1666
+
1667
+ await fs.rename(tempPath, beforeRenameTarget.absolutePath);
1668
+ const directoryHandle = await fs.open(parent, "r").catch(() => null);
1669
+ if (directoryHandle) {
1670
+ try {
1671
+ await directoryHandle.sync();
1672
+ } finally {
1673
+ await directoryHandle.close();
1674
+ }
1675
+ }
1676
+ return contentVersionHash(args.content);
1677
+ } finally {
1678
+ await tempHandle?.close().catch(() => undefined);
1679
+ await fs.rm(tempPath, { force: true }).catch(() => undefined);
1680
+ }
1681
+ }
1682
+
1683
+ /**
1684
+ * Extensions that are safe for creating a brand-new text/code file through
1685
+ * the bridge. Existing files are classified by their bytes instead, so the
1686
+ * Code workbench can edit languages and extensionless config files without a
1687
+ * permanently incomplete allowlist.
1688
+ */
1689
+ const ALLOWED_NEW_TEXT_FILE_EXTENSIONS = new Set([
1690
+ ".html",
1691
+ ".htm",
1692
+ ".css",
1693
+ ".scss",
1694
+ ".less",
1695
+ ".js",
1696
+ ".jsx",
1697
+ ".ts",
1698
+ ".tsx",
1699
+ ".mjs",
1700
+ ".cjs",
1701
+ ".json",
1702
+ ".md",
1703
+ ".mdx",
1704
+ ".vue",
1705
+ ".svelte",
1706
+ ".astro",
1707
+ ".txt",
1708
+ ".yml",
1709
+ ".yaml",
1710
+ ".svg",
1711
+ ".py",
1712
+ ".rb",
1713
+ ".php",
1714
+ ".java",
1715
+ ".kt",
1716
+ ".kts",
1717
+ ".go",
1718
+ ".rs",
1719
+ ".c",
1720
+ ".h",
1721
+ ".cc",
1722
+ ".cpp",
1723
+ ".hpp",
1724
+ ".cs",
1725
+ ".swift",
1726
+ ".sh",
1727
+ ".bash",
1728
+ ".zsh",
1729
+ ".fish",
1730
+ ".sql",
1731
+ ".toml",
1732
+ ".xml",
1733
+ ".graphql",
1734
+ ".gql",
1735
+ ".prisma",
1736
+ ".properties",
1737
+ ".ini",
1738
+ ".conf",
1739
+ ".env.example",
1740
+ ]);
1741
+
1742
+ const ALLOWED_NEW_EXTENSIONLESS_TEXT_FILES = new Set([
1743
+ "dockerfile",
1744
+ "makefile",
1745
+ "procfile",
1746
+ "gemfile",
1747
+ "rakefile",
1748
+ "license",
1749
+ "readme",
1750
+ ]);
1751
+
1752
+ const BLOCKED_BINARY_WRITE_EXTENSIONS = new Set([
1753
+ ".png",
1754
+ ".jpg",
1755
+ ".jpeg",
1756
+ ".gif",
1757
+ ".webp",
1758
+ ".ico",
1759
+ ".woff",
1760
+ ".woff2",
1761
+ ".ttf",
1762
+ ".otf",
1763
+ ".eot",
1764
+ ".mp3",
1765
+ ".mp4",
1766
+ ".mov",
1767
+ ".webm",
1768
+ ".zip",
1769
+ ".gz",
1770
+ ".tar",
1771
+ ".pdf",
1772
+ ".wasm",
1773
+ ".fig",
1774
+ ".sketch",
1775
+ ".exe",
1776
+ ".dll",
1777
+ ".dylib",
1778
+ ".so",
1779
+ ".class",
1780
+ ".jar",
1781
+ ]);
1782
+
1783
+ function assertEditableTextFile(
1784
+ relPath: string,
1785
+ existing: BridgeFileSnapshot | null,
1786
+ ): void {
1787
+ const ext = path.extname(relPath).toLowerCase();
1788
+ if (BLOCKED_BINARY_WRITE_EXTENSIONS.has(ext)) {
1789
+ throw new Error(
1790
+ `Write rejected: extension "${ext}" is a binary file type.`,
1791
+ );
1792
+ }
1793
+ if (existing) {
1794
+ if (existing.content.includes("\0")) {
1795
+ throw new Error(
1796
+ "Write rejected: the existing file contains binary data.",
1797
+ );
1798
+ }
1799
+ return;
1800
+ }
1801
+ const basename = path.basename(relPath).toLowerCase();
1802
+ if (
1803
+ !ALLOWED_NEW_TEXT_FILE_EXTENSIONS.has(ext) &&
1804
+ !ALLOWED_NEW_EXTENSIONLESS_TEXT_FILES.has(basename)
1805
+ ) {
1806
+ throw new Error(
1807
+ `Write rejected: extension "${ext || "(no extension)"}" is not recognized for a new text/code file.`,
1808
+ );
1809
+ }
1810
+ }
1811
+
1812
+ /**
1813
+ * Blocklist for secret-looking paths. Applied to /read-file, /write-file,
1814
+ * /apply-edit, AND /list-files so secrets are never returned or served
1815
+ * through the bridge, even to a caller holding a valid write grant. Reads of
1816
+ * other dotfiles (.gitignore, .prettierrc, etc.) remain allowed.
1817
+ *
1818
+ * All comparisons are case-insensitive: macOS's default filesystem (and
1819
+ * Windows) is case-insensitive, so ".ENV", "ID_RSA", or "KEY.PEM" refer to
1820
+ * the exact same on-disk file as their lowercase form and must be blocked
1821
+ * identically.
1822
+ */
1823
+ function isBlockedSecretPath(relPath: string): boolean {
1824
+ const normalized = normalizeSlash(relPath).replace(/^\/+/, "").toLowerCase();
1825
+ const segments = normalized.split("/").filter(Boolean);
1826
+ if (segments.some((segment) => segment === ".git")) return true;
1827
+ const basename = segments[segments.length - 1] ?? normalized;
1828
+ if (/^\.env/.test(basename)) return true;
1829
+ if (/\.pem$/.test(basename)) return true;
1830
+ if (/\.key$/.test(basename)) return true;
1831
+ if (/^id_rsa/.test(basename)) return true;
1832
+ return false;
1833
+ }
1834
+
1835
+ function assertNotBlockedSecretPath(relPath: string): void {
1836
+ if (isBlockedSecretPath(relPath)) {
1837
+ throw new Error(
1838
+ `Access rejected: "${relPath}" matches a blocked secret-file pattern.`,
1839
+ );
1840
+ }
1841
+ }
1842
+
1843
+ function countOccurrences(haystack: string, needle: string): number {
1844
+ if (!needle) return 0;
1845
+ let count = 0;
1846
+ let index = haystack.indexOf(needle);
1847
+ while (index !== -1) {
1848
+ count += 1;
1849
+ if (count > 1) return count;
1850
+ index = haystack.indexOf(needle, index + 1);
1851
+ }
1852
+ return count;
1853
+ }
1854
+
1855
+ // ── /list-files: recursive walk with .gitignore + always-ignore + size/binary
1856
+ // filtering ───────────────────────────────────────────────────────────────
1857
+
1858
+ /** Directories always excluded from /list-files, regardless of .gitignore. */
1859
+ const ALWAYS_IGNORED_DIR_NAMES = new Set([
1860
+ ".git",
1861
+ "node_modules",
1862
+ "dist",
1863
+ "build",
1864
+ ".next",
1865
+ ".output",
1866
+ ".nuxt",
1867
+ "coverage",
1868
+ ".cache",
1869
+ ]);
1870
+
1871
+ const ALWAYS_IGNORED_FILE_NAMES = new Set([".DS_Store"]);
1872
+
1873
+ /** Binary-looking extensions skipped from /list-files results. */
1874
+ const BINARY_LOOKING_EXTENSIONS = new Set([
1875
+ ".png",
1876
+ ".jpg",
1877
+ ".jpeg",
1878
+ ".gif",
1879
+ ".webp",
1880
+ ".ico",
1881
+ ".woff",
1882
+ ".woff2",
1883
+ ".ttf",
1884
+ ".otf",
1885
+ ".eot",
1886
+ ".mp3",
1887
+ ".mp4",
1888
+ ".mov",
1889
+ ".webm",
1890
+ ".zip",
1891
+ ".gz",
1892
+ ".tar",
1893
+ ".pdf",
1894
+ ".wasm",
1895
+ ".fig",
1896
+ ".sketch",
1897
+ ]);
1898
+
1899
+ const LIST_FILES_MAX_ENTRIES = 20_000;
1900
+ const LIST_FILES_MAX_BYTES = 2 * 1024 * 1024;
1901
+
1902
+ export interface GitignoreRule {
1903
+ /** Raw pattern as read from .gitignore, already trimmed of comments/blank lines. */
1904
+ pattern: string;
1905
+ /** True when the pattern is anchored to the root (leading "/"). */
1906
+ anchored: boolean;
1907
+ /** True when the pattern only matches directories (trailing "/"). */
1908
+ dirOnly: boolean;
1909
+ }
1910
+
1911
+ /**
1912
+ * Parse a simple subset of .gitignore syntax: exact names, `dir/`, `*.ext`,
1913
+ * and leading-slash root-anchored patterns. This intentionally does not
1914
+ * implement full gitignore glob semantics (double-star, negation, etc.) —
1915
+ * good enough to keep obviously-ignored build output and local files out of
1916
+ * the workbench file tree.
1917
+ */
1918
+ export function parseGitignore(contents: string): GitignoreRule[] {
1919
+ const rules: GitignoreRule[] = [];
1920
+ for (const rawLine of contents.split(/\r?\n/)) {
1921
+ const line = rawLine.trim();
1922
+ if (!line || line.startsWith("#")) continue;
1923
+ if (line.startsWith("!")) continue; // negation unsupported in this subset
1924
+ const anchored = line.startsWith("/");
1925
+ const withoutAnchor = anchored ? line.slice(1) : line;
1926
+ const dirOnly = withoutAnchor.endsWith("/");
1927
+ const pattern = dirOnly ? withoutAnchor.slice(0, -1) : withoutAnchor;
1928
+ if (!pattern) continue;
1929
+ rules.push({ pattern, anchored, dirOnly });
1930
+ }
1931
+ return rules;
1932
+ }
1933
+
1934
+ function globToRegExp(pattern: string): RegExp {
1935
+ let out = "";
1936
+ for (const char of pattern) {
1937
+ if (char === "*") out += "[^/]*";
1938
+ else if (char === "?") out += "[^/]";
1939
+ else out += char.replace(/[.+^${}()|[\]\\]/g, "\\$&");
1940
+ }
1941
+ return new RegExp(`^${out}$`);
1942
+ }
1943
+
1944
+ /**
1945
+ * Test one path segment (or full relative path, for anchored patterns)
1946
+ * against a parsed gitignore rule.
1947
+ */
1948
+ function ruleMatches(
1949
+ rule: GitignoreRule,
1950
+ relPath: string,
1951
+ isDir: boolean,
1952
+ ): boolean {
1953
+ if (rule.dirOnly && !isDir) return false;
1954
+ const regex = globToRegExp(rule.pattern);
1955
+ if (rule.anchored) {
1956
+ return regex.test(relPath);
1957
+ }
1958
+ // Unanchored: match against any path segment (basename) or the full path,
1959
+ // mirroring gitignore's "matches at any depth" behavior for simple patterns.
1960
+ const segments = relPath.split("/");
1961
+ return segments.some((segment) => regex.test(segment)) || regex.test(relPath);
1962
+ }
1963
+
1964
+ export function isIgnoredByGitignore(
1965
+ rules: GitignoreRule[],
1966
+ relPath: string,
1967
+ isDir: boolean,
1968
+ ): boolean {
1969
+ return rules.some((rule) => ruleMatches(rule, relPath, isDir));
1970
+ }
1971
+
1972
+ /**
1973
+ * Pure predicate: should this file be excluded from /list-files results?
1974
+ * Combines the always-ignored directory/file names, the parsed .gitignore
1975
+ * rules, the binary-looking extension list, the secret-path blocklist, and
1976
+ * the per-file size cap. `sizeBytes` may be omitted when unknown (the always/
1977
+ * gitignore/binary/secret checks still apply).
1978
+ */
1979
+ export function shouldExcludeFromListing(
1980
+ relPath: string,
1981
+ options: { gitignore: GitignoreRule[]; sizeBytes?: number },
1982
+ ): boolean {
1983
+ const normalized = normalizeSlash(relPath).replace(/^\/+/, "");
1984
+ const segments = normalized.split("/").filter(Boolean);
1985
+ if (segments.some((segment) => ALWAYS_IGNORED_DIR_NAMES.has(segment))) {
1986
+ return true;
1987
+ }
1988
+ const basename = segments[segments.length - 1] ?? normalized;
1989
+ if (ALWAYS_IGNORED_FILE_NAMES.has(basename)) return true;
1990
+ if (isBlockedSecretPath(normalized)) return true;
1991
+ if (isIgnoredByGitignore(options.gitignore, normalized, false)) return true;
1992
+ // A `dir/` gitignore rule matches the file's ancestor directories too, not
1993
+ // just the file itself (e.g. "build-output/" must ignore
1994
+ // "build-output/index.html").
1995
+ for (let depth = 1; depth < segments.length; depth += 1) {
1996
+ const ancestorPath = segments.slice(0, depth).join("/");
1997
+ if (isIgnoredByGitignore(options.gitignore, ancestorPath, true)) {
1998
+ return true;
1999
+ }
2000
+ }
2001
+ const ext = path.extname(basename).toLowerCase();
2002
+ if (BINARY_LOOKING_EXTENSIONS.has(ext)) return true;
2003
+ if (
2004
+ typeof options.sizeBytes === "number" &&
2005
+ options.sizeBytes > LIST_FILES_MAX_BYTES
2006
+ ) {
2007
+ return true;
2008
+ }
2009
+ return false;
2010
+ }
2011
+
2012
+ /**
2013
+ * Should this directory be pruned entirely from the walk? Cheaper than
2014
+ * checking every descendant file individually once a directory itself is
2015
+ * ignored (always-ignored names, gitignore dir rules, or the .git/secret
2016
+ * blocklist).
2017
+ */
2018
+ function shouldPruneDirectory(
2019
+ relPath: string,
2020
+ gitignore: GitignoreRule[],
2021
+ ): boolean {
2022
+ const normalized = normalizeSlash(relPath).replace(/^\/+/, "");
2023
+ const segments = normalized.split("/").filter(Boolean);
2024
+ const basename = segments[segments.length - 1] ?? normalized;
2025
+ if (ALWAYS_IGNORED_DIR_NAMES.has(basename)) return true;
2026
+ if (basename === ".git") return true;
2027
+ if (isIgnoredByGitignore(gitignore, normalized, true)) return true;
2028
+ return false;
2029
+ }
2030
+
2031
+ export interface ListedBridgeFile {
2032
+ path: string;
2033
+ size: number;
2034
+ }
2035
+
2036
+ export interface ListFilesResult {
2037
+ files: ListedBridgeFile[];
2038
+ truncated: boolean;
2039
+ }
2040
+
2041
+ /**
2042
+ * Recursively walk `rootPath`, honoring .gitignore + the always-ignore list +
2043
+ * binary/size/secret filtering, never following symlinks that would escape
2044
+ * root (delegates to `assertPathInside`'s realpath confinement per-entry).
2045
+ * Caps at `LIST_FILES_MAX_ENTRIES` and reports `truncated` when the cap is
2046
+ * hit.
2047
+ */
2048
+ async function walkBridgeFiles(rootPath: string): Promise<ListFilesResult> {
2049
+ let gitignore: GitignoreRule[] = [];
2050
+ try {
2051
+ const raw = await fs.readFile(path.join(rootPath, ".gitignore"), "utf8");
2052
+ gitignore = parseGitignore(raw);
2053
+ } catch {
2054
+ // No root .gitignore — proceed with only the always-ignore list.
2055
+ }
2056
+
2057
+ const files: ListedBridgeFile[] = [];
2058
+ let truncated = false;
2059
+ const resolvedRoot = await fs.realpath(rootPath);
2060
+
2061
+ async function walk(absoluteDir: string, relDir: string): Promise<void> {
2062
+ if (truncated) return;
2063
+ let entries: fsSync.Dirent[];
2064
+ try {
2065
+ entries = await fs.readdir(absoluteDir, { withFileTypes: true });
2066
+ } catch {
2067
+ return;
2068
+ }
2069
+ // Sort for deterministic output.
2070
+ entries.sort((a, b) => a.name.localeCompare(b.name));
2071
+ for (const entry of entries) {
2072
+ if (truncated) return;
2073
+ const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
2074
+ const absolutePath = path.join(absoluteDir, entry.name);
2075
+
2076
+ if (entry.isSymbolicLink()) {
2077
+ // Never follow symlinks that escape root; resolve and re-check.
2078
+ let real: string;
2079
+ try {
2080
+ real = await fs.realpath(absolutePath);
2081
+ } catch {
2082
+ continue;
2083
+ }
2084
+ if (
2085
+ real !== resolvedRoot &&
2086
+ !real.startsWith(resolvedRoot + path.sep)
2087
+ ) {
2088
+ continue;
2089
+ }
2090
+ const stat = await fs.stat(absolutePath).catch(() => null);
2091
+ if (!stat) continue;
2092
+ if (stat.isDirectory()) {
2093
+ if (shouldPruneDirectory(relPath, gitignore)) continue;
2094
+ await walk(absolutePath, relPath);
2095
+ } else if (stat.isFile()) {
2096
+ if (
2097
+ !shouldExcludeFromListing(relPath, {
2098
+ gitignore,
2099
+ sizeBytes: stat.size,
2100
+ })
2101
+ ) {
2102
+ files.push({ path: normalizeSlash(relPath), size: stat.size });
2103
+ if (files.length >= LIST_FILES_MAX_ENTRIES) {
2104
+ truncated = true;
2105
+ return;
2106
+ }
2107
+ }
2108
+ }
2109
+ continue;
2110
+ }
2111
+
2112
+ if (entry.isDirectory()) {
2113
+ if (shouldPruneDirectory(relPath, gitignore)) continue;
2114
+ await walk(absolutePath, relPath);
2115
+ continue;
2116
+ }
2117
+
2118
+ if (entry.isFile()) {
2119
+ const stat = await fs.stat(absolutePath).catch(() => null);
2120
+ if (!stat) continue;
2121
+ if (
2122
+ shouldExcludeFromListing(relPath, {
2123
+ gitignore,
2124
+ sizeBytes: stat.size,
2125
+ })
2126
+ ) {
2127
+ continue;
2128
+ }
2129
+ files.push({ path: normalizeSlash(relPath), size: stat.size });
2130
+ if (files.length >= LIST_FILES_MAX_ENTRIES) {
2131
+ truncated = true;
2132
+ return;
2133
+ }
2134
+ }
2135
+ }
2136
+ }
2137
+
2138
+ await walk(resolvedRoot, "");
2139
+ return { files, truncated };
2140
+ }
2141
+
2142
+ export async function startDesignConnectBridge(
2143
+ manifest: DesignConnectManifest,
2144
+ seedOrOptions?: string | DesignConnectBridgeOptions,
2145
+ ): Promise<DesignConnectBridge> {
2146
+ // Shared secret the browser sends (x-bridge-token) to unlock live-edit/read/
2147
+ // write. Bridge and the user's connection row must agree on it. Adopt a
2148
+ // server-minted seed when given (MCP flow); otherwise mint one and rely on
2149
+ // --app-url self-registration to push it up. Kept in-process, never served.
2150
+ const options: DesignConnectBridgeOptions =
2151
+ typeof seedOrOptions === "string"
2152
+ ? { bridgeToken: seedOrOptions }
2153
+ : (seedOrOptions ?? {});
2154
+ const bridgeToken =
2155
+ options.bridgeToken ||
2156
+ process.env["AGENT_NATIVE_BRIDGE_TOKEN"] ||
2157
+ crypto.randomBytes(32).toString("hex");
2158
+ const previewToken =
2159
+ options.previewToken ||
2160
+ process.env["AGENT_NATIVE_PREVIEW_TOKEN"] ||
2161
+ deriveDesignPreviewToken(bridgeToken);
2162
+ const configuredOrigins = new Set(
2163
+ (options.allowedOrigins ?? []).flatMap((raw): string[] => {
2164
+ try {
2165
+ return [new URL(raw).origin];
2166
+ } catch {
2167
+ return [];
2168
+ }
2169
+ }),
2170
+ );
2171
+ let liveEditBridgeScript = "";
2172
+ // One bridge process serves every URL-backed screen in an overview. The
2173
+ // editor script carries screen-specific state (notably screenId), so a
2174
+ // single global slot lets parallel iframe registrations overwrite each
2175
+ // other and boot a frame with another frame's identity. Keep keyed scripts
2176
+ // for modern clients while retaining the unkeyed slot for older clients.
2177
+ const liveEditBridgeScripts = new Map<string, string>();
2178
+ // Identifies THIS bridge process's in-memory registry, minted fresh every
2179
+ // time the bridge boots. `liveEditBridgeScripts` above only lives in
2180
+ // process memory, so a bridge restart (crash, machine sleep/wake, manual
2181
+ // restart) silently empties it: any screen that registered a bridgeKey
2182
+ // before the restart now gets a 409 "unknown bridge key" from `/live-edit`
2183
+ // even though nothing about that screen actually changed. Echoing this id
2184
+ // on both the registration response and the 409 lets a client tell the two
2185
+ // cases apart — "this exact process never saw my key" (stale/typo, id
2186
+ // matches what it already has cached) vs. "the process restarted since I
2187
+ // registered" (id changed, safe to transparently re-POST `/live-edit-bridge`
2188
+ // and retry) — instead of guessing from the error text or retrying forever.
2189
+ const bridgeInstanceId = crypto.randomBytes(16).toString("hex");
2190
+ const previewSessionCookies = new PreviewSessionCookieJar(
2191
+ manifest.devServerUrl,
2192
+ );
2193
+
2194
+ const server = http.createServer(
2195
+ (req: IncomingMessage, res: ServerResponse) => {
2196
+ const corsApproved = configureBridgeCors(req, res, configuredOrigins);
2197
+ if (req.method === "OPTIONS") {
2198
+ sendJson(
2199
+ res,
2200
+ corsApproved ? 204 : 403,
2201
+ corsApproved
2202
+ ? {}
2203
+ : { ok: false, error: "origin is not allowed by this bridge" },
2204
+ );
2205
+ return;
2206
+ }
2207
+
2208
+ const requestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
2209
+ const pathname = requestUrl.pathname;
2210
+ const providedPreviewToken =
2211
+ readHeader(req, "x-design-preview-token") ||
2212
+ requestUrl.searchParams.get("previewToken") ||
2213
+ readRequestCookie(req, PREVIEW_SESSION_COOKIE_NAME) ||
2214
+ "";
2215
+ const previewTokenValid = constantTimeTokenMatches(
2216
+ providedPreviewToken,
2217
+ previewToken,
2218
+ );
2219
+ const rejectInvalidPreviewToken = (): boolean => {
2220
+ if (previewTokenValid) return false;
2221
+ sendJson(res, 401, {
2222
+ ok: false,
2223
+ error: "invalid or missing preview token",
2224
+ });
2225
+ return true;
2226
+ };
2227
+
2228
+ // ── Read-only preview routes (preview token required) ────────────────
2229
+
2230
+ if (pathname === "/" || pathname === "/manifest.json") {
2231
+ if (rejectInvalidPreviewToken()) return;
2232
+ sendJson(res, 200, manifest as unknown as Record<string, unknown>);
2233
+ return;
2234
+ }
2235
+ if (pathname === "/routes.json") {
2236
+ if (rejectInvalidPreviewToken()) return;
2237
+ sendJson(res, 200, {
2238
+ version: 1,
2239
+ sourceType: "localhost",
2240
+ devServerUrl: manifest.devServerUrl,
2241
+ rootPath: manifest.rootPath,
2242
+ routes: manifest.routes,
2243
+ generatedAt: manifest.generatedAt,
2244
+ });
2245
+ return;
2246
+ }
2247
+ if (pathname === "/health") {
2248
+ sendJson(res, 200, {
2249
+ ok: true,
2250
+ source: manifest.source,
2251
+ appFingerprint: designConnectAppFingerprint(manifest),
2252
+ bridgeInstanceId,
2253
+ });
2254
+ return;
2255
+ }
2256
+ if (pathname === "/live-edit-bridge") {
2257
+ if (req.method !== "POST") {
2258
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
2259
+ return;
2260
+ }
2261
+ if (rejectInvalidPreviewToken()) return;
2262
+ void (async () => {
2263
+ try {
2264
+ const raw = await readRequestBody(req);
2265
+ const body = JSON.parse(raw) as Record<string, unknown>;
2266
+ const script =
2267
+ typeof body["script"] === "string" ? body["script"] : "";
2268
+ const bridgeKey =
2269
+ typeof body["bridgeKey"] === "string"
2270
+ ? body["bridgeKey"].trim()
2271
+ : "";
2272
+ const installsSupportedDesignBridge =
2273
+ script.includes("agent-native:editor-chrome-ready") ||
2274
+ script.includes("embedded-canvas-pan");
2275
+ if (!installsSupportedDesignBridge) {
2276
+ sendJson(res, 400, {
2277
+ ok: false,
2278
+ error:
2279
+ "script must install an approved Agent Native editor or canvas-pan bridge",
2280
+ });
2281
+ return;
2282
+ }
2283
+ if (
2284
+ bridgeKey &&
2285
+ (!/^[a-zA-Z0-9:._-]+$/.test(bridgeKey) || bridgeKey.length > 128)
2286
+ ) {
2287
+ sendJson(res, 400, {
2288
+ ok: false,
2289
+ error: "bridgeKey must be 1-128 safe identifier characters",
2290
+ });
2291
+ return;
2292
+ }
2293
+ liveEditBridgeScript = script;
2294
+ if (bridgeKey) {
2295
+ liveEditBridgeScripts.delete(bridgeKey);
2296
+ liveEditBridgeScripts.set(bridgeKey, script);
2297
+ // Bound the in-memory cache. Normal editor usage has one key per
2298
+ // visible screen; 128 also leaves ample room for mode changes.
2299
+ while (liveEditBridgeScripts.size > 128) {
2300
+ const oldest = liveEditBridgeScripts.keys().next().value;
2301
+ if (typeof oldest !== "string") break;
2302
+ liveEditBridgeScripts.delete(oldest);
2303
+ }
2304
+ }
2305
+ sendJson(res, 200, {
2306
+ ok: true,
2307
+ bridgeInstanceId,
2308
+ ...(bridgeKey ? { bridgeKey } : {}),
2309
+ });
2310
+ } catch (err: unknown) {
2311
+ sendJson(res, 400, {
2312
+ ok: false,
2313
+ error: err instanceof Error ? err.message : String(err),
2314
+ });
2315
+ }
2316
+ })();
2317
+ return;
2318
+ }
2319
+ if (pathname === "/live-edit") {
2320
+ if (req.method !== "GET") {
2321
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
2322
+ return;
2323
+ }
2324
+ if (rejectInvalidPreviewToken()) return;
2325
+ void (async () => {
2326
+ try {
2327
+ const targetUrl = resolvePreviewSnapshotUrl(
2328
+ manifest.devServerUrl,
2329
+ requestUrl.searchParams.get("url") ??
2330
+ requestUrl.searchParams.get("path"),
2331
+ );
2332
+ const snapshot = await fetchPreviewSnapshot(
2333
+ manifest.devServerUrl,
2334
+ targetUrl,
2335
+ previewSessionCookies,
2336
+ );
2337
+ const includeEditorBridge =
2338
+ requestUrl.searchParams.get("bridge") !== "0";
2339
+ const requestedBridgeKey =
2340
+ requestUrl.searchParams.get("bridgeKey")?.trim() ?? "";
2341
+ const editorBridgeScript = requestedBridgeKey
2342
+ ? (liveEditBridgeScripts.get(requestedBridgeKey) ?? "")
2343
+ : liveEditBridgeScript;
2344
+ if (
2345
+ includeEditorBridge &&
2346
+ requestedBridgeKey &&
2347
+ !editorBridgeScript
2348
+ ) {
2349
+ // Machine-readable `code` + echoed `bridgeKey`/`bridgeInstanceId`
2350
+ // let a client distinguish "this bridge process restarted since
2351
+ // I last registered — safe to silently re-POST
2352
+ // /live-edit-bridge and retry" from a genuine caller bug,
2353
+ // instead of string-matching `error` (see bridgeInstanceId's
2354
+ // doc comment above for the full rationale).
2355
+ sendJson(res, 409, {
2356
+ ok: false,
2357
+ code: "unknown-bridge-key",
2358
+ bridgeKey: requestedBridgeKey,
2359
+ bridgeInstanceId,
2360
+ error:
2361
+ "The requested live-edit bridge script is not registered. Reload the Design frame to register it again.",
2362
+ });
2363
+ return;
2364
+ }
2365
+ // The dev server route the SPA must boot on (e.g. "/todo"), taken
2366
+ // from the resolved snapshot target rather than the bridge's own
2367
+ // "/live-edit" request path.
2368
+ const targetParsed = new URL(targetUrl);
2369
+ const targetPath =
2370
+ `${targetParsed.pathname}${targetParsed.search}` || "/";
2371
+ const html = injectLiveEditBridge(
2372
+ snapshot.html,
2373
+ new URL("/", manifest.bridgeUrl).toString(),
2374
+ includeEditorBridge ? editorBridgeScript : "",
2375
+ targetPath,
2376
+ );
2377
+ sendText(
2378
+ res,
2379
+ snapshot.status >= 400 ? snapshot.status : 200,
2380
+ html,
2381
+ snapshot.contentType.includes("html")
2382
+ ? snapshot.contentType
2383
+ : "text/html; charset=utf-8",
2384
+ [
2385
+ ...snapshot.setCookieHeaders,
2386
+ previewSessionSetCookie(previewToken),
2387
+ ],
2388
+ );
2389
+ } catch (err: unknown) {
2390
+ sendJson(res, 400, {
2391
+ ok: false,
2392
+ error: err instanceof Error ? err.message : String(err),
2393
+ });
2394
+ }
2395
+ })();
2396
+ return;
2397
+ }
2398
+ if (pathname === "/snapshot") {
2399
+ if (req.method !== "GET") {
2400
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
2401
+ return;
2402
+ }
2403
+ if (rejectInvalidPreviewToken()) return;
2404
+ void (async () => {
2405
+ try {
2406
+ const targetUrl = resolvePreviewSnapshotUrl(
2407
+ manifest.devServerUrl,
2408
+ requestUrl.searchParams.get("url") ??
2409
+ requestUrl.searchParams.get("path"),
2410
+ );
2411
+ const snapshot = await fetchPreviewSnapshot(
2412
+ manifest.devServerUrl,
2413
+ targetUrl,
2414
+ previewSessionCookies,
2415
+ );
2416
+ sendJson(res, snapshot.status >= 400 ? snapshot.status : 200, {
2417
+ ok: snapshot.status < 400,
2418
+ source: manifest.source,
2419
+ url: snapshot.url,
2420
+ status: snapshot.status,
2421
+ contentType: snapshot.contentType,
2422
+ html: snapshot.html,
2423
+ });
2424
+ } catch (err: unknown) {
2425
+ sendJson(res, 400, {
2426
+ ok: false,
2427
+ error: err instanceof Error ? err.message : String(err),
2428
+ });
2429
+ }
2430
+ })();
2431
+ return;
2432
+ }
2433
+
2434
+ // ── Token-gated write endpoints (POST only) ───────────────────────────
2435
+
2436
+ if (
2437
+ pathname === "/read-file" ||
2438
+ pathname === "/write-file" ||
2439
+ pathname === "/apply-edit" ||
2440
+ pathname === "/list-files"
2441
+ ) {
2442
+ if (req.method !== "POST") {
2443
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
2444
+ return;
2445
+ }
2446
+
2447
+ // Authenticate with constant-time comparison to prevent timing attacks.
2448
+ const providedToken = readHeader(req, "x-bridge-token");
2449
+ const tokenValid = constantTimeTokenMatches(providedToken, bridgeToken);
2450
+ if (!tokenValid) {
2451
+ sendJson(res, 401, {
2452
+ ok: false,
2453
+ error: "invalid or missing bridge token",
2454
+ });
2455
+ return;
2456
+ }
2457
+
2458
+ // Handle asynchronously so we can use await.
2459
+ void (async () => {
2460
+ try {
2461
+ const raw = await readRequestBody(req);
2462
+ const body = JSON.parse(raw) as Record<string, unknown>;
2463
+
2464
+ if (pathname === "/list-files") {
2465
+ try {
2466
+ const result = await walkBridgeFiles(manifest.rootPath);
2467
+ sendJson(res, 200, {
2468
+ ok: true,
2469
+ files: result.files,
2470
+ truncated: result.truncated,
2471
+ });
2472
+ } catch (err: unknown) {
2473
+ sendJson(res, 500, {
2474
+ ok: false,
2475
+ error: `list-files failed: ${err instanceof Error ? err.message : String(err)}`,
2476
+ });
2477
+ }
2478
+ return;
2479
+ }
2480
+
2481
+ const relPath =
2482
+ typeof body["relPath"] === "string" ? body["relPath"] : undefined;
2483
+
2484
+ if (!relPath) {
2485
+ sendJson(res, 400, { ok: false, error: "relPath is required" });
2486
+ return;
2487
+ }
2488
+
2489
+ assertNotBlockedSecretPath(relPath);
2490
+ const initialTarget = await resolveSafeBridgeFileTarget(
2491
+ manifest.rootPath,
2492
+ relPath,
2493
+ );
2494
+
2495
+ if (pathname === "/read-file") {
2496
+ // Read-file: no extension restriction (agents need to read any
2497
+ // non-secret file), but the secret-path blocklist above still
2498
+ // applies to .env*, *.pem, *.key, id_rsa*, and anything under .git/.
2499
+ const snapshot = await readBridgeFileSnapshot(
2500
+ initialTarget.absolutePath,
2501
+ );
2502
+ if (!snapshot) {
2503
+ sendJson(res, 404, { ok: false, error: "file not found" });
2504
+ return;
2505
+ }
2506
+ sendJson(res, 200, {
2507
+ ok: true,
2508
+ content: snapshot.content,
2509
+ versionHash: snapshot.versionHash,
2510
+ });
2511
+ return;
2512
+ }
2513
+
2514
+ const expectedVersionHash =
2515
+ typeof body["expectedVersionHash"] === "string"
2516
+ ? body["expectedVersionHash"]
2517
+ : undefined;
2518
+ const requireExpectedVersionHash =
2519
+ body["requireExpectedVersionHash"] === true;
2520
+ await withBridgeWriteLock(initialTarget.canonicalPath, async () => {
2521
+ const lockedTarget = await resolveSafeBridgeFileTarget(
2522
+ manifest.rootPath,
2523
+ relPath,
2524
+ );
2525
+ if (lockedTarget.canonicalPath !== initialTarget.canonicalPath) {
2526
+ throw new Error(
2527
+ "Bridge target changed while waiting for the write lock",
2528
+ );
2529
+ }
2530
+ const existing = await readBridgeFileSnapshot(
2531
+ lockedTarget.absolutePath,
2532
+ );
2533
+ assertEditableTextFile(relPath, existing);
2534
+ assertExpectedBridgeVersion(
2535
+ expectedVersionHash,
2536
+ existing?.versionHash,
2537
+ requireExpectedVersionHash,
2538
+ );
2539
+
2540
+ if (pathname === "/write-file") {
2541
+ const content =
2542
+ typeof body["content"] === "string"
2543
+ ? body["content"]
2544
+ : undefined;
2545
+ if (content === undefined) {
2546
+ sendJson(res, 400, {
2547
+ ok: false,
2548
+ error: "content is required for write-file",
2549
+ });
2550
+ return;
2551
+ }
2552
+ const versionHash = await atomicWriteBridgeFile({
2553
+ rootPath: manifest.rootPath,
2554
+ relPath,
2555
+ lockedCanonicalPath: initialTarget.canonicalPath,
2556
+ content,
2557
+ expectedVersionHash,
2558
+ requireExpectedVersionHash,
2559
+ originalMode: existing?.mode,
2560
+ });
2561
+ sendJson(res, 200, { ok: true, relPath, versionHash });
2562
+ return;
2563
+ }
2564
+
2565
+ // /apply-edit supports either full replace ({content}) or one
2566
+ // exact search-and-replace. Both stay within this file's lock.
2567
+ if (typeof body["content"] === "string") {
2568
+ const versionHash = await atomicWriteBridgeFile({
2569
+ rootPath: manifest.rootPath,
2570
+ relPath,
2571
+ lockedCanonicalPath: initialTarget.canonicalPath,
2572
+ content: body["content"],
2573
+ expectedVersionHash,
2574
+ requireExpectedVersionHash,
2575
+ originalMode: existing?.mode,
2576
+ });
2577
+ sendJson(res, 200, {
2578
+ ok: true,
2579
+ relPath,
2580
+ method: "replace",
2581
+ versionHash,
2582
+ });
2583
+ return;
2584
+ }
2585
+
2586
+ const search =
2587
+ typeof body["search"] === "string" ? body["search"] : undefined;
2588
+ const replace =
2589
+ typeof body["replace"] === "string"
2590
+ ? body["replace"]
2591
+ : undefined;
2592
+ if (search === undefined || replace === undefined) {
2593
+ sendJson(res, 400, {
2594
+ ok: false,
2595
+ error:
2596
+ "apply-edit requires either {content} for a full replace, or {search, replace} for a patch",
2597
+ });
2598
+ return;
2599
+ }
2600
+ if (!existing) {
2601
+ sendJson(res, 404, {
2602
+ ok: false,
2603
+ error: "file not found — use write-file to create new files",
2604
+ });
2605
+ return;
2606
+ }
2607
+ if (search.length === 0) {
2608
+ sendJson(res, 400, {
2609
+ ok: false,
2610
+ error: "search string must not be empty",
2611
+ });
2612
+ return;
2613
+ }
2614
+ const occurrenceCount = countOccurrences(
2615
+ existing.content,
2616
+ search,
2617
+ );
2618
+ if (occurrenceCount === 0) {
2619
+ sendJson(res, 422, {
2620
+ ok: false,
2621
+ error: "search string not found in file",
2622
+ });
2623
+ return;
2624
+ }
2625
+ if (occurrenceCount > 1) {
2626
+ sendJson(res, 422, {
2627
+ ok: false,
2628
+ error:
2629
+ "search string is ambiguous; it appears more than once in the file",
2630
+ });
2631
+ return;
2632
+ }
2633
+ const updated = existing.content.replace(search, replace);
2634
+ const versionHash = await atomicWriteBridgeFile({
2635
+ rootPath: manifest.rootPath,
2636
+ relPath,
2637
+ lockedCanonicalPath: initialTarget.canonicalPath,
2638
+ content: updated,
2639
+ expectedVersionHash,
2640
+ requireExpectedVersionHash,
2641
+ originalMode: existing.mode,
2642
+ });
2643
+ sendJson(res, 200, {
2644
+ ok: true,
2645
+ relPath,
2646
+ method: "patch",
2647
+ versionHash,
2648
+ });
2649
+ });
2650
+ } catch (err: unknown) {
2651
+ if (err instanceof BridgePreconditionRequiredError) {
2652
+ sendJson(res, 428, {
2653
+ ok: false,
2654
+ error: "expectedVersionHash is required",
2655
+ });
2656
+ return;
2657
+ }
2658
+ if (err instanceof BridgeVersionConflictError) {
2659
+ sendJson(res, 409, {
2660
+ ok: false,
2661
+ error: "version conflict",
2662
+ currentVersionHash: err.currentVersionHash,
2663
+ });
2664
+ return;
2665
+ }
2666
+ sendJson(res, 500, {
2667
+ ok: false,
2668
+ error: err instanceof Error ? err.message : String(err),
2669
+ });
2670
+ }
2671
+ })();
2672
+ return;
2673
+ }
2674
+
2675
+ if (
2676
+ req.method &&
2677
+ ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"].includes(req.method)
2678
+ ) {
2679
+ if (!previewTokenValid) {
2680
+ sendJson(res, 401, {
2681
+ ok: false,
2682
+ error: "invalid or missing preview token",
2683
+ });
2684
+ return;
2685
+ }
2686
+ void (async () => {
2687
+ try {
2688
+ const proxyRequestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
2689
+ proxyRequestUrl.searchParams.delete("previewToken");
2690
+ const targetUrl = resolvePreviewProxyUrl(
2691
+ manifest.devServerUrl,
2692
+ `${proxyRequestUrl.pathname}${proxyRequestUrl.search}`,
2693
+ );
2694
+ const method = req.method ?? "GET";
2695
+ const requestBody =
2696
+ method === "GET" || method === "HEAD"
2697
+ ? undefined
2698
+ : await readPreviewProxyRequestBody(req);
2699
+ const cookieHeader = previewSessionCookies.headerFor(targetUrl);
2700
+ const proxied = await fetchPreviewProxyResource(
2701
+ manifest.devServerUrl,
2702
+ targetUrl,
2703
+ {
2704
+ method,
2705
+ headers: previewProxyRequestHeaders(
2706
+ req,
2707
+ manifest.devServerUrl,
2708
+ cookieHeader,
2709
+ ),
2710
+ body: requestBody,
2711
+ },
2712
+ previewSessionCookies,
2713
+ );
2714
+ const contentType = proxied.headers.get("content-type") ?? "";
2715
+ const documentNavigation =
2716
+ readHeader(req, "sec-fetch-dest") === "document";
2717
+ const responseBody =
2718
+ documentNavigation && contentType.includes("html")
2719
+ ? Buffer.from(
2720
+ injectLiveEditBridge(
2721
+ proxied.body.toString("utf8"),
2722
+ new URL("/", manifest.bridgeUrl).toString(),
2723
+ liveEditBridgeScript,
2724
+ (() => {
2725
+ const parsed = new URL(proxied.url);
2726
+ return `${parsed.pathname}${parsed.search}` || "/";
2727
+ })(),
2728
+ ),
2729
+ )
2730
+ : proxied.body;
2731
+ sendBytes(
2732
+ res,
2733
+ proxied.status,
2734
+ method === "HEAD" ? Buffer.alloc(0) : responseBody,
2735
+ proxied.headers,
2736
+ responseBody.length,
2737
+ proxied.setCookieHeaders,
2738
+ );
2739
+ } catch (err: unknown) {
2740
+ sendJson(
2741
+ res,
2742
+ err instanceof PreviewProxyRequestTooLargeError ? 413 : 400,
2743
+ {
2744
+ ok: false,
2745
+ error: err instanceof Error ? err.message : String(err),
2746
+ },
2747
+ );
2748
+ }
2749
+ })();
2750
+ return;
2751
+ }
2752
+
2753
+ sendJson(res, 404, { ok: false, error: "not found" });
2754
+ },
2755
+ );
2756
+
2757
+ // Vite's proxied /@vite/client derives its HMR socket from the document's
2758
+ // bridge origin. Tunnel WebSocket upgrades to the one connected dev-server
2759
+ // origin so Fast Refresh remains live inside URL-backed screens. The target
2760
+ // is never caller-controlled, bridge credentials are stripped, and only a
2761
+ // same-origin iframe (or an explicit preview-token caller) can open it.
2762
+ server.on("upgrade", (req, clientSocket, clientHead) => {
2763
+ const requestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
2764
+ const providedPreviewToken =
2765
+ readHeader(req, "x-design-preview-token") ||
2766
+ requestUrl.searchParams.get("previewToken") ||
2767
+ readRequestCookie(req, PREVIEW_SESSION_COOKIE_NAME) ||
2768
+ "";
2769
+ const browserOrigin = readHeader(req, "origin");
2770
+ const sameBridgeOrigin =
2771
+ browserOrigin === new URL(manifest.bridgeUrl).origin;
2772
+ if (!constantTimeTokenMatches(providedPreviewToken, previewToken)) {
2773
+ clientSocket.end(
2774
+ "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
2775
+ );
2776
+ return;
2777
+ }
2778
+ requestUrl.searchParams.delete("previewToken");
2779
+ let targetUrl: URL;
2780
+ try {
2781
+ targetUrl = new URL(
2782
+ resolvePreviewProxyUrl(
2783
+ manifest.devServerUrl,
2784
+ `${requestUrl.pathname}${requestUrl.search}`,
2785
+ ),
2786
+ );
2787
+ } catch {
2788
+ clientSocket.end(
2789
+ "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
2790
+ );
2791
+ return;
2792
+ }
2793
+
2794
+ const upstreamHeaders = { ...req.headers };
2795
+ delete upstreamHeaders["x-bridge-token"];
2796
+ delete upstreamHeaders["x-design-preview-token"];
2797
+ delete upstreamHeaders["authorization"];
2798
+ delete upstreamHeaders["cookie"];
2799
+ upstreamHeaders.host = targetUrl.host;
2800
+ upstreamHeaders.origin = new URL(manifest.devServerUrl).origin;
2801
+ const cookie = mergePreviewCookieHeaders(
2802
+ previewSessionCookies.headerFor(targetUrl.toString()),
2803
+ sameBridgeOrigin ? readHeader(req, "cookie") : "",
2804
+ );
2805
+ if (cookie) upstreamHeaders.cookie = cookie;
2806
+
2807
+ const requestImpl = targetUrl.protocol === "https:" ? https : http;
2808
+ const upstreamRequest = requestImpl.request({
2809
+ protocol: targetUrl.protocol,
2810
+ hostname: targetUrl.hostname,
2811
+ port: targetUrl.port,
2812
+ path: `${targetUrl.pathname}${targetUrl.search}`,
2813
+ method: "GET",
2814
+ headers: upstreamHeaders,
2815
+ });
2816
+ upstreamRequest.on(
2817
+ "upgrade",
2818
+ (upstreamResponse, upstreamSocket, upstreamHead) => {
2819
+ const statusLine = `HTTP/1.1 ${upstreamResponse.statusCode ?? 101} ${upstreamResponse.statusMessage || "Switching Protocols"}\r\n`;
2820
+ const headerLines: string[] = [];
2821
+ for (
2822
+ let index = 0;
2823
+ index < upstreamResponse.rawHeaders.length;
2824
+ index += 2
2825
+ ) {
2826
+ const name = upstreamResponse.rawHeaders[index];
2827
+ const value = upstreamResponse.rawHeaders[index + 1];
2828
+ if (name && value !== undefined)
2829
+ headerLines.push(`${name}: ${value}`);
2830
+ }
2831
+ clientSocket.write(`${statusLine}${headerLines.join("\r\n")}\r\n\r\n`);
2832
+ if (clientHead.length > 0) upstreamSocket.write(clientHead);
2833
+ if (upstreamHead.length > 0) clientSocket.write(upstreamHead);
2834
+ clientSocket.once("close", () => upstreamSocket.destroy());
2835
+ upstreamSocket.once("close", () => clientSocket.destroy());
2836
+ upstreamSocket.pipe(clientSocket);
2837
+ clientSocket.pipe(upstreamSocket);
2838
+ },
2839
+ );
2840
+ upstreamRequest.on("response", (upstreamResponse) => {
2841
+ clientSocket.end(
2842
+ `HTTP/1.1 ${upstreamResponse.statusCode ?? 502} ${upstreamResponse.statusMessage || "WebSocket upgrade failed"}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`,
2843
+ );
2844
+ upstreamResponse.resume();
2845
+ });
2846
+ upstreamRequest.on("error", () => {
2847
+ if (!clientSocket.destroyed) {
2848
+ clientSocket.end(
2849
+ "HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
2850
+ );
2851
+ }
2852
+ });
2853
+ upstreamRequest.end();
2854
+ });
2855
+
2856
+ await new Promise<void>((resolve, reject) => {
2857
+ server.once("error", reject);
2858
+ server.listen(new URL(manifest.bridgeUrl).port, "127.0.0.1", () => {
2859
+ server.off("error", reject);
2860
+ resolve();
2861
+ });
2862
+ });
2863
+
2864
+ return { server, manifest, bridgeToken, previewToken, bridgeInstanceId };
2865
+ }
2866
+
2867
+ /**
2868
+ * Resolve the design app URL from an explicit value or environment variables.
2869
+ * Returns undefined when no URL is configured (registration is optional).
2870
+ */
2871
+ export function resolveAppUrl(explicit?: string): string | undefined {
2872
+ const raw =
2873
+ explicit ||
2874
+ process.env["AGENT_NATIVE_URL"] ||
2875
+ process.env["DESIGN_APP_URL"] ||
2876
+ process.env["APP_URL"] ||
2877
+ process.env["VITE_APP_URL"] ||
2878
+ process.env["BETTER_AUTH_URL"] ||
2879
+ process.env["VITE_BETTER_AUTH_URL"];
2880
+ if (!raw) return undefined;
2881
+ try {
2882
+ const parsed = new URL(raw.trim());
2883
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2884
+ return undefined;
2885
+ }
2886
+ return parsed.toString().replace(/\/+$/, "");
2887
+ } catch {
2888
+ return undefined;
2889
+ }
2890
+ }
2891
+
2892
+ /**
2893
+ * Resolve a bearer token from environment variables for authenticating the
2894
+ * self-registration POST. The CLI does not perform a device-code flow — it
2895
+ * relies on a pre-minted token supplied via env var (e.g. the same token
2896
+ * already written into the agent's MCP config).
2897
+ */
2898
+ function resolveAuthToken(): string | undefined {
2899
+ return (
2900
+ process.env["AGENT_NATIVE_TOKEN"] ||
2901
+ process.env["DESIGN_ACCESS_TOKEN"] ||
2902
+ process.env["ACCESS_TOKEN"] ||
2903
+ undefined
2904
+ );
2905
+ }
2906
+
2907
+ /**
2908
+ * POST to the design app's `connect-localhost` action endpoint to register
2909
+ * (or refresh) the bridge connection and persist the real bridge token on the
2910
+ * server row. This is a best-effort call: failures are logged but do not
2911
+ * abort the bridge process.
2912
+ *
2913
+ * @param appUrl - Deployed design app base URL (e.g. https://design.agent-native.com)
2914
+ * @param bridge - The running bridge returned by startDesignConnectBridge
2915
+ * @param authToken - Optional bearer token for the authenticated action route
2916
+ */
2917
+ export async function registerConnectionWithServer(
2918
+ appUrl: string,
2919
+ bridge: DesignConnectBridge,
2920
+ authToken?: string,
2921
+ ): Promise<void> {
2922
+ const endpoint = `${appUrl}/_agent-native/actions/connect-localhost`;
2923
+ const { manifest, bridgeToken, previewToken } = bridge;
2924
+ const payload = {
2925
+ devServerUrl: manifest.devServerUrl,
2926
+ bridgeUrl: manifest.bridgeUrl,
2927
+ rootPath: manifest.rootPath,
2928
+ capabilities: manifest.capabilities.filter((capability) =>
2929
+ SERVER_REGISTRATION_BRIDGE_OPERATIONS.has(capability.operation),
2930
+ ),
2931
+ routeManifest: {
2932
+ version: 1 as const,
2933
+ sourceType: "localhost" as const,
2934
+ devServerUrl: manifest.devServerUrl,
2935
+ rootPath: manifest.rootPath,
2936
+ routes: manifest.routes,
2937
+ generatedAt: manifest.generatedAt,
2938
+ },
2939
+ // Include the real bridge token so the server stores it on the connection
2940
+ // row. grant-localhost-write-consent then reads it from the row instead of
2941
+ // minting its own unrelated token, which would always produce a 401.
2942
+ bridgeToken,
2943
+ previewToken,
2944
+ status: "connected" as const,
2945
+ };
2946
+
2947
+ const headers: Record<string, string> = {
2948
+ "content-type": "application/json",
2949
+ };
2950
+ if (authToken) {
2951
+ headers["authorization"] = `Bearer ${authToken}`;
2952
+ }
2953
+
2954
+ const controller = new AbortController();
2955
+ const timeout = setTimeout(() => controller.abort(), 15_000);
2956
+ try {
2957
+ const res = await fetch(endpoint, {
2958
+ method: "POST",
2959
+ headers,
2960
+ body: JSON.stringify(payload),
2961
+ signal: controller.signal,
2962
+ });
2963
+ if (!res.ok) {
2964
+ const message = await res.text().catch(() => res.statusText);
2965
+ throw new Error(`${res.status} ${message || res.statusText}`);
2966
+ }
2967
+ } catch (error) {
2968
+ // Best-effort: network errors or auth issues are non-fatal, but make the
2969
+ // problem discoverable before the user tries "Apply to source".
2970
+ console.error(
2971
+ `[design connect] Could not register bridge with ${endpoint}: ${
2972
+ error instanceof Error ? error.message : String(error)
2973
+ }`,
2974
+ );
2975
+ } finally {
2976
+ clearTimeout(timeout);
2977
+ }
2978
+ }
2979
+
2980
+ function printHelp() {
2981
+ console.log(`Usage:
2982
+ agent-native design connect [options]
2983
+
2984
+ Options:
2985
+ --url <url> Dev server URL to inspect (auto-detected if omitted)
2986
+ --port <number> Local bridge port (default ${DEFAULT_BRIDGE_PORT})
2987
+ --root <path> App/repo root for route discovery (default cwd)
2988
+ --route-manifest <path> Non-destructive route manifest output path
2989
+ --app-url <url> Deployed design app URL for self-registration
2990
+ (also reads AGENT_NATIVE_URL / DESIGN_APP_URL env)
2991
+ --bridge-token <token> Adopt a bridge token minted server-side by the
2992
+ authenticated connect-localhost / open-visual-edit
2993
+ action instead of minting one. Used by the remote-MCP
2994
+ /visual-edit flow so the bridge and the user's stored
2995
+ connection token match with no self-registration.
2996
+ (also reads AGENT_NATIVE_BRIDGE_TOKEN env)
2997
+ --preview-token <token> Adopt the paired read-only browser preview token.
2998
+ Optional when --bridge-token is present: compatible
2999
+ clients derive the same one-way token automatically.
3000
+ (also reads AGENT_NATIVE_PREVIEW_TOKEN env)
3001
+ --daemon Start the bridge detached, wait for /health, then exit
3002
+ --json Print the manifest JSON and exit
3003
+ --once Prepare/scaffold the manifest and exit
3004
+ --dry-run Print what would be exposed without writing files
3005
+
3006
+ Element provenance (resolveNodeToFile):
3007
+ The design editor can map a selected DOM element back to its source file,
3008
+ line, and component name using one of these provenance sources:
3009
+
3010
+ • React development builds with jsxDEV enabled (the default):
3011
+ Design reads the selected element's development-only Fiber debug stack.
3012
+ React does not emit data-source-* DOM attributes automatically.
3013
+
3014
+ • A Babel source plugin (e.g. babel-plugin-react-source or a custom plugin):
3015
+ Emits data-source-file="src/Button.tsx" data-source-line="12"
3016
+ data-source-column="4" data-component-name="Button" on each element.
3017
+
3018
+ • data-loc="src/Button.tsx:12:4" shorthand attribute (Babel source convention):
3019
+ The bridge parses this as { sourceFile, line, column } automatically.
3020
+
3021
+ In production React builds and other runtimes without explicit attributes,
3022
+ the editor still works but exact element provenance may be absent.
3023
+ Cross-origin localhost iframes cannot be read regardless of attributes (CSP).`);
3024
+ }
3025
+
3026
+ function removeDaemonFlag(argv: string[]): string[] {
3027
+ return argv.filter((arg) => arg !== "--daemon");
3028
+ }
3029
+
3030
+ function resolveCurrentCliInvocation(argv: string[]): {
3031
+ command: string;
3032
+ args: string[];
3033
+ } {
3034
+ const suffixLength = argv.length + 1; // leading "design" command + runDesign argv
3035
+ const prefixEnd = Math.max(1, process.argv.length - suffixLength);
3036
+ const cliPrefix = process.argv.slice(1, prefixEnd);
3037
+ const entry = cliPrefix[0] ?? process.argv[1];
3038
+ if (!entry) {
3039
+ throw new Error("Could not resolve current CLI entrypoint for --daemon");
3040
+ }
3041
+ if (entry.endsWith(".ts") || entry.endsWith(".tsx")) {
3042
+ return {
3043
+ command: "tsx",
3044
+ args: [...cliPrefix, "design", ...removeDaemonFlag(argv)],
3045
+ };
3046
+ }
3047
+ return {
3048
+ command: process.execPath,
3049
+ args: [...cliPrefix, "design", ...removeDaemonFlag(argv)],
3050
+ };
3051
+ }
3052
+
3053
+ async function startDetachedDesignBridge(
3054
+ argv: string[],
3055
+ manifest: DesignConnectManifest,
3056
+ previewToken?: string,
3057
+ ): Promise<number> {
3058
+ if (await waitForBridgeHealth(manifest.bridgeUrl, 800)) {
3059
+ const [runningManifest, runningFingerprint] = await Promise.all([
3060
+ fetchRunningBridgeManifest(manifest.bridgeUrl, previewToken),
3061
+ fetchRunningBridgeFingerprint(manifest.bridgeUrl),
3062
+ ]);
3063
+ const fingerprintMatches =
3064
+ runningFingerprint === designConnectAppFingerprint(manifest);
3065
+ if (
3066
+ (runningManifest &&
3067
+ designConnectManifestsTargetSameApp(runningManifest, manifest)) ||
3068
+ fingerprintMatches
3069
+ ) {
3070
+ console.error(
3071
+ `Design localhost bridge already running at ${manifest.bridgeUrl}`,
3072
+ );
3073
+ console.log(JSON.stringify(runningManifest ?? manifest, null, 2));
3074
+ return 0;
3075
+ }
3076
+
3077
+ console.error(
3078
+ [
3079
+ `Design localhost bridge already running at ${manifest.bridgeUrl} for a different app.`,
3080
+ runningManifest
3081
+ ? `Running app: ${runningManifest.devServerUrl} (${runningManifest.rootPath})`
3082
+ : "Running bridge did not expose a compatible manifest.",
3083
+ `Requested app: ${manifest.devServerUrl} (${manifest.rootPath})`,
3084
+ "Stop the existing bridge or choose a different --port.",
3085
+ ].join("\n"),
3086
+ );
3087
+ return 1;
3088
+ }
3089
+
3090
+ const invocation = resolveCurrentCliInvocation(argv);
3091
+ const child = spawn(invocation.command, invocation.args, {
3092
+ cwd: process.cwd(),
3093
+ detached: true,
3094
+ env: process.env,
3095
+ stdio: "ignore",
3096
+ shell: process.platform === "win32",
3097
+ });
3098
+ child.unref();
3099
+
3100
+ if (await waitForBridgeHealth(manifest.bridgeUrl)) {
3101
+ console.error(`Design localhost bridge running at ${manifest.bridgeUrl}`);
3102
+ console.log(JSON.stringify(manifest, null, 2));
3103
+ return 0;
3104
+ }
3105
+
3106
+ console.error(
3107
+ `Timed out waiting for detached Design bridge at ${manifest.bridgeUrl}`,
3108
+ );
3109
+ return 1;
3110
+ }
3111
+
3112
+ export async function runDesign(argv: string[]) {
3113
+ const subcommand = argv[0];
3114
+ if (subcommand !== "connect") {
3115
+ if (
3116
+ subcommand === "help" ||
3117
+ subcommand === "--help" ||
3118
+ subcommand === "-h"
3119
+ ) {
3120
+ printHelp();
3121
+ return 0;
3122
+ }
3123
+ console.error("Usage: agent-native design connect [options]");
3124
+ return 1;
3125
+ }
3126
+
3127
+ const parsed = parseDesignConnectArgs(argv);
3128
+ if (parsed.help) {
3129
+ printHelp();
3130
+ return 0;
3131
+ }
3132
+
3133
+ const manifest = await prepareDesignConnectManifest(parsed);
3134
+ const seedBridgeToken =
3135
+ parsed.bridgeToken || process.env["AGENT_NATIVE_BRIDGE_TOKEN"] || undefined;
3136
+ const seedPreviewToken =
3137
+ parsed.previewToken ||
3138
+ process.env["AGENT_NATIVE_PREVIEW_TOKEN"] ||
3139
+ (seedBridgeToken ? deriveDesignPreviewToken(seedBridgeToken) : undefined);
3140
+ const appUrl = resolveAppUrl(parsed.appUrl);
3141
+ if (parsed.daemon) {
3142
+ return startDetachedDesignBridge(argv, manifest, seedPreviewToken);
3143
+ }
3144
+ if (parsed.json || parsed.once || parsed.dryRun) {
3145
+ console.log(JSON.stringify(manifest, null, 2));
3146
+ return 0;
3147
+ }
3148
+
3149
+ const bridge = await startDesignConnectBridge(manifest, {
3150
+ bridgeToken: seedBridgeToken,
3151
+ previewToken: seedPreviewToken,
3152
+ allowedOrigins: appUrl ? [appUrl] : [],
3153
+ });
3154
+ console.error("Design localhost bridge running");
3155
+ console.error(`Bridge: ${manifest.bridgeUrl}`);
3156
+ console.error(`Manifest: ${manifest.bridgeUrl}/manifest.json`);
3157
+ console.error(`Routes: ${manifest.routeCount}`);
3158
+ console.error(`Dev URL: ${manifest.devServerUrl}`);
3159
+
3160
+ if (seedBridgeToken) {
3161
+ // Server already stored this token on the row; bridge matches it, so no
3162
+ // self-registration needed. Zero-config path for the remote-MCP flow.
3163
+ console.error(
3164
+ "[design connect] Using server-provided bridge token; skipping self-registration.",
3165
+ );
3166
+ } else {
3167
+ // No seed: fall back to self-registration — POST the minted token to
3168
+ // connect-localhost. Needs an auth token in env or it 401s (the old gap).
3169
+ if (appUrl) {
3170
+ void registerConnectionWithServer(appUrl, bridge, resolveAuthToken());
3171
+ } else {
3172
+ // No token source at all — warn rather than 401 silently at edit time.
3173
+ console.error(
3174
+ "[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.",
3175
+ );
3176
+ }
3177
+ }
3178
+
3179
+ return await new Promise<number>((resolve) => {
3180
+ const stop = () => {
3181
+ bridge.server.close(() => resolve(0));
3182
+ };
3183
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
3184
+ process.once(signal, stop);
3185
+ }
3186
+ });
3187
+ }