@agent-native/core 0.84.66 → 0.85.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 (155) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +55 -0
  4. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +55 -0
  5. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +55 -0
  6. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +55 -0
  7. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +55 -0
  8. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +55 -0
  9. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +55 -0
  10. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +55 -0
  11. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +55 -0
  12. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +55 -0
  13. package/corpus/core/docs/content/tracking.mdx +2 -0
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/agent/production-agent.ts +199 -38
  16. package/corpus/core/src/agent/run-manager.ts +79 -0
  17. package/corpus/core/src/agent/run-store.ts +117 -5
  18. package/corpus/core/src/agent/types.ts +13 -0
  19. package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
  20. package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
  21. package/corpus/core/src/client/analytics.ts +2 -0
  22. package/corpus/core/src/client/error-format.ts +4 -0
  23. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
  24. package/corpus/core/src/client/session-replay.ts +774 -2
  25. package/corpus/core/src/client/sse-event-processor.ts +65 -9
  26. package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
  27. package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
  28. package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
  29. package/corpus/core/src/server/analytics.ts +1 -34
  30. package/corpus/core/src/server/core-routes-plugin.ts +91 -4
  31. package/corpus/core/src/server/http-response-telemetry.ts +189 -0
  32. package/corpus/core/src/server/self-dispatch.ts +29 -0
  33. package/corpus/core/src/server/ssr-handler.ts +18 -377
  34. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
  35. package/corpus/templates/analytics/AGENTS.md +13 -0
  36. package/corpus/templates/analytics/actions/view-screen.ts +25 -0
  37. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
  38. package/corpus/templates/analytics/app/global.css +55 -0
  39. package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
  40. package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
  41. package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
  42. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
  43. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
  44. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
  45. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
  46. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
  47. package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
  48. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
  49. package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
  50. package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
  54. package/corpus/templates/analytics/server/db/schema.ts +3 -0
  55. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
  56. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
  57. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
  58. package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
  59. package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
  60. package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
  61. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +0 -2
  62. package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
  63. package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
  64. package/corpus/templates/forms/server/lib/public-form-ssr.ts +0 -1
  65. package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
  66. package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
  67. package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
  68. package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
  69. package/corpus/templates/plan/server/plans.ts +26 -9
  70. package/dist/agent/production-agent.d.ts.map +1 -1
  71. package/dist/agent/production-agent.js +155 -34
  72. package/dist/agent/production-agent.js.map +1 -1
  73. package/dist/agent/run-manager.d.ts +29 -0
  74. package/dist/agent/run-manager.d.ts.map +1 -1
  75. package/dist/agent/run-manager.js +73 -0
  76. package/dist/agent/run-manager.js.map +1 -1
  77. package/dist/agent/run-store.d.ts +41 -0
  78. package/dist/agent/run-store.d.ts.map +1 -1
  79. package/dist/agent/run-store.js +100 -4
  80. package/dist/agent/run-store.js.map +1 -1
  81. package/dist/agent/types.d.ts +13 -0
  82. package/dist/agent/types.d.ts.map +1 -1
  83. package/dist/agent/types.js.map +1 -1
  84. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  85. package/dist/client/RunStuckBanner.js +13 -0
  86. package/dist/client/RunStuckBanner.js.map +1 -1
  87. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  88. package/dist/client/agent-chat-adapter.js +410 -0
  89. package/dist/client/agent-chat-adapter.js.map +1 -1
  90. package/dist/client/analytics.d.ts +1 -1
  91. package/dist/client/analytics.d.ts.map +1 -1
  92. package/dist/client/analytics.js.map +1 -1
  93. package/dist/client/error-format.d.ts.map +1 -1
  94. package/dist/client/error-format.js +2 -0
  95. package/dist/client/error-format.js.map +1 -1
  96. package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
  97. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  98. package/dist/client/session-replay.d.ts +36 -0
  99. package/dist/client/session-replay.d.ts.map +1 -1
  100. package/dist/client/session-replay.js +579 -1
  101. package/dist/client/session-replay.js.map +1 -1
  102. package/dist/client/sse-event-processor.d.ts +20 -1
  103. package/dist/client/sse-event-processor.d.ts.map +1 -1
  104. package/dist/client/sse-event-processor.js +42 -11
  105. package/dist/client/sse-event-processor.js.map +1 -1
  106. package/dist/client/use-run-stuck-detection.d.ts +14 -4
  107. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  108. package/dist/client/use-run-stuck-detection.js +18 -4
  109. package/dist/client/use-run-stuck-detection.js.map +1 -1
  110. package/dist/collab/awareness.d.ts +2 -2
  111. package/dist/collab/awareness.d.ts.map +1 -1
  112. package/dist/collab/routes.d.ts +1 -1
  113. package/dist/db/runtime-diagnostics.d.ts +52 -0
  114. package/dist/db/runtime-diagnostics.d.ts.map +1 -0
  115. package/dist/db/runtime-diagnostics.js +250 -0
  116. package/dist/db/runtime-diagnostics.js.map +1 -0
  117. package/dist/observability/routes.d.ts +5 -5
  118. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  119. package/dist/resources/handlers.d.ts +2 -2
  120. package/dist/secrets/routes.d.ts +3 -3
  121. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  122. package/dist/server/agent-chat-plugin.js +101 -2
  123. package/dist/server/agent-chat-plugin.js.map +1 -1
  124. package/dist/server/analytics.d.ts +1 -19
  125. package/dist/server/analytics.d.ts.map +1 -1
  126. package/dist/server/analytics.js +1 -31
  127. package/dist/server/analytics.js.map +1 -1
  128. package/dist/server/core-routes-plugin.d.ts +17 -0
  129. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  130. package/dist/server/core-routes-plugin.js +65 -5
  131. package/dist/server/core-routes-plugin.js.map +1 -1
  132. package/dist/server/http-response-telemetry.d.ts +4 -0
  133. package/dist/server/http-response-telemetry.d.ts.map +1 -0
  134. package/dist/server/http-response-telemetry.js +174 -0
  135. package/dist/server/http-response-telemetry.js.map +1 -0
  136. package/dist/server/self-dispatch.d.ts +17 -0
  137. package/dist/server/self-dispatch.d.ts.map +1 -1
  138. package/dist/server/self-dispatch.js +11 -0
  139. package/dist/server/self-dispatch.js.map +1 -1
  140. package/dist/server/ssr-handler.d.ts.map +1 -1
  141. package/dist/server/ssr-handler.js +18 -271
  142. package/dist/server/ssr-handler.js.map +1 -1
  143. package/dist/server/transcribe-voice.d.ts +1 -1
  144. package/docs/content/locales/ar-SA/tracking.mdx +55 -0
  145. package/docs/content/locales/de-DE/tracking.mdx +55 -0
  146. package/docs/content/locales/es-ES/tracking.mdx +55 -0
  147. package/docs/content/locales/fr-FR/tracking.mdx +55 -0
  148. package/docs/content/locales/hi-IN/tracking.mdx +55 -0
  149. package/docs/content/locales/ja-JP/tracking.mdx +55 -0
  150. package/docs/content/locales/ko-KR/tracking.mdx +55 -0
  151. package/docs/content/locales/pt-BR/tracking.mdx +55 -0
  152. package/docs/content/locales/zh-CN/tracking.mdx +55 -0
  153. package/docs/content/locales/zh-TW/tracking.mdx +55 -0
  154. package/docs/content/tracking.mdx +2 -0
  155. package/package.json +1 -1
@@ -30,18 +30,11 @@ import {
30
30
  AGENT_NATIVE_SOCIAL_IMAGE_WIDTH,
31
31
  withAgentNativeSocialImageCacheBuster,
32
32
  } from "../shared/social-meta.js";
33
- import {
34
- GA_CSP_CONNECT_HOSTS,
35
- GA_CSP_IMG_HOSTS,
36
- GA_CSP_SCRIPT_HOSTS,
37
- getGaInlineConfigScriptBody,
38
- } from "./analytics.js";
39
33
  import {
40
34
  getAppBasePathFromViteEnv,
41
35
  stripAppBasePath as canonicalStripAppBasePath,
42
36
  } from "./app-base-path.js";
43
37
  import { runWithRequestContext } from "./request-context.js";
44
- import { computeInlineScriptHash } from "./security-headers.js";
45
38
  import { getSentryClientConfigScript } from "./sentry-config.js";
46
39
 
47
40
  export {
@@ -258,376 +251,17 @@ function applyDefaultSpeculationRulesHeader(
258
251
  }
259
252
 
260
253
  /**
261
- * Extract the plain JS body from a `<script ...>body</script>` string.
262
- * Returns `null` if the input is falsy or has no recognisable `</script>` end.
263
- * Used to compute the sha256 hash of framework-injected inline scripts so the
264
- * hash can be listed in app-owned `script-src` CSP directives.
265
- */
266
- function extractScriptBody(scriptTag: string | null): string | null {
267
- if (!scriptTag) return null;
268
- const start = scriptTag.indexOf(">") + 1;
269
- const end = scriptTag.lastIndexOf("</script>");
270
- if (start <= 0 || end < start) return null;
271
- return scriptTag.slice(start, end);
272
- }
273
-
274
- type CspDirective = {
275
- name: string;
276
- tokens: string[];
277
- };
278
-
279
- const CSP_DIRECTIVES_WITH_VALUE_TOKENS = new Set([
280
- "base-uri",
281
- "block-all-mixed-content",
282
- "child-src",
283
- "connect-src",
284
- "default-src",
285
- "fenced-frame-src",
286
- "font-src",
287
- "form-action",
288
- "frame-ancestors",
289
- "frame-src",
290
- "img-src",
291
- "manifest-src",
292
- "media-src",
293
- "navigate-to",
294
- "object-src",
295
- "plugin-types",
296
- "prefetch-src",
297
- "referrer",
298
- "reflected-xss",
299
- "require-sri-for",
300
- "require-trusted-types-for",
301
- "report-to",
302
- "report-uri",
303
- "sandbox",
304
- "script-src",
305
- "script-src-attr",
306
- "script-src-elem",
307
- "style-src",
308
- "style-src-attr",
309
- "style-src-elem",
310
- "trusted-types",
311
- "upgrade-insecure-requests",
312
- "webrtc",
313
- "worker-src",
314
- ]);
315
-
316
- function hasCommaJoinedCspPolicies(policy: string): boolean {
317
- let commaIndex = policy.indexOf(",");
318
- while (commaIndex !== -1) {
319
- const afterComma = policy.slice(commaIndex + 1);
320
- const directive = /^\s+([a-z][a-z0-9-]*)(?=\s|;|$)/i.exec(afterComma)?.[1];
321
- if (
322
- directive &&
323
- CSP_DIRECTIVES_WITH_VALUE_TOKENS.has(directive.toLowerCase())
324
- ) {
325
- return true;
326
- }
327
- commaIndex = policy.indexOf(",", commaIndex + 1);
328
- }
329
- return false;
330
- }
331
-
332
- function parseCsp(policy: string): CspDirective[] {
333
- return policy
334
- .split(";")
335
- .map((part) => part.trim())
336
- .filter(Boolean)
337
- .map((part) => {
338
- const [name = "", ...tokens] = part.split(/\s+/);
339
- return { name: name.toLowerCase(), tokens };
340
- })
341
- .filter((directive) => directive.name);
342
- }
343
-
344
- function serializeCsp(directives: CspDirective[]): string {
345
- return directives
346
- .map((directive) =>
347
- [directive.name, ...directive.tokens].filter(Boolean).join(" "),
348
- )
349
- .join("; ");
350
- }
351
-
352
- function appendCspTokens(
353
- tokens: string[],
354
- additions: readonly string[],
355
- ): string[] {
356
- if (!additions.length) return tokens;
357
- const next = tokens.filter((token) => token !== "'none'");
358
- const seen = new Set(next);
359
- for (const token of additions) {
360
- if (!token || seen.has(token)) continue;
361
- next.push(token);
362
- seen.add(token);
363
- }
364
- return next;
365
- }
366
-
367
- function findCspDirective(
368
- directives: CspDirective[],
369
- name: string,
370
- ): CspDirective | undefined {
371
- return directives.find((directive) => directive.name === name);
372
- }
373
-
374
- function appendToExistingOrDefaultCspDirective(
375
- directives: CspDirective[],
376
- name: string,
377
- additions: readonly string[],
378
- ): void {
379
- if (!additions.length) return;
380
- const existing = findCspDirective(directives, name);
381
- if (existing) {
382
- existing.tokens = appendCspTokens(existing.tokens, additions);
383
- return;
384
- }
385
-
386
- const defaultSrc = findCspDirective(directives, "default-src");
387
- if (!defaultSrc) return;
388
- directives.push({
389
- name,
390
- tokens: appendCspTokens([...defaultSrc.tokens], additions),
391
- });
392
- }
393
-
394
- function appendToExistingCspDirective(
395
- directives: CspDirective[],
396
- name: string,
397
- additions: readonly string[],
398
- ): void {
399
- const existing = findCspDirective(directives, name);
400
- if (!existing) return;
401
- existing.tokens = appendCspTokens(existing.tokens, additions);
402
- }
403
-
404
- function ensureCspDirective(
405
- directives: CspDirective[],
406
- name: string,
407
- tokens: readonly string[],
408
- ): void {
409
- if (findCspDirective(directives, name)) return;
410
- directives.push({ name, tokens: [...tokens] });
411
- }
412
-
413
- function hasStrictNonceScriptPolicy(tokens: readonly string[]): boolean {
414
- return tokens.some(
415
- (token) => token === "'strict-dynamic'" || token.startsWith("'nonce-"),
416
- );
417
- }
418
-
419
- function appendToScriptCspDirective(
420
- directives: CspDirective[],
421
- name: string,
422
- additions: readonly string[],
423
- ): boolean {
424
- const existing = findCspDirective(directives, name);
425
- if (existing) {
426
- if (hasStrictNonceScriptPolicy(existing.tokens)) return false;
427
- existing.tokens = appendCspTokens(existing.tokens, additions);
428
- return true;
429
- }
430
-
431
- const defaultSrc = findCspDirective(directives, "default-src");
432
- if (!defaultSrc || hasStrictNonceScriptPolicy(defaultSrc.tokens)) {
433
- return false;
434
- }
435
- directives.push({
436
- name,
437
- tokens: appendCspTokens([...defaultSrc.tokens], additions),
438
- });
439
- return true;
440
- }
441
-
442
- function appendToEffectiveScriptElementCspDirective(
443
- directives: CspDirective[],
444
- additions: readonly string[],
445
- ): boolean {
446
- const scriptSrcElem = findCspDirective(directives, "script-src-elem");
447
- if (scriptSrcElem) {
448
- if (hasStrictNonceScriptPolicy(scriptSrcElem.tokens)) return false;
449
- scriptSrcElem.tokens = appendCspTokens(scriptSrcElem.tokens, additions);
450
- return true;
451
- }
452
-
453
- return appendToScriptCspDirective(directives, "script-src", additions);
454
- }
455
-
456
- function augmentExistingEnforcedCspForFrameworkScripts(
457
- policy: string,
458
- options: {
459
- gaScriptSrcTokens: readonly string[];
460
- gaEnabled: boolean;
461
- },
462
- ): string {
463
- // Multiple CSP headers are surfaced by Headers.get() as one comma-joined
464
- // string. CSP is not a comma-list header, so serializing a parsed combined
465
- // value would turn two policies into one invalid policy. Leave those headers
466
- // app-owned; a comma inside a source/report URL is still safe to parse.
467
- if (hasCommaJoinedCspPolicies(policy)) return policy;
468
-
469
- const directives = parseCsp(policy);
470
- if (!directives.length) return policy;
471
-
472
- if (options.gaEnabled) {
473
- const addedScriptElement = appendToEffectiveScriptElementCspDirective(
474
- directives,
475
- options.gaScriptSrcTokens,
476
- );
477
- if (addedScriptElement) {
478
- appendToExistingOrDefaultCspDirective(
479
- directives,
480
- "connect-src",
481
- GA_CSP_CONNECT_HOSTS,
482
- );
483
- appendToExistingOrDefaultCspDirective(
484
- directives,
485
- "img-src",
486
- GA_CSP_IMG_HOSTS,
487
- );
488
- }
489
- }
490
-
491
- ensureCspDirective(directives, "object-src", ["'none'"]);
492
- ensureCspDirective(directives, "base-uri", ["'self'"]);
493
-
494
- return serializeCsp(directives);
495
- }
496
-
497
- function augmentExistingReportOnlyCspForFrameworkScripts(
498
- policy: string,
499
- options: {
500
- scriptSrcTokens: readonly string[];
501
- gaEnabled: boolean;
502
- },
503
- ): string {
504
- if (hasCommaJoinedCspPolicies(policy)) return policy;
505
-
506
- const directives = parseCsp(policy);
507
- if (!directives.length) return policy;
508
-
509
- appendToExistingOrDefaultCspDirective(
510
- directives,
511
- "script-src",
512
- options.scriptSrcTokens,
513
- );
514
- appendToExistingCspDirective(
515
- directives,
516
- "script-src-elem",
517
- options.scriptSrcTokens,
518
- );
519
-
520
- if (options.gaEnabled) {
521
- appendToExistingOrDefaultCspDirective(
522
- directives,
523
- "connect-src",
524
- GA_CSP_CONNECT_HOSTS,
525
- );
526
- appendToExistingOrDefaultCspDirective(
527
- directives,
528
- "img-src",
529
- GA_CSP_IMG_HOSTS,
530
- );
531
- }
532
-
533
- return serializeCsp(directives);
534
- }
535
-
536
- /**
537
- * Apply a Content-Security-Policy header to HTML document responses.
254
+ * Strip document-level CSP from app HTML responses.
538
255
  *
539
- * Two directives are always enforced in production:
540
- *
541
- * - `object-src 'none'` — disables Flash / Java / PDF plugin execution,
542
- * which are a reliable code-execution vector even in modern browsers.
543
- * - `base-uri 'self'` — prevents a `<base href="...">` injection from
544
- * hijacking all relative URLs in the document (a common attack target when
545
- * user-controlled content reaches the HTML).
546
- *
547
- * A third directive, `script-src`, is emitted via `Content-Security-Policy-
548
- * Report-Only` rather than enforced when the app has no existing document CSP.
549
- * The framework injects inline scripts for analytics, Sentry, and template
550
- * setup, and hosted apps need Google Tag Manager to load without noisy CSP
551
- * diagnostics. The report-only policy is intentionally permissive for scripts:
552
- * it includes `'unsafe-inline'` plus the known GA/GTM loader hosts.
553
- *
554
- * If an app or host already sends an enforced CSP with `script-src`,
555
- * `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge only
556
- * GA-specific allowances into existing host/hash policies. Strict nonce or
557
- * `strict-dynamic` script policies stay app-owned because blindly appending
558
- * hashes or hosts would widen the policy without reliably loading our injected
559
- * scripts.
560
- *
561
- * Templates additionally render a theme-init inline script whose exact content
562
- * varies by template (default theme param, custom docs variant, etc.) and which
563
- * is rendered by React Router, not this handler, so its hash is not available
564
- * here. Shipping script-src as Report-Only surfaces the remaining violations
565
- * without breaking template customisations; teams can graduate to enforcement
566
- * once their hashes are enumerated.
567
- *
568
- * Skipped in development (`NODE_ENV !== 'production'`) so HMR eval and Vite
569
- * dev-server injects are never blocked. Set `AGENT_NATIVE_DISABLE_DOC_CSP=1`
570
- * to opt out in production for a template with exotic needs.
256
+ * Hosted templates inject framework bootstrap scripts, analytics, Sentry config,
257
+ * and app-owned inline scripts whose exact bytes vary by build/template. Any
258
+ * shared CSP header, even Report-Only, can block or noisily report Google Tag
259
+ * Manager and those bootstraps. Extension iframes and webviews keep their own
260
+ * route-specific sandboxes; normal app documents deliberately do not emit CSP.
571
261
  */
572
- function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
573
- if (process.env.NODE_ENV !== "production") return;
574
- if (process.env.AGENT_NATIVE_DISABLE_DOC_CSP === "1") return;
575
-
576
- // script-src as Report-Only: keep this deliberately loose so the framework's
577
- // injected analytics and template bootstrap scripts do not look blocked in
578
- // browser diagnostics.
579
- const sentryBody = extractScriptBody(sentryScript);
580
- const sentryHash = sentryBody ? computeInlineScriptHash(sentryBody) : null;
581
- const gaInlineBody = getGaInlineConfigScriptBody();
582
- const gaHash = gaInlineBody ? computeInlineScriptHash(gaInlineBody) : null;
583
- const gaHosts = gaInlineBody ? [...GA_CSP_SCRIPT_HOSTS] : [];
584
- const gaScriptSrcTokens = [
585
- "'unsafe-inline'",
586
- ...(gaHash ? [gaHash] : []),
587
- ...gaHosts,
588
- ];
589
- const scriptSrcTokens = [
590
- "'self'",
591
- "'unsafe-inline'",
592
- ...(sentryHash ? [sentryHash] : []),
593
- ...(gaHash ? [gaHash] : []),
594
- ...gaHosts,
595
- ];
596
-
597
- const cspAugmentOptions = {
598
- scriptSrcTokens,
599
- gaScriptSrcTokens,
600
- gaEnabled: Boolean(gaInlineBody),
601
- };
602
- const existing = headers.get("content-security-policy") ?? "";
603
- if (!existing) {
604
- headers.set(
605
- "content-security-policy",
606
- "object-src 'none'; base-uri 'self'",
607
- );
608
- } else {
609
- headers.set(
610
- "content-security-policy",
611
- augmentExistingEnforcedCspForFrameworkScripts(
612
- existing,
613
- cspAugmentOptions,
614
- ),
615
- );
616
- }
617
-
618
- const scriptSrc = `script-src ${scriptSrcTokens.join(" ")}`;
619
- const existingRo = headers.get("content-security-policy-report-only") ?? "";
620
- if (!existingRo) {
621
- headers.set("content-security-policy-report-only", scriptSrc);
622
- } else {
623
- headers.set(
624
- "content-security-policy-report-only",
625
- augmentExistingReportOnlyCspForFrameworkScripts(
626
- existingRo,
627
- cspAugmentOptions,
628
- ),
629
- );
630
- }
262
+ function removeDocumentCsp(headers: Headers): void {
263
+ headers.delete("content-security-policy");
264
+ headers.delete("content-security-policy-report-only");
631
265
  }
632
266
 
633
267
  function isFrameworkOrAssetPath(pathname: string): boolean {
@@ -665,7 +299,15 @@ async function rewriteMountedResponse(
665
299
  }
666
300
 
667
301
  const contentType = headers.get("content-type") ?? "";
668
- if (!contentType.toLowerCase().includes("text/html") || !response.body) {
302
+ if (!contentType.toLowerCase().includes("text/html")) {
303
+ return new Response(response.body, {
304
+ status: response.status,
305
+ statusText: response.statusText,
306
+ headers,
307
+ });
308
+ }
309
+ removeDocumentCsp(headers);
310
+ if (!response.body) {
669
311
  return new Response(response.body, {
670
312
  status: response.status,
671
313
  statusText: response.statusText,
@@ -675,7 +317,6 @@ async function rewriteMountedResponse(
675
317
 
676
318
  const html = await response.text();
677
319
  headers.delete("content-length");
678
- applyDocumentCsp(headers, sentryClientConfigScript);
679
320
  return new Response(
680
321
  injectHeadScript(
681
322
  injectDefaultSocialImageMeta(
@@ -33,10 +33,62 @@ agent answers about browser recordings in the Analytics template.
33
33
  scoped to the recording, embeds a small SSR discovery payload on
34
34
  `/sessions/:recordingId`, and advertises
35
35
  `/api/session-replay/agent-context.json` plus bounded
36
- `/api/session-replay/agent-events.json` reads.
36
+ `/api/session-replay/agent-events.json` and
37
+ `/api/session-replay/agent-diagnostics.json` reads.
37
38
  - Do not make session recordings public just so an agent can inspect them.
38
39
  Tokenized agent links are the intended handoff path.
39
40
 
41
+ ## Console And Network Capture
42
+
43
+ - While recording, the core client (`session-replay.ts` in
44
+ `@agent-native/core`) patches console (`log`/`info`/`warn`/`error`/`debug`),
45
+ window `error` / `unhandledrejection`, `fetch`, and XHR, and emits rrweb
46
+ custom events tagged `agent-native.console` and `agent-native.network`.
47
+ - Capture is on by default whenever session replay is enabled. Tune or disable
48
+ it with the `console` / `network` options on the session replay config; each
49
+ accepts a boolean or an options object.
50
+ - Privacy bounds: request/response bodies and headers are never captured, URLs
51
+ are scrubbed, messages are truncated, and recorder self-traffic (the replay
52
+ ingest and tracking endpoints) is excluded.
53
+ - Per-session budgets: 1000 console events and 2000 network events, with a
54
+ truncation notice event once a budget is hit.
55
+ - On ingest, `deriveReplaySignals` computes the real `errorCount` from tagged
56
+ console events plus the additive `networkErrorCount` column on
57
+ `session_recordings`. Keep new columns additive.
58
+
59
+ ## Agent Diagnostics Surface
60
+
61
+ - `buildSessionReplayAgentContext` includes a `diagnostics` section: up to 50
62
+ console entries and 50 network entries, errors/failures first, with totals
63
+ and truncated flags. Agent-context instructions steer agents to diagnostics
64
+ as the primary debugging signal.
65
+ - The agent timeline includes `console-error` / `network-error` markers; error
66
+ markers are kept preferentially under the 200-marker cap.
67
+ - `apis.diagnostics` advertises the fuller bounded list:
68
+ `GET /api/session-replay/agent-diagnostics.json?id=<recordingId>&agent_access=<token>&kind=console|network|all&level=<level>&limit=<n>`
69
+ (limit defaults to 200, max 500). It uses the same recording-scoped
70
+ `agent_access` token as the other agent JSON APIs.
71
+
72
+ ## Dev Tools Panel
73
+
74
+ - The `/sessions/:recordingId` replay player has a Dev Tools toggle that opens
75
+ a panel with Console and Network tabs: filter chips, search, an error-count
76
+ badge, and playback-time highlighting.
77
+ - Rows are Jump-to-seek: clicking one seeks the player to that moment. Extend
78
+ this panel instead of adding a separate debugging surface.
79
+
80
+ ## Debugging A User-Reported Bug
81
+
82
+ 1. Search the reporting user's email on `/sessions` to find their recordings.
83
+ 2. Open the relevant session at `/sessions/:recordingId` and click
84
+ **Copy for agent** to mint the two-hour tokenized link.
85
+ 3. Paste the link to an agent. The agent fetches
86
+ `/api/session-replay/agent-context.json`, reads the `diagnostics` section
87
+ and timeline markers first, then drills into `apis.diagnostics` (filtered
88
+ by `kind`/`level`) and `apis.events` for the fuller bounded lists as needed.
89
+ 4. For human verification, open the Dev Tools panel in the replay player and
90
+ jump-to-seek from the failing console or network row.
91
+
40
92
  ## Capture Defaults
41
93
 
42
94
  - Replay is off unless enabled by config/env.
@@ -128,6 +128,8 @@ details live in `.agents/skills/`.
128
128
  runtimes use the in-process scheduler unless `ANALYTICS_ALERT_JOBS=0` is set.
129
129
  External cron callers can POST `/api/analytics-alerts/run` with
130
130
  `Authorization: Bearer $ANALYTICS_ALERTS_CRON_SECRET`.
131
+ Users can view and manage these rules in Settings under the Alerts card, which
132
+ uses the same action surface as the agent.
131
133
 
132
134
  ## Application State
133
135
 
@@ -153,6 +155,17 @@ details live in `.agents/skills/`.
153
155
  scoped to one recording for two hours; SSR embeds an agent discovery payload
154
156
  and the JSON APIs expose only summary/timeline metadata plus bounded event
155
157
  reads.
158
+ - Recordings also capture console logs and network request metadata (no
159
+ bodies/headers, scrubbed URLs, truncated messages, per-session budgets);
160
+ ingest derives `errorCount`/`networkErrorCount` and adds
161
+ `console-error`/`network-error` timeline markers.
162
+ - The agent context includes a bounded `diagnostics` section (errors first) and
163
+ advertises `GET /api/session-replay/agent-diagnostics.json` with
164
+ `kind`/`level`/`limit` params (limit max 500) under the same `agent_access`
165
+ token — the primary signal when debugging a user-reported issue.
166
+ - The replay player has a Dev Tools panel with Console and Network tabs
167
+ (filters, search, jump-to-seek, error badge); extend it rather than adding a
168
+ separate replay debugging surface. See the `session-replay` skill.
156
169
  - Dashboard rows that include `recording_id` should link to
157
170
  `/sessions/:recordingId`; rows that only include `session_id` can link to a
158
171
  filtered `/sessions` search.
@@ -9,6 +9,7 @@ import {
9
9
  } from "@agent-native/core/server";
10
10
  import { z } from "zod";
11
11
 
12
+ import { listAnalyticsAlertRules } from "../server/lib/analytics-alerts";
12
13
  import { listDashboardCatalog } from "../server/lib/dashboard-catalog";
13
14
  import { getAnalysis, getDashboard } from "../server/lib/dashboards-store";
14
15
  import { listAnalyticsPublicKeys } from "../server/lib/first-party-analytics.js";
@@ -195,6 +196,30 @@ export default defineAction({
195
196
  }
196
197
  } else if (nav?.view === "settings") {
197
198
  screen.page = "settings";
199
+ const email = getRequestUserEmail();
200
+ if (email) {
201
+ const orgId = getRequestOrgId() || null;
202
+ const alertRules = await listAnalyticsAlertRules({ email, orgId });
203
+ screen.analyticsAlerts = alertRules.map((rule) => ({
204
+ id: rule.id,
205
+ name: rule.name,
206
+ enabled: rule.enabled,
207
+ severity: rule.severity,
208
+ eventName: rule.eventName,
209
+ filters: rule.filters,
210
+ thresholdMode: rule.thresholdMode,
211
+ distinctBy: rule.distinctBy,
212
+ threshold: rule.threshold,
213
+ windowMinutes: rule.windowMinutes,
214
+ cooldownMinutes: rule.cooldownMinutes,
215
+ channels: rule.channels,
216
+ emailRecipients: rule.emailRecipients,
217
+ lastStatus: rule.lastStatus,
218
+ lastEvaluatedAt: rule.lastEvaluatedAt,
219
+ lastTriggeredAt: rule.lastTriggeredAt,
220
+ lastError: rule.lastError,
221
+ }));
222
+ }
198
223
  }
199
224
 
200
225
  if (Object.keys(screen).length === 0) {
@@ -1233,6 +1233,7 @@ function AnalyticsChatsSection() {
1233
1233
  const {
1234
1234
  threads,
1235
1235
  activeThreadId,
1236
+ isLoading: chatsLoading,
1236
1237
  createThread,
1237
1238
  switchThread,
1238
1239
  pinThread,
@@ -1348,6 +1349,25 @@ function AnalyticsChatsSection() {
1348
1349
 
1349
1350
  return (
1350
1351
  <div className="ms-4 min-w-0 space-y-0.5">
1352
+ {chatsLoading &&
1353
+ visibleThreads.length === 0 &&
1354
+ Array.from({ length: 3 }).map((_, i) => (
1355
+ <div
1356
+ key={`chat-skeleton-${i}`}
1357
+ className="flex items-center gap-2 px-3 py-1"
1358
+ >
1359
+ <Skeleton
1360
+ className={cn(
1361
+ "h-3.5 w-3.5 shrink-0 rounded-sm",
1362
+ SIDEBAR_SKELETON_CLASS,
1363
+ )}
1364
+ />
1365
+ <Skeleton
1366
+ className={cn("h-3 rounded", SIDEBAR_SKELETON_CLASS)}
1367
+ style={{ width: `${60 + ((i * 17) % 30)}%` }}
1368
+ />
1369
+ </div>
1370
+ ))}
1351
1371
  {visibleThreads.map((thread) => {
1352
1372
  const title = threadTitle(thread, t("chat.untitledChat"));
1353
1373
  const isActive =
@@ -215,9 +215,64 @@
215
215
  }
216
216
 
217
217
  .an-replay-stage-root .replayer-wrapper {
218
+ position: relative;
218
219
  transform-origin: top left;
219
220
  }
220
221
 
222
+ /* First-party visitor-cursor styles for rrweb playback. These are bundled
223
+ statically so the cursor stays visible even if the lazily imported
224
+ `@rrweb/replay/dist/style.css` sheet fails to load, and they replace the
225
+ default black arrow sprite with an app-styled indicator. The cursor lives
226
+ inside `.replayer-wrapper`, so it scales with the fit-to-stage transform. */
227
+ .an-replay-stage-root .replayer-mouse {
228
+ position: absolute;
229
+ z-index: 2;
230
+ width: 0.875rem;
231
+ height: 0.875rem;
232
+ border-radius: 9999px;
233
+ background: hsl(217 91% 60%);
234
+ background-image: none;
235
+ box-shadow:
236
+ 0 0 0 2px hsl(0 0% 100% / 0.9),
237
+ 0 1px 6px hsl(0 0% 0% / 0.35);
238
+ transform: translate(-50%, -50%);
239
+ transition:
240
+ left 80ms linear,
241
+ top 80ms linear;
242
+ pointer-events: none;
243
+ }
244
+
245
+ .an-replay-stage-root .replayer-mouse::after {
246
+ content: "";
247
+ position: absolute;
248
+ inset: 0;
249
+ border-radius: 9999px;
250
+ background: hsl(217 91% 60%);
251
+ opacity: 0;
252
+ }
253
+
254
+ /* rrweb toggles `.active` on mouse interactions; pulse a click ripple. */
255
+ .an-replay-stage-root .replayer-mouse.active::after {
256
+ animation: an-replay-mouse-click 0.4s ease-out 1;
257
+ }
258
+
259
+ @keyframes an-replay-mouse-click {
260
+ 0% {
261
+ opacity: 0.5;
262
+ transform: scale(1);
263
+ }
264
+ 100% {
265
+ opacity: 0;
266
+ transform: scale(3.5);
267
+ }
268
+ }
269
+
270
+ .an-replay-stage-root .replayer-mouse-tail {
271
+ position: absolute;
272
+ inset: 0;
273
+ pointer-events: none;
274
+ }
275
+
221
276
  .analytics-session-snippet .plan-code-surface {
222
277
  border: 0;
223
278
  border-radius: 0;