@agent-native/core 0.90.3 → 0.90.5

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 (256) hide show
  1. package/bin/agent-native.js +25 -3
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +17 -0
  4. package/corpus/core/bin/agent-native.js +25 -3
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/durable-background.ts +18 -18
  7. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +8 -2
  8. package/corpus/core/src/agent/engine/anthropic-engine.ts +11 -1
  9. package/corpus/core/src/agent/engine/index.ts +1 -0
  10. package/corpus/core/src/agent/engine/registry.ts +63 -2
  11. package/corpus/core/src/agent/production-agent.ts +22 -11
  12. package/corpus/core/src/agent/run-manager.ts +17 -6
  13. package/corpus/core/src/agent/run-store.ts +16 -11
  14. package/corpus/core/src/cli/create.ts +35 -0
  15. package/corpus/core/src/cli/design-connect.ts +263 -1
  16. package/corpus/core/src/cli/index.ts +84 -7
  17. package/corpus/core/src/cli/workspacify.ts +34 -0
  18. package/corpus/core/src/client/KeepTabOpenNotice.tsx +20 -15
  19. package/corpus/core/src/client/RunStuckBanner.tsx +14 -13
  20. package/corpus/core/src/client/agent-chat-adapter.ts +28 -17
  21. package/corpus/core/src/client/history/VersionHistoryPanel.tsx +77 -47
  22. package/corpus/core/src/client/history/index.ts +3 -0
  23. package/corpus/core/src/client/history/use-history.ts +28 -0
  24. package/corpus/core/src/client/index.ts +8 -0
  25. package/corpus/core/src/client/review/index.ts +5 -0
  26. package/corpus/core/src/client/review/use-review.ts +40 -2
  27. package/corpus/core/src/client/use-run-stuck-detection.ts +8 -5
  28. package/corpus/core/src/history/actions/create-resource-version.ts +11 -8
  29. package/corpus/core/src/history/index.ts +0 -1
  30. package/corpus/core/src/history/registry.ts +24 -12
  31. package/corpus/core/src/history/store.ts +84 -53
  32. package/corpus/core/src/review/actions/consume-review-feedback.ts +15 -5
  33. package/corpus/core/src/review/actions/delete-review-comment.ts +15 -3
  34. package/corpus/core/src/review/actions/resolve-review-thread.ts +12 -5
  35. package/corpus/core/src/review/index.ts +0 -5
  36. package/corpus/core/src/review/registry.ts +24 -12
  37. package/corpus/core/src/review/store.ts +10 -7
  38. package/corpus/core/src/server/agent-chat-plugin.ts +6 -0
  39. package/corpus/core/src/server/credential-provider.ts +13 -3
  40. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +11 -11
  41. package/corpus/templates/assets/actions/generate-image.ts +64 -28
  42. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +1 -1
  43. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +91 -74
  44. package/corpus/templates/assets/server/lib/generation.ts +29 -4
  45. package/corpus/templates/assets/server/lib/image-processing.ts +131 -0
  46. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +24 -2
  47. package/corpus/templates/calendar/AGENTS.md +3 -0
  48. package/corpus/templates/calendar/actions/create-event.ts +8 -34
  49. package/corpus/templates/calendar/actions/event-action-helpers.ts +52 -0
  50. package/corpus/templates/calendar/actions/manage-event-draft.ts +7 -34
  51. package/corpus/templates/calendar/actions/update-event.ts +10 -23
  52. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +47 -0
  53. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +23 -0
  54. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +59 -2
  55. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +40 -1
  56. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +34 -1
  57. package/corpus/templates/calendar/app/hooks/use-events.ts +6 -0
  58. package/corpus/templates/calendar/app/i18n/zh-TW.ts +4 -0
  59. package/corpus/templates/calendar/app/i18n-data.ts +40 -0
  60. package/corpus/templates/calendar/changelog/2026-07-07-you-can-mark-event-guests-as-optional-while-inviting-them-or.md +6 -0
  61. package/corpus/templates/calendar/server/handlers/events.ts +1 -0
  62. package/corpus/templates/calendar/server/lib/google-calendar.ts +4 -0
  63. package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
  64. package/corpus/templates/calendar/shared/api.ts +2 -0
  65. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +29 -3
  66. package/corpus/templates/clips/actions/generate-workflow.ts +19 -8
  67. package/corpus/templates/clips/actions/get-clips-ai-prefs.ts +36 -0
  68. package/corpus/templates/clips/actions/lib/clips-ai-prefs.ts +28 -0
  69. package/corpus/templates/clips/actions/regenerate-chapters.ts +21 -7
  70. package/corpus/templates/clips/actions/regenerate-summary.ts +26 -8
  71. package/corpus/templates/clips/actions/regenerate-title.ts +44 -7
  72. package/corpus/templates/clips/actions/update-clips-ai-prefs.ts +50 -0
  73. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +7 -2
  74. package/corpus/templates/clips/app/hooks/use-auto-title.ts +25 -10
  75. package/corpus/templates/clips/app/i18n/ar-SA.ts +28 -6
  76. package/corpus/templates/clips/app/i18n/de-DE.ts +28 -6
  77. package/corpus/templates/clips/app/i18n/en-US.ts +29 -6
  78. package/corpus/templates/clips/app/i18n/es-ES.ts +29 -6
  79. package/corpus/templates/clips/app/i18n/fr-FR.ts +30 -6
  80. package/corpus/templates/clips/app/i18n/hi-IN.ts +26 -6
  81. package/corpus/templates/clips/app/i18n/ja-JP.ts +27 -6
  82. package/corpus/templates/clips/app/i18n/ko-KR.ts +28 -6
  83. package/corpus/templates/clips/app/i18n/pt-BR.ts +28 -6
  84. package/corpus/templates/clips/app/i18n/zh-CN.ts +26 -6
  85. package/corpus/templates/clips/app/i18n/zh-TW.ts +25 -6
  86. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +73 -0
  87. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -0
  88. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -2
  89. package/corpus/templates/clips/changelog/2026-07-07-ai-tools-can-include-the-full-video-not-just-the-transcript-.md +6 -0
  90. package/corpus/templates/clips/changelog/2026-07-07-clips-desktop-now-surfaces-meetings-and-dictate-directly-in-.md +6 -0
  91. package/corpus/templates/clips/changelog/2026-07-07-meeting-notes-now-explain-exactly-how-to-start-granola-style.md +6 -0
  92. package/corpus/templates/clips/changelog/2026-07-07-meeting-start-alerts-now-include-separate-start-notes-and-jo.md +6 -0
  93. package/corpus/templates/clips/changelog/2026-07-07-recordings-start-more-predictably-and-newly-saved-clips-refr.md +6 -0
  94. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  95. package/corpus/templates/clips/desktop/src/app.tsx +435 -105
  96. package/corpus/templates/clips/desktop/src/lib/recorder.ts +27 -21
  97. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -22
  98. package/corpus/templates/clips/desktop/src/styles.css +167 -2
  99. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  100. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -7
  101. package/corpus/templates/clips/desktop/src-tauri/src/tray_meetings.rs +5 -4
  102. package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +16 -8
  103. package/corpus/templates/clips/shared/clips-ai-prefs.ts +81 -0
  104. package/corpus/templates/content/AGENTS.md +1 -0
  105. package/corpus/templates/content/actions/_database-source-utils.ts +73 -8
  106. package/corpus/templates/content/actions/_database-utils.ts +2 -1
  107. package/corpus/templates/content/actions/prepare-builder-source-review.ts +20 -1
  108. package/corpus/templates/content/actions/stage-builder-source-bulk-update.ts +663 -0
  109. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +33 -7
  110. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +53 -4
  111. package/corpus/templates/content/app/components/editor/database/settings.tsx +24 -0
  112. package/corpus/templates/content/app/hooks/use-content-database.ts +70 -1
  113. package/corpus/templates/content/app/hooks/use-document-properties.ts +63 -5
  114. package/corpus/templates/content/app/i18n/zh-TW.ts +3 -0
  115. package/corpus/templates/content/app/i18n-data.ts +31 -0
  116. package/corpus/templates/content/changelog/2026-07-01-builder-source-rows-can-be-staged-in-bulk-with-a-reviewable.md +6 -0
  117. package/corpus/templates/content/changelog/2026-07-07-builder-backed-database-fields-now-edit-like-normal-content-fields.md +6 -0
  118. package/corpus/templates/content/parity/matrix.md +24 -24
  119. package/corpus/templates/content/parity/matrix.ts +2 -0
  120. package/corpus/templates/content/shared/api.ts +59 -4
  121. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +16 -1
  122. package/corpus/templates/design/actions/add-localhost-screens.ts +1 -0
  123. package/corpus/templates/design/actions/connect-localhost.ts +26 -3
  124. package/corpus/templates/design/actions/open-visual-edit.ts +4 -37
  125. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +362 -12
  126. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +89 -14
  127. package/corpus/templates/design/app/components/design/bridge-security.ts +7 -1
  128. package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -1
  129. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -0
  130. package/corpus/templates/design/app/i18n-data.ts +41 -0
  131. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-keeps-bridge-access-when-opening-previousl.md +6 -0
  132. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-now-keeps-the-live-app-connection-authenti.md +6 -0
  133. package/corpus/templates/design/changelog/2026-07-07-local-visual-edit-uses-live-app-iframes-for-click-to-edit-pr.md +6 -0
  134. package/dist/agent/durable-background.d.ts +12 -12
  135. package/dist/agent/durable-background.js +18 -18
  136. package/dist/agent/durable-background.js.map +1 -1
  137. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  138. package/dist/agent/engine/ai-sdk-engine.js +8 -2
  139. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  140. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  141. package/dist/agent/engine/anthropic-engine.js +11 -1
  142. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  143. package/dist/agent/engine/index.d.ts +1 -1
  144. package/dist/agent/engine/index.d.ts.map +1 -1
  145. package/dist/agent/engine/index.js +1 -1
  146. package/dist/agent/engine/index.js.map +1 -1
  147. package/dist/agent/engine/registry.d.ts +14 -0
  148. package/dist/agent/engine/registry.d.ts.map +1 -1
  149. package/dist/agent/engine/registry.js +56 -2
  150. package/dist/agent/engine/registry.js.map +1 -1
  151. package/dist/agent/production-agent.d.ts +2 -2
  152. package/dist/agent/production-agent.d.ts.map +1 -1
  153. package/dist/agent/production-agent.js +18 -11
  154. package/dist/agent/production-agent.js.map +1 -1
  155. package/dist/agent/run-manager.d.ts +7 -1
  156. package/dist/agent/run-manager.d.ts.map +1 -1
  157. package/dist/agent/run-manager.js +6 -1
  158. package/dist/agent/run-manager.js.map +1 -1
  159. package/dist/agent/run-store.d.ts +1 -1
  160. package/dist/agent/run-store.d.ts.map +1 -1
  161. package/dist/agent/run-store.js +15 -10
  162. package/dist/agent/run-store.js.map +1 -1
  163. package/dist/cli/create.d.ts.map +1 -1
  164. package/dist/cli/create.js +31 -0
  165. package/dist/cli/create.js.map +1 -1
  166. package/dist/cli/design-connect.d.ts.map +1 -1
  167. package/dist/cli/design-connect.js +197 -1
  168. package/dist/cli/design-connect.js.map +1 -1
  169. package/dist/cli/index.js +76 -9
  170. package/dist/cli/index.js.map +1 -1
  171. package/dist/cli/workspacify.d.ts.map +1 -1
  172. package/dist/cli/workspacify.js +30 -0
  173. package/dist/cli/workspacify.js.map +1 -1
  174. package/dist/client/KeepTabOpenNotice.d.ts +9 -10
  175. package/dist/client/KeepTabOpenNotice.d.ts.map +1 -1
  176. package/dist/client/KeepTabOpenNotice.js +8 -4
  177. package/dist/client/KeepTabOpenNotice.js.map +1 -1
  178. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  179. package/dist/client/RunStuckBanner.js +14 -13
  180. package/dist/client/RunStuckBanner.js.map +1 -1
  181. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  182. package/dist/client/agent-chat-adapter.js +25 -17
  183. package/dist/client/agent-chat-adapter.js.map +1 -1
  184. package/dist/client/history/VersionHistoryPanel.d.ts +1 -1
  185. package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -1
  186. package/dist/client/history/VersionHistoryPanel.js +12 -5
  187. package/dist/client/history/VersionHistoryPanel.js.map +1 -1
  188. package/dist/client/history/index.d.ts +1 -1
  189. package/dist/client/history/index.d.ts.map +1 -1
  190. package/dist/client/history/index.js +1 -1
  191. package/dist/client/history/index.js.map +1 -1
  192. package/dist/client/history/use-history.d.ts +12 -0
  193. package/dist/client/history/use-history.d.ts.map +1 -1
  194. package/dist/client/history/use-history.js +6 -0
  195. package/dist/client/history/use-history.js.map +1 -1
  196. package/dist/client/index.d.ts +2 -2
  197. package/dist/client/index.d.ts.map +1 -1
  198. package/dist/client/index.js +2 -2
  199. package/dist/client/index.js.map +1 -1
  200. package/dist/client/review/index.d.ts +1 -1
  201. package/dist/client/review/index.d.ts.map +1 -1
  202. package/dist/client/review/index.js +1 -1
  203. package/dist/client/review/index.js.map +1 -1
  204. package/dist/client/review/use-review.d.ts +23 -0
  205. package/dist/client/review/use-review.d.ts.map +1 -1
  206. package/dist/client/review/use-review.js +8 -0
  207. package/dist/client/review/use-review.js.map +1 -1
  208. package/dist/client/use-run-stuck-detection.d.ts +1 -1
  209. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  210. package/dist/client/use-run-stuck-detection.js +6 -4
  211. package/dist/client/use-run-stuck-detection.js.map +1 -1
  212. package/dist/collab/struct-routes.d.ts +1 -1
  213. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  214. package/dist/history/actions/create-resource-version.js +7 -4
  215. package/dist/history/actions/create-resource-version.js.map +1 -1
  216. package/dist/history/index.d.ts +1 -1
  217. package/dist/history/index.d.ts.map +1 -1
  218. package/dist/history/index.js +1 -1
  219. package/dist/history/index.js.map +1 -1
  220. package/dist/history/registry.d.ts.map +1 -1
  221. package/dist/history/registry.js +14 -12
  222. package/dist/history/registry.js.map +1 -1
  223. package/dist/history/store.d.ts.map +1 -1
  224. package/dist/history/store.js +74 -49
  225. package/dist/history/store.js.map +1 -1
  226. package/dist/notifications/routes.d.ts +1 -1
  227. package/dist/resources/handlers.d.ts +3 -3
  228. package/dist/review/actions/consume-review-feedback.d.ts +1 -0
  229. package/dist/review/actions/consume-review-feedback.js +8 -2
  230. package/dist/review/actions/consume-review-feedback.js.map +1 -1
  231. package/dist/review/actions/delete-review-comment.d.ts +2 -1
  232. package/dist/review/actions/delete-review-comment.js +11 -3
  233. package/dist/review/actions/delete-review-comment.js.map +1 -1
  234. package/dist/review/actions/resolve-review-thread.d.ts +2 -1
  235. package/dist/review/actions/resolve-review-thread.js +5 -2
  236. package/dist/review/actions/resolve-review-thread.js.map +1 -1
  237. package/dist/review/index.d.ts +1 -1
  238. package/dist/review/index.d.ts.map +1 -1
  239. package/dist/review/index.js +1 -1
  240. package/dist/review/index.js.map +1 -1
  241. package/dist/review/registry.d.ts.map +1 -1
  242. package/dist/review/registry.js +14 -12
  243. package/dist/review/registry.js.map +1 -1
  244. package/dist/review/store.d.ts +3 -3
  245. package/dist/review/store.d.ts.map +1 -1
  246. package/dist/review/store.js +7 -4
  247. package/dist/review/store.js.map +1 -1
  248. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  249. package/dist/server/agent-chat-plugin.js +5 -0
  250. package/dist/server/agent-chat-plugin.js.map +1 -1
  251. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  252. package/dist/server/credential-provider.d.ts +2 -0
  253. package/dist/server/credential-provider.d.ts.map +1 -1
  254. package/dist/server/credential-provider.js +13 -3
  255. package/dist/server/credential-provider.js.map +1 -1
  256. package/package.json +1 -1
@@ -33,6 +33,16 @@ const BRIDGE_OPERATIONS = [
33
33
 
34
34
  type BridgeOperation = (typeof BRIDGE_OPERATIONS)[number];
35
35
 
36
+ const SERVER_REGISTRATION_BRIDGE_OPERATIONS = new Set<BridgeOperation>([
37
+ "select",
38
+ "resolveNodeToFile",
39
+ "readFile",
40
+ "applyEdit",
41
+ "writeFile",
42
+ "captureSnapshot",
43
+ "captureState",
44
+ ]);
45
+
36
46
  /** Additive manifest capability flags advertised alongside the operation list. */
37
47
  const MANIFEST_CAPABILITIES = {
38
48
  listFiles: true,
@@ -507,6 +517,47 @@ function sendJson(
507
517
  res.end(`${JSON.stringify(body, null, 2)}\n`);
508
518
  }
509
519
 
520
+ function sendText(
521
+ res: ServerResponse,
522
+ statusCode: number,
523
+ body: string,
524
+ contentType: string,
525
+ ) {
526
+ res.writeHead(statusCode, {
527
+ "content-type": contentType,
528
+ "access-control-allow-origin": "*",
529
+ "access-control-allow-methods": "GET, POST, OPTIONS",
530
+ "access-control-allow-headers": "content-type, x-bridge-token",
531
+ "access-control-allow-private-network": "true",
532
+ });
533
+ res.end(body);
534
+ }
535
+
536
+ function sendBytes(
537
+ res: ServerResponse,
538
+ statusCode: number,
539
+ body: Buffer,
540
+ headers: Headers,
541
+ ) {
542
+ const responseHeaders: Record<string, string> = {
543
+ "access-control-allow-origin": "*",
544
+ "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
545
+ "access-control-allow-headers": "content-type, x-bridge-token",
546
+ "access-control-allow-private-network": "true",
547
+ };
548
+ for (const name of [
549
+ "content-type",
550
+ "cache-control",
551
+ "etag",
552
+ "last-modified",
553
+ ]) {
554
+ const value = headers.get(name);
555
+ if (value) responseHeaders[name] = value;
556
+ }
557
+ res.writeHead(statusCode, responseHeaders);
558
+ res.end(body);
559
+ }
560
+
510
561
  function sameOrigin(a: string, b: string): boolean {
511
562
  try {
512
563
  return new URL(a).origin === new URL(b).origin;
@@ -531,6 +582,23 @@ function resolvePreviewSnapshotUrl(
531
582
  return parsed.toString();
532
583
  }
533
584
 
585
+ function resolvePreviewProxyUrl(
586
+ devServerUrl: string,
587
+ requestUrl: string | undefined,
588
+ ): string {
589
+ const base = normalizeHttpUrl(devServerUrl);
590
+ const parsedRequest = new URL(requestUrl ?? "/", base);
591
+ const parsed = new URL(
592
+ `${parsedRequest.pathname}${parsedRequest.search}`,
593
+ `${base}/`,
594
+ );
595
+ parsed.hash = "";
596
+ if (!sameOrigin(parsed.toString(), base)) {
597
+ throw new Error("Proxy URL must stay on the connected dev server.");
598
+ }
599
+ return parsed.toString();
600
+ }
601
+
534
602
  async function fetchPreviewSnapshot(
535
603
  devServerUrl: string,
536
604
  targetUrl: string,
@@ -579,6 +647,79 @@ async function fetchPreviewSnapshot(
579
647
  }
580
648
  }
581
649
 
650
+ async function fetchPreviewProxyResource(
651
+ devServerUrl: string,
652
+ targetUrl: string,
653
+ redirects = 0,
654
+ ): Promise<{
655
+ url: string;
656
+ status: number;
657
+ headers: Headers;
658
+ body: Buffer;
659
+ }> {
660
+ if (redirects > 5) {
661
+ throw new Error("Too many redirects while proxying preview resource.");
662
+ }
663
+ const controller = new AbortController();
664
+ const timeout = setTimeout(() => controller.abort(), 10_000);
665
+ try {
666
+ const response = await fetch(targetUrl, {
667
+ method: "GET",
668
+ redirect: "manual",
669
+ signal: controller.signal,
670
+ });
671
+ const location = response.headers.get("location");
672
+ if (location && response.status >= 300 && response.status < 400) {
673
+ const redirected = new URL(location, targetUrl);
674
+ redirected.hash = "";
675
+ if (!sameOrigin(redirected.toString(), devServerUrl)) {
676
+ throw new Error("Proxy redirect left the connected dev server.");
677
+ }
678
+ return fetchPreviewProxyResource(
679
+ devServerUrl,
680
+ redirected.toString(),
681
+ redirects + 1,
682
+ );
683
+ }
684
+ return {
685
+ url: response.url || targetUrl,
686
+ status: response.status,
687
+ headers: response.headers,
688
+ body: Buffer.from(await response.arrayBuffer()),
689
+ };
690
+ } finally {
691
+ clearTimeout(timeout);
692
+ }
693
+ }
694
+
695
+ function addLiveEditBaseHref(html: string, href: string): string {
696
+ const escapedHref = href.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
697
+ const baseTag = `<base href="${escapedHref}">`;
698
+ if (/<base\b/i.test(html)) return html;
699
+ if (/<head\b[^>]*>/i.test(html)) {
700
+ return html.replace(/<head\b[^>]*>/i, (match) => `${match}${baseTag}`);
701
+ }
702
+ if (/<html\b[^>]*>/i.test(html)) {
703
+ return html.replace(
704
+ /<html\b[^>]*>/i,
705
+ (match) => `${match}<head>${baseTag}</head>`,
706
+ );
707
+ }
708
+ 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>`;
709
+ }
710
+
711
+ function injectLiveEditBridge(html: string, targetUrl: string, script: string) {
712
+ const withBase = addLiveEditBaseHref(html, targetUrl);
713
+ if (!script) return withBase;
714
+ if (withBase.includes("</body>")) {
715
+ return withBase.replace("</body>", `${script}</body>`);
716
+ }
717
+ if (withBase.includes("</html>")) {
718
+ return withBase.replace("</html>", `${script}</html>`);
719
+ }
720
+ return `${withBase}${script}`;
721
+ }
722
+
582
723
  /** Read the full request body as a UTF-8 string. */
583
724
  async function readRequestBody(req: IncomingMessage): Promise<string> {
584
725
  return new Promise<string>((resolve, reject) => {
@@ -1046,6 +1187,7 @@ export async function startDesignConnectBridge(
1046
1187
  // an unauthenticated caller cannot read it. The server-side grant action
1047
1188
  // obtains it out-of-band (via the exported bridge reference).
1048
1189
  const bridgeToken = crypto.randomBytes(32).toString("hex");
1190
+ let liveEditBridgeScript = "";
1049
1191
 
1050
1192
  const server = http.createServer(
1051
1193
  (req: IncomingMessage, res: ServerResponse) => {
@@ -1077,6 +1219,97 @@ export async function startDesignConnectBridge(
1077
1219
  sendJson(res, 200, { ok: true, source: manifest.source });
1078
1220
  return;
1079
1221
  }
1222
+ if (pathname === "/live-edit-bridge") {
1223
+ if (req.method !== "POST") {
1224
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
1225
+ return;
1226
+ }
1227
+ const tokenHeader = req.headers["x-bridge-token"];
1228
+ const providedToken =
1229
+ typeof tokenHeader === "string" ? tokenHeader : "";
1230
+ let tokenValid = false;
1231
+ try {
1232
+ tokenValid =
1233
+ providedToken.length === bridgeToken.length &&
1234
+ crypto.timingSafeEqual(
1235
+ Buffer.from(providedToken, "utf8"),
1236
+ Buffer.from(bridgeToken, "utf8"),
1237
+ );
1238
+ } catch {
1239
+ tokenValid = false;
1240
+ }
1241
+ if (!tokenValid) {
1242
+ sendJson(res, 401, {
1243
+ ok: false,
1244
+ error: "invalid or missing bridge token",
1245
+ });
1246
+ return;
1247
+ }
1248
+ void (async () => {
1249
+ try {
1250
+ const raw = await readRequestBody(req);
1251
+ const body = JSON.parse(raw) as Record<string, unknown>;
1252
+ const script =
1253
+ typeof body["script"] === "string" ? body["script"] : "";
1254
+ if (!script.includes("agent-native:editor-chrome-ready")) {
1255
+ sendJson(res, 400, {
1256
+ ok: false,
1257
+ error: "script must install the Agent Native editor bridge",
1258
+ });
1259
+ return;
1260
+ }
1261
+ liveEditBridgeScript = script;
1262
+ sendJson(res, 200, { ok: true });
1263
+ } catch (err: unknown) {
1264
+ sendJson(res, 400, {
1265
+ ok: false,
1266
+ error: err instanceof Error ? err.message : String(err),
1267
+ });
1268
+ }
1269
+ })();
1270
+ return;
1271
+ }
1272
+ if (pathname === "/live-edit") {
1273
+ if (req.method !== "GET") {
1274
+ sendJson(res, 405, { ok: false, error: "method not allowed" });
1275
+ return;
1276
+ }
1277
+ void (async () => {
1278
+ try {
1279
+ const requestUrl = new URL(req.url ?? "/", manifest.bridgeUrl);
1280
+ const targetUrl = resolvePreviewSnapshotUrl(
1281
+ manifest.devServerUrl,
1282
+ requestUrl.searchParams.get("url") ??
1283
+ requestUrl.searchParams.get("path"),
1284
+ );
1285
+ const snapshot = await fetchPreviewSnapshot(
1286
+ manifest.devServerUrl,
1287
+ targetUrl,
1288
+ );
1289
+ const includeEditorBridge =
1290
+ requestUrl.searchParams.get("bridge") !== "0";
1291
+ const html = injectLiveEditBridge(
1292
+ snapshot.html,
1293
+ new URL("/", manifest.bridgeUrl).toString(),
1294
+ includeEditorBridge ? liveEditBridgeScript : "",
1295
+ );
1296
+ sendText(
1297
+ res,
1298
+ snapshot.status >= 400 ? snapshot.status : 200,
1299
+ html,
1300
+ snapshot.contentType.includes("html")
1301
+ ? snapshot.contentType
1302
+ : "text/html; charset=utf-8",
1303
+ );
1304
+ } catch (err: unknown) {
1305
+ sendJson(res, 400, {
1306
+ ok: false,
1307
+ error: err instanceof Error ? err.message : String(err),
1308
+ });
1309
+ }
1310
+ })();
1311
+ return;
1312
+ }
1080
1313
  if (pathname === "/snapshot") {
1081
1314
  if (req.method !== "GET") {
1082
1315
  sendJson(res, 405, { ok: false, error: "method not allowed" });
@@ -1356,6 +1589,33 @@ export async function startDesignConnectBridge(
1356
1589
  return;
1357
1590
  }
1358
1591
 
1592
+ if (req.method === "GET" || req.method === "HEAD") {
1593
+ void (async () => {
1594
+ try {
1595
+ const targetUrl = resolvePreviewProxyUrl(
1596
+ manifest.devServerUrl,
1597
+ req.url,
1598
+ );
1599
+ const proxied = await fetchPreviewProxyResource(
1600
+ manifest.devServerUrl,
1601
+ targetUrl,
1602
+ );
1603
+ sendBytes(
1604
+ res,
1605
+ proxied.status >= 400 ? proxied.status : 200,
1606
+ req.method === "HEAD" ? Buffer.alloc(0) : proxied.body,
1607
+ proxied.headers,
1608
+ );
1609
+ } catch (err: unknown) {
1610
+ sendJson(res, 400, {
1611
+ ok: false,
1612
+ error: err instanceof Error ? err.message : String(err),
1613
+ });
1614
+ }
1615
+ })();
1616
+ return;
1617
+ }
1618
+
1359
1619
  sendJson(res, 404, { ok: false, error: "not found" });
1360
1620
  },
1361
1621
  );
@@ -1432,7 +1692,9 @@ export async function registerConnectionWithServer(
1432
1692
  devServerUrl: manifest.devServerUrl,
1433
1693
  bridgeUrl: manifest.bridgeUrl,
1434
1694
  rootPath: manifest.rootPath,
1435
- capabilities: manifest.capabilities,
1695
+ capabilities: manifest.capabilities.filter((capability) =>
1696
+ SERVER_REGISTRATION_BRIDGE_OPERATIONS.has(capability.operation),
1697
+ ),
1436
1698
  routeManifest: {
1437
1699
  version: 1 as const,
1438
1700
  sourceType: "localhost" as const,
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { execSync, spawn } from "child_process";
4
4
  import fs from "fs";
5
+ import { createRequire } from "module";
5
6
  import path from "path";
6
7
  import { fileURLToPath } from "url";
7
8
 
@@ -253,11 +254,20 @@ function handleScaffoldImportError(err: any): never {
253
254
  throw err;
254
255
  }
255
256
 
257
+ function findBinUpwards(binName: string): string | undefined {
258
+ let dir = process.cwd();
259
+ for (let i = 0; i < 20; i++) {
260
+ const candidate = path.join(dir, "node_modules", ".bin", binName);
261
+ if (fs.existsSync(candidate)) return candidate;
262
+ const parent = path.dirname(dir);
263
+ if (parent === dir) break;
264
+ dir = parent;
265
+ }
266
+ return undefined;
267
+ }
268
+
256
269
  function findViteBin(): string {
257
- // Look for vite in node_modules/.bin
258
- const localVite = path.resolve("node_modules/.bin/vite");
259
- if (fs.existsSync(localVite)) return localVite;
260
- return "vite"; // fallback to PATH
270
+ return findBinUpwards("vite") ?? "vite";
261
271
  }
262
272
 
263
273
  function findTsxBin(): string {
@@ -277,9 +287,7 @@ function findTypeScriptCompilerBin(): string {
277
287
  }
278
288
 
279
289
  function findReactRouterBin(): string {
280
- const localBin = path.resolve("node_modules/.bin/react-router");
281
- if (fs.existsSync(localBin)) return localBin;
282
- return "react-router";
290
+ return findBinUpwards("react-router") ?? "react-router";
283
291
  }
284
292
 
285
293
  /** Check if the project uses React Router framework mode (has react-router.config.ts) */
@@ -290,6 +298,73 @@ function isReactRouterFramework(): boolean {
290
298
  );
291
299
  }
292
300
 
301
+ function canResolveFromProject(specifier: string): boolean {
302
+ try {
303
+ const requireFromProject = createRequire(path.resolve("package.json"));
304
+ requireFromProject.resolve(specifier);
305
+ return true;
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+
311
+ function projectUsesFsRoutes(): boolean {
312
+ for (const file of [
313
+ path.resolve("app/routes.ts"),
314
+ path.resolve("app/routes.tsx"),
315
+ path.resolve("routes.ts"),
316
+ path.resolve("routes.tsx"),
317
+ ]) {
318
+ try {
319
+ if (
320
+ fs.existsSync(file) &&
321
+ fs.readFileSync(file, "utf-8").includes("@react-router/fs-routes")
322
+ ) {
323
+ return true;
324
+ }
325
+ } catch {}
326
+ }
327
+ return false;
328
+ }
329
+
330
+ function validateReactRouterBuildDependencies(): void {
331
+ const required: Array<[packageName: string, specifier: string]> = [
332
+ ["react-router", "react-router"],
333
+ ["@react-router/dev", "@react-router/dev/vite"],
334
+ ["vite", "vite"],
335
+ ];
336
+ if (projectUsesFsRoutes()) {
337
+ required.push(["@react-router/fs-routes", "@react-router/fs-routes"]);
338
+ }
339
+
340
+ const missing = required
341
+ .filter(([, specifier]) => !canResolveFromProject(specifier))
342
+ .map(([packageName]) => packageName);
343
+
344
+ if (missing.length === 0) return;
345
+
346
+ const packageManager = fs.existsSync(path.resolve("pnpm-lock.yaml"))
347
+ ? "pnpm"
348
+ : fs.existsSync(path.resolve("yarn.lock"))
349
+ ? "yarn"
350
+ : "npm";
351
+ const installCommand =
352
+ packageManager === "pnpm"
353
+ ? `pnpm add ${missing.join(" ")}`
354
+ : packageManager === "yarn"
355
+ ? `yarn add ${missing.join(" ")}`
356
+ : `npm install ${missing.join(" ")}`;
357
+
358
+ console.error(
359
+ [
360
+ "React Router framework mode requires build packages that are not installed from this app root.",
361
+ `Missing: ${missing.join(", ")}`,
362
+ `Install them with: ${installCommand}`,
363
+ ].join("\n"),
364
+ );
365
+ process.exit(1);
366
+ }
367
+
293
368
  function isWorkspaceRoot(): boolean {
294
369
  const pkgPath = path.resolve("package.json");
295
370
  if (!fs.existsSync(pkgPath)) return false;
@@ -488,6 +563,7 @@ switch (command) {
488
563
  // continuation only runs on success.
489
564
  (async () => {
490
565
  if (isReactRouterFramework()) {
566
+ validateReactRouterBuildDependencies();
491
567
  const rr = findReactRouterBin();
492
568
  console.log("Building (React Router framework mode)...");
493
569
  await runBuildStep(rr, ["build"], { label: "react-router-build" });
@@ -592,6 +668,7 @@ switch (command) {
592
668
  // Run TypeScript type checking
593
669
  // React Router framework mode generates route types first
594
670
  if (isReactRouterFramework()) {
671
+ validateReactRouterBuildDependencies();
595
672
  const rr = findReactRouterBin();
596
673
  try {
597
674
  execSync(`${rr} typegen`, { stdio: "inherit" });
@@ -24,6 +24,12 @@ import fs from "fs";
24
24
  import path from "path";
25
25
 
26
26
  const POSTGRES_DEPENDENCY_VERSION = "^3.4.9";
27
+ const REACT_ROUTER_BUILD_DEPENDENCIES = [
28
+ "@react-router/dev",
29
+ "@react-router/fs-routes",
30
+ "react-router",
31
+ "vite",
32
+ ] as const;
27
33
 
28
34
  export interface WorkspacifyOptions {
29
35
  /** Target app directory (already populated with the copied template) */
@@ -87,6 +93,7 @@ export function workspacifyApp(opts: WorkspacifyOptions): void {
87
93
  // Add the runtime package to workspace apps so production bundles do
88
94
  // not fail only after a hosted Postgres database is configured.
89
95
  pkg.dependencies.postgres ??= POSTGRES_DEPENDENCY_VERSION;
96
+ ensureReactRouterBuildDependencies(pkg);
90
97
  // pnpm build-script approvals belong at the workspace root. Leaving the
91
98
  // template's per-app setting in place makes pnpm warn on every install.
92
99
  if (pkg.pnpm && typeof pkg.pnpm === "object") {
@@ -156,6 +163,33 @@ export function workspacifyApp(opts: WorkspacifyOptions): void {
156
163
  }
157
164
  }
158
165
 
166
+ function ensureReactRouterBuildDependencies(pkg: Record<string, any>): void {
167
+ const allDeps = {
168
+ ...pkg.dependencies,
169
+ ...pkg.devDependencies,
170
+ ...pkg.peerDependencies,
171
+ };
172
+ if (
173
+ !allDeps["@react-router/dev"] &&
174
+ !allDeps["react-router"] &&
175
+ !allDeps["@react-router/fs-routes"]
176
+ ) {
177
+ return;
178
+ }
179
+
180
+ pkg.dependencies = pkg.dependencies ?? {};
181
+ for (const key of REACT_ROUTER_BUILD_DEPENDENCIES) {
182
+ const existing =
183
+ pkg.dependencies[key] ??
184
+ pkg.devDependencies?.[key] ??
185
+ pkg.peerDependencies?.[key];
186
+ if (!existing) continue;
187
+ pkg.dependencies[key] = existing;
188
+ delete pkg.devDependencies?.[key];
189
+ delete pkg.peerDependencies?.[key];
190
+ }
191
+ }
192
+
159
193
  function writeInheritedChatPlugin(
160
194
  appDir: string,
161
195
  workspaceCoreName: string,
@@ -9,23 +9,22 @@ import { cn } from "./utils.js";
9
9
  export const DEFAULT_KEEP_TAB_OPEN_AFTER_MS = 30_000;
10
10
 
11
11
  /**
12
- * Subtle, non-blocking notice that a long-running FOREGROUND agent turn
13
- * depends on this tab staying open.
12
+ * Subtle, non-blocking notice that a long-running client-continued foreground
13
+ * agent turn depends on this tab staying open.
14
14
  *
15
15
  * On hosted deployments a foreground turn runs in ~40s chunks and the CLIENT
16
- * re-POSTs `auto_continue` to start each next chunk — close the tab
17
- * mid-turn and the work silently stops at the next chunk boundary. Only
18
- * server-driven runs (`dispatchMode` starting with "background": the durable
19
- * background worker, or a server self-chained continuation) survive a closed
20
- * tab, and for those this notice never shows.
16
+ * may need to re-POST `auto_continue` to start each next chunk. Server-owned
17
+ * runs (`dispatchMode` starting with "background" or equal to
18
+ * "foreground-self-chain") survive a closed tab, and for those this notice
19
+ * never shows.
21
20
  *
22
21
  * Visibility rules (show only while the condition is true — no new
23
22
  * always-visible chrome):
24
- * - a foreground-dispatched run for this thread has been continuously
23
+ * - a client-continued foreground run for this thread has been continuously
25
24
  * running for `showAfterMs` (default 30s — approaching the hosted ~40s
26
25
  * chunk boundary where the client-driven continuation starts), and
27
- * - the run is NOT background-dispatched (hidden immediately when the
28
- * server owns recovery), and
26
+ * - the run is not server-continued (hidden immediately when the server owns
27
+ * recovery), and
29
28
  * - this is a production client bundle (`hosted` auto-detect): local dev
30
29
  * runs a turn unbounded in a single chunk that survives a closed tab,
31
30
  * so the notice would be wrong there.
@@ -78,6 +77,13 @@ function isDevClientBundle(): boolean {
78
77
  }
79
78
  }
80
79
 
80
+ function isServerContinuedDispatch(dispatchMode: string | null): boolean {
81
+ return (
82
+ dispatchMode === "foreground-self-chain" ||
83
+ dispatchMode?.startsWith("background") === true
84
+ );
85
+ }
86
+
81
87
  export function KeepTabOpenNotice({
82
88
  threadId,
83
89
  enabled = true,
@@ -93,10 +99,9 @@ export function KeepTabOpenNotice({
93
99
  apiUrl,
94
100
  pollIntervalMs: NOTICE_POLL_INTERVAL_MS,
95
101
  });
96
- const isBackgroundDispatch =
97
- state.dispatchMode?.startsWith("background") === true;
102
+ const isServerContinued = isServerContinuedDispatch(state.dispatchMode);
98
103
  const foregroundRunning =
99
- state.status === "running" && Boolean(state.runId) && !isBackgroundDispatch;
104
+ state.status === "running" && Boolean(state.runId) && !isServerContinued;
100
105
 
101
106
  const [visible, setVisible] = useState(false);
102
107
  const runningSinceRef = useRef<number | null>(null);
@@ -109,7 +114,7 @@ export function KeepTabOpenNotice({
109
114
  }, [threadId]);
110
115
 
111
116
  useEffect(() => {
112
- if (!effectiveHosted || isBackgroundDispatch) {
117
+ if (!effectiveHosted || isServerContinued) {
113
118
  // Server-owned turn (or non-hosted client): the tab is not load-bearing.
114
119
  // Hide immediately and reset — no linger.
115
120
  runningSinceRef.current = null;
@@ -143,7 +148,7 @@ export function KeepTabOpenNotice({
143
148
  }, [
144
149
  effectiveHosted,
145
150
  foregroundRunning,
146
- isBackgroundDispatch,
151
+ isServerContinued,
147
152
  showAfterMs,
148
153
  visible,
149
154
  ]);
@@ -175,15 +175,16 @@ export function RunStuckBanner({
175
175
  }
176
176
  const ownerId = autoRetryOwnerId ?? generatedOwnerIdRef.current;
177
177
  const backgroundWorkerStillAlive = isFreshBackgroundWorker(state);
178
- // Background-dispatched runs are recovered by the SERVER (chained
179
- // continuation chunks + lost-handoff sweep); an automatic client abort
180
- // would kill a live server-chained run. Auto-retry is therefore disabled
181
- // unconditionally for ANY background dispatch modenot just a
182
- // fresh-heartbeat worker — and the localStorage/Web-Locks auto-retry claim
183
- // below is never taken for these runs (the adapter's follow loop is
184
- // read-only, so multiple tabs need no retry dedup). Only the manual banner
185
- // remains, on the wider background threshold from useRunStuckDetection.
186
- const isBackgroundDispatch =
178
+ // Server-continued runs are recovered by the SERVER (chained continuation
179
+ // chunks + lost-handoff sweep); an automatic client abort would kill a live
180
+ // server-chained run. Auto-retry is therefore disabled unconditionally for
181
+ // these modes not just a fresh-heartbeat worker and the
182
+ // localStorage/Web-Locks auto-retry claim below is never taken for them (the
183
+ // adapter's follow loop is read-only, so multiple tabs need no retry dedup).
184
+ // Only the manual banner remains, on the wider server-owned threshold from
185
+ // useRunStuckDetection.
186
+ const isServerContinuedDispatch =
187
+ state.dispatchMode === "foreground-self-chain" ||
187
188
  state.dispatchMode?.startsWith("background") === true;
188
189
 
189
190
  const lastReportedRef = useRef<{
@@ -224,9 +225,9 @@ export function RunStuckBanner({
224
225
  useEffect(() => {
225
226
  if (
226
227
  !autoRetry ||
227
- // Server owns recovery for background dispatch modes — never auto-abort
228
- // (see comment on isBackgroundDispatch above).
229
- isBackgroundDispatch ||
228
+ // Server owns recovery for these dispatch modes — never auto-abort (see
229
+ // comment on isServerContinuedDispatch above).
230
+ isServerContinuedDispatch ||
230
231
  backgroundWorkerStillAlive ||
231
232
  !state.isStuck ||
232
233
  !state.runId ||
@@ -263,7 +264,7 @@ export function RunStuckBanner({
263
264
  autoRetry,
264
265
  backgroundWorkerStillAlive,
265
266
  busy,
266
- isBackgroundDispatch,
267
+ isServerContinuedDispatch,
267
268
  onRetry,
268
269
  ownerId,
269
270
  state.isStuck,