@agent-native/core 0.80.0 → 0.80.1
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 +2 -2
- package/corpus/core/CHANGELOG.md +8 -0
- package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/corpus/core/docs/content/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/template-design.mdx +14 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/handlers.ts +1 -1
- package/corpus/core/src/brand-kit/index.ts +1 -1
- package/corpus/core/src/cli/skills.ts +18 -0
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
- package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
- package/corpus/core/src/extensions/path.ts +1 -4
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
- package/corpus/core/src/mcp/build-server.ts +1 -1
- package/corpus/core/src/org/handlers.ts +5 -5
- package/corpus/core/src/org/plugin.ts +2 -2
- package/corpus/core/src/scripts/call-agent.ts +1 -1
- package/corpus/core/src/scripts/db/safety.ts +1 -1
- package/corpus/core/src/server/auth.ts +3 -0
- package/corpus/core/src/server/builder-design-systems.ts +308 -0
- package/corpus/core/src/server/core-routes-plugin.ts +29 -3
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/styles/agent-native.css +14 -7
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
- package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +37 -0
- package/corpus/templates/analytics/app/i18n-data.ts +404 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +49 -26
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +137 -36
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
- package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +49 -26
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
- package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
- package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +29 -8
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
- package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
- package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
- package/corpus/templates/content/shared/document-export.ts +1 -1
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +32 -5
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/design/actions/update-file.ts +9 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +33 -75
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +72 -9
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +41 -39
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1029 -249
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +450 -101
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +978 -154
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -9
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +133 -66
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1234 -344
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +144 -94
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +301 -72
- package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +876 -286
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +30 -7
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +212 -155
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
- package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +487 -107
- package/corpus/templates/design/app/components/design/inspector/index.ts +27 -0
- package/corpus/templates/design/app/global.css +29 -3
- package/corpus/templates/design/app/i18n/zh-TW.ts +13 -0
- package/corpus/templates/design/app/i18n-data.ts +239 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +1023 -353
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
- package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
- package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
- package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +209 -9
- package/corpus/templates/design/shared/html-content.ts +21 -0
- package/corpus/templates/design/shared/pen-path.ts +207 -0
- package/corpus/templates/mail/app/lib/utils.ts +2 -2
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
- package/corpus/templates/slides/AGENTS.md +3 -2
- package/corpus/templates/slides/actions/import-file.ts +26 -17
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
- package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
- package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
- package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
- package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
- package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
- package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
- package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
- package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
- package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
- package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
- package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
- package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
- package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
- package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
- package/dist/application-state/handlers.js +1 -1
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/brand-kit/index.d.ts +1 -1
- package/dist/brand-kit/index.js +1 -1
- package/dist/brand-kit/index.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +18 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +1 -1
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/extensions/path.d.ts +1 -1
- package/dist/extensions/path.d.ts.map +1 -1
- package/dist/extensions/path.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +1 -1
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/build-server.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/handlers.js +5 -5
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/plugin.js +2 -2
- package/dist/org/plugin.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/scripts/call-agent.js +1 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/db/safety.js +1 -1
- package/dist/scripts/db/safety.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +3 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-design-systems.d.ts +28 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -0
- package/dist/server/builder-design-systems.js +217 -0
- package/dist/server/builder-design-systems.js.map +1 -0
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +20 -3
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +14 -7
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/docs/content/skills-guide.mdx +5 -0
- package/docs/content/template-design.mdx +14 -1
- package/package.json +1 -1
- package/corpus/templates/design/actions/import-figma-file.ts +0 -166
- package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
|
@@ -69,9 +69,33 @@ const MCP_DATA_SOURCE_TOKENS = [
|
|
|
69
69
|
"stripe",
|
|
70
70
|
];
|
|
71
71
|
|
|
72
|
+
function normalizeActionToolName(name: string): string {
|
|
73
|
+
return name
|
|
74
|
+
.trim()
|
|
75
|
+
.toLowerCase()
|
|
76
|
+
.replace(/[\s_]+/g, "-")
|
|
77
|
+
.replace(/-+/g, "-");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isToolName(name: string, expected: string): boolean {
|
|
81
|
+
return normalizeActionToolName(name) === expected;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isDataQueryActionName(name: string): boolean {
|
|
85
|
+
return DATA_QUERY_ACTIONS.has(normalizeActionToolName(name));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isCorpusSourceActionName(name: string): boolean {
|
|
89
|
+
return CORPUS_SOURCE_ACTIONS.has(normalizeActionToolName(name));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isCorpusReductionActionName(name: string): boolean {
|
|
93
|
+
return CORPUS_REDUCTION_ACTIONS.has(normalizeActionToolName(name));
|
|
94
|
+
}
|
|
95
|
+
|
|
72
96
|
function isMcpDataSourceTool(name: string): boolean {
|
|
73
|
-
if (!name.startsWith("mcp__")) return false;
|
|
74
97
|
const normalized = name.toLowerCase();
|
|
98
|
+
if (!normalized.startsWith("mcp__")) return false;
|
|
75
99
|
return MCP_DATA_SOURCE_TOKENS.some((token) => normalized.includes(token));
|
|
76
100
|
}
|
|
77
101
|
|
|
@@ -94,13 +118,13 @@ function getRunCodeBridgeToolNames(content: string | undefined): string[] {
|
|
|
94
118
|
|
|
95
119
|
function hasRunCodeDataQueryAttempt(content: string | undefined): boolean {
|
|
96
120
|
return getRunCodeBridgeToolNames(content).some(
|
|
97
|
-
(name) =>
|
|
121
|
+
(name) => isDataQueryActionName(name) || isMcpDataSourceTool(name),
|
|
98
122
|
);
|
|
99
123
|
}
|
|
100
124
|
|
|
101
125
|
function hasRunCodeCorpusWorkflowAttempt(content: string | undefined): boolean {
|
|
102
126
|
return getRunCodeBridgeToolNames(content).some(
|
|
103
|
-
(name) =>
|
|
127
|
+
(name) => isCorpusSourceActionName(name) || isCorpusCapableMcpTool(name),
|
|
104
128
|
);
|
|
105
129
|
}
|
|
106
130
|
|
|
@@ -208,7 +232,7 @@ const UNSUPPORTED_RESULT_CLAIM =
|
|
|
208
232
|
/(?:\b\d[\d,.]*(?:\.\d+)?\s*(?:%|percent|users?|customers?|accounts?|sessions?|events?|deals?|tickets?|issues?|calls?|messages?|signups?|pageviews?)\b|\$\s*\d|\b(?:data|query|results?)\s+(?:shows?|showed|indicates?|returned|found)\b|\b(?:i found|the top|the bottom|highest|lowest|increased|decreased|grew|declined|converted|churned|retained|averaged|total(?:ed)?|count(?:ed)?)\b)/i;
|
|
209
233
|
|
|
210
234
|
const SAFE_NO_DATA_RESPONSE =
|
|
211
|
-
/\b(?:i can't|i cannot|can't retrieve|cannot retrieve|couldn't retrieve|unable to retrieve|don't have access|do not have access|not configured|missing credentials?|need (?:a|the)? ?data source|need to know which source|which source|which data source|clarify|can you|once (?:that'?s|it is) (?:connected|configured|available)|no data source|without a successful|before (?:i|we) can (?:calculate|report|answer|analyze)|i need to query)\b/i;
|
|
235
|
+
/\b(?:i can't|i cannot|can't retrieve|cannot retrieve|couldn't retrieve|unable to retrieve|don't have access|do not have access|not configured|missing credentials?|need (?:a|the)? ?data source|need to know which source|which source|which data source|clarify|can you|once (?:that'?s|it is) (?:connected|configured|available)|no data source|without a successful|query failed|source query failed|sql failed|error running|before (?:i|we) can (?:calculate|report|answer|analyze)|i need to query)\b/i;
|
|
212
236
|
|
|
213
237
|
export function isSafeNoDataAnalyticsResponse(text: string): boolean {
|
|
214
238
|
const trimmed = text.trim();
|
|
@@ -358,10 +382,10 @@ export function hasIncompleteDataEvidence(
|
|
|
358
382
|
const name = String(result.name ?? "");
|
|
359
383
|
if (
|
|
360
384
|
name &&
|
|
361
|
-
!
|
|
385
|
+
!isDataQueryActionName(name) &&
|
|
362
386
|
!isMcpDataSourceTool(name) &&
|
|
363
|
-
name
|
|
364
|
-
name
|
|
387
|
+
!isToolName(name, "run-code") &&
|
|
388
|
+
!isToolName(name, "provider-api-request")
|
|
365
389
|
) {
|
|
366
390
|
return false;
|
|
367
391
|
}
|
|
@@ -419,11 +443,50 @@ export function hasDataQueryAttempt(
|
|
|
419
443
|
if (result.isError) return false;
|
|
420
444
|
if (isProviderErrorOnlyContent(result.content)) return false;
|
|
421
445
|
const name = String(result.name ?? "");
|
|
422
|
-
if (name
|
|
423
|
-
|
|
446
|
+
if (isToolName(name, "run-code")) {
|
|
447
|
+
return hasRunCodeDataQueryAttempt(result.content);
|
|
448
|
+
}
|
|
449
|
+
return isDataQueryActionName(name) || isMcpDataSourceTool(name);
|
|
424
450
|
});
|
|
425
451
|
}
|
|
426
452
|
|
|
453
|
+
function isFailedDataQueryAttempt(result: {
|
|
454
|
+
name?: string;
|
|
455
|
+
isError?: boolean;
|
|
456
|
+
content?: string;
|
|
457
|
+
}): boolean {
|
|
458
|
+
const name = String(result.name ?? "");
|
|
459
|
+
const isDataQuery =
|
|
460
|
+
isDataQueryActionName(name) ||
|
|
461
|
+
isMcpDataSourceTool(name) ||
|
|
462
|
+
(isToolName(name, "run-code") &&
|
|
463
|
+
hasRunCodeDataQueryAttempt(result.content));
|
|
464
|
+
if (!isDataQuery) return false;
|
|
465
|
+
return result.isError === true || isProviderErrorOnlyContent(result.content);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function compactToolFailure(content: string | undefined): string {
|
|
469
|
+
const trimmed = String(content ?? "").trim();
|
|
470
|
+
if (!trimmed) return "the tool returned an error result without details";
|
|
471
|
+
const max = 900;
|
|
472
|
+
return trimmed.length > max ? `${trimmed.slice(0, max)}...` : trimmed;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export function failedDataQueryAttemptMessage(
|
|
476
|
+
toolResults:
|
|
477
|
+
| Array<{ name?: string; isError?: boolean; content?: string }>
|
|
478
|
+
| undefined,
|
|
479
|
+
): string | null {
|
|
480
|
+
const failed = (toolResults ?? []).find(isFailedDataQueryAttempt);
|
|
481
|
+
if (!failed) return null;
|
|
482
|
+
const name = String(failed.name ?? "data-source query");
|
|
483
|
+
return (
|
|
484
|
+
`I did try \`${name}\`, but it did not return a successful data result: ` +
|
|
485
|
+
compactToolFailure(failed.content) +
|
|
486
|
+
"\n\nI need to fix and rerun that query, or report that exact source error instead of giving numbers."
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
427
490
|
export function hasCorpusWorkflowAttempt(
|
|
428
491
|
toolResults:
|
|
429
492
|
| Array<{ name?: string; isError?: boolean; content?: string }>
|
|
@@ -433,8 +496,8 @@ export function hasCorpusWorkflowAttempt(
|
|
|
433
496
|
if (result.isError) return false;
|
|
434
497
|
if (isProviderErrorOnlyContent(result.content)) return false;
|
|
435
498
|
const name = String(result.name ?? "");
|
|
436
|
-
if (
|
|
437
|
-
if (name
|
|
499
|
+
if (isCorpusSourceActionName(name)) return true;
|
|
500
|
+
if (isToolName(name, "run-code")) {
|
|
438
501
|
return hasRunCodeCorpusWorkflowAttempt(result.content);
|
|
439
502
|
}
|
|
440
503
|
|
|
@@ -453,10 +516,7 @@ export function hasFailedCorpusWorkflowEvidence(
|
|
|
453
516
|
): boolean {
|
|
454
517
|
return (toolResults ?? []).some((result) => {
|
|
455
518
|
const name = String(result.name ?? "");
|
|
456
|
-
if (
|
|
457
|
-
!CORPUS_SOURCE_ACTIONS.has(name) &&
|
|
458
|
-
!CORPUS_REDUCTION_ACTIONS.has(name)
|
|
459
|
-
) {
|
|
519
|
+
if (!isCorpusSourceActionName(name) && !isCorpusReductionActionName(name)) {
|
|
460
520
|
return false;
|
|
461
521
|
}
|
|
462
522
|
if (result.isError) return true;
|
|
@@ -576,19 +636,20 @@ function actionEvidenceTextForSourceRecords(result: {
|
|
|
576
636
|
content?: string;
|
|
577
637
|
}): string {
|
|
578
638
|
const name = String(result.name ?? "");
|
|
639
|
+
const normalizedName = normalizeActionToolName(name);
|
|
579
640
|
const content = String(result.content ?? "");
|
|
580
641
|
const parsed = tryParseJsonContent(content);
|
|
581
642
|
|
|
582
|
-
if (
|
|
643
|
+
if (normalizedName === "provider-corpus-job") {
|
|
583
644
|
return corpusJobSourceEvidenceText(parsed);
|
|
584
645
|
}
|
|
585
|
-
if (
|
|
646
|
+
if (normalizedName === "provider-api-request") {
|
|
586
647
|
return providerRequestEvidenceText(parsed);
|
|
587
648
|
}
|
|
588
|
-
if (
|
|
649
|
+
if (normalizedName === "query-staged-dataset") {
|
|
589
650
|
return queryStagedDatasetEvidenceText(parsed);
|
|
590
651
|
}
|
|
591
|
-
if (
|
|
652
|
+
if (normalizedName === "gong-calls") {
|
|
592
653
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
593
654
|
return "";
|
|
594
655
|
}
|
|
@@ -600,7 +661,7 @@ function actionEvidenceTextForSourceRecords(result: {
|
|
|
600
661
|
transcripts: record.transcripts,
|
|
601
662
|
});
|
|
602
663
|
}
|
|
603
|
-
if (
|
|
664
|
+
if (normalizedName === "run-code") {
|
|
604
665
|
return content;
|
|
605
666
|
}
|
|
606
667
|
if (isCorpusCapableMcpTool(name)) {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { listFileUploadProviders } from "@agent-native/core/file-upload";
|
|
2
|
+
import {
|
|
3
|
+
resolveHasBuilderPrivateKey,
|
|
4
|
+
runWithRequestContext,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Session replay chunk bytes are stored through the framework file-upload /
|
|
9
|
+
* private-blob path (Builder.io CDN by default, or an S3-compatible bucket).
|
|
10
|
+
* In production we require a configured provider — storing replay chunks inline
|
|
11
|
+
* in SQL is a local/dev-only fallback. This mirrors the Clips video-storage
|
|
12
|
+
* contract so replay capture fails loudly (and the UI can prompt setup) instead
|
|
13
|
+
* of silently recording empty, unplayable sessions.
|
|
14
|
+
*/
|
|
15
|
+
export const REPLAY_STORAGE_SETUP_REQUIRED_REASON =
|
|
16
|
+
"Session replay storage is not connected yet. Connect Builder.io or configure S3-compatible storage to record replays.";
|
|
17
|
+
|
|
18
|
+
function appDatabaseUrl(): string {
|
|
19
|
+
const appName = process.env.APP_NAME?.toUpperCase().replace(/-/g, "_");
|
|
20
|
+
if (appName) {
|
|
21
|
+
const appUrl = process.env[`${appName}_DATABASE_URL`];
|
|
22
|
+
if (appUrl) return appUrl;
|
|
23
|
+
}
|
|
24
|
+
return process.env.DATABASE_URL || process.env.NETLIFY_DATABASE_URL || "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isLikelyLocalDatabase(): boolean {
|
|
28
|
+
const url = appDatabaseUrl();
|
|
29
|
+
return url === "" || url.startsWith("file:") || !url.includes("://");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function requiresConfiguredReplayStorage(): boolean {
|
|
33
|
+
return process.env.NODE_ENV === "production" || !isLikelyLocalDatabase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReplayStorageResolveContext {
|
|
37
|
+
userEmail?: string;
|
|
38
|
+
orgId?: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether a durable upload provider (non-inline) is configured for the given
|
|
43
|
+
* owner/org scope. Iterates registered providers (e.g. S3) first, then falls
|
|
44
|
+
* back to a resolvable Builder.io private key. When a scope is supplied the
|
|
45
|
+
* check runs inside that user/org request context so org-scoped `app_secrets`
|
|
46
|
+
* credentials resolve — exactly the scope the anonymous replay-ingest path
|
|
47
|
+
* lacks on its own.
|
|
48
|
+
*/
|
|
49
|
+
export async function hasRequestReplayStorage(
|
|
50
|
+
context?: ReplayStorageResolveContext,
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
const resolve = async () => {
|
|
53
|
+
for (const provider of listFileUploadProviders()) {
|
|
54
|
+
if (provider.id === "builder") continue;
|
|
55
|
+
if (provider.isConfigured()) return true;
|
|
56
|
+
if (provider.isConfiguredForRequest) {
|
|
57
|
+
try {
|
|
58
|
+
if (await provider.isConfiguredForRequest()) return true;
|
|
59
|
+
} catch {
|
|
60
|
+
// Treat a failed scoped lookup as not configured.
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
return await resolveHasBuilderPrivateKey();
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (context?.userEmail) {
|
|
73
|
+
return runWithRequestContext(
|
|
74
|
+
{ userEmail: context.userEmail, orgId: context.orgId ?? undefined },
|
|
75
|
+
resolve,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return resolve();
|
|
79
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3-compatible file upload provider.
|
|
3
|
+
*
|
|
4
|
+
* Works with AWS S3, Cloudflare R2, DigitalOcean Spaces, MinIO, Backblaze B2,
|
|
5
|
+
* and any other S3-compatible object storage. Uses SigV4 signing via Web Crypto
|
|
6
|
+
* — no SDK dependency.
|
|
7
|
+
*
|
|
8
|
+
* Env vars (S3_* or R2_* prefix, first found wins):
|
|
9
|
+
* S3_BUCKET | R2_BUCKET — required
|
|
10
|
+
* S3_ACCESS_KEY_ID | R2_ACCESS_KEY_ID — required
|
|
11
|
+
* S3_SECRET_ACCESS_KEY | R2_SECRET_ACCESS_KEY — required
|
|
12
|
+
* S3_ENDPOINT | R2_ENDPOINT — required (e.g. https://s3.us-east-1.amazonaws.com
|
|
13
|
+
* or https://<acct>.r2.cloudflarestorage.com)
|
|
14
|
+
* S3_REGION | R2_REGION — optional, default "auto"
|
|
15
|
+
* S3_PUBLIC_BASE_URL | R2_PUBLIC_BASE_URL — optional (for public read URLs)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { FileUploadProvider } from "@agent-native/core/file-upload";
|
|
19
|
+
import { resolveSecret } from "@agent-native/core/server";
|
|
20
|
+
|
|
21
|
+
interface S3Config {
|
|
22
|
+
region: string;
|
|
23
|
+
bucket: string;
|
|
24
|
+
accessKeyId: string;
|
|
25
|
+
secretAccessKey: string;
|
|
26
|
+
endpoint: string;
|
|
27
|
+
publicBaseUrl: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cleanValue(value: string | null | undefined): string | undefined {
|
|
31
|
+
const cleaned = value?.trim();
|
|
32
|
+
return cleaned ? cleaned : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildS3Config(values: {
|
|
36
|
+
bucket?: string;
|
|
37
|
+
accessKeyId?: string;
|
|
38
|
+
secretAccessKey?: string;
|
|
39
|
+
endpoint?: string;
|
|
40
|
+
region?: string;
|
|
41
|
+
publicBaseUrl?: string;
|
|
42
|
+
}): S3Config | null {
|
|
43
|
+
const bucket = cleanValue(values.bucket);
|
|
44
|
+
const accessKeyId = cleanValue(values.accessKeyId);
|
|
45
|
+
const secretAccessKey = cleanValue(values.secretAccessKey);
|
|
46
|
+
const endpoint = cleanValue(values.endpoint);
|
|
47
|
+
if (!bucket || !accessKeyId || !secretAccessKey || !endpoint) return null;
|
|
48
|
+
return {
|
|
49
|
+
region: cleanValue(values.region) ?? "auto",
|
|
50
|
+
bucket,
|
|
51
|
+
accessKeyId,
|
|
52
|
+
secretAccessKey,
|
|
53
|
+
endpoint: endpoint.replace(/\/+$/, ""),
|
|
54
|
+
publicBaseUrl:
|
|
55
|
+
cleanValue(values.publicBaseUrl)?.replace(/\/+$/, "") ?? null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readS3EnvConfig(): S3Config | null {
|
|
60
|
+
const env = process.env;
|
|
61
|
+
return buildS3Config({
|
|
62
|
+
bucket: env.S3_BUCKET || env.R2_BUCKET,
|
|
63
|
+
accessKeyId: env.S3_ACCESS_KEY_ID || env.R2_ACCESS_KEY_ID,
|
|
64
|
+
secretAccessKey: env.S3_SECRET_ACCESS_KEY || env.R2_SECRET_ACCESS_KEY,
|
|
65
|
+
endpoint: env.S3_ENDPOINT || env.R2_ENDPOINT,
|
|
66
|
+
region: env.S3_REGION || env.R2_REGION,
|
|
67
|
+
publicBaseUrl: env.S3_PUBLIC_BASE_URL || env.R2_PUBLIC_BASE_URL,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function resolveS3Secret(primary: string, fallback: string) {
|
|
72
|
+
return (
|
|
73
|
+
cleanValue(await resolveSecret(primary).catch(() => null)) ??
|
|
74
|
+
cleanValue(await resolveSecret(fallback).catch(() => null))
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function readS3Config(): Promise<S3Config | null> {
|
|
79
|
+
return buildS3Config({
|
|
80
|
+
bucket: await resolveS3Secret("S3_BUCKET", "R2_BUCKET"),
|
|
81
|
+
accessKeyId: await resolveS3Secret("S3_ACCESS_KEY_ID", "R2_ACCESS_KEY_ID"),
|
|
82
|
+
secretAccessKey: await resolveS3Secret(
|
|
83
|
+
"S3_SECRET_ACCESS_KEY",
|
|
84
|
+
"R2_SECRET_ACCESS_KEY",
|
|
85
|
+
),
|
|
86
|
+
endpoint: await resolveS3Secret("S3_ENDPOINT", "R2_ENDPOINT"),
|
|
87
|
+
region: await resolveS3Secret("S3_REGION", "R2_REGION"),
|
|
88
|
+
publicBaseUrl: await resolveS3Secret(
|
|
89
|
+
"S3_PUBLIC_BASE_URL",
|
|
90
|
+
"R2_PUBLIC_BASE_URL",
|
|
91
|
+
),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── SigV4 helpers (Web Crypto, no SDK) ────────────────────────────────
|
|
96
|
+
|
|
97
|
+
async function hmac(key: ArrayBuffer, msg: string): Promise<ArrayBuffer> {
|
|
98
|
+
const k = await crypto.subtle.importKey(
|
|
99
|
+
"raw",
|
|
100
|
+
key,
|
|
101
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
102
|
+
false,
|
|
103
|
+
["sign"],
|
|
104
|
+
);
|
|
105
|
+
return crypto.subtle.sign("HMAC", k, new TextEncoder().encode(msg));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function sha256(data: Uint8Array): Promise<string> {
|
|
109
|
+
const ab = new ArrayBuffer(data.byteLength);
|
|
110
|
+
new Uint8Array(ab).set(data);
|
|
111
|
+
const buf = await crypto.subtle.digest("SHA-256", ab);
|
|
112
|
+
return toHex(buf);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function toHex(buf: ArrayBuffer): string {
|
|
116
|
+
const bytes = new Uint8Array(buf);
|
|
117
|
+
let out = "";
|
|
118
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
119
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function deriveSigningKey(
|
|
125
|
+
secret: string,
|
|
126
|
+
dateStamp: string,
|
|
127
|
+
region: string,
|
|
128
|
+
): Promise<ArrayBuffer> {
|
|
129
|
+
const kSecret = new TextEncoder().encode(`AWS4${secret}`);
|
|
130
|
+
const kDate = await hmac(kSecret.buffer as ArrayBuffer, dateStamp);
|
|
131
|
+
const kRegion = await hmac(kDate, region);
|
|
132
|
+
const kService = await hmac(kRegion, "s3");
|
|
133
|
+
return hmac(kService, "aws4_request");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function rfc3986(str: string): string {
|
|
137
|
+
return encodeURIComponent(str).replace(
|
|
138
|
+
/[!'()*]/g,
|
|
139
|
+
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function putObject(
|
|
144
|
+
cfg: S3Config,
|
|
145
|
+
key: string,
|
|
146
|
+
body: Uint8Array,
|
|
147
|
+
contentType: string,
|
|
148
|
+
): Promise<string> {
|
|
149
|
+
const now = new Date();
|
|
150
|
+
const amzDate =
|
|
151
|
+
now
|
|
152
|
+
.toISOString()
|
|
153
|
+
.replace(/[:-]|\.\d{3}/g, "")
|
|
154
|
+
.slice(0, 15) + "Z";
|
|
155
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
156
|
+
const credentialScope = `${dateStamp}/${cfg.region}/s3/aws4_request`;
|
|
157
|
+
|
|
158
|
+
const hostUrl = new URL(cfg.endpoint);
|
|
159
|
+
const host = hostUrl.host;
|
|
160
|
+
const canonicalUri = `/${cfg.bucket}/${key.split("/").map(rfc3986).join("/")}`;
|
|
161
|
+
|
|
162
|
+
const payloadHash = await sha256(body);
|
|
163
|
+
|
|
164
|
+
const headers: Record<string, string> = {
|
|
165
|
+
host,
|
|
166
|
+
"content-type": contentType,
|
|
167
|
+
"x-amz-content-sha256": payloadHash,
|
|
168
|
+
"x-amz-date": amzDate,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const signedHeaderKeys = Object.keys(headers).sort();
|
|
172
|
+
const signedHeaders = signedHeaderKeys.join(";");
|
|
173
|
+
const canonicalHeaders =
|
|
174
|
+
signedHeaderKeys.map((k) => `${k}:${headers[k]}`).join("\n") + "\n";
|
|
175
|
+
|
|
176
|
+
const canonicalRequest = [
|
|
177
|
+
"PUT",
|
|
178
|
+
canonicalUri,
|
|
179
|
+
"", // no query string
|
|
180
|
+
canonicalHeaders,
|
|
181
|
+
signedHeaders,
|
|
182
|
+
payloadHash,
|
|
183
|
+
].join("\n");
|
|
184
|
+
|
|
185
|
+
const crHash = await sha256(new TextEncoder().encode(canonicalRequest));
|
|
186
|
+
const stringToSign = [
|
|
187
|
+
"AWS4-HMAC-SHA256",
|
|
188
|
+
amzDate,
|
|
189
|
+
credentialScope,
|
|
190
|
+
crHash,
|
|
191
|
+
].join("\n");
|
|
192
|
+
|
|
193
|
+
const signingKey = await deriveSigningKey(
|
|
194
|
+
cfg.secretAccessKey,
|
|
195
|
+
dateStamp,
|
|
196
|
+
cfg.region,
|
|
197
|
+
);
|
|
198
|
+
const signature = toHex(await hmac(signingKey, stringToSign));
|
|
199
|
+
|
|
200
|
+
const authorization =
|
|
201
|
+
`AWS4-HMAC-SHA256 Credential=${cfg.accessKeyId}/${credentialScope}, ` +
|
|
202
|
+
`SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
203
|
+
|
|
204
|
+
const url = `${cfg.endpoint}${canonicalUri}`;
|
|
205
|
+
const res = await fetch(url, {
|
|
206
|
+
method: "PUT",
|
|
207
|
+
headers: {
|
|
208
|
+
...headers,
|
|
209
|
+
Authorization: authorization,
|
|
210
|
+
"Content-Length": String(body.byteLength),
|
|
211
|
+
},
|
|
212
|
+
body: body.buffer.slice(
|
|
213
|
+
body.byteOffset,
|
|
214
|
+
body.byteOffset + body.byteLength,
|
|
215
|
+
) as BodyInit,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
const text = await res.text().catch(() => "");
|
|
220
|
+
throw new Error(
|
|
221
|
+
`S3 PutObject failed (${res.status}): ${text || res.statusText}`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return cfg.publicBaseUrl
|
|
226
|
+
? `${cfg.publicBaseUrl}/${key}`
|
|
227
|
+
: `${cfg.endpoint}/${cfg.bucket}/${key}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Provider ──────────────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
export const s3FileUploadProvider: FileUploadProvider = {
|
|
233
|
+
id: "s3",
|
|
234
|
+
name: "S3-compatible storage",
|
|
235
|
+
isConfigured: () => readS3EnvConfig() !== null,
|
|
236
|
+
isConfiguredForRequest: async () => (await readS3Config()) !== null,
|
|
237
|
+
upload: async ({ data, filename, mimeType }) => {
|
|
238
|
+
const cfg = await readS3Config();
|
|
239
|
+
if (!cfg) throw new Error("S3 credentials are not configured");
|
|
240
|
+
|
|
241
|
+
const ext = filename?.split(".").pop() ?? "bin";
|
|
242
|
+
const stamp = Date.now();
|
|
243
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
244
|
+
const objectKey = `replays/${stamp}-${rand}.${ext}`;
|
|
245
|
+
const contentType = mimeType || "application/octet-stream";
|
|
246
|
+
|
|
247
|
+
const bytes =
|
|
248
|
+
data instanceof Uint8Array
|
|
249
|
+
? data
|
|
250
|
+
: new Uint8Array(data as unknown as ArrayBuffer);
|
|
251
|
+
|
|
252
|
+
const publicUrl = await putObject(cfg, objectKey, bytes, contentType);
|
|
253
|
+
return { url: publicUrl, provider: "s3" };
|
|
254
|
+
},
|
|
255
|
+
};
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
readPrivateBlob,
|
|
9
9
|
type PrivateBlobHandle,
|
|
10
10
|
} from "@agent-native/core/private-blob";
|
|
11
|
-
import { recordChange } from "@agent-native/core/server";
|
|
11
|
+
import { recordChange, runWithRequestContext } from "@agent-native/core/server";
|
|
12
12
|
import {
|
|
13
13
|
accessFilter,
|
|
14
14
|
resolveAccess,
|
|
@@ -988,6 +988,9 @@ function rowToSessionRecordingSummary(
|
|
|
988
988
|
}
|
|
989
989
|
|
|
990
990
|
function hasVisibleSessionRecordingIdentity(row: any): boolean {
|
|
991
|
+
// /sessions intentionally lists signed-in, email-backed recordings only (see
|
|
992
|
+
// analytics CLAUDE.md + the "rejects anonymous recordings" spec). Keep this in
|
|
993
|
+
// sync with replayVisibleIdentityCondition().
|
|
991
994
|
return Boolean(replayEmail(row.userId) || replayEmail(row.userKey));
|
|
992
995
|
}
|
|
993
996
|
|
|
@@ -1050,6 +1053,8 @@ function replayTextContains(column: unknown, query: string) {
|
|
|
1050
1053
|
}
|
|
1051
1054
|
|
|
1052
1055
|
function replayVisibleIdentityCondition() {
|
|
1056
|
+
// Email-backed identity only — /sessions lists signed-in recordings (see
|
|
1057
|
+
// analytics CLAUDE.md). Mirror of hasVisibleSessionRecordingIdentity().
|
|
1053
1058
|
return or(
|
|
1054
1059
|
replayTextContains(schema.sessionRecordings.userId, "@"),
|
|
1055
1060
|
replayTextContains(schema.sessionRecordings.userKey, "@"),
|
|
@@ -1140,7 +1145,12 @@ export async function recordSessionReplayChunks(
|
|
|
1140
1145
|
lastIngestedAt: ingestedAt,
|
|
1141
1146
|
ownerEmail: key.ownerEmail,
|
|
1142
1147
|
orgId: key.orgId,
|
|
1143
|
-
|
|
1148
|
+
// Session replay is an org-analytics surface: a recording captured under
|
|
1149
|
+
// an org-scoped analytics key must be visible to everyone in that org
|
|
1150
|
+
// (via accessFilter's "org" branch), not only the key owner. Without an
|
|
1151
|
+
// org we fall back to owner-private. This is what makes /sessions show
|
|
1152
|
+
// recordings to teammates instead of only the single key owner.
|
|
1153
|
+
visibility: key.orgId ? "org" : "private",
|
|
1144
1154
|
})
|
|
1145
1155
|
.onConflictDoNothing();
|
|
1146
1156
|
|
|
@@ -1204,12 +1214,23 @@ export async function recordSessionReplayChunks(
|
|
|
1204
1214
|
413,
|
|
1205
1215
|
);
|
|
1206
1216
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1217
|
+
// Replay ingest is anonymous + cross-origin (no session), so blob storage
|
|
1218
|
+
// would otherwise have no request context and `resolveBuilderPrivateKey()`
|
|
1219
|
+
// (and any S3 provider's scoped-secret lookup) would resolve nothing —
|
|
1220
|
+
// every chunk upload then 503s and recordings persist as empty shells.
|
|
1221
|
+
// Run the upload in the public key owner's user/org scope so the org's
|
|
1222
|
+
// connected Builder (or S3) credential in `app_secrets` resolves. Mirrors
|
|
1223
|
+
// the resources upload precedent (core resources/handlers.ts).
|
|
1224
|
+
const chunk = await runWithRequestContext(
|
|
1225
|
+
{ userEmail: key.ownerEmail, orgId: key.orgId ?? undefined },
|
|
1226
|
+
() =>
|
|
1227
|
+
storeReplayChunkBlob(rawChunk, {
|
|
1228
|
+
publicKeyId: key.id,
|
|
1229
|
+
recordingId: recording.id,
|
|
1230
|
+
ownerEmail: key.ownerEmail,
|
|
1231
|
+
orgId: key.orgId,
|
|
1232
|
+
}),
|
|
1233
|
+
);
|
|
1213
1234
|
if (rawChunk.storageKind !== "blob" && chunk.storageKind === "blob") {
|
|
1214
1235
|
const ref = decodeReplayBlobRef(chunk.storageRef);
|
|
1215
1236
|
if (ref) uploadedBlobHandles.push(ref.handle);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
10
10
|
import { renderDataDictionary } from "../lib/data-dictionary-context";
|
|
11
11
|
import {
|
|
12
|
+
failedDataQueryAttemptMessage,
|
|
12
13
|
hasExplicitPartialDisclosure,
|
|
13
14
|
hasFailedCorpusWorkflowEvidence,
|
|
14
15
|
hasDataQueryAttempt,
|
|
@@ -39,6 +40,7 @@ const INITIAL_TOOL_NAMES = [
|
|
|
39
40
|
"get-sql-dashboard",
|
|
40
41
|
"mutate-dashboard",
|
|
41
42
|
"generate-chart",
|
|
43
|
+
"query-agent-native-analytics",
|
|
42
44
|
"bigquery",
|
|
43
45
|
"search-bigquery-schema",
|
|
44
46
|
"bigquery-table-info",
|
|
@@ -139,6 +141,13 @@ function realDataFinalGuard(context: AgentLoopFinalResponseGuardContext) {
|
|
|
139
141
|
}
|
|
140
142
|
if (hasDataQueryAttempt(context.toolResults)) return null;
|
|
141
143
|
if (isSafeNoDataAnalyticsResponse(context.text)) return null;
|
|
144
|
+
const failedQueryMessage = failedDataQueryAttemptMessage(context.toolResults);
|
|
145
|
+
if (failedQueryMessage) {
|
|
146
|
+
return {
|
|
147
|
+
retryMessage: failedQueryMessage,
|
|
148
|
+
fallbackMessage: failedQueryMessage,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
142
151
|
|
|
143
152
|
return {
|
|
144
153
|
retryMessage:
|
|
@@ -175,8 +184,8 @@ export default createAgentChatPlugin({
|
|
|
175
184
|
"SURFACE DIFFERENTIATION — You are the analytics assistant for definitions, deep-dive analysis, and action. For questions about what a metric, model, or table means, use the Data Dictionary and configured schema tools first. For trends, comparisons, anomalies, current data, or anything that requires querying live data, answer directly in chat with the relevant provider query, dashboard analysis, and inline charts when useful. " +
|
|
176
185
|
"DASHBOARD CREATION RULE — You may create dashboards, analyses, SQL panels, or other resources only when the user explicitly asks you to (e.g. 'build me a dashboard for...', 'create a new analysis', 'add a chart for...'). Never create any resource proactively during research, trend analysis, or answering questions. If you think a dashboard would be useful, suggest it and wait for explicit confirmation before creating anything. Never add new items to the sidebar or modify existing dashboards without an explicit user directive. " +
|
|
177
186
|
"DASHBOARD MUTATION RULE — For dashboard edits, default to `mutate-dashboard` with the typed `dashboard.*` script API so the main payload is a string and avoids native-array serialization traps. It can move panels by id, edit titles/SQL/config, insert, duplicate, remove, and patch dashboard fields in one atomic save. The script API is constrained: no variables/imports/loops/functions, only JSON-compatible arguments on documented dashboard methods. Do not count shifting `/panels/<index>` positions for ordinary dashboard edits unless the user specifically asks for low-level JSON-pointer operations. " +
|
|
178
|
-
"DASHBOARD READ RULE — `get-sql-dashboard` is compact by default: use its `panels` summaries plus `layout.panelOrder` and `layout.
|
|
179
|
-
'DASHBOARD REORDER RULE — For simple chart/section moves, use `mutate-dashboard` code such as `dashboard.panels(["panel-a","panel-b"]).moveToTop();`. Never count shifting `/panels/<index>` positions for ordinary \'move this chart\' requests. Use `get-sql-dashboard.layout.
|
|
187
|
+
"DASHBOARD READ RULE — `get-sql-dashboard` is compact by default: use its `panels` summaries plus `layout.panelOrder`, `layout.firstPanelIds`, and `layout.groups[].rows[].rowNumber/panelIds` for orientation and verification. Pass `includeConfig: true` only when you truly need full panel SQL/config. " +
|
|
188
|
+
'DASHBOARD REORDER RULE — For simple chart/section moves, use `mutate-dashboard` code such as `dashboard.panels(["panel-a","panel-b"]).moveToTop();`. For visible placement requests like "second row" or "next to return rates", use row-aware placement such as `dashboard.insertPanel({...}).nextTo("retention-over-time")`, `.atRow(2)`, or `dashboard.panel("panel-a").moveNextTo("panel-b")`; these keep panels in the intended rendered row and expand/rebalance that row when needed. Never count shifting `/panels/<index>` positions for ordinary \'move this chart\' requests. Use `get-sql-dashboard.layout.groups[].rows` as proof of visible row placement, not only flat `panelOrder`. ' +
|
|
180
189
|
"Use configured data sources and actions only. Call `data-source-status` when you need to know which providers are connected, and treat provider actions as unavailable for analysis if they return missing credentials, permission, syntax, quota, or network errors. " +
|
|
181
190
|
"The built-in `demo` dashboard source is a demo-environment Prometheus source reserved for the Node Exporter demo. It must never satisfy REAL_DATA_REQUIRED or be cited as user analytics evidence unless the user explicitly asks to inspect the demo dashboard. " +
|
|
182
191
|
"When the user names a provider or tool such as Jira, Pylon, HubSpot, Gong, Slack, Sentry, GA4, or BigQuery, that named source is authoritative for the turn: use that provider's real tool/API surface, not a warehouse or different-provider substitute, unless the user explicitly asks for the copy/fallback. For bounded lookups where a first-class action fully models the requested source, object, filter, and pagination need, that shortcut is fine. For broad provider searches, cross-source joins, corpus-wide counts, exact cohort coverage, or any answer where absence matters, do not start and stop with shortcuts; use the broad provider API/MCP and corpus/code workflow as the primary path. " +
|