@agent-native/core 0.141.3 → 0.141.4
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/analysis-workspace/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/gong-calls.ts +56 -18
- package/corpus/templates/analytics/changelog/2026-08-05-analytics-queries-keep-every-table-tenant-scoped.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-fixed-sign-in-and-dashboard-requests-failing-when-background.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +2 -0
- package/corpus/templates/analytics/server/lib/emails.ts +51 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +8 -12
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +262 -13
- package/corpus/templates/analytics/server/lib/gong.ts +132 -33
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/server/plugins/auth.ts +21 -2
- package/corpus/templates/analytics/server/plugins/db.ts +11 -3
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +17 -7
- package/corpus/templates/calendar/server/lib/booking-emails.ts +154 -70
- package/corpus/templates/calendar/server/lib/emails.ts +167 -0
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +64 -28
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/clips/actions/invite-member.ts +39 -16
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +73 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -25
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-08-05-activity-is-now-the-default-clips-recording-tab-and-ai-setup.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-clips-can-copy-a-linked-email-preview-for-public-recordings.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-preparing-recording-now-uses-a-clean-shadow-free-status-card.md +6 -0
- package/corpus/templates/clips/desktop/src/styles.css +0 -3
- package/corpus/templates/clips/server/lib/emails.ts +256 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +24 -0
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +5 -0
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +4 -1
- package/corpus/templates/clips/shared/email-preview.ts +61 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +57 -26
- package/corpus/templates/content/server/lib/emails.ts +70 -0
- package/corpus/templates/content/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +11 -8
- package/corpus/templates/design/actions/index-design-tokens.ts +13 -6
- package/corpus/templates/design/changelog/2026-08-05-design-token-imports-classify-typography-dimensions-correctly.md +6 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/changelog/2026-08-05-transactional-email-catalogs-and-delivery-activity-are-available-again.md +6 -0
- package/corpus/templates/factory/AGENTS.md +18 -17
- package/corpus/templates/factory/actions/get-factory-automation-health.ts +50 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +8 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +26 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +27 -0
- package/corpus/templates/factory/app/routes/factory.tsx +376 -206
- package/corpus/templates/factory/changelog/2026-08-05-factory-now-keeps-production-automations-running-shows-sched.md +6 -0
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +10 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +141 -2
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +41 -21
- package/corpus/templates/forms/changelog/2026-08-05-form-links-can-now-be-cmd-clicked-to-open-in-a-new-tab.md +6 -0
- package/corpus/templates/forms/server/lib/emails.ts +52 -0
- package/corpus/templates/forms/server/lib/response-email.ts +2 -0
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/plan/actions/request-plan-access.ts +33 -12
- package/corpus/templates/plan/server/lib/comment-notifications.ts +53 -27
- package/corpus/templates/plan/server/lib/emails.ts +67 -0
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/slides/actions/import-from-url.ts +1 -0
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +492 -381
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +8 -7
- package/corpus/templates/slides/app/components/layout/Layout.tsx +27 -23
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +313 -0
- package/corpus/templates/slides/app/components/ui/command.tsx +1 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -2
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +305 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -12
- package/corpus/templates/slides/app/pages/Index.tsx +127 -137
- package/corpus/templates/slides/app/root.tsx +8 -0
- package/corpus/templates/slides/changelog/2026-08-05-deck-browsing-keeps-all-and-mine-in-the-header-with-a-tighte.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-new-deck-creation-now-flows-through-a-clean-full-screen-refe.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-website-imports-now-include-linked-stylesheets-for-more-accu.md +6 -0
- package/corpus/templates/slides/server/lib/comment-notifications.ts +44 -21
- package/corpus/templates/slides/server/lib/emails.ts +40 -0
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +5 -0
- package/dist/brand-kit/index.d.ts +1 -0
- package/dist/brand-kit/index.js +1 -0
- package/dist/brand-kit/tokens.d.ts +91 -0
- package/dist/brand-kit/tokens.js +279 -0
- package/dist/brand-kit/types.d.ts +25 -0
- package/dist/client/AssistantChat.js +7 -7
- package/dist/client/onboarding/FirstRunOnboarding.js +26 -2
- package/dist/client/onboarding/first-run-registry.d.ts +25 -0
- package/dist/client/onboarding/first-run-registry.js +18 -0
- package/dist/client/onboarding/index.d.ts +2 -0
- package/dist/client/onboarding/index.js +1 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.js +92 -3
- package/dist/deploy/workspace-deploy.js +86 -3
- package/dist/email-catalog/actions/list-email-activity.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-activity.js +19 -0
- package/dist/email-catalog/actions/list-email-engagement.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-engagement.js +15 -0
- package/dist/email-catalog/actions/list-email-log.d.ts +7 -0
- package/dist/email-catalog/actions/list-email-log.js +23 -0
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +22 -0
- package/dist/email-catalog/actions/list-transactional-emails.js +67 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +9 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.js +25 -0
- package/dist/email-catalog/authorize.d.ts +1 -0
- package/dist/email-catalog/authorize.js +9 -0
- package/dist/email-catalog/log.d.ts +60 -0
- package/dist/email-catalog/log.js +142 -0
- package/dist/email-catalog/provider-metrics.d.ts +34 -0
- package/dist/email-catalog/provider-metrics.js +182 -0
- package/dist/email-catalog/registry.d.ts +78 -0
- package/dist/email-catalog/registry.js +70 -0
- package/dist/email-catalog/schema.d.ts +231 -0
- package/dist/email-catalog/schema.js +53 -0
- package/dist/email-catalog/system-emails.d.ts +13 -0
- package/dist/email-catalog/system-emails.js +67 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.js +2 -0
- package/dist/jobs/run-history.js +48 -1
- package/dist/jobs/scheduler-dispatch.d.ts +7 -0
- package/dist/jobs/scheduler-dispatch.js +7 -0
- package/dist/jobs/scheduler-health.d.ts +22 -0
- package/dist/jobs/scheduler-health.js +172 -0
- package/dist/jobs/scheduler.js +67 -2
- package/dist/localization/default-messages.d.ts +45 -0
- package/dist/localization/default-messages.js +45 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/handlers.js +9 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +22 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +7 -1
- package/dist/server/agent-chat/recurring-jobs-runtime.js +12 -0
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.js +39 -1
- package/dist/server/better-auth-instance.js +17 -2
- package/dist/server/design-token-utils.d.ts +6 -1
- package/dist/server/design-token-utils.js +252 -59
- package/dist/server/email.d.ts +11 -0
- package/dist/server/email.js +54 -3
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/vite/client.js +20 -14
- package/package.json +2 -1
package/corpus/README.md
CHANGED
|
@@ -59,7 +59,7 @@ operation. The child must read that staged input before making provider calls.
|
|
|
59
59
|
not re-page the provider endpoint to recreate the same corpus.
|
|
60
60
|
- If the named staged input is missing or unreadable, stop with an explicit
|
|
61
61
|
missing-input error. Do not silently fall back to a full provider sweep.
|
|
62
|
-
- For
|
|
62
|
+
- For 500 or more Gong records, do not use `gong-calls(exhaustive=true)`, a
|
|
63
63
|
per-call transcript loop, or a single `provider-api-request` `fetchAllPages`
|
|
64
64
|
call as the analysis itself. If the term is a configured keyword tracker,
|
|
65
65
|
stage `/calls/extensive` tracker results and reduce them with a Data Program
|
|
@@ -203,12 +203,20 @@ function transcriptRowsByCallId(payload: unknown): Map<string, unknown> {
|
|
|
203
203
|
return rows;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
async function fetchTranscriptBatch(
|
|
206
|
+
async function fetchTranscriptBatch(
|
|
207
|
+
calls: GongCall[],
|
|
208
|
+
signal?: AbortSignal,
|
|
209
|
+
): Promise<{
|
|
207
210
|
payloads: Map<string, unknown>;
|
|
208
211
|
errors: TranscriptSearchError[];
|
|
209
212
|
}> {
|
|
210
213
|
try {
|
|
211
|
-
const payload =
|
|
214
|
+
const payload = signal
|
|
215
|
+
? await getCallTranscripts(
|
|
216
|
+
calls.map((call) => call.id),
|
|
217
|
+
{ signal },
|
|
218
|
+
)
|
|
219
|
+
: await getCallTranscripts(calls.map((call) => call.id));
|
|
212
220
|
const payloads = transcriptRowsByCallId(payload);
|
|
213
221
|
const errors: TranscriptSearchError[] = [];
|
|
214
222
|
for (const call of calls) {
|
|
@@ -223,6 +231,7 @@ async function fetchTranscriptBatch(calls: GongCall[]): Promise<{
|
|
|
223
231
|
}
|
|
224
232
|
return { payloads, errors };
|
|
225
233
|
} catch (err) {
|
|
234
|
+
if (signal?.aborted) throw err;
|
|
226
235
|
return {
|
|
227
236
|
payloads: new Map(),
|
|
228
237
|
errors: calls.map((call) => ({
|
|
@@ -235,7 +244,10 @@ async function fetchTranscriptBatch(calls: GongCall[]): Promise<{
|
|
|
235
244
|
}
|
|
236
245
|
}
|
|
237
246
|
|
|
238
|
-
async function fetchTranscriptBatches(
|
|
247
|
+
async function fetchTranscriptBatches(
|
|
248
|
+
calls: GongCall[],
|
|
249
|
+
signal?: AbortSignal,
|
|
250
|
+
): Promise<
|
|
239
251
|
Array<{
|
|
240
252
|
calls: GongCall[];
|
|
241
253
|
result: Awaited<ReturnType<typeof fetchTranscriptBatch>>;
|
|
@@ -257,7 +269,7 @@ async function fetchTranscriptBatches(calls: GongCall[]): Promise<
|
|
|
257
269
|
.slice(index, index + TRANSCRIPT_BATCH_CONCURRENCY)
|
|
258
270
|
.map(async (batch) => ({
|
|
259
271
|
calls: batch,
|
|
260
|
-
result: await fetchTranscriptBatch(batch),
|
|
272
|
+
result: await fetchTranscriptBatch(batch, signal),
|
|
261
273
|
})),
|
|
262
274
|
)),
|
|
263
275
|
);
|
|
@@ -270,6 +282,7 @@ async function searchTranscriptEvidence(
|
|
|
270
282
|
query: string,
|
|
271
283
|
scanLimit: number,
|
|
272
284
|
maxChars: number,
|
|
285
|
+
signal?: AbortSignal,
|
|
273
286
|
): Promise<{
|
|
274
287
|
inspectedCalls: number;
|
|
275
288
|
matches: TranscriptSearchMatch[];
|
|
@@ -284,7 +297,7 @@ async function searchTranscriptEvidence(
|
|
|
284
297
|
for (const {
|
|
285
298
|
calls: batch,
|
|
286
299
|
result: batchResult,
|
|
287
|
-
} of await fetchTranscriptBatches(callsToScan)) {
|
|
300
|
+
} of await fetchTranscriptBatches(callsToScan, signal)) {
|
|
288
301
|
errors.push(...batchResult.errors);
|
|
289
302
|
|
|
290
303
|
for (const call of batch) {
|
|
@@ -420,13 +433,14 @@ async function loadTranscriptEvidence(
|
|
|
420
433
|
calls: GongCall[],
|
|
421
434
|
limit: number,
|
|
422
435
|
maxChars: number,
|
|
436
|
+
signal?: AbortSignal,
|
|
423
437
|
): Promise<TranscriptEvidence[]> {
|
|
424
438
|
const evidence: TranscriptEvidence[] = [];
|
|
425
439
|
const callsToLoad = calls.slice(0, limit);
|
|
426
440
|
for (const {
|
|
427
441
|
calls: batch,
|
|
428
442
|
result: batchResult,
|
|
429
|
-
} of await fetchTranscriptBatches(callsToLoad)) {
|
|
443
|
+
} of await fetchTranscriptBatches(callsToLoad, signal)) {
|
|
430
444
|
const errorByCallId = new Map(
|
|
431
445
|
batchResult.errors.map((error) => [error.callId, error.error]),
|
|
432
446
|
);
|
|
@@ -486,7 +500,7 @@ export default defineAction({
|
|
|
486
500
|
// model round trips while still staying well below the model context limit.
|
|
487
501
|
maxResultChars: 100_000,
|
|
488
502
|
description:
|
|
489
|
-
"Query bounded Gong sales-call evidence. Pass --users for the user list, --transcript for one transcript, or --company for a bounded search by company/domain/person/email. Without --company, the action lists calls in the date window; set exhaustive=true for
|
|
503
|
+
"Query bounded Gong sales-call evidence. Pass --users for the user list, --transcript for one transcript, or --company for a bounded search by company/domain/person/email. Without --company, the action lists calls in the date window; set exhaustive=true only for a small bounded cohort of fewer than 500 records, not a broad org-wide export. For account-level transcript mention questions, transcriptQuery performs a case-insensitive local scan after batched transcript retrieval and returns coverage counts plus snippets; it is not Gong's server-side keyword search. Use includeTranscripts=true for bounded qualitative context. For broad keyword, tracker, cross-account, or absence-sensitive work, use provider-api-catalog/provider-api-docs, stage the raw Gong API response, then use query-staged-dataset or a Data Program; use provider-corpus-job only when raw transcript bodies are required.",
|
|
490
504
|
schema: z.object({
|
|
491
505
|
users: cliBoolean.optional().describe("Set to true to list Gong users"),
|
|
492
506
|
transcript: z.string().optional().describe("Call ID to get transcript"),
|
|
@@ -553,7 +567,7 @@ export default defineAction({
|
|
|
553
567
|
exhaustive: cliBoolean
|
|
554
568
|
.optional()
|
|
555
569
|
.describe(
|
|
556
|
-
"Return EVERY matching call in the window instead of stopping at `limit`; without company, return every call page for a small bounded cohort of
|
|
570
|
+
"Return EVERY matching call in the window instead of stopping at `limit`; without company, return every call page only for a small bounded cohort of fewer than 500 records. Cohorts at or above 500 fail closed with guidance to use provider-api-request staging and query-staged-dataset or a Data Program. By default this is metadata-only. With includeTranscripts=true, transcripts are fetched in batches and explicit coverage is returned. For broad or absence-sensitive work, prefer the raw Gong API tracker/staging path plus query-staged-dataset or a Data Program; use provider-corpus-job for durable raw-transcript scans. Always bound this with after/before or a small days window.",
|
|
557
571
|
),
|
|
558
572
|
after: z
|
|
559
573
|
.string()
|
|
@@ -569,12 +583,15 @@ export default defineAction({
|
|
|
569
583
|
),
|
|
570
584
|
}),
|
|
571
585
|
http: { method: "GET" },
|
|
572
|
-
run: async (args) => {
|
|
586
|
+
run: async (args, ctx) => {
|
|
587
|
+
const requestOptions = ctx?.signal ? { signal: ctx.signal } : undefined;
|
|
573
588
|
if (args.users) {
|
|
574
589
|
const users = await getUsers();
|
|
575
590
|
return { users, total: users.length };
|
|
576
591
|
} else if (args.transcript) {
|
|
577
|
-
const transcript =
|
|
592
|
+
const transcript = requestOptions
|
|
593
|
+
? await getCallTranscript(args.transcript, requestOptions)
|
|
594
|
+
: await getCallTranscript(args.transcript);
|
|
578
595
|
const transcriptText = extractTranscriptText(
|
|
579
596
|
transcript,
|
|
580
597
|
args.transcriptMaxChars,
|
|
@@ -600,6 +617,7 @@ export default defineAction({
|
|
|
600
617
|
exhaustive,
|
|
601
618
|
...(fromDateTime ? { fromDateTime } : {}),
|
|
602
619
|
...(toDateTime ? { toDateTime } : {}),
|
|
620
|
+
...(requestOptions ?? {}),
|
|
603
621
|
});
|
|
604
622
|
const shouldLoadTranscripts = Boolean(args.includeTranscripts);
|
|
605
623
|
const transcriptLimit = normalizeBoundedInt(
|
|
@@ -644,6 +662,7 @@ export default defineAction({
|
|
|
644
662
|
transcriptQuery,
|
|
645
663
|
transcriptScanLimit,
|
|
646
664
|
transcriptSearchMaxChars,
|
|
665
|
+
requestOptions?.signal,
|
|
647
666
|
)
|
|
648
667
|
: undefined;
|
|
649
668
|
const transcripts = shouldLoadTranscripts
|
|
@@ -651,6 +670,7 @@ export default defineAction({
|
|
|
651
670
|
result.calls,
|
|
652
671
|
transcriptLimit,
|
|
653
672
|
boundedTranscriptExcerptMaxChars,
|
|
673
|
+
requestOptions?.signal,
|
|
654
674
|
)
|
|
655
675
|
: undefined;
|
|
656
676
|
const transcriptErrors =
|
|
@@ -726,14 +746,30 @@ export default defineAction({
|
|
|
726
746
|
new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
727
747
|
const toDateTime = normalizeGongDate(args.before, "end");
|
|
728
748
|
const result = exhaustive
|
|
729
|
-
?
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
749
|
+
? requestOptions
|
|
750
|
+
? await getAllCalls(
|
|
751
|
+
{
|
|
752
|
+
fromDateTime,
|
|
753
|
+
...(toDateTime ? { toDateTime } : {}),
|
|
754
|
+
},
|
|
755
|
+
requestOptions,
|
|
756
|
+
)
|
|
757
|
+
: await getAllCalls({
|
|
758
|
+
fromDateTime,
|
|
759
|
+
...(toDateTime ? { toDateTime } : {}),
|
|
760
|
+
})
|
|
761
|
+
: requestOptions
|
|
762
|
+
? await getCalls(
|
|
763
|
+
{
|
|
764
|
+
fromDateTime,
|
|
765
|
+
...(toDateTime ? { toDateTime } : {}),
|
|
766
|
+
},
|
|
767
|
+
requestOptions,
|
|
768
|
+
)
|
|
769
|
+
: await getCalls({
|
|
770
|
+
fromDateTime,
|
|
771
|
+
...(toDateTime ? { toDateTime } : {}),
|
|
772
|
+
});
|
|
737
773
|
const limited = limitGongCalls(result.calls, limit);
|
|
738
774
|
const returnedCalls = exhaustive ? result.calls : limited.calls;
|
|
739
775
|
const truncated =
|
|
@@ -776,6 +812,7 @@ export default defineAction({
|
|
|
776
812
|
transcriptQuery,
|
|
777
813
|
transcriptScanLimit,
|
|
778
814
|
transcriptSearchMaxChars,
|
|
815
|
+
requestOptions?.signal,
|
|
779
816
|
)
|
|
780
817
|
: undefined;
|
|
781
818
|
const transcripts = shouldLoadTranscripts
|
|
@@ -783,6 +820,7 @@ export default defineAction({
|
|
|
783
820
|
returnedCalls,
|
|
784
821
|
transcriptLimit,
|
|
785
822
|
boundedTranscriptExcerptMaxChars,
|
|
823
|
+
requestOptions?.signal,
|
|
786
824
|
)
|
|
787
825
|
: undefined;
|
|
788
826
|
|
|
@@ -299,6 +299,7 @@ export const analyticsQueryPressureDaily = table(
|
|
|
299
299
|
tenantKey: text("tenant_key").notNull(),
|
|
300
300
|
ownerEmail: text("owner_email").notNull(),
|
|
301
301
|
orgId: text("org_id"),
|
|
302
|
+
/** UTC day bucket; lastSeenAt carries the timestamp for trailing-window reads. */
|
|
302
303
|
eventDate: text("event_date").notNull(),
|
|
303
304
|
queryClass: text("query_class").notNull(),
|
|
304
305
|
slowQueryCount: integer("slow_query_count").notNull().default(0),
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type DashboardReportCaptureOutcome,
|
|
19
19
|
type DashboardReportSubscription,
|
|
20
20
|
} from "./dashboard-report-subscriptions";
|
|
21
|
+
import { ANALYTICS_DASHBOARD_REPORT_EMAIL_ID } from "./emails";
|
|
21
22
|
|
|
22
23
|
const DATE_FILTER_TYPES: ReadonlySet<FilterType> = new Set([
|
|
23
24
|
"date",
|
|
@@ -326,6 +327,7 @@ export async function sendDashboardReportSubscription(
|
|
|
326
327
|
? { attachments: rendered.attachments }
|
|
327
328
|
: {}),
|
|
328
329
|
timeoutMs: emailTimeoutMs,
|
|
330
|
+
templateId: ANALYTICS_DASHBOARD_REPORT_EMAIL_ID,
|
|
329
331
|
});
|
|
330
332
|
}
|
|
331
333
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog entries for the transactional emails Analytics sends.
|
|
3
|
+
*
|
|
4
|
+
* Registered from `server/plugins/transactional-emails.ts` so Dispatch can list
|
|
5
|
+
* and preview them without the app having sent anything yet.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineTransactionalEmail } from "@agent-native/core/email-catalog";
|
|
9
|
+
import { emailStrong, renderEmail } from "@agent-native/core/server";
|
|
10
|
+
|
|
11
|
+
export const ANALYTICS_DASHBOARD_REPORT_EMAIL_ID = "analytics.dashboard-report";
|
|
12
|
+
|
|
13
|
+
let registered = false;
|
|
14
|
+
|
|
15
|
+
export function registerAnalyticsEmails(): void {
|
|
16
|
+
if (registered) return;
|
|
17
|
+
registered = true;
|
|
18
|
+
|
|
19
|
+
defineTransactionalEmail({
|
|
20
|
+
id: ANALYTICS_DASHBOARD_REPORT_EMAIL_ID,
|
|
21
|
+
name: "Scheduled dashboard report",
|
|
22
|
+
trigger:
|
|
23
|
+
"A dashboard report subscription comes due and at least one panel produced usable data. A run where every queried panel failed throws instead of mailing, and a degraded run is skipped when the subscription asks for complete reports only.",
|
|
24
|
+
recipientLabel: "Subscription recipients",
|
|
25
|
+
recipient:
|
|
26
|
+
"The normalized recipient list stored on the subscription. One email per address, all carrying the same rendered snapshot.",
|
|
27
|
+
senderLabel: "Default sender",
|
|
28
|
+
sender:
|
|
29
|
+
"The configured default sender. This call site sets no `from`, `fromName`, `replyTo`, or `appSender`.",
|
|
30
|
+
// The real renderer runs every panel query and rasterizes charts, so it
|
|
31
|
+
// cannot back a preview that must stay offline. This shows the frame a
|
|
32
|
+
// recipient sees; the panel body is whatever that run produced.
|
|
33
|
+
preview: () => ({
|
|
34
|
+
subject: "Daily dashboard: Growth overview — 3/4/2025",
|
|
35
|
+
...renderEmail({
|
|
36
|
+
preheader: "Your daily Growth overview report is ready.",
|
|
37
|
+
heading: "Daily dashboard: Growth overview",
|
|
38
|
+
paragraphs: [
|
|
39
|
+
`This report renders each panel of ${emailStrong("Growth overview")} as of the run time, with charts drawn server-side and attached inline.`,
|
|
40
|
+
"Panels that could not be queried are called out in place rather than dropped, so a partial report never reads as a complete one.",
|
|
41
|
+
],
|
|
42
|
+
cta: {
|
|
43
|
+
label: "Open dashboard",
|
|
44
|
+
url: "https://example.com/dashboards/dash_sample",
|
|
45
|
+
},
|
|
46
|
+
footer:
|
|
47
|
+
"You received this because you are on the recipient list for this dashboard report.",
|
|
48
|
+
}),
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -73,10 +73,6 @@ function nowIso(): string {
|
|
|
73
73
|
return new Date().toISOString();
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function todayIsoDate(): string {
|
|
77
|
-
return nowIso().slice(0, 10);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
76
|
function tenantKey(scope: AnalyticsScope): string {
|
|
81
77
|
return scope.orgId ? `org:${scope.orgId}` : `user:${scope.userEmail}`;
|
|
82
78
|
}
|
|
@@ -152,7 +148,8 @@ export async function recordFirstPartyAnalyticsQueryPressure(
|
|
|
152
148
|
if (!shouldRecordPressure(event)) return;
|
|
153
149
|
|
|
154
150
|
const durationMs = normalizedDurationMs(event.durationMs);
|
|
155
|
-
const
|
|
151
|
+
const eventTimestamp = nowIso();
|
|
152
|
+
const eventDate = eventTimestamp.slice(0, 10);
|
|
156
153
|
const key = tenantKey(scope);
|
|
157
154
|
const table = schema.analyticsQueryPressureDaily;
|
|
158
155
|
const db = getDb() as any;
|
|
@@ -174,7 +171,7 @@ export async function recordFirstPartyAnalyticsQueryPressure(
|
|
|
174
171
|
errorCount: event.outcome === "error" ? 1 : 0,
|
|
175
172
|
totalDurationMs: durationMs,
|
|
176
173
|
maxDurationMs: durationMs,
|
|
177
|
-
lastSeenAt:
|
|
174
|
+
lastSeenAt: eventTimestamp,
|
|
178
175
|
})
|
|
179
176
|
.onConflictDoUpdate({
|
|
180
177
|
target: [table.tenantKey, table.eventDate, table.queryClass],
|
|
@@ -184,7 +181,7 @@ export async function recordFirstPartyAnalyticsQueryPressure(
|
|
|
184
181
|
errorCount: sql`${table.errorCount} + ${event.outcome === "error" ? 1 : 0}`,
|
|
185
182
|
totalDurationMs: sql`${table.totalDurationMs} + ${durationMs}`,
|
|
186
183
|
maxDurationMs: sql`CASE WHEN ${table.maxDurationMs} > ${durationMs} THEN ${table.maxDurationMs} ELSE ${durationMs} END`,
|
|
187
|
-
lastSeenAt:
|
|
184
|
+
lastSeenAt: eventTimestamp,
|
|
188
185
|
},
|
|
189
186
|
});
|
|
190
187
|
}
|
|
@@ -250,9 +247,8 @@ export async function getFirstPartyAnalyticsHealth(
|
|
|
250
247
|
): Promise<FirstPartyAnalyticsHealth> {
|
|
251
248
|
const now = new Date();
|
|
252
249
|
const today = now.toISOString().slice(0, 10);
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
.slice(0, 10);
|
|
250
|
+
const nowTimestamp = now.toISOString();
|
|
251
|
+
const since24h = new Date(now.getTime() - 86_400_000).toISOString();
|
|
256
252
|
const keys = scope.orgId
|
|
257
253
|
? [`org:${scope.orgId}`, `user:${scope.userEmail}`]
|
|
258
254
|
: [`user:${scope.userEmail}`];
|
|
@@ -284,8 +280,8 @@ export async function getFirstPartyAnalyticsHealth(
|
|
|
284
280
|
.where(
|
|
285
281
|
and(
|
|
286
282
|
inArray(pressure.tenantKey, keys),
|
|
287
|
-
gte(pressure.
|
|
288
|
-
lte(pressure.
|
|
283
|
+
gte(pressure.lastSeenAt, since24h),
|
|
284
|
+
lte(pressure.lastSeenAt, nowTimestamp),
|
|
289
285
|
),
|
|
290
286
|
),
|
|
291
287
|
bigQueryStatus(scope),
|
|
@@ -23,6 +23,15 @@ export interface FirstPartyAnalyticsRollupResult {
|
|
|
23
23
|
userDayCount: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Shared with the historical backfill so live ingest and migration work hold
|
|
28
|
+
* the same transaction-scoped Postgres lock while they update rollups.
|
|
29
|
+
*/
|
|
30
|
+
export const FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY =
|
|
31
|
+
"agent-native:analytics-rollup-backfill";
|
|
32
|
+
export const FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL =
|
|
33
|
+
"SELECT pg_advisory_xact_lock(hashtextextended(?, 0::bigint))";
|
|
34
|
+
|
|
26
35
|
interface DailyRollupRow {
|
|
27
36
|
id: string;
|
|
28
37
|
tenantKey: string;
|