@agent-native/core 0.189.0 → 0.190.0-nightly-20260925211446
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 +10 -0
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +1 -1
- package/corpus/templates/analytics/AGENTS.md +11 -22
- package/corpus/templates/analytics/actions/list-data-dictionary.ts +7 -9
- package/corpus/templates/analytics/actions/search-analytics-query-catalog.ts +1 -1
- package/corpus/templates/analytics/app/components/AnalyticsReviewArtifactPreview.tsx +201 -0
- package/corpus/templates/analytics/app/i18n/en-US.ts +2 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +50 -3
- package/corpus/templates/analytics/changelog/2026-09-25-first-run-onboarding-records-setup-choices-and-builder-conne.md +6 -0
- package/corpus/templates/analytics/changelog/2026-09-25-human-review-previews-analytics-dashboards-as-real-charts.md +6 -0
- package/corpus/templates/analytics/changelog/2026-09-25-org-admins-can-review-agent-runs-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-09-25-proactive-query-context.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +98 -2
- package/corpus/templates/analytics/server/lib/analytics-agent-context.ts +557 -0
- package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +217 -46
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +2 -2
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +39 -5
- package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +4 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +268 -2
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +44 -9
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/actions/get-event-rules-status.ts +47 -0
- package/corpus/templates/calendar/actions/list-events.ts +50 -4
- package/corpus/templates/calendar/actions/undo-calendar-event-rule.ts +229 -0
- package/corpus/templates/calendar/actions/update-settings.ts +8 -0
- package/corpus/templates/calendar/app/i18n/en-US.ts +27 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +196 -1
- package/corpus/templates/calendar/changelog/2026-09-25-calendar-feeds-with-the-same-url-keep-their-own-names-and-co.md +6 -0
- package/corpus/templates/calendar/changelog/2026-09-25-calendar-invitation-rules-surface-expired-connections-and-un.md +6 -0
- package/corpus/templates/calendar/changelog/2026-09-25-restore-locally-hidden-events-from-activity-after-disconnect.md +6 -0
- package/corpus/templates/calendar/server/lib/calendar-settings.ts +44 -10
- package/corpus/templates/calendar/server/lib/google-api.ts +15 -1
- package/corpus/templates/calendar/server/lib/google-calendar.ts +1 -1
- package/corpus/templates/calendar/server/plugins/event-rules.ts +628 -0
- package/corpus/templates/calendar/shared/api.ts +13 -0
- package/corpus/templates/calendar/shared/settings.ts +71 -1
- package/corpus/templates/clips/actions/create-recording.ts +83 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +610 -110
- package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +2 -0
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +7 -0
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +15 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +126 -26
- package/corpus/templates/clips/app/hooks/use-drop-video-upload.ts +75 -15
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +5 -1
- package/corpus/templates/clips/app/routes/record.tsx +134 -40
- package/corpus/templates/clips/chrome-extension/src/background.ts +3 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +73 -16
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +188 -18
- package/corpus/templates/clips/desktop/src/lib/recording-request.ts +1 -0
- package/corpus/templates/clips/server/db/schema.ts +4 -0
- package/corpus/templates/clips/server/jobs/media-verification.ts +78 -4
- package/corpus/templates/clips/server/jobs/recording-failure-backfill.ts +142 -0
- package/corpus/templates/clips/server/lib/media-verification-state.ts +29 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +2 -0
- package/corpus/templates/clips/server/lib/recording-failures.ts +111 -0
- package/corpus/templates/clips/server/lib/recordings.ts +6 -1
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +8 -3
- package/corpus/templates/clips/server/lib/upload-lease.ts +25 -6
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/plugins/db.ts +18 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +48 -5
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +322 -31
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +223 -61
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +62 -4
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +74 -7
- package/corpus/templates/clips/shared/recording-core.ts +27 -0
- package/corpus/templates/design/actions/get-design.ts +35 -6
- package/corpus/templates/mail/.agents/skills/inbox-automations/SKILL.md +4 -0
- package/corpus/templates/mail/actions/consolidate-ai-filter-rules.ts +41 -0
- package/corpus/templates/mail/actions/get-ai-priority.ts +48 -8
- package/corpus/templates/mail/actions/record-ai-priority-feedback.ts +69 -0
- package/corpus/templates/mail/actions/update-mail-preferences.ts +2 -0
- package/corpus/templates/mail/app/components/email/EmailList.tsx +179 -32
- package/corpus/templates/mail/app/components/email/EmailListItem.tsx +97 -9
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +55 -1
- package/corpus/templates/mail/app/components/email/ImportanceFeedbackMenu.tsx +52 -0
- package/corpus/templates/mail/app/components/email/compose-autocomplete.ts +2 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +219 -188
- package/corpus/templates/mail/app/components/onboarding/AiInboxSetup.tsx +338 -0
- package/corpus/templates/mail/app/components/settings/AiFilterSection.tsx +597 -781
- package/corpus/templates/mail/app/components/settings/AiRulePromptField.tsx +37 -0
- package/corpus/templates/mail/app/global.css +42 -26
- package/corpus/templates/mail/app/hooks/use-ai-priority-feedback.ts +43 -0
- package/corpus/templates/mail/app/hooks/use-automations.ts +23 -0
- package/corpus/templates/mail/app/i18n/en-US.ts +58 -3
- package/corpus/templates/mail/app/pages/InboxPage.tsx +47 -10
- package/corpus/templates/mail/changelog/2026-09-25-editing-importance-rules-preserves-disabled-instructions-and.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-25-handle-astral-unicode-letters-in-autocomplete-word-boundarie.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-25-jev-priority-feedback-and-inbox-settings.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-25-keep-existing-ai-filter-rules-intact-when-saving-a-prompt.md +6 -0
- package/corpus/templates/mail/server/lib/automation-engine.ts +21 -12
- package/corpus/templates/mail/server/lib/automations.ts +100 -1
- package/corpus/templates/mail/shared/types.ts +3 -0
- package/corpus/templates/plan/app/i18n/en-US.ts +6 -0
- package/corpus/templates/plan/app/lib/plan-invite-suggestion.ts +43 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +249 -4
- package/corpus/templates/slides/actions/create-deck.ts +11 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +117 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +373 -330
- package/corpus/templates/slides/app/components/design-system/builder-index-response.ts +36 -5
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +21 -53
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +347 -671
- package/corpus/templates/slides/app/components/editor/bullet-editing.ts +55 -8
- package/corpus/templates/slides/app/components/editor/in-place-text-session.ts +2082 -0
- package/corpus/templates/slides/app/components/editor/list-editing.ts +174 -16
- package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +497 -62
- package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +102 -20
- package/corpus/templates/slides/app/context/DeckContext.tsx +9 -4
- package/corpus/templates/slides/app/global.css +7 -219
- package/corpus/templates/slides/app/i18n/en-US.ts +3 -0
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +7 -0
- package/corpus/templates/slides/app/lib/slide-image-replacement.ts +203 -1
- package/corpus/templates/slides/app/lib/slide-source-map.ts +43 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +182 -4
- package/corpus/templates/slides/app/routes/settings.tsx +33 -2
- package/corpus/templates/slides/changelog/2026-09-25-improve-slides-design-system-setup-feedback.md +6 -0
- package/corpus/templates/slides/changelog/2026-09-25-org-admins-can-review-agent-runs-from-settings.md +6 -0
- package/corpus/templates/slides/changelog/2026-09-25-text-on-a-slide-is-now-edited-in-place-clicking-into-it-chan.md +6 -0
- package/corpus/templates/slides/package.json +0 -12
- package/corpus/templates/slides/scripts/edit-fidelity/README.md +4 -2
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +12 -3
- package/corpus/templates/slides/vite.config.ts +1 -10
- package/dist/agent/jev-tool-prefetch.d.ts +29 -3
- package/dist/agent/jev-tool-prefetch.js +161 -34
- package/dist/agent/production-agent.d.ts +11 -0
- package/dist/agent/production-agent.js +39 -8
- package/dist/app-config/runtime.d.ts +1 -0
- package/dist/app-config/runtime.js +4 -0
- package/dist/app-config/schema.d.ts +1 -0
- package/dist/client/AgentSidebar.d.ts +3 -1
- package/dist/client/AgentSidebar.js +8 -12
- package/dist/client/analytics/index.d.ts +1 -1
- package/dist/client/analytics/index.js +1 -1
- package/dist/client/analytics.d.ts +2 -0
- package/dist/client/analytics.js +9 -0
- package/dist/client/api-path.js +5 -2
- package/dist/client/chat/run-recovery.d.ts +0 -3
- package/dist/client/chat/run-recovery.js +13 -23
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/observability/ObservabilityDashboard.d.ts +3 -0
- package/dist/client/observability/ObservabilityDashboard.js +366 -71
- package/dist/client/observability/ObservabilityReviewSummaryButton.d.ts +4 -1
- package/dist/client/observability/ObservabilityReviewSummaryButton.js +13 -9
- package/dist/client/observability/OutputPreview.d.ts +9 -4
- package/dist/client/observability/OutputPreview.js +230 -52
- package/dist/client/onboarding/FirstRunOnboarding.js +67 -4
- package/dist/client/onboarding/use-onboarding.js +2 -0
- package/dist/client/sharing/ShareButton.d.ts +2 -0
- package/dist/client/sharing/ShareButton.js +1 -0
- package/dist/client/sharing/useShareButtonController.d.ts +1 -0
- package/dist/client/sharing/useShareButtonController.js +29 -8
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +9 -1
- package/dist/embeddings/index.d.ts +3 -1
- package/dist/embeddings/index.js +20 -10
- package/dist/jobs/sweep-hooks.d.ts +7 -0
- package/dist/jobs/sweep-hooks.js +28 -0
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/actions/authorization.d.ts +1 -0
- package/dist/observability/actions/authorization.js +22 -0
- package/dist/observability/actions/get-observability-review-detail.d.ts +5 -0
- package/dist/observability/actions/get-observability-review-detail.js +10 -2
- package/dist/observability/actions/get-observability-review-summary-source.d.ts +2 -0
- package/dist/observability/actions/get-observability-review-summary-source.js +3 -2
- package/dist/observability/actions/save-observability-instruction-update.js +2 -1
- package/dist/observability/actions/save-observability-review-summary.js +21 -10
- package/dist/observability/migrations.js +25 -0
- package/dist/observability/reviews.d.ts +3 -1
- package/dist/observability/reviews.js +410 -53
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/store.d.ts +15 -0
- package/dist/observability/store.js +131 -11
- package/dist/observability/types.d.ts +18 -0
- package/dist/org/handlers.d.ts +1 -1
- package/dist/org/index.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat/plugin-options.d.ts +4 -20
- package/dist/server/agent-chat/prompt-resources.d.ts +16 -0
- package/dist/server/agent-chat/prompt-resources.js +385 -22
- package/dist/server/agent-chat-plugin.d.ts +3 -1
- package/dist/server/agent-chat-plugin.js +149 -27
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +3 -0
- package/dist/server/prompts/framework-core-compact.js +1 -1
- package/dist/server/prompts/framework-core.js +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +6 -3
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/org-settings.d.ts +4 -2
- package/dist/settings/org-settings.js +3 -3
- package/dist/settings/store.d.ts +3 -1
- package/dist/settings/store.js +8 -3
- package/dist/styles/agent-native.css +5 -0
- package/dist/templates/default/app/i18n/ar-SA.ts +1 -0
- package/dist/templates/default/app/i18n/de-DE.ts +1 -0
- package/dist/templates/default/app/i18n/en-US.ts +1 -0
- package/dist/templates/default/app/i18n/es-ES.ts +1 -0
- package/dist/templates/default/app/i18n/fr-FR.ts +1 -0
- package/dist/templates/default/app/i18n/hi-IN.ts +1 -0
- package/dist/templates/default/app/i18n/ja-JP.ts +1 -0
- package/dist/templates/default/app/i18n/ko-KR.ts +1 -0
- package/dist/templates/default/app/i18n/pt-BR.ts +1 -0
- package/dist/templates/default/app/i18n/zh-CN.ts +1 -0
- package/dist/templates/default/app/i18n/zh-TW.ts +1 -0
- package/dist/tracking/registry.js +11 -4
- package/package.json +4 -3
- package/src/templates/default/app/i18n/ar-SA.ts +1 -0
- package/src/templates/default/app/i18n/de-DE.ts +1 -0
- package/src/templates/default/app/i18n/en-US.ts +1 -0
- package/src/templates/default/app/i18n/es-ES.ts +1 -0
- package/src/templates/default/app/i18n/fr-FR.ts +1 -0
- package/src/templates/default/app/i18n/hi-IN.ts +1 -0
- package/src/templates/default/app/i18n/ja-JP.ts +1 -0
- package/src/templates/default/app/i18n/ko-KR.ts +1 -0
- package/src/templates/default/app/i18n/pt-BR.ts +1 -0
- package/src/templates/default/app/i18n/zh-CN.ts +1 -0
- package/src/templates/default/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/slides/app/components/editor/SlideBubbleMenu.tsx +0 -488
- package/corpus/templates/slides/app/components/editor/SlideInlineEditor.tsx +0 -589
- package/corpus/templates/slides/app/components/editor/SlideRichTextEditor.tsx +0 -1347
- package/corpus/templates/slides/app/components/editor/SlideSlashMenu.tsx +0 -373
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
availableEmbeddingFamilies,
|
|
5
|
+
defaultEmbeddingFamily,
|
|
6
|
+
type EmbeddingFamily,
|
|
7
|
+
} from "@agent-native/core/embeddings";
|
|
8
|
+
import { getActiveEmbeddingSet } from "@agent-native/creative-context/store";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
candidateTrustTier,
|
|
12
|
+
searchAnalyticsQueryCatalog,
|
|
13
|
+
type AnalyticsQueryCatalogCandidate,
|
|
14
|
+
} from "./analytics-query-catalog";
|
|
15
|
+
import { renderDataDictionary } from "./data-dictionary-context";
|
|
16
|
+
|
|
17
|
+
const CATALOG_CANDIDATE_LIMIT = 24;
|
|
18
|
+
const PROMPT_CANDIDATE_LIMIT = 8;
|
|
19
|
+
const FALLBACK_CANDIDATE_LIMIT = 2;
|
|
20
|
+
const MAX_REFERENCE_CONTENT_CHARS = 4_000;
|
|
21
|
+
const MAX_EMBEDDING_CACHE_ENTRIES = 256;
|
|
22
|
+
const MAX_EMBEDDING_SUMMARY_CHARS = 400;
|
|
23
|
+
|
|
24
|
+
export interface AnalyticsPromptCandidate {
|
|
25
|
+
id: string;
|
|
26
|
+
description: string;
|
|
27
|
+
metadata: Record<string, string>;
|
|
28
|
+
name: string;
|
|
29
|
+
scope: string;
|
|
30
|
+
content: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AnalyticsPromptReferences {
|
|
34
|
+
jevPromptCandidates: AnalyticsPromptCandidate[];
|
|
35
|
+
jevFallbackCandidateIds: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const CATALOG_TOOL_NAMES = new Set([
|
|
39
|
+
"search-analytics-query-catalog",
|
|
40
|
+
"search-dashboard-references",
|
|
41
|
+
"list-data-dictionary",
|
|
42
|
+
"search-bigquery-schema",
|
|
43
|
+
]);
|
|
44
|
+
// Grounding also covers schema, health, status, and connection actions; only
|
|
45
|
+
// these bounded source reads count as query outcomes.
|
|
46
|
+
// ponytail: add new source-query actions here as they ship.
|
|
47
|
+
const QUERY_TOOL_NAMES = new Set([
|
|
48
|
+
"account-deep-dive",
|
|
49
|
+
"bigquery",
|
|
50
|
+
"builder-blog-articles",
|
|
51
|
+
"content-calendar",
|
|
52
|
+
"db-admin-federated-read",
|
|
53
|
+
"gcloud",
|
|
54
|
+
"get-error-issue",
|
|
55
|
+
"gong-calls",
|
|
56
|
+
"gong-native-insights",
|
|
57
|
+
"grafana",
|
|
58
|
+
"hubspot-deals",
|
|
59
|
+
"hubspot-metrics",
|
|
60
|
+
"hubspot-records",
|
|
61
|
+
"jira",
|
|
62
|
+
"jira-search",
|
|
63
|
+
"list-error-issues",
|
|
64
|
+
"match-error-issues",
|
|
65
|
+
"pylon-issues",
|
|
66
|
+
"prometheus",
|
|
67
|
+
"query-agent-native-analytics",
|
|
68
|
+
"query-dashboard-panel",
|
|
69
|
+
"query-inbound-forms",
|
|
70
|
+
"query-staged-dataset",
|
|
71
|
+
"sentry",
|
|
72
|
+
"seo-blog-pages",
|
|
73
|
+
"seo-page-keywords",
|
|
74
|
+
"seo-top-keywords",
|
|
75
|
+
"slack-messages",
|
|
76
|
+
"stripe",
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
export function summarizeAnalyticsRun(input: {
|
|
80
|
+
events: readonly unknown[];
|
|
81
|
+
preloadedReferenceCount: number;
|
|
82
|
+
}): Record<string, number | boolean> {
|
|
83
|
+
type ToolEvent = {
|
|
84
|
+
type: "tool_start" | "tool_done";
|
|
85
|
+
tool: string;
|
|
86
|
+
id?: string;
|
|
87
|
+
isError?: boolean;
|
|
88
|
+
};
|
|
89
|
+
const toolEvents = input.events.flatMap((entry): ToolEvent[] => {
|
|
90
|
+
if (!entry || typeof entry !== "object") return [];
|
|
91
|
+
const event = (entry as { event?: unknown }).event;
|
|
92
|
+
if (!event || typeof event !== "object") return [];
|
|
93
|
+
const value = event as Record<string, unknown>;
|
|
94
|
+
if (
|
|
95
|
+
(value.type !== "tool_start" && value.type !== "tool_done") ||
|
|
96
|
+
typeof value.tool !== "string"
|
|
97
|
+
) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
type: value.type,
|
|
103
|
+
tool: value.tool,
|
|
104
|
+
...(typeof value.id === "string" ? { id: value.id } : {}),
|
|
105
|
+
...(typeof value.isError === "boolean"
|
|
106
|
+
? { isError: value.isError }
|
|
107
|
+
: {}),
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
});
|
|
111
|
+
const startedTools = toolEvents.filter(
|
|
112
|
+
(event) => event.type === "tool_start",
|
|
113
|
+
);
|
|
114
|
+
const completedTools = toolEvents.filter(
|
|
115
|
+
(event) => event.type === "tool_done",
|
|
116
|
+
);
|
|
117
|
+
const queries = startedTools.filter((event) =>
|
|
118
|
+
QUERY_TOOL_NAMES.has(String(event.tool)),
|
|
119
|
+
);
|
|
120
|
+
const toolSearchCalls = startedTools.filter((event) =>
|
|
121
|
+
/^tool[-_]search(?:$|[-_])/.test(String(event.tool)),
|
|
122
|
+
).length;
|
|
123
|
+
const properties: Record<string, number | boolean> = {
|
|
124
|
+
preloaded_reference_count: Math.max(
|
|
125
|
+
0,
|
|
126
|
+
Math.floor(input.preloadedReferenceCount),
|
|
127
|
+
),
|
|
128
|
+
tool_search_calls: toolSearchCalls,
|
|
129
|
+
catalog_calls: startedTools.filter((event) =>
|
|
130
|
+
CATALOG_TOOL_NAMES.has(String(event.tool)),
|
|
131
|
+
).length,
|
|
132
|
+
query_calls: queries.length,
|
|
133
|
+
};
|
|
134
|
+
const firstQuery = queries[0];
|
|
135
|
+
const firstQueryDone = firstQuery
|
|
136
|
+
? typeof firstQuery.id === "string"
|
|
137
|
+
? completedTools.find(
|
|
138
|
+
(event) =>
|
|
139
|
+
event.tool === firstQuery.tool && event.id === firstQuery.id,
|
|
140
|
+
)
|
|
141
|
+
: undefined
|
|
142
|
+
: undefined;
|
|
143
|
+
const firstQueryError = firstQueryDone?.isError;
|
|
144
|
+
if (typeof firstQueryError === "boolean") {
|
|
145
|
+
properties.first_query_errored = firstQueryError;
|
|
146
|
+
}
|
|
147
|
+
return properties;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const documentEmbeddingCache = new Map<string, number[]>();
|
|
151
|
+
|
|
152
|
+
function jsonText(value: unknown): string {
|
|
153
|
+
if (typeof value === "string") return value.trim();
|
|
154
|
+
if (!value || typeof value !== "object") return "";
|
|
155
|
+
const serialized = JSON.stringify(value);
|
|
156
|
+
if (typeof serialized !== "string") {
|
|
157
|
+
throw new Error("Saved dashboard query is not JSON-serializable.");
|
|
158
|
+
}
|
|
159
|
+
return serialized;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function candidateContent(candidate: AnalyticsQueryCatalogCandidate): string {
|
|
163
|
+
const lines = [
|
|
164
|
+
"Retrieved Analytics reference. Use it to guide metric definitions and query shape; run a live source query before reporting values.",
|
|
165
|
+
];
|
|
166
|
+
if (candidate.kind === "data-dictionary") {
|
|
167
|
+
lines.push(
|
|
168
|
+
renderDataDictionary([
|
|
169
|
+
{
|
|
170
|
+
metric: candidate.metric,
|
|
171
|
+
definition: candidate.definition,
|
|
172
|
+
source: candidate.source,
|
|
173
|
+
action: candidate.action,
|
|
174
|
+
table: candidate.table,
|
|
175
|
+
columnsUsed: candidate.columnsUsed,
|
|
176
|
+
queryTemplate: candidate.queryTemplate,
|
|
177
|
+
knownGotchas: candidate.knownGotchas,
|
|
178
|
+
commonQuestions: candidate.commonQuestions,
|
|
179
|
+
cuts: candidate.cuts,
|
|
180
|
+
joinPattern: candidate.joinPattern,
|
|
181
|
+
updateFrequency: candidate.updateFrequency,
|
|
182
|
+
dataLag: candidate.dataLag,
|
|
183
|
+
dependencies: candidate.dependencies,
|
|
184
|
+
validDateRange: candidate.validDateRange,
|
|
185
|
+
owner: candidate.owner,
|
|
186
|
+
approved: candidate.approved,
|
|
187
|
+
aiGenerated: candidate.aiGenerated,
|
|
188
|
+
},
|
|
189
|
+
]),
|
|
190
|
+
candidate.source?.toLowerCase().includes("bigquery")
|
|
191
|
+
? "Source dialect: BigQuery GoogleSQL; use STRING, not TEXT, and avoid ILIKE."
|
|
192
|
+
: "",
|
|
193
|
+
candidate.action ? `Query action: ${candidate.action}` : "",
|
|
194
|
+
);
|
|
195
|
+
} else {
|
|
196
|
+
lines.push(
|
|
197
|
+
`Reference type: ${candidate.origin === "saved-dashboard" ? "saved dashboard" : "dashboard template"} panel`,
|
|
198
|
+
`Dashboard: ${candidate.dashboardTitle}`,
|
|
199
|
+
candidate.dashboardDescription
|
|
200
|
+
? `Dashboard purpose: ${candidate.dashboardDescription}`
|
|
201
|
+
: "",
|
|
202
|
+
`Panel: ${candidate.panelTitle}`,
|
|
203
|
+
candidate.panelDescription
|
|
204
|
+
? `Panel purpose: ${candidate.panelDescription}`
|
|
205
|
+
: "",
|
|
206
|
+
candidate.source ? `Source: ${candidate.source}` : "",
|
|
207
|
+
candidate.timeScope ? `Time scope: ${candidate.timeScope}` : "",
|
|
208
|
+
`Certification: ${candidate.dashboardCertified ? "certified" : "not certified"}`,
|
|
209
|
+
candidate.query ? `Query: ${jsonText(candidate.query)}` : "",
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return lines.filter(Boolean).join("\n").slice(0, MAX_REFERENCE_CONTENT_CHARS);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function candidateEmbeddingSummary(
|
|
216
|
+
candidate: AnalyticsQueryCatalogCandidate,
|
|
217
|
+
): string {
|
|
218
|
+
const fields =
|
|
219
|
+
candidate.kind === "data-dictionary"
|
|
220
|
+
? [
|
|
221
|
+
`Metric: ${candidate.metric}`,
|
|
222
|
+
candidate.definition,
|
|
223
|
+
candidate.source ? `Source: ${candidate.source}` : "",
|
|
224
|
+
candidate.table ? `Table: ${candidate.table}` : "",
|
|
225
|
+
]
|
|
226
|
+
: [
|
|
227
|
+
`Dashboard: ${candidate.dashboardTitle}`,
|
|
228
|
+
`Panel: ${candidate.panelTitle}`,
|
|
229
|
+
candidate.panelDescription,
|
|
230
|
+
candidate.source ? `Source: ${candidate.source}` : "",
|
|
231
|
+
];
|
|
232
|
+
return fields
|
|
233
|
+
.filter(Boolean)
|
|
234
|
+
.join(". ")
|
|
235
|
+
.replace(/\s+/g, " ")
|
|
236
|
+
.slice(0, MAX_EMBEDDING_SUMMARY_CHARS);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function candidateName(candidate: AnalyticsQueryCatalogCandidate): string {
|
|
240
|
+
return candidate.kind === "data-dictionary"
|
|
241
|
+
? `Data dictionary: ${candidate.metric}`
|
|
242
|
+
: `${candidate.dashboardTitle}: ${candidate.panelTitle}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function candidateEmbeddingKey(
|
|
246
|
+
family: EmbeddingFamily,
|
|
247
|
+
content: string,
|
|
248
|
+
): string {
|
|
249
|
+
const digest = createHash("sha256").update(content).digest("hex");
|
|
250
|
+
return `${family.id}:${family.model}:${family.version}:${digest}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function validVector(value: unknown, dimensions: number): value is number[] {
|
|
254
|
+
return (
|
|
255
|
+
Array.isArray(value) &&
|
|
256
|
+
value.length === dimensions &&
|
|
257
|
+
value.every((item) => typeof item === "number" && Number.isFinite(item))
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function cosineSimilarity(left: number[], right: number[]): number | null {
|
|
262
|
+
if (left.length !== right.length || left.length === 0) return null;
|
|
263
|
+
let dot = 0;
|
|
264
|
+
let leftNorm = 0;
|
|
265
|
+
let rightNorm = 0;
|
|
266
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
267
|
+
const a = left[index]!;
|
|
268
|
+
const b = right[index]!;
|
|
269
|
+
dot += a * b;
|
|
270
|
+
leftNorm += a * a;
|
|
271
|
+
rightNorm += b * b;
|
|
272
|
+
}
|
|
273
|
+
if (leftNorm === 0 || rightNorm === 0) return null;
|
|
274
|
+
return dot / Math.sqrt(leftNorm * rightNorm);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function resolveEmbeddingFamily(
|
|
278
|
+
deadlineAt: number,
|
|
279
|
+
): Promise<EmbeddingFamily | null> {
|
|
280
|
+
const families = await availableEmbeddingFamilies();
|
|
281
|
+
if (Date.now() >= deadlineAt || families.length === 0) return null;
|
|
282
|
+
const activeSet = await getActiveEmbeddingSet();
|
|
283
|
+
if (Date.now() >= deadlineAt) return null;
|
|
284
|
+
if (!activeSet) return defaultEmbeddingFamily(families);
|
|
285
|
+
return (
|
|
286
|
+
families.find(
|
|
287
|
+
(family) =>
|
|
288
|
+
family.id === activeSet.family &&
|
|
289
|
+
family.model === activeSet.model &&
|
|
290
|
+
family.version === activeSet.version &&
|
|
291
|
+
family.dimensions === activeSet.dimensions,
|
|
292
|
+
) ?? null
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function embedDocuments(
|
|
297
|
+
family: EmbeddingFamily,
|
|
298
|
+
documents: string[],
|
|
299
|
+
shouldCache: () => boolean,
|
|
300
|
+
signal: AbortSignal,
|
|
301
|
+
): Promise<number[][]> {
|
|
302
|
+
const vectors = new Array<number[] | undefined>(documents.length);
|
|
303
|
+
const missing = new Map<string, string>();
|
|
304
|
+
const newlyEmbedded = new Map<string, number[]>();
|
|
305
|
+
const keys = documents.map((content, index) => {
|
|
306
|
+
const key = candidateEmbeddingKey(family, content);
|
|
307
|
+
const cached = documentEmbeddingCache.get(key);
|
|
308
|
+
if (cached) vectors[index] = cached;
|
|
309
|
+
else missing.set(key, content);
|
|
310
|
+
return key;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (missing.size > 0) {
|
|
314
|
+
const entries = [...missing.entries()];
|
|
315
|
+
const embedded = await family.embed(
|
|
316
|
+
entries.map(([, text]) => ({ text })),
|
|
317
|
+
"document",
|
|
318
|
+
{ signal },
|
|
319
|
+
);
|
|
320
|
+
if (
|
|
321
|
+
embedded.length !== entries.length ||
|
|
322
|
+
embedded.some((vector) => !validVector(vector, family.dimensions))
|
|
323
|
+
) {
|
|
324
|
+
throw new Error("Embedding provider returned invalid Analytics vectors.");
|
|
325
|
+
}
|
|
326
|
+
entries.forEach(([key], index) => {
|
|
327
|
+
const vector = embedded[index]!;
|
|
328
|
+
newlyEmbedded.set(key, vector);
|
|
329
|
+
if (!shouldCache()) return;
|
|
330
|
+
// ponytail: Keep this 256-vector FIFO process-local; use templates/brain/server/lib/hybrid-search.ts for pgvector + FTS + RRF when cold-start misses justify persistent embeddings.
|
|
331
|
+
if (documentEmbeddingCache.size >= MAX_EMBEDDING_CACHE_ENTRIES) {
|
|
332
|
+
const oldest = documentEmbeddingCache.keys().next().value;
|
|
333
|
+
if (oldest !== undefined) documentEmbeddingCache.delete(oldest);
|
|
334
|
+
}
|
|
335
|
+
documentEmbeddingCache.set(key, vector);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
340
|
+
vectors[index] ??=
|
|
341
|
+
newlyEmbedded.get(keys[index]!) ??
|
|
342
|
+
documentEmbeddingCache.get(keys[index]!);
|
|
343
|
+
}
|
|
344
|
+
if (vectors.some((vector) => !vector)) {
|
|
345
|
+
throw new Error("Analytics document embeddings were not returned.");
|
|
346
|
+
}
|
|
347
|
+
return vectors as number[][];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function rankWithEmbeddings(
|
|
351
|
+
request: string,
|
|
352
|
+
candidates: AnalyticsQueryCatalogCandidate[],
|
|
353
|
+
shouldCache: () => boolean,
|
|
354
|
+
deadlineAt: number,
|
|
355
|
+
signal: AbortSignal,
|
|
356
|
+
): Promise<RankedCandidate[]> {
|
|
357
|
+
const family = await resolveEmbeddingFamily(deadlineAt);
|
|
358
|
+
signal.throwIfAborted();
|
|
359
|
+
if (Date.now() >= deadlineAt || !family || candidates.length === 0) {
|
|
360
|
+
return candidates.map((candidate) => ({ candidate }));
|
|
361
|
+
}
|
|
362
|
+
const contents = candidates.map(candidateEmbeddingSummary);
|
|
363
|
+
const [queryVectors, documentVectors] = await Promise.all([
|
|
364
|
+
family.embed([{ text: request }], "query", { signal }),
|
|
365
|
+
embedDocuments(family, contents, shouldCache, signal),
|
|
366
|
+
]);
|
|
367
|
+
signal.throwIfAborted();
|
|
368
|
+
const queryVector = queryVectors[0];
|
|
369
|
+
if (!validVector(queryVector, family.dimensions)) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
"Embedding provider returned an invalid Analytics query vector.",
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
const scores = documentVectors.map((vector) =>
|
|
375
|
+
cosineSimilarity(queryVector, vector),
|
|
376
|
+
);
|
|
377
|
+
if (scores.some((score) => score === null || !Number.isFinite(score))) {
|
|
378
|
+
throw new Error("Analytics embedding similarity could not be calculated.");
|
|
379
|
+
}
|
|
380
|
+
return candidates
|
|
381
|
+
.map((candidate, index) => ({
|
|
382
|
+
candidate,
|
|
383
|
+
similarity: scores[index]!,
|
|
384
|
+
lexicalScore: candidate.score,
|
|
385
|
+
}))
|
|
386
|
+
.sort(
|
|
387
|
+
(left, right) =>
|
|
388
|
+
candidateTrustTier(right.candidate) -
|
|
389
|
+
candidateTrustTier(left.candidate) ||
|
|
390
|
+
right.similarity - left.similarity ||
|
|
391
|
+
right.lexicalScore - left.lexicalScore ||
|
|
392
|
+
candidateName(left.candidate).localeCompare(
|
|
393
|
+
candidateName(right.candidate),
|
|
394
|
+
),
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function jevDescription(
|
|
399
|
+
candidate: AnalyticsQueryCatalogCandidate,
|
|
400
|
+
retrievalRank: number,
|
|
401
|
+
similarity?: number,
|
|
402
|
+
): string {
|
|
403
|
+
const kind =
|
|
404
|
+
candidate.kind === "data-dictionary"
|
|
405
|
+
? "dictionary entry"
|
|
406
|
+
: "dashboard panel";
|
|
407
|
+
const trust =
|
|
408
|
+
candidate.kind === "data-dictionary"
|
|
409
|
+
? candidate.approved
|
|
410
|
+
? "approved"
|
|
411
|
+
: candidate.aiGenerated
|
|
412
|
+
? "AI generated and unapproved"
|
|
413
|
+
: "unreviewed"
|
|
414
|
+
: candidate.dashboardCertified
|
|
415
|
+
? "certified"
|
|
416
|
+
: "not certified";
|
|
417
|
+
const similarityText =
|
|
418
|
+
similarity === undefined
|
|
419
|
+
? ""
|
|
420
|
+
: `; embedding similarity ${similarity.toFixed(3)}`;
|
|
421
|
+
return `Analytics ${kind}; retrieval rank ${retrievalRank}${similarityText}; ${trust}. ${candidateEmbeddingSummary(candidate)}`.slice(
|
|
422
|
+
0,
|
|
423
|
+
MAX_EMBEDDING_SUMMARY_CHARS,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
type RankedCandidate = {
|
|
428
|
+
candidate: AnalyticsQueryCatalogCandidate;
|
|
429
|
+
similarity?: number;
|
|
430
|
+
lexicalScore?: number;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
function emptyPromptReferences(): AnalyticsPromptReferences {
|
|
434
|
+
return { jevPromptCandidates: [], jevFallbackCandidateIds: [] };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function beforeDeadline<T>(
|
|
438
|
+
work: (signal: AbortSignal) => Promise<T>,
|
|
439
|
+
deadlineAt: number,
|
|
440
|
+
): Promise<{ status: "completed"; value: T } | { status: "expired" }> {
|
|
441
|
+
const remaining = deadlineAt - Date.now();
|
|
442
|
+
if (remaining <= 0) return { status: "expired" };
|
|
443
|
+
const controller = new AbortController();
|
|
444
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
445
|
+
const result = await Promise.race([
|
|
446
|
+
Promise.resolve()
|
|
447
|
+
.then(() => work(controller.signal))
|
|
448
|
+
.then((value) => ({ status: "completed" as const, value })),
|
|
449
|
+
new Promise<{ status: "expired" }>((resolve) => {
|
|
450
|
+
timeout = setTimeout(() => {
|
|
451
|
+
controller.abort();
|
|
452
|
+
resolve({ status: "expired" });
|
|
453
|
+
}, remaining);
|
|
454
|
+
}),
|
|
455
|
+
]);
|
|
456
|
+
if (timeout) clearTimeout(timeout);
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export async function retrieveAnalyticsPromptReferences(input: {
|
|
461
|
+
request: string;
|
|
462
|
+
email: string;
|
|
463
|
+
orgId: string | null;
|
|
464
|
+
deadlineAt?: number;
|
|
465
|
+
}): Promise<AnalyticsPromptReferences> {
|
|
466
|
+
const deadlineAt = input.deadlineAt ?? Date.now() + 1_300;
|
|
467
|
+
let cacheAllowed = true;
|
|
468
|
+
let searchResults: AnalyticsQueryCatalogCandidate[];
|
|
469
|
+
try {
|
|
470
|
+
const search = await beforeDeadline(
|
|
471
|
+
(signal) =>
|
|
472
|
+
searchAnalyticsQueryCatalog({
|
|
473
|
+
search: input.request,
|
|
474
|
+
email: input.email,
|
|
475
|
+
orgId: input.orgId,
|
|
476
|
+
limit: CATALOG_CANDIDATE_LIMIT,
|
|
477
|
+
signal,
|
|
478
|
+
}),
|
|
479
|
+
deadlineAt,
|
|
480
|
+
);
|
|
481
|
+
if (search.status === "expired") {
|
|
482
|
+
console.warn(
|
|
483
|
+
"[analytics] Reference catalog exceeded the context budget.",
|
|
484
|
+
);
|
|
485
|
+
return emptyPromptReferences();
|
|
486
|
+
}
|
|
487
|
+
searchResults = search.value.candidates;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.warn(
|
|
490
|
+
"[analytics] Reference catalog unavailable; continuing without preload.",
|
|
491
|
+
error instanceof Error ? error.message : "unknown error",
|
|
492
|
+
);
|
|
493
|
+
return emptyPromptReferences();
|
|
494
|
+
}
|
|
495
|
+
if (searchResults.length === 0) {
|
|
496
|
+
return emptyPromptReferences();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
let ranked: RankedCandidate[] = searchResults.map((candidate) => ({
|
|
500
|
+
candidate,
|
|
501
|
+
}));
|
|
502
|
+
try {
|
|
503
|
+
const semanticRanking = await beforeDeadline(
|
|
504
|
+
(signal) =>
|
|
505
|
+
rankWithEmbeddings(
|
|
506
|
+
input.request,
|
|
507
|
+
searchResults,
|
|
508
|
+
() => cacheAllowed,
|
|
509
|
+
deadlineAt,
|
|
510
|
+
signal,
|
|
511
|
+
),
|
|
512
|
+
deadlineAt,
|
|
513
|
+
);
|
|
514
|
+
if (semanticRanking.status === "completed") {
|
|
515
|
+
ranked = semanticRanking.value;
|
|
516
|
+
} else {
|
|
517
|
+
cacheAllowed = false;
|
|
518
|
+
console.warn(
|
|
519
|
+
"[analytics] Embedding ranking exceeded the context budget; keeping catalog order.",
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
cacheAllowed = false;
|
|
524
|
+
console.warn(
|
|
525
|
+
"[analytics] Semantic reference ranking unavailable; keeping catalog ranking.",
|
|
526
|
+
error instanceof Error ? error.message : "unknown error",
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const jevPromptCandidates = ranked
|
|
531
|
+
.slice(0, PROMPT_CANDIDATE_LIMIT)
|
|
532
|
+
.map(({ candidate, similarity }, index) => {
|
|
533
|
+
const id = `analytics-reference-${index + 1}`;
|
|
534
|
+
return {
|
|
535
|
+
id,
|
|
536
|
+
description: jevDescription(candidate, index + 1, similarity),
|
|
537
|
+
metadata: {
|
|
538
|
+
kind: "analytics-reference",
|
|
539
|
+
referenceType: candidate.kind,
|
|
540
|
+
retrievalRank: String(index + 1),
|
|
541
|
+
...(similarity === undefined
|
|
542
|
+
? {}
|
|
543
|
+
: { similarity: similarity.toFixed(3) }),
|
|
544
|
+
},
|
|
545
|
+
name: candidateName(candidate),
|
|
546
|
+
scope: "analytics-catalog",
|
|
547
|
+
content: candidateContent(candidate),
|
|
548
|
+
};
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
jevPromptCandidates,
|
|
553
|
+
jevFallbackCandidateIds: jevPromptCandidates
|
|
554
|
+
.slice(0, FALLBACK_CANDIDATE_LIMIT)
|
|
555
|
+
.map((candidate) => candidate.id),
|
|
556
|
+
};
|
|
557
|
+
}
|