@agent-native/core 0.98.1 → 0.98.3

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 (165) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +25 -1
  4. package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +1 -1
  5. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +25 -1
  6. package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +1 -1
  7. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +25 -1
  8. package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +1 -1
  9. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +25 -1
  10. package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +1 -1
  11. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +25 -1
  12. package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +1 -1
  13. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +25 -1
  14. package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +1 -1
  15. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +25 -1
  16. package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +1 -1
  17. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +25 -1
  18. package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +1 -1
  19. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +25 -1
  20. package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +1 -1
  21. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +25 -1
  22. package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +1 -1
  23. package/corpus/core/docs/content/pr-visual-recap.mdx +50 -1
  24. package/corpus/core/docs/content/template-plan.mdx +1 -1
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/agent/production-agent.ts +2 -2
  27. package/corpus/core/src/cli/plan-local.ts +43 -11
  28. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -9
  29. package/corpus/core/src/cli/recap.ts +356 -4
  30. package/corpus/core/src/cli/skills-content/local-files.ts +17 -12
  31. package/corpus/core/src/client/analytics.ts +96 -14
  32. package/corpus/core/src/client/index.ts +7 -0
  33. package/corpus/core/src/client/session-replay.ts +10 -0
  34. package/corpus/core/src/client/settings/DemoModeSection.tsx +10 -34
  35. package/corpus/core/src/client/settings/SettingsPanel.tsx +31 -211
  36. package/corpus/core/src/client/settings/agent-settings-search.ts +219 -0
  37. package/corpus/core/src/client/settings/index.ts +4 -0
  38. package/corpus/core/src/client/url-scrub.ts +13 -0
  39. package/corpus/core/src/client/use-demo-mode-status.ts +44 -0
  40. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
  41. package/corpus/core/src/demo/config.ts +6 -6
  42. package/corpus/core/src/demo/redact.ts +18 -76
  43. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +21 -4
  44. package/corpus/templates/analytics/AGENTS.md +8 -0
  45. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -4
  46. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +429 -244
  47. package/corpus/templates/analytics/app/components/layout/command-palette-search.ts +93 -0
  48. package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +2 -1
  49. package/corpus/templates/analytics/app/lib/demo-chart-trend.ts +169 -0
  50. package/corpus/templates/analytics/app/pages/Settings.tsx +8 -45
  51. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +205 -70
  52. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +32 -16
  53. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +126 -0
  54. package/corpus/templates/analytics/changelog/2026-07-12-command-menu-loading-placeholders-now-stay-below-available-r.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-07-12-command-menu-search-now-prioritizes-the-best-match.md +6 -0
  56. package/corpus/templates/analytics/changelog/2026-07-12-daily-dashboard-emails-reliably-include-the-complete-dashboa.md +6 -0
  57. package/corpus/templates/analytics/changelog/2026-07-12-demo-charts-preserve-source-movement.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-07-12-demo-mode-charts-trend-upward.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-07-12-replay-storage-settings-stay-readable-at-narrow-widths.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-12-session-replays-preserve-recorded-styling-and-keep-malformed.md +6 -0
  61. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +2 -0
  62. package/corpus/templates/analytics/server/lib/dashboard-report.ts +17 -33
  63. package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
  64. package/corpus/templates/clips/changelog/2026-07-11-clips-no-longer-stay-stuck-on-preparing-clip-when-they-are-r.md +6 -0
  65. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +2 -2
  66. package/corpus/templates/content/changelog/2026-07-12-the-left-sidebar-stays-fixed-horizontally-while-scrolling-on.md +6 -0
  67. package/corpus/templates/plan/.agents/skills/visual-plan/references/local-files.md +17 -12
  68. package/corpus/templates/plan/.agents/skills/visual-recap/references/local-files.md +17 -12
  69. package/corpus/templates/plan/actions/validate-local-plan-source.ts +4 -4
  70. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +2 -1
  71. package/corpus/templates/plan/app/lib/plan-local-bridge.ts +41 -0
  72. package/corpus/templates/plan/app/lib/plan-tracking.ts +3 -0
  73. package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -9
  74. package/corpus/templates/plan/app/root.tsx +13 -3
  75. package/corpus/templates/plan/changelog/2026-07-12-local-plan-source-now-stays-on-device.md +6 -0
  76. package/dist/agent/production-agent.js +2 -2
  77. package/dist/agent/production-agent.js.map +1 -1
  78. package/dist/cli/plan-local.d.ts +6 -6
  79. package/dist/cli/plan-local.d.ts.map +1 -1
  80. package/dist/cli/plan-local.js +36 -7
  81. package/dist/cli/plan-local.js.map +1 -1
  82. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -8
  83. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  84. package/dist/cli/pr-visual-recap-workflow.js +2 -8
  85. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  86. package/dist/cli/recap.d.ts +22 -1
  87. package/dist/cli/recap.d.ts.map +1 -1
  88. package/dist/cli/recap.js +286 -2
  89. package/dist/cli/recap.js.map +1 -1
  90. package/dist/cli/skills-content/local-files.d.ts +1 -1
  91. package/dist/cli/skills-content/local-files.d.ts.map +1 -1
  92. package/dist/cli/skills-content/local-files.js +17 -12
  93. package/dist/cli/skills-content/local-files.js.map +1 -1
  94. package/dist/client/analytics.d.ts +8 -0
  95. package/dist/client/analytics.d.ts.map +1 -1
  96. package/dist/client/analytics.js +77 -13
  97. package/dist/client/analytics.js.map +1 -1
  98. package/dist/client/index.d.ts +3 -2
  99. package/dist/client/index.d.ts.map +1 -1
  100. package/dist/client/index.js +3 -2
  101. package/dist/client/index.js.map +1 -1
  102. package/dist/client/session-replay.d.ts +2 -0
  103. package/dist/client/session-replay.d.ts.map +1 -1
  104. package/dist/client/session-replay.js +7 -0
  105. package/dist/client/session-replay.js.map +1 -1
  106. package/dist/client/settings/DemoModeSection.d.ts +3 -3
  107. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  108. package/dist/client/settings/DemoModeSection.js +8 -27
  109. package/dist/client/settings/DemoModeSection.js.map +1 -1
  110. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  111. package/dist/client/settings/SettingsPanel.js +41 -191
  112. package/dist/client/settings/SettingsPanel.js.map +1 -1
  113. package/dist/client/settings/agent-settings-search.d.ts +16 -0
  114. package/dist/client/settings/agent-settings-search.d.ts.map +1 -0
  115. package/dist/client/settings/agent-settings-search.js +178 -0
  116. package/dist/client/settings/agent-settings-search.js.map +1 -0
  117. package/dist/client/settings/index.d.ts +1 -0
  118. package/dist/client/settings/index.d.ts.map +1 -1
  119. package/dist/client/settings/index.js +1 -0
  120. package/dist/client/settings/index.js.map +1 -1
  121. package/dist/client/url-scrub.d.ts.map +1 -1
  122. package/dist/client/url-scrub.js +14 -0
  123. package/dist/client/url-scrub.js.map +1 -1
  124. package/dist/client/use-demo-mode-status.d.ts +12 -0
  125. package/dist/client/use-demo-mode-status.d.ts.map +1 -0
  126. package/dist/client/use-demo-mode-status.js +30 -0
  127. package/dist/client/use-demo-mode-status.js.map +1 -0
  128. package/dist/collab/routes.d.ts +2 -2
  129. package/dist/demo/actions/toggle-demo-mode.js +2 -2
  130. package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
  131. package/dist/demo/config.js +6 -6
  132. package/dist/demo/config.js.map +1 -1
  133. package/dist/demo/redact.d.ts +7 -5
  134. package/dist/demo/redact.d.ts.map +1 -1
  135. package/dist/demo/redact.js +19 -54
  136. package/dist/demo/redact.js.map +1 -1
  137. package/dist/notifications/routes.d.ts +2 -2
  138. package/dist/observability/routes.d.ts +5 -5
  139. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  140. package/dist/secrets/routes.d.ts +9 -9
  141. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  142. package/dist/server/transcribe-voice.d.ts +1 -1
  143. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +25 -1
  144. package/docs/content/locales/ar-SA/template-plan.mdx +1 -1
  145. package/docs/content/locales/de-DE/pr-visual-recap.mdx +25 -1
  146. package/docs/content/locales/de-DE/template-plan.mdx +1 -1
  147. package/docs/content/locales/es-ES/pr-visual-recap.mdx +25 -1
  148. package/docs/content/locales/es-ES/template-plan.mdx +1 -1
  149. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +25 -1
  150. package/docs/content/locales/fr-FR/template-plan.mdx +1 -1
  151. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +25 -1
  152. package/docs/content/locales/hi-IN/template-plan.mdx +1 -1
  153. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +25 -1
  154. package/docs/content/locales/ja-JP/template-plan.mdx +1 -1
  155. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +25 -1
  156. package/docs/content/locales/ko-KR/template-plan.mdx +1 -1
  157. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +25 -1
  158. package/docs/content/locales/pt-BR/template-plan.mdx +1 -1
  159. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +25 -1
  160. package/docs/content/locales/zh-CN/template-plan.mdx +1 -1
  161. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +25 -1
  162. package/docs/content/locales/zh-TW/template-plan.mdx +1 -1
  163. package/docs/content/pr-visual-recap.mdx +50 -1
  164. package/docs/content/template-plan.mdx +1 -1
  165. package/package.json +2 -2
@@ -42,9 +42,8 @@ const DASHBOARD_REPORT_SCREENSHOT_PARAM = "reportScreenshot";
42
42
  const DASHBOARD_REPORT_SETTINGS_PARAM = "reportSettings";
43
43
  const DASHBOARD_REPORT_CID = "dashboard-report-snapshot";
44
44
  const LOCAL_SCREENSHOT_TIMEOUT_MS = 90_000;
45
- const SERVERLESS_SCREENSHOT_TIMEOUT_MS = 60_000;
46
- const SERVERLESS_SECOND_READY_TIMEOUT_MS = 30_000;
47
- const MAX_SCREENSHOT_VIEWPORT_HEIGHT = 30_000;
45
+ const SERVERLESS_SCREENSHOT_TIMEOUT_MS = 90_000;
46
+ const SERVERLESS_SECOND_READY_TIMEOUT_MS = 45_000;
48
47
  const SCREENSHOT_VIEWPORT_PADDING = 64;
49
48
 
50
49
  type DashboardScreenshotAttempt = {
@@ -328,42 +327,27 @@ async function scrollDashboardForLazyRendering(page: any): Promise<void> {
328
327
  })()`);
329
328
  }
330
329
 
331
- async function fitViewportToDashboardCapture(
330
+ async function fitViewportWidthToDashboardCapture(
332
331
  page: any,
333
332
  capture: any,
334
- minViewport: { width: number; height: number },
333
+ viewport: { width: number; height: number },
335
334
  ): Promise<void> {
336
335
  const box = await capture.boundingBox();
337
336
  if (!box) return;
338
337
 
339
- const size = {
340
- width: Math.max(
341
- minViewport.width,
342
- Math.min(1800, Math.ceil(box.width + SCREENSHOT_VIEWPORT_PADDING)),
343
- ),
344
- height: Math.max(
345
- minViewport.height,
346
- Math.min(
347
- MAX_SCREENSHOT_VIEWPORT_HEIGHT,
348
- Math.ceil(box.height + SCREENSHOT_VIEWPORT_PADDING),
349
- ),
350
- ),
351
- };
352
- await page.setViewportSize(size);
353
- await page.waitForTimeout(250);
354
-
355
- const resizedBox = await capture.boundingBox();
356
- if (!resizedBox) return;
357
- const resizedHeight = Math.ceil(
358
- resizedBox.height + SCREENSHOT_VIEWPORT_PADDING,
338
+ const width = Math.max(
339
+ viewport.width,
340
+ Math.min(1800, Math.ceil(box.width + SCREENSHOT_VIEWPORT_PADDING)),
359
341
  );
360
- if (resizedHeight > size.height) {
361
- await page.setViewportSize({
362
- width: size.width,
363
- height: Math.min(MAX_SCREENSHOT_VIEWPORT_HEIGHT, resizedHeight),
364
- });
365
- await page.waitForTimeout(250);
366
- }
342
+ if (width === viewport.width) return;
343
+
344
+ // Keep the render surface bounded. Playwright's locator screenshot captures
345
+ // the full element beyond the viewport, while growing Chromium to the full
346
+ // dashboard height can exhaust memory and close the browser on serverless
347
+ // workers. Width-only fitting preserves the full dashboard without that
348
+ // oversized render surface.
349
+ await page.setViewportSize({ width, height: viewport.height });
350
+ await page.waitForTimeout(250);
367
351
  }
368
352
 
369
353
  async function captureDashboardPng(
@@ -412,7 +396,7 @@ async function captureDashboardPng(
412
396
  await scrollDashboardForLazyRendering(page);
413
397
  await waitForDashboardReportReady(page, secondReadyTimeoutMs());
414
398
 
415
- await fitViewportToDashboardCapture(page, capture, attempt.viewport);
399
+ await fitViewportWidthToDashboardCapture(page, capture, attempt.viewport);
416
400
  await capture.scrollIntoViewIfNeeded();
417
401
  const image = await capture.screenshot({
418
402
  type: "png",
@@ -1083,13 +1083,17 @@ export const VideoPlayer = forwardRef<VideoPlayerHandle, VideoPlayerProps>(
1083
1083
  const fullscreenMenuContainer = isFullscreen ? containerRef.current : null;
1084
1084
 
1085
1085
  const showThroughoutCta = cta && cta.placement === "throughout";
1086
+ // Mobile Safari may defer loadeddata/canplay until playback starts. Keep
1087
+ // the paused state actionable even when those readiness events have not
1088
+ // fired yet; once the user asks to play, the pending/buffering states give
1089
+ // them accurate loading feedback.
1086
1090
  const centerOverlayMode =
1087
1091
  activeVideoSrc &&
1088
1092
  !isLoomEmbed &&
1089
1093
  !unsupportedFormat &&
1090
1094
  !showEndCta &&
1091
1095
  (!isPlaying || isPlayPending || isBuffering)
1092
- ? isPreparing || isPlayPending || isBuffering || !canPlay
1096
+ ? isPlayPending || isBuffering
1093
1097
  ? "loading"
1094
1098
  : "ready"
1095
1099
  : null;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-11
4
+ ---
5
+
6
+ Clips no longer stay stuck on Preparing clip when they are ready to play on mobile.
@@ -1188,8 +1188,8 @@ export function DocumentSidebar({
1188
1188
  </div>
1189
1189
  )}
1190
1190
 
1191
- <ScrollArea className="min-h-0 flex-1">
1192
- <div className="min-w-full w-max py-2 pe-2">
1191
+ <ScrollArea className="min-h-0 flex-1 [&_[data-radix-scroll-area-viewport]]:!overflow-x-hidden">
1192
+ <div className="w-full min-w-0 py-2 pe-2">
1193
1193
  {/* Search results */}
1194
1194
  {filteredDocuments ? (
1195
1195
  <>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ The left sidebar stays fixed horizontally while scrolling on desktop and mobile.
@@ -36,9 +36,9 @@ The local-files contract:
36
36
  — encode multiline code as JSON string attributes such as `code={"const x =\n y"}`
37
37
  (a static template literal is accepted only when it has no `${...}`
38
38
  interpolation). `plan local check` is a quick OFFLINE lint (a subset of the
39
- renderer schema), so a green `check` does not guarantee the plan renders;
40
- `plan local verify` is the authoritative validation against the real renderer
41
- schema.
39
+ renderer schema), so a green `check` does not guarantee the plan renders.
40
+ `plan local verify` also stays on-device: it uses the offline lint unless it
41
+ can reach a Plan renderer on an explicit loopback `--app-url`.
42
42
  - **Write a local MDX folder.** Use `plans/<slug>/` to check the artifact into the
43
43
  repo, or a repo-ignored/temporary folder such as `.agent-native/plans/<slug>/`
44
44
  or `/tmp/agent-native-plans/<slug>/` when it should not be checked in. The
@@ -53,7 +53,10 @@ The local-files contract:
53
53
  (use `--kind plan` for plans, `--kind recap` for recaps). Report the local
54
54
  bridge URL from stdout or `<plan-dir>/.plan-url`; treat `.plan-url` as a local
55
55
  token file and do not commit it. The URL opens the hosted Plan UI but reads from
56
- the localhost bridge on this machine, so it is not shareable across machines. On
56
+ the localhost bridge on this machine, so it is not shareable across machines.
57
+ The token is carried in the URL fragment (which is not sent to the hosted
58
+ origin), and the local-plan route disables DOM autocapture and session replay
59
+ while retaining sanitized pageviews and error monitoring. On
57
60
  macOS `--open` prefers Chromium browsers; if Safari opens, switch to
58
61
  Chrome/Chromium because Safari can block the hosted HTTPS page from fetching the
59
62
  HTTP localhost bridge. If the Plan app itself is running locally with the same
@@ -63,14 +66,16 @@ The local-files contract:
63
66
  running local Plan app.
64
67
  - **Headless verify.** Run
65
68
  `npx @agent-native/core@latest plan local verify --dir <plan-dir> --kind <plan|recap>`.
66
- It starts the bridge, checks the private-network preflight and JSON payload, AND
67
- validates the content against the real renderer schema via the Plan app's
68
- `validate-local-plan-source` action. A non-`ok` result with
69
+ It starts the bridge and checks the private-network preflight and JSON payload
70
+ entirely on loopback. It never sends MDX or assets to a remote validation
71
+ action. When `--app-url` points to a loopback Plan app, verify also validates
72
+ against that local app's real renderer schema via `validate-local-plan-source`.
73
+ A non-`ok` result with
69
74
  `validation.valid: false` lists the renderer's exact schema-path issues (e.g.
70
75
  `blocks[1].data.tabs[0]...`); fix those before handing off. If `validation.ran`
71
- is `false`, the Plan app did not expose the validate endpoint (older/unreachable
72
- deploy) point `--app-url` at a current Plan app (e.g. a local
73
- `http://localhost:8096`) for the authoritative check. If the browser hangs on
76
+ is `false`, verify used the offline lint because the app URL was remote or the
77
+ local Plan app was unavailable. Run a local Plan app and pass
78
+ `--app-url http://localhost:8096` for the authoritative check. If the browser hangs on
74
79
  "Loading plan", fetch the `bridgeUrl` from the verify/serve JSON to read the
75
80
  concrete validation error.
76
81
  - **Never call hosted tools for that plan/recap.** Do not call
@@ -86,8 +91,8 @@ The local-files contract:
86
91
  attachment, and publish/export receipts are unavailable until the user
87
92
  explicitly opts into publishing.
88
93
 
89
- Local-files mode only prevents plan/recap content from reaching the Agent-Native
90
- Plan database. It does not by itself make the coding agent's language model local;
94
+ Local-files mode prevents plan/recap content from being uploaded to the
95
+ Agent-Native Plan server or database. It does not by itself make the coding agent's language model local;
91
96
  for that stronger boundary the host agent/model must also be local or otherwise
92
97
  approved by the user.
93
98
 
@@ -36,9 +36,9 @@ The local-files contract:
36
36
  — encode multiline code as JSON string attributes such as `code={"const x =\n y"}`
37
37
  (a static template literal is accepted only when it has no `${...}`
38
38
  interpolation). `plan local check` is a quick OFFLINE lint (a subset of the
39
- renderer schema), so a green `check` does not guarantee the plan renders;
40
- `plan local verify` is the authoritative validation against the real renderer
41
- schema.
39
+ renderer schema), so a green `check` does not guarantee the plan renders.
40
+ `plan local verify` also stays on-device: it uses the offline lint unless it
41
+ can reach a Plan renderer on an explicit loopback `--app-url`.
42
42
  - **Write a local MDX folder.** Use `plans/<slug>/` to check the artifact into the
43
43
  repo, or a repo-ignored/temporary folder such as `.agent-native/plans/<slug>/`
44
44
  or `/tmp/agent-native-plans/<slug>/` when it should not be checked in. The
@@ -53,7 +53,10 @@ The local-files contract:
53
53
  (use `--kind plan` for plans, `--kind recap` for recaps). Report the local
54
54
  bridge URL from stdout or `<plan-dir>/.plan-url`; treat `.plan-url` as a local
55
55
  token file and do not commit it. The URL opens the hosted Plan UI but reads from
56
- the localhost bridge on this machine, so it is not shareable across machines. On
56
+ the localhost bridge on this machine, so it is not shareable across machines.
57
+ The token is carried in the URL fragment (which is not sent to the hosted
58
+ origin), and the local-plan route disables DOM autocapture and session replay
59
+ while retaining sanitized pageviews and error monitoring. On
57
60
  macOS `--open` prefers Chromium browsers; if Safari opens, switch to
58
61
  Chrome/Chromium because Safari can block the hosted HTTPS page from fetching the
59
62
  HTTP localhost bridge. If the Plan app itself is running locally with the same
@@ -63,14 +66,16 @@ The local-files contract:
63
66
  running local Plan app.
64
67
  - **Headless verify.** Run
65
68
  `npx @agent-native/core@latest plan local verify --dir <plan-dir> --kind <plan|recap>`.
66
- It starts the bridge, checks the private-network preflight and JSON payload, AND
67
- validates the content against the real renderer schema via the Plan app's
68
- `validate-local-plan-source` action. A non-`ok` result with
69
+ It starts the bridge and checks the private-network preflight and JSON payload
70
+ entirely on loopback. It never sends MDX or assets to a remote validation
71
+ action. When `--app-url` points to a loopback Plan app, verify also validates
72
+ against that local app's real renderer schema via `validate-local-plan-source`.
73
+ A non-`ok` result with
69
74
  `validation.valid: false` lists the renderer's exact schema-path issues (e.g.
70
75
  `blocks[1].data.tabs[0]...`); fix those before handing off. If `validation.ran`
71
- is `false`, the Plan app did not expose the validate endpoint (older/unreachable
72
- deploy) point `--app-url` at a current Plan app (e.g. a local
73
- `http://localhost:8096`) for the authoritative check. If the browser hangs on
76
+ is `false`, verify used the offline lint because the app URL was remote or the
77
+ local Plan app was unavailable. Run a local Plan app and pass
78
+ `--app-url http://localhost:8096` for the authoritative check. If the browser hangs on
74
79
  "Loading plan", fetch the `bridgeUrl` from the verify/serve JSON to read the
75
80
  concrete validation error.
76
81
  - **Never call hosted tools for that plan/recap.** Do not call
@@ -86,8 +91,8 @@ The local-files contract:
86
91
  attachment, and publish/export receipts are unavailable until the user
87
92
  explicitly opts into publishing.
88
93
 
89
- Local-files mode only prevents plan/recap content from reaching the Agent-Native
90
- Plan database. It does not by itself make the coding agent's language model local;
94
+ Local-files mode prevents plan/recap content from being uploaded to the
95
+ Agent-Native Plan server or database. It does not by itself make the coding agent's language model local;
91
96
  for that stronger boundary the host agent/model must also be local or otherwise
92
97
  approved by the user.
93
98
 
@@ -13,8 +13,8 @@ import { parsePlanMdxFolder, planMdxFileSchema } from "../server/plan-mdx.js";
13
13
  *
14
14
  * It is intentionally a pure parse: it accepts the posted MDX folder, never
15
15
  * reads `schema.plans`, never touches the filesystem, and never writes to the
16
- * database. That keeps it safe to expose without auth so the offline-friendly
17
- * `plan local verify` CLI can call it against a local or hosted Plan app.
16
+ * database. The local-files CLI calls it only on an explicit loopback Plan app;
17
+ * it never posts private source to this action on a hosted deployment.
18
18
  */
19
19
 
20
20
  // Covers the CLI's 10 MiB raw asset cap (~13.3 MiB base64) plus MDX text, so
@@ -63,7 +63,7 @@ function toValidationIssues(error: unknown): ValidationIssue[] {
63
63
 
64
64
  export default defineAction({
65
65
  description:
66
- "Validate a local plan MDX folder against the live Plan renderer schema (parsePlanMdxFolder + planContentSchema). Returns { valid, issues } where issues carry the renderer's exact schema path (e.g. blocks[1].data.items[0].id). Use this to confirm a plan will render before handing it off it is the authoritative check behind `plan local verify`. Pure parse: no database, filesystem, or schema.plans access.",
66
+ "Validate a plan MDX folder against this Plan app's renderer schema (parsePlanMdxFolder + planContentSchema). Returns { valid, issues } where issues carry the renderer's exact schema path (e.g. blocks[1].data.items[0].id). The local-files CLI calls this only on loopback Plan apps so private source stays on-device. Pure parse: no database, filesystem, or schema.plans access.",
67
67
  schema: z.object({
68
68
  mdx: planMdxFileSchema.describe(
69
69
  "The plan MDX folder: plan.mdx plus optional canvas.mdx, prototype.mdx, .plan-state.json, and assets/.",
@@ -81,7 +81,7 @@ export default defineAction({
81
81
  requiresAuth: false,
82
82
  title: "Validate Local Plan Source",
83
83
  description:
84
- "Validate a local plan MDX folder against the real renderer schema without touching the database.",
84
+ "Validate a plan MDX folder against this app's renderer schema without touching the database.",
85
85
  },
86
86
  run: async (args) => {
87
87
  let totalBytes = 0;
@@ -62,6 +62,7 @@ import {
62
62
  } from "@/components/ui/tooltip";
63
63
  import { usePlans } from "@/hooks/use-plans";
64
64
  import { APP_TITLE } from "@/lib/app-config";
65
+ import { planReturnPathFromLocation } from "@/lib/plan-local-bridge";
65
66
  import { cn } from "@/lib/utils";
66
67
 
67
68
  const PLAN_CHAT_STORAGE_KEY = "plans";
@@ -651,7 +652,7 @@ export function Sidebar({
651
652
  const location = useLocation();
652
653
  const { session, isLoading: sessionLoading } = useSession();
653
654
  const t = useT();
654
- const returnPath = `${location.pathname}${location.search}${location.hash}`;
655
+ const returnPath = planReturnPathFromLocation(location);
655
656
  const ToggleIcon = collapsed
656
657
  ? IconLayoutSidebarLeftExpand
657
658
  : IconLayoutSidebarLeftCollapse;
@@ -67,6 +67,47 @@ export class LocalPlanBridgePermissionError extends Error {
67
67
  }
68
68
  }
69
69
 
70
+ type LocalPlanBridgeSessionStorage = Pick<Storage, "getItem" | "setItem">;
71
+
72
+ function browserSessionStorage(): LocalPlanBridgeSessionStorage | null {
73
+ if (typeof window === "undefined") return null;
74
+ try {
75
+ return window.sessionStorage;
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ export function localPlanBridgeUrlFromLocation(
82
+ hash: string,
83
+ slug: string,
84
+ storage: LocalPlanBridgeSessionStorage | null = browserSessionStorage(),
85
+ ): string | null {
86
+ const hashParams = hash.startsWith("#bridge=")
87
+ ? new URLSearchParams(hash.slice(1))
88
+ : null;
89
+ const bridgeUrl = hashParams?.get("bridge") ?? null;
90
+ const storageKey = `agent-native.local-plan-bridge.${slug}`;
91
+ try {
92
+ if (bridgeUrl) {
93
+ storage?.setItem(storageKey, bridgeUrl);
94
+ return bridgeUrl;
95
+ }
96
+ return storage?.getItem(storageKey) ?? null;
97
+ } catch {
98
+ return bridgeUrl;
99
+ }
100
+ }
101
+
102
+ export function planReturnPathFromLocation(location: {
103
+ pathname: string;
104
+ search: string;
105
+ hash: string;
106
+ }): string {
107
+ const safeHash = location.hash.startsWith("#bridge=") ? "" : location.hash;
108
+ return `${location.pathname}${location.search}${safeHash}`;
109
+ }
110
+
70
111
  export async function localNetworkAccessPermissionState(): Promise<
71
112
  Exclude<LocalNetworkAccessPermissionState, "checking">
72
113
  > {
@@ -0,0 +1,3 @@
1
+ export function shouldCapturePlanContent(pathname: string): boolean {
2
+ return !(pathname === "/local-plans" || pathname.startsWith("/local-plans/"));
3
+ }
@@ -273,11 +273,13 @@ import { planDocumentTitle } from "@/lib/plan-document-title";
273
273
  import {
274
274
  fetchLocalPlanBridgeBundle,
275
275
  localNetworkAccessPermissionState,
276
+ localPlanBridgeUrlFromLocation,
276
277
  localPlanBridgeQueryKey,
277
278
  localPlanBridgeRetryDelay,
278
279
  localPlanRoutePath,
279
280
  localPlanRouteUrl,
280
281
  mergeLocalBridgeComments,
282
+ planReturnPathFromLocation,
281
283
  shouldRetryLocalPlanBridgeBundle,
282
284
  shouldShowLocalPlanLoadError,
283
285
  shouldShowPlanLoadError,
@@ -2024,9 +2026,10 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
2024
2026
  () => new URLSearchParams(location.search),
2025
2027
  [location.search],
2026
2028
  );
2027
- const localPlanBridgeUrl = localPlanMode
2028
- ? routeSearchParams.get("bridge")
2029
- : null;
2029
+ const localPlanBridgeUrl =
2030
+ localPlanMode && localPlanSlug
2031
+ ? localPlanBridgeUrlFromLocation(location.hash, localPlanSlug)
2032
+ : null;
2030
2033
  const localPlanRepoPath = localPlanMode
2031
2034
  ? routeSearchParams.get("path")
2032
2035
  : null;
@@ -2137,8 +2140,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
2137
2140
  // Redirect to sign-in, returning to wherever the guest currently is.
2138
2141
  const openSignIn = useCallback((returnOverride?: string) => {
2139
2142
  const returnPath =
2140
- returnOverride ??
2141
- window.location.pathname + window.location.search + window.location.hash;
2143
+ returnOverride ?? planReturnPathFromLocation(window.location);
2142
2144
  window.location.href = `${agentNativePath(
2143
2145
  "/_agent-native/sign-in",
2144
2146
  )}?return=${encodeURIComponent(returnPath)}`;
@@ -2292,8 +2294,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
2292
2294
  }
2293
2295
  }, [accessRequestSentPlanId, selectedId]);
2294
2296
  const startGoogleSignIn = useCallback(async () => {
2295
- const returnPath =
2296
- window.location.pathname + window.location.search + window.location.hash;
2297
+ const returnPath = planReturnPathFromLocation(window.location);
2297
2298
  try {
2298
2299
  const res = await fetch(
2299
2300
  `${agentNativePath("/_agent-native/google/auth-url")}?return=${encodeURIComponent(returnPath)}`,
@@ -6826,8 +6827,7 @@ function PlanLoadError({
6826
6827
  ? t("plansPage.loadError.orgTitle", { orgName })
6827
6828
  : null;
6828
6829
 
6829
- const returnPath = () =>
6830
- window.location.pathname + window.location.search + window.location.hash;
6830
+ const returnPath = () => planReturnPathFromLocation(window.location);
6831
6831
 
6832
6832
  const readAuthError = async (res: Response, fallback: string) => {
6833
6833
  const data = (await res.json().catch(() => null)) as {
@@ -20,7 +20,7 @@ import {
20
20
  } from "@tabler/icons-react";
21
21
  import { useQueryClient } from "@tanstack/react-query";
22
22
  import { useTheme } from "next-themes";
23
- import { useCallback, useState } from "react";
23
+ import { useCallback, useEffect, useState } from "react";
24
24
  import {
25
25
  Links,
26
26
  Meta,
@@ -44,13 +44,17 @@ import { useNavigationState } from "@/hooks/use-navigation-state";
44
44
  // their diagram/wireframe/api-spec blocks inline in the agent chat.
45
45
  import "@/lib/register-chat-renderers";
46
46
  import { APP_TITLE } from "@/lib/app-config";
47
+ import { shouldCapturePlanContent } from "@/lib/plan-tracking";
47
48
  import { TAB_ID } from "@/lib/tab-id";
48
49
 
49
50
  import changelog from "../CHANGELOG.md?raw";
50
51
  import { i18nCatalog } from "./i18n";
51
52
 
52
53
  import stylesheet from "./global.css?url";
54
+ // Keep standard pageviews, explicit analytics, and Sentry on local-plan routes,
55
+ // but disable DOM/session capture so rendered plan contents stay on-device.
53
56
  configureTracking({
57
+ contentCaptureForPath: shouldCapturePlanContent,
54
58
  getDefaultProps: (_name, properties) => ({
55
59
  ...properties,
56
60
  app: "plan",
@@ -221,6 +225,7 @@ export default function Root() {
221
225
  location.pathname.startsWith("/recaps/") ||
222
226
  location.pathname === "/local-plans" ||
223
227
  location.pathname.startsWith("/local-plans/");
228
+ const localPlanPrivacyRoute = !shouldCapturePlanContent(location.pathname);
224
229
  return (
225
230
  // Pass the plan-specific styled Toaster via `toaster` so only one sonner
226
231
  // instance renders (avoids the duplicate that would appear if AppProviders'
@@ -232,8 +237,13 @@ export default function Root() {
232
237
  toaster={<Toaster richColors position="bottom-left" />}
233
238
  i18n={{ catalog: i18nCatalog }}
234
239
  >
235
- <DbSyncSetup />
236
- <AppContent />
240
+ <div
241
+ data-an-mask={localPlanPrivacyRoute ? "" : undefined}
242
+ style={{ display: "contents" }}
243
+ >
244
+ <DbSyncSetup />
245
+ <AppContent />
246
+ </div>
237
247
  </AppProviders>
238
248
  </AppToolkitProvider>
239
249
  );
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: security
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Local plan source now stays on your device during verification and is excluded from content autocapture and session replay.
@@ -3247,8 +3247,8 @@ export async function runAgentLoop(opts) {
3247
3247
  // content parts; first-party actions opt in via the well-known
3248
3248
  // `_agentImages` result field (stripped from the JSON the model
3249
3249
  // reads, even in demo mode). Demo mode drops the images themselves —
3250
- // text redaction can't scrub pixels, and a screenshot would leak the
3251
- // real contact names/emails/numbers the redaction exists to hide. The images array
3250
+ // text redaction can't scrub pixels, and a screenshot may expose
3251
+ // original visual data. The images array
3252
3252
  // never touches the ledger — only the compact text notes appended
3253
3253
  // below are persisted.
3254
3254
  let imageNotes = [];