@agent-native/core-corpus 0.206.0-nightly-20261009153146 → 0.206.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 +1 -1
- package/corpus/templates/analytics/.agents/skills/onboarding-journeys/SKILL.md +28 -4
- package/corpus/templates/analytics/CHANGELOG.md +17 -0
- package/corpus/templates/analytics/actions/get-onboarding-journey.ts +1 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionReplayStoryboardExportDialog.tsx +1 -0
- package/corpus/templates/analytics/app/pages/sessions/session-replay-screenshot.ts +24 -8
- package/corpus/templates/analytics/changelog/2026-10-09-large-onboarding-journeys-keep-their-step-counts-when-follow.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-09-onboarding-journeys-now-count-later-activity-within-a-frozen.md +6 -0
- package/corpus/templates/analytics/package.json +1 -0
- package/corpus/templates/analytics/server/lib/analytics-mcp.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +3 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-pushdown.ts +176 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +161 -18
- package/corpus/templates/analytics/server/lib/journey-steps.ts +11 -1
- package/corpus/templates/analytics/server/lib/onboarding-journey.ts +358 -17
- package/corpus/templates/analytics/vitest.bigquery.config.ts +12 -0
- package/corpus/templates/analytics/vitest.config.ts +1 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +30 -2
- package/corpus/templates/clips/AGENTS.md +1 -0
- package/corpus/templates/clips/CHANGELOG.md +20 -0
- package/corpus/templates/clips/app/components/player/clip-agent-webmcp.tsx +14 -2
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +4 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +2 -2
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-10-09-agents-can-read-clip-transcripts-and-inspect-video-frames-fr.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-09-agents-now-get-clear-recovery-steps-when-clip-frames-are-sti.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-09-clips-share-links-now-guide-agents-to-read-full-transcripts-.md +6 -0
- package/corpus/templates/clips/server/lib/ensure-recording-thumbnail.ts +1 -1
- package/corpus/templates/clips/server/lib/public-agent-context.ts +90 -7
- package/corpus/templates/clips/server/lib/video-frame.ts +8 -6
- package/corpus/templates/clips/server/routes/[...page].get.ts +38 -38
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +4 -2
- package/corpus/templates/clips/server/routes/api/agent-frame.jpg.get.ts +143 -26
- package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +4 -2
- package/corpus/templates/clips/shared/agent-context.ts +44 -5
- package/corpus/templates/content/CHANGELOG.md +16 -0
- package/corpus/templates/content/actions/_database-row-mutation.ts +27 -0
- package/corpus/templates/content/actions/_widget-edit-capabilities.ts +39 -0
- package/corpus/templates/content/actions/add-database-item.ts +2 -0
- package/corpus/templates/content/actions/get-document.ts +19 -1
- package/corpus/templates/content/actions/update-database-item.ts +6 -0
- package/corpus/templates/content/actions/update-document.ts +46 -2
- package/corpus/templates/content/agent-native.app-skill.json +1 -1
- package/corpus/templates/content/app/components/editor/DirectoryWidgetFormattingToolbar.tsx +73 -0
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +11 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +21 -4
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +53 -4
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +29 -16
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +7 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +338 -200
- package/corpus/templates/content/app/components/editor/database/scoped-row-write.ts +57 -0
- package/corpus/templates/content/app/components/editor/directory-widget-editability.ts +23 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +25 -0
- package/corpus/templates/content/app/lib/content-landing.ts +9 -4
- package/corpus/templates/content/changelog/2026-10-08-the-content-editor-fits-chatgpt-s-panel-sizes-and-supports-d.md +6 -0
- package/corpus/templates/content/changelog/2026-10-09-content-widgets-keep-document-edits-after-reopening-and-can-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-09-document-icons-save-from-chatgpt-widgets.md +6 -0
- package/corpus/templates/content/changelog/2026-10-09-format-content-documents-directly-in-chatgpt-widgets.md +6 -0
- package/corpus/templates/content/changelog/2026-10-09-pages-without-a-collection-open-faster.md +6 -0
- package/corpus/templates/content/public/chatgpt-plugin-icon.svg +5 -7
- package/corpus/templates/content/server/lib/chatgpt-directory-tools.ts +90 -0
- package/corpus/templates/content/shared/api.ts +2 -0
- package/corpus/templates/crm/CHANGELOG.md +6 -0
- package/corpus/templates/design/AGENTS.md +1 -1
- package/corpus/templates/design/CHANGELOG.md +57 -0
- package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +3 -2
- package/corpus/templates/design/actions/create-file.ts +4 -0
- package/corpus/templates/design/actions/update-design.ts +157 -0
- package/corpus/templates/design/actions/update-file.ts +18 -2
- package/corpus/templates/design/actions/widget-write-scope.ts +79 -0
- package/corpus/templates/design/agent-native.app-skill.json +1 -1
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +136 -50
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +43 -21
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +18 -3
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +6 -1
- package/corpus/templates/design/app/components/editor/HomeImportButton.tsx +39 -6
- package/corpus/templates/design/app/i18n/en-US.ts +1 -0
- package/corpus/templates/design/app/lib/design-file-upload.ts +4 -0
- package/corpus/templates/design/app/lib/fig-client-import.ts +10 -2
- package/corpus/templates/design/app/lib/fig-import-worker-session.ts +11 -6
- package/corpus/templates/design/app/lib/pending-import.ts +46 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +31 -73
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +52 -5
- package/corpus/templates/design/app/pages/design-editor/created-screen-navigation.ts +30 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-active-screen-and-geometry.ts +4 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-canvas-and-screens.ts +3 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-generation-and-access.ts +17 -8
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +102 -45
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-tools-and-vectors.ts +59 -3
- package/corpus/templates/design/app/pages/design-editor/mcp-widget-write-capabilities.ts +13 -0
- package/corpus/templates/design/app/pages/design-editor/minimal-inspector.ts +15 -1
- package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +8 -19
- package/corpus/templates/design/app/pages/design-editor/view/editor-canvas-area.tsx +1 -0
- package/corpus/templates/design/app/pages/design-editor/view/mobile-inspector-sheet.tsx +11 -9
- package/corpus/templates/design/app/pages/design-editor/view/overview-canvas.tsx +3 -2
- package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +6 -3
- package/corpus/templates/design/changelog/2026-10-07-figma-fig-files-up-to-2-gb-now-import-in-the-browser-includi.md +6 -0
- package/corpus/templates/design/changelog/2026-10-08-a-progress-toast-now-stays-on-screen-while-a-fig-file-is-ana.md +6 -0
- package/corpus/templates/design/changelog/2026-10-08-the-overview-canvas-keeps-screen-selections-centered-without.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-chatgpt-widget-screen-duplication-preserves-layout.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-design-widget-edits-check-the-file-version-before-saving-pro.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-design-widget-edits-continue-after-reopening-a-chat.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-design-widgets-report-a-conflict-when-a-stale-edit-cannot-be.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-edit-screens-from-chatgpt-widgets.md +6 -0
- package/corpus/templates/design/public/chatgpt-plugin-icon.svg +5 -9
- package/corpus/templates/design/server/lib/chatgpt-directory-tools.ts +86 -1
- package/corpus/templates/design/server/lib/design-versions.ts +22 -8
- package/corpus/templates/design/server/lib/fig-file-decoder.ts +140 -103
- package/corpus/templates/design/server/lib/fig-file-limits.ts +66 -2
- package/corpus/templates/design/server/lib/fig-file-to-html.ts +17 -11
- package/corpus/templates/design/shared/fig-bytes.ts +2 -0
- package/corpus/templates/design/shared/fig-to-frames.ts +80 -37
- package/corpus/templates/dispatch/CHANGELOG.md +6 -0
- package/corpus/templates/forms/CHANGELOG.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +35 -0
- package/corpus/templates/slides/actions/get-deck.ts +10 -3
- package/corpus/templates/slides/actions/patch-deck.ts +302 -2
- package/corpus/templates/slides/agent-native.app-skill.json +1 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +91 -85
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +7 -4
- package/corpus/templates/slides/app/components/editor/slide-style.ts +25 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +7 -2
- package/corpus/templates/slides/app/context/DeckContext.tsx +8 -1
- package/corpus/templates/slides/app/global.css +15 -0
- package/corpus/templates/slides/app/hooks/use-deck-role.ts +12 -5
- package/corpus/templates/slides/app/lib/slide-agent-context.ts +20 -4
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +15 -6
- package/corpus/templates/slides/app/routes/chat.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-08-the-slides-editor-fits-chatgpt-s-panel-sizes-and-supports-di.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-09-deck-tweaks-save-from-chatgpt-widgets.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-09-slide-text-editing-keeps-formatting-controls-responsive-whil.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-09-slides-widgets-keep-edits-after-reopening-and-can-save-avail.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-09-the-slides-chat-stays-responsive-while-you-type-on-a-slide.md +6 -0
- package/corpus/templates/slides/public/chatgpt-plugin-icon.svg +5 -8
- package/corpus/templates/slides/scripts/edit-fidelity/README.md +1 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run-options.ts +9 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +75 -63
- package/corpus/templates/slides/server/lib/chatgpt-directory-tools.ts +47 -0
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
name: onboarding-journeys
|
|
3
3
|
description: >-
|
|
4
4
|
Build the onboarding journey tree (signup to first output, with % splits,
|
|
5
|
-
last-observed-step counts,
|
|
6
|
-
step. Use when asked for an onboarding
|
|
7
|
-
|
|
5
|
+
last-observed-step counts, window-bounded later activity, and example
|
|
6
|
+
replays) and render a screenshot per step. Use when asked for an onboarding
|
|
7
|
+
storyboard, each cohort's last recorded step or later activity within a
|
|
8
|
+
window, or frames for each onboarding step.
|
|
8
9
|
scope: dev
|
|
9
10
|
---
|
|
10
11
|
|
|
@@ -36,7 +37,8 @@ window, `app`, `maxDepth`, `minNodeSessions` (small branches merge into an
|
|
|
36
37
|
```ts
|
|
37
38
|
type JourneyExample = { sessionId: string; recordingId: string | null; ts: string; offsetMs: number | null; viewport: { width: number; height: number } | null; viewportReason?: string; replayUrl?: string };
|
|
38
39
|
type JourneyNode = { key: string; label: string; parentKey: string | null; depth: number; kind: "step" | "other"; n: number; pctOfRoot: number; pctOfParent: number; dropoffN: number; dropoffPct: number; deeperN: number; examples: JourneyExample[] };
|
|
39
|
-
type
|
|
40
|
+
type JourneyFollowup = { status: "complete" | "incomplete"; incompleteReason?: "journey_event_read_truncated" | "journey_event_read_invalid" | "journey_event_read_may_have_shifted" | "terminal_cohort_query_too_large" | "followup_aggregate_truncated" | "followup_aggregate_invalid" | "terminal_cohort_mismatch"; observationCutoff: string; observationFollowupDurationMs: { min: number; max: number; mean: number } | null; rightCensoredAtWindowEnd: true; coverage: { journeyEventRead: { rows: number; pages: number; truncated: boolean; paginationConsistency: "stable" | "may_have_shifted" }; followupAggregateRead: { rows: number | null; queries: number; truncated: boolean }; cohortSessions: number | null }; laterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null }; noLaterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null } };
|
|
41
|
+
type JourneyTree = { window: { from: string; to: string }; app: string; rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[]; followUp: JourneyFollowup; standaloneSetup?: { rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[] }; notes?: string[] };
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
Analytics returns cohort nodes with counts. When extending a Design storyboard
|
|
@@ -65,6 +67,28 @@ top-level tree to `journey:capture`; the capture CLI reads top-level `nodes`.
|
|
|
65
67
|
exit: a blocked tracker or an event outside the window reads the same.
|
|
66
68
|
`deeperN` counts sessions with a later observed step that is not represented
|
|
67
69
|
as a child, including paths past `maxDepth` or a node-list cap.
|
|
70
|
+
- `followUp` groups onboarding sessions by the key of their terminal selected
|
|
71
|
+
step. `laterRecordedActivityWithinWindow` counts sessions with any later
|
|
72
|
+
native Analytics event in the same session; `noLaterRecordedActivityWithinWindow`
|
|
73
|
+
counts the remaining sessions. The read applies the same authenticated
|
|
74
|
+
user/org scope, date window, app, identity bridge, test exclusion, and
|
|
75
|
+
Builder.io email filter as the journey read. It uses one
|
|
76
|
+
frozen `observationCutoff` for every event page and the single aggregate
|
|
77
|
+
query. `observationFollowupDurationMs`
|
|
78
|
+
summarizes the time from each terminal selected step to that cutoff.
|
|
79
|
+
`rightCensoredAtWindowEnd: true` means the no-later count is right-censored:
|
|
80
|
+
no later event was recorded before the cutoff; this is not an abandonment or
|
|
81
|
+
churn outcome. The aggregate contains counts only and does not expose the
|
|
82
|
+
session IDs or member identity keys used internally. The consistency field
|
|
83
|
+
reports `may_have_shifted` when the event read uses multiple `OFFSET` pages;
|
|
84
|
+
a late-arriving event can change page membership inside a historical window
|
|
85
|
+
too. If either read truncates, page boundaries may have shifted, or the
|
|
86
|
+
terminal cohort cannot be covered in one query under the 800,000-character
|
|
87
|
+
SQL limit or 50,000-token parser limit, `status` is `incomplete` and all new
|
|
88
|
+
cohort counts and follow-up duration are `null`; `incompleteReason`
|
|
89
|
+
identifies the limiting read. Do not report percentages from that partial
|
|
90
|
+
result. Existing journey counts and denominators remain independent of this
|
|
91
|
+
follow-up read.
|
|
68
92
|
- `maxDepth` defaults to 8 and is bounded at 40. Request `maxDepth: 40` for a
|
|
69
93
|
deeper pass. When sessions continue past the requested depth,
|
|
70
94
|
`coverage.truncated` is true and the boundary node's `deeperN` says how many
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
|
|
4
4
|
time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
5
5
|
|
|
6
|
+
## 2026-10-09
|
|
7
|
+
|
|
8
|
+
### Improved
|
|
9
|
+
|
|
10
|
+
- Onboarding journeys include Builder connection and custom-key outcomes through deeper paths
|
|
11
|
+
- Onboarding journeys now show later activity within a chosen window, mark no-later results as right-censored, and identify when follow-up counts are incomplete.
|
|
12
|
+
- You can add app origins to a public key's replay allowlist without replacing its existing origins.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Journey captures now flag visible previews missing from the recording instead of saving blank screenshots
|
|
17
|
+
- Journey recordings flag iframe content when clipping, masks, or filters make its visibility uncertain.
|
|
18
|
+
- Large onboarding journeys keep their step counts when follow-up coverage exceeds query limits
|
|
19
|
+
|
|
6
20
|
## 2026-10-08
|
|
7
21
|
|
|
8
22
|
### Added
|
|
@@ -19,6 +33,9 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
19
33
|
|
|
20
34
|
### Fixed
|
|
21
35
|
|
|
36
|
+
- Analytics journey paths group short resource IDs consistently across apps
|
|
37
|
+
- Analytics keeps onboarding counts complete when only standalone setup events exceed the journey read limit.
|
|
38
|
+
- Onboarding journey trees show Home chat setup choices in a separately counted tree and group equivalent artifact screens.
|
|
22
39
|
- Agent-shared recording links now load across batched playback chunks
|
|
23
40
|
- Fixed onboarding journey queries against BigQuery.
|
|
24
41
|
- Session replay agent links can read their recording manifest and playback chunks.
|
|
@@ -34,7 +34,7 @@ const isoDate = z
|
|
|
34
34
|
|
|
35
35
|
export default defineAction({
|
|
36
36
|
description:
|
|
37
|
-
'Build the onboarding journey tree for a date window: per-session step sequences (signup, onboarding questions, setup method, app entry, first action, first output) folded into a prefix tree. format "tree" returns { window, app, rootN, coverage, nodes } for sessions that entered onboarding, plus standaloneSetup for Home chat integration events in sessions without onboarding; its root and percentages have a separate denominator. Each node has n, pctOfRoot, pctOfParent, dropoffN/dropoffPct (last observed step, not a confirmed exit), deeperN (sessions with a later observed step omitted below this node), and example sessions with recordingId, a recording-start-relative offsetMs, and viewport. format "summary" returns the same counts as indented outlines with no examples. Check each tree\'s coverage.truncated first: true means its event sample, requested depth, or node list cut that tree; notes explain why. Use it to plan onboarding storyboards or
|
|
37
|
+
'Build the onboarding journey tree for a date window: per-session step sequences (signup, onboarding questions, setup method, app entry, first action, first output) folded into a prefix tree. format "tree" returns { window, app, rootN, coverage, nodes } for sessions that entered onboarding, plus standaloneSetup for Home chat integration events in sessions without onboarding; its root and percentages have a separate denominator. Each node has n, pctOfRoot, pctOfParent, dropoffN/dropoffPct (last observed step, not a confirmed exit), deeperN (sessions with a later observed step omitted below this node), and example sessions with recordingId, a recording-start-relative offsetMs, and viewport. Both formats include followUp counts of sessions with and without any later native Analytics event in the same session after each terminal selected step, observed before one frozen observationCutoff. followUp.rightCensoredAtWindowEnd is true: no later event means none was recorded within that bounded window, not abandonment or churn. If the event read may be incomplete, the aggregate query truncates or exceeds the SQL size or parser-token limit, or cohort coverage mismatches, followUp.status is incomplete, incompleteReason names the cause, and its cohort counts and duration are null. format "summary" returns the same tree counts as indented outlines with no examples. Check each tree\'s coverage.truncated first: true means its event sample, requested depth, or node list cut that tree; notes explain why. Use it to plan onboarding storyboards or see each cohort\'s last recorded selected step. For a standalone storyboard, pass standaloneSetup as the top-level tree to journey:capture.',
|
|
38
38
|
schema: z.object({
|
|
39
39
|
dateFrom: isoDate.describe(
|
|
40
40
|
"Inclusive UTC start date, YYYY-MM-DD. Sessions that began earlier appear mid-journey, so start a day before the period you care about.",
|
package/corpus/templates/analytics/app/pages/sessions/SessionReplayStoryboardExportDialog.tsx
CHANGED
|
@@ -185,6 +185,7 @@ export function SessionReplayStoryboardExportDialog({
|
|
|
185
185
|
|
|
186
186
|
function captureErrorMessage(error: unknown): string {
|
|
187
187
|
if (error instanceof ReplayScreenshotAssetError) {
|
|
188
|
+
console.warn("Replay screenshot capture rejected", error.reason);
|
|
188
189
|
return t("sessions.screenshotUnsupportedAssets");
|
|
189
190
|
}
|
|
190
191
|
return error instanceof Error
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
export class ReplayScreenshotAssetError extends Error {
|
|
2
|
-
constructor(
|
|
2
|
+
constructor(
|
|
3
|
+
readonly reason:
|
|
4
|
+
| "unsupportedAsset"
|
|
5
|
+
| "fontReadiness"
|
|
6
|
+
| "cloneDocument"
|
|
7
|
+
| "duplicateCloneMarker"
|
|
8
|
+
| "cloneRoot"
|
|
9
|
+
| "cloneElement"
|
|
10
|
+
| "cloneStageFrame"
|
|
11
|
+
| "clonePseudoElement" = "unsupportedAsset",
|
|
12
|
+
) {
|
|
3
13
|
super("Replay contains media or images that cannot be captured safely");
|
|
4
14
|
this.name = "ReplayScreenshotAssetError";
|
|
5
15
|
}
|
|
@@ -914,7 +924,7 @@ export function inlineReplayAssets(
|
|
|
914
924
|
const originalDocuments = [originalDocument];
|
|
915
925
|
const clonedDocuments = markedReplayDocuments(clonedDocument, captureId);
|
|
916
926
|
if (clonedDocuments.length !== 1) {
|
|
917
|
-
throw new ReplayScreenshotAssetError();
|
|
927
|
+
throw new ReplayScreenshotAssetError("cloneDocument");
|
|
918
928
|
}
|
|
919
929
|
|
|
920
930
|
const clonesByMarker = new Map<string, Element>();
|
|
@@ -922,7 +932,9 @@ export function inlineReplayAssets(
|
|
|
922
932
|
for (const element of renderedElements(cloned)) {
|
|
923
933
|
const marker = element.getAttribute(REPLAY_SCREENSHOT_MARKER);
|
|
924
934
|
if (!marker?.startsWith(`${captureId}-`)) continue;
|
|
925
|
-
if (clonesByMarker.has(marker))
|
|
935
|
+
if (clonesByMarker.has(marker)) {
|
|
936
|
+
throw new ReplayScreenshotAssetError("duplicateCloneMarker");
|
|
937
|
+
}
|
|
926
938
|
clonesByMarker.set(marker, element);
|
|
927
939
|
}
|
|
928
940
|
}
|
|
@@ -948,7 +960,7 @@ export function inlineReplayAssets(
|
|
|
948
960
|
rootMarker,
|
|
949
961
|
);
|
|
950
962
|
if (!cloned || rootMarker !== `${captureId}-${documentIndex}-0`) {
|
|
951
|
-
throw new ReplayScreenshotAssetError();
|
|
963
|
+
throw new ReplayScreenshotAssetError("cloneRoot");
|
|
952
964
|
}
|
|
953
965
|
const documentAssets = assets.get(original) ?? new Map();
|
|
954
966
|
|
|
@@ -966,7 +978,7 @@ export function inlineReplayAssets(
|
|
|
966
978
|
originalElement.tagName !== clonedElement.tagName &&
|
|
967
979
|
!originalElement.localName.includes("-"))
|
|
968
980
|
) {
|
|
969
|
-
throw new ReplayScreenshotAssetError();
|
|
981
|
+
throw new ReplayScreenshotAssetError("cloneElement");
|
|
970
982
|
}
|
|
971
983
|
if (originalElement instanceof view.HTMLIFrameElement) {
|
|
972
984
|
const screenshot = iframeScreenshots.get(originalElement);
|
|
@@ -1059,7 +1071,9 @@ export function inlineReplayAssets(
|
|
|
1059
1071
|
: "___html2canvas___pseudoelement_after";
|
|
1060
1072
|
if (!clonedElement.classList.contains(generatedClass)) continue;
|
|
1061
1073
|
const pseudoElement = pseudoElements[pseudoElementIndex++];
|
|
1062
|
-
if (!pseudoElement)
|
|
1074
|
+
if (!pseudoElement) {
|
|
1075
|
+
throw new ReplayScreenshotAssetError("clonePseudoElement");
|
|
1076
|
+
}
|
|
1063
1077
|
if (
|
|
1064
1078
|
pseudoStyles.display === "none" ||
|
|
1065
1079
|
pseudoStyles.visibility === "hidden" ||
|
|
@@ -1119,7 +1133,7 @@ async function assertDocumentFontsReady(document: Document): Promise<void> {
|
|
|
1119
1133
|
fontSet.ready,
|
|
1120
1134
|
new Promise<never>((_resolve, reject) => {
|
|
1121
1135
|
timeoutId = window.setTimeout(
|
|
1122
|
-
() => reject(new ReplayScreenshotAssetError()),
|
|
1136
|
+
() => reject(new ReplayScreenshotAssetError("fontReadiness")),
|
|
1123
1137
|
REPLAY_FONT_TIMEOUT_MS,
|
|
1124
1138
|
);
|
|
1125
1139
|
}),
|
|
@@ -1339,7 +1353,9 @@ export async function captureReplayScreenshot(
|
|
|
1339
1353
|
const clonedFrame = clonedStageRoot.querySelector(
|
|
1340
1354
|
`iframe[${REPLAY_SCREENSHOT_MARKER}="${stageFrameMarker}"]`,
|
|
1341
1355
|
);
|
|
1342
|
-
if (!clonedFrame)
|
|
1356
|
+
if (!clonedFrame) {
|
|
1357
|
+
throw new ReplayScreenshotAssetError("cloneStageFrame");
|
|
1358
|
+
}
|
|
1343
1359
|
const image = clonedStageRoot.ownerDocument.createElement("img");
|
|
1344
1360
|
image.alt = "";
|
|
1345
1361
|
image.src = replayImageUrl;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"build": "agent-native build && tsx scripts/emit-netlify-dashboard-report-cron.ts",
|
|
9
9
|
"start": "agent-native start",
|
|
10
10
|
"test": "vitest --run",
|
|
11
|
+
"test:bigquery-emulator": "vitest --run --config vitest.bigquery.config.ts",
|
|
11
12
|
"format.fix": "oxfmt --write .",
|
|
12
13
|
"typecheck": "agent-native typecheck",
|
|
13
14
|
"migrate:production": "tsx scripts/migrate-production.ts",
|
|
@@ -15,5 +15,5 @@ export const ANALYTICS_MCP = {
|
|
|
15
15
|
"list-error-issues",
|
|
16
16
|
],
|
|
17
17
|
instructions:
|
|
18
|
-
"Find saved dashboards with list-sql-dashboards or search-dashboard-references and read one with get-sql-dashboard; saved analyses with list-analyses and get-analysis. Find metric definitions and saved SQL with search-analytics-query-catalog and list-data-dictionary. Call data-source-status to see which providers are connected. List published blog articles with builder-blog-articles. CRM and calls: hubspot-deals, hubspot-records, hubspot-metrics, hubspot-pipelines, gong-calls, account-deep-dive; gong-native-insights lists Gong's paid synthesis operations. Product behavior: list-session-recordings, get-session-replay-summary, get-session-replay-timeline, list-error-issues, get-error-issue, match-error-issues. Onboarding
|
|
18
|
+
"Find saved dashboards with list-sql-dashboards or search-dashboard-references and read one with get-sql-dashboard; saved analyses with list-analyses and get-analysis. Find metric definitions and saved SQL with search-analytics-query-catalog and list-data-dictionary. Call data-source-status to see which providers are connected. List published blog articles with builder-blog-articles. CRM and calls: hubspot-deals, hubspot-records, hubspot-metrics, hubspot-pipelines, gong-calls, account-deep-dive; gong-native-insights lists Gong's paid synthesis operations. Product behavior: list-session-recordings, get-session-replay-summary, get-session-replay-timeline, list-error-issues, get-error-issue, match-error-issues. Onboarding journey tree with window-bounded later-activity counts, observation cutoff, right-censoring, and example replays: get-onboarding-journey.",
|
|
19
19
|
} satisfies AgentChatMcpOptions;
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type BigQueryTableRef,
|
|
16
16
|
} from "./bigquery.js";
|
|
17
17
|
import { requireRequestCredentialContext } from "./credentials-context.js";
|
|
18
|
+
import { firstPartyEventPushdownPredicates } from "./first-party-analytics-pushdown.js";
|
|
18
19
|
import { validateAnalyticsSqlFunctions } from "./first-party-analytics-sql-policy.js";
|
|
19
20
|
import { fetchGoogleWithRetry, getAccessToken } from "./gcloud.js";
|
|
20
21
|
import {
|
|
@@ -1302,8 +1303,10 @@ function addPartitionPrunedEventDeduplication(
|
|
|
1302
1303
|
}
|
|
1303
1304
|
// ponytail: insertAll is at-least-once; staging + MERGE is the upgrade path
|
|
1304
1305
|
// for physical exactly-once if the warehouse contract requires it.
|
|
1306
|
+
const predicates = firstPartyEventPushdownPredicates(sql, sourceIndex);
|
|
1305
1307
|
result +=
|
|
1306
1308
|
sql.slice(cursor, predicateEnd) +
|
|
1309
|
+
predicates.map((predicate) => ` AND (${predicate})`).join("") +
|
|
1307
1310
|
" QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY received_at DESC) = 1" +
|
|
1308
1311
|
(predicateEnd < sql.length ? " " : "");
|
|
1309
1312
|
cursor = predicateEnd;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { lexAgentSql, type AgentSqlToken } from "@agent-native/core/agent-sql";
|
|
2
|
+
|
|
3
|
+
const CONSTANT_WORDS = new Set([
|
|
4
|
+
"date",
|
|
5
|
+
"timestamp",
|
|
6
|
+
"cast",
|
|
7
|
+
"as",
|
|
8
|
+
"string",
|
|
9
|
+
"interval",
|
|
10
|
+
"day",
|
|
11
|
+
"week",
|
|
12
|
+
"month",
|
|
13
|
+
"year",
|
|
14
|
+
"current_date",
|
|
15
|
+
"current_timestamp",
|
|
16
|
+
"date_sub",
|
|
17
|
+
"date_add",
|
|
18
|
+
"timestamp_sub",
|
|
19
|
+
"timestamp_add",
|
|
20
|
+
"null",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function safePredicate(tokens: AgentSqlToken[], alias: string): string | null {
|
|
24
|
+
while (tokens[0]?.text === "(" && tokens[tokens.length - 1]?.text === ")") {
|
|
25
|
+
let depth = 0;
|
|
26
|
+
const encloses = tokens.every((token, index) => {
|
|
27
|
+
if (token.text === "(") depth++;
|
|
28
|
+
if (token.text === ")") depth--;
|
|
29
|
+
return depth > 0 || index === tokens.length - 1;
|
|
30
|
+
});
|
|
31
|
+
if (!encloses) break;
|
|
32
|
+
tokens = tokens.slice(1, -1);
|
|
33
|
+
}
|
|
34
|
+
let offset = 0;
|
|
35
|
+
if (tokens[1]?.text === ".") {
|
|
36
|
+
if (tokens[0].value.toLowerCase() !== alias.toLowerCase()) return null;
|
|
37
|
+
offset = 2;
|
|
38
|
+
}
|
|
39
|
+
const column = tokens[offset]?.value.toLowerCase();
|
|
40
|
+
const operator = tokens[offset + 1]?.value.toLowerCase();
|
|
41
|
+
if (column !== "event_date" && column !== "event_name") return null;
|
|
42
|
+
if (
|
|
43
|
+
!(column === "event_date"
|
|
44
|
+
? ["=", ">", ">=", "<", "<=", "between"].includes(operator)
|
|
45
|
+
: ["=", "in"].includes(operator))
|
|
46
|
+
)
|
|
47
|
+
return null;
|
|
48
|
+
const rhs = tokens.slice(offset + 2);
|
|
49
|
+
if (
|
|
50
|
+
!rhs.length ||
|
|
51
|
+
rhs.some(
|
|
52
|
+
(token, index) =>
|
|
53
|
+
token.kind === "parameter" ||
|
|
54
|
+
token.kind === "quoted-identifier" ||
|
|
55
|
+
(token.kind === "word" &&
|
|
56
|
+
["timestamp", "date", "string"].includes(token.value) &&
|
|
57
|
+
!(
|
|
58
|
+
rhs[index + 1]?.text === "(" ||
|
|
59
|
+
rhs[index + 1]?.kind === "string" ||
|
|
60
|
+
rhs[index - 1]?.value === "as"
|
|
61
|
+
)) ||
|
|
62
|
+
(token.kind === "word" &&
|
|
63
|
+
!CONSTANT_WORDS.has(token.value) &&
|
|
64
|
+
!(operator === "between" && token.value === "and")) ||
|
|
65
|
+
(token.kind === "punctuation" &&
|
|
66
|
+
!["(", ")", ","].includes(token.text)) ||
|
|
67
|
+
(token.kind === "operator" && !["+", "-"].includes(token.text)),
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
return null;
|
|
71
|
+
return [column, ...tokens.slice(offset + 1).map((token) => token.text)].join(
|
|
72
|
+
" ",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Only immutable delivery fields commute with latest-receipt deduplication.
|
|
77
|
+
// Payload, identity and timestamp filters must remain outside it.
|
|
78
|
+
export function firstPartyEventPushdownPredicates(
|
|
79
|
+
sql: string,
|
|
80
|
+
sourceIndex: number,
|
|
81
|
+
): string[] {
|
|
82
|
+
const tokens = lexAgentSql(sql, { dialect: "bigquery" });
|
|
83
|
+
const depths: number[] = [];
|
|
84
|
+
const stack: number[] = [];
|
|
85
|
+
const pairs = new Map<number, number>();
|
|
86
|
+
let wrapper: number | undefined;
|
|
87
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
88
|
+
const token = tokens[index];
|
|
89
|
+
if (token.start <= sourceIndex && token.end > sourceIndex)
|
|
90
|
+
wrapper = stack[stack.length - 1];
|
|
91
|
+
depths[index] = stack.length;
|
|
92
|
+
if (token.text === "(") stack.push(index);
|
|
93
|
+
if (token.text === ")") {
|
|
94
|
+
const open = stack.pop();
|
|
95
|
+
if (open !== undefined) pairs.set(open, index);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (wrapper === undefined || tokens[wrapper - 1]?.value !== "from") return [];
|
|
99
|
+
const close = pairs.get(wrapper);
|
|
100
|
+
if (close === undefined) return [];
|
|
101
|
+
let aliasIndex = close + 1;
|
|
102
|
+
if (tokens[aliasIndex]?.value === "as") aliasIndex++;
|
|
103
|
+
const alias = tokens[aliasIndex];
|
|
104
|
+
if (!alias || !["word", "quoted-identifier"].includes(alias.kind)) return [];
|
|
105
|
+
const depth = depths[wrapper];
|
|
106
|
+
let select = wrapper - 2;
|
|
107
|
+
while (
|
|
108
|
+
select >= 0 &&
|
|
109
|
+
!(depths[select] === depth && tokens[select].value === "select")
|
|
110
|
+
)
|
|
111
|
+
select--;
|
|
112
|
+
if (select < 0) return [];
|
|
113
|
+
// A preceding FROM means this derived source belongs to a join/comma list.
|
|
114
|
+
if (
|
|
115
|
+
tokens
|
|
116
|
+
.slice(select, wrapper - 1)
|
|
117
|
+
.some(
|
|
118
|
+
(token, index) =>
|
|
119
|
+
depths[select + index] === depth && token.value === "from",
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
return [];
|
|
123
|
+
const where = aliasIndex + 1;
|
|
124
|
+
if (tokens[where]?.value !== "where" || depths[where] !== depth) return [];
|
|
125
|
+
const predicates: AgentSqlToken[][] = [[]];
|
|
126
|
+
let between = false;
|
|
127
|
+
for (let index = where + 1; index < tokens.length; index++) {
|
|
128
|
+
const token = tokens[index];
|
|
129
|
+
if (
|
|
130
|
+
depths[index] < depth ||
|
|
131
|
+
(depths[index] === depth && token.text === ")") ||
|
|
132
|
+
(depths[index] === depth &&
|
|
133
|
+
token.kind === "word" &&
|
|
134
|
+
[
|
|
135
|
+
"group",
|
|
136
|
+
"having",
|
|
137
|
+
"qualify",
|
|
138
|
+
"order",
|
|
139
|
+
"limit",
|
|
140
|
+
"union",
|
|
141
|
+
"except",
|
|
142
|
+
"intersect",
|
|
143
|
+
"window",
|
|
144
|
+
].includes(token.value))
|
|
145
|
+
)
|
|
146
|
+
break;
|
|
147
|
+
if (
|
|
148
|
+
depths[index] === depth &&
|
|
149
|
+
token.kind === "word" &&
|
|
150
|
+
token.value === "or"
|
|
151
|
+
)
|
|
152
|
+
return [];
|
|
153
|
+
if (
|
|
154
|
+
depths[index] === depth &&
|
|
155
|
+
token.kind === "word" &&
|
|
156
|
+
token.value === "between"
|
|
157
|
+
)
|
|
158
|
+
between = true;
|
|
159
|
+
if (
|
|
160
|
+
depths[index] === depth &&
|
|
161
|
+
token.kind === "word" &&
|
|
162
|
+
token.value === "and"
|
|
163
|
+
) {
|
|
164
|
+
if (between) between = false;
|
|
165
|
+
else {
|
|
166
|
+
predicates.push([]);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
predicates[predicates.length - 1].push(token);
|
|
171
|
+
}
|
|
172
|
+
return predicates.flatMap((predicate) => {
|
|
173
|
+
const safe = safePredicate(predicate, alias.value);
|
|
174
|
+
return safe === null ? [] : [safe];
|
|
175
|
+
});
|
|
176
|
+
}
|