@agent-native/core 0.133.2 → 0.134.0

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 (213) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +62 -0
  3. package/corpus/core/docs/content/observability.mdx +41 -1
  4. package/corpus/core/docs/content/tracking.mdx +25 -0
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/agent/run-manager.ts +7 -0
  7. package/corpus/core/src/client/agent-chat-adapter.ts +10 -0
  8. package/corpus/core/src/client/analytics.ts +105 -1
  9. package/corpus/core/src/client/use-action.ts +6 -0
  10. package/corpus/core/src/deploy/build.ts +38 -1
  11. package/corpus/core/src/observability/posthog-ai.ts +294 -0
  12. package/corpus/core/src/observability/routes.ts +29 -6
  13. package/corpus/core/src/observability/traces.ts +287 -12
  14. package/corpus/core/src/observability/types.ts +11 -0
  15. package/corpus/core/src/secrets/register-framework-secrets.ts +16 -0
  16. package/corpus/core/src/server/agent-run-context.ts +21 -0
  17. package/corpus/core/src/server/capture-error.ts +8 -0
  18. package/corpus/core/src/server/core-routes-plugin.ts +75 -4
  19. package/corpus/core/src/server/deploy-environment.ts +57 -0
  20. package/corpus/core/src/server/error-noise-filter.ts +268 -0
  21. package/corpus/core/src/server/posthog-config.ts +75 -0
  22. package/corpus/core/src/server/request-context.ts +6 -0
  23. package/corpus/core/src/server/sentry-config.ts +4 -8
  24. package/corpus/core/src/server/sentry-plugin.ts +13 -29
  25. package/corpus/core/src/server/sentry.ts +13 -156
  26. package/corpus/core/src/server/ssr-handler.ts +6 -1
  27. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  28. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  29. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  30. package/corpus/core/src/tracking/error-capture.ts +44 -109
  31. package/corpus/core/src/tracking/index.ts +11 -0
  32. package/corpus/core/src/tracking/posthog-exception.ts +305 -0
  33. package/corpus/core/src/tracking/providers.ts +91 -14
  34. package/corpus/core/src/tracking/redaction.ts +105 -0
  35. package/corpus/templates/content/actions/_batch-utils.ts +33 -0
  36. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -81
  37. package/corpus/templates/content/actions/_content-files.ts +31 -21
  38. package/corpus/templates/content/actions/_database-source-utils.ts +653 -155
  39. package/corpus/templates/content/actions/_database-utils.ts +535 -41
  40. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +291 -240
  41. package/corpus/templates/content/actions/add-database-item.ts +15 -1
  42. package/corpus/templates/content/actions/attach-content-database-source.ts +156 -58
  43. package/corpus/templates/content/actions/bind-content-database-source-field.ts +2 -2
  44. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +4 -1
  45. package/corpus/templates/content/actions/delete-database-items.ts +8 -2
  46. package/corpus/templates/content/actions/disconnect-content-database-source.ts +2 -2
  47. package/corpus/templates/content/actions/duplicate-database-item.ts +15 -1
  48. package/corpus/templates/content/actions/duplicate-database-items.ts +19 -1
  49. package/corpus/templates/content/actions/execute-builder-source-execution.ts +2 -1
  50. package/corpus/templates/content/actions/get-content-database.ts +12 -64
  51. package/corpus/templates/content/actions/move-database-item.ts +4 -1
  52. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
  53. package/corpus/templates/content/actions/prepare-builder-source-review.ts +4 -8
  54. package/corpus/templates/content/actions/preview-content-database-source-attach.ts +92 -0
  55. package/corpus/templates/content/actions/process-builder-body-hydration.ts +2 -1
  56. package/corpus/templates/content/actions/query-content-database-items.ts +64 -0
  57. package/corpus/templates/content/actions/refresh-content-database-source.ts +7 -0
  58. package/corpus/templates/content/actions/review-content-database-source-change-set.ts +1 -1
  59. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +1 -1
  60. package/corpus/templates/content/actions/stage-builder-revision.ts +1 -1
  61. package/corpus/templates/content/actions/update-content-database-personal-view.ts +29 -3
  62. package/corpus/templates/content/actions/update-content-database-view.ts +1 -1
  63. package/corpus/templates/content/actions/validate-builder-source-execution.ts +1 -1
  64. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +3 -22
  65. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +267 -105
  66. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +82 -1
  67. package/corpus/templates/content/app/hooks/use-content-database.ts +285 -24
  68. package/corpus/templates/content/app/hooks/use-document-properties.ts +7 -6
  69. package/corpus/templates/content/app/i18n-data.ts +10 -0
  70. package/corpus/templates/content/changelog/2026-07-29-large-databases-keep-useful-rows-visible.md +6 -0
  71. package/corpus/templates/content/changelog/2026-07-30-builder-source-columns-now-appear-immediately-when-connected.md +6 -0
  72. package/corpus/templates/content/changelog/2026-07-30-large-builder-backed-tables-show-useful-rows-sooner-and-fini.md +6 -0
  73. package/corpus/templates/content/changelog/2026-08-01-large-builder-databases-now-show-rows-immediately-and-finish.md +6 -0
  74. package/corpus/templates/content/parity/matrix.md +2 -1
  75. package/corpus/templates/content/parity/matrix.ts +25 -0
  76. package/corpus/templates/content/shared/api.ts +54 -4
  77. package/corpus/templates/content/shared/database-query.ts +359 -0
  78. package/dist/agent/run-manager.d.ts.map +1 -1
  79. package/dist/agent/run-manager.js +7 -0
  80. package/dist/agent/run-manager.js.map +1 -1
  81. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  82. package/dist/client/agent-chat-adapter.js +12 -0
  83. package/dist/client/agent-chat-adapter.js.map +1 -1
  84. package/dist/client/analytics.d.ts +8 -0
  85. package/dist/client/analytics.d.ts.map +1 -1
  86. package/dist/client/analytics.js +95 -1
  87. package/dist/client/analytics.js.map +1 -1
  88. package/dist/client/use-action.d.ts.map +1 -1
  89. package/dist/client/use-action.js +6 -0
  90. package/dist/client/use-action.js.map +1 -1
  91. package/dist/collab/routes.d.ts +1 -1
  92. package/dist/collab/struct-routes.d.ts +1 -1
  93. package/dist/deploy/build.d.ts.map +1 -1
  94. package/dist/deploy/build.js +38 -1
  95. package/dist/deploy/build.js.map +1 -1
  96. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  97. package/dist/notifications/routes.d.ts +3 -3
  98. package/dist/observability/posthog-ai.d.ts +126 -0
  99. package/dist/observability/posthog-ai.d.ts.map +1 -0
  100. package/dist/observability/posthog-ai.js +190 -0
  101. package/dist/observability/posthog-ai.js.map +1 -0
  102. package/dist/observability/routes.d.ts +1 -1
  103. package/dist/observability/routes.d.ts.map +1 -1
  104. package/dist/observability/routes.js +26 -6
  105. package/dist/observability/routes.js.map +1 -1
  106. package/dist/observability/traces.d.ts +9 -0
  107. package/dist/observability/traces.d.ts.map +1 -1
  108. package/dist/observability/traces.js +219 -10
  109. package/dist/observability/traces.js.map +1 -1
  110. package/dist/observability/types.d.ts +10 -0
  111. package/dist/observability/types.d.ts.map +1 -1
  112. package/dist/observability/types.js +1 -0
  113. package/dist/observability/types.js.map +1 -1
  114. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  115. package/dist/secrets/register-framework-secrets.js +14 -0
  116. package/dist/secrets/register-framework-secrets.js.map +1 -1
  117. package/dist/server/agent-run-context.d.ts +8 -0
  118. package/dist/server/agent-run-context.d.ts.map +1 -1
  119. package/dist/server/agent-run-context.js +18 -0
  120. package/dist/server/agent-run-context.js.map +1 -1
  121. package/dist/server/capture-error.d.ts +8 -0
  122. package/dist/server/capture-error.d.ts.map +1 -1
  123. package/dist/server/capture-error.js.map +1 -1
  124. package/dist/server/core-routes-plugin.d.ts +0 -23
  125. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  126. package/dist/server/core-routes-plugin.js +71 -7
  127. package/dist/server/core-routes-plugin.js.map +1 -1
  128. package/dist/server/deploy-environment.d.ts +17 -0
  129. package/dist/server/deploy-environment.d.ts.map +1 -0
  130. package/dist/server/deploy-environment.js +47 -0
  131. package/dist/server/deploy-environment.js.map +1 -0
  132. package/dist/server/error-noise-filter.d.ts +80 -0
  133. package/dist/server/error-noise-filter.d.ts.map +1 -0
  134. package/dist/server/error-noise-filter.js +173 -0
  135. package/dist/server/error-noise-filter.js.map +1 -0
  136. package/dist/server/posthog-config.d.ts +28 -0
  137. package/dist/server/posthog-config.d.ts.map +1 -0
  138. package/dist/server/posthog-config.js +53 -0
  139. package/dist/server/posthog-config.js.map +1 -0
  140. package/dist/server/request-context.d.ts +6 -0
  141. package/dist/server/request-context.d.ts.map +1 -1
  142. package/dist/server/request-context.js.map +1 -1
  143. package/dist/server/sentry-config.d.ts +1 -0
  144. package/dist/server/sentry-config.d.ts.map +1 -1
  145. package/dist/server/sentry-config.js +3 -1
  146. package/dist/server/sentry-config.js.map +1 -1
  147. package/dist/server/sentry-plugin.d.ts.map +1 -1
  148. package/dist/server/sentry-plugin.js +12 -27
  149. package/dist/server/sentry-plugin.js.map +1 -1
  150. package/dist/server/sentry.d.ts.map +1 -1
  151. package/dist/server/sentry.js +8 -124
  152. package/dist/server/sentry.js.map +1 -1
  153. package/dist/server/ssr-handler.d.ts.map +1 -1
  154. package/dist/server/ssr-handler.js +6 -1
  155. package/dist/server/ssr-handler.js.map +1 -1
  156. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  157. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  158. package/dist/shared/mcp-embed-headers.js +1 -1
  159. package/dist/shared/mcp-embed-headers.js.map +1 -1
  160. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  161. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  162. package/dist/tracking/error-capture.d.ts +7 -0
  163. package/dist/tracking/error-capture.d.ts.map +1 -1
  164. package/dist/tracking/error-capture.js +10 -73
  165. package/dist/tracking/error-capture.js.map +1 -1
  166. package/dist/tracking/index.d.ts +1 -0
  167. package/dist/tracking/index.d.ts.map +1 -1
  168. package/dist/tracking/index.js +1 -0
  169. package/dist/tracking/index.js.map +1 -1
  170. package/dist/tracking/posthog-exception.d.ts +103 -0
  171. package/dist/tracking/posthog-exception.d.ts.map +1 -0
  172. package/dist/tracking/posthog-exception.js +181 -0
  173. package/dist/tracking/posthog-exception.js.map +1 -0
  174. package/dist/tracking/providers.d.ts +14 -0
  175. package/dist/tracking/providers.d.ts.map +1 -1
  176. package/dist/tracking/providers.js +63 -11
  177. package/dist/tracking/providers.js.map +1 -1
  178. package/dist/tracking/redaction.d.ts +25 -0
  179. package/dist/tracking/redaction.d.ts.map +1 -0
  180. package/dist/tracking/redaction.js +85 -0
  181. package/dist/tracking/redaction.js.map +1 -0
  182. package/docs/content/observability.mdx +41 -1
  183. package/docs/content/tracking.mdx +25 -0
  184. package/package.json +1 -1
  185. package/src/agent/run-manager.ts +7 -0
  186. package/src/client/agent-chat-adapter.ts +10 -0
  187. package/src/client/analytics.ts +105 -1
  188. package/src/client/use-action.ts +6 -0
  189. package/src/deploy/build.ts +38 -1
  190. package/src/observability/posthog-ai.ts +294 -0
  191. package/src/observability/routes.ts +29 -6
  192. package/src/observability/traces.ts +287 -12
  193. package/src/observability/types.ts +11 -0
  194. package/src/secrets/register-framework-secrets.ts +16 -0
  195. package/src/server/agent-run-context.ts +21 -0
  196. package/src/server/capture-error.ts +8 -0
  197. package/src/server/core-routes-plugin.ts +75 -4
  198. package/src/server/deploy-environment.ts +57 -0
  199. package/src/server/error-noise-filter.ts +268 -0
  200. package/src/server/posthog-config.ts +75 -0
  201. package/src/server/request-context.ts +6 -0
  202. package/src/server/sentry-config.ts +4 -8
  203. package/src/server/sentry-plugin.ts +13 -29
  204. package/src/server/sentry.ts +13 -156
  205. package/src/server/ssr-handler.ts +6 -1
  206. package/src/shared/mcp-embed-headers.ts +1 -1
  207. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +36 -10
  208. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +55 -1
  209. package/src/tracking/error-capture.ts +44 -109
  210. package/src/tracking/index.ts +11 -0
  211. package/src/tracking/posthog-exception.ts +305 -0
  212. package/src/tracking/providers.ts +91 -14
  213. package/src/tracking/redaction.ts +105 -0
package/corpus/README.md CHANGED
@@ -28,6 +28,6 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
 
29
29
  ## Generated Counts
30
30
 
31
- - core files: 1646
31
+ - core files: 1652
32
32
  - toolkit files: 168
33
- - template files: 7414
33
+ - template files: 7421
@@ -1,5 +1,67 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.134.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 46cd162: Make PostHog a first-class error-reporting and LLM-observability backend, and fix
8
+ the malformed exception events it was already receiving.
9
+
10
+ `captureException()` emitted an event named `$exception` carrying camelCase
11
+ properties. PostHog ingests anything by that name and renders it as an issue, but
12
+ it groups and symbolicates from `$exception_list` — so every PostHog-configured
13
+ app was already collecting exceptions that arrived empty and ungroupable, which
14
+ reads as coverage rather than as a failure. The PostHog provider now reshapes
15
+ those into a real `$exception_list` with parsed stack frames.
16
+
17
+ Route errors no longer depend on Sentry. The Nitro `error` hook lived inside
18
+ `sentry-plugin.ts`, which returns early when no `SENTRY_DSN` is set, so an app
19
+ running PostHog alone reported no route errors at all. The hook moved to
20
+ `core-routes-plugin.ts` and goes through the provider-agnostic `captureError()`
21
+ registry, so every configured backend receives it. The ~150 lines of
22
+ production-tuned drop rules (expected 4xx, permission rejections, Lambda
23
+ freeze/thaw `socket hang up`) moved out of Sentry's `beforeSend` into
24
+ `server/error-noise-filter.ts` and now apply to every backend — without them a
25
+ second backend receives a firehose. Server exceptions are also attributed to the
26
+ in-flight user instead of landing under `anonymous`.
27
+
28
+ Browser exceptions go to PostHog when `POSTHOG_PUBLIC_KEY` / `VITE_POSTHOG_KEY`
29
+ is set, posted directly rather than relayed through `/_agent-native/track`, which
30
+ requires a session and would drop every signed-out crash. `POSTHOG_API_KEY` is
31
+ deliberately not a fallback for the public key: that value is inlined into the
32
+ public HTML shell. Note that PostHog does not symbolicate without uploaded source
33
+ maps, so minified browser stacks stay minified.
34
+
35
+ LLM observability now emits the full PostHog trace tree. Previously a run
36
+ produced a single `$ai_generation` labelled `agent_run` whose `$ai_parent_id`
37
+ pointed at a span that was never sent, so PostHog wrapped it in a placeholder
38
+ trace with no steps. Runs now emit `$ai_trace`, one `$ai_span` per tool call, and
39
+ a generation parented to the trace. Tool calls ship inside `$ai_output_choices`
40
+ even with content capture off, because that is the only thing PostHog derives
41
+ `$ai_tools_called` from. The previously dead `capturePrompts` flag is now wired
42
+ and gates `$ai_input` and assistant text; disabled fields are omitted rather than
43
+ sent empty, and oversized content is replaced with an explicit truncation marker
44
+ instead of being silently shortened. `$ai_error` became a structured object with
45
+ the terminal code and retryability, and errors captured during a run carry the
46
+ run's `$ai_trace_id` so an issue and its trace resolve to each other.
47
+
48
+ Feedback previously emitted only for thumbs; category and free-text submissions
49
+ emitted nothing. All four now report, with `sentiment` still limited to thumbs so
50
+ a category follow-up does not double-count the vote. PostHog surfaces feedback in
51
+ LLM analytics only through a `survey sent` event, so that is emitted too when
52
+ `POSTHOG_AI_FEEDBACK_SURVEY_ID` is configured — and not at all when it is unset,
53
+ rather than inventing a survey id.
54
+
55
+ Agent traces carry the browser session as `$session_id` (read from a new
56
+ `X-Agent-Native-Session-Id` header) so a trace joins its session replay, distinct
57
+ from `$ai_session_id`, which remains the conversation thread.
58
+
59
+ ## 0.133.3
60
+
61
+ ### Patch Changes
62
+
63
+ - 9258da4: Preserve nested object parameters when browser clients call GET actions.
64
+
3
65
  ## 0.133.2
4
66
 
5
67
  ### Patch Changes
@@ -256,6 +256,7 @@ All settings are stored in the `observability-config` key:
256
256
  capturePrompts: false, // Store prompt content in traces
257
257
  captureToolArgs: false, // Store action input arguments
258
258
  captureToolResults: false, // Store action results
259
+ captureLlmSpans: true, // Emit one $ai_span per tool call to PostHog
259
260
  evalSampleRate: 0, // 0-1, fraction of runs to LLM-judge
260
261
  exporters: [] // OTLP export targets
261
262
  }
@@ -291,6 +292,39 @@ All auto-mounted at `/_agent-native/observability/`:
291
292
 
292
293
  All endpoints support `?since=N` (ms timestamp) and `?limit=N` query params.
293
294
 
295
+ ## PostHog LLM analytics {#posthog}
296
+
297
+ When `POSTHOG_API_KEY` is set, every instrumented run is mirrored to PostHog's
298
+ LLM analytics as a full trace tree:
299
+
300
+ | Event | Emitted per | Carries |
301
+ | ---------------- | ----------- | ------------------------------------------------------- |
302
+ | `$ai_trace` | agent run | Latency, error state, token totals, cost |
303
+ | `$ai_generation` | model call | Model, provider, tokens, cost, tool definitions offered |
304
+ | `$ai_span` | tool call | Tool name, duration, error state |
305
+
306
+ Nodes share the run id as `$ai_trace_id`, so PostHog renders one tree per run.
307
+ `$ai_session_id` is the conversation thread; the browser session is sent
308
+ separately as `$session_id` so a trace links to its session replay.
309
+
310
+ Tool calls always appear (PostHog derives its tool tags from them), but their
311
+ **arguments** require `captureToolArgs` and message content requires
312
+ `capturePrompts`. When those are off the fields are omitted entirely rather than
313
+ sent empty, so an unrecorded prompt is never mistaken for an empty one.
314
+
315
+ Errors carry a structured `$ai_error` with the terminal code and whether the
316
+ failure was retryable, and exceptions captured during a run carry the run's
317
+ `$ai_trace_id` — so an issue in error tracking and the trace it came from link
318
+ to each other.
319
+
320
+ ### Feedback in PostHog
321
+
322
+ Thumbs, category, and free-text feedback all emit `$ai_feedback`. PostHog's LLM
323
+ analytics feedback view reads a `survey sent` event instead, so set
324
+ `POSTHOG_AI_FEEDBACK_SURVEY_ID` (and `POSTHOG_AI_FEEDBACK_SURVEY_QUESTION_ID`
325
+ for multi-question surveys) to populate it. Without a survey id no survey event
326
+ is sent.
327
+
294
328
  ## Export to external platforms {#export}
295
329
 
296
330
  Send traces to Langfuse, Datadog, Grafana, or any OTel-compatible backend:
@@ -331,10 +365,16 @@ The agent loop emits three span kinds:
331
365
 
332
366
  Spans are finished with OK/ERROR status and record the error message on failure. Zero/sentinel attribute values are pruned so spans aren't cluttered with noise. This OTel layer is purely additive to the in-house `agent_trace_spans` / `agent_trace_summaries` tables that power the dashboard above — both are produced from the same run events.
333
367
 
334
- ## Error reporting (Sentry) {#sentry}
368
+ ## Error reporting (Sentry or PostHog) {#sentry}
335
369
 
336
370
  Server-side errors that escape Nitro route handlers are reported to Sentry when a DSN is configured. Without it the SDK silently no-ops, so it's safe to leave the env vars unset in dev. Browser and server events can go to the same Sentry project; split them into separate projects only when you want operational separation for ownership, volume, quotas, or alert routing.
337
371
 
372
+ Sentry is not required. Error reporting fans out to every configured backend, so
373
+ setting only `POSTHOG_API_KEY` gives full server-side error tracking with no
374
+ Sentry project at all — see [Tracking](/docs/tracking#posthog-error-tracking).
375
+ Configure both and each error goes to both. The noise-filtering rules below are
376
+ shared by every backend rather than being Sentry-specific.
377
+
338
378
  | Surface | SDK | Env var | Notes |
339
379
  | ------------------ | ----------------- | -------------------------------------------------------------- | --------------------------------------------------------------------- |
340
380
  | Browser / SPA | `@sentry/browser` | `VITE_SENTRY_CLIENT_DSN`, `SENTRY_CLIENT_DSN`, or `SENTRY_DSN` | Captures unhandled errors and route-change breadcrumbs in the client. |
@@ -89,6 +89,31 @@ Set an env var and the provider auto-registers at server startup. No code change
89
89
 
90
90
  Multiple providers can be active simultaneously. Every event goes to all of them.
91
91
 
92
+ ## PostHog error tracking {#posthog-error-tracking}
93
+
94
+ With `POSTHOG_API_KEY` set, PostHog also receives server exceptions — no Sentry
95
+ DSN required. Server errors are captured automatically from the framework's
96
+ route error hook and attributed to the signed-in user, with production-tuned
97
+ noise filtering (expected 4xx responses, permission rejections, and serverless
98
+ socket churn are dropped rather than reported).
99
+
100
+ | Env var | Purpose |
101
+ | ------------------------ | ------------------------------------------------------------------- |
102
+ | `POSTHOG_ERROR_TRACKING` | Set to `false` to keep analytics but send exceptions somewhere else |
103
+ | `POSTHOG_PUBLIC_KEY` | Enables browser exception capture (also read as `VITE_POSTHOG_KEY`) |
104
+ | `POSTHOG_PUBLIC_HOST` | Browser ingest host (defaults to `POSTHOG_HOST`) |
105
+
106
+ `POSTHOG_PUBLIC_KEY` is separate from `POSTHOG_API_KEY` on purpose: the public
107
+ key is inlined into the HTML served to every visitor, so the server key is never
108
+ used as a fallback for it.
109
+
110
+ <Callout tone="info">
111
+ PostHog does not symbolicate stack traces without uploaded source maps, so
112
+ **minified browser stacks stay minified**. Server-side stacks are unaffected.
113
+ If you need symbolicated browser traces today, keep Sentry configured
114
+ alongside PostHog — both receive every captured error.
115
+ </Callout>
116
+
92
117
  ## API {#api}
93
118
 
94
119
  ### `track(name, properties?, meta?)` {#track}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.133.2",
3
+ "version": "0.134.0",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -735,6 +735,7 @@ export function startRun(
735
735
  ) => {
736
736
  captureError(error, {
737
737
  route: "/_agent-native/agent-chat",
738
+ aiTraceId: runId,
738
739
  tags: {
739
740
  source: "agent-run-manager",
740
741
  phase,
@@ -1022,6 +1023,7 @@ export function startRun(
1022
1023
  phase: "abort-check",
1023
1024
  consecutiveFailures: String(consecutiveAbortCheckFailures),
1024
1025
  },
1026
+ aiTraceId: runId,
1025
1027
  extra: { runId, threadId },
1026
1028
  });
1027
1029
  if (!abort.signal.aborted) {
@@ -1065,6 +1067,7 @@ export function startRun(
1065
1067
  phase: "heartbeat",
1066
1068
  consecutiveFailures: String(consecutiveHeartbeatFailures),
1067
1069
  },
1070
+ aiTraceId: runId,
1068
1071
  extra: { runId, threadId },
1069
1072
  });
1070
1073
  }
@@ -1108,6 +1111,7 @@ export function startRun(
1108
1111
  const errorCode = getRunErrorCode(error);
1109
1112
  captureError(error, {
1110
1113
  route: "/_agent-native/agent-chat",
1114
+ aiTraceId: runId,
1111
1115
  tags: {
1112
1116
  source: "agent-run-manager",
1113
1117
  phase,
@@ -1686,6 +1690,7 @@ function subscribeFromSQL(
1686
1690
  };
1687
1691
  captureError(error, {
1688
1692
  route: "/_agent-native/agent-chat/runs/:id/events",
1693
+ aiTraceId: runId,
1689
1694
  tags: {
1690
1695
  source: "agent-run-manager",
1691
1696
  phase: "sql-subscription-poll",
@@ -2313,6 +2318,7 @@ export async function abortRunDurably(
2313
2318
  // the request report the abort it did complete.
2314
2319
  captureError(error, {
2315
2320
  route: "/_agent-native/agent-chat/runs/:id/abort",
2321
+ aiTraceId: runId,
2316
2322
  tags: {
2317
2323
  source: "agent-run-manager",
2318
2324
  phase: "abort-run",
@@ -2356,6 +2362,7 @@ export async function abortTurnDurably(
2356
2362
  // visible rather than silent.
2357
2363
  captureError(error, {
2358
2364
  route: "/_agent-native/agent-chat/runs/:id/abort",
2365
+ aiTraceId: runId,
2359
2366
  tags: { source: "agent-run-manager", phase: "abort-turn" },
2360
2367
  extra: { runId, reason, ...ref },
2361
2368
  });
@@ -18,6 +18,7 @@ import {
18
18
  clearActiveRun,
19
19
  setPendingTurn,
20
20
  } from "./active-run-state.js";
21
+ import { getOrCreateAnalyticsSessionId } from "./analytics-session.js";
21
22
  import { captureError } from "./analytics.js";
22
23
  import { agentNativePath } from "./api-path.js";
23
24
  import { formatChatErrorText, normalizeChatError } from "./error-format.js";
@@ -2075,6 +2076,15 @@ export function createAgentChatAdapter(
2075
2076
  } catch {
2076
2077
  // Non-browser or Intl unavailable — tool calls will fall back to UTC.
2077
2078
  }
2079
+ try {
2080
+ // Lets the run's `$ai_*` events carry PostHog's `$session_id`, so an
2081
+ // agent trace joins to the session replay it happened in.
2082
+ const sessionId = getOrCreateAnalyticsSessionId();
2083
+ if (sessionId) headers["x-agent-native-session-id"] = sessionId;
2084
+ // coercion-ok: replay linkage must not block sending the message
2085
+ } catch {
2086
+ // Analytics session unavailable — traces just lose replay linkage.
2087
+ }
2078
2088
  // Surface hint — the server uses this to keep code-editing dev tools
2079
2089
  // out of the app-rendered sidebar. The outer dev frame passes
2080
2090
  // "dev-frame" explicitly; the reusable in-product chat defaults to
@@ -5,6 +5,7 @@ import {
5
5
  llmConnectionTrackingProperties,
6
6
  type LlmConnectionStatus,
7
7
  } from "../shared/llm-connection.js";
8
+ import { toPostHogExceptionProperties } from "../tracking/posthog-exception.js";
8
9
  import {
9
10
  getOrCreateAnalyticsAnonymousId,
10
11
  getOrCreateAnalyticsSessionId,
@@ -54,6 +55,14 @@ declare global {
54
55
  __AGENT_NATIVE_CONFIG__?: {
55
56
  sentryDsn?: string;
56
57
  sentryEnvironment?: string;
58
+ /**
59
+ * Public PostHog project key + host. Publishable and identical for every
60
+ * visitor, so it ships inside the CDN-cached SSR shell alongside the
61
+ * Sentry DSN. Absent when no public key is configured.
62
+ */
63
+ posthogKey?: string;
64
+ posthogHost?: string;
65
+ posthogErrorTracking?: boolean;
57
66
  /**
58
67
  * Hosted Realtime Gateway config. Impersonal (same for every visitor),
59
68
  * so it is safe inside the CDN-cached SSR shell — unlike the per-user
@@ -442,6 +451,15 @@ function applyTrackingIdentity(
442
451
  return next;
443
452
  }
444
453
 
454
+ /**
455
+ * The signed-in user id used to attribute browser events, or `undefined` when
456
+ * signed out. Same value the server attributes its events to (email, falling
457
+ * back to the auth user id), so a person is one person across both.
458
+ */
459
+ function getTrackingUserId(): string | undefined {
460
+ return _trackingIdentity?.userId;
461
+ }
462
+
445
463
  function getOrCreateAnonymousId(): string | undefined {
446
464
  return getOrCreateAnalyticsAnonymousId();
447
465
  }
@@ -1313,6 +1331,89 @@ function exceptionEventProperties(
1313
1331
  };
1314
1332
  }
1315
1333
 
1334
+ /**
1335
+ * Resolve browser PostHog config, or `undefined` when error capture should not
1336
+ * reach PostHog. Reads the SSR-injected shell config first, then Vite env for
1337
+ * static/SPA builds that never render through the SSR handler.
1338
+ */
1339
+ function posthogErrorConfig(): { key: string; host: string } | undefined {
1340
+ const shell = window.__AGENT_NATIVE_CONFIG__;
1341
+ if (shell?.posthogErrorTracking === false) return undefined;
1342
+ const env = import.meta.env as Record<string, string | undefined>;
1343
+ // Static/SPA builds never render through the SSR handler, so they never see
1344
+ // the shell config that carries the server-side opt-out. Without this a
1345
+ // Vite-only deployment could not honour `POSTHOG_ERROR_TRACKING=false`
1346
+ // short of deleting the public key.
1347
+ if (env?.VITE_POSTHOG_ERROR_TRACKING?.trim().toLowerCase() === "false") {
1348
+ return undefined;
1349
+ }
1350
+ const key = shell?.posthogKey || env?.VITE_POSTHOG_KEY;
1351
+ if (!key) return undefined;
1352
+ const host = (
1353
+ shell?.posthogHost ||
1354
+ env?.VITE_POSTHOG_HOST ||
1355
+ "https://us.i.posthog.com"
1356
+ ).replace(/\/+$/, "");
1357
+ return { key, host };
1358
+ }
1359
+
1360
+ /**
1361
+ * Send the exception straight to PostHog's event endpoint.
1362
+ *
1363
+ * Not relayed through `/_agent-native/track`: that route requires a resolved
1364
+ * session, so every signed-out crash would be dropped without a trace.
1365
+ *
1366
+ * `distinct_id` uses the signed-in user id when we have one so browser events
1367
+ * join the server's events for the same person; otherwise the stable anonymous
1368
+ * id, which `$identify` later aliases on login.
1369
+ */
1370
+ function sendPostHogExceptionEvent(event: CapturedExceptionEvent): void {
1371
+ const config = posthogErrorConfig();
1372
+ if (!config) return;
1373
+
1374
+ try {
1375
+ const session = errorCaptureSessionContext();
1376
+ const body = JSON.stringify({
1377
+ api_key: config.key,
1378
+ event: AGENT_NATIVE_EXCEPTION_EVENT_NAME,
1379
+ properties: {
1380
+ distinct_id: getTrackingUserId() || session.anonymousId || "anonymous",
1381
+ ...toPostHogExceptionProperties({
1382
+ type: event.type,
1383
+ value: event.message,
1384
+ stack: event.stack,
1385
+ handled: event.handled,
1386
+ level: event.level,
1387
+ }),
1388
+ $current_url: event.url,
1389
+ $session_id: session.sessionId,
1390
+ ...(session.replayId ? { $replay_id: session.replayId } : {}),
1391
+ ...(event.release ? { release: event.release } : {}),
1392
+ ...(event.environment ? { environment: event.environment } : {}),
1393
+ ...(event.tags ? { exceptionTags: event.tags } : {}),
1394
+ source: "browser",
1395
+ },
1396
+ timestamp: event.occurredAt,
1397
+ });
1398
+ const endpoint = `${config.host}/i/v0/e/`;
1399
+
1400
+ if (navigator.sendBeacon) {
1401
+ // text/plain avoids a CORS preflight the page may not survive.
1402
+ const blob = new Blob([body], { type: "text/plain;charset=UTF-8" });
1403
+ if (navigator.sendBeacon(endpoint, blob)) return;
1404
+ }
1405
+ fetch(endpoint, {
1406
+ method: "POST",
1407
+ body,
1408
+ keepalive: true,
1409
+ headers: { "Content-Type": "text/plain;charset=UTF-8" },
1410
+ }).catch(() => {});
1411
+ // coercion-ok: throwing would replace the page's real error
1412
+ } catch {
1413
+ // Error reporting must never mask the original failure.
1414
+ }
1415
+ }
1416
+
1316
1417
  function sendExceptionEvent(event: CapturedExceptionEvent): void {
1317
1418
  // Route through the existing first-party analytics ingest as a dedicated
1318
1419
  // `$exception` event. This reuses the public-key auth + sendBeacon/keepalive
@@ -1322,6 +1423,7 @@ function sendExceptionEvent(event: CapturedExceptionEvent): void {
1322
1423
  AGENT_NATIVE_EXCEPTION_EVENT_NAME,
1323
1424
  exceptionEventProperties(event),
1324
1425
  );
1426
+ sendPostHogExceptionEvent(event);
1325
1427
  }
1326
1428
 
1327
1429
  function emitExceptionToReplay(event: CapturedExceptionEvent): void {
@@ -1339,7 +1441,9 @@ function errorCaptureAutoEnabled(): boolean {
1339
1441
  _agentNativeAnalyticsPublicKey ||
1340
1442
  (import.meta.env as Record<string, string | undefined>)
1341
1443
  ?.VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY;
1342
- return !!publicKey;
1444
+ // A PostHog public key is an equally explicit opt-in — an app running
1445
+ // PostHog and no Agent Native Analytics should still report browser crashes.
1446
+ return !!publicKey || !!posthogErrorConfig();
1343
1447
  }
1344
1448
 
1345
1449
  function maybeInstallErrorCapture(
@@ -207,6 +207,12 @@ function appendActionQueryParam(
207
207
  }
208
208
  return;
209
209
  }
210
+ if (typeof value === "object") {
211
+ // defineAction restores JSON strings when the schema expects an object.
212
+ // Preserve nested GET params instead of collapsing them to "[object Object]".
213
+ qs.append(key, JSON.stringify(value));
214
+ return;
215
+ }
210
216
  qs.append(key, String(value));
211
217
  }
212
218
 
@@ -1157,6 +1157,39 @@ function getSentryClientConfigScript() {
1157
1157
  );
1158
1158
  }
1159
1159
 
1160
+ function getPostHogClientConfigScript() {
1161
+ // MUST stay consistent with resolvePublicPostHogConfig in
1162
+ // server/posthog-config.ts (worker bundles a string copy; it can't import it).
1163
+ // Never falls back to POSTHOG_API_KEY — that key can be a private one and
1164
+ // this string is inlined into the public, CDN-cached HTML shell.
1165
+ const env = globalThis.process?.env || {};
1166
+ const posthogKey = firstNonEmpty(
1167
+ env.POSTHOG_PUBLIC_KEY,
1168
+ env.VITE_POSTHOG_KEY,
1169
+ env.VITE_POSTHOG_PUBLIC_KEY,
1170
+ );
1171
+ if (!posthogKey) return null;
1172
+ const posthogHost = (
1173
+ firstNonEmpty(
1174
+ env.POSTHOG_PUBLIC_HOST,
1175
+ env.VITE_POSTHOG_HOST,
1176
+ env.POSTHOG_HOST,
1177
+ ) || "https://us.i.posthog.com"
1178
+ ).replace(/\\/+$/, "");
1179
+ const config = {
1180
+ posthogKey,
1181
+ posthogHost,
1182
+ posthogErrorTracking:
1183
+ (env.POSTHOG_ERROR_TRACKING || "").trim().toLowerCase() !== "false",
1184
+ };
1185
+ return (
1186
+ '<script data-agent-native-posthog-config>' +
1187
+ 'window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,' +
1188
+ JSON.stringify(config) +
1189
+ ");</script>"
1190
+ );
1191
+ }
1192
+
1160
1193
  function getRealtimeClientConfigScript() {
1161
1194
  // MUST stay byte-for-byte consistent with resolveRealtimeClientConfig in
1162
1195
  // server/sentry-config.ts (worker bundles a string copy; it can't import it).
@@ -1330,7 +1363,11 @@ function applyImmutableAssetCacheHeaders(response, request) {
1330
1363
 
1331
1364
  async function rewriteMountedResponse(response, basePath, pathname, request) {
1332
1365
  const clientConfigScript =
1333
- [getSentryClientConfigScript(), getRealtimeClientConfigScript()]
1366
+ [
1367
+ getSentryClientConfigScript(),
1368
+ getPostHogClientConfigScript(),
1369
+ getRealtimeClientConfigScript(),
1370
+ ]
1334
1371
  .filter(Boolean)
1335
1372
  .join("") || null;
1336
1373
  const headers = new Headers(response.headers);