@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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +199 -38
- package/corpus/core/src/agent/run-manager.ts +79 -0
- package/corpus/core/src/agent/run-store.ts +117 -5
- package/corpus/core/src/agent/types.ts +13 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
- package/corpus/core/src/client/analytics.ts +2 -0
- package/corpus/core/src/client/error-format.ts +4 -0
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
- package/corpus/core/src/client/session-replay.ts +774 -2
- package/corpus/core/src/client/sse-event-processor.ts +65 -9
- package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
- package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
- package/corpus/core/src/server/analytics.ts +1 -34
- package/corpus/core/src/server/core-routes-plugin.ts +91 -4
- package/corpus/core/src/server/http-response-telemetry.ts +189 -0
- package/corpus/core/src/server/self-dispatch.ts +29 -0
- package/corpus/core/src/server/ssr-handler.ts +18 -377
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
- package/corpus/templates/analytics/AGENTS.md +13 -0
- package/corpus/templates/analytics/actions/view-screen.ts +25 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
- package/corpus/templates/analytics/app/global.css +55 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
- package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
- package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
- package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +3 -0
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
- package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
- package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +0 -2
- package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
- package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +0 -1
- package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
- package/corpus/templates/plan/server/plans.ts +26 -9
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +155 -34
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +29 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +73 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +41 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +100 -4
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +13 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +13 -0
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +410 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +2 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/session-replay.d.ts +36 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +579 -1
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +20 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +42 -11
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +14 -4
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +18 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/runtime-diagnostics.d.ts +52 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -0
- package/dist/db/runtime-diagnostics.js +250 -0
- package/dist/db/runtime-diagnostics.js.map +1 -0
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +101 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/analytics.d.ts +1 -19
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +1 -31
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +17 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +65 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts +4 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -0
- package/dist/server/http-response-telemetry.js +174 -0
- package/dist/server/http-response-telemetry.js.map +1 -0
- package/dist/server/self-dispatch.d.ts +17 -0
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +11 -0
- package/dist/server/self-dispatch.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +18 -271
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/docs/content/tracking.mdx +2 -0
- 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
|
-
*
|
|
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
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
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
|
|
573
|
-
|
|
574
|
-
|
|
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")
|
|
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`
|
|
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;
|