@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.
Files changed (233) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +10 -0
  3. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +1 -1
  4. package/corpus/templates/analytics/AGENTS.md +11 -22
  5. package/corpus/templates/analytics/actions/list-data-dictionary.ts +7 -9
  6. package/corpus/templates/analytics/actions/search-analytics-query-catalog.ts +1 -1
  7. package/corpus/templates/analytics/app/components/AnalyticsReviewArtifactPreview.tsx +201 -0
  8. package/corpus/templates/analytics/app/i18n/en-US.ts +2 -0
  9. package/corpus/templates/analytics/app/pages/Settings.tsx +50 -3
  10. package/corpus/templates/analytics/changelog/2026-09-25-first-run-onboarding-records-setup-choices-and-builder-conne.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-09-25-human-review-previews-analytics-dashboards-as-real-charts.md +6 -0
  12. package/corpus/templates/analytics/changelog/2026-09-25-org-admins-can-review-agent-runs-from-settings.md +6 -0
  13. package/corpus/templates/analytics/changelog/2026-09-25-proactive-query-context.md +6 -0
  14. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +98 -2
  15. package/corpus/templates/analytics/server/lib/analytics-agent-context.ts +557 -0
  16. package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +217 -46
  17. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +2 -2
  18. package/corpus/templates/analytics/server/lib/dashboards-store.ts +39 -5
  19. package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +4 -0
  20. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +268 -2
  21. package/corpus/templates/analytics/server/plugins/agent-chat.ts +44 -9
  22. package/corpus/templates/calendar/AGENTS.md +5 -0
  23. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  24. package/corpus/templates/calendar/actions/get-event-rules-status.ts +47 -0
  25. package/corpus/templates/calendar/actions/list-events.ts +50 -4
  26. package/corpus/templates/calendar/actions/undo-calendar-event-rule.ts +229 -0
  27. package/corpus/templates/calendar/actions/update-settings.ts +8 -0
  28. package/corpus/templates/calendar/app/i18n/en-US.ts +27 -0
  29. package/corpus/templates/calendar/app/pages/Settings.tsx +196 -1
  30. package/corpus/templates/calendar/changelog/2026-09-25-calendar-feeds-with-the-same-url-keep-their-own-names-and-co.md +6 -0
  31. package/corpus/templates/calendar/changelog/2026-09-25-calendar-invitation-rules-surface-expired-connections-and-un.md +6 -0
  32. package/corpus/templates/calendar/changelog/2026-09-25-restore-locally-hidden-events-from-activity-after-disconnect.md +6 -0
  33. package/corpus/templates/calendar/server/lib/calendar-settings.ts +44 -10
  34. package/corpus/templates/calendar/server/lib/google-api.ts +15 -1
  35. package/corpus/templates/calendar/server/lib/google-calendar.ts +1 -1
  36. package/corpus/templates/calendar/server/plugins/event-rules.ts +628 -0
  37. package/corpus/templates/calendar/shared/api.ts +13 -0
  38. package/corpus/templates/calendar/shared/settings.ts +71 -1
  39. package/corpus/templates/clips/actions/create-recording.ts +83 -2
  40. package/corpus/templates/clips/actions/finalize-recording.ts +610 -110
  41. package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -0
  42. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -0
  43. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +7 -0
  44. package/corpus/templates/clips/actions/lib/loom-import-job.ts +15 -1
  45. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +126 -26
  46. package/corpus/templates/clips/app/hooks/use-drop-video-upload.ts +75 -15
  47. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +5 -1
  48. package/corpus/templates/clips/app/routes/record.tsx +134 -40
  49. package/corpus/templates/clips/chrome-extension/src/background.ts +3 -0
  50. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +73 -16
  51. package/corpus/templates/clips/desktop/src/lib/recorder.ts +188 -18
  52. package/corpus/templates/clips/desktop/src/lib/recording-request.ts +1 -0
  53. package/corpus/templates/clips/server/db/schema.ts +4 -0
  54. package/corpus/templates/clips/server/jobs/media-verification.ts +78 -4
  55. package/corpus/templates/clips/server/jobs/recording-failure-backfill.ts +142 -0
  56. package/corpus/templates/clips/server/lib/media-verification-state.ts +29 -0
  57. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +2 -0
  58. package/corpus/templates/clips/server/lib/recording-failures.ts +111 -0
  59. package/corpus/templates/clips/server/lib/recordings.ts +6 -1
  60. package/corpus/templates/clips/server/lib/s3-upload-provider.ts +8 -3
  61. package/corpus/templates/clips/server/lib/upload-lease.ts +25 -6
  62. package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
  63. package/corpus/templates/clips/server/plugins/db.ts +18 -0
  64. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +48 -5
  65. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +322 -31
  66. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +223 -61
  67. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +62 -4
  68. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +74 -7
  69. package/corpus/templates/clips/shared/recording-core.ts +27 -0
  70. package/corpus/templates/design/actions/get-design.ts +35 -6
  71. package/corpus/templates/mail/.agents/skills/inbox-automations/SKILL.md +4 -0
  72. package/corpus/templates/mail/actions/consolidate-ai-filter-rules.ts +41 -0
  73. package/corpus/templates/mail/actions/get-ai-priority.ts +48 -8
  74. package/corpus/templates/mail/actions/record-ai-priority-feedback.ts +69 -0
  75. package/corpus/templates/mail/actions/update-mail-preferences.ts +2 -0
  76. package/corpus/templates/mail/app/components/email/EmailList.tsx +179 -32
  77. package/corpus/templates/mail/app/components/email/EmailListItem.tsx +97 -9
  78. package/corpus/templates/mail/app/components/email/EmailThread.tsx +55 -1
  79. package/corpus/templates/mail/app/components/email/ImportanceFeedbackMenu.tsx +52 -0
  80. package/corpus/templates/mail/app/components/email/compose-autocomplete.ts +2 -1
  81. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +219 -188
  82. package/corpus/templates/mail/app/components/onboarding/AiInboxSetup.tsx +338 -0
  83. package/corpus/templates/mail/app/components/settings/AiFilterSection.tsx +597 -781
  84. package/corpus/templates/mail/app/components/settings/AiRulePromptField.tsx +37 -0
  85. package/corpus/templates/mail/app/global.css +42 -26
  86. package/corpus/templates/mail/app/hooks/use-ai-priority-feedback.ts +43 -0
  87. package/corpus/templates/mail/app/hooks/use-automations.ts +23 -0
  88. package/corpus/templates/mail/app/i18n/en-US.ts +58 -3
  89. package/corpus/templates/mail/app/pages/InboxPage.tsx +47 -10
  90. package/corpus/templates/mail/changelog/2026-09-25-editing-importance-rules-preserves-disabled-instructions-and.md +6 -0
  91. package/corpus/templates/mail/changelog/2026-09-25-handle-astral-unicode-letters-in-autocomplete-word-boundarie.md +6 -0
  92. package/corpus/templates/mail/changelog/2026-09-25-jev-priority-feedback-and-inbox-settings.md +6 -0
  93. package/corpus/templates/mail/changelog/2026-09-25-keep-existing-ai-filter-rules-intact-when-saving-a-prompt.md +6 -0
  94. package/corpus/templates/mail/server/lib/automation-engine.ts +21 -12
  95. package/corpus/templates/mail/server/lib/automations.ts +100 -1
  96. package/corpus/templates/mail/shared/types.ts +3 -0
  97. package/corpus/templates/plan/app/i18n/en-US.ts +6 -0
  98. package/corpus/templates/plan/app/lib/plan-invite-suggestion.ts +43 -0
  99. package/corpus/templates/plan/app/pages/PlansPage.tsx +249 -4
  100. package/corpus/templates/slides/actions/create-deck.ts +11 -1
  101. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +117 -1
  102. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +373 -330
  103. package/corpus/templates/slides/app/components/design-system/builder-index-response.ts +36 -5
  104. package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +21 -53
  105. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +347 -671
  106. package/corpus/templates/slides/app/components/editor/bullet-editing.ts +55 -8
  107. package/corpus/templates/slides/app/components/editor/in-place-text-session.ts +2082 -0
  108. package/corpus/templates/slides/app/components/editor/list-editing.ts +174 -16
  109. package/corpus/templates/slides/app/components/editor/rich-text-selection.ts +497 -62
  110. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +102 -20
  111. package/corpus/templates/slides/app/context/DeckContext.tsx +9 -4
  112. package/corpus/templates/slides/app/global.css +7 -219
  113. package/corpus/templates/slides/app/i18n/en-US.ts +3 -0
  114. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +7 -0
  115. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +203 -1
  116. package/corpus/templates/slides/app/lib/slide-source-map.ts +43 -0
  117. package/corpus/templates/slides/app/pages/DeckEditor.tsx +182 -4
  118. package/corpus/templates/slides/app/routes/settings.tsx +33 -2
  119. package/corpus/templates/slides/changelog/2026-09-25-improve-slides-design-system-setup-feedback.md +6 -0
  120. package/corpus/templates/slides/changelog/2026-09-25-org-admins-can-review-agent-runs-from-settings.md +6 -0
  121. package/corpus/templates/slides/changelog/2026-09-25-text-on-a-slide-is-now-edited-in-place-clicking-into-it-chan.md +6 -0
  122. package/corpus/templates/slides/package.json +0 -12
  123. package/corpus/templates/slides/scripts/edit-fidelity/README.md +4 -2
  124. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +12 -3
  125. package/corpus/templates/slides/vite.config.ts +1 -10
  126. package/dist/agent/jev-tool-prefetch.d.ts +29 -3
  127. package/dist/agent/jev-tool-prefetch.js +161 -34
  128. package/dist/agent/production-agent.d.ts +11 -0
  129. package/dist/agent/production-agent.js +39 -8
  130. package/dist/app-config/runtime.d.ts +1 -0
  131. package/dist/app-config/runtime.js +4 -0
  132. package/dist/app-config/schema.d.ts +1 -0
  133. package/dist/client/AgentSidebar.d.ts +3 -1
  134. package/dist/client/AgentSidebar.js +8 -12
  135. package/dist/client/analytics/index.d.ts +1 -1
  136. package/dist/client/analytics/index.js +1 -1
  137. package/dist/client/analytics.d.ts +2 -0
  138. package/dist/client/analytics.js +9 -0
  139. package/dist/client/api-path.js +5 -2
  140. package/dist/client/chat/run-recovery.d.ts +0 -3
  141. package/dist/client/chat/run-recovery.js +13 -23
  142. package/dist/client/index.d.ts +1 -1
  143. package/dist/client/index.js +1 -1
  144. package/dist/client/observability/ObservabilityDashboard.d.ts +3 -0
  145. package/dist/client/observability/ObservabilityDashboard.js +366 -71
  146. package/dist/client/observability/ObservabilityReviewSummaryButton.d.ts +4 -1
  147. package/dist/client/observability/ObservabilityReviewSummaryButton.js +13 -9
  148. package/dist/client/observability/OutputPreview.d.ts +9 -4
  149. package/dist/client/observability/OutputPreview.js +230 -52
  150. package/dist/client/onboarding/FirstRunOnboarding.js +67 -4
  151. package/dist/client/onboarding/use-onboarding.js +2 -0
  152. package/dist/client/sharing/ShareButton.d.ts +2 -0
  153. package/dist/client/sharing/ShareButton.js +1 -0
  154. package/dist/client/sharing/useShareButtonController.d.ts +1 -0
  155. package/dist/client/sharing/useShareButtonController.js +29 -8
  156. package/dist/collab/awareness.d.ts +2 -2
  157. package/dist/collab/struct-routes.d.ts +1 -1
  158. package/dist/deploy/build.js +9 -1
  159. package/dist/embeddings/index.d.ts +3 -1
  160. package/dist/embeddings/index.js +20 -10
  161. package/dist/jobs/sweep-hooks.d.ts +7 -0
  162. package/dist/jobs/sweep-hooks.js +28 -0
  163. package/dist/localization/default-messages.d.ts +1 -0
  164. package/dist/localization/default-messages.js +1 -0
  165. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  166. package/dist/notifications/routes.d.ts +1 -1
  167. package/dist/observability/actions/authorization.d.ts +1 -0
  168. package/dist/observability/actions/authorization.js +22 -0
  169. package/dist/observability/actions/get-observability-review-detail.d.ts +5 -0
  170. package/dist/observability/actions/get-observability-review-detail.js +10 -2
  171. package/dist/observability/actions/get-observability-review-summary-source.d.ts +2 -0
  172. package/dist/observability/actions/get-observability-review-summary-source.js +3 -2
  173. package/dist/observability/actions/save-observability-instruction-update.js +2 -1
  174. package/dist/observability/actions/save-observability-review-summary.js +21 -10
  175. package/dist/observability/migrations.js +25 -0
  176. package/dist/observability/reviews.d.ts +3 -1
  177. package/dist/observability/reviews.js +410 -53
  178. package/dist/observability/routes.d.ts +6 -6
  179. package/dist/observability/store.d.ts +15 -0
  180. package/dist/observability/store.js +131 -11
  181. package/dist/observability/types.d.ts +18 -0
  182. package/dist/org/handlers.d.ts +1 -1
  183. package/dist/org/index.d.ts +1 -1
  184. package/dist/progress/routes.d.ts +1 -1
  185. package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
  186. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  187. package/dist/secrets/routes.d.ts +3 -3
  188. package/dist/server/agent-chat/plugin-options.d.ts +4 -20
  189. package/dist/server/agent-chat/prompt-resources.d.ts +16 -0
  190. package/dist/server/agent-chat/prompt-resources.js +385 -22
  191. package/dist/server/agent-chat-plugin.d.ts +3 -1
  192. package/dist/server/agent-chat-plugin.js +149 -27
  193. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  194. package/dist/server/index.d.ts +3 -0
  195. package/dist/server/index.js +3 -0
  196. package/dist/server/prompts/framework-core-compact.js +1 -1
  197. package/dist/server/prompts/framework-core.js +1 -1
  198. package/dist/server/realtime-token.d.ts +1 -1
  199. package/dist/server/request-context.d.ts +6 -3
  200. package/dist/server/transcribe-voice.d.ts +1 -1
  201. package/dist/settings/org-settings.d.ts +4 -2
  202. package/dist/settings/org-settings.js +3 -3
  203. package/dist/settings/store.d.ts +3 -1
  204. package/dist/settings/store.js +8 -3
  205. package/dist/styles/agent-native.css +5 -0
  206. package/dist/templates/default/app/i18n/ar-SA.ts +1 -0
  207. package/dist/templates/default/app/i18n/de-DE.ts +1 -0
  208. package/dist/templates/default/app/i18n/en-US.ts +1 -0
  209. package/dist/templates/default/app/i18n/es-ES.ts +1 -0
  210. package/dist/templates/default/app/i18n/fr-FR.ts +1 -0
  211. package/dist/templates/default/app/i18n/hi-IN.ts +1 -0
  212. package/dist/templates/default/app/i18n/ja-JP.ts +1 -0
  213. package/dist/templates/default/app/i18n/ko-KR.ts +1 -0
  214. package/dist/templates/default/app/i18n/pt-BR.ts +1 -0
  215. package/dist/templates/default/app/i18n/zh-CN.ts +1 -0
  216. package/dist/templates/default/app/i18n/zh-TW.ts +1 -0
  217. package/dist/tracking/registry.js +11 -4
  218. package/package.json +4 -3
  219. package/src/templates/default/app/i18n/ar-SA.ts +1 -0
  220. package/src/templates/default/app/i18n/de-DE.ts +1 -0
  221. package/src/templates/default/app/i18n/en-US.ts +1 -0
  222. package/src/templates/default/app/i18n/es-ES.ts +1 -0
  223. package/src/templates/default/app/i18n/fr-FR.ts +1 -0
  224. package/src/templates/default/app/i18n/hi-IN.ts +1 -0
  225. package/src/templates/default/app/i18n/ja-JP.ts +1 -0
  226. package/src/templates/default/app/i18n/ko-KR.ts +1 -0
  227. package/src/templates/default/app/i18n/pt-BR.ts +1 -0
  228. package/src/templates/default/app/i18n/zh-CN.ts +1 -0
  229. package/src/templates/default/app/i18n/zh-TW.ts +1 -0
  230. package/corpus/templates/slides/app/components/editor/SlideBubbleMenu.tsx +0 -488
  231. package/corpus/templates/slides/app/components/editor/SlideInlineEditor.tsx +0 -589
  232. package/corpus/templates/slides/app/components/editor/SlideRichTextEditor.tsx +0 -1347
  233. 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
+ }