@agent-native/core-corpus 0.212.1 → 0.213.1
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 +29 -14
- package/corpus/templates/analytics/CHANGELOG.md +11 -0
- package/corpus/templates/analytics/actions/estimate-onboarding-journey-cost.ts +23 -6
- package/corpus/templates/analytics/actions/get-onboarding-journey.ts +20 -5
- package/corpus/templates/analytics/actions/provider-api-register.ts +2 -7
- package/corpus/templates/analytics/changelog/2026-10-21-analytics-recognizes-questions-about-connected-data-product-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-21-onboarding-journeys-now-honor-larger-event-row-budgets.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-21-preserve-unknown-replay-availability-in-partial-journeys.md +6 -0
- package/corpus/templates/analytics/package.json +6 -0
- package/corpus/templates/analytics/scripts/journey-capture-plan.ts +18 -7
- package/corpus/templates/analytics/scripts/journey-capture.ts +3 -1
- package/corpus/templates/analytics/server/lib/db-admin-connections.ts +19 -12
- package/corpus/templates/analytics/server/lib/journey-tree.ts +24 -9
- package/corpus/templates/analytics/server/lib/onboarding-journey-cost.ts +163 -94
- package/corpus/templates/analytics/server/lib/onboarding-journey.ts +162 -74
- package/corpus/templates/assets/server/lib/org-admin.ts +20 -16
- package/corpus/templates/brain/server/lib/zoom-governance.ts +11 -6
- package/corpus/templates/clips/CHANGELOG.md +1 -0
- package/corpus/templates/clips/changelog/2026-10-16-rewind-status-says-when-it-remembers-audio.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +7 -4
- package/corpus/templates/clips/desktop/src/components/RewindCaptureStatus.tsx +25 -0
- package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +6 -1
- package/corpus/templates/clips/desktop/src/shared/config.ts +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +2 -0
- package/corpus/templates/clips/server/lib/recordings.ts +4 -19
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/CHANGELOG.md +8 -0
- package/corpus/templates/content/actions/_content-space-access.ts +95 -6
- package/corpus/templates/content/actions/_database-utils.ts +20 -16
- package/corpus/templates/content/actions/_github-source-freshness.ts +72 -17
- package/corpus/templates/content/actions/_github-source-metadata.ts +63 -17
- package/corpus/templates/content/actions/_github-source.ts +141 -77
- package/corpus/templates/content/actions/check-github-connection.ts +15 -0
- package/corpus/templates/content/actions/create-github-collection.ts +180 -0
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +5 -0
- package/corpus/templates/content/actions/link-github-folder.ts +104 -36
- package/corpus/templates/content/actions/refresh-content-database-source.ts +37 -4
- package/corpus/templates/content/actions/sync-github-folder.ts +23 -5
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +172 -19
- package/corpus/templates/content/app/components/editor/database/GitHubLinkDialog.tsx +938 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourcePanel.tsx +228 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +561 -0
- package/corpus/templates/content/app/components/editor/database/GitHubUnlinkDialog.tsx +96 -0
- package/corpus/templates/content/app/components/editor/database/github-availability.ts +147 -0
- package/corpus/templates/content/app/components/editor/database/github-source-text.ts +280 -0
- package/corpus/templates/content/app/components/layout/Layout.tsx +2 -0
- package/corpus/templates/content/app/hooks/content-action-refresh.ts +6 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +3 -1
- package/corpus/templates/content/app/hooks/use-github-folder.ts +199 -0
- package/corpus/templates/content/app/i18n-data.ts +154 -1
- package/corpus/templates/content/app/lib/github-link-dialog.ts +86 -0
- package/corpus/templates/content/changelog/2026-10-18-connected-ai-assistants-can-link-a-new-collection-to-a-github.md +1 -1
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-organization-favorites-include-documents-shared-with-the-cur.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.sync-policy.md +32 -7
- package/corpus/templates/content/docs/product/encyclopedia.md +3 -3
- package/corpus/templates/content/docs/product/features/content.feature.trust-your-connected-sources.md +2 -2
- package/corpus/templates/content/docs/product/roadmap.md +1 -1
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +14 -0
- package/corpus/templates/content/server/lib/github-source-client.ts +50 -1
- package/corpus/templates/content/shared/api.ts +3 -0
- package/corpus/templates/content/shared/github-source.ts +68 -0
- package/corpus/templates/design/CHANGELOG.md +1 -0
- package/corpus/templates/design/actions/connect-assets-mcp.ts +5 -9
- package/corpus/templates/design/changelog/2026-10-21-journey-canvases-accept-partial-analytics-samples-when-repla.md +6 -0
- package/corpus/templates/design/shared/journey-canvas.ts +6 -4
- package/corpus/templates/dispatch/.agents/skills/a2a-protocol/SKILL.md +16 -13
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-customer-ai-credit-questions-route-to-account-health-instead.md +6 -0
- package/corpus/templates/factory/server/lib/require-workspace-member.ts +5 -17
- package/corpus/templates/mail/server/lib/queued-drafts.ts +9 -4
- package/corpus/templates/slides/server/workspace-defaults.ts +12 -15
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -31,12 +31,18 @@ The pipeline runs without a UI. Codex does all four steps from the terminal:
|
|
|
31
31
|
|
|
32
32
|
`estimate-onboarding-journey-cost` dry-runs the same access-scoped BigQuery
|
|
33
33
|
event-page SQL as `get-onboarding-journey`. Pass matching dates, app,
|
|
34
|
-
email filter, follow-up mode,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
email filter, follow-up mode, event row budget, and `maxTotalBytesBilled`. It
|
|
35
|
+
reports requested and allowed page counts, then dry-runs each allowed 4,000-row
|
|
36
|
+
page, including the one-row overflow check when within budget. `maxTotalBytesBilled`
|
|
37
|
+
defaults to 275 GB, reserving the full 25 GB per-query cap for each of 11 pages;
|
|
38
|
+
raise it deliberately to allow more pages, up to 1.275 TB and 51 pages. Match
|
|
39
|
+
this value when calling `get-onboarding-journey`. A 45-second deadline or a
|
|
40
|
+
per-page dry-run timeout can return `status: "incomplete"`, the count of
|
|
41
|
+
unestimated allowed pages, and `possiblePagesEstimatedBytes: null`. Read
|
|
42
|
+
`pageBudgetLimited` and the requested/allowed counts before interpreting an
|
|
43
|
+
estimate. The sum is a possible-pages estimate, not executed or billed bytes.
|
|
44
|
+
Data-dependent follow-up queries are excluded. An unavailable estimate fails
|
|
45
|
+
explicitly.
|
|
40
46
|
|
|
41
47
|
`get-onboarding-journey` returns `JourneyTree`; `format: "summary"` returns the
|
|
42
48
|
same event-derived journey and follow-up counts as an indented `outline` with no
|
|
@@ -48,12 +54,12 @@ To include the bounded cross-session estimate from the repository root, run:
|
|
|
48
54
|
`pnpm --filter analytics action get-onboarding-journey --dateFrom=2026-08-01 --dateTo=2026-08-31 --app=all --emailFilter=exclude_builder --followUpMode=person --format=summary`.
|
|
49
55
|
|
|
50
56
|
```ts
|
|
51
|
-
type JourneyExample = { sessionId: string; recordingId
|
|
57
|
+
type JourneyExample = { sessionId: string; recordingId?: string | null; ts: string; offsetMs: number | null; viewport: { width: number; height: number } | null; viewportReason?: string; replayUrl?: string };
|
|
52
58
|
type JourneyNode = { key: string; keyTruncated?: boolean; label: string; labelTruncated?: boolean; parentKey: string | null; depth: number; kind: "step" | "other"; n: number; pctOfRoot: number; pctOfParent: number; dropoffN: number; dropoffPct: number; deeperN: number; examples: JourneyExample[]; otherBranchCount?: number; otherBranchSummariesPartial?: true; otherBranches?: { path: string[]; pathTruncated?: boolean; key: string; keyTruncated?: boolean; sourceStepKey?: string; sourceStepKeyTruncated?: boolean; n: number; pctOfParent: number }[] };
|
|
53
59
|
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" | "followup_aggregate_cost_limited" | "followup_aggregate_query_timeout" | "followup_aggregate_query_failed" | "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; status?: "incomplete"; backendStatus?: number | null; backendReason?: string | null; backendOperation?: "submit" | "poll" | "job" | null }; cohortSessions: number | null }; laterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null }; noLaterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null } };
|
|
54
60
|
type JourneyPersonFollowupCounts = { canonicalPeople: number; laterActivityInSelectedSession: number; laterActivityOutsideSelectedSessionOrApp: number; laterActivityInBothSelectedAndOutside: number; laterActivityObservedAnywhere: number; noActivityObservedWithinHorizon: number; rightCensoredHorizon: number; fullyObservedCanonicalPeople: number; noActivityObservedWithinHorizonPctOfFullyObservedCanonicalPeople: number | null; identityUnavailableSessions: number; identityUnavailableSessionEvidence: { laterActivityInSelectedSession: number; laterActivityOutsideSelectedSessionOrApp: number } };
|
|
55
61
|
type JourneyPersonFollowup = { status: "complete" | "incomplete"; incompleteReason?: "journey_event_read_truncated" | "journey_event_read_invalid" | "journey_event_read_may_have_shifted" | "terminal_cohort_too_large" | "terminal_cohort_invalid" | "person_followup_aggregate_truncated" | "person_followup_query_cost_limited" | "person_followup_query_timeout" | "person_followup_query_failed" | "person_followup_aggregate_invalid" | "person_followup_terminal_cohort_mismatch"; horizonDays: 30; horizonMs: number; observationWatermark: string; observationFollowupDurationMs: { min: number; max: number; mean: number } | null; coverage: { journeyEventRead: { rows: number; pages: number; truncated: boolean; paginationConsistency: "stable" | "may_have_shifted" }; followupAggregateRead: { status: "complete" | "truncated" | "incomplete" | "not_run"; rows: number | null; queries: number; truncated: boolean; backendStatus?: number | null; backendReason?: string | null; backendOperation?: "submit" | "poll" | "job" | null }; terminalSessions: number | null; sessionsWithoutSelectedStep: number | null; identityJoin: { status: "complete" | "partial" | "unavailable" | "not_applicable" | "unknown"; terminalSessions: number | null; sessionsWithCanonicalIdentity: number | null; sessionsWithoutCanonicalIdentity: number | null; uniqueCanonicalPeople: number | null; coveragePct: number | null } }; total: JourneyPersonFollowupCounts | null; byTerminalStepKey: Record<string, JourneyPersonFollowupCounts> | null };
|
|
56
|
-
type JourneyTree = { window: { from: string; to: string }; app: string; rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[]; followUp: JourneyFollowup; personFollowUp?: JourneyPersonFollowup; standaloneSetup?: { rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[] }; notes?: string[] };
|
|
62
|
+
type JourneyTree = { window: { from: string; to: string }; app: string; rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number | null; truncated: boolean }; nodes: JourneyNode[]; followUp: JourneyFollowup; personFollowUp?: JourneyPersonFollowup; standaloneSetup?: { rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number | null; truncated: boolean }; nodes: JourneyNode[] }; notes?: string[] };
|
|
57
63
|
```
|
|
58
64
|
|
|
59
65
|
Analytics returns cohort nodes with counts. When extending a Design storyboard
|
|
@@ -132,8 +138,15 @@ top-level tree to `journey:capture`; the capture CLI reads top-level `nodes`.
|
|
|
132
138
|
session IDs or member identity keys used internally. The consistency field
|
|
133
139
|
reports `may_have_shifted` when the event read uses multiple `OFFSET` pages;
|
|
134
140
|
a late-arriving event can change page membership inside a historical window
|
|
135
|
-
too. The action
|
|
136
|
-
|
|
141
|
+
too. The action reads 4,000-row pages up to `maxEventRows` (at most 200,000),
|
|
142
|
+
plus one overflow row to detect truncation. Each BigQuery page keeps a 25 GB
|
|
143
|
+
billed-byte cap; by default, `maxTotalBytesBilled` reserves that cap for 11
|
|
144
|
+
pages (275 GB), and can be raised deliberately to 1.275 TB (51 pages). Event
|
|
145
|
+
page reads also stop at a 45-second deadline. If the aggregate budget or
|
|
146
|
+
deadline cuts a read, `coverage.truncated` and `notes` identify why; read them
|
|
147
|
+
before interpreting counts. A truncated event sample skips replay reads and
|
|
148
|
+
returns `sessionsWithReplay: null` because replay availability is unknown. If
|
|
149
|
+
a read truncates, page boundaries may have shifted,
|
|
137
150
|
or the terminal cohort cannot be covered in one query under the
|
|
138
151
|
800,000-character SQL limit or 50,000-token parser limit, `status` is
|
|
139
152
|
`incomplete` and all new cohort counts and follow-up duration are `null`;
|
|
@@ -191,15 +204,17 @@ top-level tree to `journey:capture`; the capture CLI reads top-level `nodes`.
|
|
|
191
204
|
- A session is an analytics session id. One that began before `dateFrom`
|
|
192
205
|
starts mid-journey, so start the window a day early.
|
|
193
206
|
- Read `coverage` before using the numbers. `truncated: true` means the event
|
|
194
|
-
read hit `maxEventRows`,
|
|
195
|
-
`maxNodes`; `notes` says
|
|
207
|
+
read hit `maxEventRows`, `maxTotalBytesBilled`, or its 45-second deadline, a
|
|
208
|
+
session went past `maxDepth`, or the node list hit `maxNodes`; `notes` says
|
|
209
|
+
which.
|
|
196
210
|
Never report a truncated tree as the whole window.
|
|
197
211
|
- The event row cap is shared by onboarding and standalone setup. If it is hit,
|
|
198
212
|
`standaloneSetup` may be an empty, truncated tree because standalone events
|
|
199
213
|
were beyond the read boundary; do not interpret that as zero standalone use.
|
|
200
|
-
- An example with `recordingId: null` has no replay the caller can open
|
|
214
|
+
- An example with `recordingId: null` has no replay the caller can open; an
|
|
215
|
+
omitted `recordingId` means replay availability was not checked. Its
|
|
201
216
|
`viewportReason` says why the viewport is unknown (`no_recording`,
|
|
202
|
-
`not_captured
|
|
217
|
+
`recording_unknown`, `not_captured`, `unreadable`).
|
|
203
218
|
- `offsetMs` is milliseconds from the recording's `startedAt`, not the first
|
|
204
219
|
rrweb event.
|
|
205
220
|
- `replayUrl` uses that same offset in `atMs`; the session page converts it to
|
|
@@ -3,6 +3,17 @@
|
|
|
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-21
|
|
7
|
+
|
|
8
|
+
### Improved
|
|
9
|
+
|
|
10
|
+
- Analytics recognizes questions about connected data, product analytics, sessions, dashboards, and charts.
|
|
11
|
+
- Onboarding journeys honor larger event row budgets with aggregate cost limits, a read deadline, and clear partial-coverage reporting.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Preserve unknown replay availability in partial journeys
|
|
16
|
+
|
|
6
17
|
## 2026-10-20
|
|
7
18
|
|
|
8
19
|
### Fixed
|
|
@@ -14,10 +14,16 @@ import {
|
|
|
14
14
|
estimateOnboardingJourneyEventQueryCost,
|
|
15
15
|
OnboardingJourneyCostError,
|
|
16
16
|
} from "../server/lib/onboarding-journey-cost.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_ONBOARDING_EVENT_ROW_BUDGET,
|
|
19
|
+
freezeOnboardingJourneyObservationWindow,
|
|
20
|
+
MAX_ONBOARDING_EVENT_ROW_BUDGET,
|
|
21
|
+
ONBOARDING_EVENTS_DEFAULT_MAX_TOTAL_BYTES_BILLED,
|
|
22
|
+
ONBOARDING_EVENTS_MAX_BYTES_BILLED,
|
|
23
|
+
ONBOARDING_EVENTS_MAX_TOTAL_BYTES_BILLED,
|
|
24
|
+
} from "../server/lib/onboarding-journey.js";
|
|
18
25
|
|
|
19
26
|
const MAX_WINDOW_DAYS = 90;
|
|
20
|
-
const MAX_JOURNEY_EVENT_ROWS = 200_000;
|
|
21
27
|
|
|
22
28
|
const isoDate = z
|
|
23
29
|
.string()
|
|
@@ -26,7 +32,7 @@ const isoDate = z
|
|
|
26
32
|
|
|
27
33
|
export default defineAction({
|
|
28
34
|
description:
|
|
29
|
-
"Dry-run the caller-scoped BigQuery event-page queries for an onboarding journey
|
|
35
|
+
"Dry-run the caller-scoped BigQuery event-page queries for an onboarding journey. Returns requested and budget-allowed page counts, estimated bytes per page, and total possible bytes when every allowed page is estimated. Each page reserves the 25 GB per-query cap against maxTotalBytesBilled. Estimates stop after 45 seconds and explicitly report unestimated pages.",
|
|
30
36
|
schema: z.object({
|
|
31
37
|
dateFrom: isoDate.describe(
|
|
32
38
|
"Inclusive UTC start date, YYYY-MM-DD. Use the same dateFrom as get-onboarding-journey.",
|
|
@@ -59,11 +65,21 @@ export default defineAction({
|
|
|
59
65
|
.number()
|
|
60
66
|
.int()
|
|
61
67
|
.min(1000)
|
|
62
|
-
.max(
|
|
68
|
+
.max(MAX_ONBOARDING_EVENT_ROW_BUDGET)
|
|
69
|
+
.optional()
|
|
70
|
+
.default(DEFAULT_ONBOARDING_EVENT_ROW_BUDGET)
|
|
71
|
+
.describe(
|
|
72
|
+
`Event row budget, matching get-onboarding-journey. Defaults to ${DEFAULT_ONBOARDING_EVENT_ROW_BUDGET}; at most ${MAX_ONBOARDING_EVENT_ROW_BUDGET}. Estimates include the one-row overflow check; the journey reads each page only when the previous page is full.`,
|
|
73
|
+
),
|
|
74
|
+
maxTotalBytesBilled: z.coerce
|
|
75
|
+
.number()
|
|
76
|
+
.int()
|
|
77
|
+
.min(ONBOARDING_EVENTS_MAX_BYTES_BILLED)
|
|
78
|
+
.max(ONBOARDING_EVENTS_MAX_TOTAL_BYTES_BILLED)
|
|
63
79
|
.optional()
|
|
64
|
-
.default(
|
|
80
|
+
.default(ONBOARDING_EVENTS_DEFAULT_MAX_TOTAL_BYTES_BILLED)
|
|
65
81
|
.describe(
|
|
66
|
-
|
|
82
|
+
"Aggregate cap for event-page queries, in bytes billed. Each allowed BigQuery page reserves its full 25 GB per-query cap. Defaults to 275 GB (11 pages); set a higher value deliberately to allow more pages, up to 1.275 TB (51 pages). Match get-onboarding-journey.",
|
|
67
83
|
),
|
|
68
84
|
}),
|
|
69
85
|
http: { method: "GET" },
|
|
@@ -102,6 +118,7 @@ export default defineAction({
|
|
|
102
118
|
freezeOnboardingJourneyObservationWindow(filters),
|
|
103
119
|
args.followUpMode === "person",
|
|
104
120
|
context?.signal,
|
|
121
|
+
args.maxTotalBytesBilled,
|
|
105
122
|
);
|
|
106
123
|
} catch (error) {
|
|
107
124
|
if (
|
|
@@ -11,14 +11,18 @@ import {
|
|
|
11
11
|
isCalendarDate,
|
|
12
12
|
} from "../server/lib/first-party-metric-catalog.js";
|
|
13
13
|
import {
|
|
14
|
+
DEFAULT_ONBOARDING_EVENT_ROW_BUDGET,
|
|
14
15
|
getOnboardingJourney,
|
|
16
|
+
MAX_ONBOARDING_EVENT_ROW_BUDGET,
|
|
17
|
+
ONBOARDING_EVENTS_DEFAULT_MAX_TOTAL_BYTES_BILLED,
|
|
18
|
+
ONBOARDING_EVENTS_MAX_BYTES_BILLED,
|
|
19
|
+
ONBOARDING_EVENTS_MAX_TOTAL_BYTES_BILLED,
|
|
15
20
|
OnboardingJourneyReadError,
|
|
16
21
|
JourneyRecordingsError,
|
|
17
22
|
} from "../server/lib/onboarding-journey.js";
|
|
18
23
|
|
|
19
24
|
const MAX_WINDOW_DAYS = 90;
|
|
20
25
|
const MAX_DEPTH = 40;
|
|
21
|
-
const MAX_JOURNEY_EVENT_ROWS = 200_000;
|
|
22
26
|
|
|
23
27
|
function resolveScope() {
|
|
24
28
|
const userEmail = getRequestUserEmail();
|
|
@@ -37,7 +41,7 @@ const isoDate = z
|
|
|
37
41
|
|
|
38
42
|
export default defineAction({
|
|
39
43
|
description:
|
|
40
|
-
'Return an access-scoped per-session onboarding tree through explicit saved-output events, preserving session counts and direct-parent denominators. An aggregated `other` node includes up to 20 root-to-branch label paths, source step keys for identical paths, session counts, and direct-parent percentages; branch summaries are capped at 200 per tree and 64 KiB of serialized detail. `otherBranchCount` gives the full number of branches, and `otherBranchSummariesPartial: true` marks summaries omitted by those bounds. Labels/path segments longer than 300 characters and keys longer than 2,048 characters are visibly shortened with stable hash suffixes and truncation flags so the tree stays within the Design input contract. Journey counts stay bounded by the 200,000-event read cap. followUpMode "session" keeps existing bounded same-session counts. "person" adds aggregate-only activity joined by direct canonical auth_user_id across first-party sessions and apps over a fixed 30-day horizon. It separates selected-session activity, outside-session/app activity and their overlap, no activity after a fully observed horizon, right-censoring, unknown identity, identity coverage, and read completeness. Its minute-aligned observationWatermark freezes event and receive time. Unknown identity and sessions without a selected step never count as inactive; no-activity is window-bounded evidence, not permanent churn. Output and attempt IDs are never returned. Incomplete follow-up counts are null.
|
|
44
|
+
'Return an access-scoped per-session onboarding tree through explicit saved-output events, preserving session counts and direct-parent denominators. An aggregated `other` node includes up to 20 root-to-branch label paths, source step keys for identical paths, session counts, and direct-parent percentages; branch summaries are capped at 200 per tree and 64 KiB of serialized detail. `otherBranchCount` gives the full number of branches, and `otherBranchSummariesPartial: true` marks summaries omitted by those bounds. Labels/path segments longer than 300 characters and keys longer than 2,048 characters are visibly shortened with stable hash suffixes and truncation flags so the tree stays within the Design input contract. Journey counts stay bounded by the 200,000-event read cap. BigQuery pages have a 25 GB per-query cap, and maxTotalBytesBilled bounds how many pages one event read may run; it defaults to 275 GB. Reads also stop at a 45-second event-read deadline, with coverage.truncated and notes identifying partial counts. Raise maxTotalBytesBilled deliberately to read more pages, up to 1.275 TB. When event coverage is truncated, replay reads are skipped, coverage.sessionsWithReplay is null, and examples omit recordingId with viewportReason "recording_unknown"; recordingId null means a completed lookup found no replay. followUpMode "session" keeps existing bounded same-session counts. "person" adds aggregate-only activity joined by direct canonical auth_user_id across first-party sessions and apps over a fixed 30-day horizon. It separates selected-session activity, outside-session/app activity and their overlap, no activity after a fully observed horizon, right-censoring, unknown identity, identity coverage, and read completeness. Its minute-aligned observationWatermark freezes event and receive time. Unknown identity and sessions without a selected step never count as inactive; no-activity is window-bounded evidence, not permanent churn. Output and attempt IDs are never returned. Incomplete follow-up counts are null. A query that fails for a backend, cost, or per-query timeout reason returns a stage-specific error with safe BigQuery status/reason when available; an internal event-read deadline instead returns completed pages with truncated coverage. An unreadable query never becomes zero counts or an empty-tree substitute.',
|
|
41
45
|
schema: z.object({
|
|
42
46
|
dateFrom: isoDate.describe(
|
|
43
47
|
"Inclusive UTC start date, YYYY-MM-DD. Sessions that began earlier appear mid-journey, so start a day before the period you care about.",
|
|
@@ -114,11 +118,21 @@ export default defineAction({
|
|
|
114
118
|
.number()
|
|
115
119
|
.int()
|
|
116
120
|
.min(1000)
|
|
117
|
-
.max(
|
|
121
|
+
.max(MAX_ONBOARDING_EVENT_ROW_BUDGET)
|
|
118
122
|
.optional()
|
|
119
|
-
.default(
|
|
123
|
+
.default(DEFAULT_ONBOARDING_EVENT_ROW_BUDGET)
|
|
120
124
|
.describe(
|
|
121
|
-
|
|
125
|
+
`Event rows to read, plus one extra row to detect truncation. Each query reads at most 4,000 rows and has a 25 GB BigQuery bytes-billed cap. The aggregate page budget and 45-second deadline can stop the read earlier; coverage.truncated and notes say when counts are partial. Defaults to ${DEFAULT_ONBOARDING_EVENT_ROW_BUDGET}.`,
|
|
126
|
+
),
|
|
127
|
+
maxTotalBytesBilled: z.coerce
|
|
128
|
+
.number()
|
|
129
|
+
.int()
|
|
130
|
+
.min(ONBOARDING_EVENTS_MAX_BYTES_BILLED)
|
|
131
|
+
.max(ONBOARDING_EVENTS_MAX_TOTAL_BYTES_BILLED)
|
|
132
|
+
.optional()
|
|
133
|
+
.default(ONBOARDING_EVENTS_DEFAULT_MAX_TOTAL_BYTES_BILLED)
|
|
134
|
+
.describe(
|
|
135
|
+
"Aggregate cap for event-page queries, in bytes billed. Each allowed BigQuery page reserves its full 25 GB per-query cap. Defaults to 275 GB (11 pages); set a higher value deliberately to allow more pages, up to 1.275 TB (51 pages).",
|
|
122
136
|
),
|
|
123
137
|
recency: z
|
|
124
138
|
.enum(["newest", "none"])
|
|
@@ -203,6 +217,7 @@ export default defineAction({
|
|
|
203
217
|
minNodeSessions: args.minNodeSessions,
|
|
204
218
|
examplesPerNode: args.examplesPerNode,
|
|
205
219
|
maxEventRows: args.maxEventRows,
|
|
220
|
+
maxTotalBytesBilled: args.maxTotalBytesBilled,
|
|
206
221
|
settleMs: args.settleMs,
|
|
207
222
|
recency: args.recency,
|
|
208
223
|
viewport: Object.keys(viewport).length ? viewport : undefined,
|
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
import { getDbExec } from "@agent-native/core/db";
|
|
2
1
|
import {
|
|
3
2
|
createCustomProviderRegistrationAction,
|
|
4
3
|
CustomProviderRegistrationSchema,
|
|
5
4
|
} from "@agent-native/core/provider-api/actions/custom-provider-registration";
|
|
5
|
+
import { readOrgMemberRole } from "@agent-native/core/server";
|
|
6
6
|
import { getCredentialContext } from "@agent-native/core/server/request-context";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
async function resolveCallerOrgRole(orgId: string, email: string) {
|
|
10
10
|
try {
|
|
11
|
-
|
|
12
|
-
sql: `SELECT role FROM org_members WHERE org_id = $1 AND LOWER(email) = $2 LIMIT 1`,
|
|
13
|
-
args: [orgId, email.toLowerCase()],
|
|
14
|
-
});
|
|
15
|
-
const role = (rows[0] as { role?: unknown } | undefined)?.role;
|
|
16
|
-
return typeof role === "string" && role ? role : null;
|
|
11
|
+
return await readOrgMemberRole(orgId, email);
|
|
17
12
|
} catch {
|
|
18
13
|
return null;
|
|
19
14
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"name": "analytics",
|
|
3
3
|
"displayName": "Analytics",
|
|
4
4
|
"private": true,
|
|
5
|
+
"description": "Connect data sources and warehouses, including BigQuery, to explore product and event analytics, session data, dashboards, and charts.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"dev": "DATABASE_URL=${ANALYTICS_DATABASE_URL:-pglite:./data/pglite} agent-native dev",
|
|
@@ -115,6 +116,11 @@
|
|
|
115
116
|
"vitest": "catalog:"
|
|
116
117
|
},
|
|
117
118
|
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
|
|
119
|
+
"agent-native": {
|
|
120
|
+
"whenToUse": [
|
|
121
|
+
"Use Analytics for connected data sources and warehouses, including BigQuery, product and event analytics, session data, dashboards, and charts."
|
|
122
|
+
]
|
|
123
|
+
},
|
|
118
124
|
"pkg": {
|
|
119
125
|
"assets": [
|
|
120
126
|
"dist/spa/*"
|
|
@@ -15,7 +15,7 @@ export const DEFAULT_APP_URL = "https://analytics.agent-native.com";
|
|
|
15
15
|
|
|
16
16
|
export interface TreeExample {
|
|
17
17
|
sessionId: string;
|
|
18
|
-
recordingId
|
|
18
|
+
recordingId?: string | null;
|
|
19
19
|
ts: string;
|
|
20
20
|
offsetMs: number | null;
|
|
21
21
|
viewport: { width: number; height: number } | null;
|
|
@@ -53,7 +53,11 @@ export function parseTree(raw: unknown): { nodes: TreeNode[] } {
|
|
|
53
53
|
if (!e || typeof e.sessionId !== "string") {
|
|
54
54
|
fail(`${at}.sessionId`, "must be a string");
|
|
55
55
|
}
|
|
56
|
-
if (
|
|
56
|
+
if (
|
|
57
|
+
e.recordingId !== undefined &&
|
|
58
|
+
e.recordingId !== null &&
|
|
59
|
+
typeof e.recordingId !== "string"
|
|
60
|
+
) {
|
|
57
61
|
fail(`${at}.recordingId`, "must be a string or null");
|
|
58
62
|
}
|
|
59
63
|
if (
|
|
@@ -74,9 +78,10 @@ export function parseTree(raw: unknown): { nodes: TreeNode[] } {
|
|
|
74
78
|
) {
|
|
75
79
|
fail(`${at}.viewport`, "must be { width, height } or null");
|
|
76
80
|
}
|
|
81
|
+
const recordingId = e.recordingId as string | null | undefined;
|
|
77
82
|
return {
|
|
78
83
|
sessionId: e.sessionId as string,
|
|
79
|
-
recordingId
|
|
84
|
+
...(recordingId !== undefined ? { recordingId } : {}),
|
|
80
85
|
ts: typeof e.ts === "string" ? e.ts : "",
|
|
81
86
|
offsetMs: e.offsetMs as number | null,
|
|
82
87
|
viewport: v
|
|
@@ -107,7 +112,11 @@ export interface PlanItem {
|
|
|
107
112
|
export interface SkippedExample {
|
|
108
113
|
nodeKey: string;
|
|
109
114
|
exampleIndex: number;
|
|
110
|
-
reason:
|
|
115
|
+
reason:
|
|
116
|
+
| "no_recording"
|
|
117
|
+
| "replay_unknown"
|
|
118
|
+
| "no_offset"
|
|
119
|
+
| "aspect_out_of_range";
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
export function aspectInRange(
|
|
@@ -124,9 +133,10 @@ export function aspectInRange(
|
|
|
124
133
|
|
|
125
134
|
/**
|
|
126
135
|
* Up to `perNode` renderable examples per node, in the tree's order. An
|
|
127
|
-
* example with
|
|
128
|
-
*
|
|
129
|
-
* the captured size
|
|
136
|
+
* example with unknown replay availability, a completed lookup with no
|
|
137
|
+
* recording, no offset, or a known viewport outside the aspect range is listed
|
|
138
|
+
* as skipped. An unknown viewport is rendered and checked on the captured size
|
|
139
|
+
* instead.
|
|
130
140
|
*/
|
|
131
141
|
export function planCapture(
|
|
132
142
|
tree: { nodes: TreeNode[] },
|
|
@@ -139,6 +149,7 @@ export function planCapture(
|
|
|
139
149
|
node.examples.forEach((example, exampleIndex) => {
|
|
140
150
|
const skip = (reason: SkippedExample["reason"]) =>
|
|
141
151
|
skipped.push({ nodeKey: node.key, exampleIndex, reason });
|
|
152
|
+
if (example.recordingId === undefined) return skip("replay_unknown");
|
|
142
153
|
if (!example.recordingId) return skip("no_recording");
|
|
143
154
|
if (example.offsetMs === null || example.offsetMs < 0) {
|
|
144
155
|
return skip("no_offset");
|
|
@@ -2170,7 +2170,9 @@ async function main(argv: string[]): Promise<number> {
|
|
|
2170
2170
|
);
|
|
2171
2171
|
}
|
|
2172
2172
|
console.error(
|
|
2173
|
-
|
|
2173
|
+
skipped.some((example) => example.reason === "replay_unknown")
|
|
2174
|
+
? `Nothing to render: replay lookup was skipped for some examples because the journey read was truncated (see "skipped" in ${manifestPath}). Re-run get-onboarding-journey with a narrower window or a larger event budget.`
|
|
2175
|
+
: `Nothing to render: no example has a recording and an offset (see "skipped" in ${manifestPath}). Re-run get-onboarding-journey with a window that has replays.`,
|
|
2174
2176
|
);
|
|
2175
2177
|
return 1;
|
|
2176
2178
|
}
|
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
getRequestOrgId,
|
|
14
14
|
getRequestUserEmail,
|
|
15
|
+
getRequestContext,
|
|
15
16
|
getSession,
|
|
17
|
+
readOrgMemberRole,
|
|
16
18
|
runWithRequestContext,
|
|
17
19
|
} from "@agent-native/core/server";
|
|
18
20
|
import type { H3Event } from "h3";
|
|
@@ -68,18 +70,23 @@ export async function resolveOrgRole(
|
|
|
68
70
|
userEmail: string,
|
|
69
71
|
orgId: string,
|
|
70
72
|
): Promise<string | null> {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
const request = getRequestContext();
|
|
74
|
+
if (
|
|
75
|
+
request?.a2aIdentityAssurance === "org-member" &&
|
|
76
|
+
(request.orgId?.trim() !== orgId.trim() ||
|
|
77
|
+
request.userEmail?.trim().toLowerCase() !==
|
|
78
|
+
userEmail.trim().toLowerCase())
|
|
79
|
+
) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return await readOrgMemberRole(orgId, userEmail);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new DbAdminConnectionError(
|
|
86
|
+
503,
|
|
87
|
+
`Couldn't verify your organization role: ${redactDbAdminError(error)}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
export async function requireAnalyticsAdminContext(input?: {
|
|
@@ -48,20 +48,23 @@ export interface BuildJourneyTreeOptions {
|
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Why an example has no viewport. `no_recording`: no accessible recording
|
|
51
|
-
* covers the step. `
|
|
52
|
-
* `
|
|
51
|
+
* covers the step. `recording_unknown`: replay lookup was skipped.
|
|
52
|
+
* `not_captured`: the recording predates viewport capture. `unreadable`: its
|
|
53
|
+
* metadata could not be parsed.
|
|
53
54
|
*/
|
|
54
55
|
export type ViewportUnknownReason =
|
|
55
56
|
| "no_recording"
|
|
57
|
+
| "recording_unknown"
|
|
56
58
|
| "not_captured"
|
|
57
59
|
| "unreadable";
|
|
58
60
|
|
|
59
61
|
export interface JourneyExample {
|
|
60
62
|
sessionId: string;
|
|
61
|
-
|
|
63
|
+
/** Omitted when replay lookup was skipped; null means lookup found no replay. */
|
|
64
|
+
recordingId?: string | null;
|
|
62
65
|
/** ISO time of the step in this session. */
|
|
63
66
|
ts: string;
|
|
64
|
-
/** Milliseconds from recording.startedAt to the step; null
|
|
67
|
+
/** Milliseconds from recording.startedAt to the step; null when no offset is known. */
|
|
65
68
|
offsetMs: number | null;
|
|
66
69
|
viewport: { width: number; height: number } | null;
|
|
67
70
|
/** Present exactly when `viewport` is null. */
|
|
@@ -225,14 +228,17 @@ function hasViewportConstraints(constraints: ViewportConstraints | undefined) {
|
|
|
225
228
|
interface Candidate {
|
|
226
229
|
sessionId: string;
|
|
227
230
|
tsMs: number;
|
|
228
|
-
recording: JourneyRecording | null;
|
|
231
|
+
recording: JourneyRecording | null | undefined;
|
|
229
232
|
}
|
|
230
233
|
|
|
231
234
|
function chooseCandidate(
|
|
232
235
|
ref: { sessionId: string; tsMs: number },
|
|
233
|
-
recordings: readonly JourneyRecording[],
|
|
236
|
+
recordings: readonly JourneyRecording[] | null,
|
|
234
237
|
constraints: ViewportConstraints | undefined,
|
|
235
238
|
): Candidate | null {
|
|
239
|
+
if (recordings === null) {
|
|
240
|
+
return constraints?.requireKnown ? null : { ...ref, recording: undefined };
|
|
241
|
+
}
|
|
236
242
|
const constrained = hasViewportConstraints(constraints);
|
|
237
243
|
const usable = recordings
|
|
238
244
|
.filter((recording) => covers(recording, ref.tsMs))
|
|
@@ -268,6 +274,15 @@ function exampleFrom(
|
|
|
268
274
|
): JourneyExample {
|
|
269
275
|
const { recording } = candidate;
|
|
270
276
|
const ts = new Date(candidate.tsMs).toISOString();
|
|
277
|
+
if (recording === undefined) {
|
|
278
|
+
return {
|
|
279
|
+
sessionId: candidate.sessionId,
|
|
280
|
+
ts,
|
|
281
|
+
offsetMs: null,
|
|
282
|
+
viewport: null,
|
|
283
|
+
viewportReason: "recording_unknown",
|
|
284
|
+
};
|
|
285
|
+
}
|
|
271
286
|
if (!recording) {
|
|
272
287
|
return {
|
|
273
288
|
sessionId: candidate.sessionId,
|
|
@@ -305,7 +320,7 @@ function exampleFrom(
|
|
|
305
320
|
|
|
306
321
|
function pickExamples(
|
|
307
322
|
refs: ReadonlyArray<{ sessionId: string; tsMs: number }>,
|
|
308
|
-
recordings: ReadonlyMap<string, readonly JourneyRecording[]
|
|
323
|
+
recordings: ReadonlyMap<string, readonly JourneyRecording[]> | null,
|
|
309
324
|
options: BuildJourneyTreeOptions,
|
|
310
325
|
): JourneyExample[] {
|
|
311
326
|
if (options.examplesPerNode === 0) return [];
|
|
@@ -314,7 +329,7 @@ function pickExamples(
|
|
|
314
329
|
for (const ref of refs) {
|
|
315
330
|
const candidate = chooseCandidate(
|
|
316
331
|
ref,
|
|
317
|
-
recordings.get(ref.sessionId) ?? [],
|
|
332
|
+
recordings === null ? null : (recordings.get(ref.sessionId) ?? []),
|
|
318
333
|
options.viewport,
|
|
319
334
|
);
|
|
320
335
|
if (candidate) candidates.push(candidate);
|
|
@@ -354,7 +369,7 @@ const compareKeys = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
354
369
|
*/
|
|
355
370
|
export function buildJourneyTree(
|
|
356
371
|
sessions: readonly JourneySession[],
|
|
357
|
-
recordings: ReadonlyMap<string, readonly JourneyRecording[]
|
|
372
|
+
recordings: ReadonlyMap<string, readonly JourneyRecording[]> | null,
|
|
358
373
|
options: BuildJourneyTreeOptions,
|
|
359
374
|
): { rootN: number; nodes: JourneyNode[] } {
|
|
360
375
|
const root = trieNode("root", "All sessions", "root", 0);
|