@agent-native/core 0.120.2 → 0.120.4

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 (278) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +28 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/context-xray-local.ts +1 -1
  5. package/corpus/core/src/cli/index.ts +63 -18
  6. package/corpus/core/src/cli/skills.ts +5 -0
  7. package/corpus/core/src/cli/telemetry.ts +118 -1
  8. package/corpus/core/src/client/AgentPanel.tsx +10 -0
  9. package/corpus/core/src/client/CommandMenu.tsx +13 -1
  10. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +39 -4
  11. package/corpus/core/src/client/analytics.ts +13 -0
  12. package/corpus/core/src/client/error-capture.ts +6 -1
  13. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +12 -5
  14. package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  15. package/corpus/core/src/client/navigation/index.ts +2 -0
  16. package/corpus/core/src/client/org/OrgSwitcher.tsx +14 -32
  17. package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
  18. package/corpus/core/src/extensions/theme.ts +1 -1
  19. package/corpus/core/src/localization/default-messages.ts +2 -0
  20. package/corpus/core/src/server/core-routes-plugin.ts +10 -1
  21. package/corpus/core/src/server/framework-request-handler.ts +17 -26
  22. package/corpus/core/src/styles/agent-native.css +13 -0
  23. package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  24. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  25. package/corpus/core/src/templates/chat/app/global.css +1 -1
  26. package/corpus/core/src/templates/default/app/global.css +1 -1
  27. package/corpus/core/src/tracking/error-capture.ts +151 -0
  28. package/corpus/core/src/tracking/index.ts +5 -0
  29. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +1 -1
  30. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +1 -4
  31. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +1 -1
  32. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +1 -1
  33. package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +2 -2
  34. package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +1 -1
  35. package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +1 -1
  36. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -1
  37. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +1 -1
  38. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +41 -9
  39. package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -1
  40. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +1 -1
  41. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -0
  42. package/corpus/templates/analytics/app/components/layout/Layout.tsx +28 -14
  43. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +152 -124
  44. package/corpus/templates/analytics/app/global.css +5 -5
  45. package/corpus/templates/analytics/app/lib/dashboard-report-capture.ts +42 -0
  46. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +1 -1
  47. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +1 -1
  48. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -1
  49. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +1 -1
  50. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +2 -2
  51. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -3
  52. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +2 -2
  53. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  54. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +1 -1
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +162 -51
  56. package/corpus/templates/analytics/app/root.tsx +28 -0
  57. package/corpus/templates/analytics/changelog/2026-07-24-analytics-charts-now-explain-slow-loads-and-offer-a-direct-r.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-07-24-daily-dashboard-emails-now-load-signed-reports-more-reliably.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-24-manage-agent-is-clearly-marked-as-a-link-to-its-dedicated-pa.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  62. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -1
  63. package/corpus/templates/analytics/server/db/schema.ts +5 -0
  64. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +21 -0
  65. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +11 -0
  66. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +54 -0
  67. package/corpus/templates/analytics/server/lib/dashboard-report.ts +235 -26
  68. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +4 -1
  69. package/corpus/templates/analytics/server/plugins/db.ts +9 -0
  70. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +1 -1
  71. package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +376 -0
  72. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +14 -4
  73. package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
  74. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +118 -74
  75. package/corpus/templates/assets/app/global.css +1 -1
  76. package/corpus/templates/assets/app/i18n-data.ts +10 -0
  77. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +99 -10
  78. package/corpus/templates/assets/app/routes/library.tsx +21 -198
  79. package/corpus/templates/assets/changelog/2026-07-21-unified-asset-preview-click-any-asset-to-open-one-preview-wi.md +6 -0
  80. package/corpus/templates/assets/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  81. package/corpus/templates/assets/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  82. package/corpus/templates/brain/app/components/layout/Layout.tsx +3 -0
  83. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +76 -43
  84. package/corpus/templates/brain/app/global.css +1 -1
  85. package/corpus/templates/brain/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  86. package/corpus/templates/brain/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  87. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +67 -52
  88. package/corpus/templates/calendar/app/global.css +1 -1
  89. package/corpus/templates/calendar/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  90. package/corpus/templates/calendar/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  91. package/corpus/templates/chat/app/components/layout/Layout.tsx +3 -0
  92. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  93. package/corpus/templates/chat/app/global.css +1 -1
  94. package/corpus/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  95. package/corpus/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  96. package/corpus/templates/clips/app/components/library/library-layout.tsx +65 -31
  97. package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
  98. package/corpus/templates/clips/app/components/player/video-player.tsx +16 -1
  99. package/corpus/templates/clips/app/global.css +1 -1
  100. package/corpus/templates/clips/app/hooks/use-view-tracking.ts +55 -12
  101. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -5
  102. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -7
  103. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -5
  104. package/corpus/templates/clips/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  105. package/corpus/templates/clips/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  106. package/corpus/templates/clips/server/plugins/db.ts +113 -68
  107. package/corpus/templates/content/actions/update-document.ts +145 -91
  108. package/corpus/templates/content/app/components/editor/CommentComposer.tsx +4 -1
  109. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +366 -164
  110. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +51 -28
  111. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +19 -8
  112. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +66 -42
  113. package/corpus/templates/content/app/global.css +1 -1
  114. package/corpus/templates/content/app/i18n-data.ts +15 -0
  115. package/corpus/templates/content/changelog/2026-07-23-shared-pages-now-keep-durable-content-visible-and-comment-th.md +6 -0
  116. package/corpus/templates/content/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  117. package/corpus/templates/content/changelog/2026-07-24-comment-drafts-now-stay-open-when-saving-fails-and-long-live.md +6 -0
  118. package/corpus/templates/content/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  119. package/corpus/templates/design/actions/update-file.ts +85 -18
  120. package/corpus/templates/design/app/components/layout/Sidebar.tsx +65 -30
  121. package/corpus/templates/design/app/entry.client.tsx +14 -1
  122. package/corpus/templates/design/app/global.css +1 -1
  123. package/corpus/templates/design/app/lib/design-save-outbox.ts +39 -0
  124. package/corpus/templates/design/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  125. package/corpus/templates/design/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  126. package/corpus/templates/dispatch/app/global.css +1 -1
  127. package/corpus/templates/dispatch/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  128. package/corpus/templates/dispatch/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  129. package/corpus/templates/forms/app/components/layout/Layout.tsx +3 -0
  130. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +72 -37
  131. package/corpus/templates/forms/app/global.css +1 -1
  132. package/corpus/templates/forms/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  133. package/corpus/templates/forms/changelog/2026-07-24-forms-sidebar-actions-now-keep-search-language-feedback-and-.md +6 -0
  134. package/corpus/templates/forms/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  135. package/corpus/templates/macros/app/components/DailyProgress.tsx +5 -5
  136. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  137. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  138. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  139. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +56 -29
  140. package/corpus/templates/macros/app/components/ui/button.tsx +1 -2
  141. package/corpus/templates/macros/app/components/ui/card.tsx +1 -1
  142. package/corpus/templates/macros/app/global.css +1 -1
  143. package/corpus/templates/macros/app/routes/_index.tsx +1 -1
  144. package/corpus/templates/macros/app/routes/analytics.tsx +4 -7
  145. package/corpus/templates/macros/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  146. package/corpus/templates/macros/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  147. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +125 -37
  148. package/corpus/templates/mail/app/global.css +1 -1
  149. package/corpus/templates/mail/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  150. package/corpus/templates/mail/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  151. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -0
  152. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +90 -60
  153. package/corpus/templates/plan/app/global.css +1 -1
  154. package/corpus/templates/plan/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  155. package/corpus/templates/plan/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  156. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +69 -32
  157. package/corpus/templates/slides/app/global.css +1 -1
  158. package/corpus/templates/slides/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  159. package/corpus/templates/slides/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  160. package/corpus/templates/tasks/app/global.css +1 -1
  161. package/corpus/templates/tasks/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  162. package/corpus/toolkit/CHANGELOG.md +14 -0
  163. package/corpus/toolkit/README.md +8 -1
  164. package/corpus/toolkit/agent-native.eject.json +1 -1
  165. package/corpus/toolkit/package.json +1 -1
  166. package/corpus/toolkit/src/app-shell/index.ts +1 -0
  167. package/corpus/toolkit/src/app-shell/sidebar-footer-actions.tsx +48 -0
  168. package/corpus/toolkit/src/chat-history.css +52 -1
  169. package/corpus/toolkit/src/design-system/default-adapter.tsx +1 -1
  170. package/corpus/toolkit/src/editor/useCollabReconcile.ts +83 -15
  171. package/corpus/toolkit/src/ui/button.tsx +1 -2
  172. package/corpus/toolkit/src/ui/card.tsx +1 -1
  173. package/corpus/toolkit/src/ui/table.tsx +4 -11
  174. package/dist/cli/context-xray-local.js +1 -1
  175. package/dist/cli/context-xray-local.js.map +1 -1
  176. package/dist/cli/index.js +56 -17
  177. package/dist/cli/index.js.map +1 -1
  178. package/dist/cli/skills.d.ts.map +1 -1
  179. package/dist/cli/skills.js +5 -0
  180. package/dist/cli/skills.js.map +1 -1
  181. package/dist/cli/telemetry.d.ts +7 -0
  182. package/dist/cli/telemetry.d.ts.map +1 -1
  183. package/dist/cli/telemetry.js +91 -1
  184. package/dist/cli/telemetry.js.map +1 -1
  185. package/dist/client/AgentPanel.d.ts +7 -1
  186. package/dist/client/AgentPanel.d.ts.map +1 -1
  187. package/dist/client/AgentPanel.js +2 -2
  188. package/dist/client/AgentPanel.js.map +1 -1
  189. package/dist/client/CommandMenu.d.ts +2 -0
  190. package/dist/client/CommandMenu.d.ts.map +1 -1
  191. package/dist/client/CommandMenu.js +12 -1
  192. package/dist/client/CommandMenu.js.map +1 -1
  193. package/dist/client/agent-page/AgentTabsPage.d.ts +4 -0
  194. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
  195. package/dist/client/agent-page/AgentTabsPage.js +8 -4
  196. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  197. package/dist/client/analytics.d.ts.map +1 -1
  198. package/dist/client/analytics.js +7 -0
  199. package/dist/client/analytics.js.map +1 -1
  200. package/dist/client/error-capture.d.ts.map +1 -1
  201. package/dist/client/error-capture.js +8 -1
  202. package/dist/client/error-capture.js.map +1 -1
  203. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  204. package/dist/client/extensions/ExtensionsListPage.js +2 -2
  205. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  206. package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
  207. package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
  208. package/dist/client/navigation/index.d.ts +1 -1
  209. package/dist/client/navigation/index.d.ts.map +1 -1
  210. package/dist/client/navigation/index.js +1 -1
  211. package/dist/client/navigation/index.js.map +1 -1
  212. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  213. package/dist/client/org/OrgSwitcher.js +9 -13
  214. package/dist/client/org/OrgSwitcher.js.map +1 -1
  215. package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
  216. package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -1
  217. package/dist/collab/awareness.d.ts +2 -2
  218. package/dist/collab/awareness.d.ts.map +1 -1
  219. package/dist/collab/struct-routes.d.ts +1 -1
  220. package/dist/extensions/theme.js +1 -1
  221. package/dist/extensions/theme.js.map +1 -1
  222. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  223. package/dist/localization/default-messages.d.ts +2 -0
  224. package/dist/localization/default-messages.d.ts.map +1 -1
  225. package/dist/localization/default-messages.js +2 -0
  226. package/dist/localization/default-messages.js.map +1 -1
  227. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  228. package/dist/secrets/routes.d.ts +9 -9
  229. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  230. package/dist/server/core-routes-plugin.js +8 -1
  231. package/dist/server/core-routes-plugin.js.map +1 -1
  232. package/dist/server/framework-request-handler.d.ts.map +1 -1
  233. package/dist/server/framework-request-handler.js +17 -27
  234. package/dist/server/framework-request-handler.js.map +1 -1
  235. package/dist/server/realtime-token.d.ts +1 -1
  236. package/dist/styles/agent-native.css +13 -0
  237. package/dist/templates/chat/app/components/layout/Layout.tsx +3 -0
  238. package/dist/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  239. package/dist/templates/chat/app/global.css +1 -1
  240. package/dist/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  241. package/dist/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  242. package/dist/templates/default/app/global.css +1 -1
  243. package/dist/tracking/error-capture.d.ts +14 -0
  244. package/dist/tracking/error-capture.d.ts.map +1 -0
  245. package/dist/tracking/error-capture.js +113 -0
  246. package/dist/tracking/error-capture.js.map +1 -0
  247. package/dist/tracking/index.d.ts +1 -0
  248. package/dist/tracking/index.d.ts.map +1 -1
  249. package/dist/tracking/index.js +1 -0
  250. package/dist/tracking/index.js.map +1 -1
  251. package/package.json +2 -2
  252. package/src/cli/context-xray-local.ts +1 -1
  253. package/src/cli/index.ts +63 -18
  254. package/src/cli/skills.ts +5 -0
  255. package/src/cli/telemetry.ts +118 -1
  256. package/src/client/AgentPanel.tsx +10 -0
  257. package/src/client/CommandMenu.tsx +13 -1
  258. package/src/client/agent-page/AgentTabsPage.tsx +39 -4
  259. package/src/client/analytics.ts +13 -0
  260. package/src/client/error-capture.ts +6 -1
  261. package/src/client/extensions/ExtensionsListPage.tsx +12 -5
  262. package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  263. package/src/client/navigation/index.ts +2 -0
  264. package/src/client/org/OrgSwitcher.tsx +14 -32
  265. package/src/client/settings/ExtensionsSettingsContent.tsx +1 -1
  266. package/src/extensions/theme.ts +1 -1
  267. package/src/localization/default-messages.ts +2 -0
  268. package/src/server/core-routes-plugin.ts +10 -1
  269. package/src/server/framework-request-handler.ts +17 -26
  270. package/src/styles/agent-native.css +13 -0
  271. package/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  272. package/src/templates/chat/app/components/layout/Sidebar.tsx +78 -45
  273. package/src/templates/chat/app/global.css +1 -1
  274. package/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  275. package/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  276. package/src/templates/default/app/global.css +1 -1
  277. package/src/tracking/error-capture.ts +151 -0
  278. package/src/tracking/index.ts +5 -0
@@ -15,9 +15,11 @@ export {
15
15
  type UseSemanticNavigationStateResult,
16
16
  } from "../route-state.js";
17
17
  export {
18
+ COMMAND_MENU_OPEN_EVENT,
18
19
  CommandMenu,
19
20
  openAgentSidebar,
20
21
  openAgentSettings,
22
+ openCommandMenu,
21
23
  submitToAgent,
22
24
  useCommandMenuShortcut,
23
25
  type CommandMenuProps,
@@ -102,7 +102,7 @@ const APP_SUBMENU_CONTENT_CLASS =
102
102
  "z-50 w-72 rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2";
103
103
 
104
104
  const SWITCHER_BUTTON_CLASS =
105
- "flex w-full items-center gap-2 rounded-md border border-border/70 px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/50 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-60 cursor-pointer";
105
+ "flex w-full items-center gap-2 rounded-md border-0 bg-accent/50 px-2.5 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/70 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-60 cursor-pointer";
106
106
 
107
107
  const DEFAULT_ORGANIZATION_SETTINGS_PATH = "/settings#organization";
108
108
  const DEFAULT_PROFILE_PATH = "/settings#account";
@@ -151,33 +151,29 @@ function AppMenuLink({
151
151
  onNavigate: () => void;
152
152
  }) {
153
153
  const Icon = appMenuIcon(app);
154
+ const description =
155
+ app.status === "pending"
156
+ ? "Building"
157
+ : app.description?.trim() ||
158
+ (app.isDispatch ? "Workspace hub" : `${app.name} workspace`);
154
159
  return (
155
160
  <a
156
161
  href={app.href}
157
162
  onClick={onNavigate}
158
- className={`flex items-center gap-2 rounded-sm px-2.5 py-2 text-xs outline-none hover:bg-accent focus:bg-accent ${
159
- app.isDispatch ? "border border-primary/20 bg-primary/5" : ""
160
- }`}
163
+ className="flex items-center gap-2 rounded-sm px-2.5 py-2 text-xs outline-none hover:bg-accent focus:bg-accent"
161
164
  >
162
- <span
163
- className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-md ${
164
- app.isDispatch
165
- ? "bg-primary text-primary-foreground"
166
- : "bg-muted text-muted-foreground"
167
- }`}
168
- >
165
+ <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
169
166
  <Icon className="h-3.5 w-3.5" />
170
167
  </span>
171
168
  <span className="min-w-0 flex-1">
172
169
  <span className="block truncate font-medium text-foreground">
173
170
  {app.name}
174
171
  </span>
175
- <span className="block truncate text-[11px] text-muted-foreground">
176
- {app.isDispatch
177
- ? "Main hub"
178
- : app.status === "pending"
179
- ? "Building"
180
- : "Open app"}
172
+ <span
173
+ className="block truncate text-[11px] text-muted-foreground"
174
+ title={description}
175
+ >
176
+ {description}
181
177
  </span>
182
178
  </span>
183
179
  <IconArrowUpRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
@@ -187,14 +183,12 @@ function AppMenuLink({
187
183
 
188
184
  function AppsSubmenu({
189
185
  apps,
190
- isWorkspace,
191
186
  isLoading,
192
187
  dispatchHref,
193
188
  dispatchAllAppsHref,
194
189
  onNavigate,
195
190
  }: {
196
191
  apps: OrgSwitcherAppLink[];
197
- isWorkspace: boolean;
198
192
  isLoading: boolean;
199
193
  dispatchHref: string;
200
194
  dispatchAllAppsHref: string;
@@ -241,17 +235,6 @@ function AppsSubmenu({
241
235
  collisionPadding={12}
242
236
  className={APP_SUBMENU_CONTENT_CLASS}
243
237
  >
244
- <div className="px-2.5 py-1.5">
245
- <div className="text-[10px] uppercase tracking-wide text-muted-foreground">
246
- {isWorkspace ? "Workspace apps" : "Default apps"}
247
- </div>
248
- <div className="mt-0.5 text-[11px] text-muted-foreground">
249
- {isWorkspace
250
- ? "Dispatch is the workspace hub."
251
- : "Dispatch is the home base."}
252
- </div>
253
- </div>
254
-
255
238
  <AppMenuLink app={dispatchApp} onNavigate={onNavigate} />
256
239
 
257
240
  {visibleNonDispatch.length > 0 && (
@@ -269,7 +252,7 @@ function AppsSubmenu({
269
252
  onClick={onNavigate}
270
253
  className="flex items-center gap-2 rounded-sm px-2.5 py-1.5 text-xs text-foreground outline-none hover:bg-accent focus:bg-accent"
271
254
  >
272
- <IconMessageCircle className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
255
+ <IconApps className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
273
256
  <span className="flex-1">
274
257
  {`View ${remainingCount} more in Dispatch`}
275
258
  </span>
@@ -549,7 +532,6 @@ export function OrgSwitcher({
549
532
  <div className="my-1 h-px bg-border" />
550
533
  <AppsSubmenu
551
534
  apps={appLinks.apps}
552
- isWorkspace={appLinks.isWorkspace}
553
535
  isLoading={appLinks.isLoading}
554
536
  dispatchHref={appLinks.dispatchHref}
555
537
  dispatchAllAppsHref={appLinks.dispatchAllAppsHref}
@@ -9,7 +9,7 @@ const ExtensionsListPage = lazy(() =>
9
9
 
10
10
  export function ExtensionsSettingsContent() {
11
11
  return (
12
- <div className="mx-auto w-full max-w-5xl overflow-hidden rounded-lg border border-border bg-background">
12
+ <div className="mx-auto w-full max-w-5xl">
13
13
  <Suspense
14
14
  fallback={
15
15
  <div className="flex min-h-[28rem] items-center justify-center text-muted-foreground">
@@ -92,7 +92,7 @@ export function getThemeVars(_isDark?: boolean): string {
92
92
  --muted-foreground: 240 5% 64.9%;
93
93
  --accent: 240 3.7% 15.9%;
94
94
  --accent-foreground: 0 0% 98%;
95
- --destructive: 0 84% 60%;
95
+ --destructive: 0 91% 71%;
96
96
  --destructive-foreground: 0 0% 98%;
97
97
  --border: 240 3.7% 15.9%;
98
98
  --input: 240 3.7% 15.9%;
@@ -172,6 +172,7 @@ const messages = {
172
172
  workspaceControlPlane: "Workspace control plane",
173
173
  workspaceSubtitle_one: "Workspace · {{count}} app",
174
174
  workspaceSubtitle_other: "Workspace · {{count}} apps",
175
+ search: "Search",
175
176
  chats: "Chats",
176
177
  newChat: "New chat",
177
178
  newDispatchChat: "New Dispatch chat",
@@ -310,6 +311,7 @@ const messages = {
310
311
  newTerminal: "New terminal",
311
312
  panelOptions: "Agent panel options",
312
313
  collapseSidebar: "Collapse sidebar",
314
+ expandSidebar: "Expand sidebar",
313
315
  hideChats: "Hide chats",
314
316
  allChats: "All chats",
315
317
  settings: "Settings",
@@ -97,6 +97,7 @@ import {
97
97
  MCP_EMBED_CORS_ALLOW_HEADERS,
98
98
  shouldAllowMcpEmbedCredentials,
99
99
  } from "../shared/mcp-embed-headers.js";
100
+ import { captureException } from "../tracking/error-capture.js";
100
101
  import { track } from "../tracking/index.js";
101
102
  import { registerBuiltinProviders } from "../tracking/providers.js";
102
103
  import { validateTrackPayload } from "../tracking/route.js";
@@ -140,7 +141,7 @@ import {
140
141
  type BuilderRelayCredentials,
141
142
  type BuilderPreviewRelayState,
142
143
  } from "./builder-browser.js";
143
- import { captureError } from "./capture-error.js";
144
+ import { captureError, registerErrorCaptureProvider } from "./capture-error.js";
144
145
  import {
145
146
  getAllowedCorsOrigin,
146
147
  readCorsAllowedOrigins,
@@ -1197,6 +1198,14 @@ export function createCoreRoutesPlugin(
1197
1198
  // already registered the same key win.
1198
1199
  registerFrameworkSecrets();
1199
1200
  registerBuiltinProviders();
1201
+ registerErrorCaptureProvider("agent-native-analytics", (error, context) =>
1202
+ captureException(error, {
1203
+ ...context,
1204
+ handled: false,
1205
+ runtime: "node",
1206
+ source: "server",
1207
+ }),
1208
+ );
1200
1209
  registerBuiltinNotificationChannels();
1201
1210
 
1202
1211
  try {
@@ -602,33 +602,24 @@ function registerMiddleware(
602
602
  status,
603
603
  error: err,
604
604
  });
605
- // Forward 5xx to server-side Sentry — Nitro's own `error` hook may not
606
- // fire here because we convert the throw into a normal JSON response,
607
- // and a console.error alone is invisible in deployed environments.
608
- // 4xx are user-input errors (validation, auth) and aren't worth
609
- // alerting on. Lazy-loaded so the framework-request-handler module
610
- // doesn't pull @sentry/node into bundles that don't need it.
605
+ // Forward 5xx to the configured server error providers — Nitro's own
606
+ // `error` hook may not fire here because we convert the throw into a
607
+ // normal JSON response, and a console.error alone is invisible in
608
+ // deployed environments. 4xx are user-input errors (validation, auth)
609
+ // and aren't worth alerting on.
611
610
  if (status >= 500) {
612
- // Static `import` would create a cycle (sentry.ts imports auth.ts
613
- // which imports… eventually, framework-request-handler.ts).
614
- import("./sentry.js")
615
- .then(({ captureRouteError, isServerSentryEnabled }) => {
616
- if (!isServerSentryEnabled()) return;
617
- captureRouteError(err, {
618
- route: reqPath,
619
- method: event.method,
620
- userAgent: (() => {
621
- try {
622
- return event.headers?.get("user-agent") ?? undefined;
623
- } catch {
624
- return undefined;
625
- }
626
- })(),
627
- });
628
- })
629
- .catch(() => {
630
- // Sentry is observability — never let it break a response path.
631
- });
611
+ captureError(err, {
612
+ route: reqPath,
613
+ method: event.method,
614
+ tags: { status_code: String(status) },
615
+ userAgent: (() => {
616
+ try {
617
+ return event.headers?.get("user-agent") ?? undefined;
618
+ } catch {
619
+ return undefined;
620
+ }
621
+ })(),
622
+ });
632
623
  }
633
624
  try {
634
625
  setResponseStatus(event, status);
@@ -310,6 +310,10 @@
310
310
  opacity: 1;
311
311
  }
312
312
 
313
+ /* A named chat surface already morphs from its old geometry during a
314
+ full-page-chat handoff. Override only the initial style of that panel so it
315
+ does not slide in from the right underneath the morph. Keeping the normal
316
+ rules intact preserves ordinary sidebar open/close animation. */
313
317
  @starting-style {
314
318
  .agent-sidebar-panel[data-agent-sidebar-animation="desktop"][data-agent-sidebar-state="open"] {
315
319
  width: 0;
@@ -330,6 +334,15 @@
330
334
  .agent-sidebar-backdrop[data-agent-sidebar-animation="mobile"][data-agent-sidebar-state="open"] {
331
335
  opacity: 0;
332
336
  }
337
+
338
+ .agent-native-chat-view-transition.agent-sidebar-panel[data-agent-sidebar-animation="desktop"][data-agent-sidebar-chat-handoff="true"] {
339
+ width: var(--agent-sidebar-width);
340
+ }
341
+
342
+ .agent-native-chat-view-transition.agent-sidebar-panel[data-agent-sidebar-animation="desktop"][data-agent-sidebar-chat-handoff="true"]
343
+ > .agent-sidebar-panel-inner {
344
+ transform: translateX(0);
345
+ }
333
346
  }
334
347
 
335
348
  @media (prefers-reduced-motion: reduce) {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AgentSidebar,
3
3
  focusAgentChat,
4
+ isAgentChatHomeHandoffActive,
4
5
  navigateWithAgentChatViewTransition,
5
6
  useAgentChatHomeHandoff,
6
7
  useAgentChatHomeHandoffLinks,
@@ -57,6 +58,7 @@ export function Layout({ children }: LayoutProps) {
57
58
  activePath: location.pathname,
58
59
  enabled: !isChatRoute,
59
60
  });
61
+ const chatHomeHandoffPending = isAgentChatHomeHandoffActive("chat");
60
62
  useAgentChatHomeHandoffLinks({
61
63
  storageKey: "chat",
62
64
  isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
@@ -164,6 +166,7 @@ export function Layout({ children }: LayoutProps) {
164
166
  <AgentSidebar
165
167
  position="right"
166
168
  chatViewTransition
169
+ chatViewTransitionHandoff={chatHomeHandoffPending}
167
170
  storageKey="chat"
168
171
  browserTabId={TAB_ID}
169
172
  openOnChatRunning={chatHomeHandoffActive}
@@ -4,9 +4,11 @@ import {
4
4
  type ChatThreadSummary,
5
5
  } from "@agent-native/core/client/agent-chat";
6
6
  import { appPath } from "@agent-native/core/client/api-path";
7
- import { useT } from "@agent-native/core/client/i18n";
7
+ import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
8
+ import { openCommandMenu } from "@agent-native/core/client/navigation";
8
9
  import { OrgSwitcher } from "@agent-native/core/client/org";
9
10
  import { FeedbackButton } from "@agent-native/core/client/ui";
11
+ import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
10
12
  import {
11
13
  ChatHistoryRail,
12
14
  type ChatHistoryItem,
@@ -16,6 +18,7 @@ import {
16
18
  IconLayoutSidebarLeftCollapse,
17
19
  IconLayoutSidebarLeftExpand,
18
20
  IconMessageCircle,
21
+ IconSearch,
19
22
  IconSettings,
20
23
  } from "@tabler/icons-react";
21
24
  import { useEffect, useMemo } from "react";
@@ -126,7 +129,7 @@ function chatThreadPath(threadId: string) {
126
129
  return `/chat/${encodeURIComponent(threadId)}`;
127
130
  }
128
131
 
129
- function ChatThreadsSection() {
132
+ function ChatThreadsSection({ open }: { open: boolean }) {
130
133
  const navigate = useNavigate();
131
134
  const location = useLocation();
132
135
  const t = useT();
@@ -230,36 +233,42 @@ function ChatThreadsSection() {
230
233
  }
231
234
 
232
235
  return (
233
- <div className="mt-2 ms-4">
234
- <ChatHistoryRail
235
- items={chatItems}
236
- activeId={displayedActiveThreadId}
237
- onSelect={(threadId) => openThread(threadId)}
238
- onNewChat={() => void handleNewChat()}
239
- railLabels={{
240
- newChat: t("chat.newChat"),
241
- showMore: t("chat.chats"),
242
- showLess: t("chat.chats"),
243
- }}
244
- renameMaxLength={160}
245
- onTogglePin={(threadId) => {
246
- const thread = visibleThreads.find((item) => item.id === threadId);
247
- if (thread) void pinThread(threadId, !thread.pinnedAt);
248
- }}
249
- onRename={handleRenameThread}
250
- onDelete={(threadId) => void handleArchiveThread(threadId)}
251
- labels={{
252
- options: (item) =>
253
- t("chat.optionsFor", { title: item.titleText ?? "" }),
254
- renameInput: (item) =>
255
- t("chat.renameThread", { title: item.titleText ?? "" }),
256
- rename: t("chat.renameChat"),
257
- pin: t("chat.pinChat"),
258
- unpin: t("chat.unpinChat"),
259
- delete: t("chat.archiveChat"),
260
- }}
261
- className="min-w-0"
262
- />
236
+ <div
237
+ className="an-chat-history-rail__collapse"
238
+ data-state={open ? "open" : "closed"}
239
+ aria-hidden={!open}
240
+ >
241
+ <div className="ms-4">
242
+ <ChatHistoryRail
243
+ items={chatItems}
244
+ activeId={displayedActiveThreadId}
245
+ onSelect={(threadId) => openThread(threadId)}
246
+ onNewChat={() => void handleNewChat()}
247
+ railLabels={{
248
+ newChat: t("chat.newChat"),
249
+ showMore: t("chat.chats"),
250
+ showLess: t("chat.chats"),
251
+ }}
252
+ renameMaxLength={160}
253
+ onTogglePin={(threadId) => {
254
+ const thread = visibleThreads.find((item) => item.id === threadId);
255
+ if (thread) void pinThread(threadId, !thread.pinnedAt);
256
+ }}
257
+ onRename={handleRenameThread}
258
+ onDelete={(threadId) => void handleArchiveThread(threadId)}
259
+ labels={{
260
+ options: (item) =>
261
+ t("chat.optionsFor", { title: item.titleText ?? "" }),
262
+ renameInput: (item) =>
263
+ t("chat.renameThread", { title: item.titleText ?? "" }),
264
+ rename: t("chat.renameChat"),
265
+ pin: t("chat.pinChat"),
266
+ unpin: t("chat.unpinChat"),
267
+ delete: t("chat.archiveChat"),
268
+ }}
269
+ className="min-w-0"
270
+ />
271
+ </div>
263
272
  </div>
264
273
  );
265
274
  }
@@ -281,14 +290,14 @@ export function Sidebar({
281
290
  cn(
282
291
  "flex items-center text-sm transition-colors",
283
292
  collapsed
284
- ? "relative h-10 w-full justify-center rounded-none border-s-2 px-0"
293
+ ? "relative h-10 w-full justify-center rounded-none px-0"
285
294
  : "h-9 rounded-md gap-3 px-3",
286
295
  isActive
287
296
  ? collapsed
288
- ? "border-s-sidebar-accent-foreground/80 bg-sidebar-accent text-sidebar-accent-foreground"
297
+ ? "bg-sidebar-accent text-sidebar-accent-foreground"
289
298
  : "bg-sidebar-accent text-sidebar-accent-foreground"
290
299
  : collapsed
291
- ? "border-s-transparent text-sidebar-foreground/70 hover:bg-sidebar-accent/55 hover:text-sidebar-accent-foreground"
300
+ ? "text-sidebar-foreground/70 hover:bg-sidebar-accent/55 hover:text-sidebar-accent-foreground"
292
301
  : "text-sidebar-foreground hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground",
293
302
  );
294
303
  const collapseButton = collapsible ? (
@@ -317,6 +326,31 @@ export function Sidebar({
317
326
  </TooltipContent>
318
327
  </Tooltip>
319
328
  ) : null;
329
+ const searchButton = (
330
+ <Tooltip>
331
+ <TooltipTrigger asChild>
332
+ <button
333
+ type="button"
334
+ onClick={openCommandMenu}
335
+ aria-label={t("root.commandSearch")}
336
+ className="flex size-8 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
337
+ >
338
+ <IconSearch className="size-4" />
339
+ </button>
340
+ </TooltipTrigger>
341
+ <TooltipContent side="right">{t("root.commandSearch")}</TooltipContent>
342
+ </Tooltip>
343
+ );
344
+ const translateButton = (
345
+ <LanguagePicker variant="ghost-icon" label={t("settings.languageLabel")} />
346
+ );
347
+ const feedbackButton = (
348
+ <FeedbackButton
349
+ variant={collapsed ? "icon" : "sidebar"}
350
+ side="right"
351
+ className={collapsed ? "h-8 w-8" : "min-w-0"}
352
+ />
353
+ );
320
354
 
321
355
  return (
322
356
  <aside
@@ -411,8 +445,8 @@ export function Sidebar({
411
445
  ) : (
412
446
  link
413
447
  )}
414
- {!collapsed && item.view === "chat" && isChatRoute ? (
415
- <ChatThreadsSection />
448
+ {!collapsed && item.view === "chat" ? (
449
+ <ChatThreadsSection open={isChatRoute} />
416
450
  ) : null}
417
451
  </div>
418
452
  );
@@ -465,14 +499,13 @@ export function Sidebar({
465
499
  />
466
500
  </div>
467
501
 
468
- {!collapsed ? (
469
- <div className="flex items-center justify-end gap-1 px-3 py-2">
470
- <FeedbackButton className="min-w-0 flex-1" side="right" />
471
- {collapseButton}
472
- </div>
473
- ) : collapseButton ? (
474
- <div className="flex justify-center px-1 py-1">{collapseButton}</div>
475
- ) : null}
502
+ <SidebarFooterActions
503
+ collapsed={collapsed}
504
+ feedback={feedbackButton}
505
+ translate={translateButton}
506
+ search={searchButton}
507
+ collapse={collapseButton}
508
+ />
476
509
  </div>
477
510
  </aside>
478
511
  );
@@ -22,7 +22,7 @@
22
22
  --muted-foreground: 0 0% 45%;
23
23
  --accent: 0 0% 95%;
24
24
  --accent-foreground: 0 0% 15%;
25
- --destructive: 0 84% 60%;
25
+ --destructive: 0 91% 71%;
26
26
  --destructive-foreground: 0 0% 98%;
27
27
  --border: 0 0% 90%;
28
28
  --input: 0 0% 90%;
@@ -52,7 +52,7 @@
52
52
  --muted-foreground: 0 0% 60%;
53
53
  --accent: 0 0% 18%;
54
54
  --accent-foreground: 0 0% 90%;
55
- --destructive: 0 84% 60%;
55
+ --destructive: 0 91% 71%;
56
56
  --destructive-foreground: 0 0% 98%;
57
57
  --border: 0 0% 24%;
58
58
  --input: 0 0% 24%;
@@ -0,0 +1,151 @@
1
+ import { trackingIdentityProperties } from "../observability/tracking-identity.js";
2
+ import type { CaptureErrorContext } from "../server/capture-error.js";
3
+ import { track } from "./registry.js";
4
+
5
+ export type TrackingExceptionLevel =
6
+ | "fatal"
7
+ | "error"
8
+ | "warning"
9
+ | "info"
10
+ | "debug";
11
+
12
+ export interface TrackingExceptionContext extends CaptureErrorContext {
13
+ /** Whether the caller handled the error. Server error hooks default false. */
14
+ handled?: boolean;
15
+ level?: TrackingExceptionLevel;
16
+ release?: string;
17
+ environment?: string;
18
+ runtime?: "node" | "cli";
19
+ source?: "server" | "cli";
20
+ }
21
+
22
+ const MAX_MESSAGE_LENGTH = 1000;
23
+ const MAX_STACK_LENGTH = 8000;
24
+ const MAX_TAGS = 30;
25
+ const MAX_EXTRA_KEYS = 30;
26
+ const MAX_EXTRA_VALUE_LENGTH = 1000;
27
+ const SECRET_RE = /\b(?:bearer|basic)\s+[^\s]+/gi;
28
+ const SECRET_KEY_RE =
29
+ /(?:authorization|cookie|set[-_]?cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)/i;
30
+
31
+ function redact(value: string): string {
32
+ return value
33
+ .replace(SECRET_RE, (match) => `${match.split(/\s+/, 1)[0]} <redacted>`)
34
+ .replace(
35
+ /([A-Za-z0-9_$.-]*(?:authorization|cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|credential)[A-Za-z0-9_$.-]*\s*[:=]\s*)([^\s,;}]+)/gi,
36
+ "$1<redacted>",
37
+ );
38
+ }
39
+
40
+ function boundedText(value: unknown, max: number): string {
41
+ const text = typeof value === "string" ? value : String(value ?? "");
42
+ const safe = redact(text);
43
+ return safe.length > max ? safe.slice(0, max) : safe;
44
+ }
45
+
46
+ function safeValue(value: unknown, depth = 2): unknown {
47
+ if (
48
+ value == null ||
49
+ typeof value === "boolean" ||
50
+ typeof value === "number"
51
+ ) {
52
+ return value;
53
+ }
54
+ if (typeof value === "string")
55
+ return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
56
+ if (depth <= 0) return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
57
+ if (Array.isArray(value)) {
58
+ return value.slice(0, 20).map((item) => safeValue(item, depth - 1));
59
+ }
60
+ if (typeof value === "object") {
61
+ const out: Record<string, unknown> = {};
62
+ for (const [key, child] of Object.entries(value)) {
63
+ if (Object.keys(out).length >= MAX_EXTRA_KEYS) break;
64
+ const safeKey = boundedText(key, 100);
65
+ out[safeKey] = SECRET_KEY_RE.test(safeKey)
66
+ ? "<redacted>"
67
+ : safeValue(child, depth - 1);
68
+ }
69
+ return out;
70
+ }
71
+ return boundedText(value, MAX_EXTRA_VALUE_LENGTH);
72
+ }
73
+
74
+ function exceptionParts(error: unknown): {
75
+ type: string;
76
+ message: string;
77
+ stack?: string;
78
+ } {
79
+ if (error instanceof Error) {
80
+ return {
81
+ type: boundedText(error.name || "Error", 200),
82
+ message: boundedText(
83
+ error.message || error.name || "Error",
84
+ MAX_MESSAGE_LENGTH,
85
+ ),
86
+ ...(error.stack
87
+ ? { stack: boundedText(error.stack, MAX_STACK_LENGTH) }
88
+ : {}),
89
+ };
90
+ }
91
+ return {
92
+ type: "Error",
93
+ message: boundedText(error, MAX_MESSAGE_LENGTH),
94
+ };
95
+ }
96
+
97
+ function safeTags(
98
+ tags: Record<string, string | undefined> | undefined,
99
+ ): Record<string, string> {
100
+ const out: Record<string, string> = {};
101
+ for (const [key, value] of Object.entries(tags ?? {})) {
102
+ if (Object.keys(out).length >= MAX_TAGS || value == null) break;
103
+ const safeKey = boundedText(key, 100);
104
+ out[safeKey] = SECRET_KEY_RE.test(safeKey)
105
+ ? "<redacted>"
106
+ : boundedText(value, 200);
107
+ }
108
+ return out;
109
+ }
110
+
111
+ /** Emit a bounded, redacted Node/CLI exception through first-party tracking. */
112
+ export function captureException(
113
+ error: unknown,
114
+ context: TrackingExceptionContext = {},
115
+ ): void {
116
+ try {
117
+ const parts = exceptionParts(error);
118
+ const tags = safeTags({
119
+ ...context.tags,
120
+ ...(context.route ? { route: context.route } : {}),
121
+ ...(context.method ? { method: context.method } : {}),
122
+ ...(context.userAgent ? { userAgent: context.userAgent } : {}),
123
+ });
124
+ const extra = safeValue({
125
+ ...context.extra,
126
+ ...(context.contexts ? { contexts: context.contexts } : {}),
127
+ });
128
+ track("$exception", {
129
+ ...trackingIdentityProperties(),
130
+ exceptionType: parts.type,
131
+ exceptionMessage: parts.message,
132
+ ...(parts.stack ? { exceptionStack: parts.stack } : {}),
133
+ handled: context.handled ?? true,
134
+ level: context.level ?? "error",
135
+ occurredAt: new Date().toISOString(),
136
+ runtime: context.runtime ?? "node",
137
+ source: context.source ?? "server",
138
+ ...(context.route ? { url: boundedText(context.route, 500) } : {}),
139
+ ...(context.release
140
+ ? { release: boundedText(context.release, 200) }
141
+ : {}),
142
+ ...(context.environment
143
+ ? { environment: boundedText(context.environment, 100) }
144
+ : {}),
145
+ ...(Object.keys(tags).length ? { exceptionTags: tags } : {}),
146
+ ...(extra && typeof extra === "object" ? { exceptionExtra: extra } : {}),
147
+ });
148
+ } catch {
149
+ // Error reporting must never mask the original failure.
150
+ }
151
+ }
@@ -7,4 +7,9 @@ export {
7
7
  listTrackingProviders,
8
8
  } from "./registry.js";
9
9
  export { registerBuiltinProviders } from "./providers.js";
10
+ export {
11
+ captureException,
12
+ type TrackingExceptionContext,
13
+ type TrackingExceptionLevel,
14
+ } from "./error-capture.js";
10
15
  export type { TrackingProvider, TrackingEvent } from "./types.js";
@@ -52,7 +52,7 @@ export function CumulativeNetChart({
52
52
  const color = isPositive ? "#10b981" : "#ef4444";
53
53
 
54
54
  return (
55
- <Card className="bg-card border-border/50">
55
+ <Card className="bg-card">
56
56
  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
57
57
  <CardTitle className="text-base">{title}</CardTitle>
58
58
  </CardHeader>