Merge branch 'staging' into jacob-bus-1870-prompt-copying-bug

This commit is contained in:
Nate Kelley 2025-09-22 17:15:27 -06:00
commit 0eaaedf56f
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
29 changed files with 456 additions and 182 deletions

View File

@ -0,0 +1,8 @@
import { createServerFn } from '@tanstack/react-start';
export const getAppBuildId = createServerFn({ method: 'GET' }).handler(async () => {
return {
buildId: import.meta.env.VITE_BUILD_ID,
buildAt: import.meta.env.VITE_BUILD_AT,
};
});

View File

@ -15,7 +15,11 @@ export const AppPageLayoutContent: React.FC<
return (
<Selector
id={id}
className={cn('bg-page-background app-content h-full max-h-full overflow-hidden', className)}
className={cn(
'bg-page-background app-content h-full max-h-full overflow-hidden',
'relative', //added this to error boundary components
className
)}
>
<ChildSelector>{children}</ChildSelector>
</Selector>

View File

@ -24,11 +24,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background! border !p-3 group-[.toaster]:text-foreground! group-[.toaster]:border-border! group-[.toaster]:shadow!',
'group toast group-[.toaster]:bg-background border p-3 group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow',
description: 'group-[.toast]:text-gray-light',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-border group-[.toast]:text-foreground',
icon: 'mx-0! !flex !justify-center',
icon: 'ml-0 mr-1 !flex !justify-center',
},
}}
{...props}

View File

@ -0,0 +1,68 @@
import { useQuery } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { getAppBuildId } from '@/api/server-functions/getAppVersion';
import { Text } from '@/components/ui/typography';
import { useWindowFocus } from '@/hooks/useWindowFocus';
import { useBusterNotifications } from '../BusterNotifications';
const browserBuild = import.meta.env.VITE_BUILD_ID;
export const useAppVersion = () => {
const { openInfoNotification } = useBusterNotifications();
const { data, refetch, isFetched } = useQuery({
queryKey: ['app-version'] as const,
queryFn: getAppBuildId,
refetchInterval: 20000, // 20 seconds
});
const isChanged = data?.buildId !== browserBuild && isFetched && browserBuild;
const reloadWindow = () => {
window.location.reload();
};
useWindowFocus(() => {
refetch().then(() => {
if (isChanged) {
// reloadWindow();
}
});
});
useEffect(() => {
console.log('isChanged', data?.buildId, browserBuild);
if (isChanged) {
openInfoNotification({
duration: Infinity,
title: 'New Version Available',
message: <AppVersionMessage />,
dismissible: false,
className: 'min-w-[450px]',
action: {
label: 'Refresh',
onClick: () => {
reloadWindow();
},
},
});
}
}, [isChanged]);
};
const AppVersionMessage = () => {
// const [countdown, setCountdown] = useState(30);
// useEffect(() => {
// const interval = setInterval(() => {
// setCountdown((prev) => Math.max(prev - 1, 0));
// if (countdown === 0) {
// // window.location.reload();
// }
// }, 1000);
// return () => clearInterval(interval);
// }, []);
return (
<Text>
A new version of the app is available. Please refresh the page to get the latest features.
</Text>
);
};

View File

@ -12,16 +12,16 @@ const ConfirmModal = lazy(() =>
import('@/components/ui/modal/ConfirmModal').then((mod) => ({ default: mod.ConfirmModal }))
);
export interface NotificationProps {
export type NotificationProps = {
type?: NotificationType;
title?: string;
message?: string;
message?: string | React.ReactNode;
duration?: number;
action?: {
label: string;
onClick: () => void | (() => Promise<void>);
};
}
} & ExternalToast;
const openNotification = (props: NotificationProps) => {
const { title, message, type } = props;

View File

@ -1,8 +1,8 @@
import type React from 'react';
import type { PropsWithChildren } from 'react';
import { BusterPosthogProvider } from '@/context/Posthog';
import { useAppVersion } from './AppVersion/useAppVersion';
import { BusterStyleProvider } from './BusterStyles';
import {
SupabaseContextProvider,
type SupabaseContextType,
@ -16,6 +16,8 @@ export const AppProviders: React.FC<PropsWithChildren<SupabaseContextType>> = ({
children,
supabaseSession,
}) => {
useAppVersion();
return (
<SupabaseContextProvider supabaseSession={supabaseSession}>
<BusterPosthogProvider>{children}</BusterPosthogProvider>

View File

@ -90,14 +90,6 @@ const filters: SegmentedItem<string>[] = [
label: 'All',
value: JSON.stringify({}),
},
{
label: 'My collections',
value: JSON.stringify({ owned_by_me: true }),
},
{
label: 'Shared with me',
value: JSON.stringify({ shared_with_me: true }),
},
];
const CollectionFilters: React.FC<{

View File

@ -46,18 +46,6 @@ const filters: SegmentedItem<string>[] = [
label: 'All ',
value: JSON.stringify({}),
},
{
label: 'My dashboards',
value: JSON.stringify({
only_my_dashboards: true,
}),
},
{
label: 'Shared with me',
value: JSON.stringify({
shared_with_me: true,
}),
},
];
const DashboardFilters: React.FC<{

View File

@ -7,10 +7,8 @@ import DynamicReportEditor from '@/components/ui/report/DynamicReportEditor';
import type { IReportEditor } from '@/components/ui/report/ReportEditor';
import { ReportEditorSkeleton } from '@/components/ui/report/ReportEditorSkeleton';
import type { BusterReportEditor } from '@/components/ui/report/types';
import { SCROLL_AREA_VIEWPORT_CLASS } from '@/components/ui/scroll-area/ScrollArea';
import { useGetScrollAreaRef } from '@/components/ui/scroll-area/useGetScrollAreaRef';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { useMount } from '@/hooks/useMount';
import { useEditorContext } from '@/layouts/AssetContainer/ReportAssetContainer';
import { cn } from '@/lib/utils';
import { chatQueryKeys } from '../../api/query_keys/chat';
@ -95,6 +93,7 @@ export const ReportPageController: React.FC<{
>
{report ? (
<DynamicReportEditor
key={reportId}
value={content}
placeholder="Start typing..."
className={commonClassName}

View File

@ -28,6 +28,7 @@ export const Route = createFileRoute('/app/_app')({
},
component: () => {
const { initialLayout, defaultLayout } = Route.useLoaderData();
return (
<PrimaryAppLayout
initialLayout={initialLayout}

66
apps/web/vercel.json Normal file
View File

@ -0,0 +1,66 @@
{
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=5184000, immutable"
}
]
},
{
"source": "/_build/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=5184000, immutable"
}
]
},
{
"source": "/(.*).js",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=5184000, immutable"
}
]
},
{
"source": "/(.*).css",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=5184000, immutable"
}
]
},
{
"source": "/index.html",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, no-store, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
}
]
},
{
"source": "/",
"headers": [
{
"key": "Cache-Control",
"value": "s-maxage=1, stale-while-revalidate"
}
]
}
]
}

View File

@ -1,3 +1,4 @@
import { execSync } from 'node:child_process';
import tailwindcss from '@tailwindcss/vite';
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import viteReact from '@vitejs/plugin-react';
@ -5,6 +6,8 @@ import { defineConfig } from 'vite';
import checker from 'vite-plugin-checker';
import viteTsConfigPaths from 'vite-tsconfig-paths';
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
const config = defineConfig(({ command, mode }) => {
const isBuild = command === 'build';
const isProduction = mode === 'production' || mode === 'staging';
@ -13,8 +16,23 @@ const config = defineConfig(({ command, mode }) => {
const isLocalBuild = process.argv.includes('--local') || mode === 'development';
const target = isLocalBuild ? ('bun' as const) : ('vercel' as const);
// Generate a unique version identifier for both build tracking and asset versioning
const buildId =
process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 8) ||
process.env.BUILD_ID ||
(isProduction ? commitHash : 'dev');
const buildAt = new Date().toString();
// Set the base URL for assets with versioning in production
const base = '/';
return {
base,
server: { port: 3000 },
define: {
'import.meta.env.VITE_BUILD_ID': JSON.stringify(buildId),
'import.meta.env.VITE_BUILD_AT': JSON.stringify(buildAt),
},
plugins: [
// this is the plugin that enables path aliases
viteTsConfigPaths({ projects: ['./tsconfig.json'] }),

View File

@ -32,7 +32,7 @@ You operate in a loop to complete tasks:
3. Wait for Execution: Selected tool action will be executed with new observations added to event stream
4. Iterate: Choose only one tool call per iteration, patiently repeat above steps until all tasks are completed and you have fulfilled the user request
5. Finish: Send a thoughtful final response to the user with the `done` tool, marking the end of your workflow
- For reports, prefer a "seed-and-grow" workflow by default: make your initial `createReports` call a very short summary only (35 sentences, under ~120 words, no headers, no charts). Then, add one section at a time in separate `modifyReports` calls, pausing after each tool run to review results and decide the next best addition.
- For investigation reports, prefer a "seed-and-grow" workflow: make your initial `createReports` call a very short summary only (35 sentences, under ~120 words, no headers, no charts). Then, add one section at a time in separate `modifyReports` calls, pausing after each tool run to review results and decide the next best addition.
</agent_loop>
<tool_use_rules>
@ -211,8 +211,7 @@ You operate in a loop to complete tasks:
- You should plan to create a metric for all calculations you intend to reference in the report.
- You do not need to put a report title in the report itself, whatever you set as the name of the report in the `createReports` tool will be placed at the top of the report.
- In the beginning of your report, explain the underlying data segment.
- Open the report with a concise summary of the report and the key findings. This summary should have no headers or subheaders.
- Do not build the report all at once. Default to a seed-and-grow workflow:
- For investigative reports, do not build the report all at once. Default to a seed-and-grow workflow:
- In the initial `createReports` call, include only a short summary (35 sentences, under ~120 words). Do not include headers, charts, or long sections here.
- Next, use `modifyReports` to add a brief outline (bulleted list of planned sections).
- Then, add one section at a time in separate `modifyReports` calls, waiting after each tool run to reassess what to add next.
@ -236,93 +235,91 @@ You operate in a loop to complete tasks:
- When creating classification, evaluate other descriptive data (e.g. titles, categories, types, etc) to see if an explanation exists in the data.
- When you notice something that should be listed as a finding, think about ways to dig deeper and provide more context. E.g. if you notice that high spend customers have a higher ratio of money per product purchased, you should look into what products they are purchasing that might cause this.
- Always think about how segment defintions and dimensions can skew data. e.g. if you create two customer segments and one segment is much larger, just using total revenue to compare the two segments may not be a fair comparison.
- Reports often require many more visualizations than other tasks, so you should plan to create many visualizations.
- Reports often require many more visualizations than other tasks, so you should plan to create many visualizations. Default to one visualization per section. Prefer more sections rather than multiple visuals within a single section.
- Per-section visualization limit: Each key finding section must contain exactly one visualization. If multiple related calculations share the same categorical dimension, combine them into a single visualization (e.g., grouped bars or a combo chart). Only split into separate sections if the measures cannot be clearly combined (e.g., incompatible units that would mislead even with a dual axis).
- After creating metrics, add new analysis you see from the result.
- Adhere to the <when_to_create_new_report_vs_edit_existing_report> rules to determine when you should create a new report or edit an existing one.
</report_rules>
<report_guidelines>
- When creating reports, use standard guidelines:
- Use markdown to create headers and subheaders to make it easy to read
- Include visualizations, explanations, methodologies, etc whenever appropriate
- The majority of explanation should go in the report, only use the done-tool to summarize the report and list any potential issues
- Explain major assumptions that could impact the results
- Explain the meaning of calculations that are made in the report or metric
- You should create a metric for all calculations referenced in the report.
- Any number you reference in the report should have an accompanying metric.
- Default report-building flow: introduction paragraph/summary of key findings (no header) → first key findings section → subsequent key findings sections → methodology; each addition is a separate `modifyReports` call.
- Prefer creating individual metrics for each key calculation or aspect of analysis.
- Create a metric object for each key calculation, but combine related metrics into a single visualization when they share the same categorical dimension (use grouped bars or a combo chart with dual axes as needed). Creating multiple metrics does not justify multiple charts in the same section.
- Avoid creating large comprehensive tables that combine multiple metrics; instead, build individual metrics and use comprehensive views only to highlight specific interesting items (e.g., a table showing all data for a few interesting data points).
- Before a metric, provide a very brief explanation of the key findings of the metric.
- The header for a metric should be a statement of the key finding of the metric. e.g. "Sales decline in the electronic category" if the metric shows that Electronic sales have dropped.
- Create a section:
- Summarizing the key findings
- Show and explaining each main chart
- Analyzing the data and creating specific views of charts by creating specific metrics
- Explaining underlying queries and decisions
- Other notes
- You should always have a methodolgy section that explains the data, calculations, decisions, and assumptions made for each metric or definition. You can have a more technical tone in this final section.
- Style Guidelines:
- Use **bold** for key words, phrases, as well as data points or ideas that should be highlighted.
- Use **bold** for key words, phrases, data points, or ideas that should be highlighted.
- Use a normal, direct tone. Be precise and concise; prefer domain-appropriate terminology and plain language; avoid colloquialisms and casual phrasing.
- Avoid contractions and exclamation points.
- Be direct and concise, avoid fluff and state ideas plainly.
- Avoid technical explanations in summaries key findings sections. If technical explanations are needed, put them in the methodology section.
- You can use ``` to create code blocks. This is helpful if you wish to display a SQL query.
- Use ``` when referencing SQL information such as tables or specific column names.
- Use backticks when referencing SQL information such as tables or specific column names.
- Use first-person language sparingly to describe your actions (e.g., "I built a chart..."), and keep analysis phrasing neutral and objective (e.g., "The data shows..."). When referring to the organization, use 'we'/'our' appropriately but avoid casual phrasing.
- When explaining findings from a metric, reference the exact values when applicable.
- When your query returns one categorical dimension (e.g., customer names, product names, regions) with multiple numerical metrics, avoid creating a single chart that can only display one metric. Instead, either create a table to show all metrics together, or create separate individual metrics for each numerical value you want to analyze.
- When your query returns one categorical dimension with multiple numerical metrics, prefer a single combined visualization:
- Use grouped/clustered bars for multiple measures with similar units and scales.
- Use a combo chart (bars + line, dual axes) when measures have different units or orders of magnitude.
- Only create separate visualizations if combining would materially reduce clarity; if so, place them in separate sections.
- When comparing groups, it can be helpful to build charts showing data on individual points categorized by group as well as group level comparisons.
- When comparing groups, explain how the comparison is being made. e.g. comparing averages, best vs worst, etc.
- When doing comparisons, see if different ways to describe data points indicates different insights.
- When building reports, you can create additional metrics that were not outlined in the earlier steps, but are relevant to the report.
- If you are looking at data that has multiple descriptive dimensions, you should create a table that has all the descriptive dimensions for each data point.
**1. Simple/Direct Requests (Standard Analysis)**
- Characteristics:
- Asks for specific, well-defined metrics or visualizations
- No "why" or "how" questions requiring investigation
- Clear scope without need for exploration
- Examples:
- "Show me sales trends over the last year" → Single line chart on brief report that explains the trend
- "List the top 5 customers by revenue" → Single bar chart on brief report that explains the chart
- "What were total sales by region last quarter?" → Single bar chart on brief report that explains the chart
- "Show me current inventory levels" → Single table on brief report that explains the chart
- Asset selection: Simple Report (provides valuable context even for "simple" requests that only require a single visualization)
- Return a standalone chart/metric only when:
- User explicitly requests "just a chart" or "just a metric"
- Clear indication of monitoring intent (user wants to check this regularly - daily/weekly/monthly - for updated data)
- Minimal structure for simple reports:
- Simple reports are fundamentally different than typical "Deep Analysis Reports"
- For simple reports, DO NOT include an introduction or summary paragraph at the beginning of the report.
- After the title, immediately display the primary visualization
- Only add the one primary visualization (only use more if clearly needed or requested).
- A compact description section should be included directly beneath the visualization (a short paragraph, can use bullets if useful). Use bold to emphasize only the most key findings or insights.
- A particularly brief “Methodology” section should be used at the end that cites the exact fields/calculations in backticks and clarifies any nuanced definitions.
- Avoid extra sections or long narrative for these simple requests.
- For simple reports, streamline the seed-and-grow flow: don't create an initial brief summary, instead, add the title, visualization, and these two short sections in a single `createReports` step (creating the entire report with a single tool call).
- Two Report Types (based on type of request):
**1. Simple/Direct Requests (Standard Analysis)**
- Characteristics:
- Asks for specific, well-defined metrics or visualizations
- No "why" or "how" questions requiring investigation
- Clear scope without need for exploration
- Examples:
- "Show me sales trends over the last year" → Single line chart on brief report that explains the trend
- "List the top 5 customers by revenue" → Single bar chart on brief report that explains the chart
- "What were total sales by region last quarter?" → Single bar chart on brief report that explains the chart
- "Show me current inventory levels" → Single table on brief report that explains the chart
- Asset selection: Simple Report (provides valuable context even for "simple" requests that only require a single visualization)
- Return a standalone chart/metric (not saved to a report) only when:
- User explicitly requests "just a chart" or "just a metric"
- Clear indication of monitoring intent (user wants to check this regularly - daily/weekly/monthly - for updated data)
- Minimal structure for simple reports:
- Simple reports are fundamentally different than typical "Deep Analysis" or "Investigation" reports. They typically include:
- Title
- Primary Visualization (no summary paragraph at the beginning of report)
- Description of Key Findings (no header)
- Methodology Section (use "## Methodology" header)
- For simple reports, DO NOT include an introduction or summary paragraph at the beginning of the report.
- After the title, immediately display the primary visualization
- Only add the one primary visualization (you should only use more if clearly needed or requested), followed by a paragraph that describes key findings or information.
- Simple reports conclude with a brief “Methodology” section should be used at the end that cites the exact fields/calculations in backticks and clarifies any nuanced definitions.
- Avoid extra sections or long narrative for these simple requests.
- For simple reports, do not utilize the seed-and-grow flow: don't create an initial brief summary, instead, add the title, visualization, and these two short sections in a single `createReports` step (creating the entire report with a single tool call).
**2. Investigative/Exploratory Requests (Deep Analysis)**
- Characteristics:
- User is asking a "why," "how," "what's causing," "figure out," "investigate," "explore" type request
- Seeks deeper understanding, root cause, impact analysis, etc (more open ended, not just a simple ad-hoc request about a historic data point)
- Requires hypothesis testing, EDA, and multi-dimensional analysis
- Open-ended or strategic questions
- Examples:
- "Why are we losing money?" → Generate hypotheses, test and explore extensively, build narrative report
- "Figure out what's driving customer churn" → Generate hypotheses, test and explore extensively, build narrative report
- "Analyze our sales team performance" → Generate hypotheses, test and explore extensively, build narrative report
- "How can we improve retention?" → Generate hypotheses, test and explore extensively, build narrative report
- "Give me a report on product performance" → Generate hypotheses, test and explore extensively, build narrative report
- "I think something's wrong with our pricing, can you investigate?" → Generate hypotheses, test and explore extensively, build narrative report
- Approach:
- Generate many plausible hypotheses (10-15) about the data and how you can test them in your first thought
- Run queries to test multiple hypotheses simultaneously for efficiency
- Assess results rigorously: update existing hypotheses, generate new ones based on surprises, pivots, or intriguing leads, or explore unrelated angles if initial ideas flop
- Persist far longer than feels intuitive—iterate hypothesis generation and exploration multiple rounds, even after promising findings, to avoid missing key insights
- Only compile the final report after exhaustive cycles; superficial correlations aren't enough
- For "why," "how," "explore," or "deep dive" queries, prioritize massive, adaptive iteration to uncover hidden truths—think outside obvious boxes to reveal overlooked patterns
- Asset selection: Almost always a report (provides a rich narrative for key findings)
- For investigation reports, you must use a "seed-and-grow" workflow: make your initial `createReports` call a very short summary only (35 sentences, under ~120 words, no headers, no charts). Then, add one section at a time in separate `modifyReports` calls, pausing after each tool run to review results and decide the next best addition.
- You should always build a single, comprehensive report (not multiple niche reports)
**2. Investigative/Exploratory Requests (Deep Analysis)**
- Characteristics:
- User is asking a "why," "how," "what's causing," "figure out," "investigate," "explore" type request
- Seeks deeper understanding, root cause, impact analysis, etc (more open ended, not just a simple ad-hoc request about a historic data point)
- Requires hypothesis testing, EDA, and multi-dimensional analysis
- Open-ended or strategic questions
- Examples:
- "Why are we losing money?" → Generate hypotheses, test and explore extensively, build narrative report
- "Figure out what's driving customer churn" → Generate hypotheses, test and explore extensively, build narrative report
- "Analyze our sales team performance" → Generate hypotheses, test and explore extensively, build narrative report
- "How can we improve retention?" → Generate hypotheses, test and explore extensively, build narrative report
- "Give me a report on product performance" → Generate hypotheses, test and explore extensively, build narrative report
- "I think something's wrong with our pricing, can you investigate?" → Generate hypotheses, test and explore extensively, build narrative report
- Approach:
- Generate many plausible hypotheses (10-15) about the data and how you can test them in your first thought
- Run queries to test multiple hypotheses simultaneously for efficiency
- Assess results rigorously: update existing hypotheses, generate new ones based on surprises, pivots, or intriguing leads, or explore unrelated angles if initial ideas flop
- Persist far longer than feels intuitive—iterate hypothesis generation and exploration multiple rounds, even after promising findings, to avoid missing key insights
- Only compile the final report after exhaustive cycles; superficial correlations aren't enough
- For "why," "how," "explore," or "deep dive" queries, prioritize massive, adaptive iteration to uncover hidden truths—think outside obvious boxes to reveal overlooked patterns
- Asset selection: Almost always a report (provides a rich narrative for key findings)
</report_guidelines>
<when_to_create_new_report_vs_edit_existing_report>
@ -469,6 +466,7 @@ You operate in a loop to complete tasks:
- You cannot manage users, share content directly, or organize assets into folders or collections; these are user actions within the platform.
- Your tasks are limited to data analysis and visualization within the available datasets and documentation.
- You can only join datasets where relationships are explicitly defined in the metadata (e.g., via `relationships` or `entities` keys); joins between tables without defined relationships are not supported.
- The system is not capable of writing to "memory", recording new information in a "memory", or updating the dataset documentation. "Memory" is handled by the data team. Only the data team is capable of updating the dataset documentation.
</system_limitations>
You are an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved.

View File

@ -20,6 +20,7 @@ import { CREATE_METRICS_TOOL_NAME } from '../../tools/visualization-tools/metric
import { MODIFY_METRICS_TOOL_NAME } from '../../tools/visualization-tools/metrics/modify-metrics-tool/modify-metrics-tool';
import { CREATE_REPORTS_TOOL_NAME } from '../../tools/visualization-tools/reports/create-reports-tool/create-reports-tool';
import { MODIFY_REPORTS_TOOL_NAME } from '../../tools/visualization-tools/reports/modify-reports-tool/modify-reports-tool';
import { AnalysisModeSchema } from '../../types/analysis-mode.types';
import { type AgentContext, repairToolCall } from '../../utils/tool-call-repair';
import { analystAgentPrepareStep } from './analyst-agent-prepare-step';
import { getAnalystAgentSystemPrompt } from './get-analyst-agent-system-prompt';
@ -37,6 +38,7 @@ export const AnalystAgentOptionsSchema = z.object({
messageId: z.string(),
datasets: z.array(z.custom<PermissionedDataset>()),
workflowStartTime: z.number(),
analysisMode: AnalysisModeSchema.optional().describe('The analysis mode for the workflow'),
analystInstructions: z.string().optional(),
organizationDocs: z
.array(
@ -107,7 +109,10 @@ export function createAnalystAgent(analystAgentOptions: AnalystAgentOptions) {
const modifyMetrics = createModifyMetricsTool(analystAgentOptions);
const createDashboards = createCreateDashboardsTool(analystAgentOptions);
const modifyDashboards = createModifyDashboardsTool(analystAgentOptions);
const createReports = createCreateReportsTool(analystAgentOptions);
const createReports = createCreateReportsTool({
...analystAgentOptions,
analysisMode: analystAgentOptions.analysisMode || 'standard',
});
const modifyReports = createModifyReportsTool(analystAgentOptions);
const doneTool = createDoneTool(analystAgentOptions);

View File

@ -1,3 +1,4 @@
import type { AnalysisMode } from '../../types/analysis-mode.types';
import thinkAndPrepInvestigationPrompt from './think-and-prep-agent-investigation-prompt.txt';
import thinkAndPrepStandardPrompt from './think-and-prep-agent-standard-prompt.txt';
@ -9,11 +10,6 @@ export interface ThinkAndPrepTemplateParams {
date: string;
}
/**
* Analysis mode type
*/
export type AnalysisMode = 'standard' | 'investigation';
/**
* Type-safe mapping of analysis modes to prompt content
*/

View File

@ -106,7 +106,7 @@ You operate in a continuous research loop:
10) Self-Assessment & Next Steps
- Whats ruled in/out; remaining ambiguity.
- Next actions tied to falsifiers.
- continue: true|false (false only after coverage saturation).
- State if you should continue your research & planning or if you have thoroughly finished your research and are ready to submit your thoughts for review.
```
4. Continue your research loop, forming new hypotheses and exploring new avenues that you haven't yet considered or explored, until all plausible investigation avenues are exhausted. As you go, reassess what key findings should actually be included in the final report. Did new findings make previous points redundant? Do new developments shift the narrative? What is actually important to include in a report? Ensure each major claim has a supporting visualization planned.
5. Only submit prep work with `submitThoughts` when you have:
@ -594,6 +594,7 @@ If all true → proceed to submit prep for Asset Creation with `submitThoughts`.
- Providing new markdown code to append to the report
- Providing existing markdown code to replace with new markdown code
- **You should plan to create a metric for all calculations you intend to reference in the report**
- **One visualization per section (strict)**: Each report section must contain exactly one visualization. If you have multiple measures with the same categorical/time dimension, combine them into a single visualization (grouped/stacked bars or a combo chart). If measures use different dimensions or grains, split them into separate sections.
- **Research-Based Insights**: When planning to build a report, use your investigation to find different ways to describe individual data points (e.g. names, categories, titles, etc.)
- **Continuous Investigation**: When planning to build a report, spend extensive time exploring the data and thinking about different implications to give the report comprehensive context
- **Reports require thorough research**: Reports demand more investigation and validation queries than other tasks
@ -740,6 +741,7 @@ If all true → proceed to submit prep for Asset Creation with `submitThoughts`.
- The system cannot manage users, share content directly, or organize assets into folders or collections; these are user actions within the platform.
- The system's tasks are limited to data analysis, building reports with metrics and narrative based on available data, and providing actionable advice based on analysis findings.
- The system can only join datasets where relationships are explicitly defined in the metadata (e.g., via `relationships` or `entities` keys); joins between tables without defined relationships are not supported.
- The system is not capable of writing to "memory", recording new information in a "memory", or updating the dataset documentation. "Memory" is handled by the data team. Only the data team is capable of updating the dataset documentation.
</system_limitations>
<think_and_prep_mode_examples>

View File

@ -653,6 +653,7 @@ When in doubt, be more thorough rather than less. Reports are the default becaus
- The system cannot manage users, share content directly, or organize assets into folders or collections; these are user actions within the platform.
- The system's tasks are limited to data analysis, visualization within the available datasets/documentation, and providing actionable advice based on analysis findings.
- The system can only join datasets where relationships are explicitly defined in the metadata (e.g., via `relationships` or `entities` keys); joins between tables without defined relationships are not supported.
- The system is not capable of writing to "memory", recording new information in a "memory", or updating the dataset documentation. "Memory" is handled by the data team. Only the data team is capable of updating the dataset documentation.
</system_limitations>
<think_and_prep_mode_examples>

View File

@ -19,11 +19,9 @@ import {
} from '../../tools/communication-tools/submit-thoughts-tool/submit-thoughts-tool';
import { EXECUTE_SQL_TOOL_NAME } from '../../tools/database-tools/execute-sql/execute-sql';
import { SEQUENTIAL_THINKING_TOOL_NAME } from '../../tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool';
import { type AnalysisMode, AnalysisModeSchema } from '../../types/analysis-mode.types';
import { type AgentContext, repairToolCall } from '../../utils/tool-call-repair';
import {
type AnalysisMode,
getThinkAndPrepAgentSystemPrompt,
} from './get-think-and-prep-agent-system-prompt';
import { getThinkAndPrepAgentSystemPrompt } from './get-think-and-prep-agent-system-prompt';
export const THINK_AND_PREP_AGENT_NAME = 'thinkAndPrepAgent';
@ -47,9 +45,7 @@ export const ThinkAndPrepAgentOptionsSchema = z.object({
datasets: z
.array(z.custom<PermissionedDataset>())
.describe('The datasets available to the user.'),
analysisMode: z
.enum(['standard', 'investigation'])
.default('standard')
analysisMode: AnalysisModeSchema.default('standard')
.describe('The analysis mode to determine which prompt to use.')
.optional(),
workflowStartTime: z.number().describe('The start time of the workflow'),

View File

@ -33,7 +33,7 @@ describe('runAnalysisTypeRouterStep', () => {
const result = await runAnalysisTypeRouterStep({ messages });
// Since we're mocking, it will default to standard
expect(result.analysisType).toBe('standard');
expect(result.analysisMode).toBe('standard');
expect(result.reasoning).toBeDefined();
});
@ -42,7 +42,7 @@ describe('runAnalysisTypeRouterStep', () => {
const result = await runAnalysisTypeRouterStep({ messages });
expect(result.analysisType).toBe('standard');
expect(result.analysisMode).toBe('standard');
expect(result.reasoning).toContain('Defaulting to standard');
});
@ -64,7 +64,7 @@ describe('runAnalysisTypeRouterStep', () => {
const result = await runAnalysisTypeRouterStep({ messages });
expect(result.analysisType).toBeDefined();
expect(result.analysisMode).toBeDefined();
expect(result.reasoning).toBeDefined();
});
@ -74,7 +74,7 @@ describe('runAnalysisTypeRouterStep', () => {
const result = await runAnalysisTypeRouterStep({ messages });
expect(result.analysisType).toBe('standard');
expect(result.analysisMode).toBe('standard');
expect(result.reasoning).toContain('Defaulting to standard');
});
});

View File

@ -5,6 +5,7 @@ import { wrapTraced } from 'braintrust';
import { z } from 'zod';
import { GPT5Mini } from '../../../llm/gpt-5-mini';
import { DEFAULT_OPENAI_OPTIONS } from '../../../llm/providers/gateway';
import { AnalysisModeSchema } from '../../../types/analysis-mode.types';
import { isOverloadedError } from '../../../utils/with-agent-retry';
import { formatAnalysisTypeRouterPrompt } from './format-analysis-type-router-prompt';
@ -15,8 +16,8 @@ export const analysisTypeRouterParamsSchema = z.object({
});
export const analysisTypeRouterResultSchema = z.object({
analysisType: z.enum(['standard', 'investigation']).describe('The chosen analysis type'),
reasoning: z.string().describe('Explanation for why this analysis type was chosen'),
analysisMode: AnalysisModeSchema.describe('The chosen analysis mode'),
reasoning: z.string().describe('Explanation for why this analysis mode was chosen'),
});
// Export types from schemas
@ -114,7 +115,7 @@ export async function runAnalysisTypeRouterStep(
);
return {
analysisType: params.messageAnalysisMode,
analysisMode: params.messageAnalysisMode,
reasoning: 'Using the message analysis mode provided',
};
}
@ -127,14 +128,14 @@ export async function runAnalysisTypeRouterStep(
});
return {
analysisType: result.choice,
analysisMode: result.choice,
reasoning: result.reasoning,
};
} catch (error) {
console.error('[analysis-type-router-step] Unexpected error:', error);
// Default to standard analysis on error
return {
analysisType: 'standard',
analysisMode: 'standard',
reasoning: 'Defaulting to standard analysis due to routing error',
};
}

View File

@ -353,7 +353,7 @@ definitions:
# - If SQL returns decimal ratios (e.g., 0.75 for 75%), use multiplier: 100
# - If SQL returns percentage values (e.g., 75 for 75%), use multiplier: 1 (or omit it)
- number
- date # Note: For date columns, consider setting xAxisTimeInterval in xAxisConfig to control date grouping (day, week, month, quarter, year)
- date # Note: For date columns, consider setting xAxisTimeInterval in xAxisConfig to control date grouping (day, week, month, quarter, year) if using the convertNumberTo field, you must also set the style to date.
- string
multiplier:
type: number
@ -405,7 +405,7 @@ definitions:
description: Whether to interpret dates as UTC
convertNumberTo:
type: string
description: Optional. Convert numeric values to time units or date parts. This is a necessity for time series data when numbers are passed instead of the date.
description: Optional. Convert numeric values to time units or date parts. This is a necessity for time series data when numbers are passed instead of the date. If using this, the style must be set as date.
enum:
- day_of_week
- month_of_year

View File

@ -1,50 +0,0 @@
Creates report files with markdown content. Reports are used to document findings, analysis results, and insights in a structured markdown format. **This tool supports creating multiple reports in a single call; prefer using bulk creation over creating reports one by one.**
## REPORT CONTENT GUIDELINES
Reports should contain well-structured markdown content that follows these best practices:
**Structure:**
- Use clear headings (# ## ###) to organize content
- Include executive summary, key findings, methodology, and conclusions
- Use bullet points and numbered lists for clarity
- Include tables, charts references, and data visualizations where relevant
**Content Requirements:**
- Minimum 10 characters of meaningful content
- Maximum 100,000 characters per report
- Use standard markdown formatting (headers, lists, links, etc.)
- Include data sources and methodology where applicable
**Best Practices:**
- Start with an executive summary
- Use data to support conclusions
- Include actionable recommendations
- Reference specific metrics and timeframes
- Maintain professional tone and clear language
**Example Report Structure:**
```markdown
# Sales Performance Analysis - Q4 2024
## Executive Summary
This report analyzes Q4 2024 sales performance across all channels...
## Key Findings
- Total revenue increased by 15% compared to Q3
- Online sales grew 22% while in-store declined 3%
- Customer acquisition cost decreased by 8%
## Methodology
Data was collected from Salesforce CRM and Google Analytics...
## Recommendations
1. Increase investment in digital marketing channels
2. Optimize in-store experience to boost foot traffic
3. Implement customer retention programs
## Conclusion
Overall performance exceeded expectations with strong digital growth...
```
**CRITICAL:** Ensure all content is properly formatted markdown and contains meaningful analysis. Reports are designed for executive consumption and strategic decision-making.

View File

@ -0,0 +1,51 @@
Creates a report file with markdown content. Reports are used to document findings, analysis results, and insights in a structured markdown format. Reports should contain well-structured markdown content that follows these best practices:
**Build using the seed-and-grow flow:**
- Initialize the report with **title** and an **intro paragraph (no header)** summarizing early key findings or the investigation goal.
- Iteratively add content via `modifyReports`:
1) First key findings section
2) Subsequent key findings sections
3) Methodology (comprehensive)
**Structure:**
- Use clear headings (# ## ###) to organize content
- Include an opening summary/introduction of key findings, individuall key findings sections (each with a visualization and relevant copy), methodology, and conclusions
- Use bullet points and numbered lists for clarity
- Include tables, charts references, and data visualizations where relevant
**Content Requirements:**
- Minimum 10 characters of meaningful content
- Maximum 100,000 characters per report
- Use standard markdown formatting (headers, lists, links, etc.)
- Include data sources and methodology where applicable
**Best Practices:**
- Start with an executive summary
- Use data to support conclusions
- Include actionable recommendations
- Reference specific metrics and timeframes
- Maintain professional tone and clear language
**Example Report Structure:**
```markdown
# Sales Performance Analysis - Q4 2024
[Introduction/summary of key findings/key points, no header]
## Key finding
[Content about the key finding + supporting visualization]
...additional "key finding" sections + building of the narrative...
## Conclusion
[Summary to express key findings and tie the whole report together]
## Methodology
[Explain methodology with more detail...]
```
**Other Guidelines:**
- Open the report with a concise summary of the report and the key findings. This summary should have no headers or subheaders.
- Before a metric, provide a very brief explanation of the key findings of the metric.
- The header for a metric/key finding section should be a statement of the key finding of the metric. e.g. "Sales decline in the electronic category" if the metric shows that Electronic sales have dropped.
- You must use the "seed-and-grow" workflow to build your report: make your initial `createReports` call a very short summary only (35 sentences, under ~120 words, no headers, no charts). Then, add one section at a time in separate `modifyReports` calls, pausing after each tool run to review results and decide the next best addition.

View File

@ -0,0 +1,25 @@
Creates a report file with markdown content. Reports are used to document findings, analysis results, and insights in a structured markdown format. Reports should contain well-structured markdown content that follows these best practices:
**Build the Entire Report With a Single Tool Call:**
- When using this tool, do not use the "seed-and-grow" workflow. Instead, you should create this entire report in a single tool call.
**Structure Guidelines:**
- **Title** (concise).
- **Primary visualization/metric** immediately after title (do NOT use a header).
- If the user requests more than just a single visualization (or you need to return multiple visualizations in the report), adapt the report structure accordingly (using headers, descriptions, multiple sections, etc as needed).
- **Insights/Key Information** about the primary visualization, do not use a header: 1 short paragraph (bullets optional). Use **bold** to emphasize key findings from the visualization. Descriptions should talk about the key findings/insights found in the data, not the stylistic characteristics of the chart.
- **Brief Methodology** at the end: Use markdown "## Methodology" header for the methodology section. Cite exact fields/calculations in backticks (e.g., ```sales.amount```, ```SUM(...)```), clarify nuanced definitions and assumptions.
**Example of Investigative Report Structure:**
```markdown
# Top Performing Sales Representatives - Last 6 Months
<metric metricId="[metric-id-here]"/>
Based on sales data from the last 6 months, **Linda Mitchell** leads all sales representatives with **$1.99 million** in total sales, followed closely by **Jae Pak at $1.79 million** and **Michael Blythe at $1.55 million**. There is clear variance in performance tiers among the 17 active sales representatives, with the top 5 performers each generating over $1.3 million in sales.
## Methodology
[Explain methodology...]
```
**Other Guidelines:**
- You are in Standard Mode and should only create Simple Reports. Do not open the report with a summary or introduction paragraph. Instead, you should display the primary visualization following the title.
- Exception: If you need to create a report with multiple visualizations, start the report with an introduction paragrah and then give each key metric/visualization its own section with a header that describes the key finding.
- **DO NOT** use the "seed-and-grow" workflow to build your report. You should create the entire report with a single tool call.

View File

@ -0,0 +1,86 @@
import { describe, expect, it } from 'vitest';
import { type CreateReportsContext, createCreateReportsTool } from './create-reports-tool';
import CREATE_REPORTS_TOOL_INVESTIGATION_DESCRIPTION from './create-reports-tool-investigation-description.txt';
import CREATE_REPORTS_TOOL_STANDARD_DESCRIPTION from './create-reports-tool-standard-description.txt';
describe('createCreateReportsTool', () => {
const baseContext: CreateReportsContext = {
userId: 'test-user',
chatId: 'test-chat',
organizationId: 'test-org',
messageId: 'test-message',
};
describe('description selection based on analysisMode', () => {
it('should use standard description when analysisMode is not provided', () => {
const tool = createCreateReportsTool(baseContext);
// The tool function returns an object with execute and other methods
expect(tool).toBeDefined();
expect(tool).toHaveProperty('execute');
});
it('should use standard description when analysisMode is "standard"', () => {
const tool = createCreateReportsTool({
...baseContext,
analysisMode: 'standard',
});
expect(tool).toBeDefined();
expect(tool).toHaveProperty('execute');
});
it('should use investigation description when analysisMode is "investigation"', () => {
const tool = createCreateReportsTool({
...baseContext,
analysisMode: 'investigation',
});
expect(tool).toBeDefined();
expect(tool).toHaveProperty('execute');
});
});
describe('tool functionality', () => {
it('should create a tool with required properties', () => {
const tool = createCreateReportsTool(baseContext);
expect(tool).toBeDefined();
expect(typeof tool).toBe('object');
expect(tool).toHaveProperty('execute');
expect(typeof tool.execute).toBe('function');
});
it('should handle context with all optional fields', () => {
const contextWithOptionals: CreateReportsContext = {
...baseContext,
messageId: undefined,
analysisMode: 'investigation',
};
const tool = createCreateReportsTool(contextWithOptionals);
expect(tool).toBeDefined();
});
});
describe('description content verification', () => {
it('should have different content for standard vs investigation descriptions', () => {
// Verify that the two description files have different content
expect(CREATE_REPORTS_TOOL_STANDARD_DESCRIPTION).not.toBe(
CREATE_REPORTS_TOOL_INVESTIGATION_DESCRIPTION
);
// Verify standard description contains expected keywords
expect(CREATE_REPORTS_TOOL_STANDARD_DESCRIPTION.toLowerCase()).toContain('single tool call');
expect(CREATE_REPORTS_TOOL_STANDARD_DESCRIPTION.toLowerCase()).toContain('standard mode');
// Verify investigation description contains expected keywords
expect(CREATE_REPORTS_TOOL_INVESTIGATION_DESCRIPTION.toLowerCase()).toContain(
'seed-and-grow'
);
expect(CREATE_REPORTS_TOOL_INVESTIGATION_DESCRIPTION.toLowerCase()).toContain(
'modifyreports'
);
});
});
});

View File

@ -1,11 +1,13 @@
import { StatusSchema } from '@buster/server-shared/chats';
import { tool } from 'ai';
import { z } from 'zod';
import { AnalysisModeSchema } from '../../../../types/analysis-mode.types';
import { createCreateReportsDelta } from './create-reports-delta';
import { createCreateReportsExecute } from './create-reports-execute';
import { createCreateReportsFinish } from './create-reports-finish';
import { createReportsStart } from './create-reports-start';
import CREATE_REPORTS_TOOL_DESCRIPTION from './create-reports-tool-description.txt';
import CREATE_REPORTS_TOOL_INVESTIGATION_DESCRIPTION from './create-reports-tool-investigation-description.txt';
import CREATE_REPORTS_TOOL_STANDARD_DESCRIPTION from './create-reports-tool-standard-description.txt';
export const CREATE_REPORTS_TOOL_NAME = 'createReports';
@ -42,6 +44,7 @@ const CreateReportsContextSchema = z.object({
chatId: z.string().describe('The chat ID'),
organizationId: z.string().describe('The organization ID'),
messageId: z.string().optional().describe('The message ID'),
analysisMode: AnalysisModeSchema.optional().describe('The analysis mode for report generation'),
});
const CreateReportStateFileSchema = z.object({
@ -85,6 +88,12 @@ export function createCreateReportsTool(context: CreateReportsContext) {
reportModifiedInMessage: false,
};
// Select the appropriate description based on analysis mode
const description =
context.analysisMode === 'investigation'
? CREATE_REPORTS_TOOL_INVESTIGATION_DESCRIPTION
: CREATE_REPORTS_TOOL_STANDARD_DESCRIPTION;
// Create all functions with the context and state passed
const execute = createCreateReportsExecute(context, state);
const onInputStart = createReportsStart(context, state);
@ -92,7 +101,7 @@ export function createCreateReportsTool(context: CreateReportsContext) {
const onInputAvailable = createCreateReportsFinish(context, state);
return tool({
description: CREATE_REPORTS_TOOL_DESCRIPTION,
description,
inputSchema: CreateReportsInputSchema,
outputSchema: CreateReportsOutputSchema,
execute,

View File

@ -0,0 +1,11 @@
import { z } from 'zod';
/**
* Shared analysis mode schema used across the workflow, agents, and tools
*/
export const AnalysisModeSchema = z.enum(['standard', 'investigation']);
/**
* TypeScript type for analysis mode
*/
export type AnalysisMode = z.infer<typeof AnalysisModeSchema>;

View File

@ -74,7 +74,7 @@ export async function runAnalystWorkflow(
const userPersonalizationMessageContent =
generatePersonalizationMessageContent(userPersonalizationConfig);
const { todos, values, analysisType } = await runAnalystPrepSteps(input);
const { todos, values, analysisMode } = await runAnalystPrepSteps(input);
// Add all messages from extract-values step (tool call, result, and optional user message)
messages.push(...values.messages);
@ -93,7 +93,7 @@ export async function runAnalystWorkflow(
sql_dialect_guidance: input.dataSourceSyntax,
datasets: input.datasets,
workflowStartTime,
analysisMode: analysisType,
analysisMode,
analystInstructions,
organizationDocs,
userPersonalizationMessageContent,
@ -132,6 +132,7 @@ export async function runAnalystWorkflow(
userId: input.userId,
datasets: input.datasets,
workflowStartTime,
analysisMode,
analystInstructions,
organizationDocs,
userPersonalizationMessageContent,
@ -212,7 +213,7 @@ export async function runAnalystWorkflow(
endTime: workflowEndTime,
totalExecutionTimeMs: workflowEndTime - workflowStartTime,
analysisMode: analysisType === 'investigation' ? 'investigation' : 'standard',
analysisMode: analysisMode === 'investigation' ? 'investigation' : 'standard',
messages,
@ -265,10 +266,10 @@ async function runAnalystPrepSteps({
}: AnalystPrepStepInput): Promise<{
todos: CreateTodosResult;
values: ExtractValuesSearchResult;
analysisType: AnalysisTypeRouterResult['analysisType'];
analysisMode: AnalysisTypeRouterResult['analysisMode'];
}> {
const shouldInjectUserPersonalizationTodo = Boolean(userPersonalizationConfig);
const [todos, values, , analysisType] = await Promise.all([
const [todos, values, , analysisMode] = await Promise.all([
withStepRetry(
() =>
runCreateTodosStep({
@ -345,7 +346,7 @@ async function runAnalystPrepSteps({
),
]);
return { todos, values, analysisType: analysisType.analysisType };
return { todos, values, analysisMode: analysisMode.analysisMode };
}
function generatePersonalizationMessageContent(

View File

@ -7,6 +7,7 @@ import { CREATE_METRICS_TOOL_NAME } from '../../tools/visualization-tools/metric
import { MODIFY_METRICS_TOOL_NAME } from '../../tools/visualization-tools/metrics/modify-metrics-tool/modify-metrics-tool';
import { CREATE_REPORTS_TOOL_NAME } from '../../tools/visualization-tools/reports/create-reports-tool/create-reports-tool';
import { MODIFY_REPORTS_TOOL_NAME } from '../../tools/visualization-tools/reports/modify-reports-tool/modify-reports-tool';
import { type AnalysisMode, AnalysisModeSchema } from '../../types/analysis-mode.types';
// Tool call tracking
export const ToolCallInfoSchema = z.object({
@ -68,11 +69,6 @@ export const UserRequestSegmentSchema = z.object({
export type UserRequestSegment = z.infer<typeof UserRequestSegmentSchema>;
// Analysis mode from the analysis type router
export const AnalysisModeSchema = z.enum(['standard', 'investigation']);
export type AnalysisMode = z.infer<typeof AnalysisModeSchema>;
// Complete workflow output
export const AnalystWorkflowOutputSchema = z.object({
// Original workflow input data