@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDbExec } from "@agent-native/core/db";
|
|
1
|
+
import { getDbExec, isPostgres } from "@agent-native/core/db";
|
|
2
2
|
import { and, eq, isNull, or } from "drizzle-orm";
|
|
3
3
|
|
|
4
4
|
import { FIRST_PARTY_ANALYTICS_QUERY_TIMEOUT_MS } from "../../shared/dashboard-report-timeouts.js";
|
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
queryOutcomeFromError,
|
|
18
18
|
recordFirstPartyAnalyticsQueryPressure,
|
|
19
19
|
} from "./first-party-analytics-health.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY,
|
|
22
|
+
FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL,
|
|
23
|
+
upsertFirstPartyAnalyticsRollups,
|
|
24
|
+
} from "./first-party-analytics-rollups.js";
|
|
21
25
|
|
|
22
26
|
export interface AnalyticsScope {
|
|
23
27
|
userEmail: string;
|
|
@@ -506,6 +510,16 @@ export async function recordAnalyticsEvents(
|
|
|
506
510
|
|
|
507
511
|
if (rows.length) {
|
|
508
512
|
await db.transaction(async (tx: any) => {
|
|
513
|
+
if (isPostgres()) {
|
|
514
|
+
// The historical backfill takes this lock before its raw-event
|
|
515
|
+
// snapshot. Holding it through ingest's rollup increment prevents the
|
|
516
|
+
// backfill's monotonic upsert from losing a concurrently committed
|
|
517
|
+
// event.
|
|
518
|
+
await tx.execute({
|
|
519
|
+
sql: FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL,
|
|
520
|
+
args: [FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY],
|
|
521
|
+
});
|
|
522
|
+
}
|
|
509
523
|
await tx.insert(schema.analyticsEvents).values(rows);
|
|
510
524
|
await tx
|
|
511
525
|
.update(schema.analyticsPublicKeys)
|
|
@@ -579,6 +593,233 @@ function stripSqlLiterals(sql: string): string {
|
|
|
579
593
|
return out;
|
|
580
594
|
}
|
|
581
595
|
|
|
596
|
+
interface AnalyticsSqlToken {
|
|
597
|
+
value: string;
|
|
598
|
+
quoted: boolean;
|
|
599
|
+
depth: number;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
interface AnalyticsSqlSource {
|
|
603
|
+
ref: string;
|
|
604
|
+
quoted: boolean;
|
|
605
|
+
commaSeparated: boolean;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const SQL_SOURCE_CLAUSE_ENDS = new Set([
|
|
609
|
+
"where",
|
|
610
|
+
"group",
|
|
611
|
+
"order",
|
|
612
|
+
"limit",
|
|
613
|
+
"having",
|
|
614
|
+
"union",
|
|
615
|
+
"except",
|
|
616
|
+
"intersect",
|
|
617
|
+
"window",
|
|
618
|
+
"qualify",
|
|
619
|
+
"returning",
|
|
620
|
+
]);
|
|
621
|
+
|
|
622
|
+
function tokenizeAnalyticsSql(sql: string): AnalyticsSqlToken[] {
|
|
623
|
+
const tokens: AnalyticsSqlToken[] = [];
|
|
624
|
+
let depth = 0;
|
|
625
|
+
|
|
626
|
+
for (let i = 0; i < sql.length; ) {
|
|
627
|
+
const ch = sql[i];
|
|
628
|
+
const next = sql[i + 1];
|
|
629
|
+
|
|
630
|
+
if (ch === "-" && next === "-") {
|
|
631
|
+
i += 2;
|
|
632
|
+
while (i < sql.length && sql[i] !== "\n") i++;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (ch === "/" && next === "*") {
|
|
636
|
+
i += 2;
|
|
637
|
+
while (i < sql.length && !(sql[i] === "*" && sql[i + 1] === "/")) {
|
|
638
|
+
i++;
|
|
639
|
+
}
|
|
640
|
+
i += 2;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (ch === "'") {
|
|
644
|
+
i++;
|
|
645
|
+
while (i < sql.length) {
|
|
646
|
+
if (sql[i] === "'" && sql[i + 1] === "'") {
|
|
647
|
+
i += 2;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (sql[i] === "'") {
|
|
651
|
+
i++;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
i++;
|
|
655
|
+
}
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (ch === '"' || ch === "`" || ch === "[") {
|
|
659
|
+
const closing = ch === "[" ? "]" : ch;
|
|
660
|
+
let value = "";
|
|
661
|
+
i++;
|
|
662
|
+
while (i < sql.length) {
|
|
663
|
+
if (sql[i] === closing && sql[i + 1] === closing) {
|
|
664
|
+
value += closing;
|
|
665
|
+
i += 2;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (sql[i] === closing) {
|
|
669
|
+
i++;
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
value += sql[i++];
|
|
673
|
+
}
|
|
674
|
+
tokens.push({ value, quoted: true, depth });
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
678
|
+
const start = i;
|
|
679
|
+
i++;
|
|
680
|
+
while (i < sql.length && /[A-Za-z0-9_$]/.test(sql[i])) i++;
|
|
681
|
+
tokens.push({ value: sql.slice(start, i), quoted: false, depth });
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
if (ch === "(") {
|
|
685
|
+
tokens.push({ value: ch, quoted: false, depth });
|
|
686
|
+
depth++;
|
|
687
|
+
i++;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (ch === ")") {
|
|
691
|
+
depth = Math.max(0, depth - 1);
|
|
692
|
+
tokens.push({ value: ch, quoted: false, depth });
|
|
693
|
+
i++;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (ch === "." || ch === ",") {
|
|
697
|
+
tokens.push({ value: ch, quoted: false, depth });
|
|
698
|
+
}
|
|
699
|
+
i++;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return tokens;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function isAnalyticsSqlKeyword(
|
|
706
|
+
token: AnalyticsSqlToken | undefined,
|
|
707
|
+
keyword: string,
|
|
708
|
+
): boolean {
|
|
709
|
+
return Boolean(
|
|
710
|
+
token && !token.quoted && token.value.toLowerCase() === keyword,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function readAnalyticsSqlSource(
|
|
715
|
+
tokens: AnalyticsSqlToken[],
|
|
716
|
+
start: number,
|
|
717
|
+
): { source: Omit<AnalyticsSqlSource, "commaSeparated"> | null; next: number } {
|
|
718
|
+
let index = start;
|
|
719
|
+
while (
|
|
720
|
+
isAnalyticsSqlKeyword(tokens[index], "only") ||
|
|
721
|
+
isAnalyticsSqlKeyword(tokens[index], "lateral")
|
|
722
|
+
) {
|
|
723
|
+
index++;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const first = tokens[index];
|
|
727
|
+
if (!first) return { source: null, next: index };
|
|
728
|
+
if (first.value === "(") {
|
|
729
|
+
const groupDepth = first.depth;
|
|
730
|
+
index++;
|
|
731
|
+
while (
|
|
732
|
+
index < tokens.length &&
|
|
733
|
+
!(tokens[index].value === ")" && tokens[index].depth === groupDepth)
|
|
734
|
+
) {
|
|
735
|
+
index++;
|
|
736
|
+
}
|
|
737
|
+
return { source: null, next: Math.min(index + 1, tokens.length) };
|
|
738
|
+
}
|
|
739
|
+
if (!first.quoted && !/^[A-Za-z_][A-Za-z0-9_$]*$/.test(first.value)) {
|
|
740
|
+
return { source: null, next: index + 1 };
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
let ref = first.value;
|
|
744
|
+
let quoted = first.quoted;
|
|
745
|
+
if (
|
|
746
|
+
tokens[index + 1]?.value === "." &&
|
|
747
|
+
tokens[index + 2] &&
|
|
748
|
+
(tokens[index + 2].quoted ||
|
|
749
|
+
/^[A-Za-z_][A-Za-z0-9_$]*$/.test(tokens[index + 2].value))
|
|
750
|
+
) {
|
|
751
|
+
ref += `.${tokens[index + 2].value}`;
|
|
752
|
+
quoted ||= tokens[index + 2].quoted;
|
|
753
|
+
index += 2;
|
|
754
|
+
}
|
|
755
|
+
return { source: { ref, quoted }, next: index + 1 };
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function collectAnalyticsSqlSources(sql: string): {
|
|
759
|
+
cteNames: Set<string>;
|
|
760
|
+
sources: AnalyticsSqlSource[];
|
|
761
|
+
} {
|
|
762
|
+
const tokens = tokenizeAnalyticsSql(sql);
|
|
763
|
+
const cteNames = new Set<string>();
|
|
764
|
+
for (let i = 0; i + 2 < tokens.length; i++) {
|
|
765
|
+
if (
|
|
766
|
+
tokens[i].value &&
|
|
767
|
+
isAnalyticsSqlKeyword(tokens[i + 1], "as") &&
|
|
768
|
+
tokens[i + 2].value === "("
|
|
769
|
+
) {
|
|
770
|
+
cteNames.add(tokens[i].value.toLowerCase());
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const sources: AnalyticsSqlSource[] = [];
|
|
775
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
776
|
+
if (!isAnalyticsSqlKeyword(tokens[i], "from")) continue;
|
|
777
|
+
const fromDepth = tokens[i].depth;
|
|
778
|
+
let expectSource = true;
|
|
779
|
+
let inJoinCondition = false;
|
|
780
|
+
let sourceAfterComma = false;
|
|
781
|
+
|
|
782
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
783
|
+
const token = tokens[j];
|
|
784
|
+
if (token.depth < fromDepth) break;
|
|
785
|
+
if (token.depth > fromDepth) continue;
|
|
786
|
+
|
|
787
|
+
const word = token.quoted ? "" : token.value.toLowerCase();
|
|
788
|
+
if (SQL_SOURCE_CLAUSE_ENDS.has(word)) break;
|
|
789
|
+
if (word === "join") {
|
|
790
|
+
expectSource = true;
|
|
791
|
+
inJoinCondition = false;
|
|
792
|
+
sourceAfterComma = false;
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
if (word === "on" || word === "using") {
|
|
796
|
+
expectSource = false;
|
|
797
|
+
inJoinCondition = true;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (token.value === "," && !inJoinCondition) {
|
|
801
|
+
expectSource = true;
|
|
802
|
+
sourceAfterComma = true;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (!expectSource) continue;
|
|
806
|
+
|
|
807
|
+
const parsed = readAnalyticsSqlSource(tokens, j);
|
|
808
|
+
if (parsed.source) {
|
|
809
|
+
sources.push({
|
|
810
|
+
...parsed.source,
|
|
811
|
+
commaSeparated: sourceAfterComma,
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
sourceAfterComma = false;
|
|
815
|
+
expectSource = false;
|
|
816
|
+
j = Math.max(j, parsed.next - 1);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return { cteNames, sources };
|
|
821
|
+
}
|
|
822
|
+
|
|
582
823
|
export function validateFirstPartyAnalyticsSql(sql: string): void {
|
|
583
824
|
const stripped = stripSqlLiterals(sql).trim();
|
|
584
825
|
const lowered = stripped.toLowerCase();
|
|
@@ -603,30 +844,38 @@ export function validateFirstPartyAnalyticsSql(sql: string): void {
|
|
|
603
844
|
if (/\$\d+\b/.test(stripped)) {
|
|
604
845
|
throw new Error("Bind placeholders are not supported in dashboard SQL");
|
|
605
846
|
}
|
|
847
|
+
if (/\bonly\b/i.test(stripped)) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
"ONLY-qualified table sources are not supported in first-party analytics queries",
|
|
850
|
+
);
|
|
851
|
+
}
|
|
606
852
|
if (/\bsession_replay_chunks\b/i.test(stripped)) {
|
|
607
853
|
throw new Error(
|
|
608
854
|
"First-party analytics queries cannot read session replay chunks",
|
|
609
855
|
);
|
|
610
856
|
}
|
|
611
857
|
|
|
612
|
-
const cteNames =
|
|
613
|
-
const cteRe = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s+as\s*\(/gi;
|
|
614
|
-
for (const match of stripped.matchAll(cteRe)) {
|
|
615
|
-
cteNames.add(match[1].toLowerCase());
|
|
616
|
-
}
|
|
617
|
-
|
|
858
|
+
const { cteNames, sources } = collectAnalyticsSqlSources(sql);
|
|
618
859
|
let usesAllowedTable = false;
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
860
|
+
for (const source of sources) {
|
|
861
|
+
const ref = source.ref.toLowerCase();
|
|
862
|
+
if (source.commaSeparated) {
|
|
863
|
+
throw new Error(
|
|
864
|
+
"Comma-separated table sources are not supported in first-party analytics queries; use an explicit JOIN",
|
|
865
|
+
);
|
|
866
|
+
}
|
|
623
867
|
if (FIRST_PARTY_QUERY_TABLES.has(ref)) {
|
|
868
|
+
if (source.quoted) {
|
|
869
|
+
throw new Error(
|
|
870
|
+
"Quoted table identifiers are not supported in first-party analytics queries",
|
|
871
|
+
);
|
|
872
|
+
}
|
|
624
873
|
usesAllowedTable = true;
|
|
625
874
|
continue;
|
|
626
875
|
}
|
|
627
876
|
if (cteNames.has(ref)) continue;
|
|
628
877
|
throw new Error(
|
|
629
|
-
`First-party analytics queries can only read ${FIRST_PARTY_QUERY_TABLE_LIST} (found ${
|
|
878
|
+
`First-party analytics queries can only read ${FIRST_PARTY_QUERY_TABLE_LIST} (found ${source.ref})`,
|
|
630
879
|
);
|
|
631
880
|
}
|
|
632
881
|
if (!usesAllowedTable) {
|
|
@@ -17,6 +17,8 @@ const DEFAULT_API_BASE = "https://api.gong.io/v2";
|
|
|
17
17
|
const MAX_GONG_SEARCH_PAGES = 50;
|
|
18
18
|
const MAX_GONG_CALL_LIST_PAGES = 50;
|
|
19
19
|
const MAX_GONG_EXHAUSTIVE_RECORDS = 500;
|
|
20
|
+
const GONG_API_REQUEST_TIMEOUT_MS = 30_000;
|
|
21
|
+
const MAX_GONG_EXHAUSTIVE_RUNTIME_MS = 60_000;
|
|
20
22
|
|
|
21
23
|
const cache = new Map<string, { data: unknown; ts: number }>();
|
|
22
24
|
const CACHE_TTL_MS = 10 * 60 * 1000;
|
|
@@ -38,7 +40,29 @@ async function getApiBase(): Promise<string> {
|
|
|
38
40
|
return (configured || DEFAULT_API_BASE).replace(/\/+$/, "");
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
export interface GongRequestOptions {
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
deadlineAt?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function requestSignal(options?: GongRequestOptions): AbortSignal {
|
|
49
|
+
const remainingMs = options?.deadlineAt
|
|
50
|
+
? Math.max(
|
|
51
|
+
1,
|
|
52
|
+
Math.min(GONG_API_REQUEST_TIMEOUT_MS, options.deadlineAt - Date.now()),
|
|
53
|
+
)
|
|
54
|
+
: GONG_API_REQUEST_TIMEOUT_MS;
|
|
55
|
+
const timeoutSignal = AbortSignal.timeout(remainingMs);
|
|
56
|
+
return options?.signal
|
|
57
|
+
? AbortSignal.any([options.signal, timeoutSignal])
|
|
58
|
+
: timeoutSignal;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function apiGet<T>(
|
|
62
|
+
path: string,
|
|
63
|
+
cacheKey?: string,
|
|
64
|
+
options?: GongRequestOptions,
|
|
65
|
+
): Promise<T> {
|
|
42
66
|
const key = scopedCredentialCacheKey(cacheKey ?? path, "GONG_ACCESS_KEY");
|
|
43
67
|
const cached = cache.get(key);
|
|
44
68
|
if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
|
|
@@ -50,6 +74,7 @@ async function apiGet<T>(path: string, cacheKey?: string): Promise<T> {
|
|
|
50
74
|
Authorization: await getAuthHeader(),
|
|
51
75
|
"Content-Type": "application/json",
|
|
52
76
|
},
|
|
77
|
+
signal: requestSignal(options),
|
|
53
78
|
});
|
|
54
79
|
|
|
55
80
|
if (!res.ok) {
|
|
@@ -72,6 +97,7 @@ async function apiPost<T>(
|
|
|
72
97
|
path: string,
|
|
73
98
|
body: unknown,
|
|
74
99
|
cacheKey?: string,
|
|
100
|
+
options?: GongRequestOptions,
|
|
75
101
|
): Promise<T> {
|
|
76
102
|
const key = scopedCredentialCacheKey(
|
|
77
103
|
cacheKey ?? `POST:${path}:${JSON.stringify(body)}`,
|
|
@@ -89,6 +115,7 @@ async function apiPost<T>(
|
|
|
89
115
|
"Content-Type": "application/json",
|
|
90
116
|
},
|
|
91
117
|
body: JSON.stringify(body),
|
|
118
|
+
signal: requestSignal(options),
|
|
92
119
|
});
|
|
93
120
|
|
|
94
121
|
if (!res.ok) {
|
|
@@ -159,11 +186,18 @@ export interface EnrichedTranscript {
|
|
|
159
186
|
externalMonologues: EnrichedMonologue[];
|
|
160
187
|
}
|
|
161
188
|
|
|
162
|
-
export async function getCalls(
|
|
163
|
-
|
|
164
|
-
|
|
189
|
+
export async function getCalls(
|
|
190
|
+
filters?: {
|
|
191
|
+
fromDateTime?: string;
|
|
192
|
+
toDateTime?: string;
|
|
193
|
+
cursor?: string;
|
|
194
|
+
},
|
|
195
|
+
options?: GongRequestOptions,
|
|
196
|
+
): Promise<{
|
|
197
|
+
calls: GongCall[];
|
|
165
198
|
cursor?: string;
|
|
166
|
-
|
|
199
|
+
totalRecords?: number;
|
|
200
|
+
}> {
|
|
167
201
|
const params = new URLSearchParams();
|
|
168
202
|
if (filters?.fromDateTime) params.set("fromDateTime", filters.fromDateTime);
|
|
169
203
|
if (filters?.toDateTime) params.set("toDateTime", filters.toDateTime);
|
|
@@ -174,7 +208,7 @@ export async function getCalls(filters?: {
|
|
|
174
208
|
const data = await apiGet<{
|
|
175
209
|
calls?: GongCall[];
|
|
176
210
|
records?: { cursor?: string; totalRecords?: number };
|
|
177
|
-
}>(path);
|
|
211
|
+
}>(path, undefined, options);
|
|
178
212
|
return {
|
|
179
213
|
calls: data.calls ?? [],
|
|
180
214
|
cursor: data.records?.cursor,
|
|
@@ -184,41 +218,62 @@ export async function getCalls(filters?: {
|
|
|
184
218
|
};
|
|
185
219
|
}
|
|
186
220
|
|
|
187
|
-
export async function getAllCalls(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
221
|
+
export async function getAllCalls(
|
|
222
|
+
filters?: {
|
|
223
|
+
fromDateTime?: string;
|
|
224
|
+
toDateTime?: string;
|
|
225
|
+
},
|
|
226
|
+
options: GongRequestOptions = {},
|
|
227
|
+
): Promise<{
|
|
191
228
|
calls: GongCall[];
|
|
192
229
|
cursor?: string;
|
|
193
230
|
pages: number;
|
|
194
231
|
totalRecords?: number;
|
|
195
232
|
}> {
|
|
233
|
+
const deadlineAt =
|
|
234
|
+
options.deadlineAt ?? Date.now() + MAX_GONG_EXHAUSTIVE_RUNTIME_MS;
|
|
196
235
|
const calls: GongCall[] = [];
|
|
197
236
|
let cursor: string | undefined;
|
|
198
237
|
let pages = 0;
|
|
199
238
|
let totalRecords: number | undefined;
|
|
200
239
|
|
|
201
240
|
do {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
241
|
+
if (Date.now() >= deadlineAt) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"Gong exhaustive call listing exceeded its 60-second runtime budget. " +
|
|
244
|
+
"Use provider-api-request with stageAs and pagination, followed by query-staged-dataset or a Data Program.",
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const page = await getCalls(
|
|
248
|
+
{
|
|
249
|
+
...filters,
|
|
250
|
+
...(cursor ? { cursor } : {}),
|
|
251
|
+
},
|
|
252
|
+
{ ...options, deadlineAt },
|
|
253
|
+
);
|
|
206
254
|
pages += 1;
|
|
207
255
|
if (typeof page.totalRecords === "number") {
|
|
208
256
|
totalRecords = page.totalRecords;
|
|
209
257
|
}
|
|
210
258
|
if (
|
|
211
259
|
typeof totalRecords === "number" &&
|
|
212
|
-
totalRecords
|
|
260
|
+
totalRecords >= MAX_GONG_EXHAUSTIVE_RECORDS
|
|
213
261
|
) {
|
|
214
262
|
throw new Error(
|
|
215
|
-
`Gong exhaustive call listing found ${totalRecords.toLocaleString()} records in the requested window
|
|
263
|
+
`Gong exhaustive call listing found ${totalRecords.toLocaleString()} records in the requested window; ` +
|
|
264
|
+
"Use provider-api-request with stageAs and pagination, followed by query-staged-dataset or a Data Program, instead of returning the full cohort through gong-calls.",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const nextCursor = page.cursor;
|
|
268
|
+
const nextCallCount = calls.length + page.calls.length;
|
|
269
|
+
if (nextCallCount >= MAX_GONG_EXHAUSTIVE_RECORDS) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`Gong exhaustive call listing reached ${MAX_GONG_EXHAUSTIVE_RECORDS.toLocaleString()} records before the cohort was safely returned. ` +
|
|
216
272
|
"Use provider-api-request with stageAs and pagination, followed by query-staged-dataset or a Data Program, instead of returning the full cohort through gong-calls.",
|
|
217
273
|
);
|
|
218
274
|
}
|
|
219
275
|
calls.push(...page.calls);
|
|
220
276
|
|
|
221
|
-
const nextCursor = page.cursor;
|
|
222
277
|
if (!nextCursor || nextCursor === cursor) {
|
|
223
278
|
cursor = nextCursor;
|
|
224
279
|
break;
|
|
@@ -229,18 +284,23 @@ export async function getAllCalls(filters?: {
|
|
|
229
284
|
return { calls, cursor, pages, totalRecords };
|
|
230
285
|
}
|
|
231
286
|
|
|
232
|
-
export async function getCall(
|
|
287
|
+
export async function getCall(
|
|
288
|
+
callId: string,
|
|
289
|
+
options?: GongRequestOptions,
|
|
290
|
+
): Promise<GongCall | null> {
|
|
233
291
|
const body = { filter: { callIds: [callId] } };
|
|
234
292
|
const data = await apiPost<{ calls?: GongCall[] }>(
|
|
235
293
|
"/calls",
|
|
236
294
|
body,
|
|
237
295
|
`call:${callId}`,
|
|
296
|
+
options,
|
|
238
297
|
);
|
|
239
298
|
return data.calls?.[0] ?? null;
|
|
240
299
|
}
|
|
241
300
|
|
|
242
301
|
export async function getCallDetail(
|
|
243
302
|
callId: string,
|
|
303
|
+
options?: GongRequestOptions,
|
|
244
304
|
): Promise<GongCallDetail | null> {
|
|
245
305
|
const body = {
|
|
246
306
|
filter: { callIds: [callId] },
|
|
@@ -255,6 +315,7 @@ export async function getCallDetail(
|
|
|
255
315
|
"/calls/extensive",
|
|
256
316
|
body,
|
|
257
317
|
`detail:${callId}`,
|
|
318
|
+
options,
|
|
258
319
|
);
|
|
259
320
|
const call = data.calls?.[0];
|
|
260
321
|
if (!call) return null;
|
|
@@ -279,7 +340,10 @@ export async function getCallDetail(
|
|
|
279
340
|
};
|
|
280
341
|
}
|
|
281
342
|
|
|
282
|
-
export async function getCallTranscripts(
|
|
343
|
+
export async function getCallTranscripts(
|
|
344
|
+
callIds: string[],
|
|
345
|
+
options?: GongRequestOptions,
|
|
346
|
+
): Promise<unknown> {
|
|
283
347
|
const ids = Array.from(
|
|
284
348
|
new Set(
|
|
285
349
|
callIds
|
|
@@ -289,11 +353,19 @@ export async function getCallTranscripts(callIds: string[]): Promise<unknown> {
|
|
|
289
353
|
);
|
|
290
354
|
if (!ids.length) return { callTranscripts: [] };
|
|
291
355
|
const body = { filter: { callIds: ids } };
|
|
292
|
-
return apiPost(
|
|
356
|
+
return apiPost(
|
|
357
|
+
"/calls/transcript",
|
|
358
|
+
body,
|
|
359
|
+
`transcripts:${ids.join(",")}`,
|
|
360
|
+
options,
|
|
361
|
+
);
|
|
293
362
|
}
|
|
294
363
|
|
|
295
|
-
export async function getCallTranscript(
|
|
296
|
-
|
|
364
|
+
export async function getCallTranscript(
|
|
365
|
+
callId: string,
|
|
366
|
+
options?: GongRequestOptions,
|
|
367
|
+
): Promise<unknown> {
|
|
368
|
+
return getCallTranscripts([callId], options);
|
|
297
369
|
}
|
|
298
370
|
|
|
299
371
|
export async function getEnrichedTranscript(
|
|
@@ -502,6 +574,8 @@ export interface GongCallSearchOptions {
|
|
|
502
574
|
fromDateTime?: string;
|
|
503
575
|
toDateTime?: string;
|
|
504
576
|
exhaustive?: boolean;
|
|
577
|
+
signal?: AbortSignal;
|
|
578
|
+
deadlineAt?: number;
|
|
505
579
|
}
|
|
506
580
|
|
|
507
581
|
function normalizedSearchQueries(queries: string[]): string[] {
|
|
@@ -591,32 +665,57 @@ export async function searchCallsForQueries(
|
|
|
591
665
|
|
|
592
666
|
const matches = new Map<string, GongCall & { matchedQueries?: string[] }>();
|
|
593
667
|
let searchedCallCount = 0;
|
|
668
|
+
let scannedCallCount = 0;
|
|
594
669
|
let cursor: string | undefined;
|
|
595
670
|
let pages = 0;
|
|
671
|
+
const deadlineAt =
|
|
672
|
+
options.deadlineAt ?? Date.now() + MAX_GONG_EXHAUSTIVE_RUNTIME_MS;
|
|
596
673
|
do {
|
|
674
|
+
if (Date.now() >= deadlineAt) {
|
|
675
|
+
throw new Error(
|
|
676
|
+
"Gong call search exceeded its 60-second runtime budget. " +
|
|
677
|
+
"Use provider-api-request with stageAs and pagination, followed by query-staged-dataset or a Data Program.",
|
|
678
|
+
);
|
|
679
|
+
}
|
|
597
680
|
const data = await apiPost<{
|
|
598
681
|
calls?: unknown[];
|
|
599
682
|
records?: { cursor?: string; totalRecords?: number };
|
|
600
|
-
}>(
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
683
|
+
}>(
|
|
684
|
+
"/calls/extensive",
|
|
685
|
+
{
|
|
686
|
+
filter: {
|
|
687
|
+
fromDateTime,
|
|
688
|
+
...(options.toDateTime ? { toDateTime: options.toDateTime } : {}),
|
|
689
|
+
},
|
|
690
|
+
contentSelector: { exposedFields: { parties: true } },
|
|
691
|
+
...(cursor ? { cursor } : {}),
|
|
604
692
|
},
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
693
|
+
undefined,
|
|
694
|
+
{
|
|
695
|
+
signal: options.signal,
|
|
696
|
+
deadlineAt,
|
|
697
|
+
},
|
|
698
|
+
);
|
|
608
699
|
pages += 1;
|
|
609
700
|
if (
|
|
610
701
|
options.exhaustive &&
|
|
611
702
|
typeof data.records?.totalRecords === "number" &&
|
|
612
|
-
data.records.totalRecords
|
|
703
|
+
data.records.totalRecords >= MAX_GONG_EXHAUSTIVE_RECORDS
|
|
613
704
|
) {
|
|
614
705
|
throw new Error(
|
|
615
|
-
`Gong exhaustive search found ${data.records.totalRecords.toLocaleString()} records in the requested window
|
|
616
|
-
"Use provider-corpus-job with staged call IDs for searches
|
|
706
|
+
`Gong exhaustive search found ${data.records.totalRecords.toLocaleString()} records in the requested window; ` +
|
|
707
|
+
"Use provider-corpus-job with staged call IDs for searches with 500 or more records so progress is checkpointed between batches.",
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
const rawCalls = data.calls ?? [];
|
|
711
|
+
scannedCallCount += rawCalls.length;
|
|
712
|
+
if (options.exhaustive && scannedCallCount >= MAX_GONG_EXHAUSTIVE_RECORDS) {
|
|
713
|
+
throw new Error(
|
|
714
|
+
`Gong exhaustive search reached ${MAX_GONG_EXHAUSTIVE_RECORDS.toLocaleString()} records before the cohort was safely returned. ` +
|
|
715
|
+
"Use provider-corpus-job with staged call IDs for searches with 500 or more records so progress is checkpointed between batches.",
|
|
617
716
|
);
|
|
618
717
|
}
|
|
619
|
-
const calls =
|
|
718
|
+
const calls = rawCalls
|
|
620
719
|
.map(normalizeExtensiveCall)
|
|
621
720
|
.filter((call): call is GongCall => Boolean(call))
|
|
622
721
|
.filter(isExternalCall);
|
|
@@ -656,7 +656,7 @@ export function realDataFinalGuard(
|
|
|
656
656
|
) {
|
|
657
657
|
return {
|
|
658
658
|
retryMessage:
|
|
659
|
-
"The user asked a coverage-sensitive provider question, but the draft only used bounded convenience data actions. Do not finalize an exhaustive, all-records, or absence-sensitive answer from shortcut actions alone. Use the broad provider API/MCP surface and a staged analysis workflow now: provider-api-catalog/provider-api-docs when needed; for Gong, use configured tracker results from /calls/extensive when they cover the term, otherwise use provider-api-request as raw ingestion with stageAs/saveToFile followed by query-staged-dataset or a Data Program; use provider-corpus-job for durable batched raw-transcript scans. Never loop per call from run-code or a delegated agent. For
|
|
659
|
+
"The user asked a coverage-sensitive provider question, but the draft only used bounded convenience data actions. Do not finalize an exhaustive, all-records, or absence-sensitive answer from shortcut actions alone. Use the broad provider API/MCP surface and a staged analysis workflow now: provider-api-catalog/provider-api-docs when needed; for Gong, use configured tracker results from /calls/extensive when they cover the term, otherwise use provider-api-request as raw ingestion with stageAs/saveToFile followed by query-staged-dataset or a Data Program; use provider-corpus-job for durable batched raw-transcript scans. Never loop per call from run-code or a delegated agent. For 500 or more Gong records, gong-calls is not the broad-search path. If full coverage is not possible in this turn, finalize with explicit partial-coverage wording, inspected counts, filters, and remaining gaps.",
|
|
660
660
|
fallbackMessage:
|
|
661
661
|
"I can't make a confident coverage-sensitive provider claim from bounded shortcut actions alone. I need a provider API/corpus workflow, or I need to label the answer as partial with exact inspected counts and gaps.",
|
|
662
662
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createAuthPlugin,
|
|
3
|
+
isInBackgroundFunctionRuntime,
|
|
4
|
+
markDefaultPluginProvided,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
const authPlugin = createAuthPlugin({
|
|
4
8
|
publicPaths: [
|
|
5
9
|
"/track",
|
|
6
10
|
"/api/analytics/track",
|
|
@@ -23,3 +27,18 @@ export default createAuthPlugin({
|
|
|
23
27
|
],
|
|
24
28
|
},
|
|
25
29
|
});
|
|
30
|
+
|
|
31
|
+
export default async (nitroApp: any): Promise<void> => {
|
|
32
|
+
// Keep the custom slot marked so runtime discovery cannot mount a second
|
|
33
|
+
// default auth plugin while this worker is registering its internal routes.
|
|
34
|
+
markDefaultPluginProvided(nitroApp, "auth");
|
|
35
|
+
if (isInBackgroundFunctionRuntime()) {
|
|
36
|
+
// Background functions authenticate their signed processor routes locally;
|
|
37
|
+
// Better Auth would perform an unnecessary database initialization here.
|
|
38
|
+
console.info(
|
|
39
|
+
"[auth] Skipping Better Auth setup in durable background runtime",
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await authPlugin(nitroApp);
|
|
44
|
+
};
|