@agent-native/core 0.139.0 → 0.141.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/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-stay-complete-and-atomic.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +0 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +11 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +8 -18
- package/corpus/templates/analytics/server/plugins/db.ts +174 -0
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +38 -38
- package/corpus/templates/calendar/changelog/2026-08-05-out-of-office-blocks-now-open-from-their-scheduled-time-rang.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-emails.ts +70 -154
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +28 -64
- package/corpus/templates/clips/actions/get-recording-player-data.ts +1 -0
- package/corpus/templates/clips/actions/invite-member.ts +16 -39
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -3
- package/corpus/templates/clips/app/components/player/reaction-emojis.ts +10 -0
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +2 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +20 -6
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +33 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +5 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -1
- package/corpus/templates/clips/changelog/2026-08-05-comment-and-video-reaction-pickers-now-include-eyes-and-mind.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-fresh-recordings-now-open-with-the-complete-video.md +6 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +0 -24
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +26 -57
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +16 -3
- package/corpus/templates/forms/server/lib/response-email.ts +0 -2
- package/corpus/templates/plan/actions/request-plan-access.ts +12 -33
- package/corpus/templates/plan/server/lib/comment-notifications.ts +27 -53
- package/corpus/templates/slides/server/lib/comment-notifications.ts +21 -44
- package/dist/a2a/handlers.js +9 -10
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.js +8 -8
- package/dist/db/migrations.js +39 -19
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +0 -45
- package/dist/localization/default-messages.js +0 -45
- package/dist/notifications/routes.d.ts +5 -5
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/handlers.js +1 -8
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +0 -22
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +3 -1
- package/dist/server/better-auth-instance.js +2 -17
- package/dist/server/email.d.ts +0 -9
- package/dist/server/email.js +3 -44
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +0 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
- package/docs/content/authentication.mdx +25 -19
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
- package/corpus/templates/analytics/server/lib/emails.ts +0 -51
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/calendar/server/lib/emails.ts +0 -167
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/clips/server/lib/emails.ts +0 -256
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +0 -5
- package/corpus/templates/content/server/lib/emails.ts +0 -70
- package/corpus/templates/content/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +0 -4
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +0 -4
- package/corpus/templates/forms/server/lib/emails.ts +0 -52
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/plan/server/lib/emails.ts +0 -67
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/slides/server/lib/emails.ts +0 -40
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +0 -5
- package/dist/email-catalog/actions/list-email-activity.d.ts +0 -6
- package/dist/email-catalog/actions/list-email-activity.js +0 -15
- package/dist/email-catalog/actions/list-email-engagement.d.ts +0 -6
- package/dist/email-catalog/actions/list-email-engagement.js +0 -15
- package/dist/email-catalog/actions/list-email-log.d.ts +0 -8
- package/dist/email-catalog/actions/list-email-log.js +0 -22
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +0 -23
- package/dist/email-catalog/actions/list-transactional-emails.js +0 -61
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +0 -10
- package/dist/email-catalog/actions/render-transactional-email-preview.js +0 -24
- package/dist/email-catalog/authorize.d.ts +0 -2
- package/dist/email-catalog/authorize.js +0 -10
- package/dist/email-catalog/log.d.ts +0 -57
- package/dist/email-catalog/log.js +0 -117
- package/dist/email-catalog/provider-metrics.d.ts +0 -34
- package/dist/email-catalog/provider-metrics.js +0 -165
- package/dist/email-catalog/registry.d.ts +0 -79
- package/dist/email-catalog/registry.js +0 -71
- package/dist/email-catalog/schema.d.ts +0 -212
- package/dist/email-catalog/schema.js +0 -49
- package/dist/email-catalog/system-emails.d.ts +0 -14
- package/dist/email-catalog/system-emails.js +0 -68
package/corpus/README.md
CHANGED
|
@@ -15,7 +15,7 @@ function resolveScope() {
|
|
|
15
15
|
|
|
16
16
|
export default defineAction({
|
|
17
17
|
description:
|
|
18
|
-
"Query the built-in first-party Analytics source: events recorded through this app's analytics collector endpoint (/track), compact daily rollups, identifiable user-day rollups, and session replay summaries recorded through /api/analytics/replay. This source does not require an external provider connection. Use it for questions about app/site traffic, product events, template/app usage, conversions, session recordings, LLM/agent observability, model cost, token volume, latency, and other first-party data collected by this analytics app. Use source-specific actions such as BigQuery, GA4, Mixpanel, PostHog, or Amplitude when the user asks for those sources or the relevant data lives there. SQL may read analytics_events, analytics_event_daily_rollups, analytics_user_days, and session_recordings; session_replay_chunks is intentionally unavailable, and reads are automatically scoped to the current user/org. Prefer analytics_event_daily_rollups for event counts and analytics_user_days for active-user or retention questions. Use analytics_events only for bounded recent drill-downs with an explicit event_date or timestamp range. Aggregate, project only needed columns, and add a LIMIT for raw or high-cardinality reads; do not issue an unbounded raw-event scan or paginate a large cohort. An explicit all-time or lifetime request remains all-time, so do not invent a default lower time bound. Safe scoped results are cached for up to five minutes for this agent action. analytics_events columns include event_name, timestamp, event_date, user_id, anonymous_id, user_key, session_id, app, template, signed_in, url, path, hostname, referrer, properties, and context. analytics_event_daily_rollups columns include tenant_key, owner_email, org_id, event_date, event_name, app, template, and event_count. analytics_user_days columns include tenant_key, owner_email, org_id, event_date, and user_key. LLM observability events use event_name = '$ai_generation'; useful properties include $ai_trace_id/run_id, $ai_session_id/thread_id, $ai_model/model, $ai_provider/provider, $ai_input_tokens/input_tokens, $ai_output_tokens/output_tokens, cache_read_tokens, cache_write_tokens, cost_cents_x100, $ai_total_cost_usd/cost_usd, duration_ms/$ai_latency, status, tool_calls, successful_tools, failed_tools, tools, tools_truncated, delegated, delegation_protocol, caller_app, delegation_task_id, a2a_task_id, parent_run_id, parent_turn_id, and error_message/$ai_error. Agent Teams child runs use delegation_protocol = 'agent-team' and retain their own run_id while linking to the launching run through parent_run_id. The bounded tools array contains names, relative start times, durations, statuses, and coarse error classes only, never args or results; failed runs and interrupted tools remain queryable. session_recordings columns include id, session_id, user_id, anonymous_id, user_key, started_at, ended_at, duration_ms, chunk_count, event_count, page_count, error_count, rage_click_count, app, template, status, first_url, last_url, path, hostname, referrer, and metadata.",
|
|
18
|
+
"Query the built-in first-party Analytics source: events recorded through this app's analytics collector endpoint (/track), compact daily rollups, identifiable user-day rollups, and session replay summaries recorded through /api/analytics/replay. This source does not require an external provider connection. Use it for questions about app/site traffic, product events, template/app usage, conversions, session recordings, LLM/agent observability, model cost, token volume, latency, and other first-party data collected by this analytics app. Use source-specific actions such as BigQuery, GA4, Mixpanel, PostHog, or Amplitude when the user asks for those sources or the relevant data lives there. SQL may read analytics_events, analytics_event_daily_rollups, analytics_user_days, and session_recordings; session_replay_chunks is intentionally unavailable, and reads are automatically scoped to the current user/org. Prefer analytics_event_daily_rollups for event counts and analytics_user_days for active-user or retention questions. The rollups are backfilled from existing analytics_events when this schema is upgraded and are updated transactionally with new ingest. Use analytics_events only for bounded recent drill-downs with an explicit event_date or timestamp range. Aggregate, project only needed columns, and add a LIMIT for raw or high-cardinality reads; do not issue an unbounded raw-event scan or paginate a large cohort. An explicit all-time or lifetime request remains all-time, so do not invent a default lower time bound. Safe scoped results are cached for up to five minutes for this agent action. analytics_events columns include event_name, timestamp, event_date, user_id, anonymous_id, user_key, session_id, app, template, signed_in, url, path, hostname, referrer, properties, and context. analytics_event_daily_rollups columns include tenant_key, owner_email, org_id, event_date, event_name, app, template, and event_count. analytics_user_days columns include tenant_key, owner_email, org_id, event_date, and user_key. LLM observability events use event_name = '$ai_generation'; useful properties include $ai_trace_id/run_id, $ai_session_id/thread_id, $ai_model/model, $ai_provider/provider, $ai_input_tokens/input_tokens, $ai_output_tokens/output_tokens, cache_read_tokens, cache_write_tokens, cost_cents_x100, $ai_total_cost_usd/cost_usd, duration_ms/$ai_latency, status, tool_calls, successful_tools, failed_tools, tools, tools_truncated, delegated, delegation_protocol, caller_app, delegation_task_id, a2a_task_id, parent_run_id, parent_turn_id, and error_message/$ai_error. Agent Teams child runs use delegation_protocol = 'agent-team' and retain their own run_id while linking to the launching run through parent_run_id. The bounded tools array contains names, relative start times, durations, statuses, and coarse error classes only, never args or results; failed runs and interrupted tools remain queryable. session_recordings columns include id, session_id, user_id, anonymous_id, user_key, started_at, ended_at, duration_ms, chunk_count, event_count, page_count, error_count, rage_click_count, app, template, status, first_url, last_url, path, hostname, referrer, and metadata.",
|
|
19
19
|
schema: z.object({
|
|
20
20
|
sql: z
|
|
21
21
|
.string()
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
type DashboardReportCaptureOutcome,
|
|
19
19
|
type DashboardReportSubscription,
|
|
20
20
|
} from "./dashboard-report-subscriptions";
|
|
21
|
-
import { ANALYTICS_DASHBOARD_REPORT_EMAIL_ID } from "./emails";
|
|
22
21
|
|
|
23
22
|
const DATE_FILTER_TYPES: ReadonlySet<FilterType> = new Set([
|
|
24
23
|
"date",
|
|
@@ -327,7 +326,6 @@ export async function sendDashboardReportSubscription(
|
|
|
327
326
|
? { attachments: rendered.attachments }
|
|
328
327
|
: {}),
|
|
329
328
|
timeoutMs: emailTimeoutMs,
|
|
330
|
-
templateId: ANALYTICS_DASHBOARD_REPORT_EMAIL_ID,
|
|
331
329
|
});
|
|
332
330
|
}
|
|
333
331
|
|
|
@@ -73,12 +73,12 @@ function stableId(prefix: string, parts: readonly string[]): string {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* Upsert compact rollups
|
|
77
|
-
*
|
|
78
|
-
* write analytics_events, so ingestion can choose its own failure boundary.
|
|
76
|
+
* Upsert compact rollups for a normalized batch. When ingestion passes its
|
|
77
|
+
* transaction through, raw events and rollups share one commit boundary.
|
|
79
78
|
*/
|
|
80
79
|
export async function upsertFirstPartyAnalyticsRollups(
|
|
81
80
|
rows: readonly NormalizedFirstPartyAnalyticsEventRow[],
|
|
81
|
+
transaction?: any,
|
|
82
82
|
): Promise<FirstPartyAnalyticsRollupResult> {
|
|
83
83
|
const dailyRollups = new Map<string, DailyRollupRow>();
|
|
84
84
|
const userDays = new Map<string, UserDayRow>();
|
|
@@ -140,8 +140,7 @@ export async function upsertFirstPartyAnalyticsRollups(
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
const
|
|
144
|
-
await db.transaction(async (tx: any) => {
|
|
143
|
+
const writeRollups = async (tx: any) => {
|
|
145
144
|
const dailyRows = [...dailyRollups.values()];
|
|
146
145
|
await tx
|
|
147
146
|
.insert(schema.analyticsEventDailyRollups)
|
|
@@ -172,7 +171,13 @@ export async function upsertFirstPartyAnalyticsRollups(
|
|
|
172
171
|
],
|
|
173
172
|
});
|
|
174
173
|
}
|
|
175
|
-
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (transaction) {
|
|
177
|
+
await writeRollups(transaction);
|
|
178
|
+
} else {
|
|
179
|
+
await (getDb() as any).transaction(writeRollups);
|
|
180
|
+
}
|
|
176
181
|
|
|
177
182
|
return {
|
|
178
183
|
eventCount: rows.length,
|
|
@@ -505,24 +505,14 @@ export async function recordAnalyticsEvents(
|
|
|
505
505
|
});
|
|
506
506
|
|
|
507
507
|
if (rows.length) {
|
|
508
|
-
await db.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
} catch (error) {
|
|
517
|
-
// Raw events are the durable ingest record. Keep /track available when
|
|
518
|
-
// a rollup write is temporarily unavailable; the warning preserves the
|
|
519
|
-
// failure signal for operators without turning a successful raw ingest
|
|
520
|
-
// into a client retry and duplicate event batch.
|
|
521
|
-
console.warn(
|
|
522
|
-
"[first-party-analytics] Rollup update failed after raw ingest:",
|
|
523
|
-
error,
|
|
524
|
-
);
|
|
525
|
-
}
|
|
508
|
+
await db.transaction(async (tx: any) => {
|
|
509
|
+
await tx.insert(schema.analyticsEvents).values(rows);
|
|
510
|
+
await tx
|
|
511
|
+
.update(schema.analyticsPublicKeys)
|
|
512
|
+
.set({ lastUsedAt: receivedAt })
|
|
513
|
+
.where(eq(schema.analyticsPublicKeys.id, key.id));
|
|
514
|
+
await upsertFirstPartyAnalyticsRollups(rows, tx);
|
|
515
|
+
});
|
|
526
516
|
}
|
|
527
517
|
|
|
528
518
|
// Fork captured exceptions into the dedicated error-capture tables. This is
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ensureAdditiveColumns,
|
|
3
|
+
getDialect,
|
|
3
4
|
getDbExec,
|
|
5
|
+
isPostgres,
|
|
4
6
|
runMigrations,
|
|
5
7
|
} from "@agent-native/core/db";
|
|
6
8
|
|
|
@@ -28,6 +30,169 @@ function isDrizzleTable(value: unknown): value is object {
|
|
|
28
30
|
|
|
29
31
|
const schemaTables = Object.values(schema).filter(isDrizzleTable);
|
|
30
32
|
|
|
33
|
+
const ANALYTICS_ROLLUP_BACKFILL_STATEMENTS = {
|
|
34
|
+
postgres: [
|
|
35
|
+
`
|
|
36
|
+
INSERT INTO analytics_event_daily_rollups (
|
|
37
|
+
id, tenant_key, owner_email, org_id, event_date, event_name,
|
|
38
|
+
app, template, event_count
|
|
39
|
+
)
|
|
40
|
+
SELECT
|
|
41
|
+
md5(random()::text || clock_timestamp()::text), tenant_key,
|
|
42
|
+
MIN(owner_email), MIN(org_id), event_date, event_name, app, template,
|
|
43
|
+
COUNT(*)::INTEGER
|
|
44
|
+
FROM (
|
|
45
|
+
SELECT
|
|
46
|
+
CASE
|
|
47
|
+
WHEN org_id IS NOT NULL AND org_id <> '' THEN 'org:' || org_id
|
|
48
|
+
ELSE 'user:' || owner_email
|
|
49
|
+
END AS tenant_key,
|
|
50
|
+
owner_email,
|
|
51
|
+
org_id,
|
|
52
|
+
COALESCE(NULLIF(event_date, ''), substr(timestamp, 1, 10)) AS event_date,
|
|
53
|
+
event_name,
|
|
54
|
+
COALESCE(app, '') AS app,
|
|
55
|
+
COALESCE(template, '') AS template
|
|
56
|
+
FROM analytics_events
|
|
57
|
+
) AS historical_events
|
|
58
|
+
WHERE event_date <> ''
|
|
59
|
+
GROUP BY tenant_key, event_date, event_name, app, template
|
|
60
|
+
ON CONFLICT (tenant_key, event_date, event_name, app, template)
|
|
61
|
+
DO UPDATE SET event_count = GREATEST(
|
|
62
|
+
analytics_event_daily_rollups.event_count,
|
|
63
|
+
EXCLUDED.event_count
|
|
64
|
+
)
|
|
65
|
+
`,
|
|
66
|
+
`
|
|
67
|
+
INSERT INTO analytics_user_days (
|
|
68
|
+
id, tenant_key, owner_email, org_id, event_date, user_key
|
|
69
|
+
)
|
|
70
|
+
SELECT
|
|
71
|
+
md5(random()::text || clock_timestamp()::text), tenant_key,
|
|
72
|
+
owner_email, org_id, event_date, user_key
|
|
73
|
+
FROM (
|
|
74
|
+
SELECT DISTINCT
|
|
75
|
+
CASE
|
|
76
|
+
WHEN org_id IS NOT NULL AND org_id <> '' THEN 'org:' || org_id
|
|
77
|
+
ELSE 'user:' || owner_email
|
|
78
|
+
END AS tenant_key,
|
|
79
|
+
owner_email,
|
|
80
|
+
org_id,
|
|
81
|
+
COALESCE(NULLIF(event_date, ''), substr(timestamp, 1, 10)) AS event_date,
|
|
82
|
+
user_key
|
|
83
|
+
FROM analytics_events
|
|
84
|
+
WHERE user_key IS NOT NULL AND TRIM(user_key) <> ''
|
|
85
|
+
) AS historical_user_days
|
|
86
|
+
WHERE event_date <> ''
|
|
87
|
+
ON CONFLICT (tenant_key, event_date, user_key) DO NOTHING
|
|
88
|
+
`,
|
|
89
|
+
],
|
|
90
|
+
sqlite: [
|
|
91
|
+
`
|
|
92
|
+
INSERT INTO analytics_event_daily_rollups (
|
|
93
|
+
id, tenant_key, owner_email, org_id, event_date, event_name,
|
|
94
|
+
app, template, event_count
|
|
95
|
+
)
|
|
96
|
+
SELECT
|
|
97
|
+
lower(hex(randomblob(16))), tenant_key, MIN(owner_email), MIN(org_id),
|
|
98
|
+
event_date, event_name, app, template, COUNT(*)
|
|
99
|
+
FROM (
|
|
100
|
+
SELECT
|
|
101
|
+
CASE
|
|
102
|
+
WHEN org_id IS NOT NULL AND org_id <> '' THEN 'org:' || org_id
|
|
103
|
+
ELSE 'user:' || owner_email
|
|
104
|
+
END AS tenant_key,
|
|
105
|
+
owner_email,
|
|
106
|
+
org_id,
|
|
107
|
+
COALESCE(NULLIF(event_date, ''), substr(timestamp, 1, 10)) AS event_date,
|
|
108
|
+
event_name,
|
|
109
|
+
COALESCE(app, '') AS app,
|
|
110
|
+
COALESCE(template, '') AS template
|
|
111
|
+
FROM analytics_events
|
|
112
|
+
) AS historical_events
|
|
113
|
+
WHERE event_date <> ''
|
|
114
|
+
GROUP BY tenant_key, event_date, event_name, app, template
|
|
115
|
+
ON CONFLICT (tenant_key, event_date, event_name, app, template)
|
|
116
|
+
DO UPDATE SET event_count = excluded.event_count
|
|
117
|
+
`,
|
|
118
|
+
`
|
|
119
|
+
INSERT INTO analytics_user_days (
|
|
120
|
+
id, tenant_key, owner_email, org_id, event_date, user_key
|
|
121
|
+
)
|
|
122
|
+
SELECT
|
|
123
|
+
lower(hex(randomblob(16))), tenant_key, owner_email, org_id,
|
|
124
|
+
event_date, user_key
|
|
125
|
+
FROM (
|
|
126
|
+
SELECT DISTINCT
|
|
127
|
+
CASE
|
|
128
|
+
WHEN org_id IS NOT NULL AND org_id <> '' THEN 'org:' || org_id
|
|
129
|
+
ELSE 'user:' || owner_email
|
|
130
|
+
END AS tenant_key,
|
|
131
|
+
owner_email,
|
|
132
|
+
org_id,
|
|
133
|
+
COALESCE(NULLIF(event_date, ''), substr(timestamp, 1, 10)) AS event_date,
|
|
134
|
+
user_key
|
|
135
|
+
FROM analytics_events
|
|
136
|
+
WHERE user_key IS NOT NULL AND TRIM(user_key) <> ''
|
|
137
|
+
) AS historical_user_days
|
|
138
|
+
WHERE event_date <> ''
|
|
139
|
+
ON CONFLICT (tenant_key, event_date, user_key) DO NOTHING
|
|
140
|
+
`,
|
|
141
|
+
],
|
|
142
|
+
} as const;
|
|
143
|
+
|
|
144
|
+
async function runHistoricalAnalyticsRollupBackfill(): Promise<void> {
|
|
145
|
+
const db = getDbExec();
|
|
146
|
+
|
|
147
|
+
if (isPostgres()) {
|
|
148
|
+
if (!db.transaction) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
"Analytics rollup backfill requires a Postgres transaction",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await db.transaction(async (tx) => {
|
|
155
|
+
// Only one serverless instance should perform the expensive scan. A
|
|
156
|
+
// try-lock is intentional: competing cold starts fail fast instead of
|
|
157
|
+
// holding pooled Neon connections while waiting for the winner. The
|
|
158
|
+
// monotonic rollup upsert below keeps a live increment from being
|
|
159
|
+
// overwritten if ingest commits during the historical snapshot.
|
|
160
|
+
const lockResult = await tx.execute({
|
|
161
|
+
sql: "SELECT pg_try_advisory_xact_lock(hashtextextended(?, 0::bigint)) AS acquired",
|
|
162
|
+
args: ["agent-native:analytics-rollup-backfill"],
|
|
163
|
+
});
|
|
164
|
+
const acquired = lockResult.rows[0]?.acquired;
|
|
165
|
+
if (acquired !== true && acquired !== "t") {
|
|
166
|
+
throw new Error(
|
|
167
|
+
"Analytics rollup backfill is already running on another instance",
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
for (const statement of ANALYTICS_ROLLUP_BACKFILL_STATEMENTS.postgres) {
|
|
171
|
+
await tx.execute(statement);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const statements = ANALYTICS_ROLLUP_BACKFILL_STATEMENTS.sqlite;
|
|
178
|
+
if (getDialect() === "d1") {
|
|
179
|
+
if (!db.atomicBatch) {
|
|
180
|
+
throw new Error("D1 analytics rollup backfill requires an atomic batch");
|
|
181
|
+
}
|
|
182
|
+
await db.atomicBatch(statements);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!db.transaction) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
"Analytics rollup backfill requires a database transaction",
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
await db.transaction(async (tx) => {
|
|
192
|
+
for (const statement of statements) await tx.execute(statement);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
31
196
|
// Convention: every new migration below MUST set a unique `name:` slug (see
|
|
32
197
|
// packages/core/src/db/migrations.ts for the full rationale). Version numbers
|
|
33
198
|
// alone are not a safe identity across parallel branches that each extend
|
|
@@ -1367,6 +1532,15 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
1367
1532
|
name: "analytics-query-pressure-daily-key-idx",
|
|
1368
1533
|
sql: `CREATE UNIQUE INDEX IF NOT EXISTS analytics_query_pressure_daily_key_idx ON analytics_query_pressure_daily (tenant_key, event_date, query_class)`,
|
|
1369
1534
|
},
|
|
1535
|
+
// Rebuild the compact analytics rollups from raw history once when the
|
|
1536
|
+
// tables first ship. The run-only migration takes the same write lock as
|
|
1537
|
+
// ingest so a live increment cannot be overwritten by its snapshot.
|
|
1538
|
+
{
|
|
1539
|
+
version: 132,
|
|
1540
|
+
name: "analytics-rollups-historical-backfill",
|
|
1541
|
+
sql: {},
|
|
1542
|
+
run: runHistoricalAnalyticsRollupBackfill,
|
|
1543
|
+
},
|
|
1370
1544
|
],
|
|
1371
1545
|
{ table: "analytics_migrations" },
|
|
1372
1546
|
);
|
|
@@ -112,48 +112,48 @@ export function OutOfOfficeEvent({
|
|
|
112
112
|
}}
|
|
113
113
|
/>
|
|
114
114
|
</div>
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
115
|
+
<EventDetailPopover
|
|
116
|
+
event={event}
|
|
117
|
+
onDelete={onDelete}
|
|
118
|
+
isDraft={isDraft}
|
|
119
|
+
defaultOpen={defaultOpen}
|
|
120
|
+
onTitleSave={onTitleSave}
|
|
121
|
+
onDismissNew={onDismissNew}
|
|
122
|
+
onDraftUpdate={onDraftUpdate}
|
|
123
|
+
onDraftCreate={onDraftCreate}
|
|
124
|
+
onDraftDiscard={onDraftDiscard}
|
|
125
|
+
onOpenChange={onOpenChange}
|
|
125
126
|
>
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
onOpenChange={onOpenChange}
|
|
137
|
-
>
|
|
138
|
-
<button
|
|
139
|
-
onPointerDown={(pointerEvent) =>
|
|
140
|
-
onMovePointerDown?.(pointerEvent, startsOnDay)
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
data-out-of-office-trigger={event.id}
|
|
130
|
+
onPointerDown={(pointerEvent) =>
|
|
131
|
+
onMovePointerDown?.(pointerEvent, startsOnDay)
|
|
132
|
+
}
|
|
133
|
+
onClick={(clickEvent) => {
|
|
134
|
+
if (shouldSuppressClick?.()) {
|
|
135
|
+
clickEvent.preventDefault();
|
|
136
|
+
clickEvent.stopPropagation();
|
|
141
137
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
}}
|
|
139
|
+
className={`pointer-events-auto absolute inset-x-0 block border-0 bg-transparent p-0 text-left outline-none ${
|
|
140
|
+
canManipulate ? "cursor-grab" : ""
|
|
141
|
+
} ${isBeingDragged && isDragging ? "cursor-grabbing" : ""}`}
|
|
142
|
+
aria-label={`${label}: ${title}`}
|
|
143
|
+
style={{ top: `${top}px`, height: `${height}px` }}
|
|
144
|
+
>
|
|
145
|
+
<span
|
|
146
|
+
className={`pointer-events-auto absolute top-1 z-40 flex h-5 max-w-full items-center truncate rounded-sm text-[10px] font-medium text-foreground outline-none transition-[filter,box-shadow] hover:brightness-110 focus-within:ring-1 focus-within:ring-ring focus-within:ring-offset-1 ${
|
|
147
|
+
isBeingDragged && isDragging ? "z-[100] shadow-lg" : ""
|
|
148
|
+
} ${
|
|
149
149
|
compactMarker
|
|
150
150
|
? "w-5 justify-center px-0"
|
|
151
151
|
: "gap-1 px-1.5 text-left"
|
|
152
|
-
} ${canManipulate ? "cursor-grab" : ""} ${
|
|
153
|
-
isBeingDragged && isDragging ? "cursor-grabbing shadow-lg" : ""
|
|
154
152
|
}`}
|
|
155
|
-
aria-
|
|
153
|
+
aria-hidden="true"
|
|
156
154
|
style={{
|
|
155
|
+
right: compactMarker ? `${4 + markerIndex * 24}px` : undefined,
|
|
156
|
+
left: compactMarker ? undefined : `${4 + markerIndex * 12}px`,
|
|
157
157
|
backgroundColor: `color-mix(in srgb, ${color} 20%, hsl(var(--background)))`,
|
|
158
158
|
boxShadow: `0 0 0 1px color-mix(in srgb, ${color} 34%, transparent)`,
|
|
159
159
|
}}
|
|
@@ -164,9 +164,9 @@ export function OutOfOfficeEvent({
|
|
|
164
164
|
style={{ color }}
|
|
165
165
|
/>
|
|
166
166
|
{!compactMarker && <span className="truncate">{title}</span>}
|
|
167
|
-
</
|
|
168
|
-
</
|
|
169
|
-
</
|
|
167
|
+
</span>
|
|
168
|
+
</button>
|
|
169
|
+
</EventDetailPopover>
|
|
170
170
|
{canManipulate && (
|
|
171
171
|
<div
|
|
172
172
|
data-resize-handle="true"
|